├── LICENSE ├── icon.png ├── img ├── bg.png ├── album_init.jpg ├── arrow_left.png ├── arrow_right.png ├── user_man_circle.png ├── arrow_left_clicked.png ├── arrow_right_clicked.png ├── apple-ios-linen-texture.jpg ├── heart.svg ├── heart_red.svg ├── heart_active.svg ├── pause.svg ├── trash.svg ├── heart_red_active.svg ├── pause_active.svg ├── trash_active.svg ├── next.svg ├── next_active.svg ├── volume.svg ├── volume_active.svg ├── setting.svg └── setting_active.svg ├── QDoubanFM.ico ├── QDoubanFM.png ├── screenshot.png ├── data ├── win32 │ └── winrc.rc └── macx │ ├── QDoubanFM.icns │ └── Info.plist ├── .gitignore ├── triggerarea.cpp ├── QDoubanFM.desktop ├── channelwidgettrigger.h ├── plugins ├── plugin.hpp ├── mpris │ ├── org.mpris.MediaPlayer2.xml │ ├── org.freedesktop.DBus.Properties.xml │ ├── org.mpris.MediaPlayer2.Player.xml │ ├── doubanmprisplugin.h │ ├── mprisadapter.cpp │ ├── mprisadapter.h │ ├── mprisplayeradapter.h │ ├── mprisplayeradapter.cpp │ └── doubanmprisplugin.cpp ├── doubanfmplugin.cpp └── doubanfmplugin.h ├── triggerarea.h ├── pausemask.h ├── albumwidget.h ├── libs ├── lyricgetter.h ├── qlyricparser.h ├── doubanchannelmanager.h ├── douban_types.h ├── doubanchannelmanager.cpp ├── qlyricparser.cpp ├── doubanplayer.h ├── lyricgetter.cpp ├── doubanfm.h └── doubanplayer.cpp ├── install.sh ├── volumetimepanel.h ├── albumwidget.cpp ├── albumimage.h ├── pausemask.cpp ├── settingdialog.h ├── horizontalslider.h ├── imgs.qrc ├── channelwidget.h ├── Copyright ├── lyricwidget.h ├── ChangeLog ├── mainwidget.h ├── controlpanel.h ├── pausemask.ui ├── lyricwidget.ui ├── main.cpp ├── volumetimepanel.cpp ├── README.md ├── volumetimepanel.ui ├── doubanfm-qt.pro ├── horizontalslider.cpp ├── albumwidget.ui ├── mainwidget.ui ├── albumimage.cpp ├── channelwidget.cpp ├── channelwidget.ui ├── settingdialog.cpp ├── lyricwidget.cpp ├── i18n └── zh_CN.ts ├── settingdialog.ui ├── controlpanel.cpp ├── controlpanel.ui └── mainwidget.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/icon.png -------------------------------------------------------------------------------- /img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/bg.png -------------------------------------------------------------------------------- /QDoubanFM.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/QDoubanFM.ico -------------------------------------------------------------------------------- /QDoubanFM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/QDoubanFM.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/screenshot.png -------------------------------------------------------------------------------- /data/win32/winrc.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "../../QDoubanFM.ico" 2 | -------------------------------------------------------------------------------- /img/album_init.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/album_init.jpg -------------------------------------------------------------------------------- /img/arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/arrow_left.png -------------------------------------------------------------------------------- /img/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/arrow_right.png -------------------------------------------------------------------------------- /img/user_man_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/user_man_circle.png -------------------------------------------------------------------------------- /data/macx/QDoubanFM.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/data/macx/QDoubanFM.icns -------------------------------------------------------------------------------- /img/arrow_left_clicked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/arrow_left_clicked.png -------------------------------------------------------------------------------- /img/arrow_right_clicked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/arrow_right_clicked.png -------------------------------------------------------------------------------- /img/apple-ios-linen-texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zonyitoo/doubanfm-qt/HEAD/img/apple-ios-linen-texture.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .directory 2 | *.qm 3 | *.o 4 | Makefile 5 | *.user 6 | doubanfm-qt 7 | doubanfm-qt.exe 8 | Makefile.* 9 | release/ 10 | release_tmp/ 11 | debug/ 12 | tmp/ 13 | doubanfm-qt.pro.user 14 | qrc_*.cpp 15 | -------------------------------------------------------------------------------- /triggerarea.cpp: -------------------------------------------------------------------------------- 1 | #include "triggerarea.h" 2 | 3 | TriggerArea::TriggerArea(QWidget *parent) : 4 | QWidget(parent) 5 | { 6 | } 7 | 8 | void TriggerArea::enterEvent(QEvent *) { 9 | emit enter(); 10 | } 11 | 12 | void TriggerArea::leaveEvent(QEvent *) { 13 | emit leave(); 14 | } 15 | -------------------------------------------------------------------------------- /QDoubanFM.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | Encoding=UTF-8 5 | Name=QDoubanFM 6 | GenericName=DoubanFM Client 7 | Comment=A DoubanFM Client 8 | Exec=doubanfm-qt 9 | Icon=/usr/share/pixmaps/QDoubanFM.png 10 | Terminal=false 11 | Categories=GNOME;Appliation;Network 12 | 13 | 14 | Name[zh_CN]=豆瓣FM 15 | -------------------------------------------------------------------------------- /channelwidgettrigger.h: -------------------------------------------------------------------------------- 1 | #ifndef CHANNELWIDGETTRIGGER_H 2 | #define CHANNELWIDGETTRIGGER_H 3 | 4 | #include "triggerarea.h" 5 | 6 | class ChannelWidgetTrigger : public TriggerArea { 7 | public: 8 | explicit ChannelWidgetTrigger(QWidget *parent) 9 | : TriggerArea(parent) {} 10 | }; 11 | 12 | #endif // CHANNELWIDGETTRIGGER_H 13 | -------------------------------------------------------------------------------- /plugins/plugin.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLUGIN_HPP 2 | #define PLUGIN_HPP 3 | 4 | #include "plugins/doubanfmplugin.h" 5 | 6 | #ifdef WITH_MPRIS_PLUGIN 7 | #include "plugins/mpris/doubanmprisplugin.h" 8 | #endif 9 | 10 | inline void load_plugins() { 11 | #ifdef WITH_MPRIS_PLUGIN 12 | REGISTER_PLUGIN("MPRIS_PLUGIN", DoubanMprisPlugin); 13 | #endif 14 | } 15 | 16 | #endif // PLUGIN_HPP 17 | -------------------------------------------------------------------------------- /triggerarea.h: -------------------------------------------------------------------------------- 1 | #ifndef TRIGGERAREA_H 2 | #define TRIGGERAREA_H 3 | 4 | #include 5 | 6 | class TriggerArea : public QWidget 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit TriggerArea(QWidget *parent = 0); 11 | 12 | void enterEvent(QEvent *ev); 13 | void leaveEvent(QEvent *ev); 14 | signals: 15 | void enter(); 16 | void leave(); 17 | }; 18 | 19 | #endif // TRIGGERAREA_H 20 | -------------------------------------------------------------------------------- /pausemask.h: -------------------------------------------------------------------------------- 1 | #ifndef PAUSEMASK_H 2 | #define PAUSEMASK_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class PauseMask; 9 | } 10 | 11 | class PauseMask : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit PauseMask(QWidget *parent = 0); 17 | ~PauseMask(); 18 | 19 | void mousePressEvent(QMouseEvent *event); 20 | 21 | signals: 22 | void clicked(); 23 | 24 | private: 25 | Ui::PauseMask *ui; 26 | }; 27 | 28 | #endif // PAUSEMASK_H 29 | -------------------------------------------------------------------------------- /albumwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef ALBUMWIDGET_H 2 | #define ALBUMWIDGET_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class AlbumWidget; 8 | } 9 | 10 | class AlbumWidget : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit AlbumWidget(QWidget *parent = 0); 16 | ~AlbumWidget(); 17 | 18 | void enterEvent(QEvent *); 19 | void leaveEvent(QEvent *); 20 | 21 | signals: 22 | void clicked(); 23 | 24 | public slots: 25 | void setAlbumImage(const QImage& src); 26 | 27 | private: 28 | Ui::AlbumWidget *ui; 29 | }; 30 | 31 | #endif // ALBUMWIDGET_H 32 | -------------------------------------------------------------------------------- /plugins/mpris/org.mpris.MediaPlayer2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /libs/lyricgetter.h: -------------------------------------------------------------------------------- 1 | #ifndef LYRICGETTER_H 2 | #define LYRICGETTER_H 3 | 4 | #include 5 | #include 6 | #include "qlyricparser.h" 7 | #include "douban_types.h" 8 | 9 | class LyricGetter : public QObject { 10 | Q_OBJECT 11 | public: 12 | explicit LyricGetter(QObject* parent = 0); 13 | ~LyricGetter(); 14 | 15 | void getLyric(const DoubanFMSong& song); 16 | signals: 17 | void gotLyric(const QLyricList& lyric); 18 | void gotLyricError(const QString& errmsg); 19 | 20 | private: 21 | QNetworkAccessManager* querymgr; 22 | }; 23 | 24 | #endif // LYRICGETTER_H 25 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SHARE=/usr/share 4 | PIXMAP=/usr/share/pixmaps 5 | APP=/usr/share/applications 6 | BIN=/usr/bin 7 | 8 | if [ "$1" = "-u" ]; then 9 | rm $BIN/doubanfm-qt 10 | rm $PIXMAP/QDoubanFM.png 11 | rm $APP/QDoubanFM.desktop 12 | rm -r $SHARE/QDoubanFM 13 | else 14 | mkdir -p $SHARE/QDoubanFM 15 | cp doubanfm-qt $SHARE/QDoubanFM 16 | #mkdir -p $SHARE/QDoubanFM/lang/ 17 | #cp -r lang/*.qm $SHARE/QDoubanFM/lang/ 18 | rm -f $BIN/doubanfm-qt 19 | ln -s $SHARE/QDoubanFM/doubanfm-qt $BIN/doubanfm-qt 20 | cp QDoubanFM.desktop $APP 21 | cp QDoubanFM.png $PIXMAP 22 | fi 23 | -------------------------------------------------------------------------------- /plugins/mpris/org.freedesktop.DBus.Properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /volumetimepanel.h: -------------------------------------------------------------------------------- 1 | #ifndef VOLUMETIMEPANEL_H 2 | #define VOLUMETIMEPANEL_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class VolumeTimePanel; 8 | } 9 | 10 | class VolumeTimePanel : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit VolumeTimePanel(QWidget *parent = 0); 16 | ~VolumeTimePanel(); 17 | 18 | void enterEvent(QEvent *); 19 | void leaveEvent(QEvent *); 20 | 21 | signals: 22 | void volumeChanged(int); 23 | 24 | public slots: 25 | void setTick(qint64 tick); 26 | 27 | private: 28 | Ui::VolumeTimePanel *ui; 29 | }; 30 | 31 | #endif // VOLUMETIMEPANEL_H 32 | -------------------------------------------------------------------------------- /libs/qlyricparser.h: -------------------------------------------------------------------------------- 1 | #ifndef QLYRICPARSER_H 2 | #define QLYRICPARSER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct QLyric { 10 | QTime time; 11 | QString lyric; 12 | }; 13 | 14 | typedef QList QLyricList; 15 | 16 | class QLyricParser { 17 | public: 18 | static QLyricList parse(QTextStream& stream); 19 | 20 | private: 21 | #ifdef MSVC2012 22 | QLyricParser(); 23 | QLyricParser(const QLyricParser&); 24 | #else // vs2012 not support the feature of c++11 now 25 | QLyricParser() = delete; 26 | QLyricParser(const QLyricParser&) = delete; 27 | #endif 28 | }; 29 | 30 | #endif // QLYRICPARSER_H 31 | -------------------------------------------------------------------------------- /albumwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "albumwidget.h" 2 | #include "ui_albumwidget.h" 3 | 4 | AlbumWidget::AlbumWidget(QWidget *parent) : 5 | QWidget(parent), 6 | ui(new Ui::AlbumWidget) 7 | { 8 | ui->setupUi(this); 9 | 10 | connect(ui->albimImage, &AlbumImage::clicked, [this] () { 11 | emit this->clicked(); 12 | }); 13 | 14 | ui->label->raise(); 15 | ui->label->hide(); 16 | } 17 | 18 | AlbumWidget::~AlbumWidget() 19 | { 20 | delete ui; 21 | } 22 | 23 | void AlbumWidget::setAlbumImage(const QImage &src) { 24 | ui->albimImage->setAlbumImage(src); 25 | } 26 | 27 | void AlbumWidget::enterEvent(QEvent *) { 28 | ui->label->show(); 29 | } 30 | 31 | void AlbumWidget::leaveEvent(QEvent *) { 32 | ui->label->hide(); 33 | } 34 | -------------------------------------------------------------------------------- /albumimage.h: -------------------------------------------------------------------------------- 1 | #ifndef ALBUMIMAGE_H 2 | #define ALBUMIMAGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class AlbumImage : public QLabel 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) 12 | 13 | public: 14 | explicit AlbumImage(QWidget *parent = 0); 15 | 16 | void mousePressEvent(QMouseEvent *ev); 17 | qreal opacity() const; 18 | signals: 19 | void clicked(); 20 | void opacityChanged(qreal opacity); 21 | 22 | public slots: 23 | void setAlbumImage(const QImage& src); 24 | void setOpacity(qreal opacity); 25 | 26 | private: 27 | qreal _opacity; 28 | QImage before; 29 | QImage now; 30 | }; 31 | 32 | #endif // ALBUMIMAGE_H 33 | -------------------------------------------------------------------------------- /pausemask.cpp: -------------------------------------------------------------------------------- 1 | #include "pausemask.h" 2 | #include "ui_pausemask.h" 3 | #include 4 | #include "libs/doubanplayer.h" 5 | 6 | PauseMask::PauseMask(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::PauseMask) 9 | { 10 | ui->setupUi(this); 11 | connect(&DoubanPlayer::getInstance(), &DoubanPlayer::playing, [=] () { 12 | this->setVisible(false); 13 | }); 14 | connect(&DoubanPlayer::getInstance(), &DoubanPlayer::paused, [=] () { 15 | this->setVisible(true); 16 | }); 17 | } 18 | 19 | PauseMask::~PauseMask() 20 | { 21 | delete ui; 22 | } 23 | 24 | void PauseMask::mousePressEvent(QMouseEvent *event) { 25 | if (event->button() == Qt::LeftButton) { 26 | this->setVisible(false); 27 | emit clicked(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libs/doubanchannelmanager.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBANCHANNELMANAGER_H 2 | #define DOUBANCHANNELMANAGER_H 3 | 4 | #include 5 | 6 | #include 7 | class DoubanFM; 8 | 9 | class DoubanChannelManager : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | static DoubanChannelManager& getInstance(); 14 | 15 | DoubanChannel currentChannel() const; 16 | DoubanChannel& currentChannel(); 17 | 18 | signals: 19 | void reloadChannels(); 20 | void channelChanged(const DoubanChannel& channel); 21 | 22 | public slots: 23 | void reload(); 24 | 25 | private: 26 | explicit DoubanChannelManager(QObject *parent = nullptr); 27 | 28 | QList channels; 29 | QList::size_type curIndex; 30 | 31 | DoubanFM &doubanfm; 32 | }; 33 | 34 | #endif // DOUBANCHANNELMANAGER_H 35 | -------------------------------------------------------------------------------- /settingdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGDIALOG_H 2 | #define SETTINGDIALOG_H 3 | 4 | #include 5 | #include 6 | #include "libs/doubanfm.h" 7 | #include 8 | 9 | namespace Ui { 10 | class SettingDialog; 11 | } 12 | 13 | class SettingDialog : public QDialog 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit SettingDialog(QWidget *parent = 0); 19 | ~SettingDialog(); 20 | 21 | private slots: 22 | void setUserIcon(const QPixmap& pixmap); 23 | 24 | void on_loginButton_clicked(); 25 | 26 | void timer_event(); 27 | void kbps_radio_button_clicked(QAbstractButton *button); 28 | 29 | private: 30 | Ui::SettingDialog *ui; 31 | 32 | DoubanFM& doubanfm; 33 | QButtonGroup *kbpsGroup; 34 | 35 | QNetworkAccessManager *userIconGetter; 36 | QNetworkAccessManager *userInfoGetter; 37 | }; 38 | 39 | #endif // SETTINGDIALOG_H 40 | -------------------------------------------------------------------------------- /libs/douban_types.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBAN_TYPES_H 2 | #define DOUBAN_TYPES_H 3 | 4 | #include 5 | 6 | struct DoubanUser { 7 | QString user_id; 8 | QString expire; 9 | QString token; 10 | QString user_name; 11 | QString email; 12 | QString password; 13 | }; 14 | 15 | struct DoubanChannel { 16 | QString name; 17 | qint32 seq_id; 18 | QString abbr_en; 19 | qint32 channel_id; 20 | QString name_en; 21 | }; 22 | 23 | struct DoubanFMSong { 24 | QString album; 25 | QString picture; 26 | QString ssid; 27 | QString artist; 28 | QString url; 29 | QString company; 30 | QString title; 31 | double rating_avg; 32 | quint32 length; 33 | QString subtype; 34 | QString public_time; 35 | quint32 sid; 36 | quint32 aid; 37 | quint32 kbps; 38 | QString albumtitle; 39 | bool like; 40 | }; 41 | 42 | #endif // DOUBAN_TYPES_H 43 | -------------------------------------------------------------------------------- /libs/doubanchannelmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "doubanchannelmanager.h" 2 | #include "doubanfm.h" 3 | 4 | DoubanChannelManager::DoubanChannelManager(QObject *parent) : 5 | QObject(parent), 6 | curIndex(-1), 7 | doubanfm(DoubanFM::getInstance()) 8 | { 9 | connect(&doubanfm, &DoubanFM::receivedChannels, [&] (const QList& channels) { 10 | this->channels = channels; 11 | curIndex = 0; 12 | }); 13 | } 14 | 15 | DoubanChannelManager& DoubanChannelManager::getInstance() { 16 | static DoubanChannelManager _INSTANCE; 17 | return _INSTANCE; 18 | } 19 | 20 | DoubanChannel DoubanChannelManager::currentChannel() const { 21 | return channels[curIndex]; 22 | } 23 | 24 | DoubanChannel &DoubanChannelManager::currentChannel() { 25 | return channels[curIndex]; 26 | } 27 | 28 | void DoubanChannelManager::reload() { 29 | channels.clear(); 30 | curIndex = -1; 31 | 32 | doubanfm.getChannels(); 33 | } 34 | -------------------------------------------------------------------------------- /horizontalslider.h: -------------------------------------------------------------------------------- 1 | #ifndef HORIZONTALSLIDER_H 2 | #define HORIZONTALSLIDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class HorizontalSlider : public QWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit HorizontalSlider(QWidget *parent = 0); 13 | ~HorizontalSlider(); 14 | 15 | void setChildren(const QList& list); 16 | void addChild(QWidget *widget); 17 | 18 | void scrollToIndex(int index); 19 | int numberOfChildren(); 20 | 21 | void clear(); 22 | 23 | int currentIndex(); 24 | QWidget *currentObject() const; 25 | QWidget *preObject() const; 26 | QWidget *nextObject() const; 27 | 28 | private: 29 | QHBoxLayout *hbox; 30 | QWidget *container; 31 | QList items; 32 | QList widths; 33 | int curIndex; 34 | 35 | void refresh(); 36 | 37 | signals: 38 | void scrollFinished(); 39 | }; 40 | 41 | #endif // HORIZONTALSLIDER_H 42 | -------------------------------------------------------------------------------- /data/macx/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrincipalClass 6 | NSApplication 7 | CFBundleIconFile 8 | QDoubanFM.icns 9 | CFBundlePackageType 10 | APPL 11 | CFBundleGetInfoString 12 | Created by Qt/QMake 13 | CFBundleSignature 14 | ???? 15 | CFBundleExecutable 16 | DoubanFM-Qt 17 | CFBundleIdentifier 18 | me.zonyitoo.DoubanFM-Qt 19 | NOTE 20 | This file was generated by Qt/QMake. 21 | NSAppTransportSecurity 22 | 23 | 24 | NSAllowsArbitraryLoads 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /plugins/doubanfmplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "doubanfmplugin.h" 2 | #include 3 | 4 | DoubanFMPlugin::DoubanFMPlugin(QObject *parent) : QObject(parent), player(DoubanPlayer::getInstance()) {} 5 | 6 | DoubanFMPlugin::~DoubanFMPlugin() {} 7 | 8 | DoubanFMPluginLoader::DoubanFMPluginLoader(QObject *parent) : QObject(parent) {} 9 | 10 | DoubanFMPluginLoader::~DoubanFMPluginLoader() { 11 | for (auto &p : plugins) { 12 | delete p; 13 | } 14 | } 15 | 16 | DoubanFMPluginLoader &DoubanFMPluginLoader::getInstance() { 17 | static DoubanFMPluginLoader instance; 18 | return instance; 19 | } 20 | 21 | void DoubanFMPluginLoader::regPlugin(QString name, const DoubanFMPlugin *plugin) { 22 | auto itr = plugins.find(name); 23 | if (itr != plugins.end()) { 24 | qWarning() << "Plugin name \"" << name << "\" already exists"; 25 | } else { 26 | plugins[name] = plugin; 27 | } 28 | } 29 | 30 | void DoubanFMPluginLoader::rmPlugin(QString name) { plugins.remove(name); } 31 | -------------------------------------------------------------------------------- /imgs.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | img/heart.svg 4 | img/heart_active.svg 5 | img/heart_red.svg 6 | img/heart_red_active.svg 7 | img/next.svg 8 | img/next_active.svg 9 | img/trash.svg 10 | img/trash_active.svg 11 | img/pause.svg 12 | img/pause_active.svg 13 | img/volume.svg 14 | img/volume_active.svg 15 | img/setting.svg 16 | img/setting_active.svg 17 | img/arrow_left.png 18 | img/arrow_right.png 19 | img/bg.png 20 | icon.png 21 | img/album_init.jpg 22 | img/arrow_right_clicked.png 23 | img/arrow_left_clicked.png 24 | img/user_man_circle.png 25 | img/apple-ios-linen-texture.jpg 26 | 27 | 28 | -------------------------------------------------------------------------------- /channelwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef CHANNELWIDGET_H 2 | #define CHANNELWIDGET_H 3 | 4 | #include 5 | #include "libs/douban_types.h" 6 | #include 7 | #include 8 | #include "libs/doubanfm.h" 9 | #include 10 | 11 | namespace Ui { 12 | class ChannelWidget; 13 | } 14 | 15 | class ChannelWidget : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit ChannelWidget(QWidget *parent = 0); 21 | ~ChannelWidget(); 22 | 23 | void leaveEvent(QEvent *ev); 24 | void wheelEvent(QWheelEvent *ev); 25 | public slots: 26 | void setChannels(const QList& channels); 27 | 28 | signals: 29 | void channelChanged(qint32 channel); 30 | void mouseLeave(); 31 | 32 | private slots: 33 | void on_nextButton_clicked(); 34 | 35 | void on_prevButton_clicked(); 36 | 37 | private: 38 | Ui::ChannelWidget *ui; 39 | 40 | DoubanFM& doubanfm; 41 | QList channels; 42 | qint32 channel; 43 | QTimer *timer; 44 | }; 45 | 46 | #endif // CHANNELWIDGET_H 47 | -------------------------------------------------------------------------------- /plugins/doubanfmplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBANFMPLUGIN_H 2 | #define DOUBANFMPLUGIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class DoubanFMPlugin : public QObject { 9 | Q_OBJECT 10 | public: 11 | explicit DoubanFMPlugin(QObject *parent = nullptr); 12 | virtual ~DoubanFMPlugin(); 13 | 14 | signals: 15 | 16 | public slots: 17 | 18 | protected: 19 | DoubanPlayer &player; 20 | }; 21 | 22 | class DoubanFMPluginLoader : public QObject { 23 | public: 24 | ~DoubanFMPluginLoader(); 25 | 26 | void regPlugin(QString name, const DoubanFMPlugin *plugin); 27 | void rmPlugin(QString name); 28 | 29 | static DoubanFMPluginLoader &getInstance(); 30 | 31 | private: 32 | DoubanFMPluginLoader(QObject *parent = nullptr); 33 | QMap plugins; 34 | }; 35 | 36 | #define REGISTER_PLUGIN(name, cname) DoubanFMPluginLoader::getInstance().regPlugin(name, new cname()) 37 | 38 | #define REMOVE_PLUGIN(name) DoubanFMPluginLoader::getInstance().rmPlugin(name) 39 | 40 | #endif // DOUBANFMPLUGIN_H 41 | -------------------------------------------------------------------------------- /plugins/mpris/org.mpris.MediaPlayer2.Player.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Copyright: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lyricwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef LYRICWIDGET_H 2 | #define LYRICWIDGET_H 3 | 4 | #include 5 | #include "libs/qlyricparser.h" 6 | #include 7 | #include 8 | #include "ui_lyricwidget.h" 9 | #include 10 | #include "libs/doubanfm.h" 11 | #include "libs/lyricgetter.h" 12 | #include "libs/douban_types.h" 13 | 14 | class LyricWidget : public QWidget 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit LyricWidget(QWidget *parent = 0); 19 | ~LyricWidget(); 20 | 21 | 22 | void clear(); 23 | 24 | void mousePressEvent(QMouseEvent *event); 25 | 26 | signals: 27 | void clicked(); 28 | 29 | public slots: 30 | void setTick(qint64 tick); 31 | void setTime(const QTime& time); 32 | void setLyric(const QLyricList& lyric); 33 | void setSong(const DoubanFMSong& song); 34 | void setShowing(bool); 35 | 36 | private: 37 | Ui_LyricWidget *ui; 38 | QWidget *animWidget; 39 | int curInd; 40 | QLyricList lyric; 41 | QList labels; 42 | QList heights; 43 | bool firstShowing; 44 | LyricGetter *lyricGetter; 45 | bool isShowing; 46 | DoubanFMSong saveCurrentSong; 47 | bool haveSearchedLyric; 48 | qint64 saveTick; 49 | }; 50 | 51 | #endif // LYRICWIDGET_H 52 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2013-12-28 Y. T. Chung 2 | 3 | * channelwidget.cpp (Channel Widget): Swapped the next and prev 4 | buttons. 5 | * mainwidget.cpp (Main Widget): Removed QSystemTrayIcon on Ubuntu 6 | system 7 | * libs/doubanplayer.cpp (DoubanFM Player): Segmfault in KDE 4.9, need 8 | fix 9 | 10 | 2013-11-04 Y. T. Chung 11 | 12 | * libs/doubanplayer (DoubanFM Player Class): Seperated the player from Control 13 | Panel and provided APIs to control the player. 14 | 15 | * libs/doubanplayer (Auto get new playlist): Update playlist without waiting. 16 | 17 | * libs/doubanplayer (Kbps): Set kbps 18 | 19 | * plugins/mpris/* (DBus MPRIS): MPRIS service. 20 | 21 | * mainwidget: Broke the UI into 3 parts: Channel Widget, Control Panel, 22 | Lyric Widget. Add linear gadient to Control Panel's border. 23 | 24 | * contorlpanel: Removed trigger areas in left and right sides. Add a 25 | lyric widget trigger button and a setting button. 26 | 27 | * lyricwidget: Modified background image. Automatically stop updating UI 28 | if the lyric widget is not showing. 29 | 30 | * settingdialog: The setting dialog. 31 | 32 | * albumimage: Click the image will open the link of album in your 33 | default web browser. 34 | 35 | * loginpanel: Removed. 36 | 37 | * libnotify: Removed. 38 | 39 | * mainwidget: Updated Shortcuts 40 | -------------------------------------------------------------------------------- /mainwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef MainWidget_H 2 | #define MainWidget_H 3 | 4 | #include 5 | #include 6 | #include "channelwidget.h" 7 | #include "controlpanel.h" 8 | #include "pausemask.h" 9 | #include "lyricwidget.h" 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Ui { 15 | class MainWidget; 16 | } 17 | 18 | class MainWidget : public QWidget 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | explicit MainWidget(QWidget *parent = 0); 24 | ~MainWidget(); 25 | 26 | void mousePressEvent(QMouseEvent *); 27 | bool isChannelWidgetShowing() const; 28 | bool isAnimationStarted() const; 29 | bool isLyricWidgetShowing() const; 30 | 31 | signals: 32 | void animationStart(); 33 | void animationFinish(); 34 | 35 | private: 36 | Ui::MainWidget *ui; 37 | QPoint dpos; 38 | QShortcut *exitShortcut; 39 | QShortcut *pauseShortcut; 40 | QShortcut *nextShortcut; 41 | QShortcut *deleteShortcut; 42 | QShortcut *likeShortcut; 43 | QShortcut *hideShortcut; 44 | 45 | bool _isChannelWidgetShowing; 46 | bool _isAnimStarted; 47 | bool _isLyricWidgetShowing; 48 | 49 | QLabel *topBorder; 50 | QLabel *bottomBorder; 51 | 52 | void animStart(); 53 | void animFinish(); 54 | 55 | QSystemTrayIcon *systemTrayIcon; 56 | 57 | public slots: 58 | void animHideChannelWidget(); 59 | void animShowChannelWidget(); 60 | 61 | void animHideLyricWidget(); 62 | void animShowLyricWidget(); 63 | }; 64 | 65 | #endif // MainWidget_H 66 | -------------------------------------------------------------------------------- /plugins/mpris/doubanmprisplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBANMPRISPLUGIN_H 2 | #define DOUBANMPRISPLUGIN_H 3 | 4 | #include 5 | #include "libs/doubanplayer.h" 6 | #include 7 | #include 8 | #include 9 | 10 | class DoubanMprisPlugin : public DoubanFMPlugin 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit DoubanMprisPlugin(QObject *parent = 0); 15 | 16 | Q_PROPERTY(bool CanControl READ CanControl) 17 | bool CanControl() const; 18 | Q_PROPERTY(bool CanGoNext READ CanGoNext) 19 | bool CanGoNext() const; 20 | Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious) 21 | bool CanGoPrevious() const; 22 | Q_PROPERTY(bool CanPause READ CanPause) 23 | bool CanPause() const; 24 | Q_PROPERTY(bool CanSeek READ CanSeek) 25 | bool CanSeek() const; 26 | Q_PROPERTY(qlonglong Position READ Position) 27 | qlonglong Position() const; 28 | Q_PROPERTY(QVariantMap Metadata READ Metadata) 29 | QVariantMap Metadata() const; 30 | Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus) 31 | QString PlaybackStatus() const; 32 | 33 | Q_PROPERTY(bool CanQuit READ CanQuit) 34 | bool CanQuit() const; 35 | Q_PROPERTY(bool CanRaise READ CanRaise) 36 | bool CanRaise() const; 37 | Q_PROPERTY(QString DesktopEntry READ DesktopEntry) 38 | QString DesktopEntry() const; 39 | Q_PROPERTY(QString Identity READ Identity) 40 | QString Identity() const; 41 | 42 | signals: 43 | 44 | public slots: 45 | void Next(); 46 | void Pause(); 47 | void Play(); 48 | void PlayPause(); 49 | void Stop(); 50 | 51 | void Quit(); 52 | void Raise(); 53 | }; 54 | 55 | #endif // DOUBANMPRISPLUGIN_H 56 | -------------------------------------------------------------------------------- /controlpanel.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLPANEL_H 2 | #define CONTROLPANEL_H 3 | 4 | #include 5 | #include "libs/doubanfm.h" 6 | #include 7 | #include 8 | #include 9 | #include "libs/doubanplayer.h" 10 | #include "libs/lyricgetter.h" 11 | #include "settingdialog.h" 12 | 13 | namespace Ui { 14 | class ControlPanel; 15 | } 16 | 17 | class ControlPanel : public QWidget 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit ControlPanel(QWidget *parent = 0); 23 | ~ControlPanel(); 24 | 25 | signals: 26 | void openChannelPanel(); 27 | void closeChannelPanel(); 28 | void openLyricPanel(); 29 | void closeLyricPanel(); 30 | 31 | private: 32 | Ui::ControlPanel *ui; 33 | DoubanFM& doubanfm; 34 | //qint32 channel; 35 | //QList channels; 36 | //QList songs; 37 | //QMediaPlayer player; 38 | DoubanPlayer& player; 39 | 40 | QNetworkAccessManager *imgmgr; 41 | //Notification *notify; 42 | 43 | void loadConfig(); 44 | void saveConfig(); 45 | 46 | //bool isPaused; 47 | LyricGetter *lyricGetter; 48 | 49 | //int volume; 50 | 51 | SettingDialog *settingDialog; 52 | 53 | public slots: 54 | void pause(); 55 | void play(); 56 | 57 | void on_nextButton_clicked(); 58 | void on_pauseButton_clicked(); 59 | void on_likeButton_clicked(); 60 | void on_trashButton_clicked(); 61 | 62 | private slots: 63 | void setSongName(const QString &name); 64 | void setArtistName(const QString &name); 65 | void setAlbumName(const QString &name); 66 | void on_settingButton_clicked(); 67 | void on_lyricButton_clicked(bool checked); 68 | void on_channelButton_clicked(bool checked); 69 | }; 70 | 71 | #endif // CONTROLPANEL_H 72 | -------------------------------------------------------------------------------- /libs/qlyricparser.cpp: -------------------------------------------------------------------------------- 1 | #include "qlyricparser.h" 2 | #include 3 | #include 4 | #include 5 | 6 | QLyricList QLyricParser::parse(QTextStream& stream) { 7 | // [00:00.00] or [00:00.000] or [00:00] 8 | QRegularExpression timeRegExp("\\[([0-9]{2}):([0-9]{2})\\.([0-9]{2,3})\\]|\\[([0-9]{2}):([0-9]{2})\\]"); 9 | QList result; 10 | int zeroCount = 0; 11 | 12 | while (!stream.atEnd()) { 13 | QString line = stream.readLine(); 14 | QList ticks; 15 | QRegularExpressionMatch match = timeRegExp.match(line); 16 | int length = 0; 17 | while (match.hasMatch()) { 18 | length = length + match.captured(0).size(); 19 | QString minute = match.captured(1); 20 | QString second = match.captured(2); 21 | QString msecond = match.captured(3); 22 | 23 | if (msecond.isEmpty()) { 24 | // This is a flag to determine if the time format is [00:00]. It will be used afterwords. 25 | QTime time(0, match.captured(4).toInt(), match.captured(5).toInt(), 0); 26 | ticks.append(time); 27 | } else { 28 | QTime time(0, minute.toInt(), second.toInt(), msecond.toInt()); 29 | ticks.append(time); 30 | } 31 | match = timeRegExp.match(line, length); 32 | } 33 | if (ticks.size() != 0) 34 | zeroCount++; 35 | QString lyricStr = line.right(line.size() - length); 36 | for (const QTime& t : ticks) { 37 | QLyric lyric; 38 | lyric.time = t; 39 | lyric.lyric = lyricStr; 40 | result.append(lyric); 41 | } 42 | } 43 | std::sort(result.begin(), result.end(), [](const QLyric& a, const QLyric& b) -> bool { return a.time < b.time; }); 44 | 45 | return result; 46 | } 47 | -------------------------------------------------------------------------------- /plugins/mpris/mprisadapter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp org.mpris.MediaPlayer2.xml -a mprisadapter.h:mprisadapter.cpp -c MprisAdapter 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 6 | * 7 | * This is an auto-generated file. 8 | * Do not edit! All changes made to it will be lost. 9 | */ 10 | 11 | #include "mprisadapter.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* 21 | * Implementation of adaptor class MprisAdapter 22 | */ 23 | 24 | MprisAdapter::MprisAdapter(QObject *parent) 25 | : QDBusAbstractAdaptor(parent) 26 | { 27 | // constructor 28 | setAutoRelaySignals(true); 29 | } 30 | 31 | MprisAdapter::~MprisAdapter() 32 | { 33 | // destructor 34 | } 35 | 36 | bool MprisAdapter::canQuit() const 37 | { 38 | // get the value of property CanQuit 39 | return qvariant_cast< bool >(parent()->property("CanQuit")); 40 | } 41 | 42 | bool MprisAdapter::canRaise() const 43 | { 44 | // get the value of property CanRaise 45 | return qvariant_cast< bool >(parent()->property("CanRaise")); 46 | } 47 | 48 | QString MprisAdapter::desktopEntry() const 49 | { 50 | // get the value of property DesktopEntry 51 | return qvariant_cast< QString >(parent()->property("DesktopEntry")); 52 | } 53 | 54 | QString MprisAdapter::identity() const 55 | { 56 | // get the value of property Identity 57 | return qvariant_cast< QString >(parent()->property("Identity")); 58 | } 59 | 60 | void MprisAdapter::Quit() 61 | { 62 | // handle method call org.mpris.MediaPlayer2.Quit 63 | QMetaObject::invokeMethod(parent(), "Quit"); 64 | } 65 | 66 | void MprisAdapter::Raise() 67 | { 68 | // handle method call org.mpris.MediaPlayer2.Raise 69 | QMetaObject::invokeMethod(parent(), "Raise"); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /plugins/mpris/mprisadapter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp org.mpris.MediaPlayer2.xml -a mprisadapter.h:mprisadapter.cpp -c MprisAdapter 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 6 | * 7 | * This is an auto-generated file. 8 | * This file may have been hand-edited. Look for HAND-EDIT comments 9 | * before re-generating it. 10 | */ 11 | 12 | #ifndef MPRISADAPTER_H_1383326427 13 | #define MPRISADAPTER_H_1383326427 14 | 15 | #include 16 | #include 17 | QT_BEGIN_NAMESPACE 18 | class QByteArray; 19 | template class QList; 20 | template class QMap; 21 | class QString; 22 | class QStringList; 23 | class QVariant; 24 | QT_END_NAMESPACE 25 | 26 | /* 27 | * Adaptor class for interface org.mpris.MediaPlayer2 28 | */ 29 | class MprisAdapter: public QDBusAbstractAdaptor 30 | { 31 | Q_OBJECT 32 | Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") 33 | Q_CLASSINFO("D-Bus Introspection", "" 34 | " \n" 35 | " \n" 36 | " \n" 37 | " \n" 38 | " \n" 39 | " \n" 40 | " \n" 41 | " \n" 42 | "") 43 | public: 44 | MprisAdapter(QObject *parent); 45 | virtual ~MprisAdapter(); 46 | 47 | public: // PROPERTIES 48 | Q_PROPERTY(bool CanQuit READ canQuit) 49 | bool canQuit() const; 50 | 51 | Q_PROPERTY(bool CanRaise READ canRaise) 52 | bool canRaise() const; 53 | 54 | Q_PROPERTY(QString DesktopEntry READ desktopEntry) 55 | QString desktopEntry() const; 56 | 57 | Q_PROPERTY(QString Identity READ identity) 58 | QString identity() const; 59 | 60 | public Q_SLOTS: // METHODS 61 | void Quit(); 62 | void Raise(); 63 | Q_SIGNALS: // SIGNALS 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /img/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/heart_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/heart_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /pausemask.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PauseMask 4 | 5 | 6 | 7 | 0 8 | 0 9 | 544 10 | 200 11 | 12 | 13 | 14 | 15 | 544 16 | 200 17 | 18 | 19 | 20 | 21 | 544 22 | 16777215 23 | 24 | 25 | 26 | Form 27 | 28 | 29 | 30 | 0 31 | 32 | 33 | QLayout::SetNoConstraint 34 | 35 | 36 | 0 37 | 38 | 39 | 0 40 | 41 | 42 | 0 43 | 44 | 45 | 0 46 | 47 | 48 | 49 | 50 | 51 | 544 52 | 200 53 | 54 | 55 | 56 | 57 | 544 58 | 16777215 59 | 60 | 61 | 62 | 63 | 文泉驿微米黑 64 | 65 | 66 | 67 | QLabel { 68 | background: rgba(255, 255, 255, 230); 69 | } 70 | 71 | 72 | Resume > 73 | 74 | 75 | Qt::AlignCenter 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /img/trash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/heart_red_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/pause_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /lyricwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LyricWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 544 10 | 151 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 0 20 | 0 21 | 544 22 | 151 23 | 24 | 25 | 26 | 27 | 544 28 | 151 29 | 30 | 31 | 32 | 33 | 544 34 | 151 35 | 36 | 37 | 38 | 39 | 40 | 41 | :/img/apple-ios-linen-texture.jpg 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 544 53 | 151 54 | 55 | 56 | 57 | 58 | 文泉驿微米黑 59 | 10 60 | 61 | 62 | 63 | border-style: solid; 64 | border-width: 10px; 65 | border-top-color: rgba(0,0,0,0); 66 | border-bottom-color: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 rgba(0,0,0,80), stop: 1 rgba(0,0,0,0)); 67 | border-left-color: rgba(0,0,0,0); 68 | border-right-color: rgba(0,0,0,0); 69 | 70 | 71 | 72 | 73 | 74 | Qt::AlignCenter 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /img/trash_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/next_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/volume.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /img/volume_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /libs/doubanplayer.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBANPLAYER_H 2 | #define DOUBANPLAYER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class DoubanPlayer : public QObject { 11 | Q_OBJECT 12 | Q_PROPERTY(qint64 position READ position NOTIFY positionChanged) 13 | Q_PROPERTY(qint64 duration READ duration) 14 | Q_PROPERTY(DoubanFMSong currentSong READ currentSong NOTIFY currentSongChanged) 15 | Q_PROPERTY(qint32 channel READ channel WRITE setChannel) 16 | Q_PROPERTY(int volume READ volume WRITE setVolume) 17 | Q_PROPERTY(QMediaPlayer::State state READ state NOTIFY stateChanged) 18 | Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged) 19 | Q_PROPERTY(qint32 kbps READ kbps WRITE setKbps) 20 | public: 21 | static DoubanPlayer& getInstance(); 22 | ~DoubanPlayer(); 23 | 24 | qint64 position() const; 25 | qint64 duration() const; 26 | 27 | const DoubanFMSong& currentSong() const; 28 | qint32 channel() const; 29 | int volume() const; 30 | QMediaPlayer::State state() const; 31 | 32 | qint32 kbps() const; 33 | bool canControl() const; 34 | 35 | signals: 36 | 37 | void currentSongChanged(const DoubanFMSong& song); 38 | void positionChanged(qint64); 39 | 40 | void receivedSkipSong(bool); 41 | void receivedRateSong(bool); 42 | void receivedTrashSong(bool); 43 | 44 | void stateChanged(QMediaPlayer::State); 45 | 46 | void canControlChanged(bool); 47 | 48 | void playing(); 49 | void paused(); 50 | void stopped(); 51 | 52 | public slots: 53 | void next(); 54 | void pause(); 55 | void play(); 56 | void stop(); 57 | 58 | void rateCurrentSong(); 59 | void unrateCurrentSong(); 60 | void trashCurrentSong(); 61 | 62 | void setChannel(qint32); 63 | void setVolume(int); 64 | 65 | void setKbps(qint32); 66 | void setCanControl(bool); 67 | 68 | private slots: 69 | void currentIndexChanged(int position); 70 | 71 | private: 72 | explicit DoubanPlayer(QObject* parent = 0); 73 | 74 | QList songs_; 75 | QMediaPlayer player_; 76 | DoubanFM& doubanfm; 77 | qint32 _channel; 78 | qint32 _volume; 79 | bool _can_control; 80 | 81 | QMediaPlaylist* bufplaylist; 82 | QList bufsongs; 83 | 84 | time_t lastPausedTime; 85 | qint32 _kbps; 86 | }; 87 | 88 | #endif // DOUBANPLAYER_H 89 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwidget.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "plugins/plugin.hpp" 9 | 10 | #include "settingdialog.h" 11 | 12 | const QString LOCAL_SOCKET_NAME = "QDoubanFM_LocalSocket"; 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | QApplication a(argc, argv); 17 | if (true) { 18 | auto translator = new QTranslator(&a); 19 | translator->load("zh_CN", QApplication::applicationDirPath() + "/i18n"); 20 | a.installTranslator(translator); 21 | //此地暂作这样的处理,以后可以增加语言切换功能 22 | } 23 | QLocalSocket socket; 24 | socket.connectToServer(LOCAL_SOCKET_NAME); 25 | if (socket.waitForConnected(500)) { 26 | qWarning() << "There is already a instance running, raising it up"; 27 | return 0; 28 | } 29 | 30 | DoubanFM::getInstance(); 31 | 32 | MainWidget w; 33 | //w.setWindowFlags(Qt::FramelessWindowHint); 34 | //w.setAttribute(Qt::WA_NoBackground); 35 | //w.setAttribute(Qt::WA_NoSystemBackground); 36 | //w.setAttribute(Qt::WA_TranslucentBackground); 37 | w.show(); 38 | 39 | QFont appfont = QApplication::font(); 40 | appfont.setStyleStrategy(QFont::PreferAntialias); 41 | a.setFont(appfont); 42 | 43 | a.setApplicationName("QDoubanFM"); 44 | a.setApplicationDisplayName("QDoubanFM"); 45 | 46 | //QImage image(":/icon.png"); 47 | //iiibiiay i = iiibiiay::fromImage(image); 48 | //Notification *n = new Notification("Hello", "world"); 49 | //n->setHint("icon_data", QVariant(qDBusRegisterMetaType(), &i)); 50 | //n->setAutoDelete(true); 51 | //n->show(); 52 | 53 | load_plugins(); 54 | 55 | QLocalServer server(&w); 56 | w.connect(&server, &QLocalServer::newConnection, [&] () { 57 | if (w.isHidden()) 58 | w.show(); 59 | else 60 | w.activateWindow(); 61 | 62 | qDebug() << "Raise window"; 63 | }); 64 | server.listen(LOCAL_SOCKET_NAME); 65 | 66 | // i18n 67 | QTranslator qtTranslator; 68 | qtTranslator.load("qt_" + QLocale::system().name(), 69 | QLibraryInfo::location(QLibraryInfo::TranslationsPath)); 70 | a.installTranslator(&qtTranslator); 71 | 72 | QTranslator myappTranslator; 73 | myappTranslator.load("i18n/" + QLocale::system().name()); 74 | a.installTranslator(&myappTranslator); 75 | 76 | return a.exec(); 77 | } 78 | -------------------------------------------------------------------------------- /volumetimepanel.cpp: -------------------------------------------------------------------------------- 1 | #include "volumetimepanel.h" 2 | #include "ui_volumetimepanel.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | VolumeTimePanel::VolumeTimePanel(QWidget *parent) : 9 | QWidget(parent), 10 | ui(new Ui::VolumeTimePanel) 11 | { 12 | ui->setupUi(this); 13 | ui->volume->setVisible(false); 14 | connect(ui->volume, &QSlider::valueChanged, [this] (int value) {emit volumeChanged(value);}); 15 | QSettings settings("QDoubanFM", "QDoubanFM"); 16 | settings.beginGroup("General"); 17 | ui->volume->setValue(settings.value("volume", 100).toInt()); 18 | settings.endGroup(); 19 | } 20 | 21 | VolumeTimePanel::~VolumeTimePanel() 22 | { 23 | delete ui; 24 | } 25 | 26 | void VolumeTimePanel::setTick(qint64 tick) { 27 | QTime displayTime(0, (tick / 60000) % 60, (tick / 1000) % 60); 28 | ui->tick->setText(//QString("") + 29 | displayTime.toString("m:ss")); //+ QString("")); 30 | } 31 | 32 | void VolumeTimePanel::enterEvent(QEvent *) { 33 | QPropertyAnimation *anim = new QPropertyAnimation(ui->volumeImg, "geometry"); 34 | anim->setDuration(300); 35 | anim->setStartValue(ui->volumeImg->geometry()); 36 | QRect endval(0, ui->volumeImg->geometry().y(), 37 | ui->volumeImg->geometry().width(), 38 | ui->volumeImg->geometry().height()); 39 | anim->setEndValue(endval); 40 | anim->setEasingCurve(QEasingCurve::OutCubic); 41 | ui->tick->setVisible(false); 42 | connect(anim, &QPropertyAnimation::finished, [this] () { 43 | ui->volume->setVisible(true); 44 | }); 45 | anim->start(QPropertyAnimation::DeleteWhenStopped); 46 | } 47 | 48 | void VolumeTimePanel::leaveEvent(QEvent *) { 49 | QPropertyAnimation *anim = new QPropertyAnimation(ui->volumeImg, "geometry"); 50 | anim->setDuration(300); 51 | anim->setStartValue(ui->volumeImg->geometry()); 52 | QRect endval(this->width() - ui->volumeImg->width(), 53 | ui->volumeImg->geometry().y(), 54 | ui->volumeImg->geometry().width(), 55 | ui->volumeImg->geometry().height()); 56 | anim->setEndValue(endval); 57 | anim->setEasingCurve(QEasingCurve::OutCubic); 58 | ui->volume->setVisible(false); 59 | connect(anim, &QPropertyAnimation::finished, [this] () { 60 | ui->tick->setVisible(true); 61 | }); 62 | anim->start(QPropertyAnimation::DeleteWhenStopped); 63 | } 64 | -------------------------------------------------------------------------------- /img/setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /libs/lyricgetter.cpp: -------------------------------------------------------------------------------- 1 | #include "lyricgetter.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static const QString LYRIC_API = "http://api.douban.com/v2/fm/lyric"; 7 | 8 | LyricGetter::LyricGetter(QObject *parent) : QObject(parent) { 9 | querymgr = new QNetworkAccessManager(this); 10 | connect(querymgr, 11 | &QNetworkAccessManager::finished, 12 | [this](QNetworkReply *reply) { 13 | if (QNetworkReply::NoError != reply->error()) { 14 | qDebug() << "Lyric not found. Err " << qPrintable(reply->errorString()); 15 | reply->deleteLater(); 16 | emit gotLyricError("Lyric doesn't exist"); 17 | return; 18 | } 19 | const QTextCodec *codec = QTextCodec::codecForName("utf-8"); 20 | QString all = codec->toUnicode(reply->readAll()); 21 | QJsonParseError parseerr; 22 | QVariant result = QJsonDocument::fromJson(all.toUtf8(), &parseerr).toVariant(); 23 | 24 | if (parseerr.error != QJsonParseError::ParseError::NoError) { 25 | qDebug() << "Lyric response parse error: " << qPrintable(parseerr.errorString()); 26 | emit gotLyricError("Lyric doesn't exist"); 27 | return; 28 | } 29 | 30 | QVariantMap obj = result.toMap(); 31 | 32 | if (obj.find("lyric") == obj.end()) { 33 | qDebug() << "Could not find lyric"; 34 | emit gotLyricError("Lyric doesn't exist"); 35 | } else { 36 | qDebug() << "Got lyric for " << qPrintable(obj["name"].toString()); 37 | QString lyric_str = obj["lyric"].toString(); 38 | QTextStream lyric(&lyric_str); 39 | emit gotLyric(QLyricParser::parse(lyric)); 40 | } 41 | 42 | reply->deleteLater(); 43 | }); 44 | } 45 | 46 | LyricGetter::~LyricGetter() { delete querymgr; } 47 | 48 | void LyricGetter::getLyric(const DoubanFMSong &song) { 49 | qDebug() << "Going to get lyric for " << qPrintable(song.artist) << " " << qPrintable(song.title); 50 | 51 | auto request = QNetworkRequest(QUrl(LYRIC_API)); 52 | request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); 53 | 54 | QUrlQuery postData; 55 | postData.addQueryItem("sid", QString::number(song.sid)); 56 | postData.addQueryItem("ssid", song.ssid); 57 | 58 | querymgr->post(request, postData.toString(QUrl::FullyEncoded).toUtf8()); 59 | } 60 | -------------------------------------------------------------------------------- /img/setting_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DoubanFM 客户端 2 | 3 | 使用Qt5编写的DoubanFM客户端 4 | 5 | 如果你喜欢本项目的话,给我买瓶啤酒喝好不;) 6 | 7 | 10 | 11 | ![支付宝](https://tfsimg.alipay.com/images/mobilecodec/T1nwBdXXdlXXXXXXXX) 12 | 13 | ### 注意 14 | 15 | * Ubuntu由于不支持Qt的`QSystemTrayIcon`,直接使用会有Bug,**必须**在生成`Makefile`前在`doubanfm-qt.pro`中删除`DEFINES += WITH_SYSTEM_TRAY_ICON`! 16 | 17 | * KDE用户在打开时载入会出现`SIGSEGM`,目前*原因未知*,如果有人知道解决方法请告诉我谢谢!Issues中有提及。 18 | 19 | * `Ctrl`+`W`后软件界面消失为**正常行为**,只要重新点图标(启动器)就可以唤醒。若强行Kill会导致此功能失效,需要手动删除`/tmp/QDoubanFM_LocalSocket`,或者会在其他位置,不同发行版可能位置不同。 20 | 21 | * Windows及OSX上的有兼容性问题,但功能大致正常。 22 | 23 | [API接口文档](https://github.com/zonyitoo/doubanfm-qt/wiki/%E8%B1%86%E7%93%A3FM-API) 24 | 25 | ## 依赖 26 | * qt5-base 27 | * libqt5network5 libqt5gui5 libqt5core5 libqt5multimedia5 qtmultimedia5-dev 28 | * gstreamer0.10-fluendo-mp3 29 | * libpulse0 libpulse-dev 30 | * libqt5dbus5 31 | * libqt5multimedia5-plugins 32 | 33 | 不同平台相应的包名不一样,基本需求是Qt5的基础包,Qt5 Multimedia模块,Qt5 DBus模块,mp3解码器 34 | 35 | ## 截图 36 | 37 | ![截图](https://gitcafe.com/zonyitoo/doubanfm-qt/raw/master/screenshot.png) 38 | 39 | ## 安装方法 40 | 41 | * 在Archlinux上测试通过 42 | 43 | ```bash 44 | lupdate doubanfm-qt.pro 45 | lrelease doubanfm-qt.pro 46 | qmake doubanfm-qt.pro 47 | make 48 | ``` 49 | 50 | 得到`doubanfm-qt`后直接运行即可 51 | 52 | 或运行`install.sh`脚本来安装到系统中(For Linux Only),安装好后在桌面系中的的启动器中找到`QDoubanFM`运行即可 53 | 54 | * Ubuntu 55 | 56 | 已经在Ubuntu 13.04 x86\_64至Ubuntu 15.04 x86\_64上测试通过。 57 | 58 | 在Ubuntu版本低于15.04时,Qt的系统托盘不能正常工作,所以 59 | 先使用编辑器打开`doubanfm-qt.pro`,删除 60 | 61 | ``` 62 | DEFINES += WITH_SYSTEM_TRAY_ICON 63 | ``` 64 | 65 | 剩余步骤与上面相同。 66 | 67 | * openSUSE 68 | 69 | [一键安装](https://software.opensuse.org/download.html?project=home%3Aopensuse_zh&package=doubanfm-qt5) 70 | 71 | * Windows或OSX 72 | 73 | 这两个系统比较特殊,建议到官网下载QtCreator打开`doubanfm-qt.pro`选择`Release`编译。 74 | 75 | ## 快捷键 76 | `Ctrl` + `W` 隐藏窗口,最小化到托盘 77 | 78 | `Ctrl` + `Q` 退出 79 | 80 | `Space` 暂停 81 | 82 | `S` 跳过 83 | 84 | `D` 不再播放 85 | 86 | `F` 标记/取消标记红心 87 | 88 | ## TODO 89 | - [x] 基本播放功能 90 | - [x] 频道选择 91 | - [x] 用户登录 92 | - [x] 快捷键 93 | - [x] 动画 94 | - [ ] i18n支持 95 | - [x] Linux的播放提示 + DBus 96 | - [x] 后台播放 + 托盘提示 97 | - [x] 歌词 98 | 99 | ## KNOWN BUGS 100 | - [x] 在长时间暂停后重启播放会崩溃 101 | - [x] 在网络不好时卡住会崩溃 102 | - [x] 动画有Bug,若打开了频道界面然后鼠标离开,则会让控制面板滑动位置出错 103 | - [x] KDE4.9 上未知原因崩溃 (Fixed in KDE5) 104 | - [ ] Ubuntu不支持`QSystemTrayIcon`导致软件不能正常退出 105 | 106 | ## LICENSE 107 | 本项目基于MIT协议发布 108 | 109 | MIT: [http://rem.mit-license.org](http://rem.mit-license.org) 110 | 111 | -------------------------------------------------------------------------------- /libs/doubanfm.h: -------------------------------------------------------------------------------- 1 | #ifndef DOUBAN_H 2 | #define DOUBAN_H 3 | 4 | #include 5 | #include 6 | #include "libs/douban_types.h" 7 | #include 8 | 9 | static const unsigned int DOUBAN_MANAGER_ARRAY_SIZE = 9; 10 | 11 | class DoubanFM : public QObject { 12 | Q_OBJECT 13 | public: 14 | ~DoubanFM(); 15 | static DoubanFM& getInstance(); 16 | 17 | void userLogin(const QString& email, const QString& password); 18 | void userLogout(); 19 | void userReLogin(); 20 | 21 | void getNewPlayList(const qint32& channel, qint32 kbps = 64); 22 | void getPlayingList(const qint32& channel, const quint32& sid, qint32 kbps = 64); 23 | void rateSong(const quint32& sid, const qint32& channel, const bool toRate); 24 | void unrateSong(const quint32& sid, const qint32& channel); 25 | void skipSong(const quint32& sid, const qint32& channel); 26 | void songEnd(const quint32& sid, const qint32& channel); 27 | void byeSong(const quint32& sid, const qint32& channel); 28 | 29 | void getChannels(); 30 | 31 | void setUser(const DoubanUser& user); 32 | const DoubanUser* getUser() const; 33 | bool hasLogin(); 34 | 35 | signals: 36 | void receivedNewList(const QList& songs); 37 | void receivedPlayingList(const QList& songs); 38 | void receivedRateSong(bool succeed); 39 | void receivedSkipSong(bool succeed); 40 | void receivedCurrentEnd(bool succeed); 41 | void receivedByeSong(bool succeed); 42 | void receivedChannels(const QList& channels); 43 | void loginSucceed(const DoubanUser& user); 44 | void loginFailed(const QString& errmsg); 45 | void logoffSucceed(); 46 | 47 | private slots: 48 | void onReceivedAuth(QNetworkReply* reply); 49 | void onReceivedRelogin(QNetworkReply* reply); 50 | void onReceivedNewList(QNetworkReply* reply); 51 | void onReceivedPlayingList(QNetworkReply* reply); 52 | void onReceivedRateSong(QNetworkReply* reply); 53 | void onReceivedSkipSong(QNetworkReply* reply); 54 | void onReceivedCurrentEnd(QNetworkReply* reply); 55 | void onReceivedByeSong(QNetworkReply* reply); 56 | void onReceivedChannels(QNetworkReply* reply); 57 | 58 | private: 59 | explicit DoubanFM(QObject* parent = 0); 60 | /** 61 | * @brief _managers 62 | * 63 | * 0: User Relogin 64 | * 1: new list 65 | * 2: rate song/unrate song 66 | * 3: skip song 67 | * 4: current end 68 | * 5: bye song 69 | * 6: get channels 70 | * 7: playing list 71 | * 8: Login/Logout 72 | */ 73 | QNetworkAccessManager* _managers[DOUBAN_MANAGER_ARRAY_SIZE]; 74 | 75 | std::unique_ptr _user; 76 | }; 77 | 78 | #endif // DOUBAN_H 79 | -------------------------------------------------------------------------------- /volumetimepanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | VolumeTimePanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 102 10 | 20 11 | 12 | 13 | 14 | 15 | 16 | 43 17 | 0 18 | 32 19 | 20 20 | 21 | 22 | 23 | 24 | 9 25 | 50 26 | false 27 | false 28 | 29 | 30 | 31 | 0:00 32 | 33 | 34 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 35 | 36 | 37 | 38 | 39 | 40 | 80 41 | 0 42 | 20 43 | 20 44 | 45 | 46 | 47 | 48 | 49 | 50 | :/img/volume.svg 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 25 60 | 0 61 | 75 62 | 20 63 | 64 | 65 | 66 | QSlider::groove:horizontal { 67 | 68 | background: #727982; 69 | height: 5px; 70 | border-radius: 4px; 71 | } 72 | 73 | QSlider::sub-page:horizontal { 74 | background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 75 | stop: 0 #66e, stop: 1 #bbf); 76 | background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, 77 | stop: 0 #bbf, stop: 1 #55f); 78 | border: 1px solid #777; 79 | height: 5px; 80 | border-radius: 4px; 81 | } 82 | 83 | QSlider::add-page:horizontal { 84 | background: #fff; 85 | border: 1px solid #777; 86 | height: 5px; 87 | border-radius: 4px; 88 | } 89 | 90 | QSlider::handle:horizontal { 91 | width:10px 92 | } 93 | 94 | QSlider::handle:horizontal:hover { 95 | width: 10px 96 | } 97 | 98 | QSlider::sub-page:horizontal:disabled { 99 | background: #94ad6b; 100 | border-color: #999; 101 | } 102 | 103 | QSlider::add-page:horizontal:disabled { 104 | background: #eee; 105 | border-color: #999; 106 | } 107 | 108 | QSlider::handle:horizontal:disabled { 109 | width: 0px 110 | } 111 | 112 | 113 | Qt::Horizontal 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /doubanfm-qt.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2013-08-21T21:55:51 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui network multimedia xml 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = doubanfm-qt 12 | macx:TARGET = DoubanFM-Qt 13 | TEMPLATE = app 14 | 15 | INCLUDEPATH+=. 16 | TMPDIR=tmp 17 | CONFIG(release,debug|release){ 18 | TMPDIR=release_$${TMPDIR} 19 | } 20 | MOC_DIR=$${TMPDIR}/moc 21 | OBJECTS_DIR=$${TMPDIR}/obj 22 | UI_DIR=$${TMPDIR}/ui 23 | COMPILER=$$[QMAKE_SPEC] 24 | 25 | TRANSLATIONS += i18n/zh_CN.ts 26 | 27 | # Don't open it if you are using Ubuntu below 15.04 28 | DEFINES += WITH_SYSTEM_TRAY_ICON 29 | 30 | SOURCES += main.cpp\ 31 | mainwidget.cpp \ 32 | channelwidget.cpp \ 33 | controlpanel.cpp \ 34 | libs/doubanfm.cpp \ 35 | horizontalslider.cpp \ 36 | volumetimepanel.cpp \ 37 | pausemask.cpp \ 38 | libs/qlyricparser.cpp \ 39 | libs/lyricgetter.cpp \ 40 | lyricwidget.cpp \ 41 | albumimage.cpp \ 42 | triggerarea.cpp \ 43 | albumwidget.cpp \ 44 | libs/doubanplayer.cpp \ 45 | settingdialog.cpp \ 46 | plugins/doubanfmplugin.cpp \ 47 | libs/doubanchannelmanager.cpp 48 | 49 | HEADERS += mainwidget.h \ 50 | channelwidget.h \ 51 | controlpanel.h \ 52 | libs/douban_types.h \ 53 | libs/doubanfm.h \ 54 | horizontalslider.h \ 55 | volumetimepanel.h \ 56 | pausemask.h \ 57 | libs/qlyricparser.h \ 58 | libs/lyricgetter.h \ 59 | lyricwidget.h \ 60 | albumimage.h \ 61 | triggerarea.h \ 62 | channelwidgettrigger.h \ 63 | albumwidget.h \ 64 | libs/doubanplayer.h \ 65 | settingdialog.h \ 66 | plugins/doubanfmplugin.h \ 67 | plugins/plugin.hpp \ 68 | libs/doubanchannelmanager.h 69 | 70 | !win32:!macx { 71 | QT += dbus 72 | DEFINES += LINUX WITH_MPRIS_PLUGIN 73 | 74 | SOURCES += plugins/mpris/mprisplayeradapter.cpp \ 75 | plugins/mpris/mprisadapter.cpp \ 76 | plugins/mpris/doubanmprisplugin.cpp 77 | 78 | HEADERS += plugins/mpris/mprisplayeradapter.h \ 79 | plugins/mpris/mprisadapter.h \ 80 | plugins/mpris/doubanmprisplugin.h 81 | 82 | OTHER_FILES += \ 83 | plugins/org.mpris.MediaPlayer2.xml \ 84 | plugins/org.mpris.MediaPlayer2.Player.xml \ 85 | plugins/org.freedesktop.DBus.Properties.xml 86 | } 87 | 88 | FORMS += mainwidget.ui \ 89 | channelwidget.ui \ 90 | controlpanel.ui \ 91 | volumetimepanel.ui \ 92 | pausemask.ui \ 93 | lyricwidget.ui \ 94 | albumwidget.ui \ 95 | settingdialog.ui 96 | 97 | RESOURCES += \ 98 | imgs.qrc 99 | 100 | CONFIG += c++11 101 | 102 | win32 { 103 | RC_FILE = data/win32/winrc.rc 104 | } 105 | 106 | macx { 107 | ICON = data/macx/QDoubanFM.icns 108 | QMAKE_INFO_PLIST = data/macx/Info.plist 109 | } 110 | -------------------------------------------------------------------------------- /plugins/mpris/mprisplayeradapter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp org.mpris.MediaPlayer2.Player.xml -a mprisplayeradapter.h:mprisplayeradapter.cpp -c MprisPlayerAdapter 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 6 | * 7 | * This is an auto-generated file. 8 | * This file may have been hand-edited. Look for HAND-EDIT comments 9 | * before re-generating it. 10 | */ 11 | 12 | #ifndef MPRISPLAYERADAPTER_H_1383395187 13 | #define MPRISPLAYERADAPTER_H_1383395187 14 | 15 | #include 16 | #include 17 | QT_BEGIN_NAMESPACE 18 | class QByteArray; 19 | template class QList; 20 | template class QMap; 21 | class QString; 22 | class QStringList; 23 | class QVariant; 24 | QT_END_NAMESPACE 25 | 26 | /* 27 | * Adaptor class for interface org.mpris.MediaPlayer2.Player 28 | */ 29 | class MprisPlayerAdapter: public QDBusAbstractAdaptor 30 | { 31 | Q_OBJECT 32 | Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") 33 | Q_CLASSINFO("D-Bus Introspection", "" 34 | " \n" 35 | " \n" 36 | " \n" 37 | " \n" 38 | " \n" 39 | " \n" 40 | " \n" 41 | " \n" 42 | " \n" 43 | " \n" 44 | " \n" 45 | " \n" 46 | " \n" 47 | " \n" 48 | " \n" 49 | " \n" 50 | " \n" 51 | "") 52 | public: 53 | MprisPlayerAdapter(QObject *parent); 54 | virtual ~MprisPlayerAdapter(); 55 | 56 | public: // PROPERTIES 57 | Q_PROPERTY(bool CanControl READ canControl) 58 | bool canControl() const; 59 | 60 | Q_PROPERTY(bool CanGoNext READ canGoNext) 61 | bool canGoNext() const; 62 | 63 | Q_PROPERTY(bool CanGoPrevious READ canGoPrevious) 64 | bool canGoPrevious() const; 65 | 66 | Q_PROPERTY(bool CanPause READ canPause) 67 | bool canPause() const; 68 | 69 | Q_PROPERTY(bool CanSeek READ canSeek) 70 | bool canSeek() const; 71 | 72 | Q_PROPERTY(QVariantMap Metadata READ metadata) 73 | QVariantMap metadata() const; 74 | 75 | Q_PROPERTY(QString PlaybackStatus READ playbackStatus) 76 | QString playbackStatus() const; 77 | 78 | Q_PROPERTY(qlonglong Position READ position) 79 | qlonglong position() const; 80 | 81 | public Q_SLOTS: // METHODS 82 | void Next(); 83 | void Pause(); 84 | void Play(); 85 | void PlayPause(); 86 | void Stop(); 87 | Q_SIGNALS: // SIGNALS 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /horizontalslider.cpp: -------------------------------------------------------------------------------- 1 | #include "horizontalslider.h" 2 | #include 3 | #include 4 | #include 5 | 6 | HorizontalSlider::HorizontalSlider(QWidget *parent) : 7 | QWidget(parent), hbox(nullptr) 8 | { 9 | container = new QWidget(this); 10 | container->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 11 | curIndex = 0; 12 | } 13 | 14 | HorizontalSlider::~HorizontalSlider() { 15 | delete container; 16 | } 17 | 18 | void HorizontalSlider::clear() { 19 | if (hbox != nullptr) { 20 | container->setLayout(nullptr); 21 | delete hbox; 22 | } 23 | hbox = new QHBoxLayout(container); 24 | hbox->setMargin(0); 25 | hbox->setSpacing(0); 26 | hbox->setContentsMargins(0, 0, 0, 0); 27 | container->setLayout(hbox); 28 | } 29 | 30 | void HorizontalSlider::setChildren(const QList& list) { 31 | clear(); 32 | 33 | items = list; 34 | int accu = 0; 35 | for (QWidget *object : list) { 36 | object->setParent(container); 37 | hbox->addWidget(object); 38 | widths.append(object->width()); 39 | accu += object->width(); 40 | } 41 | container->setMaximumWidth(accu); 42 | container->setMinimumWidth(accu); 43 | container->setMaximumHeight(this->height()); 44 | container->setMinimumHeight(this->height()); 45 | curIndex = 0; 46 | container->move(QPoint(this->width() / 2 - widths[0] / 2, 0)); 47 | refresh(); 48 | } 49 | 50 | void HorizontalSlider::addChild(QWidget *widget) { 51 | hbox->addWidget(widget); 52 | widths.append(widget->width()); 53 | auto orig_width = container->maximumWidth(); 54 | container->setMaximumWidth(orig_width + widget->width()); 55 | container->setMinimumWidth(orig_width + widget->width()); 56 | } 57 | 58 | void HorizontalSlider::scrollToIndex(int index) { 59 | if (index == curIndex) return; 60 | if (index > numberOfChildren() - 1) return; 61 | 62 | this->curIndex = index; 63 | 64 | QPropertyAnimation *anim = new QPropertyAnimation(container, "geometry"); 65 | anim->setDuration(300); 66 | anim->setStartValue(container->geometry()); 67 | int accuwidth = 0; 68 | for (int i = 0; i < index; ++ i) accuwidth += widths[i]; 69 | accuwidth += widths[index] / 2 - this->width() / 2; 70 | QRect endval(-accuwidth, 71 | container->geometry().y(), 72 | container->geometry().width(), 73 | container->geometry().height() 74 | ); 75 | anim->setEndValue(endval); 76 | anim->setEasingCurve(QEasingCurve::OutCubic); 77 | connect(anim, &QPropertyAnimation::finished, [=] () { 78 | emit scrollFinished(); 79 | }); 80 | anim->start(QAbstractAnimation::DeleteWhenStopped); 81 | } 82 | 83 | int HorizontalSlider::numberOfChildren() { 84 | return items.size(); 85 | } 86 | 87 | int HorizontalSlider::currentIndex() { 88 | return curIndex; 89 | } 90 | 91 | void HorizontalSlider::refresh() { 92 | 93 | } 94 | 95 | QWidget *HorizontalSlider::currentObject() const { 96 | return items[curIndex]; 97 | } 98 | 99 | QWidget *HorizontalSlider::preObject() const { 100 | return items[curIndex - 1]; 101 | } 102 | 103 | QWidget *HorizontalSlider::nextObject() const { 104 | return items[curIndex + 1]; 105 | } 106 | -------------------------------------------------------------------------------- /plugins/mpris/mprisplayeradapter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by qdbusxml2cpp version 0.8 3 | * Command line was: qdbusxml2cpp org.mpris.MediaPlayer2.Player.xml -a mprisplayeradapter.h:mprisplayeradapter.cpp -c MprisPlayerAdapter 4 | * 5 | * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). 6 | * 7 | * This is an auto-generated file. 8 | * Do not edit! All changes made to it will be lost. 9 | */ 10 | 11 | #include "mprisplayeradapter.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /* 21 | * Implementation of adaptor class MprisPlayerAdapter 22 | */ 23 | 24 | MprisPlayerAdapter::MprisPlayerAdapter(QObject *parent) 25 | : QDBusAbstractAdaptor(parent) 26 | { 27 | // constructor 28 | setAutoRelaySignals(true); 29 | } 30 | 31 | MprisPlayerAdapter::~MprisPlayerAdapter() 32 | { 33 | // destructor 34 | } 35 | 36 | bool MprisPlayerAdapter::canControl() const 37 | { 38 | // get the value of property CanControl 39 | return qvariant_cast< bool >(parent()->property("CanControl")); 40 | } 41 | 42 | bool MprisPlayerAdapter::canGoNext() const 43 | { 44 | // get the value of property CanGoNext 45 | return qvariant_cast< bool >(parent()->property("CanGoNext")); 46 | } 47 | 48 | bool MprisPlayerAdapter::canGoPrevious() const 49 | { 50 | // get the value of property CanGoPrevious 51 | return qvariant_cast< bool >(parent()->property("CanGoPrevious")); 52 | } 53 | 54 | bool MprisPlayerAdapter::canPause() const 55 | { 56 | // get the value of property CanPause 57 | return qvariant_cast< bool >(parent()->property("CanPause")); 58 | } 59 | 60 | bool MprisPlayerAdapter::canSeek() const 61 | { 62 | // get the value of property CanSeek 63 | return qvariant_cast< bool >(parent()->property("CanSeek")); 64 | } 65 | 66 | QVariantMap MprisPlayerAdapter::metadata() const 67 | { 68 | // get the value of property Metadata 69 | return qvariant_cast< QVariantMap >(parent()->property("Metadata")); 70 | } 71 | 72 | QString MprisPlayerAdapter::playbackStatus() const 73 | { 74 | // get the value of property PlaybackStatus 75 | return qvariant_cast< QString >(parent()->property("PlaybackStatus")); 76 | } 77 | 78 | qlonglong MprisPlayerAdapter::position() const 79 | { 80 | // get the value of property Position 81 | return qvariant_cast< qlonglong >(parent()->property("Position")); 82 | } 83 | 84 | void MprisPlayerAdapter::Next() 85 | { 86 | // handle method call org.mpris.MediaPlayer2.Player.Next 87 | QMetaObject::invokeMethod(parent(), "Next"); 88 | } 89 | 90 | void MprisPlayerAdapter::Pause() 91 | { 92 | // handle method call org.mpris.MediaPlayer2.Player.Pause 93 | QMetaObject::invokeMethod(parent(), "Pause"); 94 | } 95 | 96 | void MprisPlayerAdapter::Play() 97 | { 98 | // handle method call org.mpris.MediaPlayer2.Player.Play 99 | QMetaObject::invokeMethod(parent(), "Play"); 100 | } 101 | 102 | void MprisPlayerAdapter::PlayPause() 103 | { 104 | // handle method call org.mpris.MediaPlayer2.Player.PlayPause 105 | QMetaObject::invokeMethod(parent(), "PlayPause"); 106 | } 107 | 108 | void MprisPlayerAdapter::Stop() 109 | { 110 | // handle method call org.mpris.MediaPlayer2.Player.Stop 111 | QMetaObject::invokeMethod(parent(), "Stop"); 112 | } 113 | 114 | -------------------------------------------------------------------------------- /albumwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AlbumWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 200 10 | 200 11 | 12 | 13 | 14 | 15 | 文泉驿微米黑 16 | 17 | 18 | 19 | Form 20 | 21 | 22 | 23 | 24 | 0 25 | 0 26 | 200 27 | 200 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 5 39 | 201 40 | 191 41 | 42 | 43 | 44 | 45 | 46 | 47 | Qt::Vertical 48 | 49 | 50 | 51 | 20 52 | 40 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Qt::Horizontal 63 | 64 | 65 | 66 | 40 67 | 20 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 文泉驿微米黑 77 | 78 | 79 | 80 | background: rgba(0,0,0,80); 81 | border-radius: 2px; 82 | 83 | 84 | <font color="white">&nbsp;&nbsp;View album info&nbsp;&nbsp;</font> 85 | 86 | 87 | Qt::AlignCenter 88 | 89 | 90 | 91 | 92 | 93 | 94 | Qt::Horizontal 95 | 96 | 97 | 98 | 40 99 | 20 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Qt::Vertical 110 | 111 | 112 | 113 | 20 114 | 40 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | AlbumImage 125 | QLabel 126 |
albumimage.h
127 |
128 |
129 | 130 | 131 |
132 | -------------------------------------------------------------------------------- /mainwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 544 10 | 200 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 544 22 | 200 23 | 24 | 25 | 26 | 27 | 544 28 | 200 29 | 30 | 31 | 32 | douban.fm 33 | 34 | 35 | 36 | :/icon.png:/icon.png 37 | 38 | 39 | 40 | 41 | 0 42 | 0 43 | 544 44 | 56 45 | 46 | 47 | 48 | 49 | 544 50 | 56 51 | 52 | 53 | 54 | 55 | 544 56 | 56 57 | 58 | 59 | 60 | 61 | 62 | 63 | 0 64 | 0 65 | 544 66 | 200 67 | 68 | 69 | 70 | 71 | 544 72 | 200 73 | 74 | 75 | 76 | 77 | 544 78 | 200 79 | 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 0 87 | 544 88 | 200 89 | 90 | 91 | 92 | 93 | 94 | 95 | 0 96 | 50 97 | 544 98 | 151 99 | 100 | 101 | 102 | 103 | 544 104 | 151 105 | 106 | 107 | 108 | 109 | 544 110 | 151 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | LyricWidget 119 | QWidget 120 |
lyricwidget.h
121 | 1 122 |
123 | 124 | ChannelWidget 125 | QWidget 126 |
channelwidget.h
127 | 1 128 |
129 | 130 | ControlPanel 131 | QWidget 132 |
controlpanel.h
133 | 1 134 |
135 | 136 | PauseMask 137 | QWidget 138 |
pausemask.h
139 | 1 140 |
141 |
142 | 143 | 144 | 145 | 146 |
147 | -------------------------------------------------------------------------------- /albumimage.cpp: -------------------------------------------------------------------------------- 1 | #include "albumimage.h" 2 | #include 3 | #include 4 | #include 5 | 6 | AlbumImage::AlbumImage(QWidget *parent) : 7 | QLabel(parent), 8 | _opacity(0.0) 9 | { 10 | before = QImage(":/img/album_init.jpg").scaled(200, 200, Qt::KeepAspectRatioByExpanding); 11 | this->setPixmap(QPixmap::fromImage(before)); 12 | } 13 | 14 | void AlbumImage::setAlbumImage(const QImage& src) { 15 | QPropertyAnimation *anim = new QPropertyAnimation(this, "opacity"); 16 | anim->setDuration(1000); 17 | anim->setStartValue(this->opacity()); 18 | anim->setEndValue(1.0); 19 | now = src.scaled(200, 200, Qt::KeepAspectRatioByExpanding).copy(0, 0, 200, 200); 20 | 21 | connect(anim, &QPropertyAnimation::finished, [this] () { 22 | before = now; 23 | _opacity = 0.0; 24 | }); 25 | anim->start(QPropertyAnimation::DeleteWhenStopped); 26 | //this->setPixmap(QPixmap::fromImage(src.scaled(200, 200, Qt::KeepAspectRatioByExpanding) 27 | // .copy(0, 0, 200, 200))); 28 | //QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(this); 29 | //effect->setOpacity(_opacity); 30 | /* 31 | static const QSize picsize(170, 170); 32 | 33 | QImage image = src.scaled(picsize.width(), picsize.height(), Qt::KeepAspectRatioByExpanding) 34 | .copy(0, 0, picsize.width(), picsize.height()); 35 | 36 | // OLD 37 | QLinearGradient gardient(QPoint(0, 0), QPoint(0, image.height())); 38 | gardient.setColorAt(0, Qt::white); 39 | gardient.setColorAt(0.1, Qt::black); 40 | 41 | QImage mask(picsize, image.format()); 42 | QPainter painter(&mask); 43 | painter.fillRect(mask.rect(), gardient); 44 | painter.end(); 45 | 46 | QImage reflection = image.mirrored(); 47 | reflection.setAlphaChannel(mask); 48 | 49 | QImage imgdraw(QSize(this->width(), this->height()), QImage::Format_ARGB32_Premultiplied); 50 | QPainter album_painter(&imgdraw); 51 | 52 | album_painter.drawImage(0, 0, image); 53 | //album_painter.setOpacity(0.5); 54 | album_painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 55 | album_painter.drawImage(0, image.height(), reflection); 56 | album_painter.end(); 57 | 58 | // NEW 59 | QPixmap mirror(image.width(), this->height() - image.height()); 60 | mirror.fill(Qt::transparent); 61 | QPainter mirrorp(&mirror); 62 | QLinearGradient linearGrad(QPoint(mirror.width(), 0), QPoint(mirror.width(), mirror.height() / 2)); 63 | linearGrad.setColorAt(1, QColor(255,255,255,0)); 64 | linearGrad.setColorAt(0.8, QColor(255,255,255,20)); 65 | linearGrad.setColorAt(0, QColor(255,255,255,200)); 66 | mirrorp.setBrush(linearGrad); 67 | mirrorp.fillRect(0, 0, mirror.width(), mirror.height(), QBrush(linearGrad)); 68 | mirrorp.setCompositionMode(QPainter::CompositionMode_SourceIn); 69 | mirrorp.drawPixmap(0, 0, QPixmap::fromImage(image.copy(0, 2 * image.height() - this->height(), image.width(), this->height() - image.height()).mirrored(false, true))); 70 | mirrorp.end(); 71 | 72 | QImage imgdraw(QSize(this->width(), this->height()), QImage::Format_ARGB32_Premultiplied); 73 | imgdraw.fill(Qt::transparent); 74 | QPainter album_painter(&imgdraw); 75 | album_painter.drawImage(0, 0, image); 76 | //album_painter.setOpacity(0.8); 77 | album_painter.drawPixmap(0, image.height(), mirror); 78 | album_painter.end(); 79 | QPixmap empty(this->size()); 80 | empty.fill(Qt::transparent); 81 | this->setPixmap(empty); 82 | this->setPixmap(QPixmap::fromImage(imgdraw)); 83 | */ 84 | } 85 | 86 | void AlbumImage::mousePressEvent(QMouseEvent *event) { 87 | if (event->button() == Qt::LeftButton) { 88 | emit clicked(); 89 | } 90 | } 91 | 92 | qreal AlbumImage::opacity() const { 93 | return _opacity; 94 | } 95 | 96 | void AlbumImage::setOpacity(qreal opacity) { 97 | _opacity = opacity; 98 | QPixmap pixmap(this->size()); 99 | QPainter painter(&pixmap); 100 | painter.setOpacity(_opacity); 101 | painter.drawImage(0, 0, now); 102 | painter.setOpacity(1.0 - _opacity); 103 | painter.drawImage(0, 0, before); 104 | painter.end(); 105 | this->setPixmap(pixmap); 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /channelwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "channelwidget.h" 2 | #include "ui_channelwidget.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "mainwidget.h" 8 | #include "libs/doubanplayer.h" 9 | 10 | #define HIGHTLIGHT_STYLE "font-size:20px;font-style:bold;color:white;" 11 | #define DARK_STYLE "font-size:15px;font-style:normal; color:grey" 12 | ChannelWidget::ChannelWidget(QWidget *parent) : 13 | QWidget(parent), 14 | ui(new Ui::ChannelWidget), 15 | doubanfm(DoubanFM::getInstance()) 16 | { 17 | ui->setupUi(this); 18 | 19 | ui->nextButton->raise(); 20 | ui->prevButton->raise(); 21 | 22 | connect(&doubanfm, SIGNAL(receivedChannels(QList)), 23 | this, SLOT(setChannels(QList))); 24 | doubanfm.getChannels(); 25 | 26 | timer = new QTimer(this); 27 | connect(timer, &QTimer::timeout, [=] () { 28 | qDebug() << "Switch to channel" << channels[ui->slider->currentIndex()].name; 29 | emit channelChanged(channels[ui->slider->currentIndex()].channel_id); 30 | }); 31 | connect(ui->slider, &HorizontalSlider::scrollFinished, [this] () { 32 | channel = channels[ui->slider->currentIndex()].channel_id; 33 | if (timer && timer->isActive()) 34 | timer->stop(); 35 | timer->setSingleShot(true); 36 | timer->start(2000); 37 | }); 38 | 39 | QSettings settings("QDoubanFM", "QDoubanFM"); 40 | settings.beginGroup("General"); 41 | channel = settings.value("channel", 1).toInt(); 42 | if (!doubanfm.hasLogin() && channel == -3) channel = 1; 43 | settings.endGroup(); 44 | 45 | connect(&doubanfm, &DoubanFM::logoffSucceed, [this] () { 46 | if (ui->slider->currentIndex() == 0){ 47 | ui->slider->currentObject()->setStyleSheet(DARK_STYLE); 48 | ui->slider->scrollToIndex(1); 49 | ui->slider->currentObject()->setStyleSheet(HIGHTLIGHT_STYLE); 50 | } else { 51 | doubanfm.getNewPlayList(channel); 52 | } 53 | qDebug() << "Logoff successfully. Refreshing playlist"; 54 | }); 55 | 56 | connect(this, SIGNAL(channelChanged(qint32)), 57 | &DoubanPlayer::getInstance(), SLOT(setChannel(qint32))); 58 | } 59 | 60 | ChannelWidget::~ChannelWidget() 61 | { 62 | delete ui; 63 | } 64 | 65 | void ChannelWidget::on_prevButton_clicked() 66 | { 67 | if (ui->slider->currentIndex() == 0) return; 68 | if (!doubanfm.hasLogin() && ui->slider->currentIndex() == 1) return; 69 | ui->slider->scrollToIndex(ui->slider->currentIndex() - 1); 70 | 71 | ui->slider->nextObject()->setStyleSheet(DARK_STYLE); 72 | ui->slider->currentObject()->setStyleSheet(HIGHTLIGHT_STYLE); 73 | 74 | } 75 | 76 | void ChannelWidget::on_nextButton_clicked() 77 | { 78 | if (ui->slider->currentIndex() == ui->slider->numberOfChildren() - 1) return; 79 | ui->slider->scrollToIndex(ui->slider->currentIndex() + 1); 80 | 81 | ui->slider->preObject()->setStyleSheet(DARK_STYLE); 82 | ui->slider->currentObject()->setStyleSheet(HIGHTLIGHT_STYLE); 83 | 84 | } 85 | 86 | void ChannelWidget::setChannels(const QList& channels) { 87 | this->channels = channels; 88 | int curindex = 0; 89 | QList labels; 90 | for (int i = 0; i < channels.size(); ++ i) { 91 | const DoubanChannel& channel = channels[i]; 92 | QLabel *label = new QLabel(ui->slider); 93 | QFont font("Sans", 12); 94 | font.setStyleStrategy(QFont::PreferAntialias); 95 | label->setFont(font); 96 | label->setText(channel.name+" MHz"); 97 | label->setStyleSheet(DARK_STYLE); 98 | label->setMinimumSize(ui->slider->width() / 3, ui->slider->height()); 99 | label->setMaximumSize(ui->slider->width() / 3, ui->slider->height()); 100 | label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); 101 | labels.append(label); 102 | if (channel.channel_id == this->channel) curindex = i; 103 | qDebug() << "Channel name=" << qPrintable(channel.name) << " id=" << channel.channel_id; 104 | } 105 | ui->slider->setChildren(labels); 106 | ui->slider->scrollToIndex(curindex); 107 | ui->slider->currentObject()->setStyleSheet(HIGHTLIGHT_STYLE); 108 | 109 | //pnt->setText(pnt->text().replace("grey", "white").replace("", "").replace("", "")); 110 | } 111 | 112 | void ChannelWidget::leaveEvent(QEvent *) { 113 | emit mouseLeave(); 114 | } 115 | 116 | void ChannelWidget::wheelEvent(QWheelEvent *ev){ 117 | if(ev->angleDelta().y()<0){ 118 | on_nextButton_clicked(); 119 | }else{ 120 | on_prevButton_clicked(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /channelwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ChannelWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 544 10 | 56 11 | 12 | 13 | 14 | 15 | 544 16 | 56 17 | 18 | 19 | 20 | 21 | 544 22 | 56 23 | 24 | 25 | 26 | Form 27 | 28 | 29 | 30 | 31 | 0 32 | 0 33 | 544 34 | 58 35 | 36 | 37 | 38 | 39 | 544 40 | 58 41 | 42 | 43 | 44 | 45 | 544 46 | 58 47 | 48 | 49 | 50 | 51 | 52 | 53 | :/img/apple-ios-linen-texture.jpg 54 | 55 | 56 | 57 | 58 | 59 | 32 60 | 16 61 | 16 62 | 24 63 | 64 | 65 | 66 | 67 | 16 68 | 24 69 | 70 | 71 | 72 | 73 | 16 74 | 24 75 | 76 | 77 | 78 | QToolButton{border-image: url(:/img/arrow_left.png);} 79 | QToolButton:pressed{border-image: url(:/img/arrow_left_clicked.png);} 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 490 89 | 16 90 | 16 91 | 24 92 | 93 | 94 | 95 | 96 | 16 97 | 24 98 | 99 | 100 | 101 | 102 | 16 103 | 24 104 | 105 | 106 | 107 | QToolButton{border-image: url(:/img/arrow_right.png);} 108 | QToolButton:pressed{border-image: url(:/img/arrow_right_clicked.png);} 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 60 118 | 0 119 | 421 120 | 56 121 | 122 | 123 | 124 | 125 | 0 126 | 56 127 | 128 | 129 | 130 | 131 | 521 132 | 54 133 | 134 | 135 | 136 | 137 | 138 | 139 | 0 140 | 0 141 | 544 142 | 56 143 | 144 | 145 | 146 | border-style: solid; 147 | border-width: 5px; 148 | border-top-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgba(0,0,0,80), stop: 1 rgba(0,0,0,0)); 149 | border-bottom-color: rgba(0,0,0,0); 150 | border-left-color: rgba(0,0,0,0); 151 | border-right-color: rgba(0,0,0,0); 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | HorizontalSlider 161 | QWidget 162 |
horizontalslider.h
163 | 1 164 |
165 |
166 | 167 | 168 | 169 | 170 |
171 | -------------------------------------------------------------------------------- /plugins/mpris/doubanmprisplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "doubanmprisplugin.h" 2 | #include 3 | #include "mprisadapter.h" 4 | #include "mprisplayeradapter.h" 5 | #include 6 | #include 7 | 8 | static const QString PLAYER_SERVICE_NAME = "org.mpris.MediaPlayer2.doubanfm"; 9 | static const QString PLAYER_OBJECT_PATH = "/org/mpris/MediaPlayer2"; 10 | 11 | static bool DBUS_NOTIFY_PROPERTIES_CHANGED(QString iface, QVariantMap changed, QStringList invalidated = QStringList()) { 12 | auto dbus_msg = QDBusMessage::createSignal(PLAYER_OBJECT_PATH, 13 | "org.freedesktop.DBus.Properties", 14 | "PropertiesChanged"); 15 | dbus_msg << iface << changed << invalidated; 16 | return QDBusConnection::sessionBus().send(dbus_msg); 17 | } 18 | 19 | DoubanMprisPlugin::DoubanMprisPlugin(QObject *parent) : 20 | DoubanFMPlugin(parent) 21 | { 22 | QDBusConnection con = QDBusConnection::sessionBus(); 23 | //con.registerService("org.mpris.MediaPlayer2"); 24 | con.registerService(PLAYER_SERVICE_NAME); 25 | new MprisAdapter(this); 26 | new MprisPlayerAdapter(this); 27 | con.registerObject(PLAYER_OBJECT_PATH, this); 28 | 29 | connect(&player, &DoubanPlayer::stateChanged, [=] (QMediaPlayer::State) { 30 | QVariantMap changedMap; 31 | changedMap.insert("PlaybackStatus", this->PlaybackStatus()); 32 | DBUS_NOTIFY_PROPERTIES_CHANGED("org.mpris.MediaPlayer2.Player", changedMap); 33 | }); 34 | 35 | connect(&player, &DoubanPlayer::currentSongChanged, [=] (const DoubanFMSong& song) { 36 | QVariantMap map; 37 | map.insert("mpris:length", song.length); 38 | map.insert("xesam:album", song.albumtitle); 39 | map.insert("mpris:artUrl", song.picture); 40 | map.insert("xesam:title", song.title); 41 | map.insert("xesam:artist", song.artist); 42 | map.insert("mpris:trackid", song.sid); 43 | QVariantList artistlist; 44 | artistlist.append(QVariant(song.artist)); 45 | map.insert("xesam:albumArtist", artistlist); 46 | map.insert("xesam:url", song.url); 47 | QVariantMap changedMap; 48 | changedMap.insert("Metadata", map); 49 | DBUS_NOTIFY_PROPERTIES_CHANGED("org.mpris.MediaPlayer2.Player", changedMap); 50 | }); 51 | } 52 | 53 | bool DoubanMprisPlugin::CanControl() const { 54 | return true; 55 | } 56 | bool DoubanMprisPlugin::CanGoNext() const { 57 | return true; 58 | } 59 | bool DoubanMprisPlugin::CanGoPrevious() const { 60 | return false; 61 | } 62 | bool DoubanMprisPlugin::CanPause() const { 63 | return true; 64 | } 65 | bool DoubanMprisPlugin::CanSeek() const { 66 | return false; 67 | } 68 | qlonglong DoubanMprisPlugin::Position() const { 69 | return this->player.position(); 70 | } 71 | 72 | bool DoubanMprisPlugin::CanQuit() const { 73 | return true; 74 | } 75 | bool DoubanMprisPlugin::CanRaise() const { 76 | return true; 77 | } 78 | QString DoubanMprisPlugin::DesktopEntry() const { 79 | return "QDoubanFM"; 80 | } 81 | QString DoubanMprisPlugin::Identity() const { 82 | return "QDoubanFM"; 83 | } 84 | 85 | QVariantMap DoubanMprisPlugin::Metadata() const { 86 | QVariantMap map; 87 | DoubanFMSong song = player.currentSong(); 88 | map.insert("mpris:length", song.length); 89 | map.insert("xesam:album", song.albumtitle); 90 | map.insert("mpris:artUrl", song.picture); 91 | map.insert("xesam:title", song.title); 92 | map.insert("xesam:artist", song.artist); 93 | map.insert("mpris:trackid", song.sid); 94 | QVariantList artistlist; 95 | artistlist.append(QVariant(song.artist)); 96 | map.insert("xesam:albumArtist", artistlist); 97 | map.insert("xesam:url", song.url); 98 | return map; 99 | } 100 | 101 | QString DoubanMprisPlugin::PlaybackStatus() const { 102 | auto status = player.state(); 103 | switch (status) { 104 | case QMediaPlayer::PlayingState: 105 | return "Playing"; 106 | case QMediaPlayer::PausedState: 107 | return "Paused"; 108 | default: 109 | return "Stopped"; 110 | } 111 | } 112 | 113 | void DoubanMprisPlugin::Next() { 114 | this->player.next(); 115 | } 116 | 117 | void DoubanMprisPlugin::Pause() { 118 | this->player.pause(); 119 | } 120 | 121 | void DoubanMprisPlugin::Play() { 122 | this->player.play(); 123 | } 124 | 125 | void DoubanMprisPlugin::PlayPause() { 126 | switch (player.state()) { 127 | case QMediaPlayer::PlayingState: 128 | player.pause(); 129 | break; 130 | default: 131 | player.play(); 132 | } 133 | } 134 | 135 | void DoubanMprisPlugin::Stop() { 136 | this->player.stop(); 137 | } 138 | 139 | void DoubanMprisPlugin::Quit() { 140 | qApp->quit(); 141 | } 142 | 143 | void DoubanMprisPlugin::Raise() { 144 | auto widgetlist = QApplication::topLevelWidgets(); 145 | for (QWidget *widget : widgetlist) { 146 | if (!widget->parent()) { 147 | if (widget->isHidden()) widget->show(); 148 | else widget->activateWindow(); 149 | break; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /settingdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "settingdialog.h" 2 | #include "ui_settingdialog.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "libs/doubanplayer.h" 8 | 9 | 10 | 11 | SettingDialog::SettingDialog(QWidget *parent) : 12 | QDialog(parent), 13 | ui(new Ui::SettingDialog), 14 | doubanfm(DoubanFM::getInstance()), 15 | kbpsGroup(new QButtonGroup(this)), 16 | userIconGetter(new QNetworkAccessManager(this)), 17 | userInfoGetter(new QNetworkAccessManager(this)) 18 | { 19 | ui->setupUi(this); 20 | kbpsGroup->addButton(ui->kbps64); 21 | kbpsGroup->addButton(ui->kbps128); 22 | kbpsGroup->addButton(ui->kbps192); 23 | connect(kbpsGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(kbps_radio_button_clicked(QAbstractButton*))); 24 | 25 | if (doubanfm.hasLogin()) { 26 | auto user = doubanfm.getUser(); 27 | ui->email->setEnabled(false); 28 | ui->password->setEnabled(false); 29 | ui->loginButton->setText(tr("Log out")); 30 | ui->email->setText(user->email); 31 | ui->password->setText(user->password); 32 | ui->usernameLabel->setText(user->user_name); 33 | userInfoGetter->get(QNetworkRequest(QUrl("http://api.douban.com/people/" + user->user_id))); 34 | } 35 | 36 | connect(userIconGetter, &QNetworkAccessManager::finished, [=] (QNetworkReply *reply) { 37 | if (QNetworkReply::NoError != reply->error()) { 38 | qDebug() << "Err: User icon image receive error"; 39 | reply->deleteLater(); 40 | return; 41 | } 42 | const QByteArray data(reply->readAll()); 43 | if (!data.size()) 44 | qDebug() << Q_FUNC_INFO << "received pixmap looks like nothing"; 45 | QImage image = QImage::fromData(data); 46 | QPixmap pixmap = QPixmap::fromImage(image); 47 | this->setUserIcon(pixmap); 48 | reply->deleteLater(); 49 | }); 50 | 51 | connect(userInfoGetter, &QNetworkAccessManager::finished, [=] (QNetworkReply *reply) { 52 | if (QNetworkReply::NoError != reply->error()) { 53 | qDebug() << "Err: User info receive error"; 54 | reply->deleteLater(); 55 | return; 56 | } 57 | const QByteArray data(reply->readAll()); 58 | QDomDocument info; 59 | if (!info.setContent(data)) { 60 | qDebug() << "Err: User info receive error"; 61 | reply->deleteLater(); 62 | return; 63 | } 64 | QDomNodeList links = info.elementsByTagName("link"); 65 | for (int i = 0; i < links.size(); ++ i) { 66 | QDomElement elem = links.at(i).toElement(); 67 | if (elem.attribute("rel") == "icon") { 68 | userIconGetter->get(QNetworkRequest(QUrl(elem.attribute("href")))); 69 | break; 70 | } 71 | } 72 | reply->deleteLater(); 73 | }); 74 | 75 | connect(&doubanfm, &DoubanFM::loginSucceed, [=] (const DoubanUser &user) { 76 | userInfoGetter->get(QNetworkRequest(QUrl("http://api.douban.com/people/" + user.user_id))); 77 | ui->loginButton->setText(tr("Log out")); 78 | ui->usernameLabel->setText(user.user_name); 79 | ui->loginButton->setEnabled(true); 80 | }); 81 | 82 | connect(&doubanfm, &DoubanFM::loginFailed, [=] (const QString& msg) { 83 | ui->email->setEnabled(true); 84 | ui->password->setEnabled(true); 85 | ui->loginButton->setText(msg); 86 | QTimer::singleShot(3000, this, SLOT(timer_event())); 87 | }); 88 | 89 | QSettings settings("QDoubanFM", "QDoubanFM"); 90 | settings.beginGroup("General"); 91 | qint32 _kbps = settings.value("kbps", 64).toInt(); 92 | settings.endGroup(); 93 | switch (_kbps) { 94 | case 64: 95 | ui->kbps64->setChecked(true); 96 | break; 97 | case 128: 98 | ui->kbps128->setChecked(true); 99 | break; 100 | case 192: 101 | ui->kbps192->setChecked(true); 102 | break; 103 | } 104 | } 105 | 106 | SettingDialog::~SettingDialog() 107 | { 108 | delete kbpsGroup; 109 | delete ui; 110 | } 111 | 112 | void SettingDialog::setUserIcon(const QPixmap &pixmap) { 113 | ui->userIcon->setPixmap(pixmap); 114 | } 115 | 116 | void SettingDialog::on_loginButton_clicked() 117 | { 118 | if (!doubanfm.hasLogin()) { 119 | QString email = ui->email->text(); 120 | QString pwd = ui->password->text(); 121 | if (email.size() == 0 || pwd.size() == 0) return; 122 | doubanfm.userLogin(email, pwd); 123 | ui->email->setEnabled(false); 124 | ui->password->setEnabled(false); 125 | ui->loginButton->setEnabled(false); 126 | ui->loginButton->setText(tr("Logging in...")); 127 | } 128 | else { 129 | doubanfm.userLogout(); 130 | ui->email->setText(""); 131 | ui->password->setText(""); 132 | ui->email->setEnabled(true); 133 | ui->password->setEnabled(true); 134 | ui->loginButton->setText(tr("Log in")); 135 | ui->userIcon->setPixmap(QPixmap(":/img/user_man_circle.png")); 136 | ui->usernameLabel->setText(tr("Not logged in")); 137 | } 138 | } 139 | 140 | void SettingDialog::timer_event() { 141 | ui->email->setEnabled(true); 142 | ui->password->setEnabled(true); 143 | ui->loginButton->setEnabled(true); 144 | ui->loginButton->setText(tr("Log in")); 145 | } 146 | 147 | void SettingDialog::kbps_radio_button_clicked(QAbstractButton *button) { 148 | auto& player = DoubanPlayer::getInstance(); 149 | if (button == ui->kbps64) { 150 | player.setKbps(64); 151 | } 152 | else if (button == ui->kbps128) { 153 | player.setKbps(128); 154 | } 155 | else if (button == ui->kbps192) { 156 | player.setKbps(192); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lyricwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "lyricwidget.h" 2 | #include "libs/doubanplayer.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | LyricWidget::LyricWidget(QWidget *parent) : 10 | QWidget(parent), ui(new Ui_LyricWidget), 11 | animWidget(nullptr), 12 | firstShowing(false), 13 | lyricGetter(new LyricGetter(this)), 14 | isShowing(false), haveSearchedLyric(false), saveTick(0) 15 | { 16 | ui->setupUi(this); 17 | ui->border->setText(QString("") + tr("No lyrics") + QString("")); 18 | ui->bg->lower(); 19 | 20 | connect(&DoubanPlayer::getInstance(), SIGNAL(positionChanged(qint64)), 21 | this, SLOT(setTick(qint64))); 22 | connect(lyricGetter, &LyricGetter::gotLyric, [this] (const QLyricList& lyric) { 23 | this->setLyric(lyric); 24 | }); 25 | connect(lyricGetter, &LyricGetter::gotLyricError, [this] (const QString&) { 26 | this->clear(); 27 | }); 28 | connect(&DoubanPlayer::getInstance(), SIGNAL(currentSongChanged(DoubanFMSong)), 29 | this, SLOT(setSong(DoubanFMSong))); 30 | } 31 | 32 | LyricWidget::~LyricWidget() { 33 | if (animWidget) delete animWidget; 34 | delete lyricGetter; 35 | delete ui; 36 | } 37 | 38 | void LyricWidget::setLyric(const QLyricList &lyric) { 39 | if (animWidget) delete animWidget; 40 | animWidget = new QWidget(this); 41 | animWidget->raise(); 42 | ui->border->raise(); 43 | QRect geo = this->geometry(); 44 | animWidget->setMinimumWidth(geo.width()); 45 | animWidget->setMaximumWidth(geo.width()); 46 | animWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 47 | 48 | QVBoxLayout *vbox = new QVBoxLayout(animWidget); 49 | vbox->setMargin(0); 50 | vbox->setSpacing(0); 51 | vbox->setContentsMargins(0, 0, 0, 0); 52 | animWidget->setLayout(vbox); 53 | 54 | labels.clear(); 55 | curInd = 0; 56 | this->lyric = lyric; 57 | firstShowing = false; 58 | 59 | int widgetWidth = this->geometry().width(); 60 | int accuHeight = 0; 61 | for (const QLyric& lyr : lyric) { 62 | QLabel *label = new QLabel(animWidget); 63 | QFont font("文泉驿微米黑", 11); 64 | font.setStyleStrategy(QFont::PreferAntialias); 65 | label->setFont(font); 66 | label->setText(QString("") + 67 | lyr.lyric + QString("")); 68 | label->setMaximumWidth(widgetWidth); 69 | label->setMaximumWidth(widgetWidth); 70 | label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); 71 | label->setWordWrap(true); 72 | labels.append(label); 73 | QRect fsize = label->fontMetrics().boundingRect(label->text()); 74 | int height = (widgetWidth + fsize.width()) / widgetWidth * fsize.height() + 15; 75 | heights.append(height); 76 | label->setMinimumHeight(height); 77 | label->setMaximumHeight(height); 78 | accuHeight += height; 79 | 80 | animWidget->layout()->addWidget(label); 81 | } 82 | 83 | if (heights.size() > 0) { 84 | animWidget->setGeometry(0, this->geometry().height() / 2 + heights[0], 85 | this->geometry().width(), accuHeight); 86 | } 87 | animWidget->show(); 88 | ui->border->setText(""); 89 | } 90 | 91 | void LyricWidget::setTick(qint64 tick) { 92 | if (!isShowing) { 93 | saveTick = tick; 94 | return; 95 | } 96 | if (tick != 0) { 97 | QTime time(0, (tick / 60000) % 60, (tick / 1000) % 60, tick % 1000); 98 | setTime(time); 99 | } 100 | else 101 | setTime(QTime(0, 0, 0, 0)); 102 | } 103 | 104 | void LyricWidget::setTime(const QTime &time) { 105 | if (lyric.size() == 0) return; 106 | int befind = curInd; 107 | if (time < lyric[curInd].time) { 108 | for (int i = curInd - 1; i >= 0; -- i) { 109 | if (lyric[i].time <= time && lyric[i + 1].time > time) { 110 | curInd = i; 111 | break; 112 | } 113 | } 114 | } 115 | else { 116 | if (curInd == lyric.size() - 2 && time >= lyric[lyric.size() - 1].time) 117 | curInd ++; 118 | else if (curInd < lyric.size() - 2) { 119 | for (int i = curInd; i < lyric.size(); ++ i) { 120 | if (lyric[i].time > time) { 121 | curInd = i > 0 ? i - 1 : 0; 122 | break; 123 | } 124 | } 125 | } 126 | } 127 | if (curInd == 0 && !firstShowing) firstShowing = true; 128 | else if (curInd == befind) return; 129 | 130 | QPropertyAnimation *anim = new QPropertyAnimation(animWidget, "geometry"); 131 | anim->setDuration(400); 132 | anim->setStartValue(animWidget->geometry()); 133 | int accuHeight = 0; 134 | for (int i = 0; i < curInd; ++ i) accuHeight += heights[i]; 135 | accuHeight += heights[curInd] / 2; 136 | accuHeight = this->height() / 2 - accuHeight; 137 | QRect endval(0, accuHeight, animWidget->width(), animWidget->height()); 138 | anim->setEndValue(endval); 139 | anim->setEasingCurve(QEasingCurve::OutCubic); 140 | labels[befind]->setText(labels[befind]->text().replace("white", "grey")); 141 | connect(anim, &QPropertyAnimation::finished, [this] () { 142 | labels[curInd]->setText(labels[curInd]->text().replace("grey", "white")); 143 | }); 144 | anim->start(QPropertyAnimation::DeleteWhenStopped); 145 | } 146 | 147 | void LyricWidget::clear() { 148 | if (animWidget) { 149 | animWidget->hide(); 150 | delete animWidget; 151 | animWidget = nullptr; 152 | } 153 | curInd = 0; 154 | lyric.clear(); 155 | labels.clear(); 156 | heights.clear(); 157 | firstShowing = false; 158 | ui->border->setText(QString("") + tr("No lyrics") + QString("")); 159 | } 160 | 161 | void LyricWidget::mousePressEvent(QMouseEvent *event) { 162 | if (event->button() == Qt::LeftButton) { 163 | emit clicked(); 164 | } 165 | } 166 | 167 | void LyricWidget::setSong(const DoubanFMSong &song) { 168 | this->clear(); 169 | if (isShowing) { 170 | lyricGetter->getLyric(song); 171 | this->haveSearchedLyric = true; 172 | } 173 | else { 174 | this->saveCurrentSong = song; 175 | this->haveSearchedLyric = false; 176 | } 177 | } 178 | 179 | void LyricWidget::setShowing(bool s) { 180 | isShowing = s; 181 | if (s) { 182 | if (!haveSearchedLyric) { 183 | this->haveSearchedLyric = true; 184 | lyricGetter->getLyric(saveCurrentSong); 185 | } 186 | else { 187 | this->setTick(saveTick); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /i18n/zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AlbumWidget 6 | 7 | 8 | Form 9 | 10 | 11 | 12 | 13 | <font color="white">&nbsp;&nbsp;View album info&nbsp;&nbsp;</font> 14 | <font color="white">&nbsp;&nbsp;查看专辑信息&nbsp;&nbsp;</font> 15 | 16 | 17 | 18 | ChannelWidget 19 | 20 | 21 | Form 22 | 23 | 24 | 25 | 26 | ControlPanel 27 | 28 | 29 | Form 30 | 31 | 32 | 33 | 34 | 35 | <html><head/><body><p><br/></p></body></html> 36 | 37 | 38 | 39 | 40 | Channel 41 | 频 道 42 | 43 | 44 | 45 | Lyric 46 | 歌 词 47 | 48 | 49 | 50 | DoubanFM 51 | 52 | 53 | My favourite 54 | 红心兆赫 55 | 56 | 57 | 58 | LyricWidget 59 | 60 | 61 | Form 62 | 63 | 64 | 65 | 66 | 67 | No lyrics 68 | 无歌词 69 | 70 | 71 | 72 | MainWidget 73 | 74 | 75 | douban.fm 76 | 77 | 78 | 79 | 80 | Open main window 81 | 打开主界面 82 | 83 | 84 | 85 | Exit 86 | 退出 87 | 88 | 89 | 90 | PauseMask 91 | 92 | 93 | Form 94 | 95 | 96 | 97 | 98 | Resume > 99 | 继续播放 > 100 | 101 | 102 | 103 | SettingDialog 104 | 105 | 106 | Settings 107 | 设置 108 | 109 | 110 | 111 | Basic 112 | 基本 113 | 114 | 115 | 116 | Bit rate 117 | 码率 118 | 119 | 120 | 121 | 64Kbps 122 | 123 | 124 | 125 | 126 | 128Kbps 127 | 128 | 129 | 130 | 131 | 192Kbps 132 | 133 | 134 | 135 | 136 | Notice: Invalid for no-Pro users 137 | 注意:此设置对非Pro用户无效 138 | 139 | 140 | 141 | User 142 | 用户 143 | 144 | 145 | 146 | 147 | Not logged in 148 | 未登录 149 | 150 | 151 | 152 | E-mail address 153 | 邮箱 154 | 155 | 156 | 157 | Password 158 | 密码 159 | 160 | 161 | 162 | 163 | 164 | Log in 165 | 登录 166 | 167 | 168 | 169 | 170 | Log out 171 | 登出 172 | 173 | 174 | 175 | Logging in... 176 | 登录中... 177 | 178 | 179 | 180 | VolumeTimePanel 181 | 182 | 183 | 0:00 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /settingdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SettingDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 278 10 | 329 11 | 12 | 13 | 14 | Settings 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 文泉驿微米黑 25 | 26 | 27 | 28 | 1 29 | 30 | 31 | 32 | Basic 33 | 34 | 35 | 36 | 37 | 38 | 39 | 文泉驿微米黑 40 | 41 | 42 | 43 | Bit rate 44 | 45 | 46 | 47 | 0 48 | 49 | 50 | 0 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64Kbps 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 128Kbps 68 | 69 | 70 | 71 | 72 | 73 | 74 | 192Kbps 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 文泉驿微米黑 85 | 86 | 87 | 88 | Notice: Invalid for no-Pro users 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Qt::Vertical 99 | 100 | 101 | 102 | 20 103 | 107 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | User 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Qt::Horizontal 121 | 122 | 123 | 124 | 40 125 | 20 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 90 137 | 90 138 | 139 | 140 | 141 | 142 | 90 143 | 90 144 | 145 | 146 | 147 | 148 | 149 | 150 | :/img/user_man_circle.png 151 | 152 | 153 | true 154 | 155 | 156 | 157 | 158 | 159 | 160 | Not logged in 161 | 162 | 163 | Qt::AlignCenter 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | Qt::Horizontal 173 | 174 | 175 | 176 | 40 177 | 20 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | Qt::ImhEmailCharactersOnly 188 | 189 | 190 | E-mail address 191 | 192 | 193 | 194 | 195 | 196 | 197 | QLineEdit::Password 198 | 199 | 200 | Password 201 | 202 | 203 | 204 | 205 | 206 | 207 | Log in 208 | 209 | 210 | true 211 | 212 | 213 | 214 | 215 | 216 | 217 | Qt::Vertical 218 | 219 | 220 | 221 | 20 222 | 40 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | Sans 236 | 237 | 238 | 239 | Qt::Horizontal 240 | 241 | 242 | QDialogButtonBox::Close 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | buttonBox 254 | accepted() 255 | SettingDialog 256 | accept() 257 | 258 | 259 | 248 260 | 254 261 | 262 | 263 | 157 264 | 274 265 | 266 | 267 | 268 | 269 | buttonBox 270 | rejected() 271 | SettingDialog 272 | reject() 273 | 274 | 275 | 316 276 | 260 277 | 278 | 279 | 286 280 | 274 281 | 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /libs/doubanplayer.cpp: -------------------------------------------------------------------------------- 1 | #include "doubanplayer.h" 2 | 3 | DoubanPlayer& DoubanPlayer::getInstance() { 4 | static DoubanPlayer _instance(nullptr); 5 | return _instance; 6 | } 7 | 8 | DoubanPlayer::DoubanPlayer(QObject* parent) 9 | : QObject(parent) 10 | , player_(this, QMediaPlayer::StreamPlayback) 11 | , doubanfm(DoubanFM::getInstance()) 12 | , _channel(-INT_MAX) 13 | , _volume(0) 14 | , _can_control(true) 15 | , bufplaylist(nullptr) 16 | , _kbps(64) { 17 | connect(&doubanfm, 18 | &DoubanFM::receivedNewList, 19 | [this](const QList& rcvsongs) { 20 | this->songs_ = rcvsongs; 21 | qDebug() << "Received new playlist with" << rcvsongs.size() << "songs"; 22 | QMediaPlaylist* playlist = player_.playlist(); 23 | if (playlist == nullptr) { 24 | playlist = new QMediaPlaylist(&player_); 25 | } 26 | playlist->clear(); 27 | for (const DoubanFMSong& song : this->songs_) { 28 | playlist->addMedia(QUrl(song.url)); 29 | } 30 | if (player_.playlist() == nullptr) { 31 | connect(playlist, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); 32 | // FIXME: Crash on KDE4.9 libkdecore.so.5 33 | // Segmentation Fault by unknown reason 34 | player_.setPlaylist(playlist); 35 | } 36 | if (player_.state() != QMediaPlayer::PlayingState) { 37 | player_.play(); 38 | } 39 | setCanControl(true); 40 | }); 41 | 42 | connect(&doubanfm, 43 | &DoubanFM::receivedPlayingList, 44 | [this](const QList& rcvsongs) { 45 | qDebug() << "Received new playlist with" << rcvsongs.size() << "songs"; 46 | if (this->bufplaylist != nullptr) { 47 | delete this->bufplaylist; 48 | } 49 | this->bufplaylist = new QMediaPlaylist(&player_); 50 | bufsongs = rcvsongs; 51 | 52 | for (const DoubanFMSong& song : rcvsongs) { 53 | bufplaylist->addMedia(QUrl(song.url)); 54 | } 55 | setCanControl(true); 56 | 57 | // if (player.state() != QMediaPlayer::PlayingState) 58 | // player.play(); 59 | }); 60 | 61 | connect(&player_, &QMediaPlayer::positionChanged, [this](qint64 tick) { emit this->positionChanged(tick); }); 62 | connect(&doubanfm, 63 | &DoubanFM::receivedByeSong, 64 | [this](bool suc) { 65 | emit this->receivedTrashSong(suc); 66 | setCanControl(true); 67 | }); 68 | connect(&doubanfm, 69 | &DoubanFM::receivedRateSong, 70 | [this](bool suc) { 71 | int curIndex = player_.playlist()->currentIndex(); 72 | songs_[curIndex].like = !songs_[curIndex].like; 73 | emit this->receivedRateSong(suc); 74 | setCanControl(true); 75 | }); 76 | connect(&doubanfm, 77 | &DoubanFM::receivedSkipSong, 78 | [this](bool suc) { 79 | emit this->receivedSkipSong(suc); 80 | setCanControl(true); 81 | }); 82 | connect(&player_, &QMediaPlayer::stateChanged, [this](QMediaPlayer::State s) { emit this->stateChanged(s); }); 83 | } 84 | 85 | DoubanPlayer::~DoubanPlayer() { 86 | player_.disconnect(); 87 | if (player_.state() != QMediaPlayer::StoppedState) { 88 | player_.stop(); 89 | } 90 | } 91 | 92 | void DoubanPlayer::currentIndexChanged(int position) { 93 | /*if (position < 0) { 94 | if (songs.size() > 0) 95 | doubanfm->getPlayingList(channel, songs.back().sid); 96 | else 97 | doubanfm->(channel); 98 | return; 99 | }*/ 100 | // Jump out of playlist 101 | if (position < 0) { 102 | if (bufplaylist == nullptr) { 103 | doubanfm.getNewPlayList(_channel, _kbps); 104 | setCanControl(false); 105 | } else { 106 | player_.playlist()->deleteLater(); 107 | player_.setPlaylist(bufplaylist); 108 | disconnect(player_.playlist(), SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); 109 | connect(bufplaylist, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); 110 | songs_ = bufsongs; 111 | bufplaylist = nullptr; 112 | if (player_.state() != QMediaPlayer::PlayingState) 113 | player_.play(); 114 | player_.playlist()->next(); 115 | } 116 | qDebug() << "Deployed new playlist"; 117 | return; 118 | } 119 | // Currently playing the last song in the list 120 | else if (position == songs_.size() - 1) { 121 | doubanfm.getPlayingList(_channel, songs_.back().sid, _kbps); 122 | this->setCanControl(false); 123 | } 124 | // Got update playlist 125 | else if (bufplaylist != nullptr) { 126 | player_.playlist()->deleteLater(); 127 | player_.setPlaylist(bufplaylist); 128 | disconnect(player_.playlist(), SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); 129 | connect(bufplaylist, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); 130 | songs_ = bufsongs; 131 | bufplaylist = nullptr; 132 | if (player_.state() != QMediaPlayer::PlayingState) { 133 | player_.play(); 134 | } 135 | player_.playlist()->next(); 136 | qDebug() << Q_FUNC_INFO << "Got updated playlist"; 137 | return; 138 | } 139 | emit this->currentSongChanged(songs_[position]); 140 | 141 | qDebug() << "CurrentPlaying: "; 142 | qDebug() << " artist: " << qPrintable(songs_[position].artist); 143 | qDebug() << " title: " << qPrintable(songs_[position].title); 144 | qDebug() << " album: " << qPrintable(songs_[position].albumtitle); 145 | qDebug() << " publicTime: " << songs_[position].public_time; 146 | qDebug() << " company: " << qPrintable(songs_[position].company); 147 | qDebug() << " kbps: " << songs_[position].kbps; 148 | qDebug() << " like: " << songs_[position].like; 149 | qDebug() << " sid: " << songs_[position].sid; 150 | qDebug() << " ssid: " << songs_[position].ssid; 151 | qDebug() << " subType: " << songs_[position].subtype; 152 | qDebug() << " url: " << songs_[position].url; 153 | } 154 | 155 | bool DoubanPlayer::canControl() const { return _can_control; } 156 | 157 | void DoubanPlayer::setCanControl(bool can) { 158 | _can_control = can; 159 | emit canControlChanged(can); 160 | } 161 | 162 | void DoubanPlayer::play() { 163 | QPropertyAnimation* fadein = new QPropertyAnimation(&player_, "volume"); 164 | fadein->setDuration(1000); 165 | fadein->setStartValue(player_.volume()); 166 | player_.play(); 167 | fadein->setEndValue(_volume); 168 | fadein->start(QPropertyAnimation::DeleteWhenStopped); 169 | emit playing(); 170 | 171 | int elapsed = time(nullptr) - this->lastPausedTime; 172 | if (elapsed >= 30 * 60) { 173 | doubanfm.getPlayingList(_channel, this->currentSong().sid, _kbps); 174 | QTime pt(0, 0, 0, 0); 175 | pt = pt.addSecs(elapsed); 176 | this->setCanControl(false); 177 | qDebug() << "Have paused " << pt << ", getting a new playlist"; 178 | } 179 | } 180 | 181 | void DoubanPlayer::pause() { 182 | emit paused(); 183 | this->lastPausedTime = time(nullptr); 184 | QPropertyAnimation* fadeout = new QPropertyAnimation(&player_, "volume"); 185 | fadeout->setDuration(1000); 186 | fadeout->setStartValue(player_.volume()); 187 | _volume = player_.volume(); 188 | fadeout->setEndValue(0); 189 | connect(fadeout, &QPropertyAnimation::finished, [this]() { player_.pause(); }); 190 | fadeout->start(QPropertyAnimation::DeleteWhenStopped); 191 | } 192 | 193 | const DoubanFMSong& DoubanPlayer::currentSong() const { 194 | int sindex = -1; 195 | if (player_.playlist() == nullptr || (sindex = player_.playlist()->currentIndex()) < 0) { 196 | static DoubanFMSong loading; 197 | loading.title = "Loading"; 198 | return loading; 199 | } 200 | return songs_[sindex]; 201 | } 202 | 203 | qint64 DoubanPlayer::position() const { return player_.position(); } 204 | 205 | void DoubanPlayer::next() { 206 | if (player_.playlist() == nullptr) 207 | return; 208 | int sindex = player_.playlist()->currentIndex(); 209 | if (sindex < 0) { 210 | doubanfm.getNewPlayList(this->_channel, _kbps); 211 | return; 212 | } 213 | doubanfm.skipSong(songs_[sindex].sid, _channel); 214 | player_.playlist()->next(); 215 | } 216 | 217 | void DoubanPlayer::stop() { 218 | this->player_.stop(); 219 | emit stopped(); 220 | } 221 | 222 | void DoubanPlayer::rateCurrentSong() { 223 | if (player_.playlist() == nullptr) 224 | return; 225 | int sindex = player_.playlist()->currentIndex(); 226 | doubanfm.rateSong(songs_[sindex].sid, _channel, true); 227 | setCanControl(false); 228 | } 229 | 230 | void DoubanPlayer::unrateCurrentSong() { 231 | if (player_.playlist() == nullptr) 232 | return; 233 | int sindex = player_.playlist()->currentIndex(); 234 | doubanfm.rateSong(songs_[sindex].sid, _channel, false); 235 | setCanControl(false); 236 | } 237 | 238 | void DoubanPlayer::trashCurrentSong() { 239 | if (player_.playlist() == nullptr) 240 | return; 241 | int sindex = player_.playlist()->currentIndex(); 242 | doubanfm.byeSong(songs_[sindex].sid, _channel); 243 | setCanControl(false); 244 | } 245 | 246 | void DoubanPlayer::setChannel(qint32 chanid) { 247 | if (chanid == _channel) 248 | return; 249 | this->_channel = chanid; 250 | doubanfm.getNewPlayList(chanid, _kbps); 251 | setCanControl(false); 252 | } 253 | 254 | void DoubanPlayer::setVolume(int v) { 255 | this->_volume = v; 256 | this->player_.setVolume(v); 257 | } 258 | 259 | qint32 DoubanPlayer::channel() const { return this->_channel; } 260 | 261 | int DoubanPlayer::volume() const { return player_.volume(); } 262 | 263 | qint64 DoubanPlayer::duration() const { return player_.duration(); } 264 | 265 | QMediaPlayer::State DoubanPlayer::state() const { return player_.state(); } 266 | 267 | void DoubanPlayer::setKbps(qint32 kbps) { this->_kbps = kbps; } 268 | 269 | qint32 DoubanPlayer::kbps() const { return this->_kbps; } 270 | -------------------------------------------------------------------------------- /controlpanel.cpp: -------------------------------------------------------------------------------- 1 | #include "controlpanel.h" 2 | #include "ui_controlpanel.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "mainwidget.h" 12 | #include 13 | #include "libs/douban_types.h" 14 | #include "settingdialog.h" 15 | 16 | ControlPanel::ControlPanel(QWidget *parent) : 17 | QWidget(parent), 18 | ui(new Ui::ControlPanel), 19 | doubanfm(DoubanFM::getInstance()), 20 | player(DoubanPlayer::getInstance()), 21 | imgmgr(new QNetworkAccessManager(this)), 22 | lyricGetter(new LyricGetter(this)), 23 | settingDialog(new SettingDialog(this)) 24 | { 25 | ui->setupUi(this); 26 | loadConfig(); 27 | 28 | // Shape of channelButton and lyricButton 29 | QPolygon polygonTop, polygonBottom; 30 | polygonTop.setPoints(4, 0, 0, 131, 0, 115, 16, 16, 16); 31 | polygonBottom.setPoints(4, 16, 0, 115, 0, 131, 16, 0, 16); 32 | QRegion regionTop(polygonTop); 33 | QRegion regionBottom(polygonBottom); 34 | ui->channelButton->setMask(regionTop); 35 | ui->lyricButton->setMask(regionBottom); 36 | 37 | connect(imgmgr, &QNetworkAccessManager::finished, [this] (QNetworkReply *reply) { 38 | if (QNetworkReply::NoError != reply->error()) { 39 | qDebug() << "Err: Album image receive error"; 40 | reply->deleteLater(); 41 | return; 42 | } 43 | const QByteArray data(reply->readAll()); 44 | if (!data.size()) 45 | qDebug() << Q_FUNC_INFO << "received pixmap looks like nothing"; 46 | QImage image = QImage::fromData(data); 47 | /* 48 | if (player.playlist()->currentIndex() >= 0) { 49 | int index = player.playlist()->currentIndex(); 50 | if (notify) { 51 | notify->close(); 52 | delete notify; 53 | notify = nullptr; 54 | } 55 | notify = new Notification(songs[index].artist, songs[index].title); 56 | if (data.size() > 0) { 57 | iiibiiay notify_icon_data = iiibiiay::fromImage(image); 58 | notify->setHint("icon_data", 59 | QVariant(qDBusRegisterMetaType(), ¬ify_icon_data)); 60 | } 61 | notify->setAutoDelete(false); 62 | notify->show(); 63 | } 64 | */ 65 | ui->albumImg->setAlbumImage(image); 66 | reply->deleteLater(); 67 | }); 68 | 69 | //player.setPlaylist(new QMediaPlaylist(&player)); 70 | connect(&player, &DoubanPlayer::currentSongChanged, [=] (const DoubanFMSong& song) { 71 | setArtistName(song.artist); 72 | setSongName(song.title); 73 | setAlbumName(song.albumtitle); 74 | 75 | QString mod_url = song.picture; 76 | mod_url.replace("mpic", "lpic"); 77 | imgmgr->get(QNetworkRequest(QUrl(mod_url))); 78 | if (song.like) { 79 | ui->likeButton->setChecked(true); 80 | } 81 | else { 82 | ui->likeButton->setChecked(false); 83 | } 84 | }); 85 | 86 | setArtistName("Loading"); 87 | 88 | connect(&player, SIGNAL(positionChanged(qint64)), ui->volumeTime, SLOT(setTick(qint64))); 89 | connect(&player, &DoubanPlayer::positionChanged, [this] (qint64 tick) { 90 | ui->seeker->setValue((qreal) tick / player.duration() * ui->seeker->maximum()); 91 | }); 92 | //connect(player, &QMediaPlayer::volumeChanged, [this] (int vol) { 93 | // qDebug() << vol; 94 | //}); 95 | 96 | connect(&player, &DoubanPlayer::receivedRateSong, [this] (bool succeed) { 97 | if (!succeed) return; 98 | 99 | if (player.currentSong().like) { 100 | ui->likeButton->setChecked(true); 101 | } 102 | else { 103 | ui->likeButton->setChecked(false); 104 | } 105 | }); 106 | 107 | connect(ui->volumeTime, &VolumeTimePanel::volumeChanged, [this] (int value) { 108 | this->player.setVolume(value); 109 | }); 110 | 111 | /*if (player->channel() == -3) { 112 | if (!doubanfm->getUser()) 113 | player->setChannel(1); 114 | }*/ 115 | 116 | connect(lyricGetter, &LyricGetter::gotLyricError, [this] (const QString& ) { 117 | /*if (ui->lyricWidget->isVisible()) 118 | emit ui->lyricWidgetTriggerRight->enter();*/ 119 | }); 120 | 121 | //ui->lyricWidget->setVisible(false); 122 | connect(ui->albumImg, &AlbumWidget::clicked, [this] () { 123 | QDesktopServices::openUrl(QUrl("http://www.douban.com" + player.currentSong().album)); 124 | }); 125 | 126 | connect(&player, &DoubanPlayer::canControlChanged, [=] (bool can) { 127 | ui->nextButton->setEnabled(can); 128 | ui->trashButton->setEnabled(can); 129 | ui->likeButton->setEnabled(can); 130 | }); 131 | /* 132 | connect(ui->lyricWidgetTriggerLeft, &LyricWidgetTriggerLeft::enter, [this] () { 133 | QPropertyAnimation *anim = new QPropertyAnimation(ui->albumImg, "geometry"); 134 | anim->setDuration(400); 135 | anim->setStartValue(ui->albumImg->geometry()); 136 | QRect endval(this->geometry().width() - ui->albumImg->geometry().width(), 137 | ui->albumImg->geometry().y(), 138 | ui->albumImg->geometry().width(), 139 | ui->albumImg->geometry().height()); 140 | anim->setEndValue(endval); 141 | anim->setEasingCurve(QEasingCurve::OutCubic); 142 | 143 | ui->album->setVisible(false); 144 | ui->artist->setVisible(false); 145 | ui->volumeTime->setVisible(false); 146 | ui->userLogin->setVisible(false); 147 | ui->trashButton->setVisible(false); 148 | ui->songName->setVisible(false); 149 | ui->seeker->setVisible(false); 150 | ui->pauseButton->setVisible(false); 151 | ui->nextButton->setVisible(false); 152 | ui->likeButton->setVisible(false); 153 | 154 | connect(anim, &QPropertyAnimation::finished, [this] () { 155 | ui->lyricWidget->setVisible(true); 156 | }); 157 | 158 | anim->start(QPropertyAnimation::DeleteWhenStopped); 159 | }); 160 | connect(ui->lyricWidgetTriggerRight, &LyricWidgetTriggerRight::enter, [this] () { 161 | QPropertyAnimation *anim = new QPropertyAnimation(ui->albumImg, "geometry"); 162 | anim->setDuration(400); 163 | anim->setStartValue(ui->albumImg->geometry()); 164 | QRect endval(0, 165 | ui->albumImg->geometry().y(), 166 | ui->albumImg->geometry().width(), 167 | ui->albumImg->geometry().height()); 168 | anim->setEndValue(endval); 169 | anim->setEasingCurve(QEasingCurve::OutCubic); 170 | 171 | ui->lyricWidget->setVisible(false); 172 | 173 | connect(anim, &QPropertyAnimation::finished, [this] () { 174 | ui->album->setVisible(true); 175 | ui->artist->setVisible(true); 176 | ui->volumeTime->setVisible(true); 177 | ui->userLogin->setVisible(true); 178 | ui->trashButton->setVisible(true); 179 | ui->songName->setVisible(true); 180 | ui->seeker->setVisible(true); 181 | ui->pauseButton->setVisible(true); 182 | ui->nextButton->setVisible(true); 183 | ui->likeButton->setVisible(true); 184 | }); 185 | 186 | anim->start(QPropertyAnimation::DeleteWhenStopped); 187 | }); 188 | */ 189 | } 190 | 191 | ControlPanel::~ControlPanel() 192 | { 193 | delete ui; 194 | saveConfig(); 195 | delete lyricGetter; 196 | delete settingDialog; 197 | } 198 | 199 | void ControlPanel::setSongName(const QString &name) { 200 | ui->songName->setText(QString("") 201 | + name + QString("")); 202 | } 203 | 204 | void ControlPanel::setArtistName(const QString &name) { 205 | ui->artist->setText(QString("") 206 | + name + QString("")); 207 | } 208 | 209 | void ControlPanel::loadConfig() { 210 | QSettings settings("QDoubanFM", "QDoubanFM"); 211 | 212 | settings.beginGroup("General"); 213 | player.setVolume(settings.value("volume", 100).toInt()); 214 | qint32 _channel = settings.value("channel", 1).toInt(); 215 | player.setKbps(settings.value("kbps", 64).toInt()); 216 | settings.endGroup(); 217 | 218 | if (_channel == -3 && doubanfm.hasLogin()) { 219 | player.setChannel(_channel); 220 | } 221 | } 222 | 223 | void ControlPanel::saveConfig() { 224 | QSettings settings("QDoubanFM", "QDoubanFM"); 225 | settings.beginGroup("General"); 226 | settings.setValue("channel", player.channel()); 227 | settings.setValue("volume", player.volume()); 228 | settings.setValue("kbps", player.kbps()); 229 | settings.endGroup(); 230 | } 231 | 232 | void ControlPanel::on_nextButton_clicked() 233 | { 234 | ui->seeker->setValue(0); 235 | player.next(); 236 | } 237 | 238 | void ControlPanel::on_pauseButton_clicked() 239 | { 240 | player.pause(); 241 | } 242 | 243 | void ControlPanel::on_likeButton_clicked() 244 | { 245 | bool is_liked = player.currentSong().like; 246 | if (is_liked) 247 | player.unrateCurrentSong(); 248 | else 249 | player.rateCurrentSong(); 250 | } 251 | 252 | void ControlPanel::on_trashButton_clicked() 253 | { 254 | player.trashCurrentSong(); 255 | } 256 | 257 | void ControlPanel::setAlbumName(const QString &name) { 258 | ui->album->setText(QString("< ") + name + QString(" >")); 259 | } 260 | 261 | void ControlPanel::play() { 262 | /*QPropertyAnimation *fadein = new QPropertyAnimation(&player, "volume"); 263 | fadein->setDuration(1000); 264 | fadein->setStartValue(player.volume()); 265 | player.play(); 266 | fadein->setEndValue(volume); 267 | fadein->start(QPropertyAnimation::DeleteWhenStopped); 268 | isPaused = false;*/ 269 | player.play(); 270 | } 271 | 272 | void ControlPanel::pause() { 273 | /*QPropertyAnimation *fadeout = new QPropertyAnimation(&player, "volume"); 274 | fadeout->setDuration(1000); 275 | fadeout->setStartValue(player.volume()); 276 | volume = player.volume(); 277 | fadeout->setEndValue(0); 278 | connect(fadeout, &QPropertyAnimation::finished, [this] () { 279 | player.pause(); 280 | }); 281 | fadeout->start(QPropertyAnimation::DeleteWhenStopped); 282 | isPaused = true;*/ 283 | player.pause(); 284 | } 285 | 286 | void ControlPanel::on_settingButton_clicked() 287 | { 288 | settingDialog->show(); 289 | } 290 | 291 | void ControlPanel::on_channelButton_clicked(bool checked) 292 | { 293 | if (checked) 294 | emit openChannelPanel(); 295 | else 296 | emit closeChannelPanel(); 297 | } 298 | 299 | void ControlPanel::on_lyricButton_clicked(bool checked) 300 | { 301 | if (checked) 302 | emit openLyricPanel(); 303 | else 304 | emit closeLyricPanel(); 305 | } 306 | -------------------------------------------------------------------------------- /controlpanel.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ControlPanel 4 | 5 | 6 | 7 | 0 8 | 0 9 | 544 10 | 200 11 | 12 | 13 | 14 | 15 | 544 16 | 200 17 | 18 | 19 | 20 | 21 | 544 22 | 200 23 | 24 | 25 | 26 | Form 27 | 28 | 29 | 30 | 31 | 0 32 | 0 33 | 544 34 | 200 35 | 36 | 37 | 38 | 39 | 544 40 | 200 41 | 42 | 43 | 44 | 45 | 544 46 | 200 47 | 48 | 49 | 50 | background: #e8edea 51 | 52 | 53 | 54 | 55 | 56 | true 57 | 58 | 59 | 60 | 61 | 62 | 217 63 | 24 64 | 261 65 | 31 66 | 67 | 68 | 69 | 70 | 文泉驿微米黑 71 | 16 72 | 50 73 | false 74 | 75 | 76 | 77 | <html><head/><body><p><br/></p></body></html> 78 | 79 | 80 | 81 | 82 | 83 | 217 84 | 69 85 | 261 86 | 20 87 | 88 | 89 | 90 | 91 | 文泉驿微米黑 92 | 11 93 | 50 94 | false 95 | 96 | 97 | 98 | <html><head/><body><p><br/></p></body></html> 99 | 100 | 101 | 102 | 103 | 104 | 480 105 | 30 106 | 30 107 | 30 108 | 109 | 110 | 111 | Qt::NoFocus 112 | 113 | 114 | 115 | :/img/pause.svg 116 | :/img/pause.svg 117 | :/img/pause_active.svg 118 | :/img/pause_active.svg:/img/pause.svg 119 | 120 | 121 | 122 | 24 123 | 24 124 | 125 | 126 | 127 | true 128 | 129 | 130 | 131 | 132 | false 133 | 134 | 135 | 136 | 217 137 | 90 138 | 291 139 | 21 140 | 141 | 142 | 143 | 1000000 144 | 145 | 146 | Qt::Horizontal 147 | 148 | 149 | 150 | 151 | 152 | 325 153 | 130 154 | 181 155 | 42 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 40 164 | 40 165 | 166 | 167 | 168 | Qt::NoFocus 169 | 170 | 171 | 172 | :/img/heart.svg 173 | :/img/heart_red.svg 174 | :/img/heart_active.svg 175 | :/img/heart_red_active.svg:/img/heart.svg 176 | 177 | 178 | 179 | 32 180 | 32 181 | 182 | 183 | 184 | true 185 | 186 | 187 | true 188 | 189 | 190 | 191 | 192 | 193 | 194 | Qt::Horizontal 195 | 196 | 197 | 198 | 40 199 | 20 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 40 209 | 40 210 | 211 | 212 | 213 | Qt::NoFocus 214 | 215 | 216 | 217 | :/img/trash.svg 218 | :/img/trash.svg 219 | :/img/trash_active.svg 220 | :/img/trash_active.svg:/img/trash.svg 221 | 222 | 223 | 224 | 32 225 | 32 226 | 227 | 228 | 229 | true 230 | 231 | 232 | 233 | 234 | 235 | 236 | Qt::Horizontal 237 | 238 | 239 | 240 | 40 241 | 20 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 40 251 | 40 252 | 253 | 254 | 255 | Qt::NoFocus 256 | 257 | 258 | 259 | :/img/next.svg 260 | :/img/next.svg 261 | :/img/next_active.svg 262 | :/img/next_active.svg:/img/next.svg 263 | 264 | 265 | 266 | 32 267 | 32 268 | 269 | 270 | 271 | true 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 404 281 | 107 282 | 101 283 | 20 284 | 285 | 286 | 287 | 288 | 289 | 290 | 218 291 | 47 292 | 261 293 | 20 294 | 295 | 296 | 297 | 298 | 文泉驿微米黑 299 | 9 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 384 310 | 0 311 | 131 312 | 16 313 | 314 | 315 | 316 | 317 | 9 318 | 319 | 320 | 321 | * { 322 | border: none; 323 | background-color:#555; 324 | color: #fff; 325 | } 326 | 327 | *:hover { 328 | background-color:#333; 329 | } 330 | 331 | *:clicked { 332 | background-color:#555; 333 | } 334 | 335 | 336 | Channel 337 | 338 | 339 | true 340 | 341 | 342 | 343 | 344 | 345 | 0 346 | 0 347 | 200 348 | 200 349 | 350 | 351 | 352 | 353 | 200 354 | 200 355 | 356 | 357 | 358 | 359 | 200 360 | 200 361 | 362 | 363 | 364 | 365 | 366 | 367 | 384 368 | 184 369 | 131 370 | 16 371 | 372 | 373 | 374 | 375 | 9 376 | 377 | 378 | 379 | Qt::NoFocus 380 | 381 | 382 | * { 383 | border: none; 384 | background-color:#555; 385 | color: #fff; 386 | } 387 | 388 | *:hover { 389 | background-color:#333; 390 | } 391 | 392 | *:clicked { 393 | background-color:#555; 394 | } 395 | 396 | 397 | Lyric 398 | 399 | 400 | true 401 | 402 | 403 | 404 | 405 | 406 | 480 407 | 60 408 | 30 409 | 30 410 | 411 | 412 | 413 | Qt::NoFocus 414 | 415 | 416 | 417 | 418 | 419 | 420 | :/img/setting.svg 421 | :/img/setting.svg 422 | :/img/setting_active.svg 423 | :/img/setting_active.svg:/img/setting.svg 424 | 425 | 426 | 427 | 24 428 | 24 429 | 430 | 431 | 432 | true 433 | 434 | 435 | 436 | 437 | 438 | VolumeTimePanel 439 | QWidget 440 |
volumetimepanel.h
441 | 1 442 |
443 | 444 | AlbumWidget 445 | QWidget 446 |
albumwidget.h
447 | 1 448 |
449 |
450 | 451 | 452 | 453 | 454 |
455 | -------------------------------------------------------------------------------- /mainwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwidget.h" 2 | #include "ui_mainwidget.h" 3 | #include "libs/doubanplayer.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | MainWidget::MainWidget(QWidget *parent) : 12 | QWidget(parent), 13 | ui(new Ui::MainWidget), 14 | _isChannelWidgetShowing(false), 15 | _isAnimStarted(false), 16 | _isLyricWidgetShowing(false), 17 | topBorder(new QLabel(this)), 18 | bottomBorder(new QLabel(this)), 19 | systemTrayIcon(nullptr) 20 | { 21 | ui->setupUi(this); 22 | ui->lyricWidget->lower(); 23 | ui->channelWidget->raise(); 24 | ui->controlWidget->raise(); 25 | 26 | // Configure borders 27 | topBorder->setMaximumSize(this->width(), 5); 28 | topBorder->setMinimumSize(this->width(), 5); 29 | topBorder->setStyleSheet("background: qlineargradient(x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 rgba(0,0,0,80), stop: 1 rgba(0,0,0,0));"); 30 | topBorder->raise(); 31 | topBorder->move(0, -topBorder->height()); 32 | bottomBorder->setMaximumSize(this->width(), 10); 33 | bottomBorder->setMinimumSize(this->width(), 10); 34 | bottomBorder->setStyleSheet("background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 rgba(0,0,0,120), stop: 1 rgba(0,0,0,0));"); 35 | bottomBorder->raise(); 36 | bottomBorder->move(0, ui->controlWidget->height()); 37 | 38 | ui->pauseWidget->raise(); 39 | ui->pauseWidget->setVisible(false); 40 | 41 | connect(ui->pauseWidget, SIGNAL(clicked()), ui->controlWidget, SLOT(play())); 42 | exitShortcut = new QShortcut(QKeySequence("Ctrl+Q"), this); 43 | connect(exitShortcut, SIGNAL(activated()), qApp, SLOT(quit())); 44 | pauseShortcut = new QShortcut(QKeySequence("Space"), this); 45 | connect(pauseShortcut, &QShortcut::activated, [this] () { 46 | bool visiable = ui->pauseWidget->isVisible(); 47 | if (visiable) 48 | ui->controlWidget->play(); 49 | else 50 | ui->controlWidget->pause(); 51 | ui->pauseWidget->setVisible(!visiable); 52 | }); 53 | nextShortcut = new QShortcut(QKeySequence("S"), this); 54 | connect(nextShortcut, SIGNAL(activated()), ui->controlWidget, SLOT(on_nextButton_clicked())); 55 | deleteShortcut = new QShortcut(QKeySequence("D"), this); 56 | connect(deleteShortcut, SIGNAL(activated()), ui->controlWidget, SLOT(on_trashButton_clicked())); 57 | likeShortcut = new QShortcut(QKeySequence("F"), this); 58 | connect(likeShortcut, SIGNAL(activated()), ui->controlWidget, SLOT(on_likeButton_clicked())); 59 | hideShortcut = new QShortcut(QKeySequence("Ctrl+W"), this); 60 | connect(hideShortcut, SIGNAL(activated()), this, SLOT(hide())); 61 | 62 | connect(ui->controlWidget, SIGNAL(openChannelPanel()), this, SLOT(animShowChannelWidget())); 63 | connect(ui->controlWidget, SIGNAL(closeChannelPanel()), this, SLOT(animHideChannelWidget())); 64 | 65 | connect(ui->controlWidget, SIGNAL(openLyricPanel()), this, SLOT(animShowLyricWidget())); 66 | connect(ui->controlWidget, SIGNAL(closeLyricPanel()), this, SLOT(animHideLyricWidget())); 67 | 68 | // FIXME: Ubuntu doesn't support QSystemTrayIcon 69 | // Following code will cause confusing behaviour 70 | #ifdef WITH_SYSTEM_TRAY_ICON 71 | if (QSystemTrayIcon::isSystemTrayAvailable()) { 72 | systemTrayIcon = new QSystemTrayIcon(QIcon("://icon.png"), this); 73 | connect(systemTrayIcon, &QSystemTrayIcon::activated, [&] () { 74 | if (this->isHidden()) { 75 | this->show(); 76 | this->raise(); 77 | this->activateWindow(); 78 | } else { 79 | if (this->isActiveWindow()) { 80 | this->hide(); 81 | } else { 82 | this->raise(); 83 | this->activateWindow(); 84 | } 85 | } 86 | }); 87 | QMenu *trayMenu = new QMenu(this); 88 | auto _openAction = trayMenu->addAction(tr("Open main window")); 89 | trayMenu->addSeparator(); 90 | auto _closeAction = trayMenu->addAction(tr("Exit")); 91 | connect(trayMenu, &QMenu::triggered, [this, _openAction, _closeAction] (QAction *action) { 92 | if (action == _openAction) 93 | this->show(); 94 | else if (action == _closeAction) 95 | qApp->quit(); 96 | }); 97 | systemTrayIcon->setContextMenu(trayMenu); 98 | systemTrayIcon->show(); 99 | } 100 | #endif 101 | 102 | } 103 | 104 | MainWidget::~MainWidget() 105 | { 106 | if (systemTrayIcon) { 107 | systemTrayIcon->hide(); 108 | systemTrayIcon->deleteLater(); 109 | } 110 | delete ui; 111 | delete exitShortcut; 112 | delete hideShortcut; 113 | delete pauseShortcut; 114 | delete topBorder; 115 | delete bottomBorder; 116 | } 117 | 118 | void MainWidget::mousePressEvent(QMouseEvent *) { 119 | 120 | } 121 | 122 | void MainWidget::animHideChannelWidget() { 123 | if (!isChannelWidgetShowing() || isAnimationStarted()) return; 124 | this->animStart(); 125 | QParallelAnimationGroup *animgroup = new QParallelAnimationGroup(this); 126 | if (this->height() == ui->channelWidget->height() + ui->controlWidget->height()) { 127 | this->setMinimumHeight(ui->controlWidget->height()); 128 | 129 | QPropertyAnimation *main_anim = new QPropertyAnimation(this, "geometry"); 130 | main_anim->setDuration(400); 131 | main_anim->setStartValue(this->geometry()); 132 | QRect endval2 = this->geometry(); 133 | endval2.setHeight(endval2.height() - ui->channelWidget->height()); 134 | main_anim->setEndValue(endval2); 135 | main_anim->setEasingCurve(QEasingCurve::OutCubic); 136 | 137 | animgroup->addAnimation(main_anim); 138 | } 139 | 140 | QPropertyAnimation *control_anim = new QPropertyAnimation(ui->controlWidget, "pos"); 141 | control_anim->setDuration(400); 142 | control_anim->setStartValue(ui->controlWidget->pos()); 143 | QPoint endpos = ui->controlWidget->pos(); 144 | endpos.setY(0); 145 | control_anim->setEndValue(endpos); 146 | control_anim->setEasingCurve(QEasingCurve::OutCubic); 147 | 148 | animgroup->addAnimation(control_anim); 149 | 150 | QPropertyAnimation *topborder_anim = new QPropertyAnimation(this->topBorder, "pos"); 151 | topborder_anim->setDuration(400); 152 | QPoint topborder_pos = topBorder->pos(); 153 | topborder_anim->setStartValue(topborder_pos); 154 | topborder_pos.setY(-topBorder->height()); 155 | topborder_anim->setEndValue(topborder_pos); 156 | topborder_anim->setEasingCurve(QEasingCurve::OutCubic); 157 | animgroup->addAnimation(topborder_anim); 158 | 159 | QPropertyAnimation *bottomborder_anim = new QPropertyAnimation(this->bottomBorder, "pos"); 160 | bottomborder_anim->setDuration(400); 161 | QPoint bottomborder_pos = bottomBorder->pos(); 162 | bottomborder_anim->setStartValue(bottomborder_pos); 163 | bottomborder_pos.setY(ui->controlWidget->height()); 164 | bottomborder_anim->setEndValue(bottomborder_pos); 165 | bottomborder_anim->setEasingCurve(QEasingCurve::OutCubic); 166 | animgroup->addAnimation(bottomborder_anim); 167 | 168 | connect(animgroup, &QParallelAnimationGroup::finished, [this] () { 169 | _isChannelWidgetShowing = false; 170 | this->animFinish(); 171 | QRect pauseGeo = ui->pauseWidget->geometry(); 172 | pauseGeo.setHeight(this->geometry().height()); 173 | ui->pauseWidget->setGeometry(pauseGeo); 174 | this->setMaximumHeight(this->height()); 175 | }); 176 | animgroup->start(QAbstractAnimation::DeleteWhenStopped); 177 | } 178 | 179 | void MainWidget::animShowChannelWidget() { 180 | if (isChannelWidgetShowing() || isAnimationStarted()) return; 181 | this->animStart(); 182 | auto _maxhei = ui->controlWidget->height() + ui->channelWidget->height(); 183 | QParallelAnimationGroup *animgroup = new QParallelAnimationGroup(this); 184 | if (_maxhei > this->height()) { 185 | this->setMaximumHeight(_maxhei); 186 | 187 | QPropertyAnimation *main_anim = new QPropertyAnimation(this, "geometry"); 188 | main_anim->setDuration(400); 189 | main_anim->setStartValue(this->geometry()); 190 | QRect endval2 = this->geometry(); 191 | endval2.setHeight(endval2.height() + ui->channelWidget->geometry().height()); 192 | main_anim->setEndValue(endval2); 193 | main_anim->setEasingCurve(QEasingCurve::OutCubic); 194 | 195 | animgroup->addAnimation(main_anim); 196 | } 197 | 198 | QPropertyAnimation *control_anim = new QPropertyAnimation(ui->controlWidget, "pos"); 199 | control_anim->setDuration(400); 200 | control_anim->setStartValue(ui->controlWidget->pos()); 201 | QPoint endpos = ui->controlWidget->pos(); 202 | endpos.setY(ui->channelWidget->height()); 203 | control_anim->setEndValue(endpos); 204 | control_anim->setEasingCurve(QEasingCurve::OutCubic); 205 | 206 | animgroup->addAnimation(control_anim); 207 | 208 | QPropertyAnimation *topborder_anim = new QPropertyAnimation(this->topBorder, "pos"); 209 | topborder_anim->setDuration(400); 210 | QPoint topborder_pos = topBorder->pos(); 211 | topborder_anim->setStartValue(topborder_pos); 212 | topborder_pos.setY(ui->channelWidget->height() - topBorder->height()); 213 | topborder_anim->setEndValue(topborder_pos); 214 | topborder_anim->setEasingCurve(QEasingCurve::OutCubic); 215 | animgroup->addAnimation(topborder_anim); 216 | 217 | QPropertyAnimation *bottomborder_anim = new QPropertyAnimation(this->bottomBorder, "pos"); 218 | bottomborder_anim->setDuration(400); 219 | QPoint bottomborder_pos = bottomBorder->pos(); 220 | bottomborder_anim->setStartValue(bottomborder_pos); 221 | bottomborder_pos.setY(ui->channelWidget->height() + ui->controlWidget->height()); 222 | bottomborder_anim->setEndValue(bottomborder_pos); 223 | bottomborder_anim->setEasingCurve(QEasingCurve::OutCubic); 224 | animgroup->addAnimation(bottomborder_anim); 225 | 226 | connect(animgroup, &QParallelAnimationGroup::finished, [this] () { 227 | _isChannelWidgetShowing = true; 228 | this->animFinish(); 229 | ui->pauseWidget->setGeometry(0, 0, ui->pauseWidget->geometry().width(), this->geometry().height()); 230 | this->setMinimumHeight(this->height()); 231 | }); 232 | animgroup->start(QAbstractAnimation::DeleteWhenStopped); 233 | } 234 | 235 | bool MainWidget::isChannelWidgetShowing() const { 236 | return _isChannelWidgetShowing; 237 | } 238 | 239 | bool MainWidget::isAnimationStarted() const { 240 | return _isAnimStarted; 241 | } 242 | 243 | bool MainWidget::isLyricWidgetShowing() const { 244 | return _isLyricWidgetShowing; 245 | } 246 | 247 | void MainWidget::animStart() { 248 | _isAnimStarted = true; 249 | emit animationStart(); 250 | } 251 | 252 | void MainWidget::animFinish() { 253 | _isAnimStarted = false; 254 | emit animationFinish(); 255 | } 256 | 257 | void MainWidget::animHideLyricWidget() { 258 | if (!isLyricWidgetShowing() || isAnimationStarted()) return; 259 | this->animStart(); 260 | this->setMinimumHeight(ui->controlWidget->pos().y() + ui->controlWidget->height()); 261 | QParallelAnimationGroup *animgroup = new QParallelAnimationGroup(this); 262 | QPoint lyric_cur = ui->lyricWidget->pos(); 263 | QRect self_cur = this->geometry(); 264 | 265 | QPropertyAnimation *lyric_anim = new QPropertyAnimation(ui->lyricWidget, "pos"); 266 | lyric_anim->setDuration(400); 267 | lyric_anim->setStartValue(lyric_cur); 268 | lyric_cur.setY(ui->controlWidget->pos().y() 269 | + ui->controlWidget->height() 270 | - ui->lyricWidget->height()); 271 | lyric_anim->setEndValue(lyric_cur); 272 | lyric_anim->setEasingCurve(QEasingCurve::OutCubic); 273 | animgroup->addAnimation(lyric_anim); 274 | 275 | QPropertyAnimation *self_anim = new QPropertyAnimation(this, "geometry"); 276 | self_anim->setDuration(400); 277 | self_anim->setStartValue(self_cur); 278 | self_cur.setHeight(ui->controlWidget->pos().y() + ui->controlWidget->height()); 279 | self_anim->setEndValue(self_cur); 280 | self_anim->setEasingCurve(QEasingCurve::OutCubic); 281 | animgroup->addAnimation(self_anim); 282 | 283 | connect(animgroup, &QAnimationGroup::finished, [=] () { 284 | _isLyricWidgetShowing = false; 285 | animFinish(); 286 | ui->pauseWidget->setGeometry(0, 0, ui->pauseWidget->geometry().width(), this->geometry().height()); 287 | this->setMaximumHeight(this->height()); 288 | ui->lyricWidget->setShowing(false); 289 | ui->lyricWidget->move(0, ui->controlWidget->height() 290 | - ui->lyricWidget->height()); 291 | }); 292 | 293 | animgroup->start(QAbstractAnimation::DeleteWhenStopped); 294 | } 295 | 296 | void MainWidget::animShowLyricWidget() { 297 | if (isLyricWidgetShowing() || isAnimationStarted()) return; 298 | this->animStart(); 299 | this->setMaximumHeight(this->height() + ui->lyricWidget->height()); 300 | QParallelAnimationGroup *animgroup = new QParallelAnimationGroup(this); 301 | QPoint lyric_cur = ui->lyricWidget->pos(); 302 | QRect self_cur = this->geometry(); 303 | 304 | QPropertyAnimation *lyric_anim = new QPropertyAnimation(ui->lyricWidget, "pos"); 305 | lyric_anim->setDuration(400); 306 | lyric_anim->setStartValue(lyric_cur); 307 | lyric_cur.setY(ui->controlWidget->height()); 308 | lyric_anim->setEndValue(lyric_cur); 309 | lyric_anim->setEasingCurve(QEasingCurve::OutCubic); 310 | animgroup->addAnimation(lyric_anim); 311 | 312 | QPropertyAnimation *self_anim = new QPropertyAnimation(this, "geometry"); 313 | self_anim->setDuration(400); 314 | self_anim->setStartValue(self_cur); 315 | self_cur.setHeight(ui->controlWidget->height() + ui->lyricWidget->height()); 316 | self_anim->setEndValue(self_cur); 317 | self_anim->setEasingCurve(QEasingCurve::OutCubic); 318 | animgroup->addAnimation(self_anim); 319 | 320 | connect(animgroup, &QAnimationGroup::finished, [=] () { 321 | _isLyricWidgetShowing = true; 322 | animFinish(); 323 | ui->pauseWidget->setGeometry(0, 0, ui->pauseWidget->geometry().width(), this->geometry().height()); 324 | this->setMinimumHeight(this->height()); 325 | ui->lyricWidget->setShowing(true); 326 | }); 327 | 328 | animgroup->start(QAbstractAnimation::DeleteWhenStopped); 329 | } 330 | --------------------------------------------------------------------------------