File indexing completed on 2024-05-12 16:39:58

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
0003 
0004    Contains code from kglobalsettings.cpp:
0005    Copyright (C) 2000, 2006 David Faure <faure@kde.org>
0006    Copyright (C) 2008 Friedrich W. H. Kossebau <kossebau@kde.org>
0007 
0008    Contains code from kdialog.cpp:
0009    Copyright (C) 1998 Thomas Tanghus (tanghus@earthling.net)
0010    Additions 1999-2000 by Espen Sand (espen@kde.org)
0011                        and Holger Freyther <freyther@kde.org>
0012              2005-2009 Olivier Goffart <ogoffart @ kde.org>
0013              2006      Tobias Koenig <tokoe@kde.org>
0014 
0015    This program is free software; you can redistribute it and/or
0016    modify it under the terms of the GNU Library General Public
0017    License as published by the Free Software Foundation; either
0018    version 2 of the License, or (at your option) any later version.
0019 
0020    This program is distributed in the hope that it will be useful,
0021    but WITHOUT ANY WARRANTY; without even the implied warranty of
0022    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0023    Library General Public License for more details.
0024 
0025    You should have received a copy of the GNU Library General Public License
0026    along with this program; see the file COPYING.  If not, write to
0027    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0028  * Boston, MA 02110-1301, USA.
0029 */
0030 
0031 #include "utils.h"
0032 #include "utils_p.h"
0033 #include "FontSettings_p.h"
0034 #include "kexiutils_global.h"
0035 #include <KexiIcon.h>
0036 
0037 #include <QDomNode>
0038 #include <QPainter>
0039 #include <QImage>
0040 #include <QImageReader>
0041 #include <QImageWriter>
0042 #include <QIcon>
0043 #include <QMetaProperty>
0044 #include <QFocusEvent>
0045 #include <QFile>
0046 #include <QStyle>
0047 #include <QLayout>
0048 #include <KMessageBox>
0049 #include <QFileInfo>
0050 #include <QClipboard>
0051 #include <QMimeDatabase>
0052 #include <QMimeType>
0053 #include <QUrl>
0054 #include <QApplication>
0055 #include <QDesktopWidget>
0056 #include <QFontDatabase>
0057 #include <QTextCodec>
0058 #include <QDebug>
0059 #include <QFileDialog>
0060 #include <QDesktopServices>
0061 #include <QStyleHints>
0062 #include <QLineEdit>
0063 #include <QProcess>
0064 
0065 #ifndef KEXI_MOBILE
0066 #include <KFileWidget>
0067 #include <KIO/JobUiDelegate>
0068 #include <KIO/OpenUrlJob>
0069 #include <KRecentDirs>
0070 #include <KRun>
0071 #include <kio_version.h>
0072 #endif
0073 #include <KAboutData>
0074 #include <KColorScheme>
0075 #include <KConfigGroup>
0076 #include <KIconEffect>
0077 
0078 #if HAVE_LANGINFO_H
0079 #include <langinfo.h>
0080 #endif
0081 
0082 #ifdef Q_OS_WIN
0083 #include <windows.h>
0084 
0085 static QRgb qt_colorref2qrgb(COLORREF col)
0086 {
0087     return qRgb(GetRValue(col), GetGValue(col), GetBValue(col));
0088 }
0089 #endif
0090 
0091 using namespace KexiUtils;
0092 
0093 DelayedCursorHandler::DelayedCursorHandler(QWidget *widget)
0094         : startedOrActive(false), m_widget(widget), m_handleWidget(widget)
0095 {
0096     m_timer.setSingleShot(true);
0097     connect(&m_timer, SIGNAL(timeout()), this, SLOT(show()));
0098 }
0099 void DelayedCursorHandler::start(bool noDelay)
0100 {
0101     startedOrActive = true;
0102     m_timer.start(noDelay ? 0 : 1000);
0103 }
0104 void DelayedCursorHandler::stop()
0105 {
0106     startedOrActive = false;
0107     m_timer.stop();
0108     if (m_handleWidget && m_widget) {
0109         m_widget->unsetCursor();
0110     } else {
0111         QApplication::restoreOverrideCursor();
0112     }
0113 }
0114 
0115 void DelayedCursorHandler::show()
0116 {
0117     const QCursor waitCursor(Qt::WaitCursor);
0118     if (m_handleWidget && m_widget) {
0119         m_widget->unsetCursor();
0120         m_widget->setCursor(waitCursor);
0121     } else {
0122         QApplication::restoreOverrideCursor();
0123         QApplication::setOverrideCursor(waitCursor);
0124     }
0125 }
0126 
0127 Q_GLOBAL_STATIC(DelayedCursorHandler, _delayedCursorHandler)
0128 
0129 void KexiUtils::setWaitCursor(bool noDelay)
0130 {
0131     if (qobject_cast<QApplication*>(qApp)) {
0132         _delayedCursorHandler->start(noDelay);
0133     }
0134 }
0135 
0136 void KexiUtils::removeWaitCursor()
0137 {
0138     if (qobject_cast<QApplication*>(qApp)) {
0139         _delayedCursorHandler->stop();
0140     }
0141 }
0142 
0143 WaitCursor::WaitCursor(bool noDelay)
0144     : m_handler(nullptr)
0145 {
0146     setWaitCursor(noDelay);
0147 }
0148 
0149 WaitCursor::WaitCursor(QWidget *widget, bool noDelay)
0150 {
0151     DelayedCursorHandler *handler = new DelayedCursorHandler(widget);
0152     handler->start(noDelay);
0153     m_handler = handler;
0154 }
0155 
0156 WaitCursor::~WaitCursor()
0157 {
0158     if (m_handler) {
0159         qobject_cast<DelayedCursorHandler*>(m_handler)->stop();
0160         delete m_handler;
0161     } else {
0162         removeWaitCursor();
0163     }
0164 }
0165 
0166 WaitCursorRemover::WaitCursorRemover()
0167 {
0168     m_reactivateCursor = _delayedCursorHandler->startedOrActive;
0169     _delayedCursorHandler->stop();
0170 }
0171 
0172 WaitCursorRemover::~WaitCursorRemover()
0173 {
0174     if (m_reactivateCursor)
0175         _delayedCursorHandler->start(true);
0176 }
0177 
0178 //--------------------------------------------------------------------------------
0179 
0180 QObject* KexiUtils::findFirstQObjectChild(QObject *o, const char* className, const char* objName)
0181 {
0182     if (!o)
0183         return 0;
0184     const QObjectList list(o->children());
0185     foreach(QObject *child, list) {
0186         if (child->inherits(className) && (!objName || child->objectName() == objName))
0187             return child;
0188     }
0189     //try children
0190     foreach(QObject *child, list) {
0191         child = findFirstQObjectChild(child, className, objName);
0192         if (child)
0193             return child;
0194     }
0195     return 0;
0196 }
0197 
0198 QMetaProperty KexiUtils::findPropertyWithSuperclasses(const QObject* object,
0199         const char* name)
0200 {
0201     const int index = object->metaObject()->indexOfProperty(name);
0202     if (index == -1)
0203         return QMetaProperty();
0204     return object->metaObject()->property(index);
0205 }
0206 
0207 bool KexiUtils::objectIsA(QObject* object, const QList<QByteArray>& classNames)
0208 {
0209     foreach(const QByteArray& ba, classNames) {
0210         if (objectIsA(object, ba.constData()))
0211             return true;
0212     }
0213     return false;
0214 }
0215 
0216 QList<QMetaMethod> KexiUtils::methodsForMetaObject(
0217     const QMetaObject *metaObject, QFlags<QMetaMethod::MethodType> types,
0218     QFlags<QMetaMethod::Access> access)
0219 {
0220     const int count = metaObject ? metaObject->methodCount() : 0;
0221     QList<QMetaMethod> result;
0222     for (int i = 0; i < count; i++) {
0223         QMetaMethod method(metaObject->method(i));
0224         if (types & method.methodType() && access & method.access())
0225             result += method;
0226     }
0227     return result;
0228 }
0229 
0230 QList<QMetaMethod> KexiUtils::methodsForMetaObjectWithParents(
0231     const QMetaObject *metaObject, QFlags<QMetaMethod::MethodType> types,
0232     QFlags<QMetaMethod::Access> access)
0233 {
0234     QList<QMetaMethod> result;
0235     while (metaObject) {
0236         const int count = metaObject->methodCount();
0237         for (int i = 0; i < count; i++) {
0238             QMetaMethod method(metaObject->method(i));
0239             if (types & method.methodType() && access & method.access())
0240                 result += method;
0241         }
0242         metaObject = metaObject->superClass();
0243     }
0244     return result;
0245 }
0246 
0247 QList<QMetaProperty> KexiUtils::propertiesForMetaObject(
0248     const QMetaObject *metaObject)
0249 {
0250     const int count = metaObject ? metaObject->propertyCount() : 0;
0251     QList<QMetaProperty> result;
0252     for (int i = 0; i < count; i++)
0253         result += metaObject->property(i);
0254     return result;
0255 }
0256 
0257 QList<QMetaProperty> KexiUtils::propertiesForMetaObjectWithInherited(
0258     const QMetaObject *metaObject)
0259 {
0260     QList<QMetaProperty> result;
0261     while (metaObject) {
0262         const int count = metaObject->propertyCount();
0263         for (int i = 0; i < count; i++)
0264             result += metaObject->property(i);
0265         metaObject = metaObject->superClass();
0266     }
0267     return result;
0268 }
0269 
0270 QStringList KexiUtils::enumKeysForProperty(const QMetaProperty& metaProperty, int filter)
0271 {
0272     QStringList result;
0273     const QMetaEnum enumerator(metaProperty.enumerator());
0274     const int count = enumerator.keyCount();
0275     int total = 0;
0276     for (int i = 0; i < count; i++) {
0277         if (filter == INT_MIN) {
0278             result.append(QString::fromLatin1(enumerator.key(i)));
0279         } else {
0280             const int v = enumerator.value(i);
0281             if ((v & filter) && !(total & v)) { // !(total & v) is a protection adding against masks
0282                 result.append(QString::fromLatin1(enumerator.key(i)));
0283                 total |= v;
0284             }
0285         }
0286     }
0287     return result;
0288 }
0289 
0290 //! @internal
0291 static QFileDialog* getImageDialog(QWidget *parent, const QString &caption, const QUrl &directory,
0292                                    const QList<QByteArray> &supportedMimeTypes)
0293 {
0294     QFileDialog *dialog = new QFileDialog(parent, caption);
0295     dialog->setDirectoryUrl(directory);
0296     const QStringList mimeTypeFilters
0297         = KexiUtils::convertTypesUsingFunction<QByteArray, QString, &QString::fromLatin1>(supportedMimeTypes);
0298     dialog->setMimeTypeFilters(mimeTypeFilters);
0299     return dialog;
0300 }
0301 
0302 QUrl KexiUtils::getOpenImageUrl(QWidget *parent, const QString &caption, const QUrl &directory)
0303 {
0304     QScopedPointer<QFileDialog> dialog(
0305         getImageDialog(parent, caption.isEmpty() ? i18n("Open") : caption, directory,
0306                        QImageReader::supportedMimeTypes()));
0307     dialog->setFileMode(QFileDialog::ExistingFile);
0308     dialog->setAcceptMode(QFileDialog::AcceptOpen);
0309     if (QDialog::Accepted == dialog->exec()) {
0310         return dialog->selectedUrls().value(0);
0311     } else {
0312         return QUrl();
0313     }
0314 }
0315 
0316 QUrl KexiUtils::getSaveImageUrl(QWidget *parent, const QString &caption, const QUrl &directory)
0317 {
0318     QScopedPointer<QFileDialog> dialog(
0319         getImageDialog(parent, caption.isEmpty() ? i18n("Save") : caption, directory,
0320                        QImageWriter::supportedMimeTypes()));
0321     dialog->setAcceptMode(QFileDialog::AcceptSave);
0322     if (QDialog::Accepted == dialog->exec()) {
0323         return dialog->selectedUrls().value(0);
0324     } else {
0325         return QUrl();
0326     }
0327 }
0328 
0329 #ifndef KEXI_MOBILE
0330 QUrl KexiUtils::getStartUrl(const QUrl &startDirOrVariable, QString *recentDirClass,
0331                             const QString &fileName)
0332 {
0333     QUrl result;
0334     if (recentDirClass) {
0335         result = KFileWidget::getStartUrl(startDirOrVariable, *recentDirClass);
0336         // Fix bug introduced by Kexi 3.0.x in KexiFileWidget: remove file protocol from path
0337         // (the KRecentDirs::add(.., dir.url()) call was invalid because of prepended protocol)
0338         const QString protocol("file:/");
0339         if (result.path().startsWith(protocol) && !result.path().startsWith(protocol + '/')) {
0340             result.setPath(result.path().mid(protocol.length() - 1));
0341         }
0342         if (!fileName.isEmpty()) {
0343             result.setPath(result.path() + '/' + fileName);
0344         }
0345     } else {
0346         qWarning() << "Missing recentDirClass";
0347     }
0348     return result;
0349 }
0350 
0351 void KexiUtils::addRecentDir(const QString &fileClass, const QString &directory)
0352 {
0353     KRecentDirs::add(fileClass, directory);
0354 }
0355 #endif
0356 
0357 bool KexiUtils::askForFileOverwriting(const QString& filePath, QWidget *parent)
0358 {
0359     QFileInfo fi(filePath);
0360     if (!fi.exists()) {
0361         return true;
0362     }
0363     const KMessageBox::ButtonCode res = KMessageBox::warningYesNo(parent,
0364                     xi18nc("@info", "<para>The file <filename>%1</filename> already exists.</para>"
0365                            "<para>Do you want to overwrite it?</para>",
0366                            QDir::toNativeSeparators(filePath)),
0367                     QString(),
0368                     KStandardGuiItem::overwrite(), KStandardGuiItem::no());
0369     return res == KMessageBox::Yes;
0370 }
0371 
0372 QColor KexiUtils::blendedColors(const QColor& c1, const QColor& c2, int factor1, int factor2)
0373 {
0374     return QColor(
0375                int((c1.red()*factor1 + c2.red()*factor2) / (factor1 + factor2)),
0376                int((c1.green()*factor1 + c2.green()*factor2) / (factor1 + factor2)),
0377                int((c1.blue()*factor1 + c2.blue()*factor2) / (factor1 + factor2)));
0378 }
0379 
0380 QColor KexiUtils::contrastColor(const QColor& c)
0381 {
0382     int g = qGray(c.rgb());
0383     if (g > 110)
0384         return c.dark(200);
0385     else if (g > 80)
0386         return c.light(150);
0387     else if (g > 20)
0388         return c.light(300);
0389     return Qt::gray;
0390 }
0391 
0392 QColor KexiUtils::bleachedColor(const QColor& c, int factor)
0393 {
0394     int h, s, v;
0395     c.getHsv(&h, &s, &v);
0396     QColor c2;
0397     if (factor < 100)
0398         factor = 100;
0399     if (s >= 250 && v >= 250) //for colors like cyan or red, make the result more white
0400         s = qMax(0, s - factor - 50);
0401     else if (s <= 5 && v <= 5)
0402         v += factor - 50;
0403     c2.setHsv(h, s, qMin(255, v + factor - 100));
0404     return c2;
0405 }
0406 
0407 QIcon KexiUtils::colorizeIconToTextColor(const QPixmap& icon, const QPalette& palette,
0408                                          QPalette::ColorRole role)
0409 {
0410     QPixmap pm(
0411         KIconEffect().apply(icon, KIconEffect::Colorize, 1.0f,
0412                             palette.color(role), false));
0413     KIconEffect::semiTransparent(pm);
0414     return QIcon(pm);
0415 }
0416 
0417 QPixmap KexiUtils::emptyIcon(KIconLoader::Group iconGroup)
0418 {
0419     const int size = KIconLoader::global()->currentSize(iconGroup);
0420     QPixmap noIcon(size, size);
0421     noIcon.fill(Qt::transparent);
0422     return noIcon;
0423 }
0424 
0425 static void drawOrScalePixmapInternal(QPainter* p, const QMargins& margins, const QRect& rect,
0426                                       QPixmap* pixmap, QPoint* pos, Qt::Alignment alignment,
0427                                       bool scaledContents, bool keepAspectRatio,
0428                                       Qt::TransformationMode transformMode = Qt::FastTransformation)
0429 {
0430     Q_ASSERT(pos);
0431     if (pixmap->isNull())
0432         return;
0433 
0434     const bool fast = false;
0435     const int w = rect.width() - margins.left() - margins.right();
0436     const int h = rect.height() - margins.top() - margins.bottom();
0437 //! @todo we can optimize painting by drawing rescaled pixmap here
0438 //! and performing detailed painting later (using QTimer)
0439 //    QPixmap pixmapBuffer;
0440 //    QPainter p2;
0441 //    QPainter *target;
0442 //    if (fast) {
0443 //       target = p;
0444 //    } else {
0445 //        target = &p2;
0446 //    }
0447 //! @todo only create buffered pixmap of the minimum size and then do not fillRect()
0448 // target->fillRect(0,0,rect.width(),rect.height(), backgroundColor);
0449 
0450     *pos = rect.topLeft() + QPoint(margins.left(), margins.top());
0451     if (scaledContents) {
0452         if (keepAspectRatio) {
0453             QImage img(pixmap->toImage());
0454             img = img.scaled(w, h, Qt::KeepAspectRatio, transformMode);
0455             if (img.width() < w) {
0456                 if (alignment & Qt::AlignRight)
0457                     pos->setX(pos->x() + w - img.width());
0458                 else if (alignment & Qt::AlignHCenter)
0459                     pos->setX(pos->x() + w / 2 - img.width() / 2);
0460             }
0461             else if (img.height() < h) {
0462                 if (alignment & Qt::AlignBottom)
0463                     pos->setY(pos->y() + h - img.height());
0464                 else if (alignment & Qt::AlignVCenter)
0465                     pos->setY(pos->y() + h / 2 - img.height() / 2);
0466             }
0467             if (p) {
0468                 p->drawImage(*pos, img);
0469             }
0470             else {
0471                 *pixmap = QPixmap::fromImage(img);
0472             }
0473         } else {
0474             if (!fast) {
0475                 *pixmap = pixmap->scaled(w, h, Qt::IgnoreAspectRatio, transformMode);
0476                 if (p) {
0477                     p->drawPixmap(*pos, *pixmap);
0478                 }
0479             }
0480         }
0481     }
0482     else {
0483         if (alignment & Qt::AlignRight)
0484             pos->setX(pos->x() + w - pixmap->width());
0485         else if (alignment & Qt::AlignHCenter)
0486             pos->setX(pos->x() + w / 2 - pixmap->width() / 2);
0487         else //left, etc.
0488             pos->setX(pos->x());
0489 
0490         if (alignment & Qt::AlignBottom)
0491             pos->setY(pos->y() + h - pixmap->height());
0492         else if (alignment & Qt::AlignVCenter)
0493             pos->setY(pos->y() + h / 2 - pixmap->height() / 2);
0494         else //top, etc.
0495             pos->setY(pos->y());
0496         *pos += QPoint(margins.left(), margins.top());
0497         if (p) {
0498             p->drawPixmap(*pos, *pixmap);
0499         }
0500     }
0501 }
0502 
0503 void KexiUtils::drawPixmap(QPainter* p, const QMargins& margins, const QRect& rect,
0504                            const QPixmap& pixmap, Qt::Alignment alignment, bool scaledContents,
0505                            bool keepAspectRatio, Qt::TransformationMode transformMode)
0506 {
0507     QPixmap px(pixmap);
0508     QPoint pos;
0509     drawOrScalePixmapInternal(p, margins, rect, &px, &pos, alignment, scaledContents,
0510                               keepAspectRatio, transformMode);
0511 }
0512 
0513 QPixmap KexiUtils::scaledPixmap(const QMargins& margins, const QRect& rect,
0514                                 const QPixmap& pixmap, QPoint* pos, Qt::Alignment alignment,
0515                                 bool scaledContents, bool keepAspectRatio,
0516                                 Qt::TransformationMode transformMode)
0517 {
0518     QPixmap px(pixmap);
0519     drawOrScalePixmapInternal(0, margins, rect, &px, pos, alignment, scaledContents, keepAspectRatio, transformMode);
0520     return px;
0521 }
0522 
0523 bool KexiUtils::loadPixmapFromData(QPixmap *pixmap, const QByteArray &data, const char *format)
0524 {
0525     bool ok = pixmap->loadFromData(data, format);
0526     if (ok) {
0527         return true;
0528     }
0529     if (format) {
0530         return false;
0531     }
0532     const QList<QByteArray> commonFormats({"png", "jpg", "bmp", "tif"});
0533     QList<QByteArray> formats(commonFormats);
0534     for(int i=0; ;) {
0535         ok = pixmap->loadFromData(data, formats[i]);
0536         if (ok) {
0537             return true;
0538         }
0539         ++i;
0540         if (i == formats.count()) {// try harder
0541             if (i == commonFormats.count()) {
0542                 formats += QImageReader::supportedImageFormats();
0543                 if (formats.count() == commonFormats.count()) {
0544                     break; // sanity check
0545                 }
0546             } else {
0547                 break;
0548             }
0549         }
0550     }
0551     return false;
0552 }
0553 
0554 void KexiUtils::setFocusWithReason(QWidget* widget, Qt::FocusReason reason)
0555 {
0556     if (!widget)
0557         return;
0558     QFocusEvent fe(QEvent::FocusIn, reason);
0559     QCoreApplication::sendEvent(widget, &fe);
0560 }
0561 
0562 void KexiUtils::unsetFocusWithReason(QWidget* widget, Qt::FocusReason reason)
0563 {
0564     if (!widget)
0565         return;
0566     QFocusEvent fe(QEvent::FocusOut, reason);
0567     QCoreApplication::sendEvent(widget, &fe);
0568 }
0569 
0570 //--------
0571 
0572 void KexiUtils::adjustIfRtl(QMargins *margins)
0573 {
0574     if (margins && QGuiApplication::isRightToLeft()) {
0575         const int left = margins->left();
0576         margins->setLeft(margins->right());
0577         margins->setRight(left);
0578     }
0579 }
0580 
0581 //---------
0582 
0583 Q_GLOBAL_STATIC(FontSettingsData, g_fontSettings)
0584 
0585 QFont KexiUtils::smallestReadableFont()
0586 {
0587     return g_fontSettings->font(FontSettingsData::SmallestReadableFont);
0588 }
0589 
0590 //---------------------
0591 
0592 KTextEditorFrame::KTextEditorFrame(QWidget * parent, Qt::WindowFlags f)
0593         : QFrame(parent, f)
0594 {
0595     QEvent dummy(QEvent::StyleChange);
0596     changeEvent(&dummy);
0597 }
0598 
0599 void KTextEditorFrame::changeEvent(QEvent *event)
0600 {
0601     if (event->type() == QEvent::StyleChange) {
0602         if (style()->objectName() != "oxygen") // oxygen already nicely paints the frame
0603             setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
0604         else
0605             setFrameStyle(QFrame::NoFrame);
0606     }
0607 }
0608 
0609 //---------------------
0610 
0611 int KexiUtils::marginHint()
0612 {
0613     return QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin);
0614 }
0615 
0616 int KexiUtils::spacingHint()
0617 {
0618     return QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
0619 }
0620 
0621 void KexiUtils::setStandardMarginsAndSpacing(QLayout *layout)
0622 {
0623     setMargins(layout, KexiUtils::marginHint());
0624     layout->setSpacing(KexiUtils::spacingHint());
0625 }
0626 
0627 void KexiUtils::setMargins(QLayout *layout, int value)
0628 {
0629     layout->setContentsMargins(value, value, value, value);
0630 }
0631 
0632 void KexiUtils::replaceColors(QPixmap* original, const QColor& color)
0633 {
0634     Q_ASSERT(original);
0635     QImage dest(original->toImage());
0636     replaceColors(&dest, color);
0637     *original = QPixmap::fromImage(dest);
0638 }
0639 
0640 void KexiUtils::replaceColors(QImage* original, const QColor& color)
0641 {
0642     Q_ASSERT(original);
0643     *original = original->convertToFormat(QImage::Format_ARGB32_Premultiplied);
0644     QPainter p(original);
0645     p.setCompositionMode(QPainter::CompositionMode_SourceIn);
0646     p.fillRect(original->rect(), color);
0647 }
0648 
0649 bool KexiUtils::isLightColorScheme()
0650 {
0651     return KColorScheme(QPalette::Active, KColorScheme::Window).background().color().lightness() >= 128;
0652 }
0653 
0654 int KexiUtils::dimmedAlpha()
0655 {
0656     return 150;
0657 }
0658 
0659 QPalette KexiUtils::paletteWithDimmedColor(const QPalette &pal, QPalette::ColorGroup group,
0660                                            QPalette::ColorRole role)
0661 {
0662     QPalette result(pal);
0663     QColor color(result.color(group, role));
0664     color.setAlpha(dimmedAlpha());
0665     result.setColor(group, role, color);
0666     return result;
0667 }
0668 
0669 QPalette KexiUtils::paletteWithDimmedColor(const QPalette &pal, QPalette::ColorRole role)
0670 {
0671     QPalette result(pal);
0672     QColor color(result.color(role));
0673     color.setAlpha(dimmedAlpha());
0674     result.setColor(role, color);
0675     return result;
0676 }
0677 
0678 QPalette KexiUtils::paletteForReadOnly(const QPalette &palette)
0679 {
0680     QPalette p(palette);
0681     p.setBrush(QPalette::Base, palette.brush(QPalette::Disabled, QPalette::Base));
0682     p.setBrush(QPalette::Text, palette.brush(QPalette::Disabled, QPalette::Text));
0683     p.setBrush(QPalette::Highlight, palette.brush(QPalette::Disabled, QPalette::Highlight));
0684     p.setBrush(QPalette::HighlightedText, palette.brush(QPalette::Disabled, QPalette::HighlightedText));
0685     return p;
0686 }
0687 
0688 void KexiUtils::setBackgroundColor(QWidget *widget, const QColor &color)
0689 {
0690     widget->setAutoFillBackground(true);
0691     QPalette pal(widget->palette());
0692     pal.setColor(widget->backgroundRole(), color);
0693     widget->setPalette(pal);
0694 }
0695 
0696 //---------------------
0697 
0698 void KexiUtils::installRecursiveEventFilter(QObject *object, QObject *filter)
0699 {
0700     if (!object || !filter || !object->isWidgetType())
0701         return;
0702 
0703 //    qDebug() << "Installing event filter on widget:" << object
0704 //        << "directed to" << filter->objectName();
0705     object->installEventFilter(filter);
0706 
0707     const QObjectList list(object->children());
0708     foreach(QObject *obj, list) {
0709         installRecursiveEventFilter(obj, filter);
0710     }
0711 }
0712 
0713 void KexiUtils::removeRecursiveEventFilter(QObject *object, QObject *filter)
0714 {
0715     object->removeEventFilter(filter);
0716     if (!object->isWidgetType())
0717         return;
0718 
0719     const QObjectList list(object->children());
0720     foreach(QObject *obj, list) {
0721         removeRecursiveEventFilter(obj, filter);
0722     }
0723 }
0724 
0725 PaintBlocker::PaintBlocker(QWidget* parent)
0726  : QObject(parent)
0727  , m_enabled(true)
0728 {
0729     parent->installEventFilter(this);
0730 }
0731 
0732 void PaintBlocker::setEnabled(bool set)
0733 {
0734     m_enabled = set;
0735 }
0736 
0737 bool PaintBlocker::enabled() const
0738 {
0739     return m_enabled;
0740 }
0741 
0742 bool PaintBlocker::eventFilter(QObject* watched, QEvent* event)
0743 {
0744     if (m_enabled && watched == parent() && event->type() == QEvent::Paint) {
0745         return true;
0746     }
0747     return false;
0748 }
0749 
0750 tristate KexiUtils::openHyperLink(const QUrl &url, QWidget *parent, const OpenHyperlinkOptions &options)
0751 {
0752 #ifdef KEXI_MOBILE
0753     //! @todo
0754     Q_UNUSED(url)
0755     Q_UNUSED(parent)
0756     Q_UNUSED(options)
0757 #else
0758     if (url.isLocalFile()) {
0759         QFileInfo fileInfo(url.toLocalFile());
0760         if (!fileInfo.exists()) {
0761             KMessageBox::sorry(parent, xi18nc("@info", "The file or directory <filename>%1</filename> does not exist.", fileInfo.absoluteFilePath()));
0762             return false;
0763         }
0764     }
0765 
0766     if (!url.isValid()) {
0767         KMessageBox::sorry(parent, xi18nc("@info", "Invalid hyperlink <link>%1</link>.",
0768                                           url.url(QUrl::PreferLocalFile)));
0769         return false;
0770     }
0771 
0772     QMimeDatabase db;
0773     QString type = db.mimeTypeForUrl(url).name();
0774 
0775     if (!options.allowExecutable && KRun::isExecutableFile(url, type)) {
0776         KMessageBox::sorry(parent, xi18nc("@info", "Executable <link>%1</link> not allowed.",
0777                                           url.url(QUrl::PreferLocalFile)));
0778         return false;
0779     }
0780 
0781     if (!options.allowRemote && !url.isLocalFile()) {
0782         KMessageBox::sorry(parent, xi18nc("@info", "Remote hyperlink <link>%1</link> not allowed.",
0783                                           url.url(QUrl::PreferLocalFile)));
0784         return false;
0785     }
0786 
0787     if (KRun::isExecutableFile(url, type)) {
0788         int ret = KMessageBox::questionYesNo(parent
0789                     , xi18nc("@info", "Do you want to run this file?"
0790                             "<warning>Running executables can be dangerous.</warning>")
0791                     , QString()
0792                     , KGuiItem(xi18nc("@action:button Run script file", "Run"), koIconName("system-run"))
0793                     , KStandardGuiItem::no()
0794                     , "AllowRunExecutable", KMessageBox::Notify | KMessageBox::Dangerous);
0795 
0796         if (ret != KMessageBox::Yes) {
0797             return cancelled;
0798         }
0799     }
0800 
0801     switch(options.tool) {
0802         case OpenHyperlinkOptions::DefaultHyperlinkTool:
0803 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0804         {
0805             auto *job = new KIO::OpenUrlJob(url, type);
0806             job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parent));
0807             job->setRunExecutables(true);
0808             job->start();
0809             return true;
0810         }
0811 #elif KIO_VERSION >= QT_VERSION_CHECK(5, 31, 0)
0812             return KRun::runUrl(url, type, parent, KRun::RunFlags(KRun::RunExecutables));
0813 #else
0814             return KRun::runUrl(url, type, parent);
0815 #endif
0816         case OpenHyperlinkOptions::BrowserHyperlinkTool:
0817             return QDesktopServices::openUrl(url);
0818         case OpenHyperlinkOptions::MailerHyperlinkTool:
0819             return QDesktopServices::openUrl(url);
0820         default:;
0821     }
0822 #endif
0823     return false;
0824 }
0825 
0826 // ----
0827 
0828 KexiDBDebugTreeWidget::KexiDBDebugTreeWidget(QWidget *parent)
0829  : QTreeWidget(parent)
0830 {
0831 }
0832 
0833 void KexiDBDebugTreeWidget::copy()
0834 {
0835     if (currentItem()) {
0836         qApp->clipboard()->setText(currentItem()->text(0));
0837     }
0838 }
0839 
0840 // ----
0841 
0842 DebugWindow::DebugWindow(QWidget * parent)
0843     : QWidget(parent, Qt::Window)
0844 {
0845 }
0846 
0847 // ----
0848 
0849 QSize KexiUtils::comboBoxArrowSize(QStyle *style)
0850 {
0851     if (!style) {
0852         style = QApplication::style();
0853     }
0854     QStyleOptionComboBox cbOption;
0855     return style->subControlRect(QStyle::CC_ComboBox, &cbOption, QStyle::SC_ComboBoxArrow).size();
0856 }
0857 
0858 void KexiUtils::addDirtyFlag(QString *text)
0859 {
0860     Q_ASSERT(text);
0861     *text = xi18nc("'Dirty (modified) object' flag", "%1*", *text);
0862 }
0863 
0864 //! From klocale_kde.cpp
0865 //! @todo KEXI3 support other OS-es (use from klocale_*.cpp)
0866 static QByteArray systemCodeset()
0867 {
0868     QByteArray codeset;
0869 #if HAVE_LANGINFO_H
0870     // Qt since 4.2 always returns 'System' as codecForLocale and KDE (for example
0871     // KEncodingFileDialog) expects real encoding name. So on systems that have langinfo.h use
0872     // nl_langinfo instead, just like Qt compiled without iconv does. Windows already has its own
0873     // workaround
0874 
0875     codeset = nl_langinfo(CODESET);
0876 
0877     if ((codeset == "ANSI_X3.4-1968") || (codeset == "US-ASCII")) {
0878         // means ascii, "C"; QTextCodec doesn't know, so avoid warning
0879         codeset = "ISO-8859-1";
0880     }
0881 #endif
0882     return codeset;
0883 }
0884 
0885 QTextCodec* g_codecForEncoding = 0;
0886 
0887 bool setEncoding(int mibEnum)
0888 {
0889     QTextCodec *codec = QTextCodec::codecForMib(mibEnum);
0890     if (codec) {
0891         g_codecForEncoding = codec;
0892     }
0893 
0894     return codec != 0;
0895 }
0896 
0897 //! From klocale_kde.cpp
0898 static void initEncoding()
0899 {
0900     if (!g_codecForEncoding) {
0901         // This all made more sense when we still had the EncodingEnum config key.
0902 
0903         QByteArray codeset = systemCodeset();
0904 
0905         if (!codeset.isEmpty()) {
0906             QTextCodec *codec = QTextCodec::codecForName(codeset);
0907             if (codec) {
0908                 setEncoding(codec->mibEnum());
0909             }
0910         } else {
0911             setEncoding(QTextCodec::codecForLocale()->mibEnum());
0912         }
0913 
0914         if (!g_codecForEncoding) {
0915             qWarning() << "Cannot resolve system encoding, defaulting to ISO 8859-1.";
0916             const int mibDefault = 4; // ISO 8859-1
0917             setEncoding(mibDefault);
0918         }
0919         Q_ASSERT(g_codecForEncoding);
0920     }
0921 }
0922 
0923 QByteArray KexiUtils::encoding()
0924 {
0925     initEncoding();
0926     return g_codecForEncoding->name();
0927 }
0928 
0929 namespace {
0930 
0931 //! @internal for graphicEffectsLevel()
0932 class GraphicEffectsLevel
0933 {
0934 public:
0935     GraphicEffectsLevel() {
0936         KConfigGroup g(KSharedConfig::openConfig(), "KDE-Global GUI Settings");
0937 
0938         // Asking for hasKey we do not ask for graphicEffectsLevelDefault() that can
0939         // contain some very slow code. If we can save that time, do it. (ereslibre)
0940         if (g.hasKey("GraphicEffectsLevel")) {
0941             value = ((GraphicEffects) g.readEntry("GraphicEffectsLevel", QVariant((int) NoEffects)).toInt());
0942             return;
0943         }
0944 
0945         // For now, let always enable animations by default. The plan is to make
0946         // this code a bit smarter. (ereslibre)
0947         value = ComplexAnimationEffects;
0948     }
0949     GraphicEffects value;
0950 };
0951 }
0952 
0953 Q_GLOBAL_STATIC(GraphicEffectsLevel, g_graphicEffectsLevel)
0954 
0955 GraphicEffects KexiUtils::graphicEffectsLevel()
0956 {
0957     return g_graphicEffectsLevel->value;
0958 }
0959 
0960 #if defined Q_OS_UNIX && !defined Q_OS_MACOS
0961 //! For detectedDesktopSession()
0962 class DetectedDesktopSession
0963 {
0964 public:
0965     DetectedDesktopSession() : name(detect()), isKDE(name == QStringLiteral("KDE"))
0966     {
0967     }
0968     const QByteArray name;
0969     const bool isKDE;
0970 
0971 private:
0972     static QByteArray detect() {
0973         // https://www.freedesktop.org/software/systemd/man/pam_systemd.html#%24XDG_SESSION_DESKTOP
0974         // KDE, GNOME, UNITY, LXDE, MATE, XFCE...
0975         const QString xdgSessionDesktop = qgetenv("XDG_SESSION_DESKTOP").trimmed();
0976         if (!xdgSessionDesktop.isEmpty()) {
0977             return xdgSessionDesktop.toLatin1().toUpper();
0978         }
0979         // Similar to detectDesktopEnvironment() from qgenericunixservices.cpp
0980         const QString xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").trimmed();
0981         if (!xdgCurrentDesktop.isEmpty()) {
0982             return xdgCurrentDesktop.toLatin1().toUpper();
0983         }
0984         // fallbacks
0985         if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) {
0986             return QByteArrayLiteral("KDE");
0987         }
0988         if (!qEnvironmentVariableIsEmpty("GNOME_DESKTOP_SESSION_ID")) {
0989             return QByteArrayLiteral("GNOME");
0990         }
0991         const QString desktopSession = qgetenv("DESKTOP_SESSION").trimmed();
0992         if (desktopSession.compare("gnome", Qt::CaseInsensitive) == 0) {
0993             return QByteArrayLiteral("GNOME");
0994         } else if (desktopSession.compare("xfce", Qt::CaseInsensitive) == 0) {
0995             return QByteArrayLiteral("XFCE");
0996         }
0997         return QByteArray();
0998     }
0999 };
1000 
1001 Q_GLOBAL_STATIC(DetectedDesktopSession, s_detectedDesktopSession)
1002 
1003 QByteArray KexiUtils::detectedDesktopSession()
1004 {
1005     return s_detectedDesktopSession->name;
1006 }
1007 
1008 bool KexiUtils::isKDEDesktopSession()
1009 {
1010     return s_detectedDesktopSession->isKDE;
1011 }
1012 
1013 bool KexiUtils::shouldUseNativeDialogs()
1014 {
1015 #if defined Q_OS_UNIX && !defined Q_OS_MACOS
1016     return isKDEDesktopSession() || detectedDesktopSession().isEmpty();
1017 #else
1018     return true;
1019 #endif
1020 }
1021 
1022 
1023 //! @return value of XFCE property @a property for channel @a channel
1024 //! Sets the value pointed by @a ok to status.
1025 //! @todo Should be part of desktop integration or KF
1026 static QByteArray xfceSettingValue(const QByteArray &channel, const QByteArray &property,
1027                                    bool *ok = nullptr)
1028 {
1029     if (ok) {
1030         *ok = false;
1031     }
1032     QByteArray result;
1033     const QString program = QString::fromLatin1("xfconf-query");
1034     const QString programPath = QStandardPaths::findExecutable(program);
1035     const QStringList arguments{ program, "-c", channel, "-p", property };
1036     QProcess process;
1037     process.start(programPath, arguments);//, QIODevice::ReadOnly | QIODevice::Text);
1038     if (!process.waitForStarted()) {
1039         qWarning() << "Count not execute command" << programPath << arguments
1040                    << "error:" << process.error();
1041         return QByteArray();
1042     }
1043     const int exitCode = process.exitCode(); // !=0 e.g. for "no such property or channel"
1044     if (exitCode != 0 || !process.waitForFinished() || process.exitStatus() != QProcess::NormalExit) {
1045         qWarning() << "Count not finish command" << programPath << arguments
1046                    << "error:" << process.error() << "exit code:" << exitCode
1047                    << "exit status:" << process.exitStatus();
1048         return QByteArray();
1049     }
1050     if (ok) {
1051         *ok = true;
1052     }
1053     result = process.readAll();
1054     result.chop(1);
1055     return result;
1056 }
1057 
1058 #else
1059 
1060 QByteArray KexiUtils::detectedDesktopSession()
1061 {
1062     return QByteArray();
1063 }
1064 #endif
1065 
1066 bool KexiUtils::activateItemsOnSingleClick(QWidget *widget)
1067 {
1068     const KConfigGroup mainWindowGroup = KSharedConfig::openConfig()->group("MainWindow");
1069 #ifdef Q_OS_WIN
1070     return mainWindowGroup.readEntry("SingleClickOpensItem", true);
1071 #else
1072     if (mainWindowGroup.hasKey("SingleClickOpensItem")) {
1073         return mainWindowGroup.readEntry("SingleClickOpensItem", true);
1074     }
1075     const QByteArray desktopSession = detectedDesktopSession();
1076     if (desktopSession == "XFCE") {
1077         /* To test:
1078            Set to true: fconf-query -c xfce4-desktop -p /desktop-icons/single-click -n -t bool -s true
1079            Get value: xfconf-query -c xfce4-desktop -p /desktop-icons/single-click
1080            Reset: xfconf-query -c xfce4-desktop -p /desktop-icons/single-click -r
1081         */
1082         return xfceSettingValue("xfce4-desktop", "/desktop-icons/single-click") == "true";
1083     }
1084 # if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
1085     Q_UNUSED(widget)
1086     return QApplication::styleHints()->singleClickActivation();
1087 # else
1088     QStyle *style = widget ? widget->style() : QApplication::style();
1089     return style->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, widget);
1090 # endif
1091 #endif
1092 }
1093 
1094 // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
1095 QColor KexiUtils::inactiveTitleColor()
1096 {
1097 #ifdef Q_OS_WIN
1098     return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTION));
1099 #else
1100     KConfigGroup g(KSharedConfig::openConfig(), "WM");
1101     return g.readEntry("inactiveBackground", QColor(224, 223, 222));
1102 #endif
1103 }
1104 
1105 // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
1106 QColor KexiUtils::inactiveTextColor()
1107 {
1108 #ifdef Q_OS_WIN
1109     return qt_colorref2qrgb(GetSysColor(COLOR_INACTIVECAPTIONTEXT));
1110 #else
1111     KConfigGroup g(KSharedConfig::openConfig(), "WM");
1112     return g.readEntry("inactiveForeground", QColor(75, 71, 67));
1113 #endif
1114 }
1115 
1116 // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
1117 QColor KexiUtils::activeTitleColor()
1118 {
1119 #ifdef Q_OS_WIN
1120     return qt_colorref2qrgb(GetSysColor(COLOR_ACTIVECAPTION));
1121 #else
1122     KConfigGroup g(KSharedConfig::openConfig(), "WM");
1123     return g.readEntry("activeBackground", QColor(48, 174, 232));
1124 #endif
1125 }
1126 
1127 // NOTE: keep this in sync with kdebase/workspace/kcontrol/colors/colorscm.cpp
1128 QColor KexiUtils::activeTextColor()
1129 {
1130 #ifdef Q_OS_WIN
1131     return qt_colorref2qrgb(GetSysColor(COLOR_CAPTIONTEXT));
1132 #else
1133     KConfigGroup g(KSharedConfig::openConfig(), "WM");
1134     return g.readEntry("activeForeground", QColor(255, 255, 255));
1135 #endif
1136 }
1137 
1138 QString KexiUtils::makeStandardCaption(const QString &userCaption, CaptionFlags flags)
1139 {
1140     QString caption = KAboutData::applicationData().displayName();
1141     if (caption.isEmpty()) {
1142         return QCoreApplication::instance()->applicationName();
1143     }
1144     QString captionString = userCaption.isEmpty() ? caption : userCaption;
1145 
1146     // If the document is modified, add '[modified]'.
1147     if (flags & ModifiedCaption) {
1148         captionString += QString::fromUtf8(" [") + xi18n("modified") + QString::fromUtf8("]");
1149     }
1150 
1151     if (!userCaption.isEmpty()) {
1152         // Add the application name if:
1153         // User asked for it, it's not a duplication  and the app name (caption()) is not empty
1154         if (flags & AppNameCaption &&
1155                 !caption.isEmpty() &&
1156                 !userCaption.endsWith(caption)) {
1157             // TODO: check to see if this is a transient/secondary window before trying to add the app name
1158             //       on platforms that need this
1159             captionString += xi18nc("Document/application separator in titlebar", " – ") + caption;
1160         }
1161     }
1162     return captionString;
1163 }
1164 
1165 QString themedIconName(const QString &name)
1166 {
1167     static bool firstUse = true;
1168     if (firstUse) {
1169         // workaround for some kde-related crash
1170         const bool _unused = KIconLoader::global()->iconPath(name, KIconLoader::NoGroup, true).isEmpty();
1171         Q_UNUSED(_unused);
1172         firstUse = false;
1173     }
1174 
1175     // try load themed icon
1176     const QColor background = qApp->palette().background().color();
1177     const bool useDarkIcons = background.value() > 100;
1178     return QLatin1String(useDarkIcons ? "dark_" : "light_") + name;
1179 }
1180 
1181 QIcon themedIcon(const QString &name)
1182 {
1183     const QString realName(themedIconName(name));
1184     const QIcon icon = QIcon::fromTheme(realName);
1185 
1186     // fallback
1187     if (icon.isNull()) {
1188         return QIcon::fromTheme(name);
1189     }
1190     return icon;
1191 }
1192 
1193 QString KexiUtils::localizedStringToHtmlSubstring(const KLocalizedString &string)
1194 {
1195     return string.isEmpty() ? QString()
1196                             : string.toString(Kuit::RichText)
1197                                   .remove(QLatin1String("<html>"))
1198                                   .remove(QLatin1String("</html>"));
1199 }
1200 
1201 QString KexiUtils::localizedSentencesToHtml(const KLocalizedString &part1, const KLocalizedString &part2,
1202                                     const KLocalizedString &part3, const KLocalizedString &part4,
1203                                     const KLocalizedString &part5, const KLocalizedString &part6)
1204 {
1205     return xi18nc("@info/plain Concatenated sentence1 sentence2 ...", "<html>%1%2%3%4%5%6</html>",
1206                   KexiUtils::localizedStringToHtmlSubstring(part1),
1207                   KexiUtils::localizedStringToHtmlSubstring(part2),
1208                   KexiUtils::localizedStringToHtmlSubstring(part3),
1209                   KexiUtils::localizedStringToHtmlSubstring(part4),
1210                   KexiUtils::localizedStringToHtmlSubstring(part5),
1211                   KexiUtils::localizedStringToHtmlSubstring(part6));
1212 }
1213 
1214 bool KexiUtils::cursorAtEnd(const QLineEdit *lineEdit)
1215 {
1216     if (!lineEdit) {
1217         return false;
1218     }
1219     if (lineEdit->inputMask().isEmpty()) {
1220         return lineEdit->cursorPosition() >= lineEdit->displayText().length();
1221     } else {
1222         return lineEdit->cursorPosition() >= (lineEdit->displayText().length() - 1);
1223     }
1224 }
1225 
1226 QDebug operator<<(QDebug dbg, const QDomNode &node)
1227 {
1228   QString s;
1229   QTextStream str(&s, QIODevice::WriteOnly);
1230   node.save(str, 2);
1231   dbg << qPrintable(s);
1232   return dbg;
1233 }