diff --git a/lgpl/generic/kpColorCollection.cpp b/lgpl/generic/kpColorCollection.cpp index afa56e2b..56452219 100644 --- a/lgpl/generic/kpColorCollection.cpp +++ b/lgpl/generic/kpColorCollection.cpp @@ -1,537 +1,519 @@ // REFACT0R: Remote open/save file logic is duplicated in kpDocument. // HITODO: Test when remote file support in KDE 4 stabilizes /* This file is part of the KDE libraries Copyright (C) 1999 Waldo Bastian (bastian@kde.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. 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. */ //----------------------------------------------------------------------------- // KDE color collection #define DEBUG_KP_COLOR_COLLECTION 0 #include "kpColorCollection.h" #include "kpUrlFormatter.h" -#include // kdelibs4support +#include +#include #include #include #include "kpLogCategories.h" #include #include #include #include #include #include #include #include struct ColorNode { ColorNode(const QColor &c, const QString &n) : color(c), name(n) {} QColor color; QString name; }; //--------------------------------------------------------------------- Q_LOGGING_CATEGORY(kpLogColorCollection, "kp.colorCollection") //BEGIN kpColorCollectionPrivate class kpColorCollectionPrivate { public: kpColorCollectionPrivate(); kpColorCollectionPrivate(const kpColorCollectionPrivate&); QList colorList; QString name; QString desc; kpColorCollection::Editable editable; }; kpColorCollectionPrivate::kpColorCollectionPrivate() : editable(kpColorCollection::Yes) { } kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p) : colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable) { } //END kpColorCollectionPrivate //--------------------------------------------------------------------- QStringList kpColorCollection::installedCollections() { QStringList paletteList; QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("colors"), QStandardPaths::LocateDirectory); for (const auto &path : paths) { paletteList.append(QDir(path).entryList(QStringList(), QDir::Files)); } return paletteList; } kpColorCollection::kpColorCollection() { d = new kpColorCollectionPrivate(); } kpColorCollection::kpColorCollection(const kpColorCollection &p) { d = new kpColorCollectionPrivate(*p.d); } kpColorCollection::~kpColorCollection() { // Need auto-save? delete d; } static void CouldNotOpenDialog (const QUrl &url, QWidget *parent) { KMessageBox::sorry (parent, i18n ("Could not open color palette \"%1\".", kpUrlFormatter::PrettyFilename (url))); } // TODO: Set d->editable? bool kpColorCollection::open(const QUrl &url, QWidget *parent) { - QString tempPaletteFilePath; - if (url.isEmpty () || !KIO::NetAccess::download (url, tempPaletteFilePath, parent)) - { - #if DEBUG_KP_COLOR_COLLECTION - qCDebug(kpLogColorCollection) << "\tcould not download"; - #endif - ::CouldNotOpenDialog (url, parent); - return false; - } + if (url.isEmpty()) { + return false; + } - // sync: remember to "KIO::NetAccess::removeTempFile (tempPaletteFilePath)" in all exit paths + KIO::StoredTransferJob *job = KIO::storedGet (url); + KJobWidgets::setWindow (job, parent); - QFile paletteFile(tempPaletteFilePath); - if (!paletteFile.exists() || - !paletteFile.open(QIODevice::ReadOnly)) - { - #if DEBUG_KP_COLOR_COLLECTION - qCDebug(kpLogColorCollection) << "\tcould not open qfile"; - #endif - KIO::NetAccess::removeTempFile (tempPaletteFilePath); - ::CouldNotOpenDialog (url, parent); - return false; - } + if (!job->exec ()) + { +#if DEBUG_KP_COLOR_COLLECTION + qCDebug(kpLogColorCollection) << "\tcould not download"; +#endif + ::CouldNotOpenDialog (url, parent); + return false; + } + + const QByteArray &data = job->data(); + QTextStream stream(data); // Read first line // Expected "GIMP Palette" - QString line = QString::fromLocal8Bit(paletteFile.readLine()); + QString line = stream.readLine(); if (line.indexOf(QLatin1String(" Palette")) == -1) { - KIO::NetAccess::removeTempFile (tempPaletteFilePath); KMessageBox::sorry (parent, i18n ("Could not open color palette \"%1\" - unsupported format.\n" "The file may be corrupt.", kpUrlFormatter::PrettyFilename (url))); return false; } QList newColorList; QString newDesc; - while( !paletteFile.atEnd() ) + while( !stream.atEnd() ) { - line = QString::fromLocal8Bit(paletteFile.readLine()); + line = stream.readLine(); if (line[0] == '#') { // This is a comment line line = line.mid(1); // Strip '#' line = line.trimmed(); // Strip remaining white space.. if (!line.isEmpty()) { newDesc += line+'\n'; // Add comment to description } } else { // This is a color line, hopefully line = line.trimmed(); if (line.isEmpty()) continue; int r, g, b; int pos = 0; if (sscanf(line.toLatin1(), "%d %d %d%n", &r, &g, &b, &pos) >= 3) { r = qBound(0, r, 255); g = qBound(0, g, 255); b = qBound(0, b, 255); QString name = line.mid(pos).trimmed(); newColorList.append(ColorNode(QColor(r, g, b), name)); } } } d->colorList = newColorList; d->name.clear (); d->desc = newDesc; - KIO::NetAccess::removeTempFile (tempPaletteFilePath); return true; } static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent) { KMessageBox::sorry (parent, i18n ("Could not open KDE color palette \"%1\".", name)); } bool kpColorCollection::openKDE(const QString &name, QWidget *parent) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "name=" << name; #endif if (name.isEmpty()) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "name.isEmpty"; #endif ::CouldNotOpenKDEDialog (name, parent); return false; } QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, "colors/" + name); if (filename.isEmpty()) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "could not find file"; #endif ::CouldNotOpenKDEDialog (name, parent); return false; } // (this will pop up an error dialog on failure) if (!open (QUrl::fromLocalFile (filename), parent)) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "could not open"; #endif return false; } d->name = name; #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "opened"; #endif return true; } static void CouldNotSaveDialog (const QUrl &url, QWidget *parent) { // TODO: use file.errorString() KMessageBox::error (parent, i18n ("Could not save color palette as \"%1\".", kpUrlFormatter::PrettyFilename (url))); } static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device) { // HITODO: QTextStream can fail but does not report errors. // Bug in KColorCollection too. QTextStream str (device); QString description = d->desc.trimmed(); description = '#'+description.split( '\n', QString::KeepEmptyParts).join(QLatin1String("\n#")); str << "KDE RGB Palette\n"; str << description << "\n"; for (const auto &node : d->colorList) { // Added for KolourPaint. if(!node.color.isValid ()) continue; int r,g,b; node.color.getRgb(&r, &g, &b); str << r << " " << g << " " << b << " " << node.name << "\n"; } str.flush(); } bool -kpColorCollection::saveAs(const QUrl &url, bool showOverwritePrompt, - QWidget *parent) const +kpColorCollection::saveAs(const QUrl &url, QWidget *parent) const { - if (showOverwritePrompt && - KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) - { - int result = KMessageBox::warningContinueCancel (parent, - i18n ("A color palette called \"%1\" already exists.\n" - "Do you want to overwrite it?", - kpUrlFormatter::PrettyFilename (url)), - QString (), - KStandardGuiItem::overwrite ()); - if (result != KMessageBox::Continue) - return false; - } - if (url.isLocalFile ()) { const QString filename = url.toLocalFile (); // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or // else, the QSaveFile destructor will overwrite the file, // , despite the failure. QSaveFile atomicFileWriter (filename); { if (!atomicFileWriter.open (QIODevice::WriteOnly)) { // We probably don't need this as has not been // opened. atomicFileWriter.cancelWriting (); #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\treturning false because could not open QSaveFile" << " error=" << atomicFileWriter.error (); #endif ::CouldNotSaveDialog (url, parent); return false; } // Write to local temporary file. ::SaveToFile (d, &atomicFileWriter); // Atomically overwrite local file with the temporary file // we saved to. if (!atomicFileWriter.commit ()) { atomicFileWriter.cancelWriting (); #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\tcould not close QSaveFile"; #endif ::CouldNotSaveDialog (url, parent); return false; } } // sync QSaveFile.cancelWriting() } // Remote file? else { // Create temporary file that is deleted when the variable goes // out of scope. QTemporaryFile tempFile; if (!tempFile.open ()) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\treturning false because could not open tempFile"; #endif ::CouldNotSaveDialog (url, parent); return false; } // Write to local temporary file. ::SaveToFile (d, &tempFile); // Collect name of temporary file now, as QTemporaryFile::fileName() // stops working after close() is called. const QString tempFileName = tempFile.fileName (); #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\ttempFileName='" << tempFileName << "'"; #endif Q_ASSERT (!tempFileName.isEmpty ()); tempFile.close (); if (tempFile.error () != QFile::NoError) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\treturning false because could not close"; #endif ::CouldNotSaveDialog (url, parent); return false; } // Copy local temporary file to overwrite remote. - // TODO: No one seems to know how to do this atomically - // [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2]. - // At least, fish:// (ssh) is definitely not atomic. - if (!KIO::NetAccess::upload (tempFileName, url, parent)) + KIO::FileCopyJob *job = KIO::file_copy (QUrl::fromLocalFile (tempFileName), + url, + -1, + KIO::Overwrite); + KJobWidgets::setWindow (job, parent); + if (!job->exec ()) { #if DEBUG_KP_COLOR_COLLECTION qCDebug(kpLogColorCollection) << "\treturning false because could not upload"; #endif ::CouldNotSaveDialog (url, parent); return false; } } d->name.clear (); return true; } bool kpColorCollection::saveKDE(QWidget *parent) const { const QString name = d->name; QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "colors/" + name; - const bool ret = saveAs (QUrl::fromLocalFile (filename), false/*no overwite prompt*/, parent); + const bool ret = saveAs (QUrl::fromLocalFile (filename), parent); // (d->name is wiped by saveAs()). d->name = name; return ret; } QString kpColorCollection::description() const { return d->desc; } void kpColorCollection::setDescription(const QString &desc) { d->desc = desc; } QString kpColorCollection::name() const { return d->name; } void kpColorCollection::setName(const QString &name) { d->name = name; } kpColorCollection::Editable kpColorCollection::editable() const { return d->editable; } void kpColorCollection::setEditable(Editable editable) { d->editable = editable; } int kpColorCollection::count() const { return (int) d->colorList.count(); } void kpColorCollection::resize(int newCount) { if (newCount == count()) return; else if (newCount < count()) { d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end()); } else if (newCount > count()) { while(newCount > count()) { const int ret = addColor(QColor(), QString()/*color name*/); Q_ASSERT(ret == count() - 1); } } } kpColorCollection& kpColorCollection::operator=( const kpColorCollection &p) { if (&p == this) return *this; d->colorList = p.d->colorList; d->name = p.d->name; d->desc = p.d->desc; d->editable = p.d->editable; return *this; } QColor kpColorCollection::color(int index) const { if ((index < 0) || (index >= count())) return {}; return d->colorList[index].color; } int kpColorCollection::findColor(const QColor &color) const { for (int i = 0; i < d->colorList.size(); ++i) { if (d->colorList[i].color == color) return i; } return -1; } QString kpColorCollection::name(int index) const { if ((index < 0) || (index >= count())) return {}; return d->colorList[index].name; } QString kpColorCollection::name(const QColor &color) const { return name(findColor(color)); } int kpColorCollection::addColor(const QColor &newColor, const QString &newColorName) { d->colorList.append(ColorNode(newColor, newColorName)); return count() - 1; } int kpColorCollection::changeColor(int index, const QColor &newColor, const QString &newColorName) { if ((index < 0) || (index >= count())) return -1; ColorNode& node = d->colorList[index]; node.color = newColor; node.name = newColorName; return index; } int kpColorCollection::changeColor(const QColor &oldColor, const QColor &newColor, const QString &newColorName) { return changeColor( findColor(oldColor), newColor, newColorName); } diff --git a/lgpl/generic/kpColorCollection.h b/lgpl/generic/kpColorCollection.h index 1e31b568..3760ad51 100644 --- a/lgpl/generic/kpColorCollection.h +++ b/lgpl/generic/kpColorCollection.h @@ -1,264 +1,264 @@ // SYNC: Periodically merge in changes from: // // trunk/KDE/kdelibs/kdeui/colors/kcolorcollection.{h,cpp} // // which this is a fork of. // // Our changes can be merged back into KDE (grep for "Added for KolourPaint" and similar). /* This file is part of the KDE libraries Copyright (C) 1999 Waldo Bastian (bastian@kde.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; version 2 of the License. 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. */ //----------------------------------------------------------------------------- // KDE color collection. #ifndef kpColorCollection_H #define kpColorCollection_H #include #include #include #include #include #include class QUrl; /** * Class for handling color collections ("palettes"). * * This class makes it easy to handle color collections, sometimes referred to * as "palettes". This class can read and write collections from and to a file. * * Collections that are managed by KDE have a non-empty name(). Collections * stored in regular files have an empty name(). * * This class uses the "GIMP" palette file format. * * @author Waldo Bastian (bastian@kde.org), Clarence Dang (dang@kde.org) **/ class KOLOURPAINT_LGPL_EXPORT kpColorCollection { public: /** * Query which KDE color collections are installed. * * @return A list with installed color collection names. */ static QStringList installedCollections(); /** * kpColorCollection constructor * * argument removed for KolourPaint. * Use openKDE() instead, which also has error handling. **/ explicit kpColorCollection(); /** * kpColorCollection copy constructor. **/ kpColorCollection(const kpColorCollection &); /** * kpColorCollection destructor. **/ ~kpColorCollection(); /** * kpColorCollection assignment operator **/ kpColorCollection& operator=( const kpColorCollection &); // On failure, this prints an error dialog and returns false. // On success, it sets the name() to an empty string and returns true. // // Added for KolourPaint. bool open(const QUrl &url, QWidget *parent); // Same as open() but is given the name of a KDE palette, not a filename. // // @param name The name of collection as returned by installedCollections(). // name() is set to this. // // Added for KolourPaint. bool openKDE(const QString &name, QWidget *parent); // On failure, this prints an error dialog and returns false. // If the user cancels any presented overwrite dialog, it also returns false. // On success, it returns true. // // The file can be overwritten without displaying any warning dialog, if // is set to false. // // name() is set to an empty string. // // Added for KolourPaint. - bool saveAs(const QUrl &url, bool showOverwritePrompt, QWidget *parent) const; + bool saveAs(const QUrl &url, QWidget *parent) const; /** * Save the collection to the KDE-local store * (usually $HOME/.kde/share/config/colors) using name(). * * @return 'true' if successful * * Renamed from save() for KolourPaint. **/ bool saveKDE(QWidget *parent) const; /** * Get the description of the collection. * @return the description of the collection. **/ QString description() const; /** * Set the description of the collection. * @param desc the new description **/ void setDescription(const QString &desc); /** * Get the name of the collection. * @return the name of the collection **/ QString name() const; /** * Set the name of the collection. * @param name the name of the collection **/ void setName(const QString &name); /** * Used to specify whether a collection may be edited. * @see editable() * @see setEditable() */ enum Editable { Yes, ///< Collection may be edited No, ///< Collection may not be edited Ask ///< Ask user before editing }; /** * Returns whether the collection may be edited. * @return the state of the collection **/ Editable editable() const; /** * Change whether the collection may be edited. * @param editable the state of the collection **/ void setEditable(Editable editable); /** * Return the number of colors in the collection. * @return the number of colors **/ int count() const; /** * Adds invalid colors or removes colors so that there will be @p newCount * colors in the color collection. * * @param target number of colors * * Added for KolourPaint. */ void resize(int newCount); /** * Find color by index. * @param index the index of the desired color * @return The @p index -th color of the collection, null if not found. **/ QColor color(int index) const; /** * Find index by @p color. * @param color the color to find * @return The index of the color in the collection or -1 if the * color is not found. **/ int findColor(const QColor &color) const; /** * Find color name by @p index. * @param index the index of the color * @return The name of the @p index -th color. * Note that not all collections have named the colors. Null is * returned if the color does not exist or has no name. **/ QString name(int index) const; /** * Find color name by @p color. * @return The name of color according to this collection. * Note that not all collections have named the colors. * Note also that each collection can give the same color * a different name. **/ QString name(const QColor &color) const; /** * Add a color. * @param newColor The color to add. * @param newColorName The name of the color, null to remove * the name. * @return The index of the added color. **/ int addColor(const QColor &newColor, const QString &newColorName = QString()); /** * Change a color. * @param index Index of the color to change * @param newColor The new color. * @param newColorName The new color name, null to remove * the name. * @return The index of the new color or -1 if the color couldn't * be changed. **/ int changeColor(int index, const QColor &newColor, const QString &newColorName = QString()); /** * Change a color. * @param oldColor The original color * @param newColor The new color. * @param newColorName The new color name, null to remove * the name. * @return The index of the new color or -1 if the color couldn't * be changed. **/ int changeColor(const QColor &oldColor, const QColor &newColor, const QString &newColorName = QString()); private: class kpColorCollectionPrivate *d; }; #endif // kpColorCollection_H diff --git a/mainWindow/kpMainWindow_Colors.cpp b/mainWindow/kpMainWindow_Colors.cpp index c586ced7..e9b235db 100644 --- a/mainWindow/kpMainWindow_Colors.cpp +++ b/mainWindow/kpMainWindow_Colors.cpp @@ -1,494 +1,495 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "kpMainWindow.h" #include "kpMainWindowPrivate.h" #include "widgets/kpColorCells.h" #include "lgpl/generic/kpColorCollection.h" #include "lgpl/generic/kpUrlFormatter.h" #include "widgets/toolbars/kpColorToolBar.h" #include #include #include #include #include "kpLogCategories.h" #include #include //--------------------------------------------------------------------- static QStringList KDEColorCollectionNames () { return kpColorCollection::installedCollections (); } //--------------------------------------------------------------------- // private void kpMainWindow::setupColorsMenuActions () { KActionCollection *ac = actionCollection (); d->actionColorsDefault = ac->addAction (QStringLiteral("colors_default")); d->actionColorsDefault->setText (i18n ("Use KolourPaint Defaults")); connect (d->actionColorsDefault, &QAction::triggered, this, &kpMainWindow::slotColorsDefault); d->actionColorsKDE = ac->add (QStringLiteral("colors_kde")); d->actionColorsKDE->setText (i18nc ("@item:inmenu colors", "Use KDE's")); // TODO: Will this slot be called spuriously if there are no colors // installed? connect (d->actionColorsKDE, static_cast(&KSelectAction::triggered), this, &kpMainWindow::slotColorsKDE); for (const auto &colName : ::KDEColorCollectionNames ()) { d->actionColorsKDE->addAction (colName); } d->actionColorsOpen = ac->addAction (QStringLiteral("colors_open")); d->actionColorsOpen->setText (i18nc ("@item:inmenu colors", "&Open...")); connect (d->actionColorsOpen, &QAction::triggered, this, &kpMainWindow::slotColorsOpen); d->actionColorsReload = ac->addAction (QStringLiteral("colors_reload")); d->actionColorsReload->setText (i18nc ("@item:inmenu colors", "Reloa&d")); connect (d->actionColorsReload, &QAction::triggered, this, &kpMainWindow::slotColorsReload); d->actionColorsSave = ac->addAction (QStringLiteral("colors_save")); d->actionColorsSave->setText (i18nc ("@item:inmenu colors", "&Save")); connect (d->actionColorsSave, &QAction::triggered, this, &kpMainWindow::slotColorsSave); d->actionColorsSaveAs = ac->addAction (QStringLiteral("colors_save_as")); d->actionColorsSaveAs->setText (i18nc ("@item:inmenu colors", "Save &As...")); connect (d->actionColorsSaveAs, &QAction::triggered, this, &kpMainWindow::slotColorsSaveAs); d->actionColorsAppendRow = ac->addAction (QStringLiteral("colors_append_row")); d->actionColorsAppendRow->setText (i18nc ("@item:inmenu colors", "Add Row")); connect (d->actionColorsAppendRow, &QAction::triggered, this, &kpMainWindow::slotColorsAppendRow); d->actionColorsDeleteRow = ac->addAction (QStringLiteral("colors_delete_row")); d->actionColorsDeleteRow->setText (i18nc ("@item:inmenu colors", "Delete Last Row")); connect (d->actionColorsDeleteRow, &QAction::triggered, this, &kpMainWindow::slotColorsDeleteRow); enableColorsMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::createColorBox () { d->colorToolBar = new kpColorToolBar (i18n ("Color Box"), this); // (needed for QMainWindow::saveState()) d->colorToolBar->setObjectName ( QStringLiteral("Color Box" )); connect (colorCells (), &kpColorCells::rowCountChanged, this, &kpMainWindow::slotUpdateColorsDeleteRowActionEnabled); } //--------------------------------------------------------------------- // private void kpMainWindow::enableColorsMenuDocumentActions (bool enable) { d->actionColorsDefault->setEnabled (enable); d->actionColorsKDE->setEnabled (enable); d->actionColorsOpen->setEnabled (enable); d->actionColorsReload->setEnabled (enable); d->actionColorsSave->setEnabled (enable); d->actionColorsSaveAs->setEnabled (enable); d->actionColorsAppendRow->setEnabled (enable); d->colorMenuDocumentActionsEnabled = enable; slotUpdateColorsDeleteRowActionEnabled (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotUpdateColorsDeleteRowActionEnabled () { // Currently, this is always enabled since kpColorCells guarantees that // there will be at least one row of cells (which might all be of the // invalid color). // // But this method is left here for future extensibility. d->actionColorsDeleteRow->setEnabled ( d->colorMenuDocumentActionsEnabled && (colorCells ()->rowCount () > 0)); } //--------------------------------------------------------------------- // Used in 2 situations: // // 1. User opens a color without using the "Use KDE's" submenu. // 2. User attempts to open a color using the "Use KDE's" submenu but the // opening fails. // // TODO: Maybe we could put the 3 actions (for different ways of opening // colors) in an exclusive group -- this might elminate the need for // this hack. // // private void kpMainWindow::deselectActionColorsKDE () { d->actionColorsKDE->setCurrentItem (-1); } //--------------------------------------------------------------------- // private bool kpMainWindow::queryCloseColors () { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::queryCloseColors() colorCells.modified=" << colorCells ()->isModified (); #endif toolEndShape (); if (!colorCells ()->isModified ()) { return true; // ok to close } int result = KMessageBox::Cancel; if (!colorCells ()->url ().isEmpty ()) { result = KMessageBox::warningYesNoCancel (this, i18n ("The color palette \"%1\" has been modified.\n" "Do you want to save it?", kpUrlFormatter::PrettyFilename (colorCells ()->url ())), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { result = KMessageBox::warningYesNoCancel (this, i18n ("The KDE color palette \"%1\" has been modified.\n" "Do you want to save it to a file?", name), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } else { result = KMessageBox::warningYesNoCancel (this, i18n ("The default color palette has been modified.\n" "Do you want to save it to a file?"), QString ()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); } } switch (result) { case KMessageBox::Yes: return slotColorsSave (); // close only if save succeeds case KMessageBox::No: return true; // close without saving default: return false; // don't close current doc } } //--------------------------------------------------------------------- // private void kpMainWindow::openDefaultColors () { colorCells ()->setColorCollection ( kpColorCells::DefaultColorCollection ()); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsDefault () { // Call just in case. toolEndShape (); if (!queryCloseColors ()) { return; } openDefaultColors (); deselectActionColorsKDE (); } //--------------------------------------------------------------------- // private bool kpMainWindow::openKDEColors (const QString &name) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::openKDEColors(" << name << ")"; #endif kpColorCollection colorCol; if (colorCol.openKDE (name, this)) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "opened"; #endif colorCells ()->setColorCollection (colorCol); return true; } else { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "failed to open"; #endif return false; } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsKDE () { // Call in case an error dialog appears. toolEndShape (); const int curItem = d->actionColorsKDE->currentItem (); if (!queryCloseColors ()) { deselectActionColorsKDE (); return; } // queryCloseColors() calls slotColorSave(), which can call // slotColorSaveAs(), which can call deselectActionColorsKDE(). d->actionColorsKDE->setCurrentItem (curItem); const QStringList colNames = ::KDEColorCollectionNames (); const int selected = d->actionColorsKDE->currentItem (); Q_ASSERT (selected >= 0 && selected < colNames.size ()); if (!openKDEColors (colNames [selected])) { deselectActionColorsKDE (); } } //--------------------------------------------------------------------- // private bool kpMainWindow::openColors (const QUrl &url) { return colorCells ()->openColorCollection (url); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsOpen () { // Call due to dialog. toolEndShape (); QFileDialog fd(this); fd.setDirectoryUrl(colorCells ()->url()); fd.setWindowTitle(i18nc ("@title:window", "Open Color Palette")); if (fd.exec ()) { if (!queryCloseColors ()) { return; } QList selected = fd.selectedUrls(); if ( selected.count() && openColors(selected[0]) ) { deselectActionColorsKDE(); } } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsReload () { toolEndShape (); if (colorCells ()->isModified ()) { int result = KMessageBox::Cancel; if (!colorCells ()->url ().isEmpty ()) { result = KMessageBox::warningContinueCancel (this, i18n ("The color palette \"%1\" has been modified.\n" "Reloading will lose all changes since you last saved it.\n" "Are you sure?", kpUrlFormatter::PrettyFilename (colorCells ()->url ())), QString ()/*caption*/, KGuiItem(i18n ("&Reload"))); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { result = KMessageBox::warningContinueCancel (this, i18n ("The KDE color palette \"%1\" has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?", colorCells ()->colorCollection ()->name ()), QString ()/*caption*/, KGuiItem (i18n ("&Reload"))); } else { result = KMessageBox::warningContinueCancel (this, i18n ("The default color palette has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?"), QString ()/*caption*/, KGuiItem (i18n ("&Reload"))); } } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "result=" << result << "vs KMessageBox::Continue" << KMessageBox::Continue; #endif if (result != KMessageBox::Continue) { return; } } if (!colorCells ()->url ().isEmpty ()) { openColors (colorCells ()->url ()); } else { const QString name = colorCells ()->colorCollection ()->name (); if (!name.isEmpty ()) { openKDEColors (name); } else { openDefaultColors (); } } } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotColorsSave () { // Call due to dialog. toolEndShape (); if (colorCells ()->url ().isEmpty ()) { return slotColorsSaveAs (); } return colorCells ()->saveColorCollection (); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotColorsSaveAs () { // Call due to dialog. toolEndShape (); QFileDialog fd(this); fd.setDirectoryUrl(colorCells ()->url()); fd.setWindowTitle(i18n("Save Color Palette As")); fd.setAcceptMode(QFileDialog::AcceptSave); + // Note that QFileDialog takes care of asking the user to confirm overwriting. if (fd.exec ()) { QList selected = fd.selectedUrls(); if ( !selected.count() || !colorCells ()->saveColorCollectionAs(selected[0]) ) { return false; } // We're definitely using our own color collection now. deselectActionColorsKDE (); return true; } return false; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsAppendRow () { // Call just in case. toolEndShape (); kpColorCells *colorCells = d->colorToolBar->colorCells (); colorCells->appendRow (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotColorsDeleteRow () { // Call just in case. toolEndShape (); kpColorCells *colorCells = d->colorToolBar->colorCells (); colorCells->deleteLastRow (); } diff --git a/widgets/kpColorCells.cpp b/widgets/kpColorCells.cpp index 3bba3d36..36bf1ccb 100644 --- a/widgets/kpColorCells.cpp +++ b/widgets/kpColorCells.cpp @@ -1,604 +1,604 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_COLOR_CELLS 0 #include "widgets/kpColorCells.h" #include "imagelib/kpColor.h" #include "lgpl/generic/kpColorCollection.h" #include "widgets/kpDefaultColorCollection.h" #include "kpLogCategories.h" #include #include #include #include #include //--------------------------------------------------------------------- // // Table Geometry // // The number of columns that the table normally has. const int TableDefaultNumColumns = 11; const int TableDefaultWidth = ::TableDefaultNumColumns * 26; const int TableDefaultHeight = 52; static int TableNumColumns (const kpColorCollection &colorCol) { if (colorCol.count () == 0) { return 0; } return ::TableDefaultNumColumns; } static int TableNumRows (const kpColorCollection &colorCol) { const int cols = ::TableNumColumns (colorCol); if (cols == 0) { return 0; } return (colorCol.count () + (cols - 1)) / cols; } static int TableCellWidth (const kpColorCollection &colorCol) { Q_UNUSED (colorCol); return ::TableDefaultWidth / ::TableDefaultNumColumns; } static int TableCellHeight (const kpColorCollection &colorCol) { if (::TableNumRows (colorCol) <= 2) { return ::TableDefaultHeight / 2; } return ::TableDefaultHeight / 3; } // // kpColorCells // struct kpColorCellsPrivate { Qt::Orientation orientation{}; // REFACTOR: This is data duplication with kpColorCellsBase::color[]. // We've probably forgotten to synchronize them in some points. // // Calls to kpColorCellsBase::setColor() (which also come from // kpColorCellsBase itself) will automatically update both // kpColorCellsBase::d->color[] and the table cells. setColor() emits // colorChanged(), which is caught by our slotColorChanged(), // which synchronizes this color collection and updates the modified flag. // // Avoid calling our grandparent's, QTableWidget's, mutating methods as we // don't override enough of them, to fire signals that we can catch to update // this color collection. // // If you modify this color collection directly (e.g. in setColorCollection(), // openColorCollection(), appendRow(), deleteLastRow(), ...), you must work // the other way and call makeCellsMatchColorCollection() to synchronize // kpColorCellsBase::d->color[] and the table cells. You still need to update // the modified flag. kpColorCollection colorCol; QUrl url; bool isModified{}; bool blockColorChangedSig{}; }; //--------------------------------------------------------------------- kpColorCells::kpColorCells (QWidget *parent, Qt::Orientation o) : kpColorCellsBase (parent, 0/*rows for now*/, 0/*cols for now*/), d (new kpColorCellsPrivate ()) { d->orientation = o; d->isModified = false; d->blockColorChangedSig = false; // When a text box is active, clicking to change the background color // should not move the keyboard focus away from the text box. setFocusPolicy (Qt::TabFocus); setShading (false); // no 3D look setAcceptDrops (true); setAcceptDrags (true); setCellsResizable (false); if (o == Qt::Horizontal) { // Reserve enough room for the default color collection's cells _and_ // a vertical scrollbar, which only appears when it's required. // This ensures that if the vertical scrollbar appears, it does not obscure // any cells or require the addition of a horizontal scrollbar, which would // look ugly and take even more precious room. // // We do not dynamically reserve room based on the actual number of rows // of cells, as that would make our containing widgets too big. setMinimumSize (::TableDefaultWidth + frameWidth () * 2 + verticalScrollBar()->sizeHint().width(), ::TableDefaultHeight + frameWidth () * 2); } else { Q_ASSERT (!"implemented"); } setVerticalScrollBarPolicy (Qt::ScrollBarAsNeeded); // The default QTableWidget policy of QSizePolicy::Expanding forces our // containing widgets to get too big. Override it. setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum); connect (this, &kpColorCells::colorSelectedWhitButton, this, &kpColorCells::slotColorSelected); connect (this, &kpColorCells::colorDoubleClicked, this, &kpColorCells::slotColorDoubleClicked); connect (this, &kpColorCells::colorChanged, this, &kpColorCells::slotColorChanged); setColorCollection (DefaultColorCollection ()); setWhatsThis ( i18n ( "" "

