/** Copyright (c) 2020-2023 The Creators of Simphone

    class qtfix: workarounds for Qt graphic elements and support of large fonts
    class fixPasteFilter (QObject): prevent pasting in QInputDialog
    class fixProxyStyle (QProxyStyle): fix scrollbar width, menu icons and indicators
    class fixMessageBox (QMessageBox): message box with buttons that do not close it

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "qtfix.h"

#include "simstar.h"
#include "../simcore/simapi.h"

#include <QTranslator>
#include <QLibraryInfo>
#include <QHeaderView>
#include <QFontDatabase>
#include <QKeyEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QLibrary>
#include <QLayout>
#include <QMenuBar>
#include <QLabel>
#include <QTextDocument>
#include <QBitmap>
#include <QPixmapCache>
#include <QWindow>

#ifdef __unix__ // usleep
#include <unistd.h>
#endif

#ifdef _WIN32
#include <windows.h>

extern QLocale qt_localeFromLCID(LCID id);
#endif

#define QT_TRANSLATION_NAME "qtbase"
#define QT_APPLICATION_NAME "qsimphone"
#define QT_ADDITIONAL_NAME "simcountry"
#define QT_SYSTEM_NAME "simcore"
#define QT_PATCH_NAME "-sim"

char ** qtfix::mc_arguments;
const char * qtfix::mc_styleArgument = 0;
char qtfix::mc_style;
QByteArray qtfix::mc_styleName;
QString qtfix::mc_styleSheetArgument;
QString qtfix::mc_setStyle;
QString qtfix::mc_setStyleSheet;
int qtfix::mc_pointSize = 0;
int qtfix::mc_maxPointSize = 1;
char qtfix::mc_language[];
QLocale qtfix::mc_locale;
QElapsedTimer qtfix::mc_elapsed;
QColor qtfix::mc_color;
QStringList qtfix::mc_pixmapColors;
QStringList qtfix::mc_indicators;

static QList<QTranslator *> g_translators;
static QList<QCheckBox *> g_checkBoxes;
static QFont * g_font = 0;

fixMessageBox::fixMessageBox(Icon icon, const QString & title, const QString & text, StandardButtons buttons,
                             QWidget * parent, Qt::WindowFlags f)
  : Parent(icon, title, text, buttons, parent, f)
{
}

QPushButton * fixMessageBox::addNocloseButton(const QString & text, ButtonRole role)
{
  QPushButton * button = Parent::addButton(text, role);
  m_buttons.append(button);
  return button;
}

void fixMessageBox::done(int r)
{
  if (m_buttons.indexOf(clickedButton()) < 0) Parent::done(r);
}

int fixProxyStyle::pixelMetric(PixelMetric metric, const QStyleOption * option, const QWidget * widget) const
{
  int result = QProxyStyle::pixelMetric(metric, option, widget);
  switch (int(metric)) {
    case PM_SmallIconSize:
      if (widget) {
        QVariant fontH = widget->property("scaled-icon-size");
        if (fontH.isValid()) result = fontH.toInt();
      }
      break;

    case PM_MenuButtonIndicator:
      if (widget) {
        QVariant fontH = widget->property("menu-button-indicator");
        if (fontH.isValid()) result = fontH.toInt();
      }
      break;

    case PM_ScrollBarExtent:
      int fontH = QFontMetrics(qApp->font("QAbstractItemView")).lineSpacing();
      if (fontH > result) result = fontH;
  }
  return result;
}

QIcon fixProxyStyle::standardIcon(StandardPixmap icon, const QStyleOption * option, const QWidget * widget) const
{
  QIcon result = QProxyStyle::standardIcon(icon, option, widget);
  if (icon != SP_MessageBoxQuestion || !qtfix::getColor().isValid() || qtfix::getStyle() != 'm') return result;

  int iconSize = pixelMetric(PM_MessageBoxIconSize, 0, widget);
  return QIcon(qtfix::fixPixmapColor(result.pixmap(0, QSize(iconSize, iconSize)), qtfix::getColor()));
}

void fixProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption * option,
                                  QPainter * painter, const QWidget * widget) const
{
  switch (int(element)) {
    case PE_PanelButtonCommand:
      if (widget->property("no-panel-button").isValid()) return;
      break;

    case PE_IndicatorHeaderArrow:
      if (widget->property("no-header-arrow").isValid()) {
        QCommonStyle::drawPrimitive(element, option, painter, widget);
        return;
      }
  }
  QProxyStyle::drawPrimitive(element, option, painter, widget);
}

void fixProxyStyle::drawControl(ControlElement element, const QStyleOption * option,
                                QPainter * painter, const QWidget * widget) const
{
#ifdef __APPLE__
  if (element == CE_MenuItem && qtfix::getStyle() == 'm' && qApp->styleSheet().isEmpty() && widget) {
    const QStyleOptionMenuItem * oldOpt = (const QStyleOptionMenuItem *)option;
    if (oldOpt && !oldOpt->icon.isNull() && oldOpt->checkType != QStyleOptionMenuItem::NotCheckable) {
      QVariant pixmap = widget->property(oldOpt->checked ? "menu-item-checked" : "menu-item-unchecked");
      if (pixmap.isValid()) {
        QStyleOptionMenuItem newOpt = *oldOpt;
        QPixmap icon = pixmap.value<QPixmap>();
        QPalette::ColorRole role = newOpt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text;
        QColor color = newOpt.palette.color(role);
        newOpt.checked = false;
        newOpt.icon = color.value() > 16 ? qtfix::fixPixmapColor(icon, color) : icon;
        QProxyStyle::drawControl(element, &newOpt, painter, widget);
        return;
      }
    }
  }
#endif
  QProxyStyle::drawControl(element, option, painter, widget);
}

QSize fixProxyStyle::sizeFromContents(ContentsType type, const QStyleOption * option,
                                      const QSize & size, const QWidget * widget) const
{
  switch (int(type)) {
    case CT_SizeGrip:
      if (!widget) break;
      return QSize(QFontMetrics(widget->font()).ascent(), QFontMetrics(widget->font()).ascent());

    case CT_HeaderSection:
      if (!widget || !widget->property("no-header-arrow").isValid()) break;
      return QCommonStyle::sizeFromContents(type, option, size, widget);
  }

  QSize newSize = QProxyStyle::sizeFromContents(type, option, size, widget);
  if (type != CT_MenuItem || qtfix::getStyle() != 'g' || qtfix::getFontSize() <= 0) return newSize;
  return QSize(newSize.width(), QCommonStyle::sizeFromContents(type, option, size, widget).height());
}

bool qtfix::hasAltModifier(QKeyEvent * keyEvent, Qt::KeyboardModifiers modifiers)
{
#ifdef _WIN32
  return keyEvent->modifiers() & modifiers && !(keyEvent->nativeModifiers() & 0x40); // WindowsNativeModifiers::AltRight
#else
  return keyEvent->modifiers() & modifiers;
#endif
}

int qtfix::hasSelectedRows(const QItemSelectionModel * model)
{
  int count = 0;
  if (model->hasSelection()) {
    const QItemSelection selection = model->selection();
    for (int i = 0; i < selection.count(); i++) {
      const QItemSelectionRange & range = selection.at(i);
      count += range.bottom() - range.top() + 1;
      if (count > 2) break;
    }
  }
  return count;
}

QString qtfix::getVersion()
{
  return qVersion() + QString(" (") + qApp->platformName() + ":" + mc_styleName + ")";
}

QString qtfix::getStyleOverride()
{
  QString qs = qgetenv("QT_STYLE_OVERRIDE");
  return mc_styleArgument ? mc_styleArgument : qs.isEmpty() ? QString() : qs;
}

bool qtfix::setStyle(const QString & style, const QString & styleSheet)
{
  QString qs = qgetenv("QT_STYLE_OVERRIDE");
  bool ok = !style.isEmpty() && !mc_styleArgument && qs.isEmpty();
  mc_setStyle = style.isEmpty() && !mc_styleArgument && !qs.isEmpty() ? qs : style;
  mc_setStyleSheet = styleSheet;
#ifndef _WIN32
  qs = getStyleSetting();
#ifdef __APPLE__
  if (qs.isEmpty() || qs.startsWith("macintosh", Qt::CaseInsensitive)) return ok;
#else
  if (qs.isEmpty() || qs.startsWith("gtk", Qt::CaseInsensitive)) return ok;
#endif
  QApplication::setDesktopSettingsAware(false);
#endif
  return ok;
}

static const char * fixLocale(const char * locale)
{
#ifdef __unix__
  static char * currentLocale = 0;
  char * s = currentLocale;
  if (s) free(s);
  if (*locale && strcmp(locale, "C")) {
    QString qs = locale;
    s = setlocale(LC_MESSAGES, qs.append(".UTF-8").toUtf8().data());
    if (s) return currentLocale = strdup(s);
  }
  s = setlocale(LC_MESSAGES, locale);
  return currentLocale = s ? strdup(s) : 0;
#else
#ifdef _WIN32
  SetThreadUILanguage(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT));
#endif
  return locale;
#endif
}

static QLocale * fixLanguage(const QString & language)
{
  QLocale * locale = 0;
  if (!language.isEmpty()) {
    locale = new QLocale(language);
    if (locale->name() == "C") {
      delete locale;
      locale = 0;
      for (int i = int(QLocale::AnyLanguage);; i++) {
        QString qs = QLocale::languageToString(QLocale::Language(i));
        if (qs == "Unknown") break;
        if (!qs.compare(language, Qt::CaseInsensitive)) {
          locale = new QLocale(QLocale::Language(i));
          break;
        }
      }
    }
  }
  return locale;
}

static bool fixTranslator(QTranslator * translator, const QLocale & locale, const QString & language,
                          const char * name, QString directory = QString())
{
  QStringList languages = locale.uiLanguages();
  QString fileName = QString(":/") + name;
  if (!language.isEmpty() && !languages.isEmpty() && !QFile(fileName + "_" + languages[0] + ".qm").exists()) {
    if (directory.isEmpty()) directory = QCoreApplication::applicationDirPath();
    if (!directory.isEmpty()) {
#ifndef _WIN32
      printf("Qt: trying to load file %s/%s_%s.qm\n", directory.toUtf8().data(), name, languages[0].toUtf8().data());
#endif
      if (translator->load(locale, name, "_", directory)) return true;
    }
  }
  return translator->load(locale, fileName, "_");
}

static bool setTranslator(QTranslator *& translator)
{
  if (!qApp->installTranslator(translator)) return false;

  g_translators.append(translator);
  translator = 0;
  return true;
}

static const char * fixTranslation(const QLocale * locale, const QString & language)
{
  const QLocale * qLocale = locale;
  QTranslator * translator = new QTranslator;
#ifdef QT_TRANSLATION_NAME
  QString qs;
  bool ok = true;
#ifdef QT_PATCH_NAME
  if (!QString(qVersion()).endsWith(QT_PATCH_NAME)) qs = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
#endif
  if (fixTranslator(translator, locale ? *locale : QLocale(), language, QT_TRANSLATION_NAME, qs)) {
    ok = setTranslator(translator);
  }
  delete translator;
  translator = new QTranslator;
#endif

#ifdef _WIN32
  static char setLocale[23];
  LCID lcid = GetUserDefaultUILanguage();
  if (locale) {
    LCID id;
    for (id = 0x401; id < 0x10000; id++) {
      if (qt_localeFromLCID(id) == *locale) {
        SetThreadUILanguage(lcid = id);
        break;
      }
    }
    if (id >= 0x10000) locale = 0;
  }
  sprintf(setLocale, "%ld", lcid);
#ifdef QT_SYSTEM_NAME
  if (sim_error_init_language(lcid) >= 0 &&
      fixTranslator(translator, locale ? *locale : QLocale(), language, QT_SYSTEM_NAME)) {
    if (!setTranslator(translator)) strcpy(setLocale, "bad_" QT_SYSTEM_NAME "_translator");
  } else {
    //printf("simcore: no locale\n");
    strcpy(setLocale, fixLocale("C"));
  }
#endif
#else
  const char * setLocale;
  setLocale = fixLocale(!locale ? "" : locale->language() == QLocale::English ? "C" : locale->name().toUtf8().data());
#ifdef __unix__
  if (!setLocale && locale && locale->language() != QLocale::English) {
    QLocale * languageLocale = fixLanguage(QLocale::languageToString(locale->language()));
    if (languageLocale) setLocale = fixLocale(languageLocale->name().toUtf8().data());
    delete languageLocale;
  }
#ifdef QT_SYSTEM_NAME
  if (setLocale && sim_error_init_language(1) >= 0 &&
      fixTranslator(translator, locale ? *locale : QLocale(), language, QT_SYSTEM_NAME)) {
    if (!setTranslator(translator)) setLocale = "bad_" QT_SYSTEM_NAME "_translator";
  } else {
    //printf("simcore: no locale %s\n", setLocale);
    setLocale = fixLocale("C");
  }
#endif
#endif // __unix__
#endif // _WIN32
  delete translator;
#ifdef QT_ADDITIONAL_NAME
  if (fixTranslator(translator = new QTranslator, qLocale ? *qLocale : QLocale(), language, QT_ADDITIONAL_NAME)) {
    if (!setTranslator(translator)) return "bad_" QT_ADDITIONAL_NAME "_translator";
  }
  delete translator;
#endif
#ifdef QT_TRANSLATION_NAME
  if (!ok) return "bad_" QT_TRANSLATION_NAME "_translator";
#endif
  return setLocale;
}

static const char * setTranslation(const QString & language)
{
  const char * setLocale = "";
#ifdef QT_APPLICATION_NAME
  if (language != "none") {
    QLocale * locale = fixLanguage(language);
    QTranslator * translator = new QTranslator;
    if (locale && fixTranslator(translator, *locale, language, QT_APPLICATION_NAME)) {
      setLocale = fixTranslation(locale, language);
      QLocale::setDefault(*locale);
    } else if (fixTranslator(translator, QLocale(), language, QT_APPLICATION_NAME)) {
      setLocale = fixTranslation(0, language);
    } else {
      setLocale = fixLocale("C");
      if (!fixTranslator(translator, QLocale(QLocale::English), language, QT_APPLICATION_NAME)) translator = 0;
    }
    if (translator && !setTranslator(translator)) setLocale = "bad_" QT_APPLICATION_NAME "_translator";
    delete translator;
    delete locale;
  }
#endif
  return setLocale;
}

bool qtfix::setLanguage(const QString & language)
{
  bool ok = true;
  for (int i = g_translators.size(); i; i--) {
    if (!qApp->removeTranslator(g_translators[i - 1])) ok = false;
    delete g_translators[i - 1];
  }
  g_translators = QList<QTranslator *>();
  QLocale::setDefault(mc_locale);
#ifdef QT_SYSTEM_NAME
  sim_error_init_language(-1);
#endif
  fixLocale("");

  if (!language.isNull()) {
    const char * setLocale = setTranslation(language);
    strncpy(mc_language, language.toUtf8().data(), sizeof(mc_language) - 1);
#ifdef QT_SYSTEM_NAME
    if (setLocale && !strcmp(setLocale, "C")) sim_error_init_language(-1);
#endif
  }

#ifndef _WIN32
  if (!ok) fprintf(stderr, "Qt: failed to remove translator\n");
#endif
  return ok;
}

qint64 qtfix::setOverrideCursor(bool start)
{
  if (!start) {
    qint64 elapsed = mc_elapsed.elapsed();
    QGuiApplication::restoreOverrideCursor();
    mc_elapsed.invalidate();
    return elapsed;
  }

  QGuiApplication::setOverrideCursor(Qt::WaitCursor);
  mc_elapsed.start();
  return -1;
}

bool qtfix::setGeometry(QWidget * window, QRect geometry, bool proper)
{
#ifndef __APPLE__
  QRect desktop = QApplication::desktop()->availableGeometry();
  if (proper ? !desktop.contains(geometry) : !desktop.intersects(geometry)) {
    geometry.moveBottom(qMin(geometry.bottom(), desktop.bottom()));
    geometry.moveLeft(qMax(geometry.left(), desktop.left()));
    geometry.moveRight(qMin(geometry.right(), desktop.right()));
  }
  geometry.moveTop(qMax(geometry.top(), desktop.top()));
#endif
  window->setGeometry(geometry);
  return proper;
}

void qtfix::setCheckBox(QMessageBox * messageBox, QCheckBox * checkBox)
{
  if (messageBox) {
    if (checkBox) {
      checkBox->setParent(messageBox);
      checkBox->setFont(qApp->font("QMessageBox"));
      fixCheckBoxIcon(checkBox, checkBox->font());
      checkBox->setFocusPolicy(Qt::NoFocus);
      if (mc_style == 'g') g_checkBoxes.append(checkBox);
    } else {
      g_checkBoxes.removeOne(messageBox->checkBox());
    }
    messageBox->setCheckBox(checkBox);
  } else {
    for (int i = 0; i < g_checkBoxes.size(); i++) {
      g_checkBoxes[i]->setFont(qApp->font("QMessageBox"));
      fixCheckBoxIcon(g_checkBoxes[i], g_checkBoxes[i]->font());
      QLabel * label = (QLabel *)findChild(g_checkBoxes[i]->parent(), "QLabel");
      if (label) label->setFont(((QWidget *)g_checkBoxes[i]->parent())->font());
    }
  }
}

// i - init, s - size change, f - font change, p - palette change
void qtfix::setFont(char mode)
{
  if (mode == 'f') {
    if (mc_style != 'g' || mc_pointSize <= 0 || mc_pointSize == qApp->font().pointSize()) return;
    delete g_font;
    g_font = 0;
  }

  if (mode != 'p') {
    if (!g_font) {
      g_font = new QFont(qApp->font("QMessageBox"));
      g_font->setBold(false);
    } else {
      mode = 'p';
    }
    qApp->setFont(mc_pointSize > 0 ? fixFontSize(qApp->font(), "QMessageBox", "") : *g_font);
  }

  if (mc_style == 'f' && (mode == 'p' || mc_pointSize > mc_maxPointSize)) {
    static const char * arrows[] = { ":/upArrow", ":/downArrow", ":/leftArrow", ":/rightArrow" };
    static const char * degrees[] = { "0", "180", "-90", "90" };
    int fontH = QFontMetrics(qApp->font("QAbstractItemView")).lineSpacing();
    QStringList colors(mc_pixmapColors);
    QColor syscolor = QApplication::palette().windowText().color();
    QString qs;
    colors.append(qs.sprintf("%02x%02x%02xff", syscolor.blue(), syscolor.green(), syscolor.red()));
    colors.append(qs.sprintf("%02x%02x%02xcd", syscolor.blue(), syscolor.green(), syscolor.red()));
    for (unsigned j = 0; j < SIM_ARRAY_SIZE(degrees); j++) {
      for (int i = 0; i < colors.size(); i++) {
        QColor color = QColor(qs.sprintf("#%s", colors[i].toUtf8().data()));
        if (color.isValid()) {
          qs = "$qt_ia-:/qt-project.org/styles/commonstyle/images/fusion_arrow.png";
          QPixmapCache::remove(qs.append(colors[i]).append(degrees[j]));
          color = QColor(color.green(), color.red(), color.alpha(), color.blue());
          if (mc_pointSize > mc_maxPointSize) {
            QPixmapCache::insert(qs, fixPixmapColor(fixPixmapSize(QPixmap(arrows[j]), fontH, false), color));
          }
        }
      }
    }
  }
}

QStringList qtfix::fixArguments(int & argc, char **& argv)
{
  bool session = false;
  QStringList arguments;
  QString platform = qgetenv("QT_QPA_PLATFORM").toLower();
  mc_styleArgument = 0;
  for (int i = 1; i < argc; i++) {
    QString arg = QString(argv[i]).toLower();
    if (arg == "-session") {
      session = true;
    } else if (arg == "-style" && i + 1 < argc) {
      mc_styleArgument = argv[++i];
    } else if (arg.startsWith("-style=")) {
      mc_styleArgument = argv[i] + 8;
    } else if (arg == "-stylesheet" && i + 1 < argc) {
      mc_styleSheetArgument = argv[++i];
    } else if (arg.startsWith("-stylesheet=")) {
      mc_styleSheetArgument = QString(argv[i]).mid(12);
    } else if (arg == "-platform" && i + 1 < argc) {
      platform = QString(argv[++i]).toLower();
      arguments << arg << argv[i];
    }
  }
  mc_arguments = argv;
  const char ** args = (const char **)malloc(sizeof(*argv) * (argc + 7));
  if (args) {
    memcpy(args, argv, sizeof(*argv) * argc);
    argv = (char **)args;
    if (!mc_styleArgument && qgetenv("QT_STYLE_OVERRIDE").isEmpty() && (platform.isEmpty() || platform == "xcb")) {
#ifdef __unix__
      QLibrary libgtk("gtk-x11-2.0", 0, 0);
#ifdef QT_SYSTEM_NAME
      simtype path = sim_init_path("/libglib-");
      bool gtk = sim_get_type(path) == SIMNIL || sim_get_length(path);
      sim_string_free(path);
#else
      bool gtk = true;
#endif
      args[argc++] = "-style";
      args[argc++] = gtk && libgtk.resolve("gtk_init") ? "gtk" : "fusion";
      if (gtk && !strcmp(args[argc - 1], "fusion")) fprintf(stderr, "Qt: %s\n", libgtk.errorString().toUtf8().data());
#endif
    }
    args[argc] = 0;
  }
  return session ? QStringList() : arguments << "-minimized";
}

const char * qtfix::fixApplication(const QString & language)
{
  QProxyStyle * proxy = new fixProxyStyle;
  mc_styleName = qApp->style()->objectName().toLower().toUtf8();
  if (mc_styleName.isEmpty()) mc_styleName = proxy->baseStyle()->objectName().toLower().toUtf8();
  mc_style = 0;
  if (mc_styleName == "android") {
    mc_style = 'a';
  } else if (mc_styleName == "fusion") {
    mc_style = 'f';
  } else if (mc_styleName.startsWith("gtk")) {
    mc_style = 'g';
  } else if (mc_styleName.startsWith("macintosh")) {
    mc_style = 'm';
  } else if (mc_styleName.startsWith("windows")) {
    mc_style = 'w';
  }
  //qApp->setAttribute(Qt::AA_UseHighDpiPixmaps); // not tested
  qApp->setStyle(proxy);
  if (mc_style != 'g' || mc_pointSize > 0) setFont('i');

  strncpy(mc_language, language.toUtf8().data(), sizeof(mc_language) - 1);
  return setTranslation(language);
}

QString qtfix::fixStyleSheet(QString styleSheet)
{
  if (styleSheet.isEmpty()) return styleSheet;

  QString qs;
  int fontH = QFontMetrics(qApp->font("QAbstractItemView")).ascent();
  int spacing = QFontMetrics(qApp->font("QAbstractItemView")).lineSpacing();
  styleSheet.append(qs.sprintf("\nQPushButton { margin-left: %dpx; margin-right: %dpx; }", fontH / 2, fontH / 2));
  styleSheet.append(qs.sprintf("\nQPushButton::menu-indicator { width: %dpx; height: %dpx; }", fontH, fontH));
  styleSheet.append(qs.sprintf("\nQSizeGrip { width: %dpx; height: %dpx; }\n", fontH, fontH));
  //styleSheet.append(qs.sprintf("QMenu::item:indicator { padding: 0px %dpx 0px %dpx; }\n", fontH, fontH));
  styleSheet.append(qs.sprintf("QScrollBar:vertical { width: %dpx; margin: %dpx 0 %dpx 0; }\n", spacing, fontH, fontH));
  qs.sprintf("QScrollBar:horizontal { height: %dpx; margin: 0 %dpx 0 %dpx; }\n", spacing, fontH, fontH);
  styleSheet.append(qs);
  qs.sprintf("QScrollBar::add-line:vertical, QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on,"
             " QScrollBar::sub-line:vertical, QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on,"
             " QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal,"
             " QScrollBar::add-line:horizontal:hover, QScrollBar::sub-line:horizontal:hover,"
             " QScrollBar::add-line:horizontal:on, QScrollBar::sub-line:horizontal:on { width: %dpx; height: %dpx; }",
             fontH, fontH);
  return styleSheet.append(qs);
}

void qtfix::fixStyleSheet(QWidget * widget, const QString & styleSheet)
{
  QString style;
#ifdef __APPLE__
  if (mc_style == 'm' && qApp->styleSheet().isEmpty()) {
    style.sprintf("\n%s { min-height: %dpx; }\n", widget->metaObject()->className(), QFontMetrics(QFont()).height());
  }
#endif
  widget->setStyleSheet(qApp->styleSheet().append(styleSheet).append(style));
}

QString qtfix::fixColorString(QString string, const QByteArray & color)
{
  QString qs;
  return string.replace("color:#ff0000;", qs.sprintf("color:%s;", color.data()));
}

#ifdef __APPLE__
QColor qtfix::fixDefaultColor(const QColor & color, const QColor &)
{
#else
QColor qtfix::fixDefaultColor(const QColor & color, const QColor & defaultColor)
{
  if (!color.isValid() && qApp->styleSheet().isEmpty() && QApplication::desktopSettingsAware()) return defaultColor;
#endif
  return color;
}

void qtfix::fixStatusBar(QStatusBar * statusBar)
{
  if (mc_style == 'g' || mc_style == 'w') {
    statusBar->setStyleSheet(qApp->styleSheet() + "\nQStatusBar::item { border: 0px solid black }");
  }
}

void qtfix::fixTabWidget(QTabWidget * tabWidget, const QFont & font)
{
  int fontH = QFontMetrics(font).height();
  if (mc_style == 'm' && (fontH > mc_maxPointSize || fontH < 12) && qApp->styleSheet().isEmpty()) {
    QString style;
    tabWidget->setStyleSheet(style.sprintf("QTabBar { font-size: %dpx; }\n", QFontMetrics(font).ascent()));
  }
}

void qtfix::fixTableView(QTableView * tableView)
{
  if (qApp->styleSheet().isEmpty()) {
    QFont font = tableView->verticalHeader()->font();
    font.setPointSize(4);
    tableView->verticalHeader()->setFont(font);
  } else {
    QString style = "\nQHeaderView::section::vertical { font-size: 4pt; padding: 0px; border: 0px; }\n";
    tableView->verticalHeader()->setStyleSheet(qApp->styleSheet().append(style));
  }
}

void qtfix::fixTableWidgetHorizontalHeader(QTableWidget * tableWidget, QStringList & labels)
{
  for (int i = 0; i < labels.size(); i++) labels[i].append("   ");
#ifdef _WIN32
  int fontH = QFontMetrics(tableWidget->font()).ascent(); // height
  QString qs = getStyleSetting();
  bool resized = tableWidget->horizontalHeader()->property("no-header-arrow").isValid();
  bool ok = mc_style != 'w' || (!qs.isEmpty() && qs.compare("windowsxp", Qt::CaseInsensitive));
  if (!ok && (fontH > mc_maxPointSize || resized) && QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
    if (qs.isEmpty()) {
      qs.sprintf("\nQHeaderView::up-arrow { width: %dpx; }\nQHeaderView::down-arrow { width: %dpx; }", fontH, fontH);
      tableWidget->horizontalHeader()->setStyleSheet(qApp->styleSheet().append(fontH > mc_maxPointSize ? qs : ""));
    }
    tableWidget->horizontalHeader()->setProperty("no-header-arrow", fontH > mc_maxPointSize ? fontH : QVariant());
  }
#endif
  tableWidget->setHorizontalHeaderLabels(labels);
}

QFont * qtfix::createPasswordFont(int pointSize, int * fontId)
{
  *fontId = QFontDatabase::addApplicationFontFromData(QByteArray((const char *)SimStar, int(sizeof(SimStar))));
  QFont * font = new QFont("simstar", pointSize > 0 ? (pointSize * 13 + 5) / 10 : 13);
  font->setStyleStrategy(QFont::NoFontMerging);
  return font;
}

void qtfix::removePasswordFont(int fontId)
{
  QFontDatabase::removeApplicationFont(fontId);
}

QFont qtfix::fixFontSize(QFont oldFont, const char * newFontClass, const char * styles)
{
  if (mc_pointSize > 0 || strchr(styles, mc_style)) {
    QFont newFont = qApp->font(newFontClass);
    int pointSize = newFont.pointSize();
    int pixelSize = newFont.pixelSize();
    if (pointSize > 0) {
      oldFont.setPointSize(mc_pointSize > 0 && strcmp(styles, "mg") ? mc_pointSize : pointSize);
    } else if (pixelSize > 0) {
      oldFont.setPixelSize(pixelSize);
    }
  }
  oldFont.setBold(!strcmp(styles, "mg"));
  return oldFont;
}

void qtfix::fixPointSize(QWidget * widget, int pointSize)
{
  if (QApplication::desktopSettingsAware() || mc_pointSize > 0) {
    QFont font = widget->font();
    pointSize = font.pointSize() * (mc_pointSize > 0 ? mc_pointSize : pointSize) / 8;
#ifdef __APPLE__
    if (!strcmp(widget->metaObject()->className(), "QPushButton") && pointSize > 15) pointSize = 15;
#endif
    font.setPointSize(pointSize);
    widget->setFont(font);
  }
}

QPixmap qtfix::fixPixmapSize(const QPixmap & pixmap, int fontH, bool nodownscale)
{
  if (pixmap.isNull() || (nodownscale && pixmap.height() >= fontH)) return pixmap;
  return pixmap.scaledToHeight(fontH, Qt::SmoothTransformation);
}

QPixmap qtfix::fixPixmapColor(const QPixmap & pixmap, const QColor & color)
{
  QPixmap newPixmap(pixmap.size());
  newPixmap.fill(color);
  newPixmap.setMask(pixmap.createMaskFromColor(Qt::transparent));
  return newPixmap;
}

void qtfix::fixCheckBoxIcon(QCheckBox * checkBox, const QFont & font)
{
  int fontH = QFontMetrics(font).height();
  QString style1;
  QString style2;

  if (mc_style != 'm' || (qApp->styleSheet().isEmpty() && mc_pointSize > mc_maxPointSize)) {
    QString style3;
    if (mc_style != 'f' && qApp->styleSheet().isEmpty()) {
      if (mc_style == 'w') {
        style1 = "QCheckBox::indicator:checked { image: url(:/checkboxCheckedEnabled); }\n"
                 "QCheckBox::indicator:unchecked { image: url(:/checkboxUncheckedEnabled); }\n"
                 "QCheckBox::indicator:indeterminate { image: url(:/checkboxIndeterminateEnabled); }\n"
                 "QCheckBox::indicator:checked:pressed { image: url(:/checkboxCheckedWindows); }\n"
                 "QCheckBox::indicator:unchecked:pressed { image: url(:/checkboxUncheckedWindows); }\n"
                 "QCheckBox::indicator:indeterminate:pressed { image: url(:/checkboxIndeterminateWindows); }\n"
                 "QCheckBox::indicator:checked:disabled { image: url(:/checkboxCheckedWindows); }\n"
                 "QCheckBox::indicator:unchecked:disabled { image: url(:/checkboxUncheckedWindows); }\n"
                 "QCheckBox::indicator:indeterminate:disabled { image: url(:/checkboxIndeterminateWindows); }\n";
      } else {
        style1 = "QCheckBox::indicator:checked { image: url(:/checkboxCheckedGtk); }\n"
                 "QCheckBox::indicator:unchecked { image: url(:/checkboxUncheckedGtk); }\n"
                 "QCheckBox::indicator:indeterminate { image: url(:/checkboxIndeterminateGtk); }\n"
                 "QCheckBox::indicator:checked:pressed { image: url(:/checkboxCheckedDisabled); }\n"
                 "QCheckBox::indicator:unchecked:pressed { image: url(:/checkboxUncheckedDisabled); }\n"
                 "QCheckBox::indicator:indeterminate:pressed { image: url(:/checkboxIndeterminateDisabled); }\n"
                 "QCheckBox::indicator:checked:disabled { image: url(:/checkboxCheckedDisabled); }\n"
                 "QCheckBox::indicator:unchecked:disabled { image: url(:/checkboxUncheckedDisabled); }\n"
                 "QCheckBox::indicator:indeterminate:disabled { image: url(:/checkboxIndeterminateDisabled); }\n";
      }
    }

    style2.sprintf("QCheckBox::indicator { width: %dpx; height: %dpx; }\n", fontH, fontH);
    if (checkBox->property("bold-font").isValid()) style3 = "QCheckBox { font-weight: bold; }\n";
    checkBox->setStyleSheet(qApp->styleSheet().append("\n").append(style1).append(style2).append(style3));
#ifdef __APPLE__
  } else if (!qApp->styleSheet().isEmpty()) {
    style1.sprintf("\nQCheckBox::indicator { width: %dpx; height: %dpx; }", fontH, fontH);
    style2.sprintf("\nQCheckBox { spacing: %dpx; }\n", fontH / 2);
    checkBox->setStyleSheet(qApp->styleSheet().append(style1).append(style2));
#endif
  }
}

void qtfix::fixRadioButtonIcon(QRadioButton * radioButton, const QFont & font)
{
  int fontH = QFontMetrics(font).height();
  QString style1;
  QString style2;
  if (mc_style != 'm' || (qApp->styleSheet().isEmpty() && mc_pointSize > mc_maxPointSize)) {
    QString style3;
    if (mc_style != 'f' && qApp->styleSheet().isEmpty()) {
      style1.sprintf("QRadioButton { outline: none; padding: %dpx; }\n"
                     "QRadioButton::indicator:checked { image: url(:/radioCheckedEnabled); }\n"
                     "QRadioButton::indicator:unchecked { image: url(:/radioUncheckedEnabled); }\n",
                     fontH / 8);
      if (mc_style == 'w') {
        style2.sprintf("QRadioButton::indicator:checked:pressed { image: url(:/radioCheckedWindows); }\n"
                       "QRadioButton::indicator:unchecked:pressed { image: url(:/radioUncheckedWindows); }\n");
      }
    }

    style3.sprintf("QRadioButton::indicator { width: %dpx; height: %dpx; }\n", fontH, fontH);
    radioButton->setStyleSheet(qApp->styleSheet().append("\n").append(style1).append(style2).append(style3));
#ifdef __APPLE__
  } else if (!qApp->styleSheet().isEmpty()) {
    style1.sprintf("\nQRadioButton::indicator { width: %dpx; height: %dpx; }", fontH, fontH);
    style2.sprintf("\nQRadioButton { spacing: %dpx; }\n", fontH / 2);
    radioButton->setStyleSheet(qApp->styleSheet().append(style1).append(style2));
#endif
  }
}

void qtfix::fixComboBoxIcon(QComboBox * comboBox, const QFont & font)
{
  if (mc_style != 'f' || qApp->styleSheet().isEmpty()) {
    int fontH = QFontMetrics(font).height();
    bool resize = !qApp->styleSheet().isEmpty() || fontH > mc_maxPointSize;
    if (resize || !comboBox->styleSheet().isEmpty()) {
      QString style;
      style.sprintf("\nQComboBox::drop-down { width: %dpx; }\n", fontH);
      if (qApp->styleSheet().isEmpty()) style.prepend("\nQComboBox { border: 1px solid gray; border-radius: 3px; }");
      comboBox->setStyleSheet(qApp->styleSheet().append(resize ? style : "\n"));
    }
  }
  comboBox->view()->setTextElideMode(Qt::ElideNone);
}

void qtfix::fixSpinBoxIcon(QSpinBox * spinBox, const QFont & font)
{
  if (mc_style == 'm' && qApp->styleSheet().isEmpty()) {
    QString style;
    spinBox->setStyleSheet(style.sprintf("QSpinBox { min-height: %dpx; }\n", QFontMetrics(font).height()));
  }
}

void qtfix::fixPushButtonIcon(QPushButton * pushButton, const QFont & font)
{
  int fontH = QFontMetrics(font).height();
  QString style;
  QSize size = pushButton->iconSize();
  size.scale(size.width(), fontH, Qt::KeepAspectRatioByExpanding);
  QIcon icon = pushButton->icon();
  QSize actual = icon.actualSize(size);
  bool resize = mc_pointSize > 0 || actual.height() < fontH;
  if (resize || !pushButton->styleSheet().isEmpty()) {
    pushButton->setIcon(icon.pixmap(size, QIcon::Normal).scaledToHeight(fontH, Qt::SmoothTransformation));
    if (mc_style != 'm' || mc_pointSize > 0 || !resize) {
      style.sprintf("\nQPushButton { icon-size: %dpx; }\n", fontH);
      pushButton->setStyleSheet(qApp->styleSheet().append(resize ? style : ""));
    }
  }
}

void qtfix::fixMenuBar(QMenuBar * menuBar, int width, QLayout * layout)
{
  QMargins margins = layout->contentsMargins();
  menuBar->adjustSize();
  menuBar->resize(width, menuBar->height());
  margins.setTop(menuBar->height());
  layout->setContentsMargins(margins);
}

void qtfix::fixMenuIndicator(QAction * item, QMenu * menu, const QFont & font)
{
  int fontH = QFontMetrics(font).height() * 3 / 4;
  bool exclusive = mc_style != 'w' && mc_style != 'm' && item->actionGroup() && item->actionGroup()->isExclusive();
  QPixmap checked = QPixmap(mc_indicators[exclusive ? 6 : mc_style == 'f' ? 2 : mc_style == 'g' ? 0 : 1]);
  QPixmap unchecked = QPixmap(mc_indicators[exclusive ? 7 : mc_style == 'f' ? 5 : mc_style == 'g' ? 3 : 4]);
  bool resize = !qApp->styleSheet().isEmpty() || fontH * 2 >= checked.height() || fontH * 2 >= unchecked.height();
  QIcon icon;

  if (resize || item->property("item-icon-size").isValid()) {
    bool ok = mc_style != 'w' && mc_style != 'g';
    item->setProperty("item-icon-size", fontH);
    menu->setProperty("scaled-icon-size", fontH);
    menu->setProperty("no-panel-button", fontH);
    if (checked.height() < fontH) checked = checked.scaledToHeight(fontH, Qt::SmoothTransformation);
    if (unchecked.height() < fontH) unchecked = unchecked.scaledToHeight(fontH, Qt::SmoothTransformation);
#ifdef _WIN32
    if (getStyleSetting().isEmpty() && QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) ok = true;
#endif
    if (!ok && qApp->styleSheet().isEmpty()) {
      QColor color = QApplication::palette().highlightedText().color();
      if (color.value() > 16 || QApplication::palette().windowText().color().value() > 16) {
        icon.addPixmap(fixPixmapColor(checked, color), QIcon::Active, QIcon::On);
        checked = fixPixmapColor(checked, QApplication::palette().windowText().color());
        unchecked = fixPixmapColor(unchecked, QApplication::palette().windowText().color());
      }
    }
    icon.addPixmap(checked, QIcon::Normal, QIcon::On);
    icon.addPixmap(unchecked, QIcon::Normal, QIcon::Off);
#ifdef __APPLE__
    if (mc_style == 'm' && qApp->styleSheet().isEmpty()) {
      menu->setProperty("menu-item-checked", resize ? checked : QVariant());
      menu->setProperty("menu-item-unchecked", resize ? unchecked : QVariant());
      if (!resize) icon = QIcon();
    }
#endif
    item->setIcon(icon);
  }
}

QAction * qtfix::addMenuItem(QMenu * menu, const QString & text, bool checked)
{
  QAction * item = new QAction(text, menu);
  item->setCheckable(true);
  item->setChecked(checked);
  fixMenuIndicator(item, menu, menu->font());
  menu->addAction(item);
  return item;
}

QAction * qtfix::addMenuItem(QMenu * menu, const QString & text, QActionGroup * group)
{
  QAction * item = new QAction(text, menu);
  if (group) {
    item->setCheckable(true);
    item->setActionGroup(group);
    fixMenuIndicator(item, menu, menu->font());
  }
  menu->addAction(item);
  return item;
}

QAction * qtfix::addMenuItem(QMenu * menu, const QString & text, const QPixmap & icon)
{
  QAction * item = new QAction(icon, text, menu);
  menu->addAction(item);
  return item;
}

void qtfix::fixIconSize(QWidget * widget, const QFont & font)
{
  int fontH = QFontMetrics(font).ascent();
  widget->setProperty("scaled-icon-size", fontH);
  widget->setProperty("menu-button-indicator", QFontMetrics(font).lineSpacing());
#ifdef _WIN32
  QString qs = getStyleSetting();
  bool ok = !qs.isEmpty() && qs.compare("windowsxp", Qt::CaseInsensitive);
  if (!ok && mc_style == 'w' && qApp->styleSheet().isEmpty() && QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA) {
    widget->setStyleSheet(qs.sprintf("\nQPushButton::menu-indicator { width: %dpx; height: %dpx; }", fontH, fontH));
  }
#endif
}

void qtfix::fixSplitterHandle(QSplitter * splitter, const QFont & font, char enable)
{
  splitter->setHandleWidth(enable != 'h' ? QFontMetrics(font).lineSpacing() / 2 : 0);
  for (int i = 0; i < splitter->count(); i++) {
    splitter->handle(i)->setEnabled(enable == 'e');
  }
}

class fixPasteFilter : public QObject
{
  bool eventFilter(QObject * obj, QEvent * event) Q_DECL_OVERRIDE
  {
    QEvent::Type type = event->type();
    if (type == QEvent::KeyPress) {
      QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
      if (keyEvent->key() == Qt::Key_Paste || keyEvent->matches(QKeySequence::Paste)) return true;
    } else if (type == QEvent::MouseButtonRelease) {
      QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
      if (mouseEvent->button() == Qt::MidButton) return true;
    }
    return QObject::eventFilter(obj, event);
  }
};

QObject * qtfix::findChild(QObject * parent, const char * type)
{
  QObject * last = 0;
  const QObjectList & list = parent->children();
  for (int i = 0; i < list.size(); ++i) {
    QObject * object = list.at(i);
    if (object->inherits(type)) last = object;
  }
  return last;
}

QString qtfix::execInputDialog(QWidget * parent, const QString & title, const QString & label,
                               const QString & text, bool * ok)
{
  QInputDialog input(parent);
  input.setWindowTitle(title);
  input.setLabelText(label);
  input.setTextValue(text);
  input.setTextEchoMode(QLineEdit::Normal);
  if (QLineEdit * lineEdit = (QLineEdit *)findChild(&input, "QLineEdit")) fixStyleSheet(lineEdit);
  *ok = input.exec() == QDialog::Accepted;
  return /*!*ok ? QString() :*/ input.textValue();
}

