File indexing completed on 2024-05-12 04:21:20

0001 
0002 // REFACT0R: Remote open/save file logic is duplicated in kpDocument.
0003 // HITODO: Test when remote file support in KDE 4 stabilizes
0004 
0005 /* This file is part of the KDE libraries
0006     Copyright (C) 1999 Waldo Bastian (bastian@kde.org)
0007     Copyright (C) 2007 Clarence Dang (dang@kde.org)
0008 
0009     This library is free software; you can redistribute it and/or
0010     modify it under the terms of the GNU Library General Public
0011     License as published by the Free Software Foundation; either
0012     version 2 of the License.
0013 
0014     This library is distributed in the hope that it will be useful,
0015     but WITHOUT ANY WARRANTY; without even the implied warranty of
0016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017     Library General Public License for more details.
0018 
0019     You should have received a copy of the GNU Library General Public License
0020     along with this library; see the file COPYING.LIB.  If not, write to
0021     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0022     Boston, MA 02110-1301, USA.
0023 */
0024 //-----------------------------------------------------------------------------
0025 // KDE color collection
0026 
0027 #define DEBUG_KP_COLOR_COLLECTION 0
0028 
0029 #include "kpColorCollection.h"
0030 
0031 #include "kpUrlFormatter.h"
0032 
0033 #include <KJobWidgets>
0034 #include <KIO/StoredTransferJob>
0035 #include <KIO/FileCopyJob>
0036 #include <KLocalizedString>
0037 #include <KMessageBox>
0038 #include "kpLogCategories.h"
0039 
0040 #include <QDir>
0041 #include <QFile>
0042 #include <QSaveFile>
0043 #include <QStandardPaths>
0044 #include <QTemporaryFile>
0045 #include <QTextStream>
0046 #include <QUrl>
0047 
0048 struct ColorNode
0049 {
0050     ColorNode(const QColor &c, const QString &n)
0051         : color(c), name(n) {}
0052 
0053     QColor color;
0054     QString name;
0055 };
0056 
0057 //---------------------------------------------------------------------
0058 
0059 Q_LOGGING_CATEGORY(kpLogColorCollection,           "kp.colorCollection")
0060 
0061 //BEGIN kpColorCollectionPrivate
0062 class kpColorCollectionPrivate
0063 {
0064 public:
0065     kpColorCollectionPrivate();
0066     kpColorCollectionPrivate(const kpColorCollectionPrivate&);
0067 
0068     QList<ColorNode> colorList;
0069     QString name;
0070     QString desc;
0071     kpColorCollection::Editable editable;
0072 };
0073 
0074 kpColorCollectionPrivate::kpColorCollectionPrivate()
0075   : editable(kpColorCollection::Yes)
0076 {
0077 }
0078 
0079 kpColorCollectionPrivate::kpColorCollectionPrivate(const kpColorCollectionPrivate& p)
0080     : colorList(p.colorList), name(p.name), desc(p.desc), editable(p.editable)
0081 {
0082 }
0083 //END kpColorCollectionPrivate
0084 
0085 //---------------------------------------------------------------------
0086 
0087 QStringList
0088 kpColorCollection::installedCollections()
0089 {
0090   QStringList paletteList;
0091 
0092   QStringList paths = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("colors"),
0093                                                 QStandardPaths::LocateDirectory);
0094   for (const auto &path : paths) {
0095     paletteList.append(QDir(path).entryList(QStringList(), QDir::Files));
0096   }
0097 
0098   return paletteList;
0099 }
0100 
0101 kpColorCollection::kpColorCollection()
0102 {
0103   d = new kpColorCollectionPrivate();
0104 }
0105 
0106 kpColorCollection::kpColorCollection(const kpColorCollection &p)
0107 {
0108     d = new kpColorCollectionPrivate(*p.d);
0109 }
0110 
0111 kpColorCollection::~kpColorCollection()
0112 {
0113   // Need auto-save?
0114     delete d;
0115 }
0116 
0117 static void CouldNotOpenDialog (const QUrl &url, QWidget *parent)
0118 {
0119      KMessageBox::error (parent,
0120         i18n ("Could not open color palette \"%1\".",
0121               kpUrlFormatter::PrettyFilename (url)));
0122 }
0123 
0124 // TODO: Set d->editable?
0125 bool
0126 kpColorCollection::open(const QUrl &url, QWidget *parent)
0127 {
0128     if (url.isEmpty()) {
0129         return false;
0130     }
0131 
0132     KIO::StoredTransferJob *job = KIO::storedGet (url);
0133     KJobWidgets::setWindow (job, parent);
0134 
0135     if (!job->exec ())
0136     {
0137 #if DEBUG_KP_COLOR_COLLECTION
0138         qCDebug(kpLogColorCollection) << "\tcould not download";
0139 #endif
0140         ::CouldNotOpenDialog (url, parent);
0141         return false;
0142     }
0143 
0144   const QByteArray &data = job->data();
0145   QTextStream stream(data);
0146 
0147   // Read first line
0148   // Expected "GIMP Palette" or "KDE RGB Palette" or "KDE RGBA Palette"
0149   QString line = stream.readLine();
0150   if (line.indexOf(QLatin1String(" Palette")) == -1)
0151   {
0152      KMessageBox::error (parent,
0153         i18n ("Could not open color palette \"%1\" - unsupported format.\n"
0154               "The file may be corrupt.",
0155               kpUrlFormatter::PrettyFilename (url)));
0156      return false;
0157   }
0158 
0159   bool hasAlpha = line == QLatin1String("KDE RGBA Palette");   // new format includes alpha
0160 
0161   QList <ColorNode> newColorList;
0162   QString newDesc;
0163 
0164   while( !stream.atEnd() )
0165   {
0166      line = stream.readLine();
0167      if ( !line.isEmpty() && (line[0] == QLatin1Char('#')) )
0168      {
0169         // This is a comment line
0170         line = line.mid(1); // Strip '#'
0171         line = line.trimmed(); // Strip remaining white space..
0172         if (!line.isEmpty())
0173         {
0174             newDesc += line+QLatin1Char('\n'); // Add comment to description
0175         }
0176      }
0177      else
0178      {
0179         // This is a color line, hopefully
0180         line = line.trimmed();
0181         if (line.isEmpty()) continue;
0182         int r, g, b, a = 255;
0183         int pos = 0;
0184         bool ok = false;
0185 
0186         if ( hasAlpha )
0187           ok = (sscanf(line.toLatin1().constData(), "%d %d %d %d%n", &r, &g, &b, &a, &pos) >= 4);
0188         else
0189           ok = (sscanf(line.toLatin1().constData(), "%d %d %d%n", &r, &g, &b, &pos) >= 3);
0190 
0191         if ( ok )
0192         {
0193            r = qBound(0, r, 255);
0194            g = qBound(0, g, 255);
0195            b = qBound(0, b, 255);
0196            a = qBound(0, a, 255);
0197            QString name = line.mid(pos).trimmed();
0198            newColorList.append(ColorNode(QColor(r, g, b, a), name));
0199         }
0200      }
0201   }
0202 
0203   d->colorList = newColorList;
0204   d->name.clear ();
0205   d->desc = newDesc;
0206 
0207   return true;
0208 }
0209 
0210 static void CouldNotOpenKDEDialog (const QString &name, QWidget *parent)
0211 {
0212      KMessageBox::error (parent,
0213         i18n ("Could not open KDE color palette \"%1\".", name));
0214 }
0215 
0216 bool
0217 kpColorCollection::openKDE(const QString &name, QWidget *parent)
0218 {
0219 #if DEBUG_KP_COLOR_COLLECTION
0220   qCDebug(kpLogColorCollection) << "name=" << name;
0221 #endif
0222 
0223   if (name.isEmpty())
0224   {
0225   #if DEBUG_KP_COLOR_COLLECTION
0226     qCDebug(kpLogColorCollection) << "name.isEmpty";
0227   #endif
0228     ::CouldNotOpenKDEDialog (name, parent);
0229     return false;
0230   }
0231 
0232   QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
0233                                             QStringLiteral("colors/") + name);
0234   if (filename.isEmpty())
0235   {
0236   #if DEBUG_KP_COLOR_COLLECTION
0237     qCDebug(kpLogColorCollection) << "could not find file";
0238   #endif
0239     ::CouldNotOpenKDEDialog (name, parent);
0240     return false;
0241   }
0242 
0243   // (this will pop up an error dialog on failure)
0244   if (!open (QUrl::fromLocalFile (filename), parent))
0245   {
0246   #if DEBUG_KP_COLOR_COLLECTION
0247     qCDebug(kpLogColorCollection) << "could not open";
0248   #endif
0249     return false;
0250   }
0251 
0252   d->name = name;
0253 #if DEBUG_KP_COLOR_COLLECTION
0254   qCDebug(kpLogColorCollection) << "opened";
0255 #endif
0256   return true;
0257 }
0258 
0259 static void CouldNotSaveDialog (const QUrl &url, QWidget *parent)
0260 {
0261     // TODO: use file.errorString()
0262     KMessageBox::error (parent,
0263                         i18n ("Could not save color palette as \"%1\".",
0264                               kpUrlFormatter::PrettyFilename (url)));
0265 }
0266 
0267 static void SaveToFile (kpColorCollectionPrivate *d, QIODevice *device)
0268 {
0269    // HITODO: QTextStream can fail but does not report errors.
0270    //         Bug in KColorCollection too.
0271    QTextStream str (device);
0272 
0273    QString description = d->desc.trimmed();
0274    description = QLatin1Char('#') + description.split(QLatin1Char('\n'), Qt::KeepEmptyParts).join(QLatin1String("\n#"));
0275 
0276    str << "KDE RGBA Palette\n";
0277    str << description << "\n";
0278    for (const auto &node : d->colorList)
0279    {
0280        // Added for KolourPaint.
0281        if ( !node.color.isValid() )
0282            continue;
0283 
0284        int r, g, b, a;
0285        node.color.getRgb(&r, &g, &b, &a);
0286        str << r << " " << g << " " << b << " " << a << " " << node.name << "\n";
0287    }
0288 
0289    str.flush();
0290 }
0291 
0292 bool
0293 kpColorCollection::saveAs(const QUrl &url, QWidget *parent) const
0294 {
0295    if (url.isLocalFile ())
0296    {
0297        const QString filename = url.toLocalFile ();
0298 
0299         // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or
0300         //       else, the QSaveFile destructor will overwrite the file,
0301         //       <filename>, despite the failure.
0302         QSaveFile atomicFileWriter (filename);
0303         {
0304             if (!atomicFileWriter.open (QIODevice::WriteOnly))
0305             {
0306                 // We probably don't need this as <filename> has not been
0307                 // opened.
0308                 atomicFileWriter.cancelWriting ();
0309 
0310             #if DEBUG_KP_COLOR_COLLECTION
0311                 qCDebug(kpLogColorCollection) << "\treturning false because could not open QSaveFile"
0312                           << " error=" << atomicFileWriter.error ();
0313             #endif
0314                 ::CouldNotSaveDialog (url, parent);
0315                 return false;
0316             }
0317 
0318             // Write to local temporary file.
0319             ::SaveToFile (d, &atomicFileWriter);
0320 
0321             // Atomically overwrite local file with the temporary file
0322             // we saved to.
0323             if (!atomicFileWriter.commit ())
0324             {
0325                 atomicFileWriter.cancelWriting ();
0326 
0327             #if DEBUG_KP_COLOR_COLLECTION
0328                 qCDebug(kpLogColorCollection) << "\tcould not close QSaveFile";
0329             #endif
0330                 ::CouldNotSaveDialog (url, parent);
0331                 return false;
0332             }
0333         }  // sync QSaveFile.cancelWriting()
0334     }
0335     // Remote file?
0336     else
0337     {
0338         // Create temporary file that is deleted when the variable goes
0339         // out of scope.
0340         QTemporaryFile tempFile;
0341         if (!tempFile.open ())
0342         {
0343         #if DEBUG_KP_COLOR_COLLECTION
0344             qCDebug(kpLogColorCollection) << "\treturning false because could not open tempFile";
0345         #endif
0346             ::CouldNotSaveDialog (url, parent);
0347             return false;
0348         }
0349 
0350         // Write to local temporary file.
0351         ::SaveToFile (d, &tempFile);
0352 
0353         // Collect name of temporary file now, as QTemporaryFile::fileName()
0354         // stops working after close() is called.
0355         const QString tempFileName = tempFile.fileName ();
0356     #if DEBUG_KP_COLOR_COLLECTION
0357             qCDebug(kpLogColorCollection) << "\ttempFileName='" << tempFileName << "'";
0358     #endif
0359         Q_ASSERT (!tempFileName.isEmpty ());
0360 
0361         tempFile.close ();
0362         if (tempFile.error () != QFile::NoError)
0363         {
0364         #if DEBUG_KP_COLOR_COLLECTION
0365             qCDebug(kpLogColorCollection) << "\treturning false because could not close";
0366         #endif
0367             ::CouldNotSaveDialog (url, parent);
0368             return false;
0369         }
0370 
0371         // Copy local temporary file to overwrite remote.
0372         KIO::FileCopyJob *job = KIO::file_copy (QUrl::fromLocalFile (tempFileName),
0373                                                 url,
0374                                                 -1,
0375                                                 KIO::Overwrite);
0376         KJobWidgets::setWindow (job, parent);
0377         if (!job->exec ())
0378         {
0379         #if DEBUG_KP_COLOR_COLLECTION
0380             qCDebug(kpLogColorCollection) << "\treturning false because could not upload";
0381         #endif
0382             ::CouldNotSaveDialog (url, parent);
0383             return false;
0384         }
0385     }
0386 
0387    d->name.clear ();
0388    return true;
0389 }
0390 
0391 QString kpColorCollection::description() const
0392 {
0393     return d->desc;
0394 }
0395 
0396 void kpColorCollection::setDescription(const QString &desc)
0397 {
0398     d->desc = desc;
0399 }
0400 
0401 QString kpColorCollection::name() const
0402 {
0403     return d->name;
0404 }
0405 
0406 void kpColorCollection::setName(const QString &name)
0407 {
0408     d->name = name;
0409 }
0410 
0411 kpColorCollection::Editable kpColorCollection::editable() const
0412 {
0413     return d->editable;
0414 }
0415 
0416 void kpColorCollection::setEditable(Editable editable)
0417 {
0418     d->editable = editable;
0419 }
0420 
0421 int kpColorCollection::count() const
0422 {
0423     return (int) d->colorList.count();
0424 }
0425 
0426 void kpColorCollection::resize(int newCount)
0427 {
0428     if (newCount == count())
0429         return;
0430     else if (newCount < count())
0431     {
0432         d->colorList.erase(d->colorList.begin() + newCount, d->colorList.end());
0433     }
0434     else if (newCount > count())
0435     {
0436          while(newCount > count())
0437          {
0438              const int ret = addColor(QColor(), QString()/*color name*/);
0439              Q_ASSERT(ret == count() - 1);
0440          }
0441     }
0442 }
0443 
0444 kpColorCollection&
0445 kpColorCollection::operator=( const kpColorCollection &p)
0446 {
0447   if (&p == this) return *this;
0448     d->colorList = p.d->colorList;
0449     d->name = p.d->name;
0450     d->desc = p.d->desc;
0451     d->editable = p.d->editable;
0452   return *this;
0453 }
0454 
0455 QColor
0456 kpColorCollection::color(int index) const
0457 {
0458     if ((index < 0) || (index >= count()))
0459     return {};
0460 
0461     return d->colorList[index].color;
0462 }
0463 
0464 int
0465 kpColorCollection::findColor(const QColor &color) const
0466 {
0467     for (int i = 0; i < d->colorList.size(); ++i)
0468   {
0469         if (d->colorList[i].color == color)
0470         return i;
0471   }
0472   return -1;
0473 }
0474 
0475 QString
0476 kpColorCollection::name(int index) const
0477 {
0478   if ((index < 0) || (index >= count()))
0479       return {};
0480 
0481   return d->colorList[index].name;
0482 }
0483 
0484 QString kpColorCollection::name(const QColor &color) const
0485 {
0486     return name(findColor(color));
0487 }
0488 
0489 int
0490 kpColorCollection::addColor(const QColor &newColor, const QString &newColorName)
0491 {
0492     d->colorList.append(ColorNode(newColor, newColorName));
0493     return count() - 1;
0494 }
0495 
0496 int
0497 kpColorCollection::changeColor(int index,
0498                       const QColor &newColor,
0499                       const QString &newColorName)
0500 {
0501     if ((index < 0) || (index >= count()))
0502       return -1;
0503 
0504   ColorNode& node = d->colorList[index];
0505   node.color = newColor;
0506   node.name  = newColorName;
0507 
0508   return index;
0509 }
0510 
0511 int kpColorCollection::changeColor(const QColor &oldColor,
0512                           const QColor &newColor,
0513                           const QString &newColorName)
0514 {
0515     return changeColor( findColor(oldColor), newColor, newColorName);
0516 }
0517