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"