int qtfix::execInputDialog(QInputDialog * input)
{
  QLabel * label = (QLabel *)findChild(input, "QLabel");
  if (label) label->setWordWrap(true);
#ifdef _DEBUG
  return input->exec();
#else
  input->setModal(true);
  input->show(); // can't see children before the show has begun
  QLineEdit * lineEdit = (QLineEdit *)findChild(input, "QLineEdit");
  QObject * fixEventPaste = 0;
  if (lineEdit) {
    fixStyleSheet(lineEdit);
    lineEdit->setAcceptDrops(false);
    lineEdit->setContextMenuPolicy(Qt::PreventContextMenu);
    lineEdit->installEventFilter(fixEventPaste = new fixPasteFilter);
  }
  int result = input->exec();
  if (lineEdit) {
    lineEdit->removeEventFilter(fixEventPaste);
    delete fixEventPaste;
  }
  return result;
#endif
}

bool qtfix::execMessageBox(QMessageBox::Icon icon, const char * title, const QString & message,
                           QWidget * parent, const QString & checkBox)
{
  bool ok;
  QMessageBox mbox(icon, title, message, QMessageBox::Ok | QMessageBox::Cancel, parent);
  QCheckBox cbox(checkBox);
  if (!checkBox.isEmpty()) setCheckBox(&mbox, &cbox);
  mbox.setDefaultButton(QMessageBox::Cancel);
  mbox.button(QMessageBox::Cancel)->hide();
  mbox.button(QMessageBox::Ok)->setFocus();
  ok = mbox.exec() == QMessageBox::Ok && cbox.isChecked();
  setCheckBox(&mbox, 0);
  return ok;
}

