1 /***************************************************************************
2  *   Copyright (C) 2006-2021 by Ilya Kotov                                 *
3  *   forkotov02@ya.ru                                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20 #include <QPixmap>
21 #include <QResizeEvent>
22 #include <QPainter>
23 #include <QFont>
24 #include <QFontMetrics>
25 #include <QSettings>
26 #include <QMenu>
27 #include <QUrl>
28 #include <QApplication>
29 #include <QHelpEvent>
30 #include <QTimer>
31 #include <QScrollBar>
32 #include <QMimeData>
33 #include <qmmpui/playlistitem.h>
34 #include <qmmpui/playlistmodel.h>
35 #include <qmmpui/qmmpuisettings.h>
36 #include <qmmpui/mediaplayer.h>
37 #include "listwidget.h"
38 #include "playlistheader.h"
39 #include "actionmanager.h"
40 #include "popupwidget.h"
41 
42 #define INVALID_INDEX -1
43 
ListWidget(PlayListModel * model,QWidget * parent)44 ListWidget::ListWidget(PlayListModel *model, QWidget *parent) : QWidget(parent),
45     m_pressed_index(INVALID_INDEX),
46     m_drop_index(INVALID_INDEX),
47     m_anchor_index(INVALID_INDEX),
48     m_model(model)
49 {
50     setFocusPolicy(Qt::StrongFocus);
51     m_ui_settings = QmmpUiSettings::instance();
52     m_timer = new QTimer(this);
53     m_timer->setInterval(50);
54     m_header = new PlayListHeader(this);
55     m_scrollBar = new QScrollBar(Qt::Vertical, this);
56     m_hslider = new QScrollBar(Qt::Horizontal, this);
57     m_hslider->setPageStep(50);
58 
59     setAcceptDrops(true);
60     setMouseTracking(true);
61 
62     readSettings();
63     connect(m_ui_settings, SIGNAL(repeatableTrackChanged(bool)), SLOT(updateRepeatIndicator()));
64     connect(m_timer, SIGNAL(timeout()), SLOT(autoscroll()));
65     connect(m_scrollBar, SIGNAL(valueChanged (int)), SLOT(setViewPosition(int)));
66     connect(m_hslider, SIGNAL(valueChanged(int)), m_header, SLOT(scroll(int)));
67     connect(m_hslider, SIGNAL(valueChanged(int)), this, SLOT(update()));
68     connect(m_model, SIGNAL(scrollToRequest(int)), SLOT(scrollTo(int)));
69     connect(m_model, SIGNAL(listChanged(int)), SLOT(updateList(int)));
70     connect(m_model, SIGNAL(sortingByColumnFinished(int,bool)), m_header, SLOT(showSortIndicator(int,bool)));
71     SET_ACTION(ActionManager::PL_SHOW_HEADER, this, SLOT(readSettings()));
72 }
73 
~ListWidget()74 ListWidget::~ListWidget()
75 {
76     qDeleteAll(m_rows);
77     m_rows.clear();
78 }
79 
readSettings()80 void ListWidget::readSettings()
81 {
82     QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
83     settings.beginGroup("Simple");
84     m_show_protocol = settings.value ("pl_show_protocol", false).toBool();
85     bool show_popup = settings.value("pl_show_popup", false).toBool();
86 
87     m_header->readSettings();
88     m_header->setVisible(ACTION(ActionManager::PL_SHOW_HEADER)->isChecked());
89     m_header->setGeometry(0,0,width(), m_header->requiredHeight());
90 
91     if (m_update)
92     {
93         m_drawer.readSettings();
94         updateList(PlayListModel::STRUCTURE);
95         if(m_popupWidget)
96         {
97             m_popupWidget->deleteLater();
98             m_popupWidget = nullptr;
99         }
100     }
101     else
102     {
103         m_update = true;
104     }
105 
106     if(show_popup)
107         m_popupWidget = new PlayListPopup::PopupWidget(this);
108 }
109 
visibleRows() const110 int ListWidget::visibleRows() const
111 {
112     return m_row_count;
113 }
114 
firstVisibleIndex() const115 int ListWidget::firstVisibleIndex() const
116 {
117     return m_first;
118 }
119 
anchorIndex() const120 int ListWidget::anchorIndex() const
121 {
122     return m_anchor_index;
123 }
124 
setAnchorIndex(int index)125 void ListWidget::setAnchorIndex(int index)
126 {
127     m_anchor_index = index;
128     updateList(PlayListModel::SELECTION);
129 }
130 
menu()131 QMenu *ListWidget::menu()
132 {
133     return m_menu;
134 }
135 
setMenu(QMenu * menu)136 void ListWidget::setMenu(QMenu *menu)
137 {
138     m_menu = menu;
139 }
140 
model()141 PlayListModel *ListWidget::model()
142 {
143     Q_ASSERT(m_model);
144     return m_model;
145 }
146 
filterMode() const147 bool ListWidget::filterMode() const
148 {
149     return m_filterMode;
150 }
151 
setModel(PlayListModel * selected,PlayListModel * previous)152 void ListWidget::setModel(PlayListModel *selected, PlayListModel *previous)
153 {
154     if(m_filterMode)
155     {
156         m_filterMode = false;
157         m_first = 0;
158         m_filteredItems.clear();
159     }
160 
161     if(previous)
162     {
163         previous->setProperty("first_visible", m_first);
164         disconnect(previous, nullptr, this, nullptr); //disconnect previous model
165         disconnect(previous,nullptr,m_header,nullptr);
166     }
167     qApp->processEvents();
168     m_model = selected;
169     m_count = m_model->count();
170     m_firstItem = nullptr;
171 
172     if(m_model->property("first_visible").isValid())
173     {
174         m_first = m_model->property("first_visible").toInt();
175         updateList(PlayListModel::STRUCTURE);
176     }
177     else
178     {
179         m_first = 0;
180         updateList(PlayListModel::STRUCTURE | PlayListModel::CURRENT);
181     }
182     connect (m_model, SIGNAL(scrollToRequest(int)), SLOT(scrollTo(int)));
183     connect (m_model, SIGNAL(listChanged(int)), SLOT(updateList(int)));
184     connect (m_model, SIGNAL(sortingByColumnFinished(int,bool)), m_header, SLOT(showSortIndicator(int,bool)));
185 }
186 
paintEvent(QPaintEvent *)187 void ListWidget::paintEvent(QPaintEvent *)
188 {
189     QPainter painter(this);
190     m_drawer.fillBackground(&painter, width(), height());
191     painter.setLayoutDirection(Qt::LayoutDirectionAuto);
192     bool rtl = (layoutDirection() == Qt::RightToLeft);
193     int scroll_bar_width = m_scrollBar->isVisibleTo(this) ? m_scrollBar->sizeHint().width() : 0;
194 
195     painter.setClipRect(5,0,width() - scroll_bar_width - 9, height());
196     painter.translate(rtl ? m_header->offset() : -m_header->offset(), 0);
197 
198     for (int i = 0; i < m_rows.size(); ++i )
199     {
200         m_drawer.drawBackground(&painter, m_rows[i], i);
201 
202         if(m_rows[i]->flags & ListWidgetRow::GROUP)
203         {
204             m_drawer.drawSeparator(&painter, m_rows[i], rtl);
205             continue;
206         }
207 
208         m_drawer.drawTrack(&painter, m_rows[i], rtl);
209     }
210     //draw drop line
211     if(m_drop_index != INVALID_INDEX)
212     {
213         m_drawer.drawDropLine(&painter, m_drop_index - m_first, width(),
214                               m_header->isVisible() ? m_header->height() : 0);
215     }
216 }
217 
mouseDoubleClickEvent(QMouseEvent * e)218 void ListWidget::mouseDoubleClickEvent (QMouseEvent *e)
219 {
220     int y = e->y();
221     int index = indexAt(y);
222 
223     if (INVALID_INDEX != index)
224     {
225         if(m_filterMode)
226         {
227             m_filterMode = false;
228             m_filteredItems.clear();
229             scrollTo(index);
230         }
231 
232         m_model->setCurrent(index);
233         MediaPlayer *player = MediaPlayer::instance();
234         player->playListManager()->selectPlayList(m_model);
235         player->playListManager()->activatePlayList(m_model);
236         player->stop();
237         player->play();
238         emit doubleClicked();
239         update();
240     }
241 }
242 
mousePressEvent(QMouseEvent * e)243 void ListWidget::mousePressEvent(QMouseEvent *e)
244 {
245     if(m_popupWidget)
246         m_popupWidget->hide();
247 
248     int index = indexAt(e->y());
249 
250     if (INVALID_INDEX != index && m_model->count() > index)
251     {
252         m_pressed_index = index;
253         if(e->button() == Qt::RightButton)
254         {
255             if(!m_model->isSelected(index))
256             {
257                 m_anchor_index = m_pressed_index;
258                 m_model->clearSelection();
259                 m_model->setSelected(index, true);
260             }
261             if(m_model->isGroup(index) && m_model->selectedTracks().isEmpty())
262             {
263                 m_anchor_index = m_pressed_index;
264                 PlayListGroup *group = m_model->group(index);
265                 m_model->setSelected(group->tracks());
266             }
267             QWidget::mousePressEvent(e);
268             return;
269         }
270 
271         if (m_model->isSelected(index) && (e->modifiers() == Qt::NoModifier))
272         {
273             m_select_on_release = true;
274             QWidget::mousePressEvent(e);
275             return;
276         }
277 
278         if ((Qt::ShiftModifier & e->modifiers()))
279         {
280             int prev_anchor_index = m_anchor_index;
281             m_anchor_index = m_pressed_index;
282             m_model->setSelected(m_pressed_index, prev_anchor_index, true);
283         }
284         else //ShiftModifier released
285         {
286             m_anchor_index = m_pressed_index;
287             if ((Qt::ControlModifier & e->modifiers()))
288             {
289                 m_model->setSelected(index, !m_model->isSelected(index));
290             }
291             else //ControlModifier released
292             {
293                 m_model->clearSelection();
294                 m_model->setSelected(index, true);
295             }
296 
297         }
298         update();
299     }
300     QWidget::mousePressEvent(e);
301 }
302 
resizeEvent(QResizeEvent * e)303 void ListWidget::resizeEvent(QResizeEvent *e)
304 {
305     m_header->setGeometry(0,0,width(), m_header->requiredHeight());
306     if(e->oldSize().height() < 10)
307         updateList(PlayListModel::STRUCTURE | PlayListModel::CURRENT); //recenter to current on first resize
308     else
309         updateList(PlayListModel::STRUCTURE);
310     QWidget::resizeEvent(e);
311 }
312 
wheelEvent(QWheelEvent * e)313 void ListWidget::wheelEvent (QWheelEvent *e)
314 {
315     if(m_hslider->underMouse())
316         return;
317 
318     if (m_model->count() <= m_row_count)
319         return;
320     if ((m_first == 0 && e->delta() > 0) ||
321             ((m_first == m_model->count() - m_row_count) && e->delta() < 0))
322         return;
323     m_first -= e->delta()/40;  //40*3 TODO: add step to config
324     if (m_first < 0)
325         m_first = 0;
326 
327     if (m_first > m_model->count() - m_row_count)
328         m_first = m_model->count() - m_row_count;
329 
330     updateList(PlayListModel::STRUCTURE);
331 }
332 
showEvent(QShowEvent *)333 void ListWidget::showEvent(QShowEvent *)
334 {
335     if(!m_rows.isEmpty())
336         updateList(PlayListModel::METADATA);
337 }
338 
event(QEvent * e)339 bool ListWidget::event (QEvent *e)
340 {
341     if(m_popupWidget)
342     {
343         if(e->type() == QEvent::ToolTip)
344         {
345             QHelpEvent *helpEvent = (QHelpEvent *) e;
346             int index = indexAt(helpEvent->y());
347             if(index < 0 || !m_model->isTrack(index))
348             {
349                 m_popupWidget->deactivate();
350                 return QWidget::event(e);
351             }
352             e->accept();
353             m_popupWidget->prepare(m_model->track(index), helpEvent->globalPos());
354             return true;
355         }
356         else if(e->type() == QEvent::Leave)
357             m_popupWidget->deactivate();
358     }
359     if(e->type() == QEvent::StyleChange)
360         readSettings();
361 
362     return QWidget::event(e);
363 }
364 
updateList(int flags)365 void ListWidget::updateList(int flags)
366 {
367     m_hslider->setRange(0, m_header->maxScrollValue());
368     m_hslider->setValue(m_header->offset());
369 
370     if(updateRowCount())
371         flags |= PlayListModel::STRUCTURE;
372 
373     if(flags & PlayListModel::STRUCTURE && m_filterMode)
374     {
375         m_filteredItems = m_model->findTracks(m_filterString);
376     }
377 
378     if(flags & PlayListModel::CURRENT)
379         recenterTo(m_model->currentIndex());
380 
381     QList<PlayListItem *> items;
382     int count = m_filterMode ? m_filteredItems.count() : m_model->count();
383 
384     if(flags & PlayListModel::STRUCTURE || flags & PlayListModel::CURRENT)
385     {
386         m_scrollBar->blockSignals(true);
387         if(m_row_count >= count)
388         {
389             m_first = 0;
390             m_scrollBar->setMaximum(0);
391             m_scrollBar->setValue(0);
392             emit positionChanged(0,0);
393         }
394         else if(m_first + m_row_count >= count)
395         {
396             //try to restore first visible first
397             if(!m_filterMode && (m_count > 0) &&
398                     (m_count != m_model->count()) && m_firstItem)
399             {
400                 restoreFirstVisible();
401             }
402             if(m_first + m_row_count >= count)
403                 m_first = qMax(0, count - m_row_count);
404             m_scrollBar->setMaximum(count - m_row_count);
405             m_scrollBar->setValue(m_first);
406             emit positionChanged(m_first, m_first);
407         }
408         else if(!m_filterMode && (m_count > 0) && (m_count != m_model->count()) &&
409                 m_firstItem && m_model->item(m_first) != m_firstItem)
410         {
411             restoreFirstVisible();
412             m_scrollBar->setMaximum(count - m_row_count);
413             m_scrollBar->setValue(m_first);
414             emit positionChanged(m_first, m_model->count() - m_row_count);
415         }
416         else
417         {
418             m_scrollBar->setMaximum(count - m_row_count);
419             m_scrollBar->setValue(m_first);
420             emit positionChanged(m_first, count - m_row_count);
421         }
422         m_scrollBar->blockSignals(false);
423 
424         if(!m_filterMode)
425         {
426             m_firstItem = m_model->isEmpty() ? nullptr : m_model->item(m_first);
427             m_count = m_model->count();
428         }
429         items = m_filterMode ? m_filteredItems.mid(m_first, m_row_count) : m_model->mid(m_first, m_row_count);
430 
431         while(m_rows.count() < qMin(m_row_count, items.count()))
432             m_rows << new ListWidgetRow;
433         while(m_rows.count() > qMin(m_row_count, items.count()))
434             delete m_rows.takeFirst();
435 
436         m_scrollBar->setVisible(count > m_row_count);
437     }
438     else
439     {
440         items = m_filterMode ? m_filteredItems.mid(m_first, m_row_count) : m_model->mid(m_first, m_row_count);
441     }
442 
443     if(flags & PlayListModel::STRUCTURE)
444         m_header->hideSortIndicator();
445 
446     if(flags & PlayListModel::STRUCTURE || flags & PlayListModel::METADATA)
447     {
448         //song numbers width
449         m_drawer.calculateNumberWidth(m_model->trackCount());
450         m_drawer.setSingleColumnMode(m_model->columnCount() == 1);
451         m_header->setNumberWidth(m_drawer.numberWidth());
452     }
453 
454     updateScrollBars();
455 
456     int scroll_bar_width = m_scrollBar->isVisibleTo(this) ? m_scrollBar->sizeHint().width() : 0;
457     int trackStateColumn = m_header->trackStateColumn();
458     int rowWidth = width() + m_header->maxScrollValue() - 10 - scroll_bar_width;
459     bool rtl = layoutDirection() == Qt::RightToLeft;
460     m_header->setScrollBarWidth(scroll_bar_width);
461     m_hslider->setVisible(m_header->maxScrollValue() > 0);
462 
463     for(int i = 0; i < items.count(); ++i)
464     {
465         ListWidgetRow *row = m_rows[i];
466         row->autoResize = m_header->hasAutoResizeColumn();
467         row->trackStateColumn = trackStateColumn;
468         items[i]->isSelected() ? row->flags |= ListWidgetRow::SELECTED :
469                 row->flags &= ~ListWidgetRow::SELECTED;
470 
471         i == (m_anchor_index - m_first) ? row->flags |= ListWidgetRow::ANCHOR :
472                 row->flags &= ~ListWidgetRow::ANCHOR;
473 
474         if(flags == PlayListModel::SELECTION)
475             continue;
476 
477         if(rtl)
478         {
479             row->rect = QRect(width() - 5 - rowWidth, (m_header->isVisibleTo(this) ? m_header->height() : 0) + i * m_drawer.rowHeight(),
480                               rowWidth, m_drawer.rowHeight() - 1);
481         }
482         else
483         {
484             row->rect = QRect(5, (m_header->isVisibleTo(this) ? m_header->height() : 0) + i * m_drawer.rowHeight(),
485                               rowWidth, m_drawer.rowHeight() - 1);
486         }
487 
488         row->titles = items[i]->formattedTitles();
489         row->sizes = m_header->sizes();
490         row->alignment = m_header->alignment();
491 
492         items[i] == m_model->currentTrack() ? row->flags |= ListWidgetRow::CURRENT :
493                 row->flags &= ~ListWidgetRow::CURRENT;
494 
495         if(items[i]->isGroup())
496         {
497             row->flags |= ListWidgetRow::GROUP;
498             row->number = -1;
499             row->length.clear();
500         }
501         else
502         {
503             row->flags &= ~ListWidgetRow::GROUP;
504             row->number = items.at(i)->trackIndex() + 1;
505             row->length = items.at(i)->formattedLength();
506             row->extraString = getExtraString(items.at(i));
507         }
508         m_drawer.prepareRow(row);  //elide titles
509     }
510     update();
511 }
512 
autoscroll()513 void ListWidget::autoscroll()
514 {
515     if(m_filterMode)
516         return;
517 
518     SimpleSelection sel = m_model->getSelection(m_pressed_index);
519     if ((sel.m_top == 0 && m_scroll_direction == TOP && sel.count() > 1) ||
520         (sel.m_bottom == m_model->count() - 1 && m_scroll_direction == DOWN && sel.count() > 1))
521         return;
522 
523     if(m_scroll_direction == DOWN)
524     {
525         int row = m_first + m_row_count;
526         (m_first + m_row_count < m_model->count()) ? m_first ++ : m_first;
527         m_model->moveItems(m_pressed_index,row);
528         m_pressed_index = row;
529     }
530     else if(m_scroll_direction == TOP && m_first > 0)
531     {
532         m_first--;
533         m_model->moveItems(m_pressed_index, m_first);
534         m_pressed_index = m_first;
535     }
536 }
537 
updateRepeatIndicator()538 void ListWidget::updateRepeatIndicator()
539 {
540     updateList(PlayListModel::CURRENT | PlayListModel::STRUCTURE);
541 }
542 
scrollTo(int index)543 void ListWidget::scrollTo(int index)
544 {
545     if (m_row_count && !m_filterMode)
546     {
547         recenterTo(index);
548         updateList(PlayListModel::STRUCTURE);
549     }
550 }
551 
setViewPosition(int sc)552 void ListWidget::setViewPosition(int sc)
553 {
554     if (m_model->count() <= m_row_count)
555         return;
556     m_first = sc;
557     updateList(PlayListModel::STRUCTURE);
558 }
559 
setFilterString(const QString & str)560 void ListWidget::setFilterString(const QString &str)
561 {
562     m_filterString = str;
563     if(str.isEmpty())
564     {
565         m_filteredItems.clear();
566         m_filterString.clear();
567         m_filterMode = false;
568     }
569     else
570     {
571         m_filterMode = true;
572     }
573     m_first = 0;
574     updateList(PlayListModel::STRUCTURE);
575 }
576 
clear()577 void ListWidget::clear()
578 {
579     if(m_filterMode)
580     {
581         m_model->removeTracks(m_filteredItems);
582         m_filteredItems.clear();
583     }
584     else
585     {
586         m_model->clear();
587     }
588 }
589 
removeSelected()590 void ListWidget::removeSelected()
591 {
592     if(m_filterMode)
593     {
594         QList<PlayListItem *> items;
595         for(PlayListItem *item : m_filteredItems)
596         {
597             if(item->isSelected())
598                 items << item;
599         }
600         m_model->removeTracks(items);
601     }
602     else
603     {
604         m_model->removeSelected();
605     }
606 }
607 
removeUnselected()608 void ListWidget::removeUnselected()
609 {
610     if(m_filterMode)
611     {
612         QList<PlayListItem *> items;
613         for(PlayListItem *item : m_filteredItems)
614         {
615             if(!item->isSelected())
616                 items << item;
617         }
618         m_model->removeTracks(items);
619     }
620     else
621     {
622         m_model->removeUnselected();
623     }
624 }
625 
updateSkin()626 void ListWidget::updateSkin()
627 {
628     m_drawer.loadSystemColors();
629     update();
630 }
631 
dragEnterEvent(QDragEnterEvent * event)632 void ListWidget::dragEnterEvent(QDragEnterEvent *event)
633 {
634     if(event->mimeData()->hasFormat("text/uri-list") || event->mimeData()->hasFormat("application/json"))
635         event->acceptProposedAction();
636 }
637 
dropEvent(QDropEvent * event)638 void ListWidget::dropEvent(QDropEvent *event)
639 {
640     if(!m_filterMode && (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/json")))
641     {
642         event->acceptProposedAction();
643         QApplication::restoreOverrideCursor();
644 
645         int index = indexAt(event->pos().y());
646         if(index == INVALID_INDEX)
647             index = qMin(m_first + m_row_count, m_model->count());
648 
649         if(event->mimeData()->hasUrls())
650         {
651             QList<QUrl> list_urls = event->mimeData()->urls();
652             m_model->insert(index, list_urls);
653         }
654         else if(event->mimeData()->hasFormat("application/json"))
655         {
656             QByteArray json = event->mimeData()->data("application/json");
657             m_model->insert(index, json);
658         }
659     }
660     m_drop_index = INVALID_INDEX;
661 }
662 
dragLeaveEvent(QDragLeaveEvent *)663 void ListWidget::dragLeaveEvent(QDragLeaveEvent *)
664 {
665     m_drop_index = INVALID_INDEX;
666     update();
667 }
668 
dragMoveEvent(QDragMoveEvent * event)669 void ListWidget::dragMoveEvent(QDragMoveEvent *event)
670 {
671     int index = indexAt(event->pos().y());
672     if(index == INVALID_INDEX)
673         index = qMin(m_first + m_row_count, m_model->count());
674     if(index != m_drop_index)
675     {
676         m_drop_index = index;
677         update();
678     }
679     if (event->mimeData()->hasFormat("text/uri-list"))
680         event->acceptProposedAction();
681 }
682 
getExtraString(PlayListItem * item)683 const QString ListWidget::getExtraString(PlayListItem *item)
684 {
685     if(item->isGroup())
686         return QString();
687 
688     QString extra_string;
689     PlayListTrack *track = static_cast<PlayListTrack *>(item);
690 
691     if (m_show_protocol && track->path().contains("://"))
692         extra_string = "[" + track->path().split("://").at(0) + "]";
693 
694     if (m_model->isQueued(track))
695     {
696         int index = m_model->queuedIndex(track);
697         extra_string += "|"+QString::number(index + 1)+"|";
698     }
699 
700     if(m_model->currentTrack() == track && m_ui_settings->isRepeatableTrack())
701         extra_string += "|R|";
702     else if(m_model->isStopAfter(track))
703         extra_string += "|S|";
704 
705     return extra_string.trimmed(); //remove white space
706 }
707 
updateRowCount()708 bool ListWidget::updateRowCount()
709 {
710     int h = height();
711     if(m_header->isVisibleTo(this))
712         h -= m_header->requiredHeight();
713     if(m_hslider->isVisibleTo(this))
714         h -= m_hslider->height();
715     int row_count = qMax(0, h / m_drawer.rowHeight());
716     if(m_row_count != row_count)
717     {
718         m_row_count = row_count;
719         return true;
720     }
721     return false;
722 }
723 
restoreFirstVisible()724 void ListWidget::restoreFirstVisible()
725 {
726     if(m_first < m_model->count() && m_firstItem == m_model->item(m_first))
727         return;
728 
729     int delta = m_model->count() - m_count;
730 
731     //try to find and restore first visible index
732     if(delta > 0)
733     {
734         int from = qMin(m_model->count() - 1, m_first + 1);
735         for(int i = from; i <= qMin(m_model->count() - 1, m_first + delta); ++i)
736         {
737             if(m_model->item(i) == m_firstItem)
738             {
739                 m_first = i;
740                 break;
741             }
742         }
743     }
744     else
745     {
746         int from = qMin(m_model->count() - 1, m_first - 1);
747         for(int i = from; i >= qMax(0, m_first + delta); --i)
748         {
749             if(m_model->item(i) == m_firstItem)
750             {
751                 m_first = i;
752                 break;
753             }
754         }
755     }
756 }
757 
updateScrollBars()758 void ListWidget::updateScrollBars()
759 {
760     bool rtl = layoutDirection() == Qt::RightToLeft;
761 
762     int vslider_width = m_scrollBar->isVisibleTo(this) ? m_scrollBar->sizeHint().width() : 0;
763     int hslider_height = m_hslider->isVisibleTo(this) ? m_hslider->sizeHint().height() : 0;
764 
765     if(rtl)
766     {
767         m_scrollBar->setGeometry(0, 0, m_scrollBar->sizeHint().width(), height() - hslider_height);
768         m_hslider->setGeometry(vslider_width, height() - m_hslider->sizeHint().height(),
769                                width() - vslider_width, m_hslider->sizeHint().height());
770     }
771     else
772     {
773         m_scrollBar->setGeometry(width() - m_scrollBar->sizeHint().width(), 0,
774                                  m_scrollBar->sizeHint().width(), height() - hslider_height);
775         m_hslider->setGeometry(0, height() - m_hslider->sizeHint().height(), width() - vslider_width,
776                                m_hslider->sizeHint().height());
777     }
778 }
779 
mouseMoveEvent(QMouseEvent * e)780 void ListWidget::mouseMoveEvent(QMouseEvent *e)
781 {
782     if(m_filterMode)
783         return;
784 
785     if(e->buttons() == Qt::LeftButton)
786     {
787         if (m_prev_y > e->y())
788             m_scroll_direction = TOP;
789         else if (m_prev_y < e->y())
790             m_scroll_direction = DOWN;
791         else
792             m_scroll_direction = NONE;
793 
794         if(e->y() < 0 || e->y() > height())
795         {
796             if(!m_timer->isActive())
797                 m_timer->start();
798             return;
799         }
800         m_timer->stop();
801 
802         int index = indexAt(e->y());
803 
804         if (INVALID_INDEX != index)
805         {
806             m_anchor_index = index;
807             SimpleSelection sel = m_model->getSelection(m_pressed_index);
808             if(sel.count() > 1 && m_scroll_direction == TOP)
809             {
810                 if(sel.m_top == 0 || sel.m_top == m_first)
811                     return;
812             }
813             else if(sel.count() > 1 && m_scroll_direction == DOWN)
814             {
815                 if(sel.m_bottom == m_model->count() - 1 || sel.m_bottom == m_first + m_row_count)
816                     return;
817             }
818             m_model->moveItems(m_pressed_index,index);
819 
820             m_prev_y = e->y();
821             m_pressed_index = index;
822         }
823     }
824     else if(m_popupWidget)
825     {
826         int index = indexAt(e->y());
827         if(index < 0 || !m_model->isTrack(index) || m_popupWidget->url() != m_model->track(index)->path())
828             m_popupWidget->deactivate();
829     }
830 }
831 
mouseReleaseEvent(QMouseEvent * e)832 void ListWidget::mouseReleaseEvent(QMouseEvent *e)
833 {
834     if (m_select_on_release)
835     {
836         m_model->clearSelection();
837         m_model->setSelected(m_pressed_index,true);
838         m_anchor_index = m_pressed_index;
839         m_select_on_release = false;
840     }
841     m_pressed_index = INVALID_INDEX;
842     m_scroll_direction = NONE;
843     m_timer->stop();
844     QWidget::mouseReleaseEvent(e);
845 }
846 
indexAt(int y) const847 int ListWidget::indexAt(int y) const
848 {
849     y -= m_header->isVisible() ? m_header->height() : 0;
850 
851     if(m_filterMode)
852     {
853         for (int i = 0; i < qMin(m_row_count, m_filteredItems.count() - m_first); ++i)
854         {
855             if ((y >= i * m_drawer.rowHeight()) && (y <= (i+1) * m_drawer.rowHeight()))
856                 return m_model->indexOf(m_filteredItems.at(m_first + i));
857         }
858     }
859     else
860     {
861         for (int i = 0; i < qMin(m_row_count, m_model->count() - m_first); ++i)
862         {
863             if ((y >= i * m_drawer.rowHeight()) && (y <= (i+1) * m_drawer.rowHeight()))
864                 return m_first + i;
865         }
866     }
867     return INVALID_INDEX;
868 }
869 
contextMenuEvent(QContextMenuEvent * event)870 void ListWidget::contextMenuEvent(QContextMenuEvent * event)
871 {
872     if (menu())
873         menu()->exec(event->globalPos());
874 }
875 
recenterTo(int index)876 void ListWidget::recenterTo(int index)
877 {
878     if (m_row_count && !m_filterMode)
879     {
880         if (m_first + m_row_count < index + 1)
881             m_first = qMin(m_model->count() - m_row_count, index - m_row_count/2);
882         else if (m_first > index)
883             m_first = qMax (index - m_row_count/2, 0);
884     }
885 }
886