File indexing completed on 2024-05-05 04:49:29

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de>                          *
0003  * Copyright (c) 2012 Ralf Engels <ralf-engels@gmx.de>                                  *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #include "TokenDropTarget.h"
0019 
0020 #include "Token.h"
0021 #include "TokenPool.h"
0022 #include "core/support/Debug.h"
0023 
0024 #include <KLocalizedString>
0025 
0026 #include <QDropEvent>
0027 #include <QPainter>
0028 #include <QVBoxLayout>
0029 #include <QMimeData>
0030 
0031 TokenDropTarget::TokenDropTarget( QWidget *parent )
0032     : QWidget( parent )
0033     , m_rowLimit( 0 )
0034     , m_rows( 0 )
0035     , m_horizontalStretch( false ) // DANGER: m_horizontalStretch is used as int in the following code, assuming that true == 1
0036     , m_verticalStretch( true )
0037     , m_tokenFactory( new TokenFactory() )
0038 {
0039     setAcceptDrops( true );
0040 
0041     QBoxLayout* mainLayout = new QVBoxLayout( this );
0042     mainLayout->setSpacing( 0 );
0043     mainLayout->addStretch( 1 ); // the vertical stretch
0044 
0045     mainLayout->setContentsMargins( 0, 0, 0, 0 );
0046 }
0047 
0048 TokenDropTarget::~TokenDropTarget()
0049 {
0050     delete m_tokenFactory;
0051 }
0052 
0053 QSize
0054 TokenDropTarget::sizeHint() const
0055 {
0056     QSize result = QWidget::sizeHint();
0057 
0058      // we need at least space for the "empty" text.
0059     int h = fontMetrics().height();
0060     result = result.expandedTo( QSize( 36 * h, 2 * h ) );
0061 
0062     return result;
0063 }
0064 
0065 QSize
0066 TokenDropTarget::minimumSizeHint() const
0067 {
0068     QSize result = QWidget::minimumSizeHint();
0069 
0070      // we need at least space for the "empty" text.
0071     int h = fontMetrics().height();
0072     result = result.expandedTo( QSize( 36 * h, 2 * h ) );
0073 
0074     return result;
0075 }
0076 
0077 
0078 QHBoxLayout *
0079 TokenDropTarget::appendRow()
0080 {
0081     QHBoxLayout *box = new QHBoxLayout;
0082     box->setSpacing( 0 );
0083     if( m_horizontalStretch )
0084         box->addStretch();
0085     static_cast<QVBoxLayout*>(layout())->insertLayout( rows(), box );
0086     m_rows++;
0087     return box;
0088 }
0089 
0090 void
0091 TokenDropTarget::clear()
0092 {
0093     QList< Token *> allTokens = tokensAtRow();
0094     foreach( Token* token, allTokens )
0095     {
0096         token->setParent(nullptr);
0097         token->deleteLater();
0098     }
0099 
0100     Q_EMIT changed();
0101 }
0102 
0103 int
0104 TokenDropTarget::count() const
0105 {
0106     int c = 0;
0107     for( int row = rows() - 1; row >= 0; --row )
0108         if( QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() ) )
0109             c += box->count() - m_horizontalStretch;
0110 
0111     return c;
0112 }
0113 
0114 void
0115 TokenDropTarget::setRowLimit( uint r )
0116 {
0117     // if we have more than one row we have a stretch at the end.
0118     QBoxLayout *mainLayout = qobject_cast<QBoxLayout*>( layout() );
0119     if( ( r == 1 ) && (m_rowLimit != 1 ) )
0120         mainLayout->takeAt( mainLayout->count() - 1 );
0121     else if( ( r != 1 ) && (m_rowLimit == 1 ) )
0122         mainLayout->addStretch( 1 ); // the vertical stretch
0123 
0124     m_rowLimit = r;
0125 }
0126 
0127 void
0128 TokenDropTarget::deleteEmptyRows()
0129 {
0130     DEBUG_BLOCK;
0131 
0132     for( int row = rows() - 1; row >= 0; --row )
0133     {
0134         QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
0135         if( box && box->count() < ( 1 + m_horizontalStretch ) ) // sic! last is spacer
0136         {
0137             delete layout()->takeAt( row );
0138             m_rows--;
0139         }
0140     }
0141     update(); // this removes empty layouts somehow for deleted tokens. don't remove
0142 }
0143 
0144 QList< Token *>
0145 TokenDropTarget::tokensAtRow( int row )
0146 {
0147     DEBUG_BLOCK;
0148 
0149     int lower = 0;
0150     int upper = (int)rows();
0151     if( row > -1 && row < (int)rows() )
0152     {
0153         lower = row;
0154         upper = row + 1;
0155     }
0156 
0157     QList< Token *> list;
0158     Token *token;
0159     for( row = lower; row < upper; ++row )
0160         if ( QHBoxLayout *rowBox = qobject_cast<QHBoxLayout*>( layout()->itemAt( row )->layout() ) )
0161         {
0162             for( int col = 0; col < rowBox->count() - m_horizontalStretch; ++col )
0163                 if ( ( token = qobject_cast<Token*>( rowBox->itemAt( col )->widget() ) ) )
0164                     list << token;
0165         }
0166 
0167     debug() << "Row:"<<row<<"items:"<<list.count();
0168 
0169     return list;
0170 }
0171 
0172 void
0173 TokenDropTarget::insertToken( Token *token, int row, int col )
0174 {
0175     // - copy the token if it belongs to a token pool (fix BR 296136)
0176     if( qobject_cast<TokenPool*>(token->parent() ) ) {
0177         debug() << "Copying token" << token->name();
0178         token = m_tokenFactory->createToken( token->name(),
0179                                              token->iconName(),
0180                                              token->value() );
0181     }
0182 
0183     token->setParent( this );
0184 
0185     // - validate row
0186     if ( row < 0 && rowLimit() && rows() >= rowLimit() )
0187         row = rowLimit() - 1; // want to append, but we can't so use the last row instead
0188 
0189     QBoxLayout *box;
0190     if( row < 0 || row >= (int)rows() )
0191         box = appendRow();
0192     else
0193         box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
0194 
0195     // - validate col
0196     if( col < 0 || col > box->count() - ( 1 + m_horizontalStretch ) )
0197         col = box->count() - m_horizontalStretch;
0198 
0199     // - copy the token if it belongs to a token pool (fix BR 296136)
0200     if( qobject_cast<TokenPool*>(token->parent() ) ) {
0201         debug() << "Copying token" << token->name();
0202         token = m_tokenFactory->createToken( token->name(),
0203                                              token->iconName(),
0204                                              token->value() );
0205     }
0206 
0207     box->insertWidget( col, token );
0208     token->show();
0209 
0210     connect( token, &Token::changed, this, &TokenDropTarget::changed );
0211     connect( token, &Token::gotFocus, this, &TokenDropTarget::tokenSelected );
0212     connect( token, &Token::removed, this, &TokenDropTarget::deleteEmptyRows );
0213 
0214     Q_EMIT changed();
0215 }
0216 
0217 
0218 Token*
0219 TokenDropTarget::tokenAt( const QPoint &pos ) const
0220 {
0221     for( uint row = 0; row < rows(); ++row )
0222         if( QBoxLayout *rowBox = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() ) )
0223             for( int col = 0; col < rowBox->count(); ++col )
0224                 if( QWidget *kid = rowBox->itemAt( col )->widget() )
0225                 {
0226                     if( kid->geometry().contains( pos ) )
0227                         return qobject_cast<Token*>(kid);
0228                 }
0229     return nullptr;
0230 }
0231 
0232 void
0233 TokenDropTarget::drop( Token *token, const QPoint &pos )
0234 {
0235     DEBUG_BLOCK;
0236 
0237     if ( !token )
0238         return;
0239 
0240     // find the token at the position.
0241     QWidget *child = childAt( pos );
0242     Token *targetToken = qobject_cast<Token*>(child); // tokenAt( pos );
0243     if( !targetToken && child && child->parent() ) // sometimes we get the label of the token.
0244         targetToken = qobject_cast<Token*>( child->parent() );
0245 
0246     // unlayout in case of move
0247     if( QBoxLayout *box = rowBox( token ) )
0248     {
0249         box->removeWidget( token );
0250         deleteEmptyRows(); // a row could now be empty due to a move
0251     }
0252 
0253     if( targetToken )
0254     {   // we hit a sibling, -> prepend
0255         QPoint idx;
0256         rowBox( targetToken, &idx );
0257 
0258         if( rowLimit() != 1 && rowLimit() < m_rows && idx.y() == (int)m_rows - 1 &&
0259             pos.y() > targetToken->geometry().y() + ( targetToken->height() * 2 / 3 ) )
0260             insertToken( token, idx.y() + 1, idx.x());
0261         else if( pos.x() > targetToken->geometry().x() + targetToken->width() / 2 )
0262             insertToken( token, idx.y(), idx.x() + 1);
0263         else
0264             insertToken( token, idx.y(), idx.x() );
0265     }
0266     else
0267     {
0268         appendToken( token );
0269     }
0270 
0271     token->setFocus( Qt::OtherFocusReason ); // select the new token right away
0272 }
0273 
0274 void
0275 TokenDropTarget::dragEnterEvent( QDragEnterEvent *event )
0276 {
0277     if( event->mimeData()->hasFormat( Token::mimeType() ) )
0278         event->acceptProposedAction();
0279 }
0280 
0281 void
0282 TokenDropTarget::dropEvent( QDropEvent *event )
0283 {
0284     if( event->mimeData()->hasFormat( Token::mimeType() ) )
0285     {
0286         event->acceptProposedAction();
0287 
0288         Token *token = qobject_cast<Token*>( event->source() );
0289 
0290         if ( !token ) // decode the stream created in TokenPool::dropEvent
0291             token = m_tokenFactory->createTokenFromMime( event->mimeData(), this );
0292 
0293     // - copy the token if it belongs to a token pool (fix BR 296136)
0294     if( qobject_cast<TokenPool*>(token->parent() ) ) {
0295         token = m_tokenFactory->createToken( token->name(),
0296                                              token->iconName(),
0297                                              token->value() );
0298     }
0299 
0300         if( token )
0301             drop( token, event->pos() );
0302     }
0303 }
0304 
0305 void
0306 TokenDropTarget::paintEvent(QPaintEvent *pe)
0307 {
0308     QWidget::paintEvent(pe);
0309     if (count())
0310         return;
0311     QPainter p(this);
0312     QColor c = palette().color(foregroundRole());
0313     c.setAlpha(c.alpha()*64/255);
0314     p.setPen(c);
0315     p.drawText(rect(), Qt::AlignCenter | Qt::TextWordWrap, i18n("Drag in and out items from above."));
0316     p.end();
0317 }
0318 
0319 int
0320 TokenDropTarget::row( Token *token ) const
0321 {
0322     for( uint row = 0; row <= rows(); ++row )
0323     {
0324         QBoxLayout *box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
0325         if ( box && ( box->indexOf( token ) ) > -1 )
0326             return row;
0327     }
0328     return -1;
0329 }
0330 
0331 QBoxLayout *
0332 TokenDropTarget::rowBox( QWidget *w, QPoint *idx ) const
0333 {
0334     QBoxLayout *box = nullptr;
0335     int col;
0336     for( uint row = 0; row < rows(); ++row )
0337     {
0338         box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
0339         if ( box && ( col = box->indexOf( w ) ) > -1 )
0340         {
0341             if ( idx )
0342             {
0343                 idx->setX( col );
0344                 idx->setY( row );
0345             }
0346             return box;
0347         }
0348     }
0349     return nullptr;
0350 }
0351 
0352 QBoxLayout *
0353 TokenDropTarget::rowBox( const QPoint &pt ) const
0354 {
0355     QBoxLayout *box = nullptr;
0356     for( uint row = 0; row < rows(); ++row )
0357     {
0358         box = qobject_cast<QBoxLayout*>( layout()->itemAt( row )->layout() );
0359         if ( !box )
0360             continue;
0361         for ( int col = 0; col < box->count(); ++col )
0362         {
0363             if ( QWidget *w = box->itemAt( col )->widget() )
0364             {
0365                 const QRect &geo = w->geometry();
0366                 if ( geo.y() <= pt.y() && geo.bottom() >= pt.y() )
0367                     return box;
0368                 break; // yes - all widgets are supposed of equal height. we checked on, we checked all
0369             }
0370         }
0371     }
0372     return nullptr;
0373 }
0374 
0375 void
0376 TokenDropTarget::setCustomTokenFactory( TokenFactory * factory )
0377 {
0378     delete m_tokenFactory;
0379     m_tokenFactory = factory;
0380 }
0381 
0382 void
0383 TokenDropTarget::setVerticalStretch( bool value )
0384 {
0385     if( value == m_verticalStretch )
0386         return;
0387 
0388     m_verticalStretch = value;
0389 
0390     if( m_verticalStretch )
0391         qobject_cast<QBoxLayout*>( layout() )->addStretch( 1 );
0392     else
0393         delete layout()->takeAt( layout()->count() - 1 );
0394 }
0395 
0396