bool qtfix::execMessageBox(QCheckBox * checkBox, const QString & title, const QString & message,
                           QWidget * parent, bool yes)
{
  bool ok;
  QMessageBox mbox(QMessageBox::Question, title, message, QMessageBox::Yes | QMessageBox::No, parent);
  if (checkBox) setCheckBox(&mbox, checkBox);
  mbox.setDefaultButton(yes ? QMessageBox::Yes : QMessageBox::No);
  ok = mbox.exec() == QMessageBox::Yes;
  setCheckBox(&mbox, 0);
  return ok;
}

bool qtfix::execMessageBox(bool critical, const QString & title, const QString & message,
                           QWidget * parent, const QString & checkBox)
{
  QMessageBox::Icon icon = critical ? QMessageBox::Critical : QMessageBox::Warning;
  QString qs2 = critical ? tr("Simphone error") : tr("Simphone warning");
  QString qs = "<p><b>" + title.toHtmlEscaped().replace(" ", "&nbsp;") + "</b></p><p>";
  qs.append(Qt::mightBeRichText(message) ? message : message.toHtmlEscaped().replace("\n", "<br/>"));
  if (!checkBox.isEmpty()) qs.append("<br/>");
  qs.append("</p>");
  if (checkBox.isEmpty()) {
    QMessageBox mbox(icon, qs2.toUtf8().data(), qs, QMessageBox::Ok, parent);
    mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
    mbox.exec();
    return false;
  }
  return execMessageBox(icon, qs2.toUtf8().data(), qs, parent, checkBox);
}

