1 #include "playlistcontroller.h"
2 
3 #include <QDebug>
4 #include <QHeaderView>
5 #include <QAbstractItemView>
6 #include <QScrollBar>
7 #include <QThread>
8 #include <QTimer>
9 #include <QMouseEvent>
10 
11 namespace PlaylistUi {
Controller(QTableView * v,QLineEdit * s,BusySpinner * sp,Config::Local & local_cfg,Config::Global & global_cfg,QObject * parent)12   Controller::Controller(QTableView *v, QLineEdit *s, BusySpinner *sp, Config::Local &local_cfg, Config::Global &global_cfg, QObject *parent) : QObject(parent), search(s), spinner(sp), local_conf(local_cfg), global_conf(global_cfg) {
13     restore_scroll_once = true;
14     view = v;
15     scroll_positions.clear();
16 
17     loadColumnsConfig();
18 
19     model = new Model(view->style(), columns_config, this);
20     proxy = new ProxyFilterModel(model, this);
21     view->setModel(proxy);
22     view->horizontalHeader()->hide();
23     view->verticalHeader()->hide();
24     view->setSelectionBehavior(QAbstractItemView::SelectRows);
25     view->setShowGrid(false);
26     //view->setFocusPolicy(Qt::NoFocus);
27     view->setEditTriggers(QAbstractItemView::NoEditTriggers);
28     //view->horizontalHeader()->setStretchLastSection(true);
29 
30     view->setAlternatingRowColors(true);
31     view->setFocus();
32 
33     view->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
34     view->verticalHeader()->setDefaultSectionSize(view->verticalHeader()->minimumSectionSize());
35 
36     for (int c = 0; c < view->horizontalHeader()->count(); ++c) {
37       view->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Fixed);
38     }
39 
40     view->viewport()->installEventFilter(this);
41     view->installEventFilter(this);
42 
43     connect(view, &QTableView::activated, [=](const QModelIndex &index) {
44       emit activated(model->itemAt(proxy->mapToSource(index)));
45     });
46 
47     connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &Controller::on_currentSelectionChanged);
48     connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Controller::on_selectionChanged);
49 
50     connect(search, &QLineEdit::textChanged, this, &Controller::on_search);
51     search->setClearButtonEnabled(true);
52 
53     connect(view->verticalScrollBar(), &QScrollBar::valueChanged, [=](int val) {
54       if (model->playlist() != nullptr) {
55         scroll_positions[model->playlist()->uid()] = val;
56       }
57     });
58 
59     context_menu = new PlaylistContextMenu(model, proxy, view, search, this);
60     connect(context_menu, &PlaylistContextMenu::playlistChanged, this, &Controller::changed);
61 
62     view->setContextMenuPolicy(Qt::CustomContextMenu);
63     connect(view, &QTableView::customContextMenuRequested, context_menu, &PlaylistContextMenu::show);
64   }
65 
loadColumnsConfig()66   void PlaylistUi::Controller::loadColumnsConfig() {
67     auto c = global_conf.columnsConfig();
68     if (c.count() == 0) {
69       global_conf.saveColumnsConfig(columns_config);
70     } else {
71       columns_config = global_conf.columnsConfig();
72     }
73   }
74 
on_load(const std::shared_ptr<Playlist::Playlist> pi)75   void Controller::on_load(const std::shared_ptr<Playlist::Playlist> pi) {
76     if (pi == nullptr) {
77       return;
78     }
79     model->setPlaylist(pi);
80 
81     if (scroll_positions.contains(pi->uid())) {
82       QTimer::singleShot(20, [=]() { // hack: https://stackoverflow.com/questions/50989433/qtableviewscrollto-immediately-after-model-reset-and-after-some-delay
83         view->verticalScrollBar()->setValue(scroll_positions[pi->uid()]);
84       });
85     }
86   }
87 
on_unload()88   void Controller::on_unload() {
89     model->setPlaylist(nullptr);
90   }
91 
on_stop()92   void Controller::on_stop() {
93     model->highlight(0, Model::HighlightState::None);
94   }
95 
on_start(const Track & t)96   void Controller::on_start(const Track &t) {
97     model->highlight(t.uid(), Model::HighlightState::Playing);
98   }
99 
on_pause(const Track & t)100   void Controller::on_pause(const Track &t) {
101     model->highlight(t.uid(), Model::HighlightState::Paused);
102   }
103 
on_scrollTo(const Track & track)104   void Controller::on_scrollTo(const Track &track) {
105     QModelIndex index = proxy->mapFromSource(model->indexOf(track.uid()));
106     if (index.isValid()) {
107       view->setCurrentIndex(index);
108       view->scrollTo(index, QAbstractItemView::PositionAtCenter);
109       emit selected(track);
110     }
111   }
112 
on_appendToPlaylist(const QList<QDir> & filepaths)113   void Controller::on_appendToPlaylist(const QList<QDir> &filepaths) {
114     if (model->playlist() != nullptr) {
115       connect(&*model->playlist(), &Playlist::Playlist::concatAsyncFinished, this, &Controller::on_appendAsyncFinished);
116       model->playlist()->concatAsync(filepaths);
117       spinner->show();
118     }
119   }
120 
sortBy(const QString & criteria)121   void Controller::sortBy(const QString &criteria) {
122     if (model->playlist() != nullptr) {
123       model->playlist()->sortBy(criteria);
124       model->reload();
125       emit changed(model->playlist());
126     }
127   }
128 
on_appendAsyncFinished(Playlist::Playlist * pl)129   void Controller::on_appendAsyncFinished(Playlist::Playlist *pl) {
130     disconnect(pl, &Playlist::Playlist::concatAsyncFinished, this, &Controller::on_appendAsyncFinished);
131     model->reload();
132     emit changed(model->playlist());
133     spinner->hide();
134   }
135 
eventFilter(QObject * obj,QEvent * event)136   bool Controller::eventFilter(QObject *obj, QEvent *event) {
137     if (obj == view->viewport()) {
138       eventFilterViewport(event);
139     } else if (obj == view) {
140       eventFilterTableView(event);
141     }
142     return QObject::eventFilter(obj, event);
143   }
144 
eventFilterTableView(QEvent * event)145   void Controller::eventFilterTableView(QEvent *event) {
146     if (event->type() == QEvent::KeyPress) {
147       QKeyEvent* keyevent = dynamic_cast<QKeyEvent*>(event);
148       if (keyevent->key() == Qt::Key_Delete) {
149         context_menu->on_remove();
150       }
151     }
152   }
153 
eventFilterViewport(QEvent * event)154   void Controller::eventFilterViewport(QEvent *event) {
155     if (event->type() == QEvent::Resize) {
156       int total_width = view->width();
157       view->horizontalHeader()->setMinimumSectionSize(20);
158       view->setColumnWidth(0, 20);
159 
160       for (int col = 1; col <= columns_config.count(); col++) {
161         auto rel_width = columns_config.width(col);
162         if (rel_width > 0) {
163           view->setColumnWidth(col, static_cast<int>(total_width * rel_width));
164         }
165         if (columns_config.stretch(col)) {
166           view->horizontalHeader()->setSectionResizeMode(col, QHeaderView::Stretch);
167         }
168       }
169 
170     } else if (event->type() == QEvent::WindowActivate) {
171       if (restore_scroll_once) {
172         restore_scroll_once = false;
173         view->verticalScrollBar()->setValue(local_conf.playlistViewScrollPosition());
174       }
175     } else if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress) {
176       local_conf.savePlaylistViewScrollPosition(view->verticalScrollBar()->value());
177     }
178 
179     if (event->type() == QEvent::MouseButtonPress) {
180       QMouseEvent *me = dynamic_cast<QMouseEvent *>(event);
181       if (me->button() == Qt::BackButton) {
182         search->clear();
183       }
184     }
185   }
186 
on_search(const QString & term)187   void Controller::on_search(const QString &term) {
188     proxy->filter(term);
189     if (term.isEmpty()) {
190       QTimer::singleShot(20, [=]() {
191         if (!view->selectionModel()->selectedRows().isEmpty()) {
192           view->scrollTo(view->selectionModel()->selectedRows().first(), QAbstractItemView::PositionAtCenter);
193         }
194       });
195     }
196   }
197 
on_currentSelectionChanged(const QModelIndex & index,const QModelIndex & prev)198   void Controller::on_currentSelectionChanged(const QModelIndex &index, const QModelIndex &prev) {
199     Q_UNUSED(prev)
200     auto source_index = proxy->mapToSource(index);
201     if (index.isValid() && source_index.isValid() && source_index.row() < model->rowCount()) {
202       emit selected(model->itemAt(source_index));
203     }
204   }
205 
on_selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)206   void Controller::on_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
207     Q_UNUSED(deselected)
208     Q_UNUSED(selected)
209 
210     quint32 selection_time = 0;
211     for (auto i: view->selectionModel()->selectedRows()) {
212       auto source_index = proxy->mapToSource(i);
213       if (i.isValid() && source_index.isValid() && source_index.row() < model->rowCount()) {
214         selection_time += model->itemAt(source_index).duration();
215       }
216     }
217     emit durationOfSelectedChanged(selection_time);
218   }
219 }
220