QDomElement LayoutManager::createItemElement( QDomDocument doc, const QString &name, const LayoutItemConfig & item ) const { QDomElement element = doc.createElement( name ); QString showIcon = item.showIcon() ? "true" : "false"; element.setAttribute ( "show_metacontact_icon", showIcon ); for( int i = 0; i < item.rows(); i++ ) { LayoutItemConfigRow row = item.row( i ); QDomElement rowElement = doc.createElement( "row" ); element.appendChild( rowElement ); for( int j = 0; j < row.count(); j++ ) { LayoutItemConfigRowElement element = row.element( j ); QDomElement elementElement = doc.createElement( "element" ); elementElement.setAttribute ( "value", m_tokens.at(element.value()).mConfigName ); elementElement.setAttribute ( "size", QString::number( element.size() ) ); elementElement.setAttribute ( "bold", element.bold() ? "true" : "false" ); elementElement.setAttribute ( "italic", element.italic() ? "true" : "false" ); elementElement.setAttribute ( "small", element.small() ? "true" : "false" ); elementElement.setAttribute ( "optimalSize", element.optimalSize() ? "true" : "false" ); QString alignmentString; if ( element.alignment() & Qt::AlignLeft ) alignmentString = "left"; else if ( element.alignment() & Qt::AlignRight ) alignmentString = "right"; else alignmentString = "center"; elementElement.setAttribute ( "alignment", alignmentString ); rowElement.appendChild( elementElement ); } } return element; }
void InlineEditorWidget::createChildWidgets() { QBoxLayout* boxLayout = qobject_cast<QBoxLayout*>( layout() ); Q_ASSERT( boxLayout ); boxLayout->setSpacing( 0 ); //For now, we don't allow editing of the "head" data, just the body LayoutItemConfig config = m_layout.layoutForItem( m_index ); const int rowCount = config.rows(); if( rowCount == 0 ) return; // we have to use the same metrics as the PrettyItemDelegate or else // the widgets will change places when editing const int horizontalSpace = style()->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ); const int frameHMargin = style()->pixelMetric( QStyle::PM_FocusFrameHMargin ); const int frameVMargin = style()->pixelMetric( QStyle::PM_FocusFrameVMargin ); int rowOffsetX = frameHMargin; // keep the text a little bit away from the border const int coverHeight = m_widgetHeight - frameVMargin * 2; const bool showCover = config.showCover(); if( showCover ) rowOffsetX += coverHeight + horizontalSpace/* + frameHMargin * 2*/; const int contentHeight = m_widgetHeight - frameVMargin * 2; int rowHeight = contentHeight / rowCount; const int rowWidth = m_widgetWidth - rowOffsetX - frameHMargin * 2; if( showCover ) { QModelIndex coverIndex = m_index.model()->index( m_index.row(), CoverImage ); QPixmap albumPixmap = coverIndex.data( Qt::DisplayRole ).value<QPixmap>(); if( !albumPixmap.isNull() ) { if( albumPixmap.width() > albumPixmap.height() ) albumPixmap = albumPixmap.scaledToWidth( coverHeight ); else albumPixmap = albumPixmap.scaledToHeight( coverHeight ); QLabel *coverLabel = new QLabel( this ); coverLabel->setPixmap( albumPixmap ); if( albumPixmap.width() < coverHeight ) coverLabel->setContentsMargins( ( coverHeight - albumPixmap.width() ) / 2, 0, ( coverHeight - albumPixmap.width() + 1 ) / 2, 0 ); boxLayout->setStretchFactor( coverLabel, 0 ); boxLayout->addSpacing( horizontalSpace ); } } KVBox *rowsWidget = new KVBox( this ); // --- paint all the rows for( int i = 0; i < rowCount; i++ ) { LayoutItemConfigRow row = config.row( i ); const int elementCount = row.count(); QSplitter *rowWidget = new QSplitter( rowsWidget ); connect( rowWidget, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved(int,int)) ); m_splitterRowMap.insert( rowWidget, i ); //we need to do a quick pass to figure out how much space is left for auto sizing elements qreal spareSpace = 1.0; int autoSizeElemCount = 0; for( int k = 0; k < elementCount; ++k ) { spareSpace -= row.element( k ).size(); if( row.element( k ).size() < 0.001 ) autoSizeElemCount++; } const qreal spacePerAutoSizeElem = spareSpace / (qreal)autoSizeElemCount; //give left over pixels to the first rows. Widgets are doing it the same. if( i == 0 ) rowHeight++; if( i == ( contentHeight % rowCount ) ) rowHeight--; QList<int> itemWidths; int currentItemX = 0; for( int j = 0; j < elementCount; ++j ) { LayoutItemConfigRowElement element = row.element( j ); // -- calculate the size qreal size; if( element.size() < 0.001 ) size = spacePerAutoSizeElem; else size = element.size(); int itemWidth; if( j == elementCount - 1 ) // use the full with for the last item itemWidth = rowWidth - currentItemX; else itemWidth = rowWidth * size; itemWidths.append( itemWidth ); int value = element.value(); QModelIndex textIndex = m_index.model()->index( m_index.row(), value ); QString text = textIndex.data( Qt::DisplayRole ).toString(); m_orgValues.insert( value, text ); QWidget *widget = 0; //special case for painting the rating... if( value == Rating ) { int rating = textIndex.data( Qt::DisplayRole ).toInt(); KRatingWidget* ratingWidget = new KRatingWidget( 0 ); ratingWidget->setAlignment( element.alignment() ); ratingWidget->setRating( rating ); ratingWidget->setAttribute( Qt::WA_NoMousePropagation, true ); connect( ratingWidget, SIGNAL(ratingChanged(uint)), this, SLOT(ratingValueChanged()) ); m_editorRoleMap.insert( ratingWidget, value ); widget = ratingWidget; } else if( value == Divider ) { QPixmap left = The::svgHandler()->renderSvg( "divider_left", 1, rowHeight, "divider_left" ); QPixmap right = The::svgHandler()->renderSvg( "divider_right", 1, rowHeight, "divider_right" ); QPixmap dividerPixmap( 2, rowHeight ); dividerPixmap.fill( Qt::transparent ); QPainter painter( ÷rPixmap ); painter.drawPixmap( 0, 0, left ); painter.drawPixmap( 1, 0, right ); QLabel* dividerLabel = new QLabel( 0 ); dividerLabel->setPixmap( dividerPixmap ); dividerLabel->setAlignment( element.alignment() ); widget = dividerLabel; } else if( value == Moodbar ) { //we cannot ask the model for the moodbar directly as we have no //way of asking for a specific size. Instead just get the track from //the model and ask the moodbar manager ourselves. Meta::TrackPtr track = m_index.data( TrackRole ).value<Meta::TrackPtr>(); QLabel* moodbarLabel = new QLabel( 0 ); moodbarLabel->setScaledContents( true ); if( The::moodbarManager()->hasMoodbar( track ) ) { QPixmap moodbar = The::moodbarManager()->getMoodbar( track, itemWidth, rowHeight - 8 ); moodbarLabel->setPixmap( moodbar ); } widget = moodbarLabel; } //actual playlist item text is drawn here else { QLineEdit * edit = new QLineEdit( text, 0 ); edit->setFrame( false ); edit->setAlignment( element.alignment() ); edit->installEventFilter(this); // -- set font bool bold = element.bold(); bool italic = element.italic(); bool underline = element.underline(); QFont font = edit->font(); font.setBold( bold ); font.setItalic( italic ); font.setUnderline( underline ); edit->setFont( font ); connect( edit, SIGNAL(editingFinished()), this, SLOT(editValueChanged()) ); //check if this is a column that is editable. If not, make the //line edit read only. if( !isEditableColumn( static_cast<Playlist::Column>(value) ) ) { edit->setReadOnly( true ); edit->setDisabled( true ); } m_editorRoleMap.insert( edit, value ); widget = edit; } widget->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ); // or else the widget size hint influences the space it get's which messes up the layout rowWidget->addWidget( widget ); // handles are nice, but if we don't compensate for their sizes the layout // would be different from the item delegate if( j > 0 ) widget->setContentsMargins( -( ( rowWidget->handleWidth() + 1 ) / 2 ), 0, 0, 0 ); if( j < elementCount - 1 ) widget->setContentsMargins( 0, 0, -( rowWidget->handleWidth() / 2 ), 0 ); currentItemX += itemWidth; } rowWidget->setSizes( itemWidths ); } }
void Playlist::PrettyItemDelegate::paintItem( const LayoutItemConfig &config, QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, bool headerRow ) const { const int rowCount = config.rows(); if( rowCount == 0 ) return; QStyle *style; if( QWidget *w = qobject_cast<QWidget*>(parent()) ) style = w->style(); else style = QApplication::style(); // we have to use the same metrics as the InlineEditorWidget or else // the widgets will change places when editing const int horizontalSpace = style->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ); const int smallIconSize = style->pixelMetric( QStyle::PM_SmallIconSize ); const int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin ); const int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin ); const int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing ); int rowOffsetX = frameHMargin; // keep the text a little bit away from the border int rowOffsetY = frameVMargin; const int coverHeight = option.rect.height() - frameVMargin * 2; QRectF nominalImageRect( frameHMargin, frameVMargin, coverHeight, coverHeight ); const bool showCover = config.showCover(); if( showCover ) rowOffsetX += coverHeight + horizontalSpace/* + frameHMargin * 2*/; const int contentHeight = option.rect.height() - frameVMargin * 2; int rowHeight = contentHeight / rowCount; const int rowWidth = option.rect.width() - rowOffsetX - frameHMargin * 2; // --- paint the active track background // We do not want to paint this for head items. if( !headerRow && index.data( ActiveTrackRole ).toBool() ) { //paint this in 3 parts to solve stretching issues with wide playlists //TODO: propper 9 part painting, but I don't want to bother with this until we //get some new graphics anyway... // -- try not to highlight the indicator row int overlayXOffset = 0; int overlayYOffset = config.activeIndicatorRow() * rowHeight; int overlayHeight = option.rect.height() - overlayYOffset; int overlayLength = option.rect.width(); int endWidth = overlayHeight / 4; if( m_animationTimeLine->currentFrame() == m_animationTimeLine->startFrame() && EngineController::instance()->isPlaying() ) { m_animationTimeLine->setDirection( QTimeLine::Forward ); if( m_animationTimeLine->state() == QTimeLine::NotRunning ) m_animationTimeLine->start(); } else if( m_animationTimeLine->currentFrame() == m_animationTimeLine->endFrame() ) { m_animationTimeLine->setDirection( QTimeLine::Backward ); m_animationTimeLine->start(); } // Opacity is used for animating the active track item const qreal opacity = qreal( m_animationTimeLine->currentFrame() ) / 1000; // If opacity is not the default value we cannot render from cache const bool skipCache = opacity == 1.0 ? false : true; painter->drawPixmap( overlayXOffset, overlayYOffset, The::svgHandler()->renderSvg( "active_overlay_left", endWidth, overlayHeight, "active_overlay_left", skipCache, opacity ) ); painter->drawPixmap( overlayXOffset + endWidth, overlayYOffset, The::svgHandler()->renderSvg( "active_overlay_mid", overlayLength - endWidth * 2, overlayHeight, "active_overlay_mid", skipCache, opacity ) ); painter->drawPixmap( overlayXOffset + ( overlayLength - endWidth ), overlayYOffset, The::svgHandler()->renderSvg( "active_overlay_right", endWidth, overlayHeight, "active_overlay_right", skipCache, opacity ) ); } // --- paint the cover if( showCover ) { QModelIndex coverIndex = index.model()->index( index.row(), CoverImage ); QPixmap albumPixmap = coverIndex.data( Qt::DisplayRole ).value<QPixmap>(); if( !albumPixmap.isNull() ) { //offset cover if non square QPointF offset = centerImage( albumPixmap, nominalImageRect ); QRectF imageRect( nominalImageRect.x() + offset.x(), nominalImageRect.y() + offset.y(), nominalImageRect.width() - offset.x() * 2, nominalImageRect.height() - offset.y() * 2 ); painter->drawPixmap( imageRect, albumPixmap, QRectF( albumPixmap.rect() ) ); } QModelIndex emblemIndex = index.model()->index( index.row(), SourceEmblem ); QPixmap emblemPixmap = emblemIndex.data( Qt::DisplayRole ).value<QPixmap>(); if( !emblemPixmap.isNull() ) painter->drawPixmap( QRectF( nominalImageRect.x(), nominalImageRect.y() , 16, 16 ), emblemPixmap, QRectF( 0, 0 , 16, 16 ) ); } // --- paint the markers int markerOffsetX = frameHMargin; const int rowOffsetXBeforeMarkers = rowOffsetX; if( !headerRow ) { const int queuePosition = index.data( QueuePositionRole ).toInt(); if( queuePosition > 0 ) { const int x = markerOffsetX; const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); const QString number = QString::number( queuePosition ); const int iconWidth = option.fontMetrics.boundingRect( number ).width() + smallIconSize / 2; const QRect rect( x, y, iconWidth, smallIconSize ); // shift text by 1 pixel left const KColorScheme colorScheme( option.palette.currentColorGroup() ); QPen rectanglePen = painter->pen(); rectanglePen.setColor( colorScheme.foreground( KColorScheme::PositiveText ).color() ); QBrush rectangleBrush = colorScheme.background( KColorScheme::PositiveBackground ); painter->save(); painter->setPen( rectanglePen ); painter->setBrush( rectangleBrush ); painter->setRenderHint( QPainter::Antialiasing, true ); painter->drawRoundedRect( QRect( x, y - 1, iconWidth, smallIconSize ), smallIconSize / 3, smallIconSize / 3 ); painter->drawText( rect, Qt::AlignCenter, number ); painter->restore(); markerOffsetX += ( iconWidth + iconSpacing ); if ( !showCover ) rowOffsetX += ( iconWidth + iconSpacing ); else rowOffsetX += qMax( 0, iconWidth - smallIconSize - iconSpacing ); } if( index.data( MultiSourceRole ).toBool() ) { const int x = markerOffsetX; const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); painter->drawPixmap( x, y, The::svgHandler()->renderSvg( "multi_marker", smallIconSize, smallIconSize, "multi_marker" ) ); markerOffsetX += ( smallIconSize + iconSpacing ); if ( !showCover ) rowOffsetX += ( smallIconSize + iconSpacing ); } if( index.data( StopAfterTrackRole ).toBool() ) { const int x = markerOffsetX; const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); painter->drawPixmap( x, y, The::svgHandler()->renderSvg( "stop_button", smallIconSize, smallIconSize, "stop_button" ) ); markerOffsetX += ( smallIconSize + iconSpacing ); if ( !showCover ) rowOffsetX += ( smallIconSize + iconSpacing ); } } int markersWidth = rowOffsetX - rowOffsetXBeforeMarkers; Meta::TrackPtr trackPtr = index.data( TrackRole ).value<Meta::TrackPtr>(); QMap<QString, QString> trackArgs; if( trackPtr ) trackArgs = buildTrackArgsMap( trackPtr ); // --- paint all the rows for( int i = 0; i < rowCount; i++ ) { LayoutItemConfigRow row = config.row( i ); const int elementCount = row.count(); //we need to do a quick pass to figure out how much space is left for auto sizing elements qreal spareSpace = 1.0; int autoSizeElemCount = 0; for( int k = 0; k < elementCount; ++k ) { spareSpace -= row.element( k ).size(); if( row.element( k ).size() < 0.001 ) autoSizeElemCount++; } const qreal spacePerAutoSizeElem = spareSpace / (qreal)autoSizeElemCount; //give left over pixels to the first rows. Widgets are doing it the same. if( i == 0 ) rowHeight++; if( i == ( contentHeight % rowCount ) ) rowHeight--; int currentItemX = rowOffsetX; for( int j = 0; j < elementCount; ++j ) { LayoutItemConfigRowElement element = row.element( j ); // -- calculate the size qreal size; if( element.size() < 0.001 ) size = spacePerAutoSizeElem; else size = element.size(); qreal itemWidth; if( j == elementCount - 1 ) // use the full with for the last item itemWidth = rowWidth - (currentItemX - rowOffsetXBeforeMarkers); else itemWidth = rowWidth * size - markersWidth; markersWidth = 0; // leave columns > 0 alone, they are unaffected by markers if( itemWidth <= 1 ) continue; // no sense to paint such small items // -- set font bool bold = element.bold(); bool italic = element.italic(); bool underline = element.underline(); int alignment = element.alignment(); QFont font = option.font; font.setBold( bold ); font.setItalic( italic ); font.setUnderline( underline ); painter->setFont( font ); int value = element.value(); QModelIndex textIndex = index.model()->index( index.row(), value ); // -- paint the element if( value == Rating ) { int rating = textIndex.data( Qt::DisplayRole ).toInt(); KRatingPainter::paintRating( painter, QRect( currentItemX, rowOffsetY, itemWidth, rowHeight ), element.alignment(), rating ); } else if( value == Divider ) { QPixmap left = The::svgHandler()->renderSvg( "divider_left", 1, rowHeight , "divider_left" ); QPixmap right = The::svgHandler()->renderSvg( "divider_right", 1, rowHeight, "divider_right" ); if( alignment & Qt::AlignLeft ) { painter->drawPixmap( currentItemX, rowOffsetY, left ); painter->drawPixmap( currentItemX + 1, rowOffsetY, right ); } else if( alignment & Qt::AlignRight ) { painter->drawPixmap( currentItemX + itemWidth - 1, rowOffsetY, left ); painter->drawPixmap( currentItemX + itemWidth, rowOffsetY, right ); } else { int center = currentItemX + ( itemWidth / 2 ); painter->drawPixmap( center, rowOffsetY, left ); painter->drawPixmap( center + 1, rowOffsetY, right ); } } else if( value == Moodbar ) { //we cannot ask the model for the moodbar directly as we have no //way of asking for a specific size. Instead just get the track from //the model and ask the moodbar manager ourselves. Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>(); if( The::moodbarManager()->hasMoodbar( track ) ) { QPixmap moodbar = The::moodbarManager()->getMoodbar( track, itemWidth, rowHeight - 8 ); painter->drawPixmap( currentItemX, rowOffsetY + 4, moodbar ); } } //actual playlist item text is drawn here else { //TODO: get rid of passing TrackPtr as data, use custom role instead Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>(); QString text = textIndex.data( Qt::DisplayRole ).toString(); Amarok::QStringx prefix( element.prefix() ); Amarok::QStringx suffix( element.suffix() ); text = prefix.namedOptArgs( trackArgs ) + text + suffix.namedOptArgs( trackArgs ); text = QFontMetricsF( font ).elidedText( text, Qt::ElideRight, itemWidth ); //if the track can't be played, it should be grayed out to show that it is unavailable if( !track->isPlayable() ) { painter->save(); QPen grayPen = painter->pen(); grayPen.setColor( QColor( 127, 127, 127 ) ); // TODO: use disabled role painter->setPen( grayPen ); painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY, itemWidth - frameHMargin * 2, rowHeight ), alignment, text ); painter->restore(); } else { painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY, itemWidth - frameHMargin * 2, rowHeight ), alignment, text ); } } currentItemX += itemWidth; } rowOffsetY += rowHeight; } }
LayoutItemConfig LayoutManager::parseItemConfig( const QDomElement &elem ) { const bool showMetaContactIcon = ( elem.attribute( "show_metacontact_icon", "false" ).compare( "true", Qt::CaseInsensitive ) == 0 ); LayoutItemConfig config; config.setShowIcon( showMetaContactIcon ); QDomNodeList rows = elem.elementsByTagName("row"); int index = 0; while ( index < rows.size() ) { QDomNode rowNode = rows.item( index ); index++; LayoutItemConfigRow row; QDomNodeList elements = rowNode.toElement().elementsByTagName("element"); int index2 = 0; while ( index2 < elements.size() ) { QDomNode elementNode = elements.item( index2 ); index2++; int value = 0; // Placeholder as default QString configName = elementNode.toElement().attribute( "value" ); if ( !configName.isEmpty() ) { for ( int i = 0; i < m_tokens.size(); i++) { if ( m_tokens.at(i).mConfigName == configName ) { value = i; break; } } } QString prefix = elementNode.toElement().attribute( "prefix", QString() ); QString sufix = elementNode.toElement().attribute( "suffix", QString() ); qreal size = elementNode.toElement().attribute( "size", "0.0" ).toDouble(); bool bold = ( elementNode.toElement().attribute( "bold", "false" ).compare( "true", Qt::CaseInsensitive ) == 0 ); bool italic = ( elementNode.toElement().attribute( "italic", "false" ).compare( "true", Qt::CaseInsensitive ) == 0 ); bool small = ( elementNode.toElement().attribute( "small", "false" ).compare( "true", Qt::CaseInsensitive ) == 0 ); bool optimalSize = ( elementNode.toElement().attribute( "optimalSize", "false" ).compare( "true", Qt::CaseInsensitive ) == 0 ); QString alignmentString = elementNode.toElement().attribute( "alignment", "left" ); Qt::Alignment alignment; if ( alignmentString.compare( "left", Qt::CaseInsensitive ) == 0 ) alignment = Qt::AlignLeft | Qt::AlignVCenter; else if ( alignmentString.compare( "right", Qt::CaseInsensitive ) == 0 ) alignment = Qt::AlignRight| Qt::AlignVCenter; else alignment = Qt::AlignCenter| Qt::AlignVCenter; row.addElement( LayoutItemConfigRowElement( value, size, bold, italic, small, optimalSize, alignment, prefix, sufix ) ); } config.addRow( row ); } return config; }