aboutsummaryrefslogblamecommitdiff
path: root/src/plugins/Ui/skinned/cursorimage.cpp
blob: 074c78eb1ab36557239d0d1eaeef73fe7f289270 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                             
                                                                            
                                                                             













                                  
                         













































































                                                                                                                                                                                                                                                      
                                                                                                                                                             




                                                              
/***************************************************************************
 *   Copyright (C) 2009 by Erik Ölsar                                      *
 *   erlk.ozlr@gmail.com                                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <QCursor>
#include <QString>
#include <QPixmap>
#include <QImage>
#include <QByteArray>
#include <QFile>
#include <QBitmap>
#include <QtDebug>
#include "cursorimage.h"

QCursor createCursor(QString path)
{
	if (path.isEmpty())
        return QCursor();
	
	// read file headers
	QFile curFile(path);
	curFile.open(QIODevice::ReadOnly);
	QDataStream curStream(&curFile);
	curStream.setByteOrder(QDataStream::LittleEndian);
	
	struct {
		quint16 zero;
		quint16 type;
		quint16 icons;
	} header2;
	curStream >> header2.zero >> header2.type >> header2.icons;

	struct {
		quint8 width;
		quint8 height;
		quint8 ncolours;
		quint8 zero;
		quint16 xhot;
		quint16 yhot;
		quint32 bytes;
		quint32 dibOffset;
	} directory2;
	curStream >> directory2.width >> directory2.height >> directory2.ncolours >> directory2.zero >> directory2.xhot >> directory2.yhot >> directory2.bytes >> directory2.dibOffset;
	
	curFile.seek(directory2.dibOffset);
	
	// prepare a .bmp for delegating decoding to qt
	struct {
		unsigned char magic[2];
		quint32 size;
		quint32 zero;
		quint32 rdataOffset;
	} bmpHeader;
	int bmpHeaderSize = (2+4+4+4);
	struct {
		quint32 hdrSize;
		quint32 width;
		quint32 height;
		quint16 planes;
		quint16 bpp;
		quint32 compression;
		quint32 dataSize;
		quint32 unused1;
		quint32 unused2;
		quint32 unused3;
		quint32 unused4;
	} dibHeader;
	int dibHeaderSize = (4+4+4+2+2+4+4+4+4+4+4);
	
	bmpHeader.magic[0] = 'B'; bmpHeader.magic[1] = 'M';
	bmpHeader.zero = 0;
	bmpHeader.size = bmpHeaderSize + directory2.bytes;
	bmpHeader.rdataOffset = bmpHeaderSize + dibHeaderSize + directory2.ncolours * 4;
	
	curStream >> dibHeader.hdrSize >> dibHeader.width >> dibHeader.height >> dibHeader.planes >> dibHeader.bpp >> dibHeader.compression >> dibHeader.dataSize >> dibHeader.unused1 >> dibHeader.unused2 >> dibHeader.unused3 >> dibHeader.unused4;
	dibHeader.height >>= 1;
	
	// the bmp bytes are in 'bmpData'
	QByteArray bmpData;
	QDataStream bmpStream(&bmpData, QIODevice::WriteOnly);
	bmpStream.setByteOrder(QDataStream::LittleEndian);
	bmpStream.writeRawData((char*) bmpHeader.magic, 2);
	bmpStream << bmpHeader.size << bmpHeader.zero << bmpHeader.rdataOffset;
	bmpStream << dibHeader.hdrSize << dibHeader.width << dibHeader.height << dibHeader.planes << dibHeader.bpp << dibHeader.compression << dibHeader.dataSize << dibHeader.unused1 << dibHeader.unused2 << dibHeader.unused3 << dibHeader.unused4;
	bmpData.append(curFile.read(directory2.bytes - dibHeaderSize));
	
	// decode the image into 'pix'
	int width = directory2.width;
	int height = directory2.height;
	QImage image;
	image.loadFromData(bmpData);
	//qDebug() << image.rect() << path;
	QPixmap pix = QPixmap::fromImage(image);
	
	// now we need the mask (transparency)
	QByteArray maskData = bmpData.right((width * height) / 8);
	QImage maskImage = QBitmap::fromData(QSize(width, height), (const uchar*) maskData.constData(), QImage::Format_Mono).toImage().mirrored(false, true);
	maskImage.invertPixels();
	pix.setMask(QBitmap::fromImage(maskImage));
	
	return QCursor(pix, directory2.xhot, directory2.yhot);
}
****************** * Copyright (C) 2006-2019 by Ilya Kotov * * forkotov02@ya.ru * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include <QPixmap> #include <QResizeEvent> #include <QPainter> #include <QFont> #include <QFontMetrics> #include <QSettings> #include <QMenu> #include <QUrl> #include <QApplication> #include <QHelpEvent> #include <QTimer> #include <QMimeData> #include <qmmpui/playlistitem.h> #include <qmmpui/playlistmodel.h> #include <qmmpui/qmmpuisettings.h> #include <qmmpui/playlistmanager.h> #include "listwidget.h" #include "playlistheader.h" #include "actionmanager.h" #include "skin.h" #include "popupwidget.h" #include "horizontalslider.h" #include "playlist.h" #define INVALID_INDEX -1 ListWidget::ListWidget(QWidget *parent) : QWidget(parent) { m_popupWidget = nullptr; m_skin = Skin::instance(); m_ui_settings = QmmpUiSettings::instance(); m_menu = new QMenu(this); m_timer = new QTimer(this); m_timer->setInterval(50); m_header = new PlayListHeader(this); m_hslider = new HorizontalSlider(this); m_update = false; m_drop_index = INVALID_INDEX; m_scroll_direction = NONE; m_prev_y = 0; m_anchor_index = INVALID_INDEX; m_pressed_index = INVALID_INDEX; m_first = 0; m_row_count = 0; m_count = 0; m_firstItem = nullptr; m_select_on_release = false; setAcceptDrops(true); setMouseTracking(true); readSettings(); connect(m_skin, SIGNAL(skinChanged()), SLOT(updateSkin())); connect(m_ui_settings, SIGNAL(repeatableTrackChanged(bool)), SLOT(updateRepeatIndicator())); connect(m_timer, SIGNAL(timeout()), SLOT(autoscroll())); connect(m_hslider, SIGNAL(sliderMoved(int)), m_header, SLOT(scroll(int))); connect(m_hslider, SIGNAL(sliderMoved(int)), this, SLOT(update())); SET_ACTION(ActionManager::PL_SHOW_HEADER, this, SLOT(readSettings())); } ListWidget::~ListWidget() { qDeleteAll(m_rows); m_rows.clear(); } void ListWidget::readSettings() { QSettings settings(Qmmp::configFile(), QSettings::IniFormat); settings.beginGroup("Skinned"); m_show_protocol = settings.value ("pl_show_protocol", false).toBool(); bool show_popup = settings.value("pl_show_popup", false).toBool(); m_header->readSettings(); m_header->setVisible(ACTION(ActionManager::PL_SHOW_HEADER)->isChecked()); m_header->setGeometry(0,0,width(), m_header->requiredHeight()); if (m_update) { m_drawer.readSettings(); updateList(PlayListModel::STRUCTURE); if(m_popupWidget) { m_popupWidget->deleteLater(); m_popupWidget = nullptr; } } else { m_update = true; } if(show_popup) m_popupWidget = new PlayListPopup::PopupWidget(this); } int ListWidget::visibleRows() const { return m_row_count; } int ListWidget::firstVisibleIndex() const { return m_first; } int ListWidget::anchorIndex() const { return m_anchor_index; } void ListWidget::setAnchorIndex(int index) { m_anchor_index = index; updateList(PlayListModel::SELECTION); } QMenu *ListWidget::menu() { return m_menu; } PlayListModel *ListWidget::model() { Q_ASSERT(m_model); return m_model; } void ListWidget::paintEvent(QPaintEvent *) { QPainter painter(this); m_drawer.fillBackground(&painter, width(), height()); painter.setLayoutDirection(Qt::LayoutDirectionAuto); bool rtl = (layoutDirection() == Qt::RightToLeft); painter.setClipRect(5,0,width() - 9, height()); painter.translate(rtl ? m_header->offset() : -m_header->offset(), 0); for (int i = 0; i < m_rows.size(); ++i ) { m_drawer.drawBackground(&painter, m_rows[i]); if(m_rows[i]->flags & ListWidgetRow::GROUP) { m_drawer.drawSeparator(&painter, m_rows[i], rtl); continue; } m_drawer.drawTrack(&painter, m_rows[i], rtl); } //draw drop line if(m_drop_index != INVALID_INDEX) { m_drawer.drawDropLine(&painter, m_drop_index - m_first, width(), m_header->isVisible() ? m_header->height() : 0); } } void ListWidget::mouseDoubleClickEvent (QMouseEvent *e) { int y = e->y(); int index = indexAt(y); if (INVALID_INDEX != index) { m_model->setCurrent(index); emit doubleClicked(); update(); } } void ListWidget::mousePressEvent(QMouseEvent *e) { if(m_popupWidget) m_popupWidget->hide(); int index = indexAt(e->y()); if (INVALID_INDEX != index && m_model->count() > index) { m_pressed_index = index; if(e->button() == Qt::RightButton) { if(!m_model->isSelected(index)) { m_anchor_index = m_pressed_index; m_model->clearSelection(); m_model->setSelected(index, true); } if(m_model->isGroup(index) && m_model->selectedTracks().isEmpty()) { m_anchor_index = m_pressed_index; PlayListGroup *group = m_model->group(index); m_model->setSelected(group->tracks()); } QWidget::mousePressEvent(e); return; } if (m_model->isSelected(index) && (e->modifiers() == Qt::NoModifier)) { m_select_on_release = true; QWidget::mousePressEvent(e); return; } if ((Qt::ShiftModifier & e->modifiers())) { int prev_anchor_index = m_anchor_index; m_anchor_index = m_pressed_index; m_model->setSelected(m_pressed_index, prev_anchor_index, true); } else //ShiftModifier released { m_anchor_index = m_pressed_index; if ((Qt::ControlModifier & e->modifiers())) { m_model->setSelected(index, !m_model->isSelected(index)); } else //ControlModifier released { m_model->clearSelection(); m_model->setSelected(index, true); } } update(); } QWidget::mousePressEvent(e); } void ListWidget::resizeEvent(QResizeEvent *e) { m_header->setGeometry(0,0,width(), m_header->requiredHeight()); m_hslider->setGeometry(5,height() - 7, width() - 10, 7); updateList(PlayListModel::STRUCTURE); QWidget::resizeEvent(e); } void ListWidget::wheelEvent (QWheelEvent *e) { if (m_model->count() <= m_row_count) return; if ((m_first == 0 && e->delta() > 0) || ((m_first == m_model->count() - m_row_count) && e->delta() < 0)) return; m_first -= e->delta()/40; //40*3 TODO: add step to config if (m_first < 0) m_first = 0; if (m_first > m_model->count() - m_row_count) m_first = m_model->count() - m_row_count; updateList(PlayListModel::STRUCTURE); } bool ListWidget::event (QEvent *e) { if(m_popupWidget) { if(e->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = (QHelpEvent *) e; int index = indexAt(helpEvent->y()); if(index < 0 || !m_model->isTrack(index)) { m_popupWidget->deactivate(); return QWidget::event(e); } e->accept(); m_popupWidget->prepare(m_model->track(index), helpEvent->globalPos()); return true; } else if(e->type() == QEvent::Leave) m_popupWidget->deactivate(); } return QWidget::event(e); } void ListWidget::updateList(int flags) { m_hslider->setVisible(m_header->maxScrollValue() > 0); m_hslider->setPos(m_header->offset(), m_header->maxScrollValue()); if(updateRowCount()) flags |= PlayListModel::STRUCTURE; if(flags & PlayListModel::CURRENT) recenterTo(m_model->currentIndex()); QList<PlayListItem *> items; if(flags & PlayListModel::STRUCTURE || flags & PlayListModel::CURRENT) { if(m_row_count >= m_model->count()) { m_first = 0; emit positionChanged(0,0); } else if(m_first + m_row_count >= m_model->count()) { //try to restore first visible first if((m_count > 0) && (m_count != m_model->count()) && m_firstItem) { restoreFirstVisible(); } if(m_first + m_row_count >= m_model->count()) m_first = qMax(0, m_model->count() - m_row_count); emit positionChanged(m_first, m_first); } else if((m_count > 0) && (m_count != m_model->count()) && m_firstItem && m_model->item(m_first) != m_firstItem) { restoreFirstVisible(); emit positionChanged(m_first, m_model->count() - m_row_count); } else { emit positionChanged(m_first, m_model->count() - m_row_count); } m_firstItem = m_model->isEmpty() ? nullptr : m_model->item(m_first); m_count = m_model->count(); items = m_model->mid(m_first, m_row_count); while(m_rows.count() < qMin(m_row_count, items.count())) m_rows << new ListWidgetRow; while(m_rows.count() > qMin(m_row_count, items.count())) delete m_rows.takeFirst(); } else { items = m_model->mid(m_first, m_row_count); } if(flags & PlayListModel::STRUCTURE) m_header->hideSortIndicator(); if(flags & PlayListModel::STRUCTURE || flags & PlayListModel::METADATA) { //song numbers width m_drawer.calculateNumberWidth(m_model->trackCount()); m_drawer.setSingleColumnMode(m_model->columnCount() == 1); m_header->setNumberWidth(m_drawer.numberWidth()); } int trackStateColumn = m_header->trackStateColumn(); int rowWidth = width() + m_header->maxScrollValue() - 10; bool rtl = (layoutDirection() == Qt::RightToLeft); for(int i = 0; i < items.count(); ++i) { ListWidgetRow *row = m_rows[i]; row->autoResize = m_header->hasAutoResizeColumn(); row->trackStateColumn = trackStateColumn; items[i]->isSelected() ? row->flags |= ListWidgetRow::SELECTED : row->flags &= ~ListWidgetRow::SELECTED; i == (m_anchor_index - m_first) ? row->flags |= ListWidgetRow::ANCHOR : row->flags &= ~ListWidgetRow::ANCHOR; if(flags == PlayListModel::SELECTION) continue; if(rtl) { row->rect = QRect(width() - 5 - rowWidth, (m_header->isVisibleTo(this) ? m_header->height() : 0) + i * m_drawer.rowHeight(), rowWidth, m_drawer.rowHeight() - 1); } else { row->rect = QRect(5, (m_header->isVisibleTo(this) ? m_header->height() : 0) + i * m_drawer.rowHeight(), rowWidth, m_drawer.rowHeight() - 1); } row->titles = items[i]->formattedTitles(); row->sizes = m_header->sizes(); row->alignment = m_header->alignment(); (m_first + i) == m_model->currentIndex() ? row->flags |= ListWidgetRow::CURRENT : row->flags &= ~ListWidgetRow::CURRENT; if(items[i]->isGroup()) { row->flags |= ListWidgetRow::GROUP; row->number = -1; row->length.clear(); } else { row->flags &= ~ListWidgetRow::GROUP; row->number = items.at(i)->trackIndex() + 1; row->length = items[i]->formattedLength(); row->extraString = getExtraString(m_first + i); } m_drawer.prepareRow(row); //elide titles } update(); } void ListWidget::autoscroll() { SimpleSelection sel = m_model->getSelection(m_pressed_index); if ((sel.m_top == 0 && m_scroll_direction == TOP && sel.count() > 1) || (sel.m_bottom == m_model->count() - 1 && m_scroll_direction == DOWN && sel.count() > 1)) return; if(m_scroll_direction == DOWN) { int row = m_first + m_row_count; (m_first + m_row_count < m_model->count()) ? m_first ++ : m_first; m_model->moveItems(m_pressed_index,row); m_pressed_index = row; } else if(m_scroll_direction == TOP && m_first > 0) { m_first--; m_model->moveItems(m_pressed_index, m_first); m_pressed_index = m_first; } } void ListWidget::updateRepeatIndicator() { updateList(PlayListModel::CURRENT | PlayListModel::STRUCTURE); } void ListWidget::scrollTo(int index) { if (m_row_count) { recenterTo(index); updateList(PlayListModel::STRUCTURE); } } void ListWidget::setModel(PlayListModel *selected, PlayListModel *previous) { if(previous) { previous->setProperty("first_visible", m_first); disconnect(previous, nullptr, this, nullptr); //disconnect previous model disconnect(previous,nullptr,m_header,nullptr); } qApp->processEvents(); m_model = selected; m_count = m_model->count(); m_firstItem = nullptr; if(m_model->property("first_visible").isValid()) { m_first = m_model->property("first_visible").toInt(); updateList(PlayListModel::STRUCTURE); } else { m_first = 0; updateList(PlayListModel::STRUCTURE | PlayListModel::CURRENT); } connect (m_model, SIGNAL(scrollToRequest(int)), SLOT(scrollTo(int))); connect (m_model, SIGNAL(listChanged(int)), SLOT(updateList(int))); connect (m_model, SIGNAL(sortingByColumnFinished(int,bool)), m_header, SLOT(showSortIndicator(int,bool))); } void ListWidget::setViewPosition(int sc) { if (m_model->count() <= m_row_count) return; if(m_first != sc) { m_first = sc; updateList(PlayListModel::STRUCTURE); } } void ListWidget::updateSkin() { m_drawer.loadColors(); update(); } void ListWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("text/uri-list")) event->acceptProposedAction(); } void ListWidget::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { QList<QUrl> list_urls = event->mimeData()->urls(); event->acceptProposedAction(); QApplication::restoreOverrideCursor(); int index = indexAt(event->pos().y()); if(index == INVALID_INDEX) { index = qMin(m_first + m_row_count, m_model->count()); } m_model->insert(index, list_urls); } m_drop_index = INVALID_INDEX; } void ListWidget::dragLeaveEvent(QDragLeaveEvent *) { m_drop_index = INVALID_INDEX; update(); } void ListWidget::dragMoveEvent(QDragMoveEvent *event) { int index = indexAt(event->pos().y()); if(index == INVALID_INDEX) index = qMin(m_first + m_row_count, m_model->count()); if(index != m_drop_index) { m_drop_index = index; update(); } if (event->mimeData()->hasFormat("text/uri-list")) event->acceptProposedAction(); } const QString ListWidget::getExtraString(int i) { QString extra_string; PlayListTrack *track = m_model->track(i); if(!track) return extra_string; if (m_show_protocol && track->path().contains("://")) extra_string = "[" + track->path().split("://").at(0) + "]"; if (m_model->isQueued(track)) { int index = m_model->queuedIndex(track); extra_string += "|"+QString::number(index + 1)+"|"; } if(m_model->currentIndex() == i && m_ui_settings->isRepeatableTrack()) extra_string += "|R|"; else if(m_model->isStopAfter(track)) extra_string += "|S|"; return extra_string.trimmed(); //remove white space } bool ListWidget::updateRowCount() { int h = height(); if(m_header->isVisibleTo(this)) h -= m_header->requiredHeight(); if(m_hslider->isVisibleTo(this)) h -= m_hslider->height(); int row_count = qMax(0, h / m_drawer.rowHeight()); if(m_row_count != row_count) { m_row_count = row_count; return true; } return false; } void ListWidget::restoreFirstVisible() { if(m_first < m_model->count() && m_firstItem == m_model->item(m_first)) return; int delta = m_model->count() - m_count; //try to find and restore first visible index if(delta > 0) { int from = qMin(m_model->count() - 1, m_first + 1); for(int i = from; i <= qMin(m_model->count() - 1, m_first + delta); ++i) { if(m_model->item(i) == m_firstItem) { m_first = i; break; } } } else { int from = qMin(m_model->count() - 1, m_first - 1); for(int i = from; i >= qMax(0, m_first + delta); --i) { if(m_model->item(i) == m_firstItem) { m_first = i; break; } } } } void ListWidget::mouseMoveEvent(QMouseEvent *e) { if(e->buttons() == Qt::LeftButton) { if (m_prev_y > e->y()) m_scroll_direction = TOP; else if (m_prev_y < e->y()) m_scroll_direction = DOWN; else m_scroll_direction = NONE; if(e->y() < 0 || e->y() > height()) { if(!m_timer->isActive()) m_timer->start(); return; } m_timer->stop(); int index = indexAt(e->y()); if (INVALID_INDEX != index) { m_anchor_index = index; SimpleSelection sel = m_model->getSelection(m_pressed_index); if(sel.count() > 1 && m_scroll_direction == TOP) { if(sel.m_top == 0 || sel.m_top == m_first) return; } else if(sel.count() > 1 && m_scroll_direction == DOWN) { if(sel.m_bottom == m_model->count() - 1 || sel.m_bottom == m_first + m_row_count) return; } m_model->moveItems(m_pressed_index,index); m_prev_y = e->y(); m_pressed_index = index; } } else if(m_popupWidget) { int index = indexAt(e->y()); if(index < 0 || !m_model->isTrack(index) || m_popupWidget->url() != m_model->track(index)->path()) m_popupWidget->deactivate(); } } void ListWidget::mouseReleaseEvent(QMouseEvent *e) { if (m_select_on_release) { m_model->clearSelection(); m_model->setSelected(m_pressed_index,true); m_anchor_index = m_pressed_index; m_select_on_release = false; } m_pressed_index = INVALID_INDEX; m_scroll_direction = NONE; m_timer->stop(); QWidget::mouseReleaseEvent(e); } int ListWidget::indexAt(int y) const { y -= m_header->isVisible() ? m_header->height() : 0; for (int i = 0; i < qMin(m_row_count, m_model->count() - m_first); ++i) { if ((y >= i * m_drawer.rowHeight()) && (y <= (i+1) * m_drawer.rowHeight())) return m_first + i; } return INVALID_INDEX; } void ListWidget::contextMenuEvent(QContextMenuEvent * event) { if (menu()) menu()->exec(event->globalPos()); } void ListWidget::recenterTo(int index) { if (m_row_count) { if (m_first + m_row_count < index + 1) m_first = qMin(m_model->count() - m_row_count, index - m_row_count/2); else if (m_first > index) m_first = qMax (index - m_row_count/2, 0); } }