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

    class qtfix: work-arounds 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 <QMenu>
#include <QKeyEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QLibrary>
#include <QLabel>
#include <QTextDocument>

#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;
char * qtfix::mc_styleArgument = 0;
char qtfix::mc_style;
bool qtfix::mc_styleSheet;
QString qtfix::mc_styleName;
QString qtfix::mc_styleSheetName;
char qtfix::mc_language[];
QLocale qtfix::mc_locale;
QElapsedTimer qtfix::mc_elapsed;

static QList<QTranslator *> g_translators;

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);
  if (metric == PM_ScrollBarExtent) {
    int fontH = QFontMetrics(widget->font()).lineSpacing();
    if (fontH > result) return fontH;
  } else if (metric == PM_SmallIconSize && widget) {
    QVariant fontH = widget->property("scaled-icon-size");
    if (fontH.isValid()) return fontH.toInt();
  } else if (metric == PM_MenuButtonIndicator && widget) {
    QVariant fontH = widget->property("menu-button-indicator");
    if (fontH.isValid()) return fontH.toInt();
  }
  return result;
}

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 + ")";
}

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) printf("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;
}

QStringList qtfix::fixArguments(int & argc, char **& argv)
{
  bool style = false;
  bool session = false;
  QStringList arguments;
  QString platform = qgetenv("QT_QPA_PLATFORM").toLower();
  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) {
      style = true;
      mc_styleArgument = argv[i + 1];
      arguments << arg << argv[i + 1];
    } else if (arg.startsWith("-style=")) {
      style = true;
      mc_styleArgument = argv[i] + 8;
      arguments << arg;
    } else if (arg == "-stylesheet" && i + 1 < argc) {
      mc_styleSheetName = argv[++i];
      arguments << arg << argv[i];
    } else if (arg.startsWith("-stylesheet=")) {
      mc_styleSheetName = QString(argv[i]).mid(12);
      arguments << arg;
    } 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 + 3));
  if (args) {
    memcpy(args, argv, sizeof(*argv) * argc);
    args[argc] = 0;
    argv = (char **)args;
    if (!style && qgetenv("QT_STYLE_OVERRIDE").isEmpty() && (platform.isEmpty() || platform == "xcb")) {
#ifdef __unix__
      QLibrary libgtk("gtk-x11-2.0", 0, 0);
      args[argc++] = "-style";
      args[argc++] = libgtk.resolve("gtk_init") ? "gtk" : "windows";
      args[argc] = 0;
      if (!strcmp(args[argc - 1], "windows")) printf("Qt: %s\n", libgtk.errorString().toUtf8().data());
#endif
    }
  }
  return session ? QStringList() : arguments << "-minimized";
}

const char * qtfix::fixApplication(const QString & language)
{
  QProxyStyle * proxy = new fixProxyStyle;
  mc_styleName = qApp->style()->objectName().toLower();
  if (mc_styleName.isEmpty()) mc_styleName = proxy->baseStyle()->objectName().toLower();
  mc_styleSheet = !qApp->styleSheet().isEmpty();
  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
  if (mc_style == 'f' || mc_style == 'w') qApp->setFont(qApp->font("QMessageBox"), "QInputDialog");
  delete proxy;

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

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

void qtfix::fixTableView(QTableView * tableView)
{
  QFont font = tableView->verticalHeader()->font();
  font.setPointSize(4);
  tableView->verticalHeader()->setFont(font);
}

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, bool monospace)
{
  if (monospace ? mc_style == 'g' || mc_style == 'm' : mc_style == 'f' || mc_style == 'w') {
    QFont newFont = qApp->font(newFontClass);
    int pointSize = newFont.pointSize();
    int pixelSize = newFont.pixelSize();
    if (pointSize > 0) {
      oldFont.setPointSize(pointSize);
    } else if (pixelSize > 0) {
      oldFont.setPixelSize(pixelSize);
    }
  }
  return oldFont;
}