To select the foreground color that tools use to draw," " left-click on a filled-in color cell." " To select the background color, right-click instead.

" "

To change the color of a color cell itself, double-click on it.

" "

You can also swap the color of a filled-in cell with any other" " cell using drag and drop." " Also, if you hold down the Ctrl key, the destination" " cell's color will be" " overwritten, instead of being swapped with the color of the source cell.

" "
")); } //--------------------------------------------------------------------- kpColorCells::~kpColorCells () { delete d; } //--------------------------------------------------------------------- // public static kpColorCollection kpColorCells::DefaultColorCollection () { return kpDefaultColorCollection (); } //--------------------------------------------------------------------- // public Qt::Orientation kpColorCells::orientation () const { return d->orientation; } //--------------------------------------------------------------------- // public void kpColorCells::setOrientation (Qt::Orientation o) { d->orientation = o; makeCellsMatchColorCollection (); } //--------------------------------------------------------------------- // protected // OPT: Find out why this is being called multiple times on startup. void kpColorCells::makeCellsMatchColorCollection () { int c, r; if (orientation () == Qt::Horizontal) { c = ::TableNumColumns (d->colorCol); r = ::TableNumRows (d->colorCol); } else { c = ::TableNumRows (d->colorCol); r = ::TableNumColumns (d->colorCol); } #if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::makeCellsMatchColorCollection():" << "r=" << r << "c=" << c; qCDebug(kpLogWidgets) << "verticalScrollBar=" << verticalScrollBar () << " sizeHint=" << (verticalScrollBar () ? verticalScrollBar ()->sizeHint () : QSize (-12, -34)); #endif // Delete all cell widgets. This ensures that there will be no left-over // cell widgets, for the colors in the new color collection that are // actually invalid (which should not have cell widgets). clearContents (); setRowCount (r); setColumnCount (c); int CellWidth = ::TableCellWidth (d->colorCol), CellHeight = ::TableCellHeight (d->colorCol); // TODO: Take a screenshot of KolourPaint, magnify it and you'll find the // cells don't have exactly the sizes requested here. e.g. the // top row of cells is 1 pixel shorter than the bottom row. There // are probably other glitches. for (int y = 0; y < r; y++) { setRowHeight (y, CellHeight); } for (int x = 0; x < c; x++) { setColumnWidth (x, CellWidth); } const bool oldBlockColorChangedSig = d->blockColorChangedSig; d->blockColorChangedSig = true; // The last "(rowCount() * columnCount()) - d->colorCol.count()" cells // will be empty because we did not initialize them. for (int i = 0; i < d->colorCol.count (); i++) { int y, x; int pos; if (orientation () == Qt::Horizontal) { y = i / c; x = i % c; pos = i; } else { y = i % r; x = i / r; // int x = c - 1 - i / r; pos = y * c + x; } #if DEBUG_KP_COLOR_CELLS && 0 qCDebug(kpLogWidgets) << "\tSetting cell " << i << ": y=" << y << " x=" << x << " pos=" << pos; qCDebug(kpLogWidgets) << "\t\tcolor=" << (int *) d->colorCol.color (i).rgba() << "isValid=" << d->colorCol.color (i).isValid (); #endif // (color may be invalid resulting in a hole in the middle of the table) setColor (pos, d->colorCol.color (i)); //this->setToolTip( cellGeometry (y, x), colors [i].name ()); } d->blockColorChangedSig = oldBlockColorChangedSig; } //--------------------------------------------------------------------- bool kpColorCells::isModified () const { return d->isModified; } //--------------------------------------------------------------------- void kpColorCells::setModified (bool yes) { #if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::setModified(" << yes << ")"; #endif if (yes == d->isModified) { return; } d->isModified = yes; emit isModifiedChanged (yes); } //--------------------------------------------------------------------- void kpColorCells::setModified () { setModified (true); } //--------------------------------------------------------------------- QUrl kpColorCells::url () const { return d->url; } //--------------------------------------------------------------------- QString kpColorCells::name () const { return d->colorCol.name (); } //--------------------------------------------------------------------- const kpColorCollection *kpColorCells::colorCollection () const { return &d->colorCol; } //--------------------------------------------------------------------- void kpColorCells::ensureHaveAtLeastOneRow () { if (d->colorCol.count () == 0) { d->colorCol.resize (::TableDefaultNumColumns); } } //--------------------------------------------------------------------- void kpColorCells::setColorCollection (const kpColorCollection &colorCol, const QUrl &url) { d->colorCol = colorCol; ensureHaveAtLeastOneRow (); d->url = url; setModified (false); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); emit urlChanged (d->url); emit nameChanged (name ()); } //--------------------------------------------------------------------- bool kpColorCells::openColorCollection (const QUrl &url) { // (this will pop up an error dialog on failure) if (d->colorCol.open (url, this)) { ensureHaveAtLeastOneRow (); d->url = url; setModified (false); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); emit urlChanged (d->url); emit nameChanged (name ()); return true; } return false; } //--------------------------------------------------------------------- bool kpColorCells::saveColorCollectionAs (const QUrl &url) { // (this will pop up an error dialog on failure) - if (d->colorCol.saveAs (url, true/*show overwrite prompt*/, this)) + if (d->colorCol.saveAs (url, this)) { d->url = url; setModified (false); emit urlChanged (d->url); return true; } return false; } //--------------------------------------------------------------------- bool kpColorCells::saveColorCollection () { // (this will pop up an error dialog on failure) - if (d->colorCol.saveAs (d->url, false/*no overwrite prompt*/, this)) + if (d->colorCol.saveAs (d->url, this)) { setModified (false); return true; } return false; } //--------------------------------------------------------------------- void kpColorCells::appendRow () { // This is the easiest implementation: change the color collection // and then synchronize the table cells. The other way is to call // setRowCount() and then, synchronize the color collection. const int targetNumCells = (rowCount () + 1) * ::TableDefaultNumColumns; d->colorCol.resize (targetNumCells); setModified (true); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); } //--------------------------------------------------------------------- void kpColorCells::deleteLastRow () { // This is the easiest implementation: change the color collection // and then synchronize the table cells. The other way is to call // setRowCount() and then, synchronize the color collection. const int targetNumCells = qMax (0, (rowCount () - 1) * ::TableDefaultNumColumns); d->colorCol.resize (targetNumCells); // If there was only one row of colors to start with, the effect of this // line (after the above resize()) is to change that row to a row of // invalid colors. ensureHaveAtLeastOneRow (); setModified (true); makeCellsMatchColorCollection (); emit rowCountChanged (rowCount ()); } //--------------------------------------------------------------------- // protected virtual [base QWidget] void kpColorCells::contextMenuEvent (QContextMenuEvent *e) { // Eat right-mouse press to prevent it from getting to the toolbar. e->accept (); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorSelected (int cell, const QColor &color, Qt::MouseButton button) { #if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "kpColorCells::slotColorSelected(cell=" << cell << ") mouseButton = " << button << " rgb=" << (int *) color.rgba(); #else Q_UNUSED (cell); #endif if (button == Qt::LeftButton) { emit foregroundColorChanged (kpColor (color.rgba())); } else if (button == Qt::RightButton) { emit backgroundColorChanged (kpColor (color.rgba())); } // REFACTOR: Make selectedness configurable inside kpColorCellsBase? // // Deselect the selected cell (selected by above kpColorCellsBase::mouseReleaseEvent()). // KolourPaint's palette has no concept of a current cell/color: you can // pick a color but you can't mark a cell as selected. In any case, a // selected cell would be rendered as violet, which would ruin the cell. // // setSelectionMode (kpColorCellsBase::NoSelection); does not work so we // clearSelection(). I think setSelectionMode() concerns when the user // directly selects a cell - not when kpColorCellsBase::mouseReleaseEvent() // selects a cell programmatically. clearSelection (); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorDoubleClicked (int cell, const QColor &) { QColorDialog dialog(this); dialog.setCurrentColor(kpColorCellsBase::color(cell)); dialog.setOptions(QColorDialog::ShowAlphaChannel); if ( dialog.exec() == QDialog::Accepted ) setColor (cell, dialog.currentColor()); } //--------------------------------------------------------------------- // protected slot void kpColorCells::slotColorChanged (int cell, const QColor &color) { #if DEBUG_KP_COLOR_CELLS qCDebug(kpLogWidgets) << "cell=" << cell << "color=" << (const int *) color.rgba() << "d->colorCol.count()=" << d->colorCol.count (); #endif if (d->blockColorChangedSig) { return; } // Cater for adding new colors to the end. if (cell >= d->colorCol.count ()) { d->colorCol.resize (cell + 1); } // TODO: We lose color names on a color swap (during drag-and-drop). const int ret = d->colorCol.changeColor (cell, color, QString ()/*color name*/); Q_ASSERT (ret == cell); setModified (true); }