diff --git a/src/dialogs/EditFilterDialog.cpp b/src/dialogs/EditFilterDialog.cpp index f667884b8a..428911bb2f 100644 --- a/src/dialogs/EditFilterDialog.cpp +++ b/src/dialogs/EditFilterDialog.cpp @@ -1,501 +1,501 @@ /**************************************************************************************** * Copyright (c) 2006 Giovanni Venturi * * Copyright (c) 2010 Sergey Ivanov <123kash@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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "EditFilterDialog" #include "amarokconfig.h" #include "ui_EditFilterDialog.h" #include "core/support/Debug.h" #include "core-impl/collections/support/CollectionManager.h" #include "core-impl/collections/support/Expression.h" #include "dialogs/EditFilterDialog.h" #include "widgets/TokenDropTarget.h" #include #include #include #include #define OR_TOKEN Meta::valCustom + 1 #define AND_TOKEN Meta::valCustom + 2 #define AND_TOKEN_CONSTRUCT new Token( i18n( "AND" ), "filename-and-amarok", AND_TOKEN ) #define OR_TOKEN_CONSTRUCT new Token( i18n( "OR" ), "filename-divider", OR_TOKEN ) #define SIMPLE_TEXT_CONSTRUCT new Token( i18n( "Simple text" ), "media-track-edit-amarok", 0 ) EditFilterDialog::EditFilterDialog( QWidget* parent, const QString &text ) : QDialog( parent ) , m_ui( new Ui::EditFilterDialog ) , m_curToken( 0 ) , m_separator( " AND " ) , m_isUpdating() { setWindowTitle( i18n( "Edit Filter" ) ); setLayout( new QVBoxLayout ); auto mainWidget = new QWidget( this ); m_ui->setupUi( mainWidget ); layout()->addWidget( mainWidget ); auto buttonBox = new QDialogButtonBox( QDialogButtonBox::Reset | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this ); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); auto resetButton = buttonBox->button( QDialogButtonBox::Reset ); connect( resetButton, &QPushButton::clicked, this, &EditFilterDialog::slotReset ); layout()->addWidget( buttonBox ); m_ui->dropTarget->setRowLimit( 1 ); initTokenPool(); m_ui->searchEdit->setText( text ); updateDropTarget( text ); updateAttributeEditor(); connect( m_ui->mqwAttributeEditor, &MetaQueryWidget::changed, this, &EditFilterDialog::slotAttributeChanged ); connect( m_ui->cbInvert, &QCheckBox::toggled, this, &EditFilterDialog::slotInvert ); connect( m_ui->rbAnd, &QCheckBox::toggled, this, &EditFilterDialog::slotSeparatorChange ); connect( m_ui->rbOr, &QCheckBox::toggled, this, &EditFilterDialog::slotSeparatorChange ); connect( m_ui->tpTokenPool, &TokenPool::onDoubleClick, m_ui->dropTarget, &TokenDropTarget::appendToken ); connect( m_ui->dropTarget, &TokenDropTarget::tokenSelected, this, &EditFilterDialog::slotTokenSelected ); connect( m_ui->dropTarget, &TokenDropTarget::changed, this, &EditFilterDialog::updateSearchEdit ); // in case someone dragged a token around. connect( m_ui->searchEdit, &QLineEdit::textEdited, this, &EditFilterDialog::slotSearchEditChanged ); } EditFilterDialog::~EditFilterDialog() { delete m_ui; } void EditFilterDialog::initTokenPool() { m_ui->tpTokenPool->addToken( SIMPLE_TEXT_CONSTRUCT ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valTitle ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valArtist ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbumArtist ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valAlbum ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valGenre ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valComposer ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valComment ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valUrl ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valYear ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valTrackNr ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valDiscNr ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valBpm ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valLength ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valBitrate ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valSamplerate ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valFilesize ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valFormat ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valCreateDate ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valScore ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valRating ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valFirstPlayed ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valLastPlayed ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valPlaycount ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valLabel ) ); m_ui->tpTokenPool->addToken( tokenForField( Meta::valModified ) ); m_ui->tpTokenPool->addToken( OR_TOKEN_CONSTRUCT ); m_ui->tpTokenPool->addToken( AND_TOKEN_CONSTRUCT ); } Token * EditFilterDialog::tokenForField( const qint64 field ) { QString icon = Meta::iconForField( field ); QString text = Meta::i18nForField( field ); return new Token( text, icon, field ); } EditFilterDialog::Filter & EditFilterDialog::filterForToken( Token *token ) { // a new token! if( !m_filters.contains( token ) ) { Filter newFilter; newFilter.filter.setField( token->value() ); newFilter.inverted = false; m_filters.insert( token, newFilter ); - connect( token, &Token::destroyed, - this, &EditFilterDialog::slotTokenDestroyed ); + connect( token, &Token::removed, + this, &EditFilterDialog::slotTokenRemoved ); } return m_filters[token]; } void EditFilterDialog::slotTokenSelected( Token *token ) { DEBUG_BLOCK; if( m_curToken == token ) return; // nothing to do m_curToken = token; if( m_curToken && m_curToken->value() > Meta::valCustom ) // OR / AND tokens case m_curToken = 0; updateAttributeEditor(); } void -EditFilterDialog::slotTokenDestroyed( QObject *token ) +EditFilterDialog::slotTokenRemoved( Token *token ) { DEBUG_BLOCK - m_filters.take( qobject_cast(token) ); + m_filters.take( token ); if( m_curToken == token ) { m_curToken = 0; updateAttributeEditor(); } updateSearchEdit(); } void EditFilterDialog::slotAttributeChanged( const MetaQueryWidget::Filter &newFilter ) { DEBUG_BLOCK; if( m_curToken ) m_filters[m_curToken].filter = newFilter; updateSearchEdit(); } void EditFilterDialog::slotInvert( bool checked ) { if( m_curToken ) m_filters[m_curToken].inverted = checked; updateSearchEdit(); } void EditFilterDialog::slotSeparatorChange() { if( m_ui->rbAnd->isChecked() ) m_separator = QString( " AND " ); else m_separator = QString( " OR " ); updateSearchEdit(); } void EditFilterDialog::slotSearchEditChanged( const QString &filterText ) { updateDropTarget( filterText ); updateAttributeEditor(); } void EditFilterDialog::slotReset() { m_ui->dropTarget->clear(); m_ui->rbAnd->setChecked( true ); updateAttributeEditor(); updateSearchEdit(); } void EditFilterDialog::accept() { Q_EMIT filterChanged( filter() ); QDialog::accept(); } void EditFilterDialog::updateAttributeEditor() { DEBUG_BLOCK; if( m_isUpdating ) return; m_isUpdating = true; if( m_curToken ) { Filter &filter = filterForToken( m_curToken ); m_ui->mqwAttributeEditor->setFilter( filter.filter ); m_ui->cbInvert->setChecked( filter.inverted ); } m_ui->mqwAttributeEditor->setEnabled( ( bool )m_curToken ); m_ui->cbInvert->setEnabled( ( bool )m_curToken ); m_isUpdating = false; } void EditFilterDialog::updateSearchEdit() { DEBUG_BLOCK; if( m_isUpdating ) return; m_isUpdating = true; m_ui->searchEdit->setText( filter() ); m_isUpdating = false; } void EditFilterDialog::updateDropTarget( const QString &text ) { DEBUG_BLOCK; if( m_isUpdating ) return; m_isUpdating = true; m_ui->dropTarget->clear(); // some code duplication, see Collections::semanticDateTimeParser ParsedExpression parsed = ExpressionParser::parse( text ); bool AND = false; // need an AND token bool OR = false; // need an OR token bool isDateAbsolute = false; foreach( const or_list &orList, parsed ) { foreach( const expression_element &elem, orList ) { if( AND ) m_ui->dropTarget->appendToken( AND_TOKEN_CONSTRUCT ); else if( OR ) m_ui->dropTarget->appendToken( OR_TOKEN_CONSTRUCT ); Filter filter; filter.filter.setField( !elem.field.isEmpty() ? Meta::fieldForName( elem.field ) : 0 ); if( filter.filter.field() == Meta::valRating ) { filter.filter.numValue = 2 * elem.text.toFloat(); } else if( filter.filter.isDate() ) { QString strTime = elem.text; // parse date using local settings auto date = QLocale().toDate( strTime, QLocale::ShortFormat ); // parse date using a backup standard independent from local settings QRegExp shortDateReg("(\\d{1,2})[-.](\\d{1,2})"); QRegExp longDateReg("(\\d{1,2})[-.](\\d{1,2})[-.](\\d{4})"); // NOTE for absolute time specifications numValue is a unix timestamp, // for relative time specifications numValue is a time difference in seconds 'pointing to the past' if( date.isValid() ) { filter.filter.numValue = QDateTime( date ).toSecsSinceEpoch(); isDateAbsolute = true; } else if( strTime.contains(shortDateReg) ) { filter.filter.numValue = QDateTime( QDate( QDate::currentDate().year(), shortDateReg.cap(2).toInt(), shortDateReg.cap(1).toInt() ) ).toSecsSinceEpoch(); isDateAbsolute = true; } else if( strTime.contains(longDateReg) ) { filter.filter.numValue = QDateTime( QDate( longDateReg.cap(3).toInt(), longDateReg.cap(2).toInt(), longDateReg.cap(1).toInt() ) ).toSecsSinceEpoch(); isDateAbsolute = true; } else { // parse a "#m#d" (discoverability == 0, but without a GUI, how to do it?) int years = 0, months = 0, days = 0, secs = 0; QString tmp; for( int i = 0; i < strTime.length(); i++ ) { QChar c = strTime.at( i ); if( c.isNumber() ) { tmp += c; } else if( c == 'y' ) { years += tmp.toInt(); tmp.clear(); } else if( c == 'm' ) { months += tmp.toInt(); tmp.clear(); } else if( c == 'w' ) { days += tmp.toInt() * 7; tmp.clear(); } else if( c == 'd' ) { days += tmp.toInt(); tmp.clear(); } else if( c == 'h' ) { secs += tmp.toInt() * 60 * 60; tmp.clear(); } else if( c == 'M' ) { secs += tmp.toInt() * 60; tmp.clear(); } else if( c == 's' ) { secs += tmp.toInt(); tmp.clear(); } } filter.filter.numValue = years*365*24*60*60 + months*30*24*60*60 + days*24*60*60 + secs; isDateAbsolute = false; } } else if( filter.filter.isNumeric() ) { filter.filter.numValue = elem.text.toInt(); } if( filter.filter.isDate() ) { switch( elem.match ) { case expression_element::Less: if( isDateAbsolute ) filter.filter.condition = MetaQueryWidget::LessThan; else filter.filter.condition = MetaQueryWidget::NewerThan; break; case expression_element::More: if( isDateAbsolute ) filter.filter.condition = MetaQueryWidget::GreaterThan; else filter.filter.condition = MetaQueryWidget::OlderThan; break; default: filter.filter.condition = MetaQueryWidget::Equals; } } else if( filter.filter.isNumeric() ) { switch( elem.match ) { case expression_element::Equals: filter.filter.condition = MetaQueryWidget::Equals; break; case expression_element::Less: filter.filter.condition = MetaQueryWidget::LessThan; break; case expression_element::More: filter.filter.condition = MetaQueryWidget::GreaterThan; break; case expression_element::Contains: break; } } else { switch( elem.match ) { case expression_element::Contains: filter.filter.condition = MetaQueryWidget::Contains; break; case expression_element::Equals: filter.filter.condition = MetaQueryWidget::Equals; break; case expression_element::Less: case expression_element::More: break; } filter.filter.value = elem.text; } filter.inverted = elem.negate; Token *nToken = filter.filter.field() ? tokenForField( filter.filter.field() ) : SIMPLE_TEXT_CONSTRUCT; m_filters.insert( nToken, filter ); - connect( nToken, &Token::destroyed, - this, &EditFilterDialog::slotTokenDestroyed ); + connect( nToken, &Token::removed, + this, &EditFilterDialog::slotTokenRemoved); m_ui->dropTarget->appendToken( nToken ); OR = true; } OR = false; AND = true; } m_isUpdating = false; } QString EditFilterDialog::filter() { QString filterString; QList < Token *> tokens = m_ui->dropTarget->tokensAtRow(); bool join = false; foreach( Token *token, tokens ) { if( token->value() == OR_TOKEN ) { filterString.append( " OR " ); join = false; } else if( token->value() == AND_TOKEN ) { filterString.append( " AND " ); join = false; } else { if( join ) filterString.append( m_separator ); Filter &filter = filterForToken( token ); filterString.append( filter.filter.toString( filter.inverted ) ); join = true; } } return filterString; } diff --git a/src/dialogs/EditFilterDialog.h b/src/dialogs/EditFilterDialog.h index ff96ea8050..a9041c9b92 100644 --- a/src/dialogs/EditFilterDialog.h +++ b/src/dialogs/EditFilterDialog.h @@ -1,86 +1,86 @@ /**************************************************************************************** * Copyright (c) 2006 Giovanni Venturi * * Copyright (c) 2010 Ralf Engels * * Copyright (c) 2010 Sergey Ivanov <123kash@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, see . * ****************************************************************************************/ #ifndef AMAROK_EDITFILTERDIALOG_H #define AMAROK_EDITFILTERDIALOG_H #include "core/meta/forward_declarations.h" #include "widgets/MetaQueryWidget.h" #include "widgets/TokenPool.h" #include #include namespace Ui { class EditFilterDialog; } class EditFilterDialog : public QDialog { Q_OBJECT public: explicit EditFilterDialog( QWidget* parent, const QString &text = QString() ); ~EditFilterDialog() override; QString filter(); Q_SIGNALS: void filterChanged( const QString &filter ); private Q_SLOTS: void slotTokenSelected( Token *token ); - void slotTokenDestroyed( QObject *token ); + void slotTokenRemoved( Token *token ); void slotAttributeChanged( const MetaQueryWidget::Filter &filter ); void slotInvert( bool checked ); void slotSeparatorChange(); void slotSearchEditChanged( const QString &filterText ); void slotReset(); void accept() override; void updateAttributeEditor(); void updateSearchEdit(); /** Parses the given text and set's the dropTarget accordingly. */ void updateDropTarget( const QString &filterText ); private: void initTokenPool(); Token *tokenForField( const qint64 field ); struct Filter { MetaQueryWidget::Filter filter; bool inverted; }; Filter &filterForToken( Token *token ); Ui::EditFilterDialog *m_ui; Token *m_curToken; QMap< Token *, Filter > m_filters; QString m_separator; /** True if we are already updating the status. This blocks recursive calls to updateWidgets or parsteTextFilters. */ bool m_isUpdating; }; #endif /* AMAROK_EDITFILTERDIALOG_H */ diff --git a/src/widgets/Token.cpp b/src/widgets/Token.cpp index c87216dcda..d97a2b7475 100644 --- a/src/widgets/Token.cpp +++ b/src/widgets/Token.cpp @@ -1,279 +1,278 @@ /**************************************************************************************** * Copyright (c) 2008 Téo Mrnjavac * * Copyright (c) 2008-2009 Seb Ruiz * * Copyright (c) 2009 Roman Jarosz * * Copyright (c) 2009 Daniel Dewald * * Copyright (c) 2012 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #include "Token.h" #include "TokenDropTarget.h" #include #include #include #include #include #include #include #include Token* TokenFactory::createToken(const QString & text, const QString & iconName, qint64 value, QWidget * parent) const { return new Token( text, iconName, value, parent ); } Token* TokenFactory::createTokenFromMime( const QMimeData* mimeData, QWidget* parent ) const { // decode the stream created in Token::mimeData QByteArray itemData = mimeData->data( Token::mimeType() ); QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString tokenName; QString tokenIconName; qint64 tokenValue; QColor tokenTextColor; dataStream >> tokenName; dataStream >> tokenIconName; dataStream >> tokenValue; dataStream >> tokenTextColor; Token* token = createToken( tokenName, tokenIconName, tokenValue, parent ); token->setTextColor( tokenTextColor ); return token; } Token::Token( const QString &name, const QString &iconName, qint64 value, QWidget *parent ) : QWidget( parent ) , m_name( name ) , m_icon( QIcon::fromTheme( iconName ) ) , m_iconName( iconName ) , m_value( value ) , m_customColor( false ) { setFocusPolicy( Qt::StrongFocus ); // Note: We set all the margins because we need a quite small size. // Vertically for the EditPlaylistLayoutDialog and horizontally for // the OrganizeTracksDialog m_label = new QLabel( this ); m_label->setAlignment( Qt::AlignCenter ); m_label->setContentsMargins( 0, 0, 0, 0 ); m_label->setMargin( 0 ); m_label->setText( name ); m_iconContainer = new QLabel( this ); m_iconContainer->setContentsMargins( 0, 0, 0, 0 ); m_iconContainer->setMargin( 0 ); QPixmap pixmap = QPixmap( icon().pixmap( 16, 16 ) ); m_iconContainer->setPixmap( pixmap ); QHBoxLayout *hlayout = new QHBoxLayout( this ); hlayout->setSpacing( 3 ); hlayout->setContentsMargins( 3, 0, 3, 0 ); // to allow the label to overwrite the border if space get's tight hlayout->addWidget( m_iconContainer ); hlayout->addWidget( m_label ); setLayout( hlayout ); updateCursor(); } QString Token::name() const { return m_name; } qint64 Token::value() const { return m_value; } QIcon Token::icon() const { return m_icon; } QString Token::iconName() const { return m_iconName; } QColor Token::textColor() const { return m_label->palette().color( QPalette::WindowText ); } void Token::setTextColor( QColor textColor ) { m_customColor = true; if( textColor == this->textColor() ) return; QPalette myPalette( m_label->palette() ); myPalette.setColor( QPalette::WindowText, textColor ); m_label->setPalette( myPalette ); } QMimeData* Token::mimeData() const { QByteArray itemData; QDataStream dataStream( &itemData, QIODevice::WriteOnly ); dataStream << name() << iconName() << value() << textColor(); QMimeData *mimeData = new QMimeData; mimeData->setData( mimeType(), itemData ); return mimeData; } QString Token::mimeType() { return QLatin1String( "application/x-amarok-tag-token" ); } QSize Token::sizeHint() const { QSize result = QWidget::sizeHint(); result += QSize( 6, 6 ); // the border return result; } QSize Token::minimumSizeHint() const { QSize result = QWidget::minimumSizeHint(); return result; } void Token::changeEvent( QEvent *event ) { QWidget::changeEvent( event ); if( !event || event->type() == QEvent::EnabledChange ) updateCursor(); } void Token::focusInEvent( QFocusEvent* event ) { QWidget::focusInEvent( event ); Q_EMIT gotFocus( this ); } void Token::updateCursor() { if( isEnabled() ) setCursor( Qt::OpenHandCursor ); else unsetCursor(); } void Token::mousePressEvent( QMouseEvent* event ) { if( event->button() == Qt::LeftButton ) m_startPos = event->pos(); //store the start position QWidget::mousePressEvent( event ); //feed it to parent's event } void Token::mouseMoveEvent( QMouseEvent* event ) { if( isEnabled() && event->buttons() & Qt::LeftButton ) { int distance = ( event->pos() - m_startPos ).manhattanLength(); if ( distance >= QApplication::startDragDistance() ) performDrag(); } QWidget::mouseMoveEvent( event ); } //Handles the creation of a QDrag object that carries the (text-only) QDataStream from an item in TokenPool void Token::performDrag() { bool stacked = parentWidget() && qobject_cast( parentWidget() ); // true if token originated from a TokenDropTarget. if( stacked ) hide(); - Token *token = this; - QDrag *drag = new QDrag( this ); drag->setMimeData( mimeData() ); // icon for pointer - QPixmap pixmap( token->size() ); - token->render( &pixmap ); + QPixmap pixmap( size() ); + render( &pixmap ); drag->setPixmap( pixmap ); drag->setHotSpot ( pixmap.rect().center() ); Qt::DropAction dropAction = drag->exec( Qt::MoveAction | Qt::CopyAction, Qt::CopyAction ); if( dropAction != Qt::MoveAction && dropAction != Qt::CopyAction ) // dragged out and not just dragged to another position. { // TODO: nice poof animation? ;-) - token->deleteLater(); + Q_EMIT removed( this ); + deleteLater(); } } void Token::paintEvent(QPaintEvent *pe) { Q_UNUSED( pe ) QPainter p( this ); p.setBrush( Qt::NoBrush ); p.setRenderHint( QPainter::Antialiasing ); QColor c; if( isEnabled() && hasFocus() ) { c = palette().color( QPalette::Highlight ); } else if( isEnabled() ) { c = palette().color( foregroundRole() ); c.setAlpha( c.alpha() * 0.5 ); } else { c = palette().color( foregroundRole() ); c.setAlpha( c.alpha() * 0.3 ); } p.setPen( QPen( c, 2 ) ); p.drawRoundedRect( rect().adjusted(1,1,-1,-1), 4, 4 ); p.end(); } diff --git a/src/widgets/Token.h b/src/widgets/Token.h index 768ee35175..b21fc9676a 100644 --- a/src/widgets/Token.h +++ b/src/widgets/Token.h @@ -1,117 +1,118 @@ /**************************************************************************************** * Copyright (c) 2008 Téo Mrnjavac * * Copyright (c) 2008-2009 Seb Ruiz * * Copyright (c) 2009 Daniel Dewald * * * * 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, see . * ****************************************************************************************/ #ifndef AMAROK_TOKEN_H #define AMAROK_TOKEN_H #include #include #include #include class Token; class QMimeData; /** The token factory is used by the TokenDropTarget to create suitable Token objects. The TokenWithLayout class has it's own TokenFactory to be used with the EditFilterDialog. */ class TokenFactory { public: virtual ~TokenFactory() {} virtual Token* createToken( const QString &text, const QString &iconName, qint64 value, QWidget* parent = nullptr ) const; virtual Token* createTokenFromMime( const QMimeData* mimeData, QWidget* parent = nullptr ) const; }; /** A widget that is used in the FilenameLayoutWidget to display part of a filename It is drag&droppable in the TokenDropTarget from the TokenPool widget. Note: A disabled token cannot be dragged. See setEnabled(). */ class Token : public QWidget { Q_OBJECT public: explicit Token( const QString &text, const QString &iconName, qint64 value, QWidget *parent = nullptr ); QIcon icon() const; QString iconName() const; QString name() const; qint64 value() const; QColor textColor() const; void setTextColor( QColor textColor ); /** Return true if somebody has previously set the text color */ bool hasCustomColor() const { return m_customColor; } /** Returns the mime data for this token. Caller has to free the QMimeData object. */ QMimeData* mimeData() const; /** Returns the mime type for an amarok tag token */ static QString mimeType(); QSize sizeHint() const override; QSize minimumSizeHint() const override; Q_SIGNALS: void changed(); + void removed( Token *token ); /** Emitted when the token get's the focus */ void gotFocus( Token* thisToken ); protected: /** overloaded to update the cursor in case the token is set to inactive */ void changeEvent( QEvent* event = nullptr ) override; void focusInEvent( QFocusEvent* event ) override; void updateCursor(); /** Handles start of drag. */ void mousePressEvent( QMouseEvent* event ) override; /** Handles start of drag. */ void mouseMoveEvent( QMouseEvent* event ) override; void paintEvent(QPaintEvent *pe) override; void performDrag(); protected: QString m_name; QIcon m_icon; QString m_iconName; qint64 m_value; // TODO: make this more typesave bool m_customColor; QLabel *m_iconContainer; QLabel *m_label; /** Position of the mouse press event (used for drag and drop) */ QPoint m_startPos; }; #endif // AMAROK_TOKEN_H diff --git a/src/widgets/TokenDropTarget.cpp b/src/widgets/TokenDropTarget.cpp index 68e13963dc..e0aa730e62 100644 --- a/src/widgets/TokenDropTarget.cpp +++ b/src/widgets/TokenDropTarget.cpp @@ -1,393 +1,393 @@ /**************************************************************************************** * Copyright (c) 2009 Thomas Luebking * * Copyright (c) 2012 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #include "TokenDropTarget.h" #include "Token.h" #include "TokenPool.h" #include "core/support/Debug.h" #include #include #include #include #include TokenDropTarget::TokenDropTarget( QWidget *parent ) : QWidget( parent ) , m_rowLimit( 0 ) , m_rows( 0 ) , m_horizontalStretch( false ) // DANGER: m_horizontalStretch is used as int in the following code, assuming that true == 1 , m_verticalStretch( true ) , m_tokenFactory( new TokenFactory() ) { setAcceptDrops( true ); QBoxLayout* mainLayout = new QVBoxLayout( this ); mainLayout->setSpacing( 0 ); mainLayout->addStretch( 1 ); // the vertical stretch mainLayout->setContentsMargins( 0, 0, 0, 0 ); } TokenDropTarget::~TokenDropTarget() { delete m_tokenFactory; } QSize TokenDropTarget::sizeHint() const { QSize result = QWidget::sizeHint(); // we need at least space for the "empty" text. int h = fontMetrics().height(); result = result.expandedTo( QSize( 36 * h, 2 * h ) ); return result; } QSize TokenDropTarget::minimumSizeHint() const { QSize result = QWidget::minimumSizeHint(); // we need at least space for the "empty" text. int h = fontMetrics().height(); result = result.expandedTo( QSize( 36 * h, 2 * h ) ); return result; } QHBoxLayout * TokenDropTarget::appendRow() { QHBoxLayout *box = new QHBoxLayout; box->setSpacing( 0 ); if( m_horizontalStretch ) box->addStretch(); static_cast(layout())->insertLayout( rows(), box ); m_rows++; return box; } void TokenDropTarget::clear() { QList< Token *> allTokens = tokensAtRow(); foreach( Token* token, allTokens ) - delete token; + token->deleteLater();; Q_EMIT changed(); } int TokenDropTarget::count() const { int c = 0; for( int row = rows() - 1; row >= 0; --row ) if( QBoxLayout *box = qobject_cast( layout()->itemAt( row )->layout() ) ) c += box->count() - m_horizontalStretch; return c; } void TokenDropTarget::setRowLimit( uint r ) { // if we have more than one row we have a stretch at the end. QBoxLayout *mainLayout = qobject_cast( layout() ); if( ( r == 1 ) && (m_rowLimit != 1 ) ) mainLayout->takeAt( mainLayout->count() - 1 ); else if( ( r != 1 ) && (m_rowLimit == 1 ) ) mainLayout->addStretch( 1 ); // the vertical stretch m_rowLimit = r; } void TokenDropTarget::deleteEmptyRows() { DEBUG_BLOCK; for( int row = rows() - 1; row >= 0; --row ) { QBoxLayout *box = qobject_cast( layout()->itemAt( row )->layout() ); if( box && box->count() < ( 1 + m_horizontalStretch ) ) // sic! last is spacer { delete layout()->takeAt( row ); m_rows--; } } update(); // this removes empty layouts somehow for deleted tokens. don't remove } QList< Token *> TokenDropTarget::tokensAtRow( int row ) { DEBUG_BLOCK; int lower = 0; int upper = (int)rows(); if( row > -1 && row < (int)rows() ) { lower = row; upper = row + 1; } QList< Token *> list; Token *token; for( row = lower; row < upper; ++row ) if ( QHBoxLayout *rowBox = qobject_cast( layout()->itemAt( row )->layout() ) ) { for( int col = 0; col < rowBox->count() - m_horizontalStretch; ++col ) if ( ( token = qobject_cast( rowBox->itemAt( col )->widget() ) ) ) list << token; } debug() << "Row:"<(token->parent() ) ) { debug() << "Copying token" << token->name(); token = m_tokenFactory->createToken( token->name(), token->iconName(), token->value() ); } token->setParent( this ); // - validate row if ( row < 0 && rowLimit() && rows() >= rowLimit() ) row = rowLimit() - 1; // want to append, but we can't so use the last row instead QBoxLayout *box; if( row < 0 || row >= (int)rows() ) box = appendRow(); else box = qobject_cast( layout()->itemAt( row )->layout() ); // - validate col if( col < 0 || col > box->count() - ( 1 + m_horizontalStretch ) ) col = box->count() - m_horizontalStretch; // - copy the token if it belongs to a token pool (fix BR 296136) if( qobject_cast(token->parent() ) ) { debug() << "Copying token" << token->name(); token = m_tokenFactory->createToken( token->name(), token->iconName(), token->value() ); } box->insertWidget( col, token ); token->show(); connect( token, &Token::changed, this, &TokenDropTarget::changed ); connect( token, &Token::gotFocus, this, &TokenDropTarget::tokenSelected ); - connect( token, &Token::changed, this, &TokenDropTarget::deleteEmptyRows ); + connect( token, &Token::removed, this, &TokenDropTarget::deleteEmptyRows ); Q_EMIT changed(); } Token* TokenDropTarget::tokenAt( const QPoint &pos ) const { for( uint row = 0; row < rows(); ++row ) if( QBoxLayout *rowBox = qobject_cast( layout()->itemAt( row )->layout() ) ) for( int col = 0; col < rowBox->count(); ++col ) if( QWidget *kid = rowBox->itemAt( col )->widget() ) { if( kid->geometry().contains( pos ) ) return qobject_cast(kid); } return 0; } void TokenDropTarget::drop( Token *token, const QPoint &pos ) { DEBUG_BLOCK; if ( !token ) return; // find the token at the position. QWidget *child = childAt( pos ); Token *targetToken = qobject_cast(child); // tokenAt( pos ); if( !targetToken && child && child->parent() ) // sometimes we get the label of the token. targetToken = qobject_cast( child->parent() ); // unlayout in case of move if( QBoxLayout *box = rowBox( token ) ) { box->removeWidget( token ); deleteEmptyRows(); // a row could now be empty due to a move } if( targetToken ) { // we hit a sibling, -> prepend QPoint idx; rowBox( targetToken, &idx ); if( rowLimit() != 1 && rowLimit() < m_rows && idx.y() == (int)m_rows - 1 && pos.y() > targetToken->geometry().y() + ( targetToken->height() * 2 / 3 ) ) insertToken( token, idx.y() + 1, idx.x()); else if( pos.x() > targetToken->geometry().x() + targetToken->width() / 2 ) insertToken( token, idx.y(), idx.x() + 1); else insertToken( token, idx.y(), idx.x() ); } else { appendToken( token ); } token->setFocus( Qt::OtherFocusReason ); // select the new token right away } void TokenDropTarget::dragEnterEvent( QDragEnterEvent *event ) { if( event->mimeData()->hasFormat( Token::mimeType() ) ) event->acceptProposedAction(); } void TokenDropTarget::dropEvent( QDropEvent *event ) { if( event->mimeData()->hasFormat( Token::mimeType() ) ) { event->acceptProposedAction(); Token *token = qobject_cast( event->source() ); if ( !token ) // decode the stream created in TokenPool::dropEvent token = m_tokenFactory->createTokenFromMime( event->mimeData(), this ); // - copy the token if it belongs to a token pool (fix BR 296136) if( qobject_cast(token->parent() ) ) { token = m_tokenFactory->createToken( token->name(), token->iconName(), token->value() ); } if( token ) drop( token, event->pos() ); } } void TokenDropTarget::paintEvent(QPaintEvent *pe) { QWidget::paintEvent(pe); if (count()) return; QPainter p(this); QColor c = palette().color(foregroundRole()); c.setAlpha(c.alpha()*64/255); p.setPen(c); p.drawText(rect(), Qt::AlignCenter | Qt::TextWordWrap, i18n("Drag in and out items from above.")); p.end(); } int TokenDropTarget::row( Token *token ) const { for( uint row = 0; row <= rows(); ++row ) { QBoxLayout *box = qobject_cast( layout()->itemAt( row )->layout() ); if ( box && ( box->indexOf( token ) ) > -1 ) return row; } return -1; } QBoxLayout * TokenDropTarget::rowBox( QWidget *w, QPoint *idx ) const { QBoxLayout *box = 0; int col; for( uint row = 0; row < rows(); ++row ) { box = qobject_cast( layout()->itemAt( row )->layout() ); if ( box && ( col = box->indexOf( w ) ) > -1 ) { if ( idx ) { idx->setX( col ); idx->setY( row ); } return box; } } return NULL; } QBoxLayout * TokenDropTarget::rowBox( const QPoint &pt ) const { QBoxLayout *box = 0; for( uint row = 0; row < rows(); ++row ) { box = qobject_cast( layout()->itemAt( row )->layout() ); if ( !box ) continue; for ( int col = 0; col < box->count(); ++col ) { if ( QWidget *w = box->itemAt( col )->widget() ) { const QRect &geo = w->geometry(); if ( geo.y() <= pt.y() && geo.bottom() >= pt.y() ) return box; break; // yes - all widgets are supposed of equal height. we checked on, we checked all } } } return NULL; } void TokenDropTarget::setCustomTokenFactory( TokenFactory * factory ) { delete m_tokenFactory; m_tokenFactory = factory; } void TokenDropTarget::setVerticalStretch( bool value ) { if( value == m_verticalStretch ) return; m_verticalStretch = value; if( m_verticalStretch ) qobject_cast( layout() )->addStretch( 1 ); else delete layout()->takeAt( layout()->count() - 1 ); }