kolourpaint/lgpl/generic/widgets/kpColorCellsBase.cpp

564 lines
16 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 1997 Martin Jones (mjones@kde.org)
Copyright (C) 2007 Roberto Raggi (roberto@kdevelop.org)
Copyright (C) 2007 Clarence Dang (dang@kde.org)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
//-----------------------------------------------------------------------------
#define DEBUG_KP_COLOR_CELLS_BASE 0
#include "kpColorCellsBase.h"
#include <QApplication>
#include <QDrag>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QHeaderView>
#include <QItemDelegate>
#include <QMouseEvent>
#include <QPainter>
#include <QImage>
#include "kpLogCategories.h"
#include <KColorMimeData>
class kpColorCellsBase::kpColorCellsBasePrivate
{
public:
kpColorCellsBasePrivate(kpColorCellsBase *q): q(q)
{
colors = nullptr;
inMouse = false;
selected = -1;
shade = false;
acceptDrags = false;
cellsResizable = true;
}
kpColorCellsBase *q;
// Note: This is a good thing and is _not_ data duplication with the
// colors of QTableWidget cells, for the following reasons:
//
// 1. QColor in Qt4 is full-quality RGB. However, QTableWidget
// cells are lossy as their colors may be dithered on the screen.
//
// Eventually, this field will be changed to a kpColor.
//
// 2. We change the QTableWidget cells' colors when the widget is
// disabled (see changeEvent()).
//
// Therefore, do not remove this field without better reasons.
QColor *colors;
QPoint mousePos;
int selected;
bool shade;
bool acceptDrags;
bool cellsResizable;
bool inMouse;
};
kpColorCellsBase::kpColorCellsBase( QWidget *parent, int rows, int cols )
: QTableWidget( parent ), d(new kpColorCellsBasePrivate(this))
{
setItemDelegate(new QItemDelegate(this));
setFrameShape(QFrame::NoFrame);
d->shade = true;
setRowCount( rows );
setColumnCount( cols );
verticalHeader()->setMinimumSectionSize(16);
verticalHeader()->hide();
horizontalHeader()->setMinimumSectionSize(16);
horizontalHeader()->hide();
d->colors = new QColor [ rows * cols ];
d->selected = 0;
d->inMouse = false;
// Drag'n'Drop
setAcceptDrops( true);
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
viewport()->setBackgroundRole( QPalette::Window );
setBackgroundRole( QPalette::Window );
}
kpColorCellsBase::~kpColorCellsBase()
{
delete [] d->colors;
delete d;
}
void kpColorCellsBase::invalidateAllColors ()
{
for (int r = 0; r < rowCount (); r++)
for (int c = 0; c < columnCount (); c++)
d->colors [r * columnCount () + c] = QColor ();
}
void kpColorCellsBase::clear()
{
invalidateAllColors ();
QTableWidget::clear ();
}
void kpColorCellsBase::clearContents()
{
invalidateAllColors ();
QTableWidget::clearContents ();
}
void kpColorCellsBase::setRowColumnCounts (int rows, int columns)
{
const int oldRows = rowCount (), oldCols = columnCount ();
const int newRows = rows, newCols = columns;
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "oldRows=" << oldRows << "oldCols=" << oldCols
<< "newRows=" << newRows << "newCols=" << newCols;
#endif
if (oldRows == newRows && oldCols == newCols)
return;
QTableWidget::setColumnCount (newCols);
QTableWidget::setRowCount (newRows);
QColor *oldColors = d->colors;
d->colors = new QColor [newRows * newCols];
for (int r = 0; r < qMin (oldRows, newRows); r++)
for (int c = 0; c < qMin (oldCols, newCols); c++)
d->colors [r * newCols + c] = oldColors [r * oldCols + c];
delete [] oldColors;
}
void kpColorCellsBase::setColumnCount (int newColumns)
{
setRowColumnCounts (rowCount (), newColumns);
}
void kpColorCellsBase::setRowCount (int newRows)
{
setRowColumnCounts (newRows, columnCount ());
}
QColor kpColorCellsBase::color(int index) const
{
return d->colors[index];
}
int kpColorCellsBase::count() const
{
return rowCount() * columnCount();
}
void kpColorCellsBase::setShading(bool _shade)
{
d->shade = _shade;
}
void kpColorCellsBase::setAcceptDrags(bool _acceptDrags)
{
d->acceptDrags = _acceptDrags;
}
void kpColorCellsBase::setCellsResizable(bool yes)
{
d->cellsResizable = yes;
}
void kpColorCellsBase::setSelected(int index)
{
Q_ASSERT( index >= 0 && index < count() );
d->selected = index;
}
int kpColorCellsBase::selectedIndex() const
{
return d->selected;
}
//---------------------------------------------------------------------
static void TableWidgetItemSetColor (QTableWidgetItem *tableItem, const QColor &color)
{
Q_ASSERT (tableItem);
QImage image(16, 16, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image);
const int StippleSize = 4;
QColor useColor;
for (int dy = 0; dy < 16; dy += StippleSize)
{
for (int dx = 0; dx < 16; dx += StippleSize)
{
const bool parity = ((dy + dx) / StippleSize) % 2;
if (!parity)
useColor = Qt::white;
else
useColor = Qt::lightGray;
painter.fillRect(dx, dy, StippleSize, StippleSize, useColor);
}
}
painter.fillRect(image.rect(), color);
painter.end();
tableItem->setData(Qt::BackgroundRole , QBrush(image));
}
//---------------------------------------------------------------------
void kpColorCellsBase::setColor( int column, const QColor &colorIn )
{
const int tableRow = column / columnCount();
const int tableColumn = column % columnCount();
Q_ASSERT( tableRow >= 0 && tableRow < rowCount() );
Q_ASSERT( tableColumn >= 0 && tableColumn < columnCount() );
QColor color = colorIn;
d->colors[column] = color;
QTableWidgetItem* tableItem = item(tableRow,tableColumn);
if (color.isValid ())
{
if ( tableItem == nullptr ) {
tableItem = new QTableWidgetItem();
setItem(tableRow,tableColumn,tableItem);
}
if (isEnabled ())
::TableWidgetItemSetColor (tableItem, color);
}
else
{
delete tableItem;
}
emit colorChanged (column, color);
}
void kpColorCellsBase::changeEvent( QEvent* event )
{
QTableWidget::changeEvent (event);
if (event->type () != QEvent::EnabledChange)
return;
for (int r = 0; r < rowCount (); r++)
{
for (int c = 0; c < columnCount (); c++)
{
const int index = r * columnCount () + c;
QTableWidgetItem* tableItem = item(r, c);
// See API Doc for this invariant.
Q_ASSERT (!!tableItem == d->colors [index].isValid ());
if (!tableItem)
continue;
QColor color;
if (isEnabled ())
color = d->colors [index];
else
color = palette ().color (backgroundRole ());
::TableWidgetItemSetColor (tableItem, color);
}
}
}
/*void kpColorCellsBase::paintCell( QPainter *painter, int row, int col )
{
painter->setRenderHint( QPainter::Antialiasing , true );
QBrush brush;
int w = 1;
if (shade)
{
qDrawShadePanel( painter, 1, 1, cellWidth()-2,
cellHeight()-2, palette(), true, 1, &brush );
w = 2;
}
QColor color = colors[ row * numCols() + col ];
if (!color.isValid())
{
if (!shade) return;
color = palette().color(backgroundRole());
}
const QRect colorRect( w, w, cellWidth()-w*2, cellHeight()-w*2 );
painter->fillRect( colorRect, color );
if ( row * numCols() + col == selected ) {
painter->setPen( qGray(color.rgb())>=127 ? Qt::black : Qt::white );
painter->drawLine( colorRect.topLeft(), colorRect.bottomRight() );
painter->drawLine( colorRect.topRight(), colorRect.bottomLeft() );
}
}*/
void kpColorCellsBase::resizeEvent( QResizeEvent* e )
{
if (d->cellsResizable)
{
// According to the Qt doc:
// If you need to set the width of a given column to a fixed value, call
// QHeaderView::resizeSection() on the table's {horizontal,vertical}
// header.
// Therefore we iterate over each row and column and set the header section
// size, as the sizeHint does indeed appear to be ignored in favor of a
// minimum size that is larger than what we want.
for ( int index = 0 ; index < columnCount() ; index++ )
horizontalHeader()->resizeSection( index, sizeHintForColumn(index) );
for ( int index = 0 ; index < rowCount() ; index++ )
verticalHeader()->resizeSection( index, sizeHintForRow(index) );
}
else
{
// Update scrollbars if they're forced on by a subclass.
// TODO: Should the d->cellsResizable path (from kdelibs) do this as well?
QTableWidget::resizeEvent (e);
}
}
int kpColorCellsBase::sizeHintForColumn(int /*column*/) const
{
// TODO: Should it be "(width() - frameWidth() * 2) / columnCount()"?
return width() / columnCount() ;
}
int kpColorCellsBase::sizeHintForRow(int /*row*/) const
{
// TODO: Should be "(height() - frameWidth() * 2) / rowCount()"?
return height() / rowCount() ;
}
void kpColorCellsBase::mousePressEvent( QMouseEvent *e )
{
d->inMouse = true;
d->mousePos = e->pos();
}
int kpColorCellsBase::positionToCell(const QPoint &pos, bool ignoreBorders,
bool allowEmptyCell) const
{
//TODO ignoreBorders not yet handled
Q_UNUSED( ignoreBorders )
const int r = indexAt (pos).row (), c = indexAt (pos).column ();
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "r=" << r << "c=" << c;
#endif
if (r == -1 || c == -1)
return -1;
if (!allowEmptyCell && !itemAt(pos))
return -1;
const int cell = r * columnCount() + c;
/*if (!ignoreBorders)
{
int border = 2;
int x = pos.x() - col * cellWidth();
int y = pos.y() - row * cellHeight();
if ( (x < border) || (x > cellWidth()-border) ||
(y < border) || (y > cellHeight()-border))
return -1;
}*/
return cell;
}
void kpColorCellsBase::mouseMoveEvent( QMouseEvent *e )
{
if( !(e->buttons() & Qt::LeftButton)) return;
if(d->inMouse) {
int delay = QApplication::startDragDistance();
if(e->x() > d->mousePos.x()+delay || e->x() < d->mousePos.x()-delay ||
e->y() > d->mousePos.y()+delay || e->y() < d->mousePos.y()-delay){
// Drag color object
int cell = positionToCell(d->mousePos);
if (cell != -1)
{
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "beginning drag from cell=" << cell
<< "color: isValid=" << d->colors [cell].isValid ()
<< " rgba=" << (int *) d->colors [cell].rgba();
#endif
Q_ASSERT (d->colors[cell].isValid());
KColorMimeData::createDrag(d->colors[cell], this)->exec(Qt::CopyAction | Qt::MoveAction);
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "finished drag";
#endif
}
}
}
}
// LOTODO: I'm not quite clear on how the drop actions logic is supposed
// to be done e.g.:
//
// 1. Who is supposed to call setDropAction().
// 2. Which variant of accept(), setAccepted(), acceptProposedAction() etc.
// is supposed to be called to accept a move -- rather than copy --
// action.
//
// Nevertheless, it appears to work -- probably because we restrict
// the non-Qt-default move/swap action to be intrawidget.
static void SetDropAction (QWidget *self, QDropEvent *event)
{
// TODO: Would be nice to default to CopyAction if the destination cell
// is null.
if (event->source () == self && (event->keyboardModifiers () & Qt::ControlModifier) == 0)
event->setDropAction(Qt::MoveAction);
else
event->setDropAction(Qt::CopyAction);
}
void kpColorCellsBase::dragEnterEvent( QDragEnterEvent *event)
{
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "kpColorCellsBase::dragEnterEvent() acceptDrags="
<< d->acceptDrags
<< " canDecode=" << KColorMimeData::canDecode(event->mimeData());
#endif
event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData()));
if (event->isAccepted ())
::SetDropAction (this, event);
}
// Reimplemented to override QTableWidget's override. Else dropping doesn't work.
void kpColorCellsBase::dragMoveEvent (QDragMoveEvent *event)
{
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "kpColorCellsBase::dragMoveEvent() acceptDrags="
<< d->acceptDrags
<< " canDecode=" << KColorMimeData::canDecode(event->mimeData());
#endif
// TODO: Disallow drag that isn't onto a cell.
event->setAccepted( d->acceptDrags && KColorMimeData::canDecode( event->mimeData()));
if (event->isAccepted ())
::SetDropAction (this, event);
}
void kpColorCellsBase::dropEvent( QDropEvent *event)
{
QColor c=KColorMimeData::fromMimeData(event->mimeData());
const int dragSourceCell = event->source () == this ?
positionToCell (d->mousePos, true) :
-1;
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "kpColorCellsBase::dropEvent()"
<< "color: rgba=" << (const int *) c.rgba () << "isValid=" << c.isValid()
<< "source=" << event->source () << "dragSourceCell=" << dragSourceCell;
#endif
if( c.isValid()) {
::SetDropAction (this, event);
int cell = positionToCell(event->pos(), true, true/*allow empty cell*/);
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "\tcell=" << cell;
#endif
// TODO: I believe kdelibs forgets to do this.
if (cell == -1)
return;
// Avoid NOP.
if (cell == dragSourceCell)
return;
QColor destOldColor = d->colors [cell];
setColor(cell,c);
#if DEBUG_KP_COLOR_CELLS_BASE
qCDebug(kpLogColorCollection) << "\tdropAction=" << event->dropAction ()
<< "destOldColor.rgba=" << (const int *) destOldColor.rgba ();
#endif
if (event->dropAction () == Qt::MoveAction && dragSourceCell != -1) {
setColor(dragSourceCell, destOldColor);
}
}
}
void kpColorCellsBase::mouseReleaseEvent( QMouseEvent *e )
{
int cell = positionToCell(d->mousePos);
int currentCell = positionToCell(e->pos());
// If we release the mouse in another cell and we don't have
// a drag we should ignore this event.
if (currentCell != cell)
cell = -1;
if ( (cell != -1) && (d->selected != cell) )
{
d->selected = cell;
const int newRow = cell/columnCount();
const int newColumn = cell%columnCount();
clearSelection(); // we do not want old violet selected cells
item(newRow,newColumn)->setSelected(true);
}
d->inMouse = false;
if (cell != -1)
{
emit colorSelected( cell , color(cell) );
emit colorSelectedWhitButton( cell , color(cell), e->button() );
}
}
void kpColorCellsBase::mouseDoubleClickEvent( QMouseEvent * /*e*/ )
{
int cell = positionToCell(d->mousePos, false, true/*allow empty cell*/);
if (cell != -1)
emit colorDoubleClicked( cell , color(cell) );
}