#ifdef __APPLE__
void qtfix::fixPointSize(QWidget * widget, int pointSize, int maxPointSize)
#else
void qtfix::fixPointSize(QWidget * widget, int pointSize, int)
#endif
{
  if (mc_style == 'g' || mc_style == 'm') {
    QFont font = widget->font();
    pointSize = font.pointSize() * pointSize / 8;
#ifdef __APPLE__
    if (maxPointSize && pointSize > maxPointSize) pointSize = maxPointSize;
#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);
}

void qtfix::fixCheckBoxIcon(QCheckBox * checkBox, const QFont & font)
{
  if (mc_style == 'f' || mc_style == 'g' || mc_style == 'w') {
    int fontH = QFontMetrics(font).height();
    QString style1;
    QString style2;
    QString style3;
    if (mc_style != 'f') {
      // windows style checkbox has a size of 7 pixels, so replace it with a bigger one.
      // it still won't be upscaled but at least it will be centered into the square
      style1 = "QCheckBox::indicator:checked { image: url(:/checkboxCheckedEnabled); }\n"
               "QCheckBox::indicator:unchecked { image: url(:/checkboxUncheckedEnabled); }\n"
               "QCheckBox::indicator:indeterminate { image: url(:/checkboxIndeterminateEnabled); }\n";
      if (!mc_styleSheet) {
        if (mc_style == 'w') {
          style2 = "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 {
          style2 = "QCheckBox::indicator:checked:pressed { image: url(:/checkboxCheckedGtk); }\n"
                   "QCheckBox::indicator:unchecked:pressed { image: url(:/checkboxUncheckedGtk); }\n"
                   "QCheckBox::indicator:indeterminate:pressed { image: url(:/checkboxIndeterminateGtk); }\n"
                   "QCheckBox::indicator:checked:disabled { image: url(:/checkboxCheckedGtk); }\n"
                   "QCheckBox::indicator:unchecked:disabled { image: url(:/checkboxUncheckedGtk); }\n"
                   "QCheckBox::indicator:indeterminate:disabled { image: url(:/checkboxIndeterminateGtk); }\n";
        }
      }
    }

    style3.sprintf("QCheckBox::indicator { width: %dpx; height: %dpx; }\n", fontH, fontH);
    checkBox->setStyleSheet(qApp->styleSheet() + "\n" + style1 + style2 + style3 + checkBox->styleSheet());
  }
}

void qtfix::fixRadioButtonIcon(QRadioButton * radioButton, const QFont & font)
{
  if (mc_style == 'f' || mc_style == 'g' || mc_style == 'w') {
    int fontH = QFontMetrics(font).height();
    QString style1;
    QString style2;
    QString style3;
    if (mc_style != 'f') {
      // also needed by gtk style
      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' && !mc_styleSheet) {
        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() + "\n" + style1 + style2 + style3);
  }
}

void qtfix::fixComboBoxIcon(QComboBox * comboBox, const QFont & font)
{
  if (mc_style == 'f' || mc_style == 'g' || mc_style == 'w') {
    QPixmap pixmap;
    int fontH = QFontMetrics(font).height();
    if (pixmap.load(":/downArrow") && fontH > pixmap.width() / 2) {
      QString style;
      style.sprintf("QComboBox { border: 1px solid gray; border-radius: 3px; } QComboBox::drop-down { width: %dpx; }"
                    " QComboBox::down-arrow { border-image: url(:/downArrow); }\n",
                    pixmap.width() * fontH / pixmap.height());
      comboBox->setStyleSheet(qApp->styleSheet() + "\n" + style);
    }
  }
  comboBox->view()->setTextElideMode(Qt::ElideNone);
}

void qtfix::fixPushButtonIcon(QPushButton * pushButton, const QFont & font)
{
  int fontH = QFontMetrics(font).ascent();
  QString style;
  QSize size = pushButton->iconSize();
  size.scale(size.width(), fontH, Qt::KeepAspectRatioByExpanding);
  QIcon icon = pushButton->icon();
  QSize actual = icon.actualSize(size);
  if (actual.height() < fontH) {
    // this isn't pretty, but better look ugly than look invisible
    QPixmap pixmap = icon.pixmap(size, QIcon::Normal);
    pixmap = pixmap.scaledToHeight(fontH, Qt::SmoothTransformation);
    pushButton->setIcon(QIcon(pixmap));

    if (mc_style == 'f' || mc_style == 'g') {
      pushButton->setStyleSheet(style.sprintf("QPushButton { icon-size: %dpx; }\n", fontH) + qApp->styleSheet());
    }
  }
}

void qtfix::fixMenuIndicator(QAction * item, QMenu * menu)
{
  if (mc_style == 'f' || mc_style == 'g' || mc_style == 'w') {
    int fontH = QFontMetrics(menu->font()).ascent();
    bool exclusive = mc_style != 'w' && item->actionGroup() && item->actionGroup()->isExclusive();
    QPixmap checked = QPixmap(exclusive
                                ? ":/radioCheckedEnabled"
                                : mc_styleSheet
                                    ? ":/checkboxCheckedEnabled"
                                    : ":/checkmarkCheckedUnselected");
    QPixmap unchecked = QPixmap(exclusive ? ":/radioUncheckedEnabled" : ":/checkmarkUncheckedUnselected");
    QIcon icon;

    if (fontH * 2 > checked.height() || fontH * 2 > unchecked.height()) {
      menu->setProperty("scaled-icon-size", fontH);
      if (checked.height() < fontH) checked = checked.scaledToHeight(fontH, Qt::SmoothTransformation);
      if (unchecked.height() < fontH) unchecked = unchecked.scaledToHeight(fontH, Qt::SmoothTransformation);
      icon.addPixmap(checked, QIcon::Normal, QIcon::On);
      icon.addPixmap(unchecked, QIcon::Normal, QIcon::Off);
      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->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->addAction(item);
  return item;
}

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

void qtfix::fixIconSize(QWidget * widget, const QFont & font)
{
  if (mc_style == 'f' || mc_style == 'g' || mc_style == 'w') {
    widget->setProperty("scaled-icon-size", QFontMetrics(font).ascent());
    widget->setProperty("menu-button-indicator", QFontMetrics(font).lineSpacing());
  }
}

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;
}

int qtfix::execInputDialog(QInputDialog * input)
{
  QLabel * label = (QLabel *)qtfix::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) {
    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);
  cbox.setFont(qApp->font("QMessageBox"));
  fixCheckBoxIcon(&cbox, cbox.font());
  cbox.setFocusPolicy(Qt::NoFocus);
  if (!checkBox.isEmpty()) mbox.setCheckBox(&cbox);
  mbox.setDefaultButton(QMessageBox::Cancel);
  mbox.button(QMessageBox::Cancel)->hide();
  mbox.button(QMessageBox::Ok)->setFocus();
  ok = mbox.exec() == QMessageBox::Ok && cbox.isChecked();
  mbox.setCheckBox(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) {
    checkBox->setFont(qApp->font("QMessageBox"));
    fixCheckBoxIcon(checkBox, checkBox->font());
    checkBox->setFocusPolicy(Qt::NoFocus);
    mbox.setCheckBox(checkBox);
  }
  mbox.setDefaultButton(yes ? QMessageBox::Yes : QMessageBox::No);
  ok = mbox.exec() == QMessageBox::Yes;
  mbox.setCheckBox(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::resizeMainWindow(QMainWindow * window, int width, int height)
{
#ifdef __unix__
  if (window->isMaximized()) window->showNormal();
#endif
  window->resize(width, 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;
}
