Make the tooltip actions work properly by click and shortcut key, add a pretty mouseover animation to the tooltip actions, pass mouse events through to the proper widget when the tooltip is open.

This commit is contained in:
David Sansome 2011-09-18 23:07:57 +01:00
parent 7ec059dc13
commit b45c5a866e
6 changed files with 194 additions and 44 deletions

View File

@ -34,7 +34,7 @@ const qreal GlobalSearchTooltip::kArrowWidth = 10.0;
const qreal GlobalSearchTooltip::kArrowHeight = 10.0;
GlobalSearchTooltip::GlobalSearchTooltip(QObject* event_target)
GlobalSearchTooltip::GlobalSearchTooltip(QWidget* event_target)
: QWidget(NULL),
desktop_(qApp->desktop()),
event_target_(event_target)
@ -43,16 +43,6 @@ GlobalSearchTooltip::GlobalSearchTooltip(QObject* event_target)
setFocusPolicy(Qt::NoFocus);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_TranslucentBackground);
add_ = new QAction(tr("Add to playlist"), this);
add_and_play_ = new QAction(tr("Add and play now"), this);
add_and_queue_ = new QAction(tr("Queue track"), this);
replace_ = new QAction(tr("Replace current playlist"), this);
add_->setShortcut(QKeySequence(Qt::Key_Return));
add_and_play_->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
add_and_queue_->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Return));
replace_->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Return));
}
void GlobalSearchTooltip::SetResults(const SearchProvider::ResultList& results) {
@ -73,7 +63,7 @@ void GlobalSearchTooltip::SetResults(const SearchProvider::ResultList& results)
// Add the action widget
QList<QAction*> actions;
actions << add_ << add_and_play_ << add_and_queue_ << replace_;
actions.append(common_actions_);
action_widget_ = new TooltipActionWidget(this);
action_widget_->SetActions(actions);
@ -119,14 +109,42 @@ void GlobalSearchTooltip::ShowAt(const QPoint& pointing_to) {
show();
}
void GlobalSearchTooltip::keyPressEvent(QKeyEvent* e) {
// Copy the event to send to the target
QKeyEvent copy(e->type(), e->key(), e->modifiers(), e->text(),
e->isAutoRepeat(), e->count());
bool GlobalSearchTooltip::event(QEvent* e) {
switch (e->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::InputMethod:
case QEvent::Shortcut:
case QEvent::ShortcutOverride:
if (QApplication::sendEvent(event_target_, e)) {
return true;
}
break;
qApp->sendEvent(event_target_, &copy);
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
if (!underMouse()) {
QMouseEvent* me = static_cast<QMouseEvent*>(e);
QMouseEvent c(me->type(), event_target_->mapFromGlobal(me->globalPos()),
me->globalPos(), me->button(),
me->buttons(), me->modifiers());
e->accept();
QWidget* child = event_target_->childAt(c.pos());
if (child)
child->setAttribute(Qt::WA_UnderMouse, true);
QApplication::sendEvent(child ? child : event_target_, &c);
return true;
}
break;
default:
break;
}
return QWidget::event(e);
}
void GlobalSearchTooltip::paintEvent(QPaintEvent*) {

View File

@ -30,20 +30,22 @@ class GlobalSearchTooltip : public QWidget {
Q_OBJECT
public:
GlobalSearchTooltip(QObject* event_target);
GlobalSearchTooltip(QWidget* event_target);
static const qreal kBorderRadius;
static const qreal kBorderWidth;
static const qreal kArrowWidth;
static const qreal kArrowHeight;
void SetActions(const QList<QAction*>& actions) { common_actions_ = actions; }
void SetResults(const SearchProvider::ResultList& results);
void ShowAt(const QPoint& pointing_to);
qreal ArrowOffset() const;
bool event(QEvent* e);
protected:
void keyPressEvent(QKeyEvent* e);
void paintEvent(QPaintEvent*);
private:
@ -51,18 +53,14 @@ private:
private:
QDesktopWidget* desktop_;
QAction* add_;
QAction* add_and_play_;
QAction* add_and_queue_;
QAction* replace_;
TooltipActionWidget* action_widget_;
QList<QAction*> common_actions_;
SearchProvider::ResultList results_;
qreal arrow_offset_;
QRect inner_rect_;
QObject* event_target_;
QWidget* event_target_;
QWidgetList widgets_;
};

View File

@ -82,6 +82,24 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
ui_->search->installEventFilter(this);
// Actions
add_ = new QAction(tr("Add to playlist"), this);
add_and_play_ = new QAction(tr("Add and play now"), this);
add_and_queue_ = new QAction(tr("Queue track"), this);
replace_ = new QAction(tr("Replace current playlist"), this);
add_->setShortcut(QKeySequence(Qt::Key_Return));
add_and_play_->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
add_and_queue_->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Return));
replace_->setShortcut(QKeySequence(Qt::ALT | Qt::Key_Return));
connect(add_, SIGNAL(triggered()), SLOT(AddCurrent()));
connect(add_and_play_, SIGNAL(triggered()), SLOT(AddAndPlayCurrent()));
connect(add_and_queue_, SIGNAL(triggered()), SLOT(AddAndQueueCurrent()));
connect(replace_, SIGNAL(triggered()), SLOT(ReplaceCurrent()));
actions_ << add_ << add_and_play_ << add_and_queue_ << replace_;
// Load style sheets
StyleSheetLoader* style_loader = new StyleSheetLoader(this);
style_loader->SetStyleSheet(this, ":globalsearch.css");
@ -89,10 +107,13 @@ GlobalSearchWidget::GlobalSearchWidget(QWidget* parent)
connect(ui_->search, SIGNAL(textEdited(QString)), SLOT(TextEdited(QString)));
connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
SLOT(AddResults(int,SearchProvider::ResultList)));
connect(engine_, SIGNAL(SearchFinished(int)), SLOT(SearchFinished(int)));
connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)));
connect(engine_, SIGNAL(TracksLoaded(int,MimeData*)), SLOT(TracksLoaded(int,MimeData*)));
connect(view_, SIGNAL(doubleClicked(QModelIndex)), SLOT(AddCurrent()));
connect(engine_, SIGNAL(SearchFinished(int)), SLOT(SearchFinished(int)),
Qt::QueuedConnection);
connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)),
Qt::QueuedConnection);
connect(engine_, SIGNAL(TracksLoaded(int,MimeData*)), SLOT(TracksLoaded(int,MimeData*)),
Qt::QueuedConnection);
connect(view_, SIGNAL(doubleClicked(QModelIndex)), SLOT(ResultDoubleClicked()));
connect(view_->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
SLOT(UpdateTooltip()));
}
@ -358,9 +379,15 @@ bool GlobalSearchWidget::EventFilterPopup(QObject*, QEvent* e) {
switch (key) {
case Qt::Key_Return:
case Qt::Key_Enter:
case Qt::Key_Tab:
HidePopup();
AddCurrent();
// Handle the QActions here - they don't activate when the tooltip is showing
if (ke->modifiers() & Qt::AltModifier)
replace_->trigger();
else if (ke->modifiers() & Qt::ControlModifier)
add_and_play_->trigger();
else if (ke->modifiers() & Qt::ShiftModifier)
add_and_queue_->trigger();
else
add_->trigger();
break;
case Qt::Key_F4:
@ -427,7 +454,27 @@ void GlobalSearchWidget::ArtLoaded(int id, const QPixmap& pixmap) {
model_->itemFromIndex(index)->setData(pixmap, Qt::DecorationRole);
}
void GlobalSearchWidget::ResultDoubleClicked() {
LoadTracks(NULL);
}
void GlobalSearchWidget::AddCurrent() {
LoadTracks(add_);
}
void GlobalSearchWidget::AddAndPlayCurrent() {
LoadTracks(add_and_play_);
}
void GlobalSearchWidget::AddAndQueueCurrent() {
LoadTracks(add_and_queue_);
}
void GlobalSearchWidget::ReplaceCurrent() {
LoadTracks(replace_);
}
void GlobalSearchWidget::LoadTracks(QAction* trigger) {
QModelIndex index = view_->currentIndex();
if (!index.isValid())
index = proxy_->index(0, 0);
@ -435,16 +482,31 @@ void GlobalSearchWidget::AddCurrent() {
if (!index.isValid())
return;
engine_->LoadTracksAsync(index.data(Role_PrimaryResult).value<SearchProvider::Result>());
int id = engine_->LoadTracksAsync(
index.data(Role_PrimaryResult).value<SearchProvider::Result>());
track_requests_[id] = trigger;
}
void GlobalSearchWidget::TracksLoaded(int id, MimeData* mime_data) {
Q_UNUSED(id);
if (!track_requests_.contains(id))
return;
QAction* trigger = track_requests_.take(id);
if (!mime_data)
return;
mime_data->from_doubleclick_ = true;
if (trigger == NULL) {
mime_data->from_doubleclick_ = true;
} else if (trigger == add_) {
} else if (trigger == add_and_play_) {
mime_data->play_now_ = true;
} else if (trigger == add_and_queue_) {
mime_data->enqueue_now_ = true;
} else if (trigger == replace_) {
mime_data->clear_first_= true;
}
emit AddToPlaylist(mime_data);
}
@ -528,6 +590,7 @@ void GlobalSearchWidget::UpdateTooltip() {
tooltip_.reset(new GlobalSearchTooltip(view_));
tooltip_->setFont(view_->font());
tooltip_->setPalette(view_->palette());
tooltip_->SetActions(actions_);
}
const QRect item_rect = view_->visualRect(current);

View File

@ -81,7 +81,11 @@ private slots:
void TracksLoaded(int id, MimeData* mime_data);
void ResultDoubleClicked();
void AddCurrent();
void AddAndPlayCurrent();
void AddAndQueueCurrent();
void ReplaceCurrent();
void HidePopup();
void UpdateTooltip();
@ -102,6 +106,8 @@ private:
bool EventFilterSearchWidget(QObject* o, QEvent* e);
bool EventFilterPopup(QObject* o, QEvent* e);
void LoadTracks(QAction* trigger);
private:
Ui_GlobalSearchWidget* ui_;
@ -110,6 +116,7 @@ private:
bool clear_model_on_next_result_;
QMap<int, QModelIndex> art_requests_;
QMap<int, QAction*> track_requests_;
QStandardItemModel* model_;
QSortFilterProxyModel* proxy_;
@ -125,6 +132,12 @@ private:
QStringList provider_order_;
QScopedPointer<GlobalSearchTooltip> tooltip_;
QAction* add_;
QAction* add_and_play_;
QAction* add_and_queue_;
QAction* replace_;
QList<QAction*> actions_;
};
#endif // GLOBALSEARCHWIDGET_H

View File

@ -19,10 +19,13 @@
#include "core/logging.h"
#include <QAction>
#include <QMouseEvent>
#include <QPainter>
const int TooltipActionWidget::kBorder = 16;
const int TooltipActionWidget::kSpacing = 6;
const int TooltipActionWidget::kTopPadding = 3;
const int TooltipActionWidget::kFadeDurationMsec = 200;
TooltipActionWidget::TooltipActionWidget(QWidget* parent)
: QWidget(parent),
@ -30,12 +33,14 @@ TooltipActionWidget::TooltipActionWidget(QWidget* parent)
shortcut_width_(0),
description_width_(0)
{
setMouseTracking(true);
}
void TooltipActionWidget::SetActions(QList<QAction*> actions) {
actions_ = actions;
action_opacities_.clear();
int h = 3 + kTextHeight * actions.count();
int h = kTopPadding + kTextHeight * actions.count();
shortcut_width_ = 0;
description_width_ = 0;
@ -45,6 +50,10 @@ void TooltipActionWidget::SetActions(QList<QAction*> actions) {
fontMetrics().width(action->shortcut().toString(QKeySequence::NativeText)));
description_width_ =
qMax(description_width_, fontMetrics().width(action->text()));
QTimeLine* timeline = new QTimeLine(kFadeDurationMsec, this);
connect(timeline, SIGNAL(valueChanged(qreal)), SLOT(update()));
action_opacities_ << timeline;
}
size_hint_ = QSize(
@ -55,19 +64,22 @@ void TooltipActionWidget::SetActions(QList<QAction*> actions) {
}
void TooltipActionWidget::paintEvent(QPaintEvent*) {
int y = 3;
const qreal shortcut_opacity = 0.4;
const qreal description_opacity = 0.7;
int y = kTopPadding;
QPainter p(this);
p.setPen(palette().color(QPalette::Text));
foreach (const QAction* action, actions_) {
for (int i=0 ; i<actions_.count() ; ++i) {
const QAction* action = actions_[i];
const QTimeLine* timeline = action_opacities_[i];
const QRect shortcut_rect(kBorder, y, shortcut_width_, kTextHeight);
const QRect description_rect(shortcut_rect.right() + kSpacing, y,
description_width_, kTextHeight);
const qreal shortcut_opacity = 0.4 + 0.3 * timeline->currentValue();
const qreal description_opacity = 0.7 + 0.3 * timeline->currentValue();
p.setOpacity(shortcut_opacity);
p.drawText(shortcut_rect, Qt::AlignRight | Qt::AlignVCenter,
action->shortcut().toString(QKeySequence::NativeText));
@ -79,6 +91,40 @@ void TooltipActionWidget::paintEvent(QPaintEvent*) {
}
}
void TooltipActionWidget::mousePressEvent(QMouseEvent* e) {
int TooltipActionWidget::ActionAt(const QPoint& pos) const {
return (pos.y() - kTopPadding) / kTextHeight;
}
void TooltipActionWidget::mouseMoveEvent(QMouseEvent* e) {
const int action = ActionAt(e->pos());
for (int i=0 ; i<actions_.count() ; ++i) {
if (i == action) {
StartAnimation(i, QTimeLine::Forward);
} else {
StartAnimation(i, QTimeLine::Backward);
}
}
}
void TooltipActionWidget::leaveEvent(QEvent* e) {
for (int i=0 ; i<actions_.count() ; ++i) {
StartAnimation(i, QTimeLine::Backward);
}
}
void TooltipActionWidget::StartAnimation(int i, QTimeLine::Direction direction) {
QTimeLine* timeline = action_opacities_[i];
if (timeline->direction() != direction) {
timeline->setDirection(direction);
if (timeline->state() != QTimeLine::Running)
timeline->resume();
}
}
void TooltipActionWidget::mousePressEvent(QMouseEvent* e) {
const int action = ActionAt(e->pos());
if (action >= 0 && action < actions_.count()) {
actions_[action]->trigger();
}
}

View File

@ -18,8 +18,10 @@
#ifndef TOOLTIPACTIONWIDGET_H
#define TOOLTIPACTIONWIDGET_H
#include <QTimeLine>
#include <QWidget>
class TooltipActionWidget : public QWidget {
Q_OBJECT
@ -28,6 +30,8 @@ public:
static const int kBorder;
static const int kSpacing;
static const int kTopPadding;
static const int kFadeDurationMsec;
void SetActions(QList<QAction*> actions);
@ -35,12 +39,20 @@ public:
protected:
void paintEvent(QPaintEvent*);
void mouseMoveEvent(QMouseEvent* e);
void leaveEvent(QEvent* e);
void mousePressEvent(QMouseEvent* e);
private:
int ActionAt(const QPoint& pos) const;
void StartAnimation(int i, QTimeLine::Direction direction);
private:
const int kTextHeight;
QList<QAction*> actions_;
QList<QTimeLine*> action_opacities_;
QSize size_hint_;
int shortcut_width_;
int description_width_;