File indexing completed on 2024-05-05 04:21:24
0001 0002 /* 0003 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org> 0004 All rights reserved. 0005 0006 Redistribution and use in source and binary forms, with or without 0007 modification, are permitted provided that the following conditions 0008 are met: 0009 0010 1. Redistributions of source code must retain the above copyright 0011 notice, this list of conditions and the following disclaimer. 0012 2. Redistributions in binary form must reproduce the above copyright 0013 notice, this list of conditions and the following disclaimer in the 0014 documentation and/or other materials provided with the distribution. 0015 0016 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 0017 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 0018 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 0019 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 0020 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 0021 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0022 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0023 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0024 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0025 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0026 */ 0027 0028 0029 #define DEBUG_KP_COLOR_CELLS 0 0030 0031 0032 #include "widgets/kpColorCells.h" 0033 0034 #include "imagelib/kpColor.h" 0035 #include "lgpl/generic/kpColorCollection.h" 0036 #include "widgets/kpDefaultColorCollection.h" 0037 0038 #include "kpLogCategories.h" 0039 #include <KLocalizedString> 0040 0041 #include <QColorDialog> 0042 #include <QContextMenuEvent> 0043 #include <QScrollBar> 0044 0045 //--------------------------------------------------------------------- 0046 // 0047 // Table Geometry 0048 // 0049 0050 0051 // The number of columns that the table normally has. 0052 const int TableDefaultNumColumns = 11; 0053 0054 const int TableDefaultWidth = ::TableDefaultNumColumns * 26; 0055 0056 const int TableDefaultHeight = 52; 0057 0058 0059 static int TableNumColumns (const kpColorCollection &colorCol) 0060 { 0061 if (colorCol.count () == 0) { 0062 return 0; 0063 } 0064 0065 return ::TableDefaultNumColumns; 0066 } 0067 0068 static int TableNumRows (const kpColorCollection &colorCol) 0069 { 0070 const int cols = ::TableNumColumns (colorCol); 0071 if (cols == 0) { 0072 return 0; 0073 } 0074 0075 return (colorCol.count () + (cols - 1)) / cols; 0076 } 0077 0078 0079 static int TableCellWidth (const kpColorCollection &colorCol) 0080 { 0081 Q_UNUSED (colorCol); 0082 0083 return ::TableDefaultWidth / ::TableDefaultNumColumns; 0084 } 0085 0086 static int TableCellHeight (const kpColorCollection &colorCol) 0087 { 0088 if (::TableNumRows (colorCol) <= 2) { 0089 return ::TableDefaultHeight / 2; 0090 } 0091 0092 return ::TableDefaultHeight / 3; 0093 } 0094 0095 0096 // 0097 // kpColorCells 0098 // 0099 0100 0101 struct kpColorCellsPrivate 0102 { 0103 Qt::Orientation orientation{}; 0104 0105 // REFACTOR: This is data duplication with kpColorCellsBase::color[]. 0106 // We've probably forgotten to synchronize them in some points. 0107 // 0108 // Calls to kpColorCellsBase::setColor() (which also come from 0109 // kpColorCellsBase itself) will automatically update both 0110 // kpColorCellsBase::d->color[] and the table cells. setColor() emits 0111 // colorChanged(), which is caught by our slotColorChanged(), 0112 // which synchronizes this color collection and updates the modified flag. 0113 // 0114 // Avoid calling our grandparent's, QTableWidget's, mutating methods as we 0115 // don't override enough of them, to fire signals that we can catch to update 0116 // this color collection. 0117 // 0118 // If you modify this color collection directly (e.g. in setColorCollection(), 0119 // openColorCollection(), appendRow(), deleteLastRow(), ...), you must work 0120 // the other way and call makeCellsMatchColorCollection() to synchronize 0121 // kpColorCellsBase::d->color[] and the table cells. You still need to update 0122 // the modified flag. 0123 kpColorCollection colorCol; 0124 0125 QUrl url; 0126 bool isModified{}; 0127 0128 bool blockColorChangedSig{}; 0129 }; 0130 0131 //--------------------------------------------------------------------- 0132 0133 kpColorCells::kpColorCells (QWidget *parent, 0134 Qt::Orientation o) 0135 : kpColorCellsBase (parent, 0/*rows for now*/, 0/*cols for now*/), 0136 d (new kpColorCellsPrivate ()) 0137 { 0138 d->orientation = o; 0139 d->isModified = false; 0140 d->blockColorChangedSig = false; 0141 0142 0143 // When a text box is active, clicking to change the background color 0144 // should not move the keyboard focus away from the text box. 0145 setFocusPolicy (Qt::TabFocus); 0146 0147 setShading (false); // no 3D look 0148 0149 setAcceptDrops (true); 0150 setAcceptDrags (true); 0151 0152 0153 setCellsResizable (false); 0154 0155 if (o == Qt::Horizontal) 0156 { 0157 // Reserve enough room for the default color collection's cells _and_ 0158 // a vertical scrollbar, which only appears when it's required. 0159 // This ensures that if the vertical scrollbar appears, it does not obscure 0160 // any cells or require the addition of a horizontal scrollbar, which would 0161 // look ugly and take even more precious room. 0162 // 0163 // We do not dynamically reserve room based on the actual number of rows 0164 // of cells, as that would make our containing widgets too big. 0165 setMinimumSize (::TableDefaultWidth + frameWidth () * 2 + 0166 verticalScrollBar()->sizeHint().width(), 0167 ::TableDefaultHeight + frameWidth () * 2); 0168 } 0169 else 0170 { 0171 Q_ASSERT (!"implemented"); 0172 } 0173 0174 setVerticalScrollBarPolicy (Qt::ScrollBarAsNeeded); 0175 0176 // The default QTableWidget policy of QSizePolicy::Expanding forces our 0177 // containing widgets to get too big. Override it. 0178 setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum); 0179 0180 connect (this, &kpColorCells::colorSelectedWhitButton, 0181 this, &kpColorCells::slotColorSelected); 0182 0183 connect (this, &kpColorCells::colorDoubleClicked, 0184 this, &kpColorCells::slotColorDoubleClicked); 0185 0186 connect (this, &kpColorCells::colorChanged, 0187 this, &kpColorCells::slotColorChanged); 0188 0189 0190 setColorCollection (DefaultColorCollection ()); 0191 0192 0193 setWhatsThis ( 0194 i18n ( 0195 "<qt>" 0196 0197 "<p>To select the foreground color that tools use to draw," 0198 " left-click on a filled-in color cell." 0199 " To select the background color, right-click instead.</p>" 0200 0201 "<p>To change the color of a color cell itself, double-click on it.</p>" 0202 0203 "<p>You can also swap the color of a filled-in cell with any other" 0204 " cell using drag and drop." 0205 " Also, if you hold down the <b>Ctrl</b> key, the destination" 0206 " cell's color will be" 0207 " overwritten, instead of being swapped with the color of the source cell.</p>" 0208 0209 "</qt>")); 0210 } 0211 0212 //--------------------------------------------------------------------- 0213 0214 kpColorCells::~kpColorCells () 0215 { 0216 delete d; 0217 } 0218 0219 //--------------------------------------------------------------------- 0220 0221 0222 // public static 0223 kpColorCollection kpColorCells::DefaultColorCollection () 0224 { 0225 return kpDefaultColorCollection (); 0226 } 0227 0228 //--------------------------------------------------------------------- 0229 0230 0231 // public 0232 Qt::Orientation kpColorCells::orientation () const 0233 { 0234 return d->orientation; 0235 } 0236 0237 //--------------------------------------------------------------------- 0238 0239 // public 0240 void kpColorCells::setOrientation (Qt::Orientation o) 0241 { 0242 d->orientation = o; 0243 0244 makeCellsMatchColorCollection (); 0245 } 0246 0247 //--------------------------------------------------------------------- 0248 0249 0250 // protected 0251 // OPT: Find out why this is being called multiple times on startup. 0252 void kpColorCells::makeCellsMatchColorCollection () 0253 { 0254 int c, r; 0255 0256 if (orientation () == Qt::Horizontal) 0257 { 0258 c = ::TableNumColumns (d->colorCol); 0259 r = ::TableNumRows (d->colorCol); 0260 } 0261 else 0262 { 0263 c = ::TableNumRows (d->colorCol); 0264 r = ::TableNumColumns (d->colorCol); 0265 } 0266 0267 #if DEBUG_KP_COLOR_CELLS 0268 qCDebug(kpLogWidgets) << "kpColorCells::makeCellsMatchColorCollection():" 0269 << "r=" << r << "c=" << c; 0270 qCDebug(kpLogWidgets) << "verticalScrollBar=" << verticalScrollBar () 0271 << " sizeHint=" 0272 << (verticalScrollBar () ? 0273 verticalScrollBar ()->sizeHint () : 0274 QSize (-12, -34)); 0275 #endif 0276 0277 // Delete all cell widgets. This ensures that there will be no left-over 0278 // cell widgets, for the colors in the new color collection that are 0279 // actually invalid (which should not have cell widgets). 0280 clearContents (); 0281 0282 setRowCount (r); 0283 setColumnCount (c); 0284 0285 0286 int CellWidth = ::TableCellWidth (d->colorCol), 0287 CellHeight = ::TableCellHeight (d->colorCol); 0288 0289 // TODO: Take a screenshot of KolourPaint, magnify it and you'll find the 0290 // cells don't have exactly the sizes requested here. e.g. the 0291 // top row of cells is 1 pixel shorter than the bottom row. There 0292 // are probably other glitches. 0293 for (int y = 0; y < r; y++) { 0294 setRowHeight (y, CellHeight); 0295 } 0296 for (int x = 0; x < c; x++) { 0297 setColumnWidth (x, CellWidth); 0298 } 0299 0300 0301 const bool oldBlockColorChangedSig = d->blockColorChangedSig; 0302 d->blockColorChangedSig = true; 0303 // The last "(rowCount() * columnCount()) - d->colorCol.count()" cells 0304 // will be empty because we did not initialize them. 0305 for (int i = 0; i < d->colorCol.count (); i++) 0306 { 0307 int y, x; 0308 int pos; 0309 0310 if (orientation () == Qt::Horizontal) 0311 { 0312 y = i / c; 0313 x = i % c; 0314 pos = i; 0315 } 0316 else 0317 { 0318 y = i % r; 0319 x = i / r; 0320 // int x = c - 1 - i / r; 0321 pos = y * c + x; 0322 } 0323 #if DEBUG_KP_COLOR_CELLS && 0 0324 qCDebug(kpLogWidgets) << "\tSetting cell " << i << ": y=" << y << " x=" << x 0325 << " pos=" << pos; 0326 qCDebug(kpLogWidgets) << "\t\tcolor=" << (int *) d->colorCol.color (i).rgba() 0327 << "isValid=" << d->colorCol.color (i).isValid (); 0328 #endif 0329 0330 // (color may be invalid resulting in a hole in the middle of the table) 0331 setColor (pos, d->colorCol.color (i)); 0332 //this->setToolTip( cellGeometry (y, x), colors [i].name ()); 0333 } 0334 d->blockColorChangedSig = oldBlockColorChangedSig; 0335 } 0336 0337 //--------------------------------------------------------------------- 0338 0339 0340 bool kpColorCells::isModified () const 0341 { 0342 return d->isModified; 0343 } 0344 0345 //--------------------------------------------------------------------- 0346 0347 void kpColorCells::setModified (bool yes) 0348 { 0349 #if DEBUG_KP_COLOR_CELLS 0350 qCDebug(kpLogWidgets) << "kpColorCells::setModified(" << yes << ")"; 0351 #endif 0352 0353 if (yes == d->isModified) { 0354 return; 0355 } 0356 0357 d->isModified = yes; 0358 0359 Q_EMIT isModifiedChanged (yes); 0360 } 0361 0362 //--------------------------------------------------------------------- 0363 0364 void kpColorCells::setModified () 0365 { 0366 setModified (true); 0367 } 0368 0369 //--------------------------------------------------------------------- 0370 0371 0372 QUrl kpColorCells::url () const 0373 { 0374 return d->url; 0375 } 0376 0377 //--------------------------------------------------------------------- 0378 0379 QString kpColorCells::name () const 0380 { 0381 return d->colorCol.name (); 0382 } 0383 0384 //--------------------------------------------------------------------- 0385 0386 0387 const kpColorCollection *kpColorCells::colorCollection () const 0388 { 0389 return &d->colorCol; 0390 } 0391 0392 //--------------------------------------------------------------------- 0393 0394 0395 void kpColorCells::ensureHaveAtLeastOneRow () 0396 { 0397 if (d->colorCol.count () == 0) { 0398 d->colorCol.resize (::TableDefaultNumColumns); 0399 } 0400 } 0401 0402 //--------------------------------------------------------------------- 0403 0404 void kpColorCells::setColorCollection (const kpColorCollection &colorCol, const QUrl &url) 0405 { 0406 d->colorCol = colorCol; 0407 ensureHaveAtLeastOneRow (); 0408 0409 d->url = url; 0410 setModified (false); 0411 0412 makeCellsMatchColorCollection (); 0413 0414 Q_EMIT rowCountChanged (rowCount ()); 0415 Q_EMIT urlChanged (d->url); 0416 Q_EMIT nameChanged (name ()); 0417 } 0418 0419 //--------------------------------------------------------------------- 0420 0421 0422 bool kpColorCells::openColorCollection (const QUrl &url) 0423 { 0424 // (this will pop up an error dialog on failure) 0425 if (d->colorCol.open (url, this)) 0426 { 0427 ensureHaveAtLeastOneRow (); 0428 0429 d->url = url; 0430 setModified (false); 0431 0432 makeCellsMatchColorCollection (); 0433 0434 Q_EMIT rowCountChanged (rowCount ()); 0435 Q_EMIT urlChanged (d->url); 0436 Q_EMIT nameChanged (name ()); 0437 0438 return true; 0439 } 0440 0441 return false; 0442 } 0443 0444 //--------------------------------------------------------------------- 0445 0446 bool kpColorCells::saveColorCollectionAs (const QUrl &url) 0447 { 0448 // (this will pop up an error dialog on failure) 0449 if (d->colorCol.saveAs (url, this)) 0450 { 0451 d->url = url; 0452 setModified (false); 0453 0454 Q_EMIT urlChanged (d->url); 0455 return true; 0456 } 0457 0458 return false; 0459 } 0460 0461 //--------------------------------------------------------------------- 0462 0463 bool kpColorCells::saveColorCollection () 0464 { 0465 // (this will pop up an error dialog on failure) 0466 if (d->colorCol.saveAs (d->url, this)) 0467 { 0468 setModified (false); 0469 return true; 0470 } 0471 0472 return false; 0473 } 0474 0475 //--------------------------------------------------------------------- 0476 0477 0478 void kpColorCells::appendRow () 0479 { 0480 // This is the easiest implementation: change the color collection 0481 // and then synchronize the table cells. The other way is to call 0482 // setRowCount() and then, synchronize the color collection. 0483 0484 const int targetNumCells = (rowCount () + 1) * ::TableDefaultNumColumns; 0485 d->colorCol.resize (targetNumCells); 0486 0487 setModified (true); 0488 0489 makeCellsMatchColorCollection (); 0490 0491 Q_EMIT rowCountChanged (rowCount ()); 0492 } 0493 0494 //--------------------------------------------------------------------- 0495 0496 void kpColorCells::deleteLastRow () 0497 { 0498 // This is the easiest implementation: change the color collection 0499 // and then synchronize the table cells. The other way is to call 0500 // setRowCount() and then, synchronize the color collection. 0501 0502 const int targetNumCells = 0503 qMax (0, (rowCount () - 1) * ::TableDefaultNumColumns); 0504 d->colorCol.resize (targetNumCells); 0505 0506 // If there was only one row of colors to start with, the effect of this 0507 // line (after the above resize()) is to change that row to a row of 0508 // invalid colors. 0509 ensureHaveAtLeastOneRow (); 0510 0511 setModified (true); 0512 0513 makeCellsMatchColorCollection (); 0514 0515 Q_EMIT rowCountChanged (rowCount ()); 0516 } 0517 0518 //--------------------------------------------------------------------- 0519 0520 0521 // protected virtual [base QWidget] 0522 void kpColorCells::contextMenuEvent (QContextMenuEvent *e) 0523 { 0524 // Eat right-mouse press to prevent it from getting to the toolbar. 0525 e->accept (); 0526 } 0527 0528 //--------------------------------------------------------------------- 0529 0530 // protected slot 0531 void kpColorCells::slotColorSelected (int cell, const QColor &color, 0532 Qt::MouseButton button) 0533 { 0534 #if DEBUG_KP_COLOR_CELLS 0535 qCDebug(kpLogWidgets) << "kpColorCells::slotColorSelected(cell=" << cell 0536 << ") mouseButton = " << button 0537 << " rgb=" << (int *) color.rgba(); 0538 #else 0539 Q_UNUSED (cell); 0540 #endif 0541 0542 if (button == Qt::LeftButton) 0543 { 0544 Q_EMIT foregroundColorChanged (kpColor (color.rgba())); 0545 } 0546 else if (button == Qt::RightButton) 0547 { 0548 Q_EMIT backgroundColorChanged (kpColor (color.rgba())); 0549 } 0550 0551 // REFACTOR: Make selectedness configurable inside kpColorCellsBase? 0552 // 0553 // Deselect the selected cell (selected by above kpColorCellsBase::mouseReleaseEvent()). 0554 // KolourPaint's palette has no concept of a current cell/color: you can 0555 // pick a color but you can't mark a cell as selected. In any case, a 0556 // selected cell would be rendered as violet, which would ruin the cell. 0557 // 0558 // setSelectionMode (kpColorCellsBase::NoSelection); does not work so we 0559 // clearSelection(). I think setSelectionMode() concerns when the user 0560 // directly selects a cell - not when kpColorCellsBase::mouseReleaseEvent() 0561 // selects a cell programmatically. 0562 clearSelection (); 0563 } 0564 0565 //--------------------------------------------------------------------- 0566 0567 // protected slot 0568 void kpColorCells::slotColorDoubleClicked (int cell, const QColor &) 0569 { 0570 QColorDialog dialog(this); 0571 dialog.setCurrentColor(kpColorCellsBase::color(cell)); 0572 dialog.setOptions(QColorDialog::ShowAlphaChannel); 0573 if ( dialog.exec() == QDialog::Accepted ) 0574 setColor (cell, dialog.currentColor()); 0575 } 0576 0577 //--------------------------------------------------------------------- 0578 0579 // protected slot 0580 void kpColorCells::slotColorChanged (int cell, const QColor &color) 0581 { 0582 #if DEBUG_KP_COLOR_CELLS 0583 qCDebug(kpLogWidgets) << "cell=" << cell << "color=" << (const int *) color.rgba() 0584 << "d->colorCol.count()=" << d->colorCol.count (); 0585 #endif 0586 0587 if (d->blockColorChangedSig) { 0588 return; 0589 } 0590 0591 // Cater for adding new colors to the end. 0592 if (cell >= d->colorCol.count ()) { 0593 d->colorCol.resize (cell + 1); 0594 } 0595 0596 // TODO: We lose color names on a color swap (during drag-and-drop). 0597 const int ret = d->colorCol.changeColor (cell, color, 0598 QString ()/*color name*/); 0599 Q_ASSERT (ret == cell); 0600 0601 setModified (true); 0602 } 0603 0604 #include "moc_kpColorCells.cpp"