File indexing completed on 2025-01-05 03:56:44

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-04-19
0007  * Description : A tab to display general item information
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2013      by Michael G. Hansen <mike at mghansen dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "itempropertiestab_p.h"
0017 
0018 namespace Digikam
0019 {
0020 
0021 typedef QPair<QString, QVariant> PathValuePair;
0022 
0023 static bool naturalLessThan(const PathValuePair& a, const PathValuePair& b)
0024 {
0025     return (QCollator().compare(a.first, b.first) < 0);
0026 }
0027 
0028 QStringList ItemPropertiesTab::shortenedTagPaths(const QStringList& tagPaths, QList<QVariant>* identifiers)
0029 {
0030     QList<PathValuePair> tagsSorted;
0031 
0032     if (identifiers)
0033     {
0034         for (int i = 0 ; i < tagPaths.size() ; ++i)
0035         {
0036             tagsSorted << PathValuePair(tagPaths.at(i), (*identifiers).at(i));
0037         }
0038     }
0039     else
0040     {
0041         for (int i = 0 ; i < tagPaths.size() ; ++i)
0042         {
0043             tagsSorted << PathValuePair(tagPaths.at(i), QVariant());
0044         }
0045     }
0046 
0047     std::stable_sort(tagsSorted.begin(), tagsSorted.end(), naturalLessThan);
0048 
0049     if (identifiers)
0050     {
0051         identifiers->clear();
0052     }
0053 
0054     QStringList tagsShortened;
0055     QString previous;
0056 
0057     Q_FOREACH (const PathValuePair& pair, tagsSorted)
0058     {
0059         const QString& tagPath   = pair.first;
0060         QString shortenedPath    = tagPath;
0061         QStringList currentPath  = tagPath.split(QLatin1Char('/'), QT_SKIP_EMPTY_PARTS);
0062         QStringList previousPath = previous.split(QLatin1Char('/'), QT_SKIP_EMPTY_PARTS);
0063         int depth;
0064 
0065         for (depth = 0 ; (depth < currentPath.size()) && (depth < previousPath.size()) ; ++depth)
0066         {
0067             if (currentPath.at(depth) != previousPath.at(depth))
0068             {
0069                 break;
0070             }
0071         }
0072 
0073         if (depth)
0074         {
0075             QString indent;
0076             indent.fill(QLatin1Char(' '), qMin(depth, 5));
0077 /*
0078             indent += QChar(0x2026);
0079 */
0080             shortenedPath = indent + tagPath.section(QLatin1Char('/'), depth);
0081         }
0082 
0083         shortenedPath.replace(QLatin1Char('/'), QLatin1String(" / "));
0084         tagsShortened << shortenedPath;
0085         previous = tagPath;
0086 
0087         if (identifiers)
0088         {
0089             (*identifiers) << pair.second;
0090         }
0091     }
0092 
0093     return tagsShortened;
0094 }
0095 
0096 void ItemPropertiesTab::shortenedMakeInfo(QString& make)
0097 {
0098     make.remove(QLatin1String(" CORPORATION"),       Qt::CaseInsensitive);        // from Nikon, Pentax, and Olympus
0099     make.remove(QLatin1String("EASTMAN "),           Qt::CaseInsensitive);        // from Kodak
0100     make.remove(QLatin1String(" COMPANY"),           Qt::CaseInsensitive);        // from Kodak
0101     make.remove(QLatin1String(" OPTICAL CO.,LTD"),   Qt::CaseInsensitive);        // from Olympus
0102     make.remove(QLatin1String(" IMAGING CORP."),     Qt::CaseInsensitive);        // from Olympus
0103     make.remove(QLatin1String(" Techwin co.,Ltd."),  Qt::CaseInsensitive);        // from Samsung
0104     make.remove(QLatin1String(" Electric Co.,Ltd."), Qt::CaseInsensitive);        // from Sanyo
0105     make.remove(QLatin1String(" Electric Co.,Ltd"),  Qt::CaseInsensitive);        // from Sanyo
0106     make.remove(QLatin1String(" COMPUTER CO.,LTD."), Qt::CaseInsensitive);        // from Casio
0107     make.remove(QLatin1String(" COMPUTER CO.,LTD"),  Qt::CaseInsensitive);        // from Casio
0108     make.remove(QLatin1String(" Co., Ltd."),         Qt::CaseInsensitive);        // from Minolta
0109     make.remove(QLatin1String("  Co.,Ltd."),         Qt::CaseInsensitive);        // from Minolta
0110     make = make.trimmed();
0111 }
0112 
0113 void ItemPropertiesTab::shortenedModelInfo(QString& model)
0114 {
0115     model.remove(QLatin1String("Canon "),           Qt::CaseInsensitive);
0116     model.remove(QLatin1String("NIKON "),           Qt::CaseInsensitive);
0117     model.remove(QLatin1String("PENTAX "),          Qt::CaseInsensitive);
0118     model.remove(QLatin1String(" DIGITAL"),         Qt::CaseInsensitive);        // from Canon
0119     model.remove(QLatin1String("KODAK "),           Qt::CaseInsensitive);
0120     model.remove(QLatin1String(" CAMERA"),          Qt::CaseInsensitive);        // from Kodak
0121     model = model.trimmed();
0122 }
0123 
0124 /**
0125  * Find rational approximation to given real number
0126  *
0127  *   val    : double value to convert as humain readable fraction
0128  *   num    : fraction numerator
0129  *   den    : fraction denominator
0130  *   maxden : the maximum denominator allowed
0131  *
0132  * This function return approximation error of the fraction
0133  *
0134  * Based on the theory of continued fractions
0135  * if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
0136  * Then best approximation is found by truncating this series
0137  * wwith some adjustments in the last term.
0138  *
0139  * Note the fraction can be recovered as the first column of the matrix
0140  *  ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
0141  *  ( 1  0 ) ( 1  0 ) ( 1  0 )
0142  * Instead of keeping the sequence of continued fraction terms,
0143  * we just keep the last partial product of these matrices.
0144  *
0145  * Details: stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
0146  *
0147  */
0148 double ItemPropertiesTab::doubleToHumanReadableFraction(double val, long* num, long* den, long maxden)
0149 {
0150     double x = val;
0151     long   m[2][2];
0152     long   ai;
0153 
0154     // Initialize matrix
0155 
0156     m[0][0] = m[1][1] = 1;
0157     m[0][1] = m[1][0] = 0;
0158 
0159     // Loop finding terms until denominator gets too big
0160 
0161     while (m[1][0] * (ai = (long)x) + m[1][1] <= maxden)
0162     {
0163         long t  = m[0][0] * ai + m[0][1];
0164         m[0][1] = m[0][0];
0165         m[0][0] = t;
0166         t       = m[1][0] * ai + m[1][1];
0167         m[1][1] = m[1][0];
0168         m[1][0] = t;
0169 
0170         if (x == (double)ai)
0171         {
0172             break;     // division by zero
0173         }
0174 
0175         x       = 1 / (x - (double)ai);
0176 
0177         if (x > (double)0x7FFFFFFF)
0178         {
0179             break;     // representation failure
0180         }
0181     }
0182 
0183     // Now remaining x is between 0 and 1/ai
0184     // Approx as either 0 or 1/m where m is max that will fit in maxden
0185 
0186     *num = m[0][0];
0187     *den = m[1][0];
0188 
0189     // Return approximation error
0190 
0191     return (val - ((double)m[0][0] / (double)m[1][0]));
0192 }
0193 
0194 bool ItemPropertiesTab::aspectRatioToString(int width, int height, QString& arString)
0195 {
0196     if ((width == 0) || (height == 0))
0197     {
0198         return false;
0199     }
0200 
0201     double ratio  = (double)qMax(width, height) / (double)qMin(width, height);
0202     long   num    = 0;
0203     long   den    = 0;
0204 
0205     doubleToHumanReadableFraction(ratio, &num, &den, 10);
0206 
0207     double aratio = (double)qMax(num, den) / (double)qMin(num, den);
0208 
0209     arString = i18nc("@info: width : height (Aspect Ratio)", "%1:%2 (%3)",
0210                      (width > height) ? num : den,
0211                      (width > height) ? den : num,
0212                      QLocale().toString(aratio, 'g', 2));
0213 
0214     return true;
0215 }
0216 
0217 QString ItemPropertiesTab::permissionsString(const QFileInfo& fi)
0218 {
0219     QString str;
0220     QFile::Permissions perms = fi.permissions();
0221 
0222     str.append(fi.isSymLink()                    ? QLatin1String("l") : QLatin1String("-"));
0223 
0224     str.append((perms & QFileDevice::ReadOwner)  ? QLatin1String("r") : QLatin1String("-"));
0225     str.append((perms & QFileDevice::WriteOwner) ? QLatin1String("w") : QLatin1String("-"));
0226     str.append((perms & QFileDevice::ExeOwner)   ? QLatin1String("x") : QLatin1String("-"));
0227 
0228     str.append((perms & QFileDevice::ReadGroup)  ? QLatin1String("r") : QLatin1String("-"));
0229     str.append((perms & QFileDevice::WriteGroup) ? QLatin1String("w") : QLatin1String("-"));
0230     str.append((perms & QFileDevice::ExeGroup)   ? QLatin1String("x") : QLatin1String("-"));
0231 
0232     str.append((perms & QFileDevice::ReadOther)  ? QLatin1String("r") : QLatin1String("-"));
0233     str.append((perms & QFileDevice::WriteOther) ? QLatin1String("w") : QLatin1String("-"));
0234     str.append((perms & QFileDevice::ExeOther)   ? QLatin1String("x") : QLatin1String("-"));
0235 
0236     return str;
0237 }
0238 
0239 QString ItemPropertiesTab::humanReadableBytesCount(qint64 bytes, bool si)
0240 {
0241     int unit        = si ? 1000 : 1024;
0242     QString byteStr = i18nc("@info: unit file size in bytes", "B");
0243     QString ret     = QString::number(bytes);
0244 
0245     if (bytes >= unit)
0246     {
0247         int exp     = (int)(qLn(bytes) / qLn(unit));
0248         QString pre = QString(si ? QLatin1String("kMGTPEZY")
0249                                  : QLatin1String("KMGTPEZY")).at(exp-1) + (si ? QLatin1String("")
0250                                                                               : QLatin1String("i"));
0251         ret         = QString().asprintf("%.1f %s", bytes / qPow(unit, exp), pre.toUtf8().constData());
0252     }
0253 
0254     return (QString::fromUtf8("%1%2").arg(ret).arg(byteStr));
0255 }
0256 
0257 } // namespace Digikam