void qtfix::updateMessageBox(QMessageBox * messageBox, const QString & text)
{
  QString qs = messageBox->text();
  if (qs != text) {
    QLabel * label = (QLabel *)findChild(messageBox, "QLabel");

    int i = label ? label->selectionStart() : -1;
    int l = i >= 0 ? qs.size() - label->selectedText().size() : 0;
    messageBox->setText(text);
    if (i >= 0) label->setSelection(i, text.size() >= l && l >= 0 ? text.size() - l : text.size());
  }
}

void qtfix::resizeMaximizedWindow(QMainWindow * window, int width)
{
#ifdef __unix__
  if (window->isMaximized()) {
    Qt::WindowStates state = window->windowState();
    if (state & Qt::WindowMinimized) window->setWindowState(state ^ Qt::WindowMinimized);
    window->showNormal();
  }
#endif
  if (width >= 0) window->resize(width, window->height());
}

bool qtfix::showActivateWindow(QWidget * window, bool spontaneous)
{
  Qt::WindowStates state = window->windowState();
#ifdef __APPLE__
  if (spontaneous && !(state & Qt::WindowMinimized) && window->isVisible()) window->hide();
#endif
  window->show();
  window->activateWindow();
  if (state & Qt::WindowMinimized) window->setWindowState(state ^ Qt::WindowMinimized);
  window->raise();
  return spontaneous;
}

void qtfix::showMaximizedWindow(QWidget * window)
{
#ifdef __unix__
  QRect rect = QApplication::desktop()->availableGeometry();
  window->resize(rect.right() - rect.left(), rect.bottom() - rect.top());
#endif
  window->setWindowState(window->windowState() | Qt::WindowMaximized);
}

#ifdef __unix__
void qtfix::showMinimizedWindow(QWidget * window, int delay)
{
  if (delay) {
    window->showMinimized();
    usleep(abs(delay) * 1000);
  } else {
    window->show();
    window->setWindowState((window->windowState() & ~Qt::WindowActive) | Qt::WindowMinimized);
  }
#else
void qtfix::showMinimizedWindow(QWidget * window, int)
{
  window->showMinimized();
#endif
}

Qt::WindowStates qtfix::hideMinimizedWindow(QWidget * window)
{
  Qt::WindowStates state = window->windowState();
#ifdef __APPLE__
  if (state & Qt::WindowMinimized) window->setWindowState(state ^ Qt::WindowMinimized);
#endif
  window->hide();
  return state;
}

void qtfix::fixWindowPosition(QWidget * window)
{
#ifdef __unix__
  window->windowHandle()->setFramePosition(window->windowHandle()->framePosition());
#endif
}
