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