From a61ded973ee5cb817475478bdf69d497e6a2cb78 Mon Sep 17 00:00:00 2001 From: Flavio Date: Sat, 3 Dec 2011 14:49:23 +0100 Subject: [PATCH] Lots of changes, sorry I'm doing a bad work with git lately... --- minitube.pro | 16 +- src/AboutView.h | 9 +- src/ListModel.cpp | 62 +++++++ src/ListModel.h | 17 +- src/MainWindow.cpp | 198 +++++++++++++++++++---- src/MainWindow.h | 15 +- src/MediaView.cpp | 48 +++++- src/MediaView.h | 14 +- src/SearchView.cpp | 38 +++-- src/SearchView.h | 12 +- src/autocomplete.cpp | 29 ++-- src/autocomplete.h | 4 + src/downloaditem.cpp | 8 + src/downloadlistview.cpp | 12 +- src/downloadlistview.h | 3 +- src/downloadview.cpp | 4 +- src/downloadview.h | 4 +- src/main.cpp | 5 +- src/playlist/PrettyItemDelegate.cpp | 25 ++- src/playlist/PrettyItemDelegate.h | 2 + src/playlistview.cpp | 76 +++++++++ src/playlistview.h | 28 ++++ src/playlistwidget.cpp | 2 +- src/playlistwidget.h | 4 +- src/searchlineedit.h | 2 +- src/segmentedcontrol.cpp | 243 ++++++++++++++++++++++++++++ src/segmentedcontrol.h | 49 ++++++ 27 files changed, 809 insertions(+), 120 deletions(-) create mode 100644 src/playlistview.cpp create mode 100644 src/playlistview.h create mode 100644 src/segmentedcontrol.cpp create mode 100644 src/segmentedcontrol.h diff --git a/minitube.pro b/minitube.pro index 75dda4e..89d67fb 100644 --- a/minitube.pro +++ b/minitube.pro @@ -1,6 +1,6 @@ CONFIG += release TEMPLATE = app -VERSION = 1.6 +VERSION = 1.7 DEFINES += APP_VERSION="$$VERSION" INCLUDEPATH += /usr/include/phonon @@ -48,7 +48,6 @@ HEADERS += src/MainWindow.h \ src/videowidget.h \ src/videodefinition.h \ src/fontutils.h \ - src/thlibrary/thblackbar.h \ src/globalshortcuts.h \ src/globalshortcutbackend.h \ src/downloadmanager.h \ @@ -60,7 +59,9 @@ HEADERS += src/MainWindow.h \ src/youtubesuggest.h \ src/suggester.h \ src/channelsuggest.h \ - src/temporary.h + src/temporary.h \ + src/segmentedcontrol.h \ + src/playlistview.h SOURCES += src/main.cpp \ src/MainWindow.cpp \ src/SearchView.cpp \ @@ -88,7 +89,6 @@ SOURCES += src/main.cpp \ src/videodefinition.cpp \ src/constants.cpp \ src/fontutils.cpp \ - src/thlibrary/thblackbar.cpp \ src/globalshortcuts.cpp \ src/globalshortcutbackend.cpp \ src/downloadmanager.cpp \ @@ -99,7 +99,9 @@ SOURCES += src/main.cpp \ src/downloadsettings.cpp \ src/youtubesuggest.cpp \ src/channelsuggest.cpp \ - src/temporary.cpp + src/temporary.cpp \ + src/segmentedcontrol.cpp \ + src/playlistview.cpp RESOURCES += resources.qrc DESTDIR = build/target/ OBJECTS_DIR = build/obj/ @@ -162,3 +164,7 @@ unix:!mac { } mac|win32:include(local/local.pri) + + + + diff --git a/src/AboutView.h b/src/AboutView.h index 9d6a443..2d3e0c4 100644 --- a/src/AboutView.h +++ b/src/AboutView.h @@ -4,6 +4,9 @@ #include #include "View.h" #include "constants.h" +#ifdef APP_MAC +#include "macutils.h" +#endif class AboutView : public QWidget, public View { @@ -11,7 +14,11 @@ class AboutView : public QWidget, public View { public: AboutView(QWidget *parent); - void appear() {} + void appear() { +#ifdef APP_MAC + mac::uncloseWindow(window()->winId()); +#endif + } void disappear() {} QMap metadata() { QMap metadata; diff --git a/src/ListModel.cpp b/src/ListModel.cpp index 644e512..2662e14 100644 --- a/src/ListModel.cpp +++ b/src/ListModel.cpp @@ -12,6 +12,10 @@ ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) { m_activeVideo = 0; m_activeRow = -1; skip = 1; + + hoveredRow = -1; + authorHovered = false; + authorPressed = false; } ListModel::~ListModel() { @@ -80,6 +84,12 @@ QVariant ListModel::data(const QModelIndex &index, int role) const { return video == m_activeVideo; case Qt::DisplayRole: return video->title(); + case HoveredItemRole: + return hoveredRow == index.row(); + case AuthorHoveredRole: + return authorHovered; + case AuthorPressedRole: + return authorPressed; } return QVariant(); @@ -113,6 +123,13 @@ int ListModel::nextRow() const { return -1; } +int ListModel::previousRow() const { + int prevRow = m_activeRow - 1; + if (rowExists(prevRow)) + return prevRow; + return -1; +} + Video* ListModel::videoAt( int row ) const { if ( rowExists( row ) ) return videos.at( row ); @@ -404,3 +421,48 @@ void ListModel::move(QModelIndexList &indexes, bool up) { emit needSelectionFor(movedVideos); } + +/* row hovering */ + +void ListModel::setHoveredRow(int row) { + int oldRow = hoveredRow; + hoveredRow = row; + emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) ); + emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) ); +} + +void ListModel::clearHover() { + emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) ); + hoveredRow = -1; +} + +/* clickable author */ + +void ListModel::enterAuthorHover() { + if (authorHovered) return; + authorHovered = true; + updateAuthor(); +} + +void ListModel::exitAuthorHover() { + if (!authorHovered) return; + authorHovered = false; + updateAuthor(); + setHoveredRow(hoveredRow); +} + +void ListModel::enterAuthorPressed() { + if (authorPressed) return; + authorPressed = true; + updateAuthor(); +} + +void ListModel::exitAuthorPressed() { + if (!authorPressed) return; + authorPressed = false; + updateAuthor(); +} + +void ListModel::updateAuthor() { + emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) ); +} diff --git a/src/ListModel.h b/src/ListModel.h index c84b605..2752aab 100644 --- a/src/ListModel.h +++ b/src/ListModel.h @@ -12,7 +12,9 @@ enum DataRoles { DownloadItemRole, HoveredItemRole, DownloadButtonHoveredRole, - DownloadButtonPressedRole + DownloadButtonPressedRole, + AuthorHoveredRole, + AuthorPressedRole }; enum ItemTypes { @@ -49,6 +51,7 @@ public: bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); } int activeRow() const { return m_activeRow; } // returns -1 if there is no active row int nextRow() const; + int previousRow() const; void removeIndexes(QModelIndexList &indexes); int rowForVideo(Video* video); QModelIndex indexForVideo(Video* video); @@ -70,6 +73,14 @@ public slots: void searchError(QString message); void updateThumbnail(); + void setHoveredRow(int row); + void clearHover(); + void enterAuthorHover(); + void exitAuthorHover(); + void enterAuthorPressed(); + void exitAuthorPressed(); + void updateAuthor(); + signals: void activeRowChanged(int); void needSelectionFor(QList); @@ -90,6 +101,10 @@ private: Video *m_activeVideo; QString errorMessage; + + int hoveredRow; + bool authorHovered; + bool authorPressed; }; #endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 137ee6f..b7c9b1b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -13,6 +13,7 @@ #include "mac_startup.h" #include "macfullscreen.h" #include "macsupport.h" +#include "macutils.h" #endif #ifndef Q_WS_X11 #include "extra.h" @@ -24,6 +25,18 @@ #include "demostartupview.h" #endif #include "temporary.h" +#ifdef APP_MAC +#include "searchlineedit_mac.h" +#else +#include "searchlineedit.h" +#endif + +static MainWindow *singleton = 0; + +MainWindow* MainWindow::instance() { + if (!singleton) singleton = new MainWindow(); + return singleton; +} MainWindow::MainWindow() : aboutView(0), @@ -33,6 +46,8 @@ MainWindow::MainWindow() : m_fullscreen(false), updateChecker(0) { + singleton = this; + // views mechanism history = new QStack(); views = new QStackedWidget(this); @@ -46,11 +61,6 @@ MainWindow::MainWindow() : mediaView = new MediaView(this); views->addWidget(mediaView); - toolbarSearch = new SearchLineEdit(this); - toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize()*15); - toolbarSearch->setSuggester(new YouTubeSuggest(this)); - connect(toolbarSearch, SIGNAL(search(const QString&)), this, SLOT(startToolbarSearch(const QString&))); - // build ui createActions(); createMenus(); @@ -94,6 +104,7 @@ MainWindow::MainWindow() : connect(&shortcuts, SIGNAL(PlayPause()), pauseAct, SLOT(trigger())); connect(&shortcuts, SIGNAL(Stop()), this, SLOT(stop())); connect(&shortcuts, SIGNAL(Next()), skipAct, SLOT(trigger())); + connect(&shortcuts, SIGNAL(Previous()), skipBackwardAct, SLOT(trigger())); connect(DownloadManager::instance(), SIGNAL(statusMessageChanged(QString)), SLOT(updateDownloadMessage(QString))); @@ -110,6 +121,15 @@ MainWindow::~MainWindow() { delete history; } +void MainWindow::changeEvent(QEvent* event) { +#ifdef APP_MAC + if (event->type() == QEvent::WindowStateChange) { + The::globalActions()->value("minimize")->setEnabled(!isMinimized()); + } +#endif + QMainWindow::changeEvent(event); +} + bool MainWindow::eventFilter(QObject *obj, QEvent *event) { #ifdef Q_WS_X11 if (event->type() == QEvent::MouseMove && this->m_fullscreen) { @@ -143,6 +163,18 @@ void MainWindow::createActions() { actions->insert("stop", stopAct); connect(stopAct, SIGNAL(triggered()), this, SLOT(stop())); + skipBackwardAct = new QAction( + QtIconLoader::icon("media-skip-backward"), + tr("P&revious"), this); + skipBackwardAct->setStatusTip(tr("Go back to the previous track")); + skipBackwardAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left)); +#if QT_VERSION >= 0x040600 + skipBackwardAct->setPriority(QAction::LowPriority); +#endif + skipBackwardAct->setEnabled(false); + actions->insert("previous", skipBackwardAct); + connect(skipBackwardAct, SIGNAL(triggered()), mediaView, SLOT(skipBackward())); + skipAct = new QAction(QtIconLoader::icon("media-skip-forward"), tr("S&kip"), this); skipAct->setStatusTip(tr("Skip to the next video")); skipAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Right) << QKeySequence(Qt::Key_MediaNext)); @@ -163,7 +195,7 @@ void MainWindow::createActions() { #ifdef APP_MAC fsShortcuts << QKeySequence(Qt::CTRL + Qt::META + Qt::Key_F); #else - fsShortcuts << QKeySequence(Qt::Key_F11); + fsShortcuts << QKeySequence(Qt::Key_F11) << QKeySequence(Qt::ALT + Qt::Key_Return); #endif fullscreenAct->setShortcuts(fsShortcuts); fullscreenAct->setShortcutContext(Qt::ApplicationShortcut); @@ -247,10 +279,10 @@ void MainWindow::createActions() { quitAct = new QAction(tr("&Quit"), this); quitAct->setMenuRole(QAction::QuitRole); - quitAct->setShortcuts(QList() << QKeySequence(tr("Ctrl+Q")) << QKeySequence(Qt::CTRL + Qt::Key_W)); + quitAct->setShortcut(QKeySequence::Quit); quitAct->setStatusTip(tr("Bye")); actions->insert("quit", quitAct); - connect(quitAct, SIGNAL(triggered()), this, SLOT(close())); + connect(quitAct, SIGNAL(triggered()), SLOT(quit())); siteAct = new QAction(tr("&Website"), this); siteAct->setShortcut(QKeySequence::HelpContents); @@ -281,13 +313,13 @@ void MainWindow::createActions() { addAction(searchFocusAct); volumeUpAct = new QAction(this); - volumeUpAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Plus) << QKeySequence(Qt::Key_VolumeUp)); + volumeUpAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Plus)); actions->insert("volume-up", volumeUpAct); connect(volumeUpAct, SIGNAL(triggered()), this, SLOT(volumeUp())); addAction(volumeUpAct); volumeDownAct = new QAction(this); - volumeDownAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Minus) << QKeySequence(Qt::Key_VolumeDown)); + volumeDownAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_Minus)); actions->insert("volume-down", volumeDownAct); connect(volumeDownAct, SIGNAL(triggered()), this, SLOT(volumeDown())); addAction(volumeDownAct); @@ -295,9 +327,7 @@ void MainWindow::createActions() { volumeMuteAct = new QAction(this); volumeMuteAct->setIcon(QtIconLoader::icon("audio-volume-high")); volumeMuteAct->setStatusTip(tr("Mute volume")); - volumeMuteAct->setShortcuts(QList() - << QKeySequence(tr("Ctrl+M")) - << QKeySequence(Qt::Key_VolumeMute)); + volumeMuteAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_E)); actions->insert("volume-mute", volumeMuteAct); connect(volumeMuteAct, SIGNAL(triggered()), SLOT(volumeMute())); addAction(volumeMuteAct); @@ -366,6 +396,23 @@ void MainWindow::createActions() { actions->insert("email", action); connect(action, SIGNAL(triggered()), mediaView, SLOT(shareViaEmail())); + action = new QAction(tr("&Close"), this); + action->setShortcut(QKeySequence(QKeySequence::Close)); + actions->insert("close", action); + connect(action, SIGNAL(triggered()), SLOT(close())); + + action = new QAction(QtIconLoader::icon("go-top"), tr("&Float on Top"), this); + action->setCheckable(true); + actions->insert("ontop", action); + connect(action, SIGNAL(toggled(bool)), SLOT(floatOnTop(bool))); + + action = new QAction(QtIconLoader::icon("media-playback-stop"), tr("&Stop after this video"), this); + action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Escape)); + action->setCheckable(true); + action->setEnabled(false); + actions->insert("stopafterthis", action); + connect(action, SIGNAL(toggled(bool)), SLOT(showStopAfterThisInStatusBar(bool))); + // common action properties foreach (QAction *action, actions->values()) { @@ -378,7 +425,6 @@ void MainWindow::createActions() { action->setAutoRepeat(false); // set to something more meaningful then the toolbar text - // HELP! how to remove tooltips altogether?! if (!action->statusTip().isEmpty()) action->setToolTip(action->statusTip()); @@ -398,7 +444,12 @@ void MainWindow::createMenus() { QMap *menus = The::globalMenus(); fileMenu = menuBar()->addMenu(tr("&Application")); - // menus->insert("file", fileMenu); +#ifdef APP_DEMO + QAction* action = new QAction(tr("Buy %1...").arg(Constants::NAME), this); + action->setMenuRole(QAction::ApplicationSpecificRole); + connect(action, SIGNAL(triggered()), SLOT(buy())); + fileMenu->addAction(action); +#endif fileMenu->addAction(clearAct); #ifndef APP_MAC fileMenu->addSeparator(); @@ -407,9 +458,12 @@ void MainWindow::createMenus() { QMenu* playbackMenu = menuBar()->addMenu(tr("&Playback")); menus->insert("playback", playbackMenu); - playbackMenu->addAction(stopAct); playbackMenu->addAction(pauseAct); + playbackMenu->addAction(stopAct); + playbackMenu->addAction(The::globalActions()->value("stopafterthis")); + playbackMenu->addSeparator(); playbackMenu->addAction(skipAct); + playbackMenu->addAction(skipBackwardAct); #ifdef APP_MAC MacSupport::dockMenu(playbackMenu); #endif @@ -436,6 +490,8 @@ void MainWindow::createMenus() { menus->insert("view", viewMenu); viewMenu->addAction(fullscreenAct); viewMenu->addAction(compactViewAct); + viewMenu->addSeparator(); + viewMenu->addAction(The::globalActions()->value("ontop")); QMenu* shareMenu = menuBar()->addMenu(tr("&Share")); menus->insert("share", shareMenu); @@ -445,6 +501,10 @@ void MainWindow::createMenus() { shareMenu->addAction(The::globalActions()->value("facebook")); shareMenu->addAction(The::globalActions()->value("email")); +#ifdef APP_MAC + MacSupport::windowMenu(this); +#endif + helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(siteAct); #if !defined(APP_MAC) && !defined(APP_WIN) @@ -530,16 +590,32 @@ void MainWindow::createToolBars() { mainToolBar->addWidget(new Spacer()); +#ifdef APP_MAC + SearchWrapper* searchWrapper = new SearchWrapper(this); + toolbarSearch = searchWrapper->getSearchLineEdit(); +#else + toolbarSearch = new SearchLineEdit(this); +#endif + toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize()*15); + toolbarSearch->setSuggester(new YouTubeSuggest(this)); + connect(toolbarSearch, SIGNAL(search(const QString&)), this, SLOT(startToolbarSearch(const QString&))); + connect(toolbarSearch, SIGNAL(suggestionAccepted(const QString&)), SLOT(startToolbarSearch(const QString&))); toolbarSearch->setStatusTip(searchFocusAct->statusTip()); +#ifdef APP_MAC + mainToolBar->addWidget(searchWrapper); +#else mainToolBar->addWidget(toolbarSearch); - - mainToolBar->addWidget(new Spacer()); + Spacer* spacer = new Spacer(); + spacer->setWidth(4); + mainToolBar->addWidget(spacer); +#endif addToolBar(mainToolBar); } void MainWindow::createStatusBar() { - QToolBar *toolBar = new QToolBar(this); + QToolBar* toolBar = new QToolBar(this); + statusToolBar = toolBar; toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar->setIconSize(QSize(16, 16)); toolBar->addAction(The::globalActions()->value("downloads")); @@ -549,6 +625,24 @@ void MainWindow::createStatusBar() { statusBar()->show(); } +void MainWindow::showStopAfterThisInStatusBar(bool show) { + QAction* action = The::globalActions()->value("stopafterthis"); + if (show) { + statusToolBar->insertAction(statusToolBar->actions().first(), action); + } else { + statusToolBar->removeAction(action); + } +} + +void MainWindow::showFloatOnTopInStatusBar(bool show) { + QAction* action = The::globalActions()->value("ontop"); + if (show) { + statusToolBar->insertAction(statusToolBar->actions().first(), action); + } else { + statusToolBar->removeAction(action); + } +} + void MainWindow::readSettings() { QSettings settings; restoreGeometry(settings.value("geometry").toByteArray()); @@ -612,12 +706,18 @@ void MainWindow::showWidget ( QWidget* widget ) { findVideoPartsAct->setEnabled(widget == mediaView); toolbarSearch->setEnabled(widget == searchView || widget == mediaView || widget == downloadView); + if (widget == searchView) { + skipAct->setEnabled(false); + The::globalActions()->value("previous")->setEnabled(false); + The::globalActions()->value("download")->setEnabled(false); + The::globalActions()->value("stopafterthis")->setEnabled(false); + } + The::globalActions()->value("twitter")->setEnabled(widget == mediaView); The::globalActions()->value("facebook")->setEnabled(widget == mediaView); The::globalActions()->value("email")->setEnabled(widget == mediaView); aboutAct->setEnabled(widget != aboutView); - The::globalActions()->value("download")->setEnabled(widget == mediaView); The::globalActions()->value("downloads")->setChecked(widget == downloadView); // toolbar only for the mediaView @@ -659,18 +759,39 @@ void MainWindow::donate() { } void MainWindow::quit() { +#ifdef APP_MAC + if (!confirmQuit()) { + return; + } +#endif writeSettings(); Temporary::deleteAll(); qApp->quit(); } void MainWindow::closeEvent(QCloseEvent *event) { +#ifdef APP_MAC + mac::closeWindow(winId()); + event->ignore(); +#else + if (!confirmQuit()) { + event->ignore(); + return; + } + quit(); + QWidget::closeEvent(event); +#endif +} + +bool MainWindow::confirmQuit() { if (DownloadManager::instance()->activeItems() > 0) { - QMessageBox msgBox; + QMessageBox msgBox(this); msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); msgBox.setText(tr("Do you want to exit %1 with a download in progress?").arg(Constants::NAME)); msgBox.setInformativeText(tr("If you close %1 now, this download will be cancelled.").arg(Constants::NAME)); msgBox.setModal(true); + // make it a "sheet" on the Mac + msgBox.setWindowModality(Qt::WindowModal); msgBox.addButton(tr("Close and cancel download"), QMessageBox::RejectRole); QPushButton *waitButton = msgBox.addButton(tr("Wait for download to finish"), QMessageBox::ActionRole); @@ -678,13 +799,10 @@ void MainWindow::closeEvent(QCloseEvent *event) { msgBox.exec(); if (msgBox.clickedButton() == waitButton) { - event->ignore(); - return; + return false; } - } - quit(); - QWidget::closeEvent(event); + return true; } void MainWindow::showSearch() { @@ -694,9 +812,6 @@ void MainWindow::showSearch() { } void MainWindow::showMedia(SearchParams *searchParams) { - if (toolbarSearch->text().isEmpty() && !searchParams->keywords().isEmpty()) { - toolbarSearch->lineEdit()->setText(searchParams->keywords()); - } mediaView->search(searchParams); showWidget(mediaView); } @@ -722,18 +837,15 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState pauseAct->setIcon(QtIconLoader::icon("media-playback-pause")); pauseAct->setText(tr("&Pause")); pauseAct->setStatusTip(tr("Pause playback") + " (" + pauseAct->shortcut().toString(QKeySequence::NativeText) + ")"); - skipAct->setEnabled(true); // stopAct->setEnabled(true); break; case Phonon::StoppedState: pauseAct->setEnabled(false); - skipAct->setEnabled(false); // stopAct->setEnabled(false); break; case Phonon::PausedState: - skipAct->setEnabled(true); pauseAct->setEnabled(true); pauseAct->setIcon(QtIconLoader::icon("media-playback-start")); pauseAct->setText(tr("&Play")); @@ -743,7 +855,6 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState case Phonon::BufferingState: case Phonon::LoadingState: - skipAct->setEnabled(true); pauseAct->setEnabled(false); currentTime->clear(); totalTime->clear(); @@ -1205,3 +1316,24 @@ void MainWindow::gotNewVersion(QString version) { } } + +void MainWindow::floatOnTop(bool onTop) { + showFloatOnTopInStatusBar(onTop); +#ifdef APP_MAC + mac::floatOnTop(winId(), onTop); + return; +#endif + if (onTop) { + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + } else { + setWindowFlags(windowFlags() ^ Qt::WindowStaysOnTopHint); + } +} + +void MainWindow::messageReceived(const QString &message) { + if (!message.isEmpty()) { + SearchParams *searchParams = new SearchParams(); + searchParams->setKeywords(message); + showMedia(searchParams); + } +} diff --git a/src/MainWindow.h b/src/MainWindow.h index b0c71b0..776afb0 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -2,7 +2,6 @@ #define MAINWINDOW_H #include -#include "searchlineedit.h" #include #include #include @@ -13,6 +12,7 @@ #include "AboutView.h" #include "downloadview.h" +class SearchLineEdit; class UpdateChecker; class MainWindow : public QMainWindow { @@ -20,14 +20,19 @@ class MainWindow : public QMainWindow { Q_OBJECT public: + static MainWindow* instance(); MainWindow(); ~MainWindow(); Phonon::SeekSlider* getSeekSlider() { return seekSlider; } + void readSettings(); + void writeSettings(); public slots: void showMedia(SearchParams *params); + void messageReceived(const QString &message); protected: + void changeEvent(QEvent *); void closeEvent(QCloseEvent *); bool eventFilter(QObject *obj, QEvent *event); void dragEnterEvent(QDragEnterEvent *event); @@ -72,6 +77,9 @@ private slots: void toggleDownloads(bool show); void startToolbarSearch(QString query); + void floatOnTop(bool); + void showFloatOnTopInStatusBar(bool show); + void showStopAfterThisInStatusBar(bool show); private: void initPhonon(); @@ -79,10 +87,9 @@ private: void createMenus(); void createToolBars(); void createStatusBar(); - void readSettings(); - void writeSettings(); void showWidget(QWidget*); static QString formatTime(qint64 time); + bool confirmQuit(); UpdateChecker *updateChecker; @@ -106,6 +113,7 @@ private: QAction *searchFocusAct; // media actions + QAction *skipBackwardAct; QAction *skipAct; QAction *pauseAct; QAction *stopAct; @@ -135,6 +143,7 @@ private: // toolbar QToolBar *mainToolBar; SearchLineEdit *toolbarSearch; + QToolBar *statusToolBar; // phonon Phonon::SeekSlider *seekSlider; diff --git a/src/MediaView.cpp b/src/MediaView.cpp index 324adfc..1edc22b 100644 --- a/src/MediaView.cpp +++ b/src/MediaView.cpp @@ -1,4 +1,5 @@ #include "MediaView.h" +#include "playlistview.h" #include "playlist/PrettyItemDelegate.h" #include "networkaccess.h" #include "videowidget.h" @@ -30,7 +31,7 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { splitter = new MiniSplitter(this); splitter->setChildrenCollapsible(false); - sortBar = new THBlackBar(this); + sortBar = new SegmentedControl(this); mostRelevantAction = new QAction(tr("Most relevant"), this); QKeySequence keySequence(Qt::CTRL + Qt::Key_1); mostRelevantAction->setShortcut(keySequence); @@ -53,7 +54,7 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection); sortBar->addAction(mostViewedAction); - listView = new QListView(this); + listView = new PlaylistView(this); listView->setItemDelegate(new PrettyItemDelegate(this)); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -83,6 +84,8 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )), this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & ))); + connect(listView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex))); + playlistWidget = new PlaylistWidget(this, sortBar, listView); splitter->addWidget(playlistWidget); @@ -197,6 +200,10 @@ void MediaView::search(SearchParams *searchParams) { } +void MediaView::appear() { + listView->setFocus(); +} + void MediaView::disappear() { timerPlayFlag = true; } @@ -259,8 +266,6 @@ void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) qDebug("loading"); break; - default: - ; } } @@ -332,7 +337,6 @@ void MediaView::activeRowChanged(int row) { QMainWindow* mainWindow = dynamic_cast(window()); if (mainWindow) mainWindow->statusBar()->showMessage(video->title()); - The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0); // ensure active item is visible // int row = listModel->activeRow(); @@ -341,6 +345,12 @@ void MediaView::activeRowChanged(int row) { listView->scrollTo(index, QAbstractItemView::EnsureVisible); } + // enable/disable actions + The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0); + The::globalActions()->value("skip")->setEnabled(true); + The::globalActions()->value("previous")->setEnabled(row > 0); + The::globalActions()->value("stopafterthis")->setEnabled(true); + // see you in gotStreamUrl... } @@ -483,13 +493,24 @@ void MediaView::skip() { listModel->setActiveRow(nextRow); } +void MediaView::skipBackward() { + int prevRow = listModel->previousRow(); + if (prevRow == -1) return; + listModel->setActiveRow(prevRow); +} + void MediaView::playbackFinished() { // qDebug() << "finished" << mediaObject->currentTime() << mediaObject->totalTime(); // add 10 secs for imprecise Phonon backends (VLC, Xine) if (mediaObject->currentTime() + 10000 < mediaObject->totalTime()) { // mediaObject->seek(mediaObject->currentTime()); QTimer::singleShot(3000, this, SLOT(playbackResume())); - } else skip(); + } else { + QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis"); + if (stopAfterThisAction->isChecked()) { + stopAfterThisAction->setChecked(false); + } else skip(); + } } void MediaView::playbackResume() { @@ -815,3 +836,18 @@ void MediaView::shareViaEmail() { url.addQueryItem("body", body); QDesktopServices::openUrl(url); } + +void MediaView::authorPushed(QModelIndex index) { + Video* video = listModel->videoAt(index.row()); + if (!video) return; + + QString channel = video->author(); + if (channel.isEmpty()) return; + + SearchParams *searchParams = new SearchParams(); + searchParams->setAuthor(channel); + searchParams->setSortBy(SearchParams::SortByNewest); + + // go! + search(searchParams); +} diff --git a/src/MediaView.h b/src/MediaView.h index acfa6a6..c6deee1 100644 --- a/src/MediaView.h +++ b/src/MediaView.h @@ -7,13 +7,14 @@ #include #include "View.h" #include "ListModel.h" -#include "thlibrary/thblackbar.h" +#include "segmentedcontrol.h" #include "searchparams.h" #include "playlistwidget.h" #include "loadingwidget.h" #include "videoareawidget.h" class DownloadItem; +class PlaylistView; namespace The { QMap* globalActions(); @@ -27,9 +28,7 @@ public: void initialize(); // View - void appear() { - listView->setFocus(); - } + void appear(); void disappear(); QMap metadata() { QMap metadata; @@ -48,6 +47,7 @@ public slots: void pause(); void stop(); void skip(); + void skipBackward(); void skipVideo(); void openWebPage(); void copyWebPage(); @@ -90,6 +90,7 @@ private slots: void downloadStatusChanged(); void playbackFinished(); void playbackResume(); + void authorPushed(QModelIndex); /* void downloadProgress(int percent); @@ -105,11 +106,11 @@ private: QSplitter *splitter; PlaylistWidget *playlistWidget; - QListView *listView; + PlaylistView *listView; ListModel *listModel; // sortBar - THBlackBar *sortBar; + SegmentedControl *sortBar; QAction *mostRelevantAction; QAction *mostRecentAction; QAction *mostViewedAction; @@ -134,7 +135,6 @@ private: #endif DownloadItem *downloadItem; - // QSlider *slider; }; diff --git a/src/SearchView.cpp b/src/SearchView.cpp index 4bfd3fd..411d952 100644 --- a/src/SearchView.cpp +++ b/src/SearchView.cpp @@ -4,6 +4,11 @@ #include "searchparams.h" #include "youtubesuggest.h" #include "channelsuggest.h" +#ifdef APP_MAC +#include "searchlineedit_mac.h" +#else +#include "searchlineedit.h" +#endif namespace The { QMap* globalActions(); @@ -109,10 +114,9 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) { queryEdit = new SearchLineEdit(this); queryEdit->setFont(biggerFont); queryEdit->setMinimumWidth(queryEdit->fontInfo().pixelSize()*15); - queryEdit->sizeHint(); - queryEdit->setFocus(Qt::OtherFocusReason); - connect(queryEdit, SIGNAL(search(const QString&)), this, SLOT(watch(const QString&))); - connect(queryEdit, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &))); + connect(queryEdit, SIGNAL(search(const QString&)), SLOT(watch(const QString&))); + connect(queryEdit, SIGNAL(textChanged(const QString &)), SLOT(textChanged(const QString &))); + connect(queryEdit, SIGNAL(suggestionAccepted(const QString&)), SLOT(watch(const QString&))); youtubeSuggest = new YouTubeSuggest(this); channelSuggest = new ChannelSuggest(this); @@ -140,13 +144,8 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) { recentKeywordsLayout->setSpacing(5); recentKeywordsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); recentKeywordsLabel = new QLabel(tr("Recent keywords").toUpper(), this); -#if defined(APP_MAC) | defined(APP_WIN) - QPalette palette = recentKeywordsLabel->palette(); - palette.setColor(QPalette::WindowText, QColor(0x65, 0x71, 0x80)); - recentKeywordsLabel->setPalette(palette); -#else + recentKeywordsLabel->setProperty("recentHeader", true); recentKeywordsLabel->setForegroundRole(QPalette::Dark); -#endif recentKeywordsLabel->hide(); recentKeywordsLabel->setFont(smallerFont); recentKeywordsLayout->addWidget(recentKeywordsLabel); @@ -158,13 +157,8 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) { recentChannelsLayout->setSpacing(5); recentChannelsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); recentChannelsLabel = new QLabel(tr("Recent channels").toUpper(), this); -#if defined(APP_MAC) | defined(APP_WIN) - palette = recentChannelsLabel->palette(); - palette.setColor(QPalette::WindowText, QColor(0x65, 0x71, 0x80)); - recentChannelsLabel->setPalette(palette); -#else + recentChannelsLabel->setProperty("recentHeader", true); recentChannelsLabel->setForegroundRole(QPalette::Dark); -#endif recentChannelsLabel->hide(); recentChannelsLabel->setFont(smallerFont); recentChannelsLayout->addWidget(recentChannelsLabel); @@ -180,6 +174,14 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) { } +void SearchView::appear() { + updateRecentKeywords(); + updateRecentChannels(); + queryEdit->selectAll(); + queryEdit->enableSuggest(); + QTimer::singleShot(0, queryEdit, SLOT(setFocus())); +} + void SearchView::updateRecentKeywords() { // cleanup @@ -215,6 +217,7 @@ void SearchView::updateRecentKeywords() { + "\" style=\"color:palette(text); text-decoration:none\">" + display + "", this); itemLabel->setAttribute(Qt::WA_DeleteOnClose); + itemLabel->setProperty("recentItem", true); itemLabel->setMaximumWidth(queryEdit->width() + watchButton->width()); // itemLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Make links navigable with the keyboard too @@ -256,6 +259,7 @@ void SearchView::updateRecentChannels() { + "\" style=\"color:palette(text); text-decoration:none\">" + display + "", this); itemLabel->setAttribute(Qt::WA_DeleteOnClose); + itemLabel->setProperty("recentItem", true); itemLabel->setMaximumWidth(queryEdit->width() + watchButton->width()); // itemLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Make links navigable with the keyboard too @@ -311,7 +315,7 @@ void SearchView::watchChannel(QString channel) { } // remove spaces from channel name - channel = channel.replace(" ", ""); + channel = channel.remove(" "); SearchParams *searchParams = new SearchParams(); searchParams->setAuthor(channel); diff --git a/src/SearchView.h b/src/SearchView.h index 30888d3..b6be275 100644 --- a/src/SearchView.h +++ b/src/SearchView.h @@ -3,8 +3,8 @@ #include #include "View.h" -#include "searchlineedit.h" +class SearchLineEdit; class SearchParams; class YouTubeSuggest; class ChannelSuggest; @@ -17,15 +17,7 @@ public: SearchView(QWidget *parent); void updateRecentKeywords(); void updateRecentChannels(); - - void appear() { - updateRecentKeywords(); - updateRecentChannels(); - queryEdit->clear(); - queryEdit->enableSuggest(); - QTimer::singleShot(0, queryEdit, SLOT(setFocus())); - } - + void appear(); void disappear() {} QMap metadata() { diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 1db03ee..a50fe38 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -2,18 +2,20 @@ #include "suggester.h" AutoComplete::AutoComplete(QWidget *parent, QLineEdit *editor): - QObject(parent), buddy(parent), editor(editor), suggester(0) { + QObject(parent), buddy(parent), editor(editor), suggester(0) { enabled = true; popup = new QListWidget; popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - popup->installEventFilter(this); popup->setMouseTracking(true); popup->setWindowOpacity(.9); + popup->installEventFilter(this); + popup->setWindowFlags(Qt::Popup); + popup->setFocusPolicy(Qt::NoFocus); + popup->setFocusProxy(parent); - connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), - SLOT(doneCompletion())); + connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(doneCompletion())); // connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), // SLOT(currentItemChanged(QListWidgetItem *))); @@ -22,15 +24,11 @@ AutoComplete::AutoComplete(QWidget *parent, QLineEdit *editor): // connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), // SLOT(currentItemChanged(QListWidgetItem *))); - popup->setWindowFlags(Qt::Popup); - popup->setFocusPolicy(Qt::NoFocus); - popup->setFocusProxy(parent); - timer = new QTimer(this); timer->setSingleShot(true); timer->setInterval(300); connect(timer, SIGNAL(timeout()), SLOT(autoSuggest())); - connect(editor, SIGNAL(textEdited(QString)), timer, SLOT(start())); + connect(parent, SIGNAL(textChanged(QString)), timer, SLOT(start())); } @@ -114,7 +112,7 @@ void AutoComplete::showCompletion(const QStringList &choices) { popup->adjustSize(); popup->setUpdatesEnabled(true); - int h = popup->sizeHintForRow(0) * choices.count() + 4; + int h = popup->sizeHintForRow(0) * choices.count(); popup->resize(buddy->width(), h); popup->move(buddy->mapToGlobal(QPoint(0, buddy->height()))); @@ -130,11 +128,7 @@ void AutoComplete::doneCompletion() { QListWidgetItem *item = popup->currentItem(); if (item) { editor->setText(item->text()); - QKeyEvent *e; - e = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); - QApplication::postEvent(editor, e); - e = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier); - QApplication::postEvent(editor, e); + emit suggestionAccepted(item->text()); } } @@ -162,7 +156,10 @@ void AutoComplete::autoSuggest() { QString query = editor->text(); originalText = query; // qDebug() << "originalText" << originalText; - if (query.isEmpty()) return; + if (query.isEmpty()) { + popup->hide(); + return; + } if (suggester) suggester->suggest(query); diff --git a/src/autocomplete.h b/src/autocomplete.h index 9a6e8b3..46e4525 100644 --- a/src/autocomplete.h +++ b/src/autocomplete.h @@ -14,6 +14,7 @@ public: bool eventFilter(QObject *obj, QEvent *ev); void showCompletion(const QStringList &choices); void setSuggester(Suggester* suggester); + QListWidget* getPopup() { return popup; } public slots: void doneCompletion(); @@ -23,6 +24,9 @@ public slots: void currentItemChanged(QListWidgetItem *current); void suggestionsReady(QStringList suggestions); +signals: + void suggestionAccepted(QString suggestion); + private: QWidget *buddy; QLineEdit *editor; diff --git a/src/downloaditem.cpp b/src/downloaditem.cpp index c919e2a..8e66e29 100644 --- a/src/downloaditem.cpp +++ b/src/downloaditem.cpp @@ -5,6 +5,10 @@ #include #include +#ifdef APP_MAC +#include "macutils.h" +#endif + namespace The { NetworkAccess* http(); } @@ -86,8 +90,12 @@ void DownloadItem::open() { void DownloadItem::openFolder() { QFileInfo info(m_file); +#ifdef APP_MAC + mac::showInFinder(info.absoluteFilePath()); +#else QUrl url = QUrl::fromLocalFile(info.absolutePath()); QDesktopServices::openUrl(url); +#endif } void DownloadItem::tryAgain() { diff --git a/src/downloadlistview.cpp b/src/downloadlistview.cpp index 3a87956..25ddf2f 100644 --- a/src/downloadlistview.cpp +++ b/src/downloadlistview.cpp @@ -1,13 +1,9 @@ #include "downloadlistview.h" #include "downloadmodel.h" #include "playlist/PrettyItemDelegate.h" -#include DownloadListView::DownloadListView(QWidget *parent) : QListView(parent) { - // playIconHovered = false; - // setMouseTracking(true); - } void DownloadListView::leaveEvent(QEvent * /* event */) { @@ -21,9 +17,9 @@ void DownloadListView::mouseMoveEvent(QMouseEvent *event) { QListView::mouseMoveEvent(event); if (isHoveringPlayIcon(event)) { - QMetaObject::invokeMethod(model(), "enterPlayIconHover", Qt::DirectConnection); + QMetaObject::invokeMethod(model(), "enterPlayIconHover"); } else { - QMetaObject::invokeMethod(model(), "exitPlayIconHover", Qt::DirectConnection); + QMetaObject::invokeMethod(model(), "exitPlayIconHover"); } } @@ -31,7 +27,7 @@ void DownloadListView::mouseMoveEvent(QMouseEvent *event) { void DownloadListView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && isHoveringPlayIcon(event)) { - QMetaObject::invokeMethod(model(), "enterPlayIconPressed", Qt::DirectConnection); + QMetaObject::invokeMethod(model(), "enterPlayIconPressed"); } else { QListView::mousePressEvent(event); } @@ -39,7 +35,7 @@ void DownloadListView::mousePressEvent(QMouseEvent *event) { void DownloadListView::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - QMetaObject::invokeMethod(model(), "exitPlayIconPressed", Qt::DirectConnection); + QMetaObject::invokeMethod(model(), "exitPlayIconPressed"); if (isHoveringPlayIcon(event)) emit downloadButtonPushed(indexAt(event->pos())); } else { diff --git a/src/downloadlistview.h b/src/downloadlistview.h index 2aae0ff..72f457a 100644 --- a/src/downloadlistview.h +++ b/src/downloadlistview.h @@ -1,8 +1,7 @@ #ifndef DOWNLOADLISTVIEW_H #define DOWNLOADLISTVIEW_H -#include -#include +#include class DownloadListView : public QListView { diff --git a/src/downloadview.cpp b/src/downloadview.cpp index 9ab12a9..3ed343b 100644 --- a/src/downloadview.cpp +++ b/src/downloadview.cpp @@ -6,7 +6,7 @@ #include "downloadsettings.h" #include "ListModel.h" #include "playlist/PrettyItemDelegate.h" -#include "thlibrary/thblackbar.h" +#include "segmentedcontrol.h" DownloadView::DownloadView(QWidget *parent) : QWidget(parent) { @@ -14,7 +14,7 @@ DownloadView::DownloadView(QWidget *parent) : QWidget(parent) { layout->setMargin(0); layout->setSpacing(0); - bar = new THBlackBar(this); + bar = new SegmentedControl(this); QAction *action = new QAction(tr("Downloads"), this); bar->addAction(action); layout->addWidget(bar); diff --git a/src/downloadview.h b/src/downloadview.h index 10f5589..fdbbb54 100644 --- a/src/downloadview.h +++ b/src/downloadview.h @@ -4,7 +4,7 @@ #include #include "View.h" -class THBlackBar; +class SegmentedControl; class DownloadModel; class DownloadListView; class DownloadSettings; @@ -29,7 +29,7 @@ public slots: void buttonPushed(QModelIndex index); private: - THBlackBar *bar; + SegmentedControl *bar; DownloadListView *listView; DownloadModel *listModel; QTimer *updateTimer; diff --git a/src/main.cpp b/src/main.cpp index b2df305..c114f7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,8 @@ int main(int argc, char **argv) { #endif QtSingleApplication app(argc, argv); - if (app.sendMessage("Wake up!")) + QString message = app.arguments().size() > 1 ? app.arguments().at(1) : ""; + if (app.sendMessage(message)) return 0; app.setApplicationName(Constants::NAME); @@ -69,6 +70,7 @@ int main(int argc, char **argv) { mainWin.setWindowTitle(Constants::NAME); #ifdef Q_WS_MAC + app.setQuitOnLastWindowClosed(false); mac::SetupFullScreenWindow(mainWin.winId()); #endif @@ -102,6 +104,7 @@ int main(int argc, char **argv) { mainWin.show(); + mainWin.connect(&app, SIGNAL(messageReceived(const QString &)), &mainWin, SLOT(messageReceived(const QString &))); app.setActivationWindow(&mainWin, true); // all string literals are UTF-8 diff --git a/src/playlist/PrettyItemDelegate.cpp b/src/playlist/PrettyItemDelegate.cpp index 8194771..6cdac51 100644 --- a/src/playlist/PrettyItemDelegate.cpp +++ b/src/playlist/PrettyItemDelegate.cpp @@ -6,11 +6,15 @@ #include #include +#include const qreal PrettyItemDelegate::THUMB_HEIGHT = 90.0; const qreal PrettyItemDelegate::THUMB_WIDTH = 120.0; const qreal PrettyItemDelegate::PADDING = 10.0; +QRect lastAuthorRect; +QHash authorRects; + PrettyItemDelegate::PrettyItemDelegate(QObject* parent, bool downloadInfo) : QStyledItemDelegate(parent), downloadInfo(downloadInfo) { @@ -135,14 +139,27 @@ void PrettyItemDelegate::paintBody( QPainter* painter, painter->drawText(publishedTextBox, Qt::AlignLeft | Qt::AlignTop, publishedString); // author + bool authorHovered = false; + bool authorPressed = false; + const bool isHovered = index.data(HoveredItemRole).toBool(); + if (isHovered) { + authorHovered = index.data(AuthorHoveredRole).toBool(); + authorPressed = index.data(AuthorPressedRole).toBool(); + } + painter->save(); painter->setFont(smallerBoldFont); - if (!isSelected && !isActive) - painter->setPen(QPen(option.palette.brush(QPalette::Mid), 0)); + if (!isSelected) { + if (authorHovered) + painter->setPen(QPen(option.palette.brush(QPalette::Highlight), 0)); + else + painter->setPen(QPen(option.palette.brush(QPalette::Mid), 0)); + } QString authorString = video->author(); QSizeF authorStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, authorString ) ); textLoc.setX(textLoc.x() + publishedStringSize.width() + PADDING); QRectF authorTextBox( textLoc , authorStringSize); + authorRects.insert(index.row(), authorTextBox.toRect()); painter->drawText(authorTextBox, Qt::AlignLeft | Qt::AlignTop, authorString); painter->restore(); @@ -358,3 +375,7 @@ QRect PrettyItemDelegate::downloadButtonRect(QRect line) const { 16, 16); } + +QRect PrettyItemDelegate::authorRect(const QModelIndex& index) const { + return authorRects.value(index.row()); +} diff --git a/src/playlist/PrettyItemDelegate.h b/src/playlist/PrettyItemDelegate.h index 4260558..8934f57 100644 --- a/src/playlist/PrettyItemDelegate.h +++ b/src/playlist/PrettyItemDelegate.h @@ -18,6 +18,7 @@ public: QSize sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const; void paint( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const; QRect downloadButtonRect(QRect line) const; + QRect authorRect(const QModelIndex& index) const; private: void createPlayIcon(); @@ -44,6 +45,7 @@ private: bool downloadInfo; QProgressBar *progressBar; + }; #endif diff --git a/src/playlistview.cpp b/src/playlistview.cpp new file mode 100644 index 0000000..52bdb63 --- /dev/null +++ b/src/playlistview.cpp @@ -0,0 +1,76 @@ +#include "playlistview.h" +#include "ListModel.h" +#include "playlist/PrettyItemDelegate.h" + +PlaylistView::PlaylistView(QWidget *parent) : QListView(parent) { + connect(this, SIGNAL(entered(const QModelIndex &)), SLOT(itemEntered(const QModelIndex &))); + setMouseTracking(true); +} + +void PlaylistView::itemEntered(const QModelIndex &index) { + ListModel *listModel = dynamic_cast(model()); + if (listModel) listModel->setHoveredRow(index.row()); +} + +void PlaylistView::leaveEvent(QEvent * /* event */) { + ListModel *listModel = dynamic_cast(model()); + if (listModel) listModel->clearHover(); +} + +void PlaylistView::mouseMoveEvent(QMouseEvent *event) { + // qDebug() << "PlaylistView::mouseMoveEvent" << event->pos(); + + QListView::mouseMoveEvent(event); + + if (isHoveringAuthor(event)) { + + // check for special "message" item + ListModel *listModel = dynamic_cast(model()); + if (listModel && listModel->rowCount() == indexAt(event->pos()).row()) + return; + + QMetaObject::invokeMethod(model(), "enterAuthorHover"); + setCursor(Qt::PointingHandCursor); + } else { + QMetaObject::invokeMethod(model(), "exitAuthorHover"); + unsetCursor(); + } + +} + +void PlaylistView::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton + && isHoveringAuthor(event)) { + QMetaObject::invokeMethod(model(), "enterAuthorPressed"); + event->ignore(); + } else { + QListView::mousePressEvent(event); + } +} + +void PlaylistView::mouseReleaseEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton) { + QMetaObject::invokeMethod(model(), "exitAuthorPressed"); + if (isHoveringAuthor(event)) + emit authorPushed(indexAt(event->pos())); + } else { + QListView::mousePressEvent(event); + } +} + +bool PlaylistView::isHoveringAuthor(QMouseEvent *event) { + const QModelIndex itemIndex = indexAt(event->pos()); + const QRect itemRect = visualRect(itemIndex); + // qDebug() << " itemRect.x()" << itemRect.x(); + + PrettyItemDelegate *delegate = dynamic_cast(itemDelegate()); + if (!delegate) return false; + + QRect rect = delegate->authorRect(itemIndex); + + const int x = event->x() - itemRect.x() - rect.x(); + const int y = event->y() - itemRect.y() - rect.y(); + bool ret = x > 0 && x < rect.width() && y > 0 && y < rect.height(); + + return ret; +} diff --git a/src/playlistview.h b/src/playlistview.h new file mode 100644 index 0000000..b058e8b --- /dev/null +++ b/src/playlistview.h @@ -0,0 +1,28 @@ +#ifndef PLAYLISTVIEW_H +#define PLAYLISTVIEW_H + +#include + +class PlaylistView : public QListView { + + Q_OBJECT + +public: + PlaylistView(QWidget *parent = 0); + +protected: + void leaveEvent(QEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + bool isHoveringAuthor(QMouseEvent *event); + +signals: + void authorPushed(QModelIndex index); + +private slots: + void itemEntered(const QModelIndex &index); + +}; + +#endif // PLAYLISTVIEW_H diff --git a/src/playlistwidget.cpp b/src/playlistwidget.cpp index 61b1336..58daf3e 100644 --- a/src/playlistwidget.cpp +++ b/src/playlistwidget.cpp @@ -1,6 +1,6 @@ #include "playlistwidget.h" -PlaylistWidget::PlaylistWidget (QWidget *parent, THBlackBar *tabBar, QListView *listView) +PlaylistWidget::PlaylistWidget (QWidget *parent, SegmentedControl *tabBar, QListView *listView) : QWidget(parent) { QBoxLayout *layout = new QVBoxLayout(); layout->setMargin(0); diff --git a/src/playlistwidget.h b/src/playlistwidget.h index 71cb687..37208a0 100644 --- a/src/playlistwidget.h +++ b/src/playlistwidget.h @@ -2,12 +2,12 @@ #define PLAYLISTWIDGET_H #include -#include "thlibrary/thblackbar.h" +#include "segmentedcontrol.h" class PlaylistWidget : public QWidget { public: - PlaylistWidget(QWidget *parent, THBlackBar *tabBar, QListView *listView); + PlaylistWidget(QWidget *parent, SegmentedControl *tabBar, QListView *listView); }; #endif // PLAYLISTWIDGET_H diff --git a/src/searchlineedit.h b/src/searchlineedit.h index f56ca80..c5f53a8 100644 --- a/src/searchlineedit.h +++ b/src/searchlineedit.h @@ -93,7 +93,7 @@ public: void updateGeometries(); void enableSuggest(); void preventSuggest(); - void selectAll() { lineEdit()->selectAll(); }; + void selectAll() { lineEdit()->selectAll(); } void setSuggester(Suggester *suggester) { completion->setSuggester(suggester); } protected: diff --git a/src/segmentedcontrol.cpp b/src/segmentedcontrol.cpp new file mode 100644 index 0000000..73b8a98 --- /dev/null +++ b/src/segmentedcontrol.cpp @@ -0,0 +1,243 @@ +#include "segmentedcontrol.h" +#include "fontutils.h" + +static const QColor borderColor = QColor(0x26, 0x26, 0x26); + +class SegmentedControl::Private { +public: + QList actionList; + QAction *checkedAction; + QAction *hoveredAction; + QAction *pressedAction; +}; + +SegmentedControl::SegmentedControl (QWidget *parent) + : QWidget(parent), d(new SegmentedControl::Private) { + + setMouseTracking(true); + + d->hoveredAction = 0; + d->checkedAction = 0; + d->pressedAction = 0; +} + +SegmentedControl::~SegmentedControl() { + delete d; +} + +QAction *SegmentedControl::addAction(QAction *action) { + QWidget::addAction(action); + action->setCheckable(true); + d->actionList.append(action); + return action; +} + +bool SegmentedControl::setCheckedAction(int index) { + if (index < 0) { + d->checkedAction = 0; + return true; + } + QAction* newCheckedAction = d->actionList.at(index); + return setCheckedAction(newCheckedAction); +} + +bool SegmentedControl::setCheckedAction(QAction *action) { + if (d->checkedAction == action) { + return false; + } + d->checkedAction = action; + update(); + return true; +} + +QSize SegmentedControl::minimumSizeHint (void) const { + int itemsWidth = calculateButtonWidth() * d->actionList.size() * 1.2; + return(QSize(itemsWidth, QFontMetrics(font()).height() * 1.9)); +} + +void SegmentedControl::paintEvent (QPaintEvent *event) { + int height = event->rect().height(); + int width = event->rect().width(); + + QPainter p(this); + + QLinearGradient linearGrad(rect().topLeft(), rect().bottomLeft()); + linearGrad.setColorAt(0, borderColor); + linearGrad.setColorAt(1, QColor(0x3c, 0x3c, 0x3c)); + p.fillRect(rect(), QBrush(linearGrad)); + + // Calculate Buttons Size & Location + const int buttonWidth = width / d->actionList.size(); + + // Draw Buttons + QRect rect(0, 0, buttonWidth, height); + const int actionCount = d->actionList.size(); + for (int i = 0; i < actionCount; i++) { + QAction *action = d->actionList.at(i); + + if (i + 1 == actionCount) { + rect.setWidth(width - buttonWidth * (actionCount-1)); + drawButton(&p, rect, action); + } else { + drawButton(&p, rect, action); + rect.moveLeft(rect.x() + rect.width()); + } + + } + +} + +void SegmentedControl::mouseMoveEvent (QMouseEvent *event) { + QWidget::mouseMoveEvent(event); + + QAction *action = hoveredAction(event->pos()); + + if (!action && d->hoveredAction) { + d->hoveredAction = 0; + update(); + } else if (action && action != d->hoveredAction) { + d->hoveredAction = action; + action->hover(); + update(); + + // status tip + QMainWindow* mainWindow = dynamic_cast(window()); + if (mainWindow) mainWindow->statusBar()->showMessage(action->statusTip()); + } +} + +void SegmentedControl::mousePressEvent(QMouseEvent *event) { + QWidget::mousePressEvent(event); + if (d->hoveredAction) { + d->pressedAction = d->hoveredAction; + update(); + } +} + +void SegmentedControl::mouseReleaseEvent(QMouseEvent *event) { + QWidget::mouseReleaseEvent(event); + d->pressedAction = 0; + if (d->hoveredAction) { + bool changed = setCheckedAction(d->hoveredAction); + if (changed) d->hoveredAction->trigger(); + } +} + +void SegmentedControl::leaveEvent(QEvent *event) { + QWidget::leaveEvent(event); + // status tip + QMainWindow* mainWindow = dynamic_cast(window()); + if (mainWindow) mainWindow->statusBar()->clearMessage(); + d->hoveredAction = 0; + d->pressedAction = 0; + update(); +} + +QAction *SegmentedControl::hoveredAction(const QPoint& pos) const { + if (pos.y() <= 0 || pos.y() >= height()) + return 0; + + int buttonWidth = width() / d->actionList.size(); + int buttonsWidth = width(); + int buttonsX = 0; + + if (pos.x() <= buttonsX || pos.x() >= (buttonsX + buttonsWidth)) + return 0; + + int buttonIndex = (pos.x() - buttonsX) / buttonWidth; + + if (buttonIndex >= d->actionList.size()) + return 0; + return(d->actionList[buttonIndex]); +} + +int SegmentedControl::calculateButtonWidth (void) const { + QFont smallerBoldFont = FontUtils::smallBold(); + QFontMetrics fontMetrics(smallerBoldFont); + int tmpItemWidth, itemWidth = 0; + foreach (QAction *action, d->actionList) { + tmpItemWidth = fontMetrics.width(action->text()); + if (itemWidth < tmpItemWidth) itemWidth = tmpItemWidth; + } + return itemWidth; +} + +void SegmentedControl::drawButton (QPainter *painter, + const QRect& rect, + const QAction *action) { + if (action == d->checkedAction) + drawSelectedButton(painter, rect, action); + else + drawUnselectedButton(painter, rect, action); +} + +void SegmentedControl::drawUnselectedButton (QPainter *painter, + const QRect& rect, + const QAction *action) { + paintButton(painter, rect, action); +} + +void SegmentedControl::drawSelectedButton (QPainter *painter, + const QRect& rect, + const QAction *action) { + painter->save(); + painter->translate(rect.topLeft()); + + const int width = rect.width(); + const int height = rect.height(); + const int hCenter = width * .5; + QRadialGradient gradient(hCenter, 0, + width, + hCenter, 0); + gradient.setColorAt(1, Qt::black); + gradient.setColorAt(0, QColor(0x33, 0x33, 0x33)); + painter->fillRect(0, 0, width, height, QBrush(gradient)); + + painter->restore(); + paintButton(painter, rect, action); +} + +void SegmentedControl::paintButton(QPainter *painter, const QRect& rect, const QAction *action) { + painter->save(); + painter->translate(rect.topLeft()); + + const int height = rect.height(); + const int width = rect.width(); + + if (action == d->pressedAction && action != d->checkedAction) { + const int hCenter = width * .5; + QRadialGradient gradient(hCenter, 0, + width, + hCenter, 0); + gradient.setColorAt(1, QColor(0x00, 0x00, 0x00, 0)); + gradient.setColorAt(0, QColor(0x00, 0x00, 0x00, 16)); + painter->fillRect(0, 0, width, height, QBrush(gradient)); + } else if (action == d->hoveredAction && action != d->checkedAction) { + const int hCenter = width * .5; + QRadialGradient gradient(hCenter, 0, + width, + hCenter, 0); + gradient.setColorAt(1, QColor(0xff, 0xff, 0xff, 0)); + gradient.setColorAt(0, QColor(0xff, 0xff, 0xff, 16)); + painter->fillRect(0, 0, width, height, QBrush(gradient)); + } + + painter->setPen(borderColor); +#if defined(APP_MAC) | defined(APP_WIN) + painter->drawRect(-1, -1, width, height); +#else + painter->drawRect(0, 0, width, height - 1); +#endif + + painter->setFont(FontUtils::smallBold()); + + // text shadow + painter->setPen(QColor(0, 0, 0, 128)); + painter->drawText(0, -1, width, height, Qt::AlignCenter, action->text()); + + painter->setPen(QPen(Qt::white, 1)); + painter->drawText(0, 0, width, height, Qt::AlignCenter, action->text()); + + painter->restore(); +} + diff --git a/src/segmentedcontrol.h b/src/segmentedcontrol.h new file mode 100644 index 0000000..094a3b7 --- /dev/null +++ b/src/segmentedcontrol.h @@ -0,0 +1,49 @@ +#ifndef SEGMENTEDCONTROL_H +#define SEGMENTEDCONTROL_H + +#include + +class SegmentedControl : public QWidget { + + Q_OBJECT + +public: + SegmentedControl(QWidget *parent = 0); + ~SegmentedControl(); + QAction *addAction(QAction *action); + bool setCheckedAction(int index); + bool setCheckedAction(QAction *action); + QSize minimumSizeHint(void) const; + +signals: + void checkedActionChanged(QAction & action); + +protected: + void paintEvent(QPaintEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void leaveEvent(QEvent *event); + +private: + void drawButton(QPainter *painter, + const QRect& rect, + const QAction *action); + void drawUnselectedButton(QPainter *painter, + const QRect& rect, + const QAction *action); + void drawSelectedButton(QPainter *painter, + const QRect& rect, + const QAction *action); + void paintButton(QPainter *painter, + const QRect& rect, + const QAction *action); + QAction *hoveredAction(const QPoint& pos) const; + int calculateButtonWidth(void) const; + + class Private; + Private *d; + +}; + +#endif /* !SEGMENTEDCONTROL_H */ -- 2.39.5