1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #ifndef PLAYLISTVIEW_H
23 #define PLAYLISTVIEW_H
24 
25 #include "config.h"
26 
27 #include <memory>
28 
29 #include <QtGlobal>
30 #include <QObject>
31 #include <QAbstractItemDelegate>
32 #include <QAbstractItemModel>
33 #include <QStyleOptionViewItem>
34 #include <QAbstractItemView>
35 #include <QTreeView>
36 #include <QList>
37 #include <QByteArray>
38 #include <QString>
39 #include <QImage>
40 #include <QPixmap>
41 #include <QColor>
42 #include <QRect>
43 #include <QRegion>
44 #include <QStyleOption>
45 #include <QProxyStyle>
46 #include <QPoint>
47 #include <QBasicTimer>
48 #include <QCommonStyle>
49 
50 #include "core/song.h"
51 #include "covermanager/albumcoverloaderresult.h"
52 #include "settings/appearancesettingspage.h"
53 #include "playlist.h"
54 
55 class QWidget;
56 class QTimer;
57 class QTimeLine;
58 class QPainter;
59 class QEvent;
60 class QShowEvent;
61 class QContextMenuEvent;
62 class QDragEnterEvent;
63 class QDragLeaveEvent;
64 class QDragMoveEvent;
65 class QDropEvent;
66 class QFocusEvent;
67 class QHideEvent;
68 class QKeyEvent;
69 class QMouseEvent;
70 class QPaintEvent;
71 class QTimerEvent;
72 
73 class Application;
74 class CollectionBackend;
75 class PlaylistHeader;
76 class DynamicPlaylistControls;
77 class RatingItemDelegate;
78 
79 // This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
80 // that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
81 // That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
82 // This proxy style uses QCommonStyle to paint the affected elements.
83 // This class is used by internet search view as well.
84 class PlaylistProxyStyle : public QProxyStyle {
85   Q_OBJECT
86 
87  public:
88   explicit PlaylistProxyStyle(QObject *parent = nullptr);
89 
90   void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
91   void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
92 
93  private:
94   std::unique_ptr<QCommonStyle> common_style_;
95 };
96 
97 class PlaylistView : public QTreeView {
98   Q_OBJECT
99 
100  public:
101   explicit PlaylistView(QWidget *parent = nullptr);
102   ~PlaylistView() override;
103 
104   static ColumnAlignmentMap DefaultColumnAlignment();
105 
106   void Init(Application *app);
107   void SetItemDelegates();
108   void SetPlaylist(Playlist *playlist);
109   void RemoveSelected();
110 
SetReadOnlySettings(const bool read_only)111   void SetReadOnlySettings(const bool read_only) { read_only_settings_ = read_only; }
112 
playlist()113   Playlist *playlist() const { return playlist_; }
background_image_type()114   AppearanceSettingsPage::BackgroundImageType background_image_type() const { return background_image_type_; }
115   Qt::Alignment column_alignment(int section) const;
116 
117   void ResetHeaderState();
118 
119   // QTreeView
120   void setModel(QAbstractItemModel *model) override;
121 
122  public slots:
123   void ReloadSettings();
124   void SaveSettings();
125   void SetColumnAlignment(const int section, const Qt::Alignment alignment);
126   void JumpToCurrentlyPlayingTrack();
edit(const QModelIndex & idx)127   void edit(const QModelIndex &idx) { QAbstractItemView::edit(idx); }
128 
129  signals:
130   void PlayItem(QModelIndex idx, Playlist::AutoScroll autoscroll);
131   void PlayPause(const quint64 offset_nanosec = 0, Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Never);
132   void RightClicked(QPoint global_pos, QModelIndex idx);
133   void SeekForward();
134   void SeekBackward();
135   void FocusOnFilterSignal(QKeyEvent *event);
136   void BackgroundPropertyChanged();
137   void ColumnAlignmentChanged(ColumnAlignmentMap alignment);
138 
139  protected:
140   // QWidget
141   void keyPressEvent(QKeyEvent *event) override;
142   void contextMenuEvent(QContextMenuEvent *e) override;
143   void hideEvent(QHideEvent *event) override;
144   void showEvent(QShowEvent *event) override;
145   void timerEvent(QTimerEvent *event) override;
146   void mouseMoveEvent(QMouseEvent *event) override;
147   void mousePressEvent(QMouseEvent *event) override;
148   void leaveEvent(QEvent*) override;
149   void paintEvent(QPaintEvent *event) override;
150   void dragMoveEvent(QDragMoveEvent *event) override;
151   void dragEnterEvent(QDragEnterEvent *event) override;
152   void dragLeaveEvent(QDragLeaveEvent *event) override;
153   void dropEvent(QDropEvent *event) override;
154   bool eventFilter(QObject *object, QEvent *event) override;
155   void focusInEvent(QFocusEvent *event) override;
156   void resizeEvent(QResizeEvent *event) override;
157 
158   // QTreeView
159   void drawTree(QPainter *painter, const QRegion &region) const;
160   void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
161 
162   // QAbstractScrollArea
163   void scrollContentsBy(int dx, int dy) override;
164 
165   // QAbstractItemView
166   void rowsInserted(const QModelIndex &parent, int start, int end) override;
167   bool edit(const QModelIndex &idx, QAbstractItemView::EditTrigger trigger, QEvent *event) override;
168   void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) override;
169 
170  private slots:
Update()171   void Update() { update(); }
172   void SetHeaderState();
173   void InhibitAutoscrollTimeout();
174   void MaybeAutoscroll(const Playlist::AutoScroll autoscroll);
175   void InvalidateCachedCurrentPixmap();
176   void PlaylistDestroyed();
177   void StretchChanged(const bool stretch);
178   void FadePreviousBackgroundImage(const qreal value);
179   void StopGlowing();
180   void StartGlowing();
181   void JumpToLastPlayedTrack();
182   void CopyCurrentSongToClipboard() const;
183   void Playing();
184   void Stopped();
185   void SongChanged(const Song &song);
186   void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
187   void DynamicModeChanged(const bool dynamic);
188   void SetRatingLockStatus(const bool state);
189   void RatingHoverIn(const QModelIndex &idx, const QPoint pos);
190   void RatingHoverOut();
191 
192  private:
193   void LoadHeaderState();
194   void RestoreHeaderState();
195 
196   void ReloadBarPixmaps();
197   QList<QPixmap> LoadBarPixmap(const QString &filename);
198   void UpdateCachedCurrentRowPixmap(QStyleOptionViewItem option, const QModelIndex &idx);
199 
set_background_image_type(AppearanceSettingsPage::BackgroundImageType bg)200   void set_background_image_type(AppearanceSettingsPage::BackgroundImageType bg) {
201     background_image_type_ = bg;
202     emit BackgroundPropertyChanged();  // clazy:exclude=incorrect-emit
203   }
204   // Save image as the background_image_ after applying some modifications (opacity, ...).
205   // Should be used instead of modifying background_image_ directly
206   void set_background_image(const QImage &image);
207 
208   void GlowIntensityChanged();
209 
210  private:
211   static const int kGlowIntensitySteps;
212   static const int kAutoscrollGraceTimeout;
213   static const int kDropIndicatorWidth;
214   static const int kDropIndicatorGradientWidth;
215 
216   QList<int> GetEditableColumns();
217   QModelIndex NextEditableIndex(const QModelIndex &current);
218   QModelIndex PrevEditableIndex(const QModelIndex &current);
219 
220   void RepositionDynamicControls();
221 
222   Application *app_;
223   PlaylistProxyStyle *style_;
224   Playlist *playlist_;
225   PlaylistHeader *header_;
226 
227   qreal device_pixel_ratio_;
228   AppearanceSettingsPage::BackgroundImageType background_image_type_;
229   QString background_image_filename_;
230   AppearanceSettingsPage::BackgroundImagePosition background_image_position_;
231   int background_image_maxsize_;
232   bool background_image_stretch_;
233   bool background_image_do_not_cut_;
234   bool background_image_keep_aspect_ratio_;
235   int blur_radius_;
236   int opacity_level_;
237 
238   bool background_initialized_;
239   bool set_initial_header_layout_;
240   bool header_state_loaded_;
241   bool header_state_restored_;
242   bool read_only_settings_;
243 
244   QImage background_image_;
245   QImage current_song_cover_art_;
246   QPixmap cached_scaled_background_image_;
247 
248   // For fading when image change
249   QPixmap previous_background_image_;
250   qreal previous_background_image_opacity_;
251   QTimeLine *fade_animation_;
252 
253   // To know if we should redraw the background or not
254   bool force_background_redraw_;
255   int last_height_;
256   int last_width_;
257   int current_background_image_x_;
258   int current_background_image_y_;
259   int previous_background_image_x_;
260   int previous_background_image_y_;
261 
262   bool glow_enabled_;
263   bool select_track_;
264   bool auto_sort_;
265 
266   bool currently_glowing_;
267   QBasicTimer glow_timer_;
268   int glow_intensity_step_;
269   QModelIndex last_current_item_;
270   QRect last_glow_rect_;
271 
272   QTimer *inhibit_autoscroll_timer_;
273   bool inhibit_autoscroll_;
274   bool currently_autoscrolling_;
275 
276   int row_height_;  // Used to invalidate the currenttrack_bar pixmaps
277   QList<QPixmap> currenttrack_bar_left_;
278   QList<QPixmap> currenttrack_bar_mid_;
279   QList<QPixmap> currenttrack_bar_right_;
280   QPixmap currenttrack_play_;
281   QPixmap currenttrack_pause_;
282 
283   QRegion current_paint_region_;
284   QPixmap cached_current_row_;
285   QRect cached_current_row_rect_;
286   int cached_current_row_row_;
287 
288   QPixmap cached_tree_;
289   int drop_indicator_row_;
290   bool drag_over_;
291 
292   int header_state_version_;
293   QByteArray header_state_;
294   ColumnAlignmentMap column_alignment_;
295   bool rating_locked_;
296 
297   Song song_playing_;
298 
299   DynamicPlaylistControls *dynamic_controls_;
300   RatingItemDelegate *rating_delegate_;
301 
302   QColor playlist_playing_song_color_;
303 
304 };
305 
306 #endif  // PLAYLISTVIEW_H
307