/*************************************************************************** * Copyright (C) 2005 by S�astien Laot * * slaout@linux62.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "kcolorcombo2.h" //#include "tqeffects.h" //#define DEBUG_COLOR_ARRAY //#define OUTPUT_GIMP_PALETTE #ifdef DEBUG_COLOR_ARRAY #include #include #endif #ifdef OUTPUT_GIMP_PALETTE #include #include #endif /** class KColorPopup: */ const int KColorPopup::MARGIN = 1; const int KColorPopup::FRAME_WIDTH = 1; KColorPopup::KColorPopup(KColorCombo2 *tqparent) : TQWidget(/*tqparent=*/0, /*name=*/0, WType_Popup | WNoAutoErase), m_selector(tqparent) { hide(); setMouseTracking(true); //resize(20, 20); //setWFlags(TQt::WNoAutoErase); } KColorPopup::~KColorPopup() { } #include void KColorPopup::retqlayout() // FIXME: retqlayout should NOT redraw the pixmap! { int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); bool haveDefault = m_selector->defaultColor().isValid(); int width = 2 + MARGIN + (colorWidth + MARGIN) * columnCount; int height = 2 + MARGIN + (colorHeight + MARGIN) * rowCount + (colorHeight + MARGIN); resize(width, height); // Initialize the pixmap: m_pixmap.resize(width, height); TQPainter painter(&m_pixmap); painter.fillRect(0, 0, width, height, KGlobalSettings::baseColor()); painter.setPen(KGlobalSettings::textColor()); painter.drawRect(0, 0, width, height); // Needed to draw: int x, y; TQRect selectionRect; // Draw the color array: for (int i = 0; i < columnCount; ++i) { for (int j = 0; j < rowCount; ++j) { x = 1 + MARGIN + (colorWidth + MARGIN) * i; y = 1 + MARGIN + (colorHeight + MARGIN) * j; if (i == m_selectedColumn && j == m_selectedRow) { selectionRect = TQRect(x - 2, y - 2, colorWidth + 4, colorHeight + 4); painter.fillRect(selectionRect, KGlobalSettings::highlightColor()); } m_selector->drawColorRect(painter, x, y, m_selector->colorAt(i, j), /*isDefault=*/false, colorWidth, colorHeight); } } m_columnOther = (haveDefault ? columnCount / 2 : 0); // "(Default)" is allowed, paint "Other..." on the right int defaultCellWidth = (colorWidth + MARGIN) * m_columnOther; int otherCellWidth = (colorWidth + MARGIN) * (columnCount - m_columnOther); // Draw the "(Default)" and "Other..." colors: y = height - (colorHeight + MARGIN) - 1; TQColor textColor; if (m_selector->defaultColor().isValid()) { x = 1 + MARGIN; if (m_selectedColumn < m_columnOther && rowCount == m_selectedRow) { selectionRect = TQRect(x - 2, y - 2, defaultCellWidth, colorHeight + 4); painter.fillRect(selectionRect, KGlobalSettings::highlightColor()); textColor = KGlobalSettings::highlightedTextColor(); } else textColor = KGlobalSettings::textColor(); m_selector->drawColorRect(painter, x, y, m_selector->defaultColor(), /*isDefault=*/true, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, AlignLeft | AlignVCenter | DontClip, i18n("(Default)")); } x = 1 + MARGIN + m_columnOther * (colorWidth + MARGIN); if (m_selectedColumn >= m_columnOther && rowCount == m_selectedRow) { selectionRect = TQRect(x - 2, y - 2, otherCellWidth, colorHeight + 4); painter.fillRect(selectionRect, KGlobalSettings::highlightColor()); textColor = KGlobalSettings::highlightedTextColor(); } else textColor = KGlobalSettings::textColor(); m_selector->drawColorRect(painter, x, y, m_otherColor, /*isDefault=*/false, colorWidth, colorHeight); painter.setFont(m_selector->font()); painter.setPen(textColor); painter.drawText(x + 2 + colorWidth, y, /*width=*/5000, colorHeight, AlignLeft | AlignVCenter | DontClip, i18n("Other...")); // TQPoint pos = mapFromGlobal(TQCursor::pos()); // painter.drawRect(pos.x(), pos.y(), 5000, 5000); } void KColorPopup::updateCell(int column, int row) { int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); int x = 1 + MARGIN + - 2 + column * (colorWidth + MARGIN); int y = 1 + MARGIN + - 2 + row * (colorHeight + MARGIN); int width = colorWidth + MARGIN; int height = colorHeight + MARGIN; if (row == m_selector->rowCount()) { if (m_selectedColumn < m_columnOther) // The "(Default)" cell: width = (colorWidth + MARGIN) * m_columnOther; else // The "Other..." cell: width = (colorWidth + MARGIN) * (m_selector->columnCount() - m_columnOther); } update(x, y, width, height); } void KColorPopup::doSelection() { m_otherColor = TQColor(); // If the selected color is not the default one, try to find it in the array: if (m_selector->color().isValid()) { bool isInArray = false; for (int column = 0; column < m_selector->columnCount(); ++column) for (int row = 0; row < m_selector->rowCount(); ++row) if (m_selector->color() == m_selector->colorAt(column, row)) { m_selectedColumn = column; m_selectedRow = row; isInArray = true; } // If not found in array, it's another one: if (!isInArray) { m_selectedColumn = m_columnOther; m_selectedRow = m_selector->rowCount(); m_otherColor = m_selector->color(); } // If it's the default one: } else { m_selectedColumn = 0; m_selectedRow = m_selector->rowCount(); } } void KColorPopup::validate() { hide(); close(); if (m_selectedRow != m_selector->rowCount()) // A normal row: m_selector->setColor(m_selector->colorAt(m_selectedColumn, m_selectedRow)); else if (m_selectedColumn < m_columnOther) // The default color: m_selector->setColor(TQColor()); else { // The user want to choose one: TQColor color = m_selector->effectiveColor(); if (KColorDialog::getColor(color, this) == TQDialog::Accepted) m_selector->setColor(color); } } void KColorPopup::mousePressEvent(TQMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < 0 || y < 0 || x >= width() || y >= height()) { hide(); close(); } else validate(); event->accept(); } void KColorPopup::paintEvent(TQPaintEvent *event) { TQPainter painter(this); painter.drawPixmap(0, 0, m_pixmap); painter.setPen(TQt::black); painter.drawRect(event->rect()); } void KColorPopup::mouseMoveEvent(TQMouseEvent *event) { int x = event->pos().x(); int y = event->pos().y(); if (x < FRAME_WIDTH + 2 || y < FRAME_WIDTH + 2 || x > width() - 2 - 2*FRAME_WIDTH || y > height() - 2 - 2*FRAME_WIDTH) return; int colorHeight = m_selector->colorRectHeight(); int colorWidth = m_selector->colorRectWidthForHeight(colorHeight); // int oldSelectedColumn = m_selectedColumn; // int oldSelectedRow = m_selectedRow; m_selectedColumn = (x - FRAME_WIDTH - MARGIN + 2) / (colorWidth + MARGIN); m_selectedRow = (y - FRAME_WIDTH - MARGIN + 2) / (colorHeight + MARGIN); retqlayout(); update(); } void KColorPopup::keyPressEvent(TQKeyEvent *event) { int column = m_selectedColumn; int row = m_selectedRow; int columnCount = m_selector->columnCount(); int rowCount = m_selector->rowCount(); switch (event->key()) { case TQt::Key_Right: if (m_selectedRow != rowCount) // A normal row: column = (column + 1) % columnCount; else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case TQt::Key_Left: if (m_selectedRow != rowCount) { // A normal row: column = (column - 1); if (column < 0) column = columnCount - 1; } else { // The last row, if there are two choices, switch. Else, do nothing: if (m_selector->defaultColor().isValid()) column = (m_selectedColumn < m_columnOther ? m_columnOther : 0); } break; case TQt::Key_Up: row = (row - 1); if (row < 0) row = rowCount; break; case TQt::Key_Down: row = (row + 1) % (rowCount+1); break; case TQt::Key_PageDown: row += 10; if (row > rowCount) row = rowCount; break; case TQt::Key_PageUp: row -= 10; if (row < 0) row = 0; break; case TQt::Key_Home: row = 0; column = 0; break; case TQt::Key_End: row = rowCount; column = columnCount - 1; break; case TQt::Key_Return: validate(); break; default: TQWidget::keyPressEvent(event); } if (row != m_selectedRow || column != m_selectedColumn) { m_selectedRow = row; m_selectedColumn = column; retqlayout(); update(); } } /** Helper function: */ TQColor Tool_mixColors(const TQColor &color1, const TQColor &color2) { TQColor mixedColor; mixedColor.setRgb( (color1.red() + color2.red()) / 2, (color1.green() + color2.green()) / 2, (color1.blue() + color2.blue()) / 2 ); return mixedColor; } /** class KColorCombo2Private */ class KColorCombo2::KColorCombo2Private { }; /** class KColorCombo2: */ /* All code for the popup management (including the constructor, popup() and eventFilter()) * has been copied from the KDateEdit widget (in libkdepim). * * Some other piece of code comes from KColorButton (in libkdeui) to enable color drag, drop, copy and paste. */ KColorCombo2::KColorCombo2(const TQColor &color, const TQColor &defaultColor, TQWidget *tqparent, const char *name) : TQComboBox(/*editable=*/false, tqparent, name), m_color(color), m_defaultColor(defaultColor) { init(); } KColorCombo2::KColorCombo2(const TQColor &color, TQWidget *tqparent, const char *name) : TQComboBox(/*editable=*/false, tqparent, name), m_color(color), m_defaultColor() { init(); } void KColorCombo2::init() { m_discardNextMousePress = false; m_colorArray = 0; d = new KColorCombo2Private(); setDefaultColor(m_defaultColor); insertItem("", /*index=*/0); updateComboBox(); // It need an item of index 0 to exists, so we created it. setAcceptDrops(true); m_popup = new KColorPopup(this); m_popup->installEventFilter(this); // By default, the array is filled with setRainbowPreset(). // But we allocate it on demand (the later as possible) to avoid performances issues if the developer set another array. // However, to keep columnCount() rowCount() const, we define theme here: m_columnCount = 13; m_rowCount = 9; } KColorCombo2::~KColorCombo2() { deleteColorArray(); } void KColorCombo2::setColor(const TQColor &color) { // Do nothing if the color should be set to the default one and there is no such default color allowed: if (!color.isValid() && !m_defaultColor.isValid()) { // kdebug << this::FUNCTION << "Trying to assign the default color (an invalid one) whereas no such default color is allowed"; return; } if (m_color != color) { m_color = color; updateComboBox(); emit changed(color); } } TQColor KColorCombo2::color() const { return m_color; } TQColor KColorCombo2::effectiveColor() const { if (m_color.isValid()) return m_color; else return m_defaultColor; } void KColorCombo2::setRainbowPreset(int colorColumnCount, int lightRowCount, int darkRowCount, bool withGray) { // At least one row and one column: if (colorColumnCount < 1 - (withGray ? 1 : 0)) colorColumnCount = 1 - (withGray ? 1 : 0); if (lightRowCount < 0) lightRowCount = 0; if (darkRowCount < 0) darkRowCount = 0; // Create the array: int columnCount = colorColumnCount + (withGray ? 1 : 0); int rowCount = lightRowCount + 1 + darkRowCount; newColorArray(columnCount, rowCount); // Fill the array: for (int i = 0; i < colorColumnCount; ++i) { int hue = i * 360 / colorColumnCount; // With light colors: for (int j = 1; j <= lightRowCount; ++j) { // Start to 1 because we don't want a row full of white! int saturation = j * 255 / (lightRowCount + 1); setColorAt(i, j - 1, TQColor(hue, saturation, 255, TQColor::Hsv)); } // With pure colors: setColorAt(i, lightRowCount, TQColor(hue, 255, 255, TQColor::Hsv)); // With dark colors: for (int j = 1; j <= darkRowCount; ++j) { int value = 255 - j * 255 / (darkRowCount + 1); setColorAt(i, lightRowCount + j, TQColor(hue, 255, value, TQColor::Hsv)); } } // Fill the gray column: if (withGray) { for (int i = 0; i < rowCount; ++i) { int gray = ( rowCount == 1 ? 128 : 255 - (i * 255 / (rowCount - 1)) ); setColorAt(columnCount-1, i, TQColor(gray, gray, gray)); } } #ifdef DEBUG_COLOR_ARRAY std::cout << "KColorCombo2::setColorPreset" << std::endl; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { int h, s, v; m_colorArray[i][j].getHsv(h, s, v); std::cout << "(" << std::setw(3) << h << "," << std::setw(3) << s << "," << std::setw(3) << v << ") "; //std::cout << colorArray[i][j].name() << " "; } std::cout << std::endl; } #endif #ifdef OUTPUT_GIMP_PALETTE std::cout << "GIMP Palette" << std::endl; for (int j = 0; j < rowCount; ++j) { for (int i = 0; i < columnCount; ++i) { std::cout << std::setw(3) << m_colorArray[i][j].red() << ", " << std::setw(3) << m_colorArray[i][j].green() << ", " << std::setw(3) << m_colorArray[i][j].blue() << std::endl; } } #endif } int KColorCombo2::columnCount() const { return m_columnCount; } int KColorCombo2::rowCount() const { return m_rowCount; } TQColor KColorCombo2::colorAt(int column, int row)/* const*/ { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) return TQColor(); return m_colorArray[column][row]; } TQColor KColorCombo2::defaultColor() const { return m_defaultColor; } void KColorCombo2::newColorArray(int columnCount, int rowCount) { if (columnCount <= 0 || rowCount <= 0) { // kdebug << this::FUNCTION << "Trying to create an empty new color array (with %d columns and %d rows)"; return; } // Delete any previous array (if any): deleteColorArray(); // Create a new array of the wanted dimentions: m_columnCount = columnCount; m_rowCount = rowCount; m_colorArray = new TQColor* [columnCount]; for (int i = 0; i < columnCount; ++i) m_colorArray[i] = new TQColor[rowCount]; m_popup->retqlayout(); } void KColorCombo2::setColorAt(int column, int row, const TQColor &color) { if (!m_colorArray) setRainbowPreset(); if (column < 0 || row < 0 || column >= m_columnCount || row >= m_rowCount) { // kdebug << this::FUNCTION << "Trying to set a color at an invalid index (at column %d and row %d, whereas the array have %d columns and %d rows)"; return; } m_colorArray[column][row] = color; } void KColorCombo2::setDefaultColor(const TQColor &color) { m_defaultColor = color; if (!m_defaultColor.isValid() && !m_color.isValid()) m_color = TQt::white; // FIXME: Use the first one. } TQPixmap KColorCombo2::colorRectPixmap(const TQColor &color, bool isDefault, int width, int height) { // Prepare to draw: TQPixmap pixmap(width, height); TQBitmap tqmask(width, height); TQPainter painter(&pixmap); TQPainter tqmaskPainter(&tqmask); // Draw pixmap: drawColorRect(painter, 0, 0, color, isDefault, width, height); // Draw tqmask (make the four corners transparent): tqmaskPainter.fillRect(0, 0, width, height, TQt::color1); // opaque tqmaskPainter.setPen(TQt::color0); // transparent tqmaskPainter.drawPoint(0, 0); tqmaskPainter.drawPoint(0, height - 1); tqmaskPainter.drawPoint(width - 1, height - 1); tqmaskPainter.drawPoint(width - 1, 0); // Finish: painter.end(); tqmaskPainter.end(); pixmap.setMask(tqmask); return pixmap; } void KColorCombo2::drawColorRect(TQPainter &painter, int x, int y, const TQColor &color, bool isDefault, int width, int height) { // Fill: if (color.isValid()) painter.fillRect(x /*+ 1*/, y /*+ 1*/, width /*- 2*/, height /*- 2*/, color); else { // If it's an invalid color, it's for the "Other..." entry: draw a rainbow. // If it wasn't for the "Other..." entry, the programmer made a fault, so (s)he will be informed about that visually. for (int i = 0; i < width-2; ++i) { int hue = i * 360 / (width-2); for (int j = 0; j < height-2; ++j) { int saturation = 255 - (j * 255 / (height-2)); painter.setPen( TQColor(hue, saturation, /*value=*/255, TQColor::Hsv) ); painter.drawPoint(x + i + 1, y + j + 1); } } } // Stroke: int dontCare, value; color.getHsv(/*hue:*/&dontCare, /*saturation:*/&dontCare, &value); TQColor stroke = (color.isValid() ? color.dark(125) : KGlobalSettings::textColor()); painter.setPen(/*color);//*/stroke); painter.drawLine(x + 1, y, x + width - 2, y); painter.drawLine(x, y + 1, x, y + height - 2); painter.drawLine(x + 1, y + height - 1, x + width - 2, y + height - 1); painter.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2); // Round corners: TQColor antialiasing; if (color.isValid()) { antialiasing = Tool_mixColors(color, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); painter.drawPoint(x + width - 2, y + 1); } else { // The two top corners: antialiasing = Tool_mixColors(TQt::red, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + 1); painter.drawPoint(x + width - 2, y + 1); // The two bottom ones: antialiasing = Tool_mixColors(TQt::white, stroke); painter.setPen(antialiasing); painter.drawPoint(x + 1, y + height - 2); painter.drawPoint(x + width - 2, y + height - 2); } // Mark default color: if (isDefault) { painter.setPen(stroke); painter.drawLine(x + 1, y + height - 2, x + width - 2, y + 1); } } int KColorCombo2::colorRectHeight() const { return (fontMetrics().boundingRect(i18n("(Default)")).height() + 2)*3/2; } int KColorCombo2::colorRectWidthForHeight(int height) const { return height * 14 / 10; // 1.4 times the height, like A4 papers. } void KColorCombo2::deleteColorArray() { if (m_colorArray) { for (int i = 0; i < m_columnCount; ++i) delete[] m_colorArray[i]; delete[] m_colorArray; m_colorArray = 0; } } void KColorCombo2::updateComboBox() { int height = colorRectHeight()*2/3; // fontMetrics().boundingRect(i18n("(Default)")).height() + 2 TQPixmap pixmap = colorRectPixmap(effectiveColor(), !m_color.isValid(), colorRectWidthForHeight(height), height); // TODO: isDefaultColorSelected() changeItem(pixmap, (m_color.isValid() ? "" : i18n("(Default)")), /*index=*/0); } void KColorCombo2::popup() { if (!m_colorArray) setRainbowPreset(); // Compute where to show the popup: TQRect desk = KGlobalSettings::desktopGeometry(this); TQPoint popupPoint = mapToGlobal(TQPoint(0, 0)); int popupHeight = m_popup->tqsizeHint().height(); if (popupPoint.y() + height() + popupHeight > desk.bottom()) popupPoint.setY(popupPoint.y() - popupHeight); else popupPoint.setY(popupPoint.y() + height()); int popupWidth = m_popup->tqsizeHint().width(); if (popupPoint.x() + popupWidth > desk.right()) popupPoint.setX(desk.right() - popupWidth); if (popupPoint.x() < desk.left()) popupPoint.setX(desk.left()); if (popupPoint.y() < desk.top()) popupPoint.setY(desk.top()); // Configure the popup: m_popup->move(popupPoint); //m_popup->setColor(m_color); m_popup->doSelection(); m_popup->retqlayout(); // FIXME: In aboutToShow() ? #if 0 //#ifndef TQT_NO_EFFECTS if (TQApplication::isEffectEnabled(UI_AnimateCombo)) { if (m_popup->y() < mapToGlobal(TQPoint(0,0)).y()) qScrollEffect(m_popup, TQEffects::UpScroll); else qScrollEffect(m_popup); } else #endif m_popup->show(); // The combo box is now shown pressed. Make it show not pressed again // by causing its (invisible) list box to emit a 'selected' signal. // Simulate an Enter to unpress it: TQListBox *lb = listBox(); if (lb) { lb->setCurrentItem(0); TQKeyEvent* keyEvent = new TQKeyEvent(TQEvent::KeyPress, TQt::Key_Enter, 0, 0); TQApplication::postEvent(lb, keyEvent); } } bool KColorCombo2::eventFilter(TQObject */*object*/, TQEvent *event) { TQMouseEvent *mouseEvent; switch (event->type()) { case TQEvent::MouseButtonDblClick: case TQEvent::MouseButtonPress: mouseEvent = (TQMouseEvent*)event; if ( !TQT_TQRECT_OBJECT(m_popup->rect()).tqcontains(mouseEvent->pos()) ) { TQPoint globalPos = m_popup->mapToGlobal(mouseEvent->pos()); if (TQApplication::widgetAt(globalPos, /*child=*/true) == this) { // The popup is being closed by a click on the KColorCombo2 widget. // Avoid popping it up again immediately: m_discardNextMousePress = true; } } break; default: break; } // Don't stop the event being handled further: return false; } void KColorCombo2::mousePressEvent(TQMouseEvent *event) { m_dragStartPos = event->pos(); if (event->button() == Qt::LeftButton && m_discardNextMousePress) m_discardNextMousePress = false; else TQComboBox::mousePressEvent(event); } void KColorCombo2::mouseMoveEvent(TQMouseEvent *event) { if( (event->state() & Qt::LeftButton) && (event->pos() - m_dragStartPos).manhattanLength() > KGlobalSettings::dndEventDelay() ) { // Drag color object: KColorDrag *colorDrag = new KColorDrag(effectiveColor(), this); // Replace the drag pixmap with our own rounded one, at the same position and dimetions: TQPixmap pixmap = colorDrag->pixmap(); pixmap = colorRectPixmap(effectiveColor(), /*isDefault=*/false, pixmap.width(), pixmap.height()); colorDrag->setPixmap(pixmap, colorDrag->pixmapHotSpot()); colorDrag->dragCopy(); //setDown(false); } } void KColorCombo2::dragEnterEvent(TQDragEnterEvent *event) { event->accept(isEnabled() && KColorDrag::canDecode(event)); } void KColorCombo2::dropEvent(TQDropEvent *event) { TQColor color; if (KColorDrag::decode(event, color)) setColor(color); } void KColorCombo2::keyPressEvent(TQKeyEvent *event) { KKey key(event); if (KStdAccel::copy().contains(key)) { TQMimeSource *mime = new KColorDrag(effectiveColor()); TQApplication::tqclipboard()->setData(mime, TQClipboard::Clipboard); } else if (KStdAccel::paste().contains(key)) { TQColor color; KColorDrag::decode(TQApplication::tqclipboard()->data(TQClipboard::Clipboard), color); setColor(color); } else TQComboBox::keyPressEvent(event); } void KColorCombo2::fontChange(const TQFont &oldFont) { // Since the color-rectangle is the same height of the text, we should resize it if the font change: updateComboBox(); TQComboBox::fontChange(oldFont); // To update tqgeometry. } void KColorCombo2::virtual_hook(int /*id*/, void */*data*/) { /* KBASE::virtual_hook(id, data); */ } #include "kcolorcombo2.moc"