Newer
Older
Import / applications / MakePDF / MdiWindow.cpp.edited
#include <QWidget>
#include <QFileDialog>
#include <QMdiSubWindow>
#include <QTableWidget>
#include <QScrollArea>
#include <QButtonGroup>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QHttpPart>
#include <QStandardPaths>
#include <QInputDialog>
#include <QMessageBox>
#include <QNetworkInterface>
#include <QNetworkConfiguration>
#include <QNetworkConfigurationManager>
#include <QNetworkSession>
#include <iostream>
#include <string>
#include <regex>
#include "Utilities.h"
#include "DocStyle.h"
#include "DocTemplate.h"
#include "MdiWindow.h"
#include "LicenseCheck.h"
#include "FingerPrint.h"
#include "Util.h"
#include "ui_ExtensibleObjectModelUI.h"
#include "ui_PaletteEditor.h"
#include "ui.h"
#include "html.h"
#include "document.h"
#include "DocConvert.h"


class AutoRelease
{
public:
  AutoRelease(bool& var) : mVar(var) { mVar = true; }
  ~AutoRelease() { mVar = false; }
  bool& mVar;
};


#define MAKE_NON_REENTRANT \
  static bool avoidReentry = false; \
  if (avoidReentry) \
    return; \
  AutoRelease ar(avoidReentry);


extern uint32_t rgbFromHsvF(qreal h1, qreal s, qreal v, qreal /*a*/);


QString resourcesLocation()
{
  QString exeDir = QCoreApplication::applicationDirPath();
  return exeDir + "/../Resources";
}


QString dataLocation()
{
  QString docsDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  QDir().mkdir(docsDir);
  return docsDir + "/WickedDocs";
}


void createDataLocations()
{
  QString baseDir = dataLocation();
  QDir().mkdir(baseDir);
  QDir().mkdir(baseDir + "/Templates");
  QDir().mkdir(baseDir + "/Styles");
  QDir().mkdir(baseDir + "/Documents");
}


QStringList getCombinedList(QString type, QString ext)
{
  QStringList exts = QString("*" + ext.split(';').join(";*")).split(';');
  return QDir(resourcesLocation() + "/" + type).entryList(exts, QDir::Files) +
    QDir(dataLocation() + "/" + type).entryList(exts, QDir::Files);
}


QStringList templatesList()
{
  return getCombinedList("Templates", ".template");
}


QStringList stylesList()
{
  return getCombinedList("Styles", ".style");
}


QStringList documentsList()
{
  return getCombinedList("Documents", ".md;.markdown;.txt");
}


QColor colorFromPaletteAndIndex(uint32_t palette, int index)
{
  ColorScheme pal;
  pal.m_primaryHue        = (palette >> 24) & 0xFF;
  pal.m_secondaryHueDelta = (palette >> 16) & 0xFF;
  pal.m_saturation        = (palette >>  8) & 0xFF;
  pal.m_stretch           = (palette >>  3) & 0x1F;
  pal.m_mode              = (palette >>  0) & 0x07;
  pal.m_mode              %= 5;

  if (index == 0)
    return QColor(0xFF000000);
  if (index == 1)
    return QColor(0xFFFFFFFF);
  if (index == 2)
    return QColor(0xFF888888);
  if (index == 3)
    return QColor(0xFF444444);
  index -= 4;

  float hue = pal.hue(0); // Get the primary color
  float sat = float(pal.m_saturation) / 256.0;
  float val = float(pal.m_stretch) / 32.0;

  QColor PrimaryCols[4];
  QColor SecondaryCols[16];

  // Update the palette preview
  int colIdxForMode[5][5] = { { 0, 0, 0, 0, -1 }, { 0, 0, 0, 1, -1 }, { 0, 0, 1, 2, -1 }, { 0, 2, 3, 1, -1 }, { 0, 2, 3, 1, -1 } };
  for (int i = 0; i < 4; i++)
  {
    int idx = colIdxForMode[pal.m_mode][i];
    float sats[4] = { 1.0 - 0.65, 1.0 - 0.35, 1.0, 1.0 };
    float vals[4] = { 1.0, 1.0, 1.0 - 0.35, 1.0 - 0.65 };
    float baseTone = 0.15;
    float hue2 = pal.hue(idx);
    QColor c = QColor::fromHsvF(hue2, sat*(1.0 - baseTone), val*(1.0 - (baseTone*0.6)));

    int a = i;
    int w = 1;
    PrimaryCols[i] = c;
    while (idx == colIdxForMode[pal.m_mode][i + 1])
    {
      i++;
      w++;
      PrimaryCols[i] = c;
    }

    for (int s = 0; s < 4; s++)
    {
      QColor x = rgbFromHsvF(hue2, sat*sats[s], val*vals[s], 1.0);
      for (int t = 0; t < w; t++)
      {
        SecondaryCols[a * 4 + w * s + t] = x;
      }
    }
  }

  if (index < 4)
    return PrimaryCols[index];
  index -= 4;
  return SecondaryCols[index];
}


void MdiWindow::populateProjectDialogValues()
{
  if (avoidPopulateRecursion)
    return;
  avoidPopulateRecursion = true;

  int currentStyle = appWindow->styleSelector->currentIndex();
  int currentTemplate = appWindow->templateSelector->currentIndex();

  appWindow->styleSelector->clear();
  appWindow->styleSelector->addItems(stylesList());
  appWindow->styleSelector->setCurrentIndex(currentStyle);
  appWindow->templateSelector->clear();
  appWindow->templateSelector->addItems(templatesList());

  // TODO: needs to be set back to the item with same value, not same index
  appWindow->templateSelector->setCurrentIndex(currentTemplate);

  int selectedTextRole = appWindow->textFormatSelector->currentRow();
  std::string curPalette = appWindow->paletteCombo->currentText().toLatin1().data();
  // If file is the model:
  //   std::string curPalette = style.selectedPalette();

  uiContext->paletteEditor.schemeNameEdit->setText(curPalette.c_str());
  

  if (currentStyle >= 0 && stylesList().size() > currentStyle)
  {
    DocStyle style;
	  // style.readStyleFile(QString(dataLocation() + "/Styles/" + stylesList()[0]).toLatin1().data());
	  style.readStyleFile(QString(resourcesLocation() + "/Styles/" + stylesList()[currentStyle]).toLatin1().data());

    std::vector<std::string> palettes = style.paletteList(); // For populating the ui combo
    appWindow->paletteCombo->clear();
    int palIdx = 0; bool found = false;
    for (auto pal : palettes)
    {
      found = found || (pal == curPalette);
      appWindow->paletteCombo->addItem(pal.c_str());
      palIdx += (found) ? 0 : 1;
    }
    // std::string curPalette = style.selectedPalette();
    appWindow->paletteCombo->setCurrentIndex(palIdx);

    uint32_t palette = style.palette(curPalette);

    QLabel* fgPaletteMap[24] = {
      appWindow->fgX0,  appWindow->fgX1,  appWindow->fgX2,  appWindow->fgX3,
      appWindow->fgP0,  appWindow->fgP1,  appWindow->fgP2,  appWindow->fgP3,
      appWindow->fgS0,  appWindow->fgS1,  appWindow->fgS2,  appWindow->fgS3,
      appWindow->fgS4,  appWindow->fgS5,  appWindow->fgS6,  appWindow->fgS7,
      appWindow->fgS8,  appWindow->fgS9,  appWindow->fgS10, appWindow->fgS11,
      appWindow->fgS12, appWindow->fgS13, appWindow->fgS14, appWindow->fgS15
    };

    QLabel* bgPaletteMap[24] = {
      appWindow->bgX0,  appWindow->bgX1,  appWindow->bgX2,  appWindow->bgX3,
      appWindow->bgP0,  appWindow->bgP1,  appWindow->bgP2,  appWindow->bgP3,
      appWindow->bgS0,  appWindow->bgS1,  appWindow->bgS2,  appWindow->bgS3,
      appWindow->bgS4,  appWindow->bgS5,  appWindow->bgS6,  appWindow->bgS7,
      appWindow->bgS8,  appWindow->bgS9,  appWindow->bgS10, appWindow->bgS11,
      appWindow->bgS12, appWindow->bgS13, appWindow->bgS14, appWindow->bgS15
    };

    for (int i = 0; i < 24; i++)
    {
      QColor col = colorFromPaletteAndIndex(palette, i);
      fgPaletteMap[i]->setPalette(QPalette(col, col));
      bgPaletteMap[i]->setPalette(QPalette(col, col));
      fgPaletteMap[i]->setFrameShape(QFrame::NoFrame);
      bgPaletteMap[i]->setFrameShape(QFrame::NoFrame);
    }

    uiContext->paletteEditorDialog.SetColorScheme( palette );// schemeNameEdit->setText(curPalette.c_str());

    appWindow->textFormatSelector->setUniformItemSizes(false);
    appWindow->textFormatSelector->clear();

    int c = TextRole::Last + 1;
    for (int i = 0; i < c; i++)
    {
      TextRoleProperties textProps = style.textProperties((TextRole)i);

      QListWidgetItem* item = new QListWidgetItem(textProps.m_displayName.c_str());
      appWindow->textFormatSelector->addItem(item);
      
      // QListWidgetItem* item = appWindow->textFormatSelector->item(i);
      
      QFont::Weight weight = QFont::Normal;
      bool italics = false;
      switch (textProps.m_fontStyle)
      {
        case FS_Normal:        weight = QFont::Normal; italics = false; break;
        case FS_Italics:       weight = QFont::Normal; italics = true;  break;
        case FS_Bold:          weight = QFont::Bold;   italics = false; break;
        case FS_BoldItalics:   weight = QFont::Bold;   italics = true;  break;
        case FS_StrikeThrough: weight = QFont::Normal; italics = false; break;
        case FS_Underline:     weight = QFont::Normal; italics = false; break;
        case FS_Light:         weight = QFont::Light;  italics = false; break;
        case FS_LightItalics:  weight = QFont::Light;  italics = true;  break;
        case FS_Medium:        weight = QFont::Medium; italics = false; break;
        case FS_MediumItalics: weight = QFont::Medium; italics = true;  break;
        case FS_FixedWidth:    weight = QFont::Normal; italics = false; break;
      }

      QFont font(textProps.m_fontName.c_str(), (int)textProps.m_fontSize, weight, italics);
      font.setStrikeOut(textProps.m_fontStyle == FS_StrikeThrough);
      font.setUnderline(textProps.m_fontStyle == FS_Underline);
      item->setFont(font);
      
      QColor fgCol = colorFromPaletteAndIndex(palette, textProps.m_foregroundColorIndex);
      QColor bgCol = colorFromPaletteAndIndex(palette, textProps.m_backgroundColorIndex);

      item->setForeground(QBrush(fgCol));
      item->setBackground(QBrush(bgCol));
    }
    
    appWindow->textFormatSelector->setCurrentRow(selectedTextRole);
    TextRoleProperties textProps = style.textProperties((TextRole)selectedTextRole);
   
    if (textProps.m_foregroundColorIndex >= 0 && textProps.m_foregroundColorIndex < 24)
      fgPaletteMap[ textProps.m_foregroundColorIndex ]->setFrameShape(QFrame::Box);
    if (textProps.m_backgroundColorIndex >= 0 && textProps.m_backgroundColorIndex < 24)
      bgPaletteMap[ textProps.m_backgroundColorIndex ]->setFrameShape(QFrame::Box);

    // If updating:
    //style.setTextProperties((TextRole)selectedTextRole, textProps);

    appWindow->fontCombo->setCurrentFont(QFont(textProps.m_fontName.c_str()));
    appWindow->fontStyleCombo->setCurrentIndex((int)textProps.m_fontStyle);
    
    appWindow->textIndent->setValue(textProps.m_textIndent);
    appWindow->bulletIndent->setValue(textProps.m_bulletIndent);
    appWindow->bulletType->setCurrentIndex((int)textProps.m_bulletType);
    appWindow->lineSpacing->setValue(textProps.m_lineSpacing);
    appWindow->spacing->setValue(textProps.m_spacing);
  }


  //if (templatesList().size())
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());


    if ( templatesList()[currentTemplate] != "Default.template" )
      updateProjectSettingsOnWidgetChange();


#define HPDF_DEF_PAGE_WIDTH         595.276F
#define HPDF_DEF_PAGE_HEIGHT        841.89F
    Point topLeft;
    Point bottomRight;
    int columns;
    float columnSpacing;
    templ.PageBounds(topLeft, bottomRight);
    templ.Columns(columns, columnSpacing);
    float footerHeight = HPDF_DEF_PAGE_HEIGHT - bottomRight.y;
    float rightMargin = HPDF_DEF_PAGE_WIDTH - bottomRight.x;
    appWindow->leftMargin->setValue(topLeft.x);
    appWindow->headerHeight->setValue(topLeft.y);
    appWindow->rightMargin->setValue(rightMargin);
    appWindow->footerHeight->setValue(footerHeight);
    printf("mdi set col: %i\n", columns);
    appWindow->columns->setValue(columns);
    appWindow->columnSpacing->setValue(columnSpacing);


    const std::vector<DocTemplateItem>& items = templ.Items();

    int selectedTemplateRow = appWindow->templateItems->currentRow();

    appWindow->templateItems->clear();
    for (const auto& itm : items)
    {
      QListWidgetItem* item = new QListWidgetItem(stringFromDocItemType(itm.m_type).c_str());
      appWindow->templateItems->addItem(item);
    }
    appWindow->templateItems->setCurrentRow(selectedTemplateRow);
    
    if (selectedTemplateRow >= 0 && selectedTemplateRow < items.size())
    {
      const DocTemplateItem& selectedTemplateItem = items[selectedTemplateRow];

#define ADD_TEMPLATE_ITEM(row, typ, mem) \
      appWindow->templateItemProperties->setItem(row, 1, new QTableWidgetItem(stringFrom##typ(selectedTemplateItem.mem).c_str()))

      ADD_TEMPLATE_ITEM( 0 , DocItemType , m_type);
      ADD_TEMPLATE_ITEM( 1 , TemplateRole, m_role);
      ADD_TEMPLATE_ITEM(13 , Alignment   , m_alignment);
      ADD_TEMPLATE_ITEM( 3 , Point       , m_pos);
      ADD_TEMPLATE_ITEM( 7 , Point       , m_size);
      ADD_TEMPLATE_ITEM( 2 , Float       , m_alpha);
      ADD_TEMPLATE_ITEM( 8 , Color       , m_fillColor);
      ADD_TEMPLATE_ITEM( 9 , Color       , m_penColor);
      ADD_TEMPLATE_ITEM(10 , Float       , m_penWidth);
      ADD_TEMPLATE_ITEM(11 , String      , m_font);
      ADD_TEMPLATE_ITEM(12 , Float       , m_fontSize);
      ADD_TEMPLATE_ITEM(15 , Alignment   , m_boundsAlignment);
      ADD_TEMPLATE_ITEM( 4 , String      , m_text);
      ADD_TEMPLATE_ITEM( 6 , Polygon     , m_shape);
      ADD_TEMPLATE_ITEM( 5 , String      , m_imageFile);
    }
  }

  avoidPopulateRecursion = false;
}


void MdiWindow::updateProjectSettingsOnTextChange()
{
  if (avoidProjectPopulateRecursion)
    return;
  avoidProjectPopulateRecursion = true;

  DocConvert::MetaData metaData;
  MdiChild* child = activeMdiChild();
  std::string password;
  if (child)
  {
    // printf("extracting meta from editor text\n");
    QByteArray text = child->toPlainText().toLatin1();
    DocConvert::ExtractMetadataValues(text.size(), text.data(), metaData);
    password = metaData.map["password"].value.c_str();
    password = DocConvert::CryptPassword(password);
  }
  appWindow->title->setText(metaData.map["title"].value.c_str());
  appWindow->subtitle->setText(metaData.map["subtitle"].value.c_str());
  appWindow->version->setText(metaData.map["version"].value.c_str());
  appWindow->author->setText(metaData.map["author"].value.c_str());
  appWindow->company->setText(metaData.map["company"].value.c_str());
  appWindow->address->setText(metaData.map["address"].value.c_str());
  appWindow->copyright->setText(metaData.map["copyright"].value.c_str());
  appWindow->comments->setPlainText(metaData.map["comment"].value.c_str());
  
  // TODO:  If template or style change, then update the project style/template combo-box
  // appWindow->styleCombo->setText(metaData.map["style"].value.c_str());

  std::string tmpl = metaData.map["template"].value.c_str();
  for (int i = 0; i < templatesList().size(); ++i)
  {
    if (templatesList()[i] == tmpl.c_str())
    {
      appWindow->templateSelector->setCurrentIndex(i);
      break;
    }
  }

  // not // Ignore password changes from editing the text - these won't be right
  appWindow->password->setText(password.c_str());

  avoidProjectPopulateRecursion = false;
}


static bool AskUserForName(const char* promptText, std::string& result)
{
  QInputDialog nameDialog;
  nameDialog.setInputMode(QInputDialog::TextInput);
  nameDialog.setLabelText(promptText);
  if (nameDialog.exec())
  {
    result = nameDialog.textValue().toLatin1().data();
    return true;
  }
  return false;
}


static bool ConfirmDelete(const char* name)
{
  return QMessageBox::question(nullptr, "Confirm Delete", QString("Are you sure you want to delete ") + name) == QMessageBox::Yes;
}


static void AlreadyExistsDialog(const char* name, const char* type)
{
  QMessageBox::warning(nullptr, "Already Exists", QString("Unable to create %1 as a %2 with the same name already exists.").arg(name).arg(type));
}


void MdiWindow::newStyle()
{
  std::string newStyleName;
  if (AskUserForName("Enter New Style Name", newStyleName))
  {
    newStyleName += ".style";
    printf("New style name will be: %s\n", newStyleName.c_str());
    // TODO: make it
    populateProjectDialogValues();
  }
}


void MdiWindow::deleteStyle()
{
  int currentStyle = appWindow->styleSelector->currentIndex();
  if (currentStyle >= 0 && stylesList().size() > currentStyle)
  {
    QString styleName = resourcesLocation() + "/Styles/" + stylesList()[currentStyle];
    if (ConfirmDelete(styleName.toLatin1().data()))
    {
      printf("will delete it\n");
      populateProjectDialogValues();
    }
  }
}


void MdiWindow::newTemplate()
{
  std::string newTemplateName;
  if (AskUserForName("Enter New Template Name", newTemplateName))
  {
    newTemplateName += ".template";
    printf("New template name will be: %s\n", newTemplateName.c_str());
    // TODO: make it
    populateProjectDialogValues();
  }
}


void MdiWindow::deleteTemplate()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    QString templateName = resourcesLocation() + "/Templates/" + templatesList()[currentTemplate];
    if (ConfirmDelete(templateName.toLatin1().data()))
    {
      printf("will delete it\n");
      populateProjectDialogValues();
    }
  }
}


void MdiWindow::templateChanged()
{
  MAKE_NON_REENTRANT
  if (avoidPopulateRecursion)
    return;

  //int currentProject = appWindow->projectSelector->currentIndex();
  //int currentStyle = appWindow->styleSelector->currentIndex();
  int currentTemplate = appWindow->templateSelector->currentIndex();

  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    const std::vector<DocTemplateItem>& items = templ.Items();

    int selectedTemplateRow = appWindow->templateItems->currentRow();
    if (selectedTemplateRow < 0 || items.size() <= selectedTemplateRow)
      return;
    const DocTemplateItem& selectedTemplateItem = items[selectedTemplateRow];
    DocTemplateItem newItem = selectedTemplateItem;

    newItem.m_type = stringToDocItemType(appWindow->templateItemProperties->item(0,  1)->text().toLatin1().data());

#define GET_TEMPLATE_ITEM(row, typ, mem) \
    { \
      auto item = appWindow->templateItemProperties->item(row, 1); \
      std::string txt = (item) ? item->text().toLatin1().data() : ""; \
      newItem.mem = stringTo##typ(txt); \
      /* appWindow->templateItemProperties->item(row, 1)->text().toLatin1().data() */; \
    }

    GET_TEMPLATE_ITEM( 1 , TemplateRole, m_role);
    GET_TEMPLATE_ITEM(13 , Alignment   , m_alignment);
    GET_TEMPLATE_ITEM( 3 , Point       , m_pos);
    GET_TEMPLATE_ITEM( 7 , Point       , m_size);
    GET_TEMPLATE_ITEM( 2 , Float       , m_alpha);
    GET_TEMPLATE_ITEM( 8 , Color       , m_fillColor);
    GET_TEMPLATE_ITEM( 9 , Color       , m_penColor);
    GET_TEMPLATE_ITEM(10 , Float       , m_penWidth);
    GET_TEMPLATE_ITEM(11 , String      , m_font);
    GET_TEMPLATE_ITEM(12 , Float       , m_fontSize);
    GET_TEMPLATE_ITEM(15 , Alignment   , m_boundsAlignment);
    GET_TEMPLATE_ITEM( 4 , String      , m_text);
    GET_TEMPLATE_ITEM( 6 , Polygon     , m_shape);
    GET_TEMPLATE_ITEM( 5 , String      , m_imageFile);

    templ.UpdateItem(selectedTemplateRow, newItem);
    templ.Save();
    updatePreview();

    // refresh item name in template item list
    appWindow->templateItems->clear();
    for (const auto& itm : items)
    {
      QListWidgetItem* item = new QListWidgetItem(stringFromDocItemType(itm.m_type).c_str());
      appWindow->templateItems->addItem(item);
    }
    appWindow->templateItems->setCurrentRow(selectedTemplateRow);
  }
}
    

void MdiWindow::removeTemplateItem()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();
  int selectedTemplateRow = appWindow->templateItems->currentRow();
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    if (selectedTemplateRow > 0)
    {
      templ.DeleteItem(selectedTemplateRow);
      templ.Save();
      appWindow->templateItems->setCurrentRow(selectedTemplateRow - 1);
      populateProjectDialogValues();
      updatePreview();
    }
  }
}


void MdiWindow::addTemplateItem()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();
  int selectedTemplateRow = appWindow->templateItems->currentRow();
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    templ.AddItem(DIT_Unknown);
    templ.Save();
    populateProjectDialogValues();
    updatePreview();
    appWindow->templateItems->setCurrentRow(templ.Items().size() - 1);
  }
}


void MdiWindow::moveTemplateItemUp()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();
  int selectedTemplateRow = appWindow->templateItems->currentRow();
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    appWindow->templateItems->setCurrentRow(templ.MoveItemUp(selectedTemplateRow));
    templ.Save();
    populateProjectDialogValues();
    updatePreview();
  }
}


void MdiWindow::moveTemplateItemDown()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();
  int selectedTemplateRow = appWindow->templateItems->currentRow();
  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    appWindow->templateItems->setCurrentRow(templ.MoveItemDown(selectedTemplateRow));
    templ.Save();
    populateProjectDialogValues();
    updatePreview();
  }
}


void MdiWindow::pageMarginsChanged()
{
  int currentTemplate = appWindow->templateSelector->currentIndex();

  if (currentTemplate >= 0 && templatesList().size() > currentTemplate)
  {
    DocTemplate templ;
	  templ.ReadTemplateFile(QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data());
    const std::vector<DocTemplateItem>& items = templ.Items();

    Point topLeft;
    Point bottomRight;
    topLeft.x = appWindow->leftMargin->value();
    topLeft.y = appWindow->headerHeight->value();
    float rightMargin = appWindow->rightMargin->value();
    float footerMargin = appWindow->footerHeight->value();
    bottomRight.x = HPDF_DEF_PAGE_WIDTH - rightMargin;
    bottomRight.y = HPDF_DEF_PAGE_HEIGHT - footerMargin;

    templ.SetPageBounds(topLeft, bottomRight);
    templ.SetColumns((int)appWindow->columns->value(), (float)appWindow->columnSpacing->value());
    templ.Save();
    updatePreview();
  }
}


void MdiWindow::paletteChanged()
{
  if (stylesList().size())
  {
    DocStyle style;
    int currentStyle = appWindow->styleSelector->currentIndex();

    style.readStyleFile(QString(resourcesLocation() + "/Styles/" + stylesList()[currentStyle]).toLatin1().data());

    QString newPaletteName = uiContext->paletteEditor.schemeNameEdit->text();
    int pal = uiContext->paletteEditorDialog.GetColorScheme();
    if (paletteEditIsNew)
    {
      style.addPalette(newPaletteName.toLatin1().data(), pal);
    }
    else
    {
      std::string curPalette = appWindow->paletteCombo->currentText().toLatin1().data();
      if (curPalette.c_str() == newPaletteName)
      {
        // changed
        style.editPalette(curPalette, pal);
      }
      else
      {
        // renamed
        style.removePalette(curPalette);
        style.addPalette(newPaletteName.toLatin1().data(), pal);
      }
    }
 
    // Re-populate the combo
    std::string newPal = newPaletteName.toLatin1().data();
    std::vector<std::string> palettes = style.paletteList();
    appWindow->paletteCombo->clear();
    int palIdx = 0; bool found = false;
    for (auto pal : palettes)
    {
      found = found || (pal == newPal);
      appWindow->paletteCombo->addItem(pal.c_str());
      palIdx += (found) ? 0 : 1;
    }
    // Set it on the edited one
    appWindow->paletteCombo->setCurrentIndex(palIdx);

    style.writeStyleFile(QString(resourcesLocation() + "/Styles/" + stylesList()[currentStyle]).toLatin1().data());
    
    populateProjectDialogValues();
  }

}


void MdiWindow::newPalette()
{
  paletteEditIsNew = true;
  uiContext->paletteEditor.schemeNameEdit->setText("New Palette");
  uiContext->paletteEditorDialog.SetColorScheme(0x0);
  uiContext->paletteEditorDialog.exec();
}


void MdiWindow::editPalette()
{
  paletteEditIsNew = false;
  QString curPalette = appWindow->paletteCombo->currentText();
  uiContext->paletteEditor.schemeNameEdit->setText(curPalette);
  //uiContext->paletteEditorDialog.SetColorScheme(style.palette(curPalette.toLatin1().data()));
  uiContext->paletteEditorDialog.exec();
}


void MdiWindow::checkForUpdates()
{
  if (checkRequest)
    return;

  uiContext->checkUpdates.checkLabel->setEnabled(true);
  uiContext->checkUpdates.checkLabel->setText("Checking for updates...");
  uiContext->checkUpdates.checkProgressBar->setEnabled(true);
  uiContext->checkUpdates.checkProgressBar->setValue(25);
  uiContext->checkUpdates.checkReady->setEnabled(false);

  QNetworkRequest request(QUrl("https://www.subflexion.com/WickedDocs/updates/latest"));
  checkRequest = networkManager.get(request);
  QObject::connect(checkRequest, SIGNAL(downloadProgress(qint64,qint64)), SLOT(checkProgress(qint64,qint64)));
  QObject::connect(checkRequest, SIGNAL(finished()),                      SLOT(checkFinished()));
}


void MdiWindow::checkProgress(qint64 bytesReceived, qint64 bytesTotal)
{
  if (bytesTotal)
    uiContext->checkUpdates.checkProgressBar->setValue(25 + (bytesReceived * 75 / bytesTotal));
}


// When check completes
void MdiWindow::checkFinished()
{
  if (!checkRequest)
    return;

  bool okay = checkRequest->error() == QNetworkReply::NoError;
  QByteArray ba;
  if (okay)
    ba = checkRequest->readAll();
  checkRequest->deleteLater();
  checkRequest = nullptr;

  uiContext->checkUpdates.checkLabel->setText("");
  uiContext->checkUpdates.checkReady->setEnabled(true);
  uiContext->checkUpdates.checkedVersion->setEnabled(true);
  uiContext->checkUpdates.checkProgressBar->setValue(okay ? 100 : 0);
  uiContext->checkUpdates.checkReady->setText(okay ? "Latest version available:" : "Error checking for updates.");
  uiContext->checkUpdates.checkedVersion->setText("");
  uiContext->checkUpdates.downloadButton->setEnabled(okay);

  if (!okay)
    return;

  std::string data = ba.data();
  auto csv = split(data, ',');
  latestVersion = csv[0];
  latestUrl = csv[1];
  printf("latest ver:  %s\n", latestVersion.c_str());
  printf("latest url:  %s\n", latestUrl.c_str());
  uiContext->checkUpdates.checkedVersion->setText(latestVersion.c_str());
}


void MdiWindow::downloadUpdate()
{
  if (downloadRequest)
    return;

  uiContext->checkUpdates.downloadLabel->setEnabled(true);
  uiContext->checkUpdates.downloadLabel->setText("Downloading update...");
  uiContext->checkUpdates.downloadProgressBar->setEnabled(true);
  uiContext->checkUpdates.downloadProgressBar->setValue(0);
  uiContext->checkUpdates.downloadReady->setEnabled(false);

  QNetworkRequest request(QUrl(latestUrl.c_str()));
  downloadRequest = networkManager.get(request);
  QObject::connect(downloadRequest, SIGNAL(downloadProgress(qint64,qint64)), SLOT(downloadProgress(qint64,qint64)));
  QObject::connect(downloadRequest, SIGNAL(finished()),                      SLOT(downloadFinished()));
}


void MdiWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
  if (bytesTotal)
    uiContext->checkUpdates.downloadProgressBar->setValue(bytesReceived * 100 / bytesTotal);
}


// When download completes
void MdiWindow::downloadFinished()
{
  if (!downloadRequest)
    return;

  bool okay = downloadRequest->error() == QNetworkReply::NoError;
  QByteArray downloadedInstaller;
  if (okay)
    downloadedInstaller = downloadRequest->readAll();
  downloadRequest->deleteLater();
  downloadRequest = nullptr;

  uiContext->checkUpdates.downloadLabel->setText("");
  uiContext->checkUpdates.downloadReady->setEnabled(true);
  uiContext->checkUpdates.downloadedVersion->setEnabled(true);
  uiContext->checkUpdates.downloadProgressBar->setValue(okay ? 100 : 0);
  uiContext->checkUpdates.downloadReady->setText(okay ? "Version ready to install:" : "Error downloading update.");
  uiContext->checkUpdates.downloadedVersion->setText("");
  uiContext->checkUpdates.installButton->setEnabled(okay);

  if (!okay)
    return;

  // Validate the download - check signature - updates/latest version contains   ver,url,signed-hash
  //   process:
  //     - use pub key to decrypt the signed-hash
  //     - create hash of the download
  //     - compare the hashes
  //     - if okay, allow installing
  //     - else ask to check for updates and download again

  // Goal
  //   - all installations should have a unique LicenseId
  //
  //   - checks for updates on start (by default) - should pass the LicenseId in the request
  //         - can track executions

  // Copy protection ideas
  //   - when application is installed, if no license, then it goes to website to request a trial license
  //         - can track installs
  //   - when application is installed, if license is valid but for different version, then it goes to website to request a new license
  //         - can track updates
  //
  //   - trial license is generated with date and installed to read-only location
  //
  //   - license also contains hash of the binary
  //         - prevent the app being modified
  //

  uiContext->checkUpdates.downloadedVersion->setText(latestVersion.c_str());
}


void MdiWindow::installUpdate()
{
  uiContext->checkUpdates.installLabel->setEnabled(true);
  uiContext->checkUpdates.installLabel->setText("Installing...");
  uiContext->checkUpdates.installProgressBar->setEnabled(true);
  uiContext->checkUpdates.installProgressBar->setValue(0);

  // TODO: actually hook this up
  QTimer::singleShot(2000, this, SLOT(installFinished()));
}


void MdiWindow::installProgress(qint64 bytesReceived, qint64 bytesTotal)
{
}


// When install completes
void MdiWindow::installFinished()
{
  uiContext->checkUpdates.installLabel->setText("");
  uiContext->checkUpdates.downloadReady->setText("Version installed:");
  uiContext->checkUpdates.installProgressBar->setValue(100);
  uiContext->checkUpdates.restartButton->setEnabled(true);
}


void MdiWindow::openRecentFile()
{
  QAction *action = qobject_cast<QAction *>(sender());
  if (action)
    openFile(action->data().toString());
}


void MdiWindow::setCurrentFile(const QString &fileName)
{
  // setWindowFilePath(fileName);

  QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "Subflexion", "WickedDocs");
  settings.beginGroup("MainWindow");
  QStringList files = settings.value("RecentFileList").toStringList();
  files.removeAll(fileName);
  files.prepend(fileName);
  while (files.size() > MaxRecentFiles)
    files.removeLast();
  settings.setValue("RecentFileList", files);
  settings.endGroup();

  updateRecentFileActions();
}


void MdiWindow::updateRecentFileActions()
{
  QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "Subflexion", "WickedDocs");
  settings.beginGroup("MainWindow");
  QStringList files = settings.value("RecentFileList").toStringList();
  settings.endGroup();

  for (int i = 0; i < MaxRecentFiles; ++i)
  {
    bool visible = i < files.size();
    if (visible)
    {
      QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName());
      recentFileActs[i]->setText(text);
      recentFileActs[i]->setData(files[i]);
    }
    recentFileActs[i]->setVisible(visible);
  }
  separatorAct->setVisible(files.size() > 0);
}


void MdiWindow::restart()
{
  qApp->quit();
}


// TODO:
bool MdiWindow::RequestTrialUse()
{
  return false;
}


// TODO:
bool MdiWindow::RequestLicenseUpgrade()
{
  QObject::connect(uiContext->registration.applyLicense, SIGNAL(clicked()), this, SLOT(licenseActivate()));  // TODO
  if (!uiContext->registrationDialog.exec())
  {
    /*
    LicenseChecker license;
    if (!license.LicenseValid())
    {
      QMessageBox::critical(this, "Activation required", "Activation of the application is required. The application will now quit.");
      qApp->quit();
    }
    */
  }
  return false;
}


bool MdiWindow::RequestTrialActivation()
{
  QObject::connect(uiContext->evaluation.trialActivate, SIGNAL(clicked()), this, SLOT(trialActivate())); 
  if (!uiContext->evaluationDialog.exec())
  {
    LicenseChecker license;
    if (!license.LicenseValid())
    {
      QMessageBox::critical(this, "Activation required", "Activation of the application is required. The application will now quit.");
      qApp->quit();
    }
  }
  return false;
}


static bool EmailValid(const std::string& email)
{
  // return std::regex_match(email, std::regex("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+"));
  return std::regex_match(email, std::regex("(.*)@(.*)"));
}


void MdiWindow::trialActivate()
{
  if (trialActivateRequest)
    return;

  std::string name = uiContext->evaluation.trialName->text().toUtf8().data();
  std::string email = uiContext->evaluation.trialEmail->text().toUtf8().data();

  if (name.size() < 2)
  {
    QMessageBox::warning(this, "Name too short", "Please enter your name.");
    return;
  }

  if (!EmailValid(email))
  {
    QMessageBox::warning(this, "Email invalid", "Please enter a valid email address.");
    return;
  }

  uiContext->evaluation.activateLabel->setEnabled(true);
  uiContext->evaluation.activateLabel->setText("Activating...");
  uiContext->evaluation.activateProgressBar->setEnabled(true);
  uiContext->evaluation.activateProgressBar->setValue(0);

  QNetworkRequest request(QUrl("https://www.subflexion.com/WickedDocs/activation/trial.php"));
  request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");


  // TODO : - refactor to a function which makes request to the server
  //          - can call this function with a given id
  //          - for activating the trial, generate the id (from computer fingerprint)
  //          - for continuing the trial, read it from the saved license
  //          - check the response
  //          - have server check dates

  auto ba = qApp->arguments().at(0).toUtf8();
  const char* argv0 = ba.data();
  CheckApplicationName(argv0);

  QUrlQuery params;
  params.addQueryItem("id", QString::number(GetMacAddress(), 16));
  params.addQueryItem("date", QDate::currentDate().toString("dd/MM/yyyy"));
  params.addQueryItem("product", APP_NAME);
  params.addQueryItem("platform", YQ_STRINGIZE(OS_PLATFORM));
  params.addQueryItem("version", YQ_STRINGIZE(APP_VERSION));
  params.addQueryItem("name", name.c_str());
  params.addQueryItem("email", email.c_str());
  params.addQueryItem("application", ApplicationHash(argv0).c_str());
  params.addQueryItem("machine", SmudgedFingerPrint(argv0).c_str());

std::string SmudgedFingerPrint(const char* file);
  QByteArray data = params.toString(QUrl::FullyEncoded).toLatin1();

  trialActivateRequest = networkManager.post(request, data);
  QObject::connect(trialActivateRequest, SIGNAL(downloadProgress(qint64,qint64)), SLOT(trialActivationProgress(qint64,qint64)));
  QObject::connect(trialActivateRequest, SIGNAL(finished()),                      SLOT(trialActivationFinished()));
}


void MdiWindow::trialActivationProgress(qint64 bytesReceived, qint64 bytesTotal)
{
  if (bytesTotal)
    uiContext->evaluation.activateProgressBar->setValue(bytesReceived * 100 / bytesTotal);
}


// When activate completes
void MdiWindow::trialActivationFinished()
{
  if (!trialActivateRequest)
    return;

  bool okay = trialActivateRequest->error() == QNetworkReply::NoError;
  QByteArray trialActivateionResult;
  if (okay)
    trialActivateionResult = trialActivateRequest->readAll();
  trialActivateRequest->deleteLater();
  trialActivateRequest = nullptr;

  uiContext->evaluation.activateLabel->setText("");
  //uiContext->evaluation.activateReady->setEnabled(true);
  //uiContext->evaluation.activateedVersion->setEnabled(true);
  uiContext->evaluation.activateProgressBar->setValue(okay ? 100 : 0);
  //uiContext->evaluation.activateReady->setText(okay ? "Version ready to install:" : "Error downloading update.");
  //uiContext->evaluation.activateedVersion->setText("");
  //uiContext->checkUpdates.installButton->setEnabled(okay);

  QString result(trialActivateionResult.data());
  if (!okay)
  {
    QMessageBox::critical(this, "Activation Error", "Error while activating, message from server was: " + result);
  }
  else
  {
    LicenseChecker license;
    if (license.LicenseExists())
    {
      QMessageBox::critical(this, "Activation Failure", "Error 008 - inconsistency");
      qApp->quit();
    }
    else
    {
      if (!license.InstallLicense(result.toUtf8().data()))
      {
        QMessageBox::critical(this, "Activation Failure", "Error 009 - could not write license file to disk. Check that home directory is writable.");
      }
      else
      {
        LicenseChecker license2;
        if (license2.LicenseValid())
        {
          QMessageBox::information(this, "Activation Success", "Your trial has been successfully activated.");
          uiContext->evaluationDialog.close();
        }
        else
        {
          QMessageBox::critical(this, "Activation Failure", "Error 010 - server returned an invalid license.");
        }
      }
    }
    //QMessageBox::critical(this, "Activation Success", "Message from server was: " + result);
    //printf("license:\n%s\n", result.toUtf8().data());
    //FILE* f = fopen("
  }
}


void MdiWindow::showTrialDialog()
{
  QObject::connect(uiContext->trial.buyNowButton,       SIGNAL(clicked()), this, SLOT( upgradeClicked() ));          // takes user to registration page
  QObject::connect(uiContext->trial.enterLicenseButton, SIGNAL(clicked()), this, SLOT( RequestLicenseUpgrade() ));

  // TODO: phone-home that invoked again, check if new day, count another day of trial used up
  //       if no internet connection, then try to record that locally, allow so many times to start offline, then require an internet connection
  //       if have an internet connection, send info of times started offline etc.
  //       of perhaps flat out only allow in trial mode to run with an internet connection - give a dialog to say that if registered, this doesn't happen

  LicenseChecker license;
  QDate licenseDate = QDate::fromString(license.Date().c_str(), "dd/MM/yyyy");
  QDate currentDate = QDate::currentDate();
  int daysRemaining = 30 - licenseDate.daysTo(currentDate);

  if (daysRemaining <= 0)
  {
    QMessageBox::critical(this, "Trial expired", "Sorry, your evaluation period has ended. If you wish to continue using this application, please purchase a license. The application will now quit.");
    qApp->quit();
  
  }

  if (daysRemaining > 31) // 31 is an extra day to allow for timezone differences. Hopefully 1 day is enough to handle this. Server and client might be different
                          // and client then changes timezones, but max time diff between places on the world would have to be up to just under 24 hours. At some point
                          // you cross the international date line and there is a 24 hour change bringing the time diff back closer together.
  {
    QMessageBox::critical(this, "Clock changed", "Sorry, it appears you have set your date and time to before you began your evaluation. Please check your date and time settings and try again. The application will now quit.");
    qApp->quit();
  }

  uiContext->trial.trialTitle->setText(QString("Evaluation License - %1 Days Left").arg(daysRemaining));
  if (!uiContext->trialDialog.exec())
  {
    QMessageBox::critical(this, "Trial agreement required", "Agreement with the terms of the evaluation license are required. The application will now quit.");
    qApp->quit();
  }
  else
  {
    // todo: phone home
    // Do a server authoritative check of the license against known licenses. If it is not found, perhaps log it and accept it if the signature checks out

    /*
    QNetworkRequest request(QUrl("https://www.subflexion.com/WickedDocs/activation/trial.php"));
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");


    // TODO : - refactor to a function which makes request to the server
    //          - can call this function with a given id
    //          - for activating the trial, generate the id (from computer fingerprint)
    //          - for continuing the trial, read it from the saved license
    //          - check the response
    //          - have server check dates


    QUrlQuery params;
    params.addQueryItem("id", QString::number(GetMacAddress(), 16));
    params.addQueryItem("date", QDate::currentDate().toString("dd/MM/yyyy"));
    params.addQueryItem("product", APP_NAME);
    params.addQueryItem("platform", YQ_STRINGIZE(OS_PLATFORM));
    params.addQueryItem("version", YQ_STRINGIZE(APP_VERSION));
    params.addQueryItem("name", name.c_str());
    params.addQueryItem("email", email.c_str());
    QByteArray data = params.toString(QUrl::FullyEncoded).toLatin1();

    trialActivateRequest = networkManager.post(request, data);
    QObject::connect(trialActivateRequest, SIGNAL(downloadProgress(qint64,qint64)), SLOT(trialActivationProgress(qint64,qint64)));
    QObject::connect(trialActivateRequest, SIGNAL(finished()),                      SLOT(trialActivationFinished()));
    */

  }
}


MdiWindow::MdiWindow(QWidget *parent)
    : QMainWindow(parent)
    , appWindow(nullptr)
{
  server = 0;
  //QTimer::singleShot(1000, this, SLOT(setupServer()));
}


QObject* MakeTestObjectTree();
void EnumPropertyEditorTest();
void MdiWindow::init(Ui_MainWindow* ui, UiContext* context)
{
    // server = 0;
    appWindow = ui;
    uiContext = context;
    mdiArea = ui->mdiArea;//new QMdiArea;
    connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateActions()));
    connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updatePreview()));
    connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateProjectSettingsOnTextChange()));

    windowMapper = new QSignalMapper(this);
    connect(windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));

    QObject::connect(appWindow->actionNew,          SIGNAL(triggered(bool)), this, SLOT(newFile()));
    QObject::connect(appWindow->actionOpen,         SIGNAL(triggered(bool)), this, SLOT(open()));
    QObject::connect(appWindow->actionSave,         SIGNAL(triggered(bool)), this, SLOT(save()));
    QObject::connect(appWindow->actionSave_As,      SIGNAL(triggered(bool)), this, SLOT(saveAs()));
    QObject::connect(appWindow->actionExport,       SIGNAL(triggered(bool)), this, SLOT(exportPDF()));
    QObject::connect(appWindow->actionSwitchRTL,    SIGNAL(triggered(bool)), this, SLOT(switchLayoutDirection()));
#ifndef QT_NO_CLIPBOARD
    QObject::connect(appWindow->actionCut,          SIGNAL(triggered(bool)), this, SLOT(cut()));
    QObject::connect(appWindow->actionCopy,         SIGNAL(triggered(bool)), this, SLOT(copy()));
    QObject::connect(appWindow->actionPaste,        SIGNAL(triggered(bool)), this, SLOT(paste()));
#endif
    QObject::connect(appWindow->actionBold,         SIGNAL(triggered(bool)), this, SLOT(bold()));
    QObject::connect(appWindow->actionItalic,       SIGNAL(triggered(bool)), this, SLOT(italic()));
    QObject::connect(appWindow->actionQuote,        SIGNAL(triggered(bool)), this, SLOT(quote()));
    QObject::connect(appWindow->actionCode,         SIGNAL(triggered(bool)), this, SLOT(code()));
    QObject::connect(appWindow->actionHeading_1,    SIGNAL(triggered(bool)), this, SLOT(heading1()));
    QObject::connect(appWindow->actionHeading_2,    SIGNAL(triggered(bool)), this, SLOT(heading2()));
    QObject::connect(appWindow->actionHeading_3,    SIGNAL(triggered(bool)), this, SLOT(heading3()));
    QObject::connect(appWindow->actionHeading_4,    SIGNAL(triggered(bool)), this, SLOT(heading4()));
    QObject::connect(appWindow->actionHyperlink,    SIGNAL(triggered(bool)), this, SLOT(hyperlink()));
    QObject::connect(appWindow->actionImage,        SIGNAL(triggered(bool)), this, SLOT(image()));
    QObject::connect(appWindow->actionUnordered_List,    SIGNAL(triggered(bool)), this, SLOT(unorderedList()));
    QObject::connect(appWindow->actionOrdered_List,      SIGNAL(triggered(bool)), this, SLOT(orderedList()));
    QObject::connect(appWindow->actionHorizontal_Rule,   SIGNAL(triggered(bool)), this, SLOT(horizontalRule()));
    QObject::connect(appWindow->actionInsertTimestamp,   SIGNAL(triggered(bool)), this, SLOT(timestamp()));
    QObject::connect(appWindow->actionInsertPageNumber,  SIGNAL(triggered(bool)), this, SLOT(pageNumber()));
    QObject::connect(appWindow->actionInsertPageCount,   SIGNAL(triggered(bool)), this, SLOT(pageCount()));
    QObject::connect(appWindow->actionInsertDate,   SIGNAL(triggered(bool)), this, SLOT(date()));
    QObject::connect(appWindow->actionInsertTime,   SIGNAL(triggered(bool)), this, SLOT(time()));
    QObject::connect(appWindow->actionInsertTOC,    SIGNAL(triggered(bool)), this, SLOT(tableOfContents()));
    QObject::connect(appWindow->actionOptions,      SIGNAL(triggered(bool)), this, SLOT(options()));
    QObject::connect(appWindow->actionSave_All,     SIGNAL(triggered(bool)), this, SLOT(saveAll()));
    QObject::connect(appWindow->actionSelect_All,   SIGNAL(triggered(bool)), this, SLOT(selectAll()));
    QObject::connect(appWindow->actionDelete,       SIGNAL(triggered(bool)), this, SLOT(del()));
    QObject::connect(appWindow->actionUndo,         SIGNAL(triggered(bool)), this, SLOT(undo()));
    QObject::connect(appWindow->actionRedo,         SIGNAL(triggered(bool)), this, SLOT(redo()));
    QObject::connect(appWindow->actionFind,         SIGNAL(triggered(bool)), this, SLOT(find()));
    QObject::connect(appWindow->actionFind_Next,    SIGNAL(triggered(bool)), this, SLOT(findNext()));
    QObject::connect(appWindow->actionFind_Previous,SIGNAL(triggered(bool)), this, SLOT(findPrevious()));
    QObject::connect(appWindow->actionGo_to_Line,   SIGNAL(triggered(bool)), this, SLOT(goToLine()));
    QObject::connect(appWindow->action_Tile_Windows,SIGNAL(triggered(bool)), this, SLOT(tileSubWindows()));
    QObject::connect(appWindow->action_Windowed,    SIGNAL(triggered(bool)), this, SLOT(cascadeSubWindows()));
    QObject::connect(appWindow->actionTabbed,       SIGNAL(triggered(bool)), this, SLOT(tabSubWindows()));
    QObject::connect(appWindow->actionClose,        SIGNAL(triggered(bool)), mdiArea, SLOT(closeActiveSubWindow()));
    QObject::connect(appWindow->actionClose_All,    SIGNAL(triggered(bool)), mdiArea, SLOT(closeAllSubWindows()));
    QObject::connect(appWindow->actionNext,         SIGNAL(triggered(bool)), mdiArea, SLOT(activateNextSubWindow()));
    QObject::connect(appWindow->actionPrevious,     SIGNAL(triggered(bool)), mdiArea, SLOT(activatePreviousSubWindow()));

    QObject::connect(appWindow->actionSupport,      SIGNAL(triggered(bool)), this, SLOT(support()));
    QObject::connect(appWindow->actionWebsite,      SIGNAL(triggered(bool)), this, SLOT(website()));

    QObject::connect(appWindow->actionRefresh_Preview, SIGNAL(triggered(bool)), this, SLOT(refreshPreviewNow()));
    QObject::connect(appWindow->previewRefresh,        SIGNAL(clicked()),       this, SLOT(refreshPreviewNow()));
    QObject::connect(appWindow->previewAutoRefresh,    SIGNAL(toggled(bool)),   this, SLOT(setAutoRefreshEnabled(bool)));
    QObject::connect(appWindow->actionToolbar_Labels,  SIGNAL(toggled(bool)),   this, SLOT(setToolbarLabelsEnabled(bool)));
    QObject::connect(appWindow->actionToolbar_Small,   SIGNAL(toggled(bool)),   this, SLOT(setToolbarSmall(bool)));
    
    appWindow->previewDock->toggleViewAction()->setIcon(appWindow->actionPreview->icon());
    appWindow->projectDock->toggleViewAction()->setIcon(appWindow->actionProject->icon());
    appWindow->viewToolBar->addAction(appWindow->previewDock->toggleViewAction());
    appWindow->viewToolBar->addAction(appWindow->projectDock->toggleViewAction());
    appWindow->viewToolBar->removeAction(appWindow->actionPreview);
    appWindow->viewToolBar->removeAction(appWindow->actionProject);
    
    QObject::connect(appWindow->title,       SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->subtitle,    SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->version,     SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->author,      SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->company,     SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->copyright,   SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->password,    SIGNAL(textChanged(const QString&)),  this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->address,     SIGNAL(textChanged()),                this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->comments,    SIGNAL(textChanged()),                this, SLOT(updateProjectSettingsOnWidgetChange()));
    QObject::connect(appWindow->viewPassword,SIGNAL(pressed()),                    this, SLOT(passwordShow()));
    QObject::connect(appWindow->viewPassword,SIGNAL(released()),                   this, SLOT(passwordHide()));
  
    // Hookup refresh of project settings in ui on changes
    QObject::connect(appWindow->textFormatSelector, SIGNAL(currentRowChanged(int)),   this, SLOT(populateProjectDialogValues()));
    QObject::connect(appWindow->styleSelector,      SIGNAL(currentIndexChanged(int)), this, SLOT(populateProjectDialogValues()));
    QObject::connect(appWindow->paletteCombo,       SIGNAL(currentIndexChanged(int)), this, SLOT(populateProjectDialogValues()));
    QObject::connect(appWindow->templateItems,      SIGNAL(currentRowChanged(int)),   this, SLOT(populateProjectDialogValues()));
    QObject::connect(appWindow->templateSelector,   SIGNAL(currentIndexChanged(int)),   this, SLOT(populateProjectDialogValues()));
    
    //QObject::connect(appWindow->textFormatSelector, SIGNAL(currentRowChanged(int)), this, SLOT(populateProjectDialogValues()));

    // TODO: need to set the name and values in the dialog when go to edit it
    QObject::connect(appWindow->editPalette,  SIGNAL(clicked()),  this, SLOT(editPalette()));
    QObject::connect(appWindow->newPalette,   SIGNAL(clicked()),  this, SLOT(newPalette()));
    QObject::connect(&uiContext->paletteEditorDialog,  SIGNAL(accepted()),  this, SLOT(paletteChanged()));

    QObject::connect(appWindow->templateItemProperties,  SIGNAL(cellChanged(int,int)),  this, SLOT(templateChanged()));

    QObject::connect(appWindow->headerHeight, SIGNAL(valueChanged(double)), this, SLOT(pageMarginsChanged()));
    QObject::connect(appWindow->leftMargin,   SIGNAL(valueChanged(double)), this, SLOT(pageMarginsChanged()));
    QObject::connect(appWindow->footerHeight, SIGNAL(valueChanged(double)), this, SLOT(pageMarginsChanged()));
    QObject::connect(appWindow->rightMargin,  SIGNAL(valueChanged(double)), this, SLOT(pageMarginsChanged()));
    QObject::connect(appWindow->columns,      SIGNAL(valueChanged(int)),    this, SLOT(pageMarginsChanged()));
    QObject::connect(appWindow->columnSpacing,SIGNAL(valueChanged(double)), this, SLOT(pageMarginsChanged()));
    
    QObject::connect(appWindow->removeTemplateItem,   SIGNAL(clicked()), this, SLOT(removeTemplateItem()));
    QObject::connect(appWindow->addTemplateItem,      SIGNAL(clicked()), this, SLOT(addTemplateItem()));
    QObject::connect(appWindow->moveTemplateItemUp,   SIGNAL(clicked()), this, SLOT(moveTemplateItemUp()));
    QObject::connect(appWindow->moveTemplateItemDown, SIGNAL(clicked()), this, SLOT(moveTemplateItemDown()));

    QObject::connect(appWindow->newStyle,       SIGNAL(clicked()),  this, SLOT(newStyle()));
    QObject::connect(appWindow->deleteStyle,    SIGNAL(clicked()),  this, SLOT(deleteStyle()));
    QObject::connect(appWindow->newTemplate,    SIGNAL(clicked()),  this, SLOT(newTemplate()));
    QObject::connect(appWindow->deleteTemplate, SIGNAL(clicked()),  this, SLOT(deleteTemplate()));

    QObject::connect(uiContext->checkUpdates.checkButton,     SIGNAL(clicked()),  this, SLOT(checkForUpdates()));
    QObject::connect(uiContext->checkUpdates.downloadButton,  SIGNAL(clicked()),  this, SLOT(downloadUpdate()));
    QObject::connect(uiContext->checkUpdates.installButton,   SIGNAL(clicked()),  this, SLOT(installUpdate()));
    QObject::connect(uiContext->checkUpdates.restartButton,   SIGNAL(clicked()),  this, SLOT(restart()));
  
    uiContext->checkUpdates.currentVersion->setText(YQ_STRINGIZE(APP_VERSION));

    QString helpTopicMain = "https://www.subflexion.com/WickedDocs/app_docs.html";
    uiContext->help.helpViewer->setUrl(QUrl(helpTopicMain));

    m_autoRefreshEnabled = appWindow->previewAutoRefresh->isChecked();

    setAcceptDrops(true);

    currentStyle = "Default";
    currentTemplate = "Default";
    populateProjectDialogValues();


    statusBar()->showMessage(tr("Ready"));
    updateActions();

    setStyleSheet("\
QToolBar:!active\
{ border: 0px; background-color: #F6F6F6; }\
QToolBar:active\
{ border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d4d5d6, stop: 1 #cacbcc); }\
");

    // EnumPropertyEditorTest();
    updateObjectTree(MakeTestObjectTree());
    // changeProjectTree(this);

    Project *p = new Project;
    p->setObjectName("P1");
    p->m_Author = "John";
    //p->m_Name = "Project Name";

    /*
    Output *out = new Output;
    out->m_Title = "Title";
    out->m_Filename = "OutFile.pdf";
    out->m_FileType = Output::PDF;
    p->setOutput(out);

    Template *tmpl = new Template;
    tmpl->m_Filename = "Templ.tmpl";
    tmpl->m_Color = QColor(1,2,3);
    p->setTemplate(tmpl);
    */

    p->setTitle("Title");
    p->setOutputFilename("OutFile.pdf");
    p->setOutputFileType(Project::PDF);
    p->setTemplate("Templ.tmpl");
    p->setColor(QColor(1,2,3));

    Input *in = new Input;
    in->setObjectName("InFile1");
    in->m_Filename = "InFile1.md";
    p->AddInput(in);

    Input *in2 = new Input;
    in2->setObjectName("InFile2");
    in2->m_Filename = "InFile2.md";
    p->AddInput(in2);

    /*
    in.m_Filename = "InFile2.md";
    p.m_Inputs.append(in);
    in.m_Filename = "InFile3.md";
    p.m_Inputs.append(in);
    */

    //solution.m_Projects.append(p);
    //solution.AddProject(p);

    /*
    p.m_Name = "Project Name 2";
    p.m_Output.m_Title = "Title 2";
    p.m_Output.m_Filename = "OutFile2.pdf";
    solution.m_Projects.append(p);
    */

    changeProjectTree(p);
    //changeProjectTree(&solution);
    //changeProjectTree(this);

    //tabifyDockWidget(appWindow->projectDock, appWindow->objectsDock);
    //tabifyDockWidget(appWindow->projectDock, appWindow->propertiesDock);
    appWindow->projectDock->raise();

    readSettings();

    //if (BT_IsRelease)
    {
      // If want to make a macOS native looking toolbar, perhaps can do something like this:
      //appWindow->macToolbar->setTitleBarWidget(new QWidget(appWindow->macToolbar));
      //appWindow->macToolbar->setStyleSheet("QWidget { border: 0px; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #d4d5d6, stop: 1 #cacbcc); }");

      appWindow->macToolbar->hide();
      appWindow->eventLogDock->hide();
      appWindow->consoleDock->hide();
      appWindow->menuBar->removeAction(appWindow->menu_Debug_Test->menuAction());

      removeDockWidget(appWindow->macToolbar);
      removeDockWidget(appWindow->eventLogDock);
      removeDockWidget(appWindow->consoleDock);
      removeDockWidget(appWindow->legacyProjectDock);
      removeDockWidget(appWindow->objectsDock);
      removeDockWidget(appWindow->propertiesDock);
    }
}


void MdiWindow::dragEnterEvent(QDragEnterEvent *e)
{
    if (e->mimeData()->hasUrls())
    {
        e->acceptProposedAction();
    }
}


void MdiWindow::dropEvent(QDropEvent *e)
{
    int i = 0;
    foreach (const QUrl &url, e->mimeData()->urls())
    {
      // prompt if more than 10 files dragged asking user if they want to continue opening
      if (i++ == 10 && QMessageBox::question(nullptr, "Confirm Opening", QString("Do you want to continue opening more dragged files?")) != QMessageBox::Yes)
        break;
      openFile(url.toLocalFile());
    }
}


void MdiWindow::closeEvent(QCloseEvent *event)
{
    YQ_LOG_DEBUG("Closing all documents (received MdiWindow::closeEvent)");
    mdiArea->closeAllSubWindows();
    if (mdiArea->currentSubWindow()) {
        YQ_LOG_DEBUG("Ignoring MdiWindow::closeEvent");
        event->ignore();
    } else {
        writeSettings();
        YQ_LOG_DEBUG("Accepting MdiWindow::closeEvent");
        event->accept();
    }
}


void MdiWindow::newFile()
{
    YQ_LOG_DEBUG("newFile");
    MdiChild *child = createMdiChild();
    child->newFile();
    child->show();
}


void MdiWindow::open()
{
    YQ_LOG_DEBUG("open");
    openFile(QFileDialog::getOpenFileName(this));
}


bool MdiWindow::openFile(const QString &fileName)
{
    YQ_LOG_DEBUG("openFile %s", fileName.toLatin1().data());
    if (!fileName.isEmpty()) {
        QMdiSubWindow *existing = findMdiChild(fileName);
        if (existing) {
            mdiArea->setActiveSubWindow(existing);
            return true;
        }
        return loadFile(fileName);
    }
    return false;
}


bool MdiWindow::loadFile(const QString &fileName)
{
    YQ_LOG_DEBUG("loadFile %s", fileName.toLatin1().data());
    MdiChild *child = createMdiChild();
    const bool succeeded = child->loadFile(fileName);
    if (succeeded)
    {
        child->show();
        setCurrentFile(child->currentFile());
        statusBar()->showMessage(tr("File loaded"), 2000);
    }
    else
    {
        child->close();
        statusBar()->showMessage(tr("Unable to load file"), 2000);
    }
    return succeeded;
}


void MdiWindow::save()
{
    YQ_LOG_DEBUG("save");
    if (activeMdiChild() && activeMdiChild()->save())
    {
        setCurrentFile(activeMdiChild()->currentFile());
        statusBar()->showMessage(tr("File saved"), 2000);
    }
}


void MdiWindow::saveAs()
{
    YQ_LOG_DEBUG("saveAs");
    if (activeMdiChild() && activeMdiChild()->saveAs())
    {
        setCurrentFile(activeMdiChild()->currentFile());
        statusBar()->showMessage(tr("File saved"), 2000);
    }
}


static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void * /*user_data*/, int line, const char* file)
{
	// DocOutputDevice* context = (DocOutputDevice*)user_data;
	fprintf(stderr, "MdiWindow, %s(%i): error %04X: detail_no=%u\n", file, line, (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
	// delete context; // Can't do this, it could be on the stack as is the case currently
//	exit(-1);

   // HPDF_Error_Reset (HPDF_Error error)
}


void MdiWindow::exportPDF()
{
  YQ_LOG_DEBUG("exportPDF");
  MdiChild* child = activeMdiChild();
  if (!child) {
    QMessageBox::warning(this, tr("Error"), tr("Internal error, couldn't have file.\n"));
    return;
  }
  QString fileName = QFileDialog::getSaveFileName(this, tr("Export PDF"), QFileInfo(child->userFriendlyCurrentFile()).baseName() + ".pdf");
  if (fileName.isEmpty())
    return;
 
  int currentTemplate = appWindow->templateSelector->currentIndex();
  std::string templateFileName = ""; // TODO: set to a default template file name
  if (templatesList().size() > currentTemplate)
  {
    templateFileName = QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data();
  }
  DocConvert converter((void*)error_handler);
  QByteArray text = child->toPlainText().toLatin1();
  converter.SetSourceData(text.data(), text.size());
  converter.Convert();
  converter.OutputPDF(templateFileName.c_str(), fileName.toUtf8().data(), false);
  // TODO: error handling if file not writable or to warn about overwriting etc
}


// Project settings
void MdiWindow::updateProjectSettingsOnWidgetChange()
{
  MdiChild* child = activeMdiChild();
  if (!child)
    return;

  if (avoidProjectPopulateRecursion)
    return;
  avoidProjectPopulateRecursion = true;

  // Get any existing metadata
  DocConvert::MetaData metaData;
  metaData.AddValue("title"    , "Title:      ", ""); 
  metaData.AddValue("subtitle" , "Subtitle:   ", ""); 
  metaData.AddValue("version"  , "Version:    ", ""); 
  metaData.AddValue("author"   , "Author:     ", ""); 
  metaData.AddValue("company"  , "Company:    ", ""); 
  metaData.AddValue("address"  , "Address:    ", ""); 
  metaData.AddValue("copyright", "Copyright:  ", ""); 
  metaData.AddValue("password" , "Password:   ", ""); 
  metaData.AddValue("comment"  , "Comment:    ", ""); 

  //printf("extracing meta from editor text\n");
  QByteArray text = child->toPlainText().toLatin1();
  int endOfMeta = DocConvert::ExtractMetadataValues(text.size(), text.data(), metaData);

  std::string password = appWindow->password->text().toUtf8().data();
  if (!password.empty())
  {
    std::string pwOneTimePad = "WickedDocs";
    while (password.size() > pwOneTimePad.size())
      pwOneTimePad += pwOneTimePad;
    std::string pw;
    int i = 0;
    for (char ch : password)
    {
      pw += int2hex(uint8_t(ch ^ pwOneTimePad[i]));
      i++;
    }
    password = pw;
  }

  // merge it with data from dialog widgets
  // Need to merge the metadata together 
  metaData.map["title"].value     = appWindow->title->text().toUtf8().data();
  metaData.map["subtitle"].value  = appWindow->subtitle->text().toUtf8().data();
  metaData.map["version"].value   = appWindow->version->text().toUtf8().data();
  metaData.map["author"].value    = appWindow->author->text().toUtf8().data();
  metaData.map["company"].value   = appWindow->company->text().toUtf8().data();
  metaData.map["address"].value   = appWindow->address->toPlainText().toUtf8().data();
  metaData.map["copyright"].value = appWindow->copyright->text().toUtf8().data();
  metaData.map["password"].value  = password;
  metaData.map["comment"].value   = appWindow->comments->toPlainText().toUtf8().data();

  // If non-default template, set that
  std::string tmpl = appWindow->templateSelector->currentText().toUtf8().data();
  if (tmpl != "Default.template" && tmpl != "Default")
  {
    metaData.map["template"].value = tmpl;
  }

  QString metaMarker = ".......................................................\n";
  QString meta = metaMarker;
  for (const auto& metaKey : metaData.insertionOrder)
  {
    const auto& metaVal = metaData.map[metaKey];
    QString k = metaVal.originalKey.c_str();
    QString v = metaVal.value.c_str();

    std::string val = metaVal.value;
    if (val.empty())
      continue;

    // convert all whitespace characters to spaces
    //std::transform(val.begin(), val.end(), val.begin(), [](const char& ch) { return (std::isspace(ch)) ? ' ' : ch; });
    // compress whitespace
    //val.erase(std::unique(val.begin(), val.end(), [](char lhs, char rhs){ return (lhs == ' ' || lhs == '\n') && (rhs == ' '); }), val.end());
    //val = trim(val);


    // Preserve newlines, but add padding at start of lines over multi-line meta value to align the text
    int maxW = metaMarker.size();
    int keyW = k.size();
    std::string pad(keyW, ' ');
    const auto& lines = split(val, '\n');
    val = "";
    bool first = true;
    for (const auto& line : lines)
    {
      if (!first)
        val += "\n" + pad;
      val += line;
      first = false;
    }
    val = trim(val);


    meta += k + val.c_str() + "\n";
  }
  meta += metaMarker;

  QString restOfDocument = text.data() + endOfMeta;

  child->setPlainText( meta + restOfDocument );
  child->document()->setModified(true);
  child->setWindowModified(true);

  avoidProjectPopulateRecursion = false;
}


void MdiWindow::passwordShow()
{
  appWindow->password->setEchoMode(QLineEdit::Normal);
}


void MdiWindow::passwordHide()
{
  appWindow->password->setEchoMode(QLineEdit::Password);
}


// TODO: probably remove, project settings are getting saved as metadata in the md file
void MdiWindow::saveProject()
{
}


#ifndef QT_NO_CLIPBOARD
void MdiWindow::cut()
{
    if (activeMdiChild())
        activeMdiChild()->cut();
}


void MdiWindow::copy()
{
    if (activeMdiChild())
        activeMdiChild()->copy();
}


void MdiWindow::paste()
{
    if (activeMdiChild())
        activeMdiChild()->paste();
}
#endif


void MdiWindow::support()
{
    QDesktopServices::openUrl(QUrl("https://www.subflexion.com/WickedDocs/support.html"));
    lower();
}


void MdiWindow::website()
{
    QDesktopServices::openUrl(QUrl("https://www.subflexion.com/WickedDocs/index.html"));
}


void MdiWindow::upgradeClicked()
{
    QDesktopServices::openUrl(QUrl("https://www.subflexion.com/WickedDocs/register.html"));
}


void MdiWindow::bold()
{
    if (activeMdiChild()) activeMdiChild()->bold();
}
void MdiWindow::italic()
{
    if (activeMdiChild()) activeMdiChild()->italic();
}
void MdiWindow::quote()
{
    if (activeMdiChild()) activeMdiChild()->quote();
}
void MdiWindow::code()
{
    if (activeMdiChild()) activeMdiChild()->code();
}
void MdiWindow::heading1()
{
    if (activeMdiChild()) activeMdiChild()->heading1();
}
void MdiWindow::heading2()
{
    if (activeMdiChild()) activeMdiChild()->heading2();
}
void MdiWindow::heading3()
{
    if (activeMdiChild()) activeMdiChild()->heading3();
}
void MdiWindow::heading4()
{
    if (activeMdiChild()) activeMdiChild()->heading4();
}
void MdiWindow::hyperlink()
{
    if (activeMdiChild()) activeMdiChild()->hyperlink();
}
void MdiWindow::image()
{
    if (activeMdiChild()) activeMdiChild()->image();
}
void MdiWindow::unorderedList()
{
    if (activeMdiChild()) activeMdiChild()->unorderedList();
}
void MdiWindow::orderedList()
{
    if (activeMdiChild()) activeMdiChild()->orderedList();
}
void MdiWindow::horizontalRule()
{
    if (activeMdiChild()) activeMdiChild()->horizontalRule();
}
void MdiWindow::timestamp()
{
    if (activeMdiChild()) activeMdiChild()->timestamp();
}
void MdiWindow::pageNumber()
{
    if (activeMdiChild()) activeMdiChild()->pageNumber();
}
void MdiWindow::pageCount()
{
    if (activeMdiChild()) activeMdiChild()->pageCount();
}
void MdiWindow::date()
{
    if (activeMdiChild()) activeMdiChild()->insertText("[DATE]");
}
void MdiWindow::time()
{
    if (activeMdiChild()) activeMdiChild()->insertText("[TIME]");
}
void MdiWindow::tableOfContents()
{
    if (activeMdiChild()) activeMdiChild()->insertText("[%TOC]");
}
void MdiWindow::options()
{
}
void MdiWindow::saveAll()
{
}
void MdiWindow::selectAll()
{
    if (activeMdiChild()) activeMdiChild()->selectAll();
}
void MdiWindow::del()
{
    if (activeMdiChild()) activeMdiChild()->del();
}
void MdiWindow::undo()
{
    if (activeMdiChild()) activeMdiChild()->undo();
}
void MdiWindow::redo()
{
    if (activeMdiChild()) activeMdiChild()->redo();
}
void MdiWindow::find()
{
    if (activeMdiChild()) activeMdiChild()->find();
}
void MdiWindow::findNext()
{
    if (activeMdiChild()) activeMdiChild()->findNext();
}
void MdiWindow::findPrevious()
{
    if (activeMdiChild()) activeMdiChild()->findPrevious();
}
void MdiWindow::goToLine()
{
    if (activeMdiChild()) activeMdiChild()->goToLine();
}


void MdiWindow::handleRequest()
{
    printf("handling request: this: %x\n", this);
    printf("server ptr: %x\n", server);
    if (server && server->hasPendingConnections())
    {
        QTcpSocket* sock = server->nextPendingConnection();
        sock->waitForReadyRead(100);
        QByteArray ba = sock->readAll();
        //if (sock->canReadLine())
        {
            QString str(ba);
            QStringList tokens = str.split(QRegExp("[ \r\n][ \r\n]*"));
            bool haveGet = false;
            bool haveDoc = false;
            for (int i = 0; i < tokens.size(); i++) {
                if (tokens[i] == "GET")
                    haveGet = true;
                if (tokens[i].contains(QString("pdf"), Qt::CaseInsensitive))
                    haveDoc = true;
            }
            if (haveGet && haveDoc) //tokens[0] == "GET" && tokens[1].contains(QString("pdf"), Qt::CaseInsensitive ))
            {
              printf("have get and doc %li %c %c\n", pdfContents.size(), pdfContents.data()[0], pdfContents.data()[1] );
                QTextStream os(sock);
                os.setAutoDetectUnicode(true);
                os << "HTTP/1.0 200 Ok\r\n"
                    "Content-Type: application/pdf; charset=\"utf-8\"\r\n"
                    "Content-Length: " << pdfContents.size() <<
                    "\r\n"
                    "\r\n";
                //os << pdfContents.data();
                //"<h1>Nothing to see here</h1>\n"
                //<< QDateTime::currentDateTime().toString() << "\n";
                os.flush();
                sock->flush();
                sock->write(pdfContents.data(), pdfContents.size());
                sock->flush();
                //sock->write(ba);
            }
            else
            {
              printf("no get and doc\n");
               QTextStream os(sock);
                os.setAutoDetectUnicode(true);
                os << "HTTP/1.0 404 Ok\r\n"
                      "\r\n"
                      "\r\n";
            }
        }
        sock->waitForBytesWritten();
        // sock->waitForDisconnected();
        sock->close();
        delete sock;
    }
}


void MdiWindow::setupServer()
{
    printf("setting up server\n");

    // setup the web server
    server = new QTcpServer;
    printf("server ptr: %x\n", server);
    printf("this ptr: %x\n", this);
    if (!server->listen(QHostAddress::LocalHost, 12345))
    // if (!server->listen("127.0.0.1", 12345))
    {
        qFatal("Failed to open web socket server.");
    }
    connect(server, SIGNAL(newConnection()), this, SLOT(handleRequest()));
/*

    // open a browser window with the client HTML page
    QUrl url = QUrl::fromLocalFile(BUILD_DIR "/index.html");
    url.setQuery(QStringLiteral("webChannelBaseUrl=") + server.serverUrl().toString());
    QDesktopServices::openUrl(url);
    dialog.displayMessage(QObject::tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString()));
*/
}


QString MdiWindow::outputFileName()
{
  MdiChild* child = activeMdiChild();
  return (child) ? child->tmpFileName() : "";
}


void MdiWindow::outputPDF(bool preview)
{
  MdiChild* child = activeMdiChild();
  QString tmpFile = outputFileName();
  if (tmpFile.isEmpty())
    return;
  QByteArray ba = tmpFile.toLatin1();
  //printf("update: tmpFile: %s\n", ba.data());
  const char *fileName = ba.data();// "test/test.pdf";

  QByteArray text = child->toPlainText().toLatin1();
  char* data;
  size_t size;

  int currentTemplate = appWindow->templateSelector->currentIndex();
  std::string templateFileName = ""; // TODO: set to a default template file name
  if (templatesList().size() > currentTemplate)
  {
    templateFileName = QString(resourcesLocation() + "/Templates/" + templatesList()[currentTemplate]).toLatin1().data();
  }

  DocConvert converter((void*)error_handler);
  converter.SetSourceData(text.data(), text.size());
  converter.Convert();
  converter.GetHTMLData(&data, &size);

  converter.OutputPDF(templateFileName.c_str(), ba.data(), preview);

  // TODO: need to get a tmp name for this
  // TODO: Debug thing - remove in final release
  converter.OutputHTML("debugging.html");
}


void MdiWindow::refreshPreviewNow()
{
  reloadWebPreview();
}


void MdiWindow::setToolbarSmall(bool enabled)
{
  const int sm = 18;
  QSize smSz(sm, sm);
  const int lg = 22;
  QSize lgSz(lg, lg);
  if (enabled)
  {
    appWindow->fileToolBar->setIconSize(smSz);
    appWindow->editToolBar->setIconSize(smSz);
    appWindow->formatToolBar->setIconSize(smSz);
  }
  else
  {
    appWindow->fileToolBar->setIconSize(lgSz);
    appWindow->editToolBar->setIconSize(lgSz);
    appWindow->formatToolBar->setIconSize(lgSz);
  }
}


void MdiWindow::setToolbarLabelsEnabled(bool enabled)
{
  if (enabled)
  {
    appWindow->fileToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    appWindow->editToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    appWindow->formatToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
  }
  else
  {
    appWindow->fileToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
    appWindow->editToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
    appWindow->formatToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
  }
}


//void MdiWindow::setAutoRefreshEnabled(bool);

void MdiWindow::updatePreview()
{
  MdiChild* child = activeMdiChild();
  if (!child)
    return;

  QString tmpFile = outputFileName();
  if (tmpFile.isEmpty())
    return;
  if ( currentTmpFile != tmpFile )
  {
    outputPDF(true);
    setWebPreviewURL(tmpFile);
    currentTmpFile = tmpFile;
  }
  else
  {
    //printf("reloading\n");
    if (m_autoRefreshEnabled && !m_doingRefresh)
    {
      reloadWebPreview();
    }
    else
    {
      m_refreshNeeded = true;
    }
  }

        DocConvert converter;
        converter.SetSourceData(text.data(), text.size());
        converter.Convert();
      
        // convert to HTML and PDF
        converter.GetHTMLData(&data, &size);

        // save the PDF
        converter.OutputPDF(fileName);

        // Update the preview based on HTML
        appWindow->previewText->setText(QString::fromLatin1(data, size));

        //pdfContents.resize(5*1024*1024);
        //size = 5*1024*1024;
        //converter.GetPDFData(pdfContents.data(), &size);
        //pdfContents.resize(size);
        
        
        //pdfContents = QByteArray(data, size);

        //QTimer::singleShot(100, this, SLOT(setWebPreviewURL()));
        // ../../../build-MakePDF-Desktop-Debug/

        // appWindow->webPreview->setUrl(QUrl(QString("file:///home/jryland/Code/applications/MakePDF/3rdParty/pdf.js/web/viewer.html?file=http://localhost:12345/") + fileName));
       
        //appWindow->webPreview->settings()->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
        //appWindow->webPreview->settings()->setAttribute(QWebSettings::XSSAuditingEnabled, false);


        // Update the preview based on the PDF
        appWindow->webPreview->load(QUrl(QString("file:///home/jryland/Code/applications/MakePDF/3rdParty/pdf.js/web/viewer.html?file=file:///home/jryland/Code/applications/MakePDF/Build/test/test.pdf")));// + fileName));
        //appWindow->webPreview->setUrl(QUrl(QString("file:///home/jryland/Code/applications/MakePDF/3rdParty/pdf.js/web/viewer.html?file=file:///home/jryland/Code/applications/MakePDF/Build/test/test.pdf")));// + fileName));
        //appWindow->webPreview->reload();
        

        //appWindow->webPreview->setUrl(QUrl(QString("http://localhost:12345/") + fileName));
        
        //appWindow->webPreview->setUrl(QUrl(QString("http://127.0.0.1:12345/") + fileName));

        //QTimer::singleShot(1000, this, SLOT(refreshWebPreview()));
    }
}


void MdiWindow::reloadWebPreview()
{
  QString tmpFile = outputFileName();
  if (tmpFile.isEmpty())
    return;

  // TODO: put on a thread
  m_doingRefresh = true;
  appWindow->webPreview->setUpdatesEnabled(false);
  outputPDF(true);
  
  setWebPreviewURL(tmpFile);

  // WebEngine specific
  // appWindow->webPreview->page()->runJavaScript("ReloadFile();\n"); 
  //appWindow->webPreview->runJavaScript("ReloadFile();\n"); 

  QTimer::singleShot(500, this, SLOT(refreshWebPreview()));
}


void MdiWindow::refreshWebPreview()
{
    //appWindow->webPreview->setUpdatesEnabled(true);
}


void MdiWindow::updateActions()
{
    bool hasMdiChild = (activeMdiChild() != 0);
    bool hasSelection = (hasMdiChild && activeMdiChild()->hasSelection());

    appWindow->actionSave->setEnabled(hasMdiChild);
    appWindow->actionSave_As->setEnabled(hasMdiChild);
    appWindow->actionSave_All->setEnabled(hasMdiChild);
    appWindow->actionClose->setEnabled(hasMdiChild);
    appWindow->actionExport->setEnabled(hasMdiChild);
#ifndef QT_NO_CLIPBOARD
    appWindow->actionCut->setEnabled(hasSelection);
    appWindow->actionCopy->setEnabled(hasSelection);
    appWindow->actionPaste->setEnabled(hasMdiChild);
#endif
    appWindow->actionSelect_All->setEnabled(hasMdiChild);
    appWindow->actionDelete->setEnabled(hasMdiChild);
    appWindow->actionUndo->setEnabled(hasMdiChild);
    appWindow->actionRedo->setEnabled(hasMdiChild);
    appWindow->actionFind->setEnabled(hasMdiChild);
    appWindow->actionFind_Next->setEnabled(hasMdiChild);
    appWindow->actionFind_Previous->setEnabled(hasMdiChild);
    appWindow->actionGo_to_Line->setEnabled(hasMdiChild);
    appWindow->actionBold->setEnabled(hasMdiChild);
    appWindow->actionItalic->setEnabled(hasMdiChild);
    appWindow->actionQuote->setEnabled(hasMdiChild);
    appWindow->actionCode->setEnabled(hasMdiChild);
    appWindow->actionHeading_1->setEnabled(hasMdiChild);
    appWindow->actionHeading_2->setEnabled(hasMdiChild);
    appWindow->actionHeading_3->setEnabled(hasMdiChild);
    appWindow->actionHeading_4->setEnabled(hasMdiChild);
    appWindow->actionHyperlink->setEnabled(hasMdiChild);
    appWindow->actionImage->setEnabled(hasMdiChild);
    appWindow->actionUnordered_List->setEnabled(hasMdiChild);
    appWindow->actionOrdered_List->setEnabled(hasMdiChild);
    appWindow->actionHorizontal_Rule->setEnabled(hasMdiChild);
    appWindow->actionInsertTimestamp->setEnabled(hasMdiChild);
    appWindow->actionInsertPageNumber->setEnabled(hasMdiChild);
    appWindow->actionInsertPageCount->setEnabled(hasMdiChild);
    appWindow->actionInsertDate->setEnabled(hasMdiChild);
    appWindow->actionInsertTime->setEnabled(hasMdiChild);
    appWindow->actionInsertTOC->setEnabled(hasMdiChild);
    appWindow->actionClose_All->setEnabled(hasMdiChild);
    appWindow->actionNext->setEnabled(hasMdiChild);
    appWindow->actionPrevious->setEnabled(hasMdiChild);
    appWindow->action_Tile_Windows->setEnabled(hasMdiChild);
    appWindow->action_Windowed->setEnabled(hasMdiChild);

    //appWindow->actionTabbed->setEnabled(hasMdiChild);
    //separatorAct->setVisible(hasMdiChild);

    appWindow->menuView->clear();
    appWindow->menuView->addAction(appWindow->action_Tile_Windows);
    appWindow->menuView->addAction(appWindow->action_Windowed);
    appWindow->menuView->addAction(appWindow->actionTabbed);
    appWindow->menuView->addSeparator();
    appWindow->menuView->addAction(appWindow->actionNext);
    appWindow->menuView->addAction(appWindow->actionPrevious);
    appWindow->menuView->addSeparator();
    appWindow->menuView->addAction(appWindow->projectDock->toggleViewAction());
    appWindow->menuView->addAction(appWindow->previewDock->toggleViewAction());
    appWindow->menuView->addSeparator();
    appWindow->menuView->addAction(appWindow->actionRefresh_Preview);
    appWindow->menuView->addSeparator();
    appWindow->menuView->addAction(appWindow->actionToolbar_Labels);
    appWindow->menuView->addAction(appWindow->actionToolbar_Small);
    appWindow->menuView->addSeparator();

    QList<QMdiSubWindow *> windows = mdiArea->subWindowList();
    if (!windows.isEmpty())
        appWindow->menuView->addSeparator();

    for (int i = 0; i < windows.size(); ++i) {
        MdiChild *child = qobject_cast<MdiChild *>(windows.at(i)->widget());
        QString text = QString("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
        if (i < 9)
            text = "&" + text;
        QAction *action = appWindow->menuView->addAction(text);
        action->setCheckable(true);
        action->setChecked(child == activeMdiChild());
        connect(action, SIGNAL(triggered(bool)), windowMapper, SLOT(map()));
        windowMapper->setMapping(action, windows.at(i));
    }

    appWindow->menuView->addSeparator();


    // Recreate the file menu
    appWindow->menuFile->clear();
    appWindow->menuFile->addAction(appWindow->actionNew_Project);
    appWindow->menuFile->addAction(appWindow->actionNew);
    appWindow->menuFile->addAction(appWindow->actionOpen);
    appWindow->menuFile->addSeparator();
    appWindow->menuFile->addAction(appWindow->actionSave);
    appWindow->menuFile->addAction(appWindow->actionSave_As);
    appWindow->menuFile->addAction(appWindow->actionSave_All);
    appWindow->menuFile->addSeparator();
    appWindow->menuFile->addAction(appWindow->actionExport);
    appWindow->menuFile->addSeparator();
    appWindow->menuFile->addAction(appWindow->actionClose);
    appWindow->menuFile->addAction(appWindow->actionClose_All);
    separatorAct = appWindow->menuFile->addSeparator();
    for (int i = 0; i < MaxRecentFiles; ++i)
        appWindow->menuFile->addAction(recentFileActs[i]);
    appWindow->menuFile->addSeparator();
    appWindow->menuFile->addAction(appWindow->actionExit);
    updateRecentFileActions();
}


MdiChild *MdiWindow::createMdiChild()
{
    MdiChild *child = new MdiChild;
    mdiArea->addSubWindow(child, Qt::SubWindow);
    connect(child, SIGNAL(documentContentsChanged()), this, SLOT(updatePreview()));
    connect(child, SIGNAL(documentContentsChanged()), this, SLOT(updateProjectSettingsOnTextChange()));
    connect(child, SIGNAL(closing()), this,  SLOT(updateActions()));
/*
#ifndef QT_NO_CLIPBOARD
    connect(child, SIGNAL(copyAvailable(bool)), appWindow->actionCut,  SLOT(setEnabled(bool)));
    connect(child, SIGNAL(copyAvailable(bool)), appWindow->actionCopy, SLOT(setEnabled(bool)));
#endif
*/
    return child;
}


void MdiWindow::cascadeSubWindows()
{
    mdiArea->setViewMode(QMdiArea::SubWindowView);
    mdiArea->cascadeSubWindows();
}


void MdiWindow::tileSubWindows()
{
    mdiArea->setViewMode(QMdiArea::SubWindowView);
    mdiArea->tileSubWindows();
}


void MdiWindow::tabSubWindows()
{
    mdiArea->setViewMode(QMdiArea::TabbedView);
}


void MdiWindow::readLicense()
{
    YQ_LOG_DEBUG("Reading license");
    QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "Subflexion", "WickedDocs");
}


void MdiWindow::readSettings()
{
    appWindow->actionToolbar_Labels->setChecked(false);
    appWindow->actionToolbar_Small->setChecked(false);
    appWindow->actionToolbar_Labels->setChecked(true);
    appWindow->actionToolbar_Small->setChecked(true);

    YQ_LOG_DEBUG("Reading settings");
    QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "Subflexion", "WickedDocs");
    settings.beginGroup("MainWindow");
    restoreState(settings.value("DockLayout").toByteArray());
    move(settings.value("Position", QPoint(200, 200)).toPoint());
    resize(settings.value("Size", QSize(400, 400)).toSize());
    appWindow->actionToolbar_Labels->setChecked( settings.value("ToolbarLabels", true).toBool() );
    appWindow->actionToolbar_Small->setChecked( settings.value("SmallToolbarIcons", false).toBool() );
    settings.endGroup();
    settings.beginGroup("License");
    QString user = settings.value("User").toString();
    QString licenseType = settings.value("Type").toString();
    settings.endGroup();
}


void MdiWindow::writeSettings()
{
    YQ_LOG_DEBUG("Saving settings");
    QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "Subflexion", "WickedDocs");
    settings.beginGroup("MainWindow");
    settings.setValue("DockLayout", saveState());
    settings.setValue("Position", pos());
    settings.setValue("Size", size());
    settings.setValue("ToolbarLabels", appWindow->actionToolbar_Labels->isChecked());
    settings.setValue("SmallToolbarIcons", appWindow->actionToolbar_Small->isChecked());
    settings.endGroup();
}


MdiChild *MdiWindow::activeMdiChild()
{
    QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow();
    if (activeSubWindow)
        // return qobject_cast<MdiChild *>(activeSubWindow->widget());
        return dynamic_cast<MdiChild *>(activeSubWindow->widget());
    return 0;
}


QMdiSubWindow *MdiWindow::findMdiChild(const QString &fileName)
{
    QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();

    foreach (QMdiSubWindow *window, mdiArea->subWindowList()) {
        MdiChild *mdiChild = qobject_cast<MdiChild *>(window->widget());
        if (mdiChild && mdiChild->currentFile() == canonicalFilePath)
            return window;
    }
    return 0;
}


void MdiWindow::switchLayoutDirection()
{
    if (layoutDirection() == Qt::LeftToRight)
        qApp->setLayoutDirection(Qt::RightToLeft);
    else
        qApp->setLayoutDirection(Qt::LeftToRight);
}


void MdiWindow::setActiveSubWindow(QWidget *window)
{
    if (!window)
        return;
    mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
}



#include "Objects.h"
QObject* MakeTestObjectTree()
{
    QObject* o = new MyObjectTest;
    return o;
}


void EnumEditor::ListSelectionChanged()
{
    QList<QListWidgetItem*> items = lw->selectedItems();
    if (!items.count())
        return;
    QString combinedString = items.at(0)->text();
    for (int i = 1; i < items.count(); i++)
        combinedString += " | " + items.at(i)->text();
    int value = m_metaEnum.keysToValue(combinedString.toLocal8Bit().data());
    sb->setValue(value);
    str->setText(m_metaEnum.valueToKeys(value).data());
}


void EnumEditor::ComboSelectionChanged()
{
    QString combinedString = cb->currentText();
    sb->setValue(m_metaEnum.keysToValue(combinedString.toLocal8Bit().data()));
    str->setText(combinedString);
}


EnumEditor::EnumEditor(QMetaEnum metaEnum)
{
    m_metaEnum = metaEnum;
    QGridLayout* grid = new QGridLayout();
    QLabel* l1 = new QLabel("Name");
    QLabel* l2 = new QLabel("Enum");
    QLabel* l3 = new QLabel("Value");
    QLabel* l4 = new QLabel("String");
    grid->addWidget(l1, 0, 0);
    grid->addWidget(l2, 1, 0);
    grid->addWidget(l3, 2, 0);
    grid->addWidget(l4, 3, 0);
    QLabel* v1 = new QLabel(metaEnum.name());

    QWidget* v2 = 0;
    if (metaEnum.isFlag())
    {
        lw = new QListWidget();
        lw->setSelectionMode(QAbstractItemView::MultiSelection);
        for (int i = 0; i < metaEnum.keyCount(); i++)
        {
            lw->addItem(metaEnum.valueToKey(metaEnum.value(i)));
        }
        v2 = lw;
        connect(lw, SIGNAL(itemSelectionChanged()), this, SLOT(ListSelectionChanged()));
    }
    else
    {
        cb = new QComboBox();
        for (int i = 0; i < metaEnum.keyCount(); i++)
        {
            cb->addItem(metaEnum.valueToKey(metaEnum.value(i)));
        }
        connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(ComboSelectionChanged()));
        v2 = cb;
    }

    sb = new QSpinBox();
    str = new QLabel();
    grid->addWidget(v1, 0, 1);
    grid->addWidget(v2, 1, 1);
    grid->addWidget(sb, 2, 1);
    grid->addWidget(str, 3, 1);
    setLayout(grid);
    show();
}


void EnumPropertyEditorTest()
{
    MyObjectTest* o = new MyObjectTest;
    for (int i = 0; i < o->metaObject()->enumeratorCount(); i++)
        new EnumEditor(o->metaObject()->enumerator(i));
}



#include "ObjectVisitor.h"


class SerializeVisitor : public Visitor
{
public:
    SerializeVisitor(const char* fileName) { file = fopen(fileName, "wb"); }
    virtual ~SerializeVisitor() { fclose(file); }
    virtual void enterObject(const char* className, const char* objectName)
    {
        std::string p = (path.size()) ? path.back() + "/" : "";
        path.push_back(p + objectName);
        fprintf(file, "[%s]\n", path.back().c_str());
        fprintf(file, "class=%s\n", className);
    }
    virtual void leaveObject(const char* /*className*/, const char* objectName)
    {
        fprintf(file, "; end of %s\n\n", objectName);
        path.pop_back();
        //fprintf(file, "--Leaving %s (%s)\n", objectName, className);
    }
    virtual void visit(const char* /*className*/,
                        const char* propertyName, QVariant::Type /*typ*/, QVariant& value)
    {
        //fprintf(file, "----Property:  %s %s::%s = %s\n", QVariant::typeToName(typ),
        //            className, propertyName, value.toString().toLocal8Bit().data());
        fprintf(file, "%s=%s\n", propertyName, value.toString().toLocal8Bit().data());
    }
private:
    FILE* file;
    std::vector<std::string> path;
};


#include <QLayout>
#include <QAction>
#include <QActionGroup>
//#include <QPdfWriter>

#define CLASS_FACTORY(ClassName, ...) \
            else if (strcmp(className, #ClassName) == 0) return new ClassName(__VA_ARGS__)

QObject* classObjectFactory(const char* className)
{
    if (false) {}
    CLASS_FACTORY(QObject);
    CLASS_FACTORY(QWidget);
    CLASS_FACTORY(QTableView);
    CLASS_FACTORY(QTableWidget);
    CLASS_FACTORY(QLabel);
    CLASS_FACTORY(QPushButton);
    CLASS_FACTORY(QVBoxLayout);
    CLASS_FACTORY(QHBoxLayout);
    CLASS_FACTORY(QAction, nullptr);
    CLASS_FACTORY(QActionGroup, nullptr);
    CLASS_FACTORY(QButtonGroup);
    //CLASS_FACTORY(QFileSelector);
    //CLASS_FACTORY(QPdfWriter);
    //CLASS_FACTORY(QSound);
    CLASS_FACTORY(QThread);
    CLASS_FACTORY(QTimer);
    printf("Unrecognized class name, ignoring\n");
    return 0;
}


class DeserializeVisitor : public Visitor
{
public:
    DeserializeVisitor(const char* fileName) { file = fopen(fileName, "rb"); }
    virtual ~DeserializeVisitor() { fclose(file); }
    virtual void enterObject(const char* className, const char* objectName)
    {
        std::string p = (path.size()) ? path.back() + "/" : "";
        path.push_back(p + objectName);
        fprintf(file, "[%s]\n", path.back().c_str());
        fprintf(file, "class=%s\n", className);
    }
    virtual void leaveObject(const char* /*className*/, const char* objectName)
    {
        fprintf(file, "; end of %s\n\n", objectName);
        path.pop_back();
        //fprintf(file, "--Leaving %s (%s)\n", objectName, className);
    }
    virtual void visit(const char* /*className*/,
                        const char* propertyName, QVariant::Type /*typ*/, QVariant& value)
    {
        //fprintf(file, "----Property:  %s %s::%s = %s\n", QVariant::typeToName(typ),
        //            className, propertyName, value.toString().toLocal8Bit().data());
        fprintf(file, "%s=%s\n", propertyName, value.toString().toLocal8Bit().data());
    }
    virtual bool isWriting()
    {
        return true;
    }
private:
    FILE* file;
    std::vector<std::string> path;
};

#include "qtpropertymanager.h"
#include "qtvariantproperty.h"
#include "qttreepropertybrowser.h"
#include "qtgroupboxpropertybrowser.h"
#include "ObjectTreeModel.h"


static QWidget *WrapWidgetInLayout(QWidget* wrapper, QWidget* w)
{
    QVBoxLayout *layout = new QVBoxLayout;
    wrapper->setLayout(layout);
    layout->addWidget(w);
    layout->setMargin(0);
    layout->setSpacing(0);
    layout->setContentsMargins(0,0,0,0);
    return wrapper;
}


void MdiWindow::updatePropertiesView(QObject *object)
{
    // Dynamically build the properties UI elements for the given object's properties
    QtVariantPropertyManager *variantManager = new QtVariantPropertyManager();
    QtVariantEditorFactory *variantFactory = new QtVariantEditorFactory();
    QtAbstractPropertyBrowser *groupEditor = new QtGroupBoxPropertyBrowser();
    groupEditor->setFactoryForManager(variantManager, variantFactory);
    QtTreePropertyBrowser *variantEditor = new QtTreePropertyBrowser();
    variantEditor->setFactoryForManager(variantManager, variantFactory);
    std::map<std::string, QtProperty*> pmap;
    for (int i = 0; i < object->metaObject()->propertyCount(); i++)
    {
        const QMetaProperty& property = object->metaObject()->property(i);
        std::string className = property.enclosingMetaObject()->className();
        QtProperty *parentItem = nullptr;
        if (pmap.find(className) == pmap.end()) {
            parentItem = variantManager->addProperty(QtVariantPropertyManager::groupTypeId(), className.c_str());
            pmap.insert(std::make_pair(className, parentItem));
            groupEditor->addProperty(parentItem);
            variantEditor->addProperty(parentItem);
        } else {
            parentItem = pmap[className];
        }
        QtVariantProperty *item = variantManager->addProperty(property.type(), property.name());
        if ( item ) {
            item->setValue(property.read(object));
            parentItem->addSubProperty(item);
        }
    }
    variantEditor->setIndentation(30);
    variantEditor->setPropertiesWithoutValueMarked(true);
    variantEditor->setRootIsDecorated(false);

    QScrollArea *scrollArea = new QScrollArea;
    scrollArea->setWidget(groupEditor);

    QTabWidget *tabs = new QTabWidget;
    tabs->addTab(WrapWidgetInLayout(new QWidget, variantEditor), "Property List");
    tabs->addTab(WrapWidgetInLayout(new QWidget, scrollArea), "Grouped");

    foreach (QObject* child, appWindow->propertiesView->children())
        if (child->isWidgetType())
            delete child;

    QLayout* lo = appWindow->propertiesView->layout();
    if (lo) {
        lo->addWidget(tabs);
    } else {
        WrapWidgetInLayout(appWindow->propertiesView, tabs);
    }
    //QObject::connect(variantManager, SIGNAL(propertyChanged(QtProperty*)), &blah, SLOT(updateProp(QtProperty*)));
}


void MdiWindow::changePropertiesObject(const QModelIndex &index)
{
    QObject* ptr = (QObject*)index.internalPointer();
    if (ptr)
        updatePropertiesView(ptr);
}


void MdiWindow::changeRootObject(const QModelIndex &index)
{
    QObject* ptr = (QObject*)index.internalPointer();
    if (ptr)
        updateObjectTree(ptr);
}


void MdiWindow::changeProjectTree(QObject *rootObject)
{
    QList<ObjPtr> list;
    list.append(ObjPtr(rootObject));

    if (appWindow->projectsView->model()) {
        ((ObjectTreeModel *)appWindow->projectsView->model())->setRootObjects(list);
    } else {
        ObjectTreeModel *objectTree = new ObjectTreeModel(appWindow->projectsView);
        objectTree->setRootObjects(list);
        appWindow->projectsView->setModel(objectTree);
    }

    appWindow->projectsView->header()->hide();
    QObject::connect(appWindow->projectsView, SIGNAL(clicked(const QModelIndex &)),
                     this, SLOT(changeRootObject(const QModelIndex &)));
}


void MdiWindow::updateObjectTree(QObject *rootObject)
{
    SerializeVisitor visitor("/tmp/object-dump.txt");
    Visit(visitor, rootObject);

    QList<ObjPtr> list;
    list.append(ObjPtr(rootObject));

    if (appWindow->objectsView->model()) {
        ((ObjectTreeModel *)appWindow->objectsView->model())->setRootObjects(list);
    } else {
        ObjectTreeModel *objectTree = new ObjectTreeModel(appWindow->objectsView);
        objectTree->setRootObjects(list);
        appWindow->objectsView->setModel(objectTree);
    }

    appWindow->objectsView->header()->hide();
    QObject::connect(appWindow->objectsView, SIGNAL(doubleClicked(const QModelIndex &)),
                     this, SLOT(changeRootObject(const QModelIndex &)));
    QObject::connect(appWindow->objectsView, SIGNAL(clicked(const QModelIndex &)),
                     this, SLOT(changePropertiesObject(const QModelIndex &)));
}














PaletteEditorDialog::PaletteEditorDialog(QWidget *parent)
    : QDialog(parent)
    , editor(nullptr)
{
}

template <typename T>
void AssignArray(T *dst, std::vector<T> init, int count)
{
    for (int i = 0; i < count; i++)
        dst[i] = init[i];
}


void PaletteEditorDialog::init(Ui_PaletteEditor* ui)
{
    editor = ui;
    AssignArray( tints,   { ui->tint1, ui->tint2, ui->tint3, ui->tint4, ui->tint5,
                            ui->tint6, ui->tint7, ui->tint8, ui->tint9, ui->tint10 }, 10);
    AssignArray( tones,   { ui->tone1, ui->tone2, ui->tone3, ui->tone4, ui->tone5,
                            ui->tone6, ui->tone7, ui->tone8, ui->tone9, ui->tone10 }, 10);
    AssignArray( shades,  { ui->shade1, ui->shade2, ui->shade3, ui->shade4, ui->shade5,
                            ui->shade6, ui->shade7, ui->shade8, ui->shade9, ui->shade10 }, 10);
    
    AssignArray( PrimaryHSVs, { ui->paletteHue1SV1, ui->paletteHue2SV1, ui->paletteHue3SV1, ui->paletteHue4SV1 }, 4);
    AssignArray( SecondaryHSVs, { ui->paletteHue1SV2, ui->paletteHue1SV3, ui->paletteHue1SV4, ui->paletteHue1SV5,
                                  ui->paletteHue2SV2, ui->paletteHue2SV3, ui->paletteHue2SV4, ui->paletteHue2SV5,
                                  ui->paletteHue3SV2, ui->paletteHue3SV3, ui->paletteHue3SV4, ui->paletteHue3SV5,
                                  ui->paletteHue4SV2, ui->paletteHue4SV3, ui->paletteHue4SV4, ui->paletteHue4SV5 }, 16);

    AssignArray( swatchHues,    { ui->baseHueSwatch1, ui->baseHueSwatch2, ui->baseHueSwatch3, ui->baseHueSwatch4 }, 4);
    AssignArray( swatchValues,  { ui->baseHueValue1,  ui->baseHueValue2,  ui->baseHueValue3,  ui->baseHueValue4 }, 4);

    QObject::connect(ui->schemeButton1, SIGNAL(pressed()), this, SLOT(setMode1()));
    QObject::connect(ui->schemeButton2, SIGNAL(pressed()), this, SLOT(setMode2()));
    QObject::connect(ui->schemeButton3, SIGNAL(pressed()), this, SLOT(setMode3()));
    QObject::connect(ui->schemeButton4, SIGNAL(pressed()), this, SLOT(setMode4()));
    QObject::connect(ui->schemeButton5, SIGNAL(pressed()), this, SLOT(setMode5()));

    QObject::connect(ui->colorWheel, SIGNAL(paletteChanged()), this, SLOT(updatePalette()));
    QObject::connect(ui->toneGradient, SIGNAL(paletteChanged()), this, SLOT(updatePalette()));
    // QLineEdit *schemeNameEdit;

    ui->schemeButton2->setChecked(true);
    setMode2();
}


uint32_t PaletteEditorDialog::GetColorScheme() const
{
  ColorScheme pal = editor->colorWheel->palette();
  editor->toneGradient->updatePalette(pal);
  pal = editor->toneGradient->palette();
  uint32_t col = 0;
  col |= pal.m_primaryHue        << 24;
  col |= pal.m_secondaryHueDelta << 16;
  col |= pal.m_saturation        <<  8;
  col |= pal.m_stretch           <<  3;
  col |= pal.m_mode              <<  0;
  return col;
}


void PaletteEditorDialog::SetColorScheme(uint32_t col)
{
  ColorScheme pal;
  pal.m_primaryHue        = (col >> 24) & 0xFF;
  pal.m_secondaryHueDelta = (col >> 16) & 0xFF;
  pal.m_saturation        = (col >>  8) & 0xFF;
  pal.m_stretch           = (col >>  3) & 0x1F;
  pal.m_mode              = (col >>  0) & 0x07;
  pal.m_mode              %= 5;
 
  editor->colorWheel->setPalette(pal);
  editor->colorWheel->setMode(pal.m_mode);
  editor->toneGradient->updateShades(pal);
  switch (pal.m_mode)
  {
    case 0: editor->schemeButton1->setChecked(true); break;
    case 1: editor->schemeButton2->setChecked(true); break;
    case 2: editor->schemeButton3->setChecked(true); break;
    case 3: editor->schemeButton4->setChecked(true); break;
    case 5: editor->schemeButton5->setChecked(true); break;
  }
  updatePalette();
}


void PaletteEditorDialog::updatePalette()
{
    ColorScheme pal = editor->colorWheel->palette();
    float hue = pal.hue(0); // Get the primary color
    QString modeName = pal.modeName();
    editor->schemeGroup->setTitle(" " + modeName + " Color Scheme");

    // Draw the tone gradient
    editor->toneGradient->updatePalette(pal);
    pal = editor->toneGradient->palette();
    float sat = float(pal.m_saturation) / 256.0;
    float val = float(pal.m_stretch) / 32.0;

    // Draw the shades, tones and tints   (black,grey,white)
    for (int i = 0; i < 10; i++)
    {
      float t = i / float(9);

      uint32_t shade = rgbFromHsvF(hue, sat*(1.0),     val*(1.0 - t),       1.0); // shades
      uint32_t tone  = rgbFromHsvF(hue, sat*(1.0 - t), val*(1.0 - (t*0.6)), 1.0); // tones
      uint32_t tint  = rgbFromHsvF(hue, sat*(1.0 - t), val*(1.0),           1.0); // tints
    
      tints[i]->setPalette(QPalette(QColor(tint), QColor(tint)));
      tones[i]->setPalette(QPalette(QColor(tone), QColor(tone)));
      shades[i]->setPalette(QPalette(QColor(shade), QColor(shade)));
    }

    // Update the base hues
    for (int h = 0; h < 4; h++)
    {
      swatchHues[h]->setEnabled(false);
      swatchHues[h]->setPalette(QPalette());
      swatchValues[h]->setText("");
    }
    for (int h = 0; h < pal.hueCount(); h++)
    {
      QColor c = QColor::fromHsvF(pal.hue(h), sat*1.0, val*1.0);
      swatchHues[h]->setEnabled(true);
      swatchHues[h]->setPalette(QPalette(c, c));
      swatchValues[h]->setText(QVariant(c.toRgb()).toString());
    }

    // Update the palette preview
    int colIdxForMode[5][5] = { { 0, 0, 0, 0, -1 }, { 0, 0, 0, 1, -1 }, { 0, 0, 1, 2, -1 }, { 0, 2, 3, 1, -1 }, { 0, 2, 3, 1, -1 } };
    for (int i = 0; i < 4; i++)
    {
        int idx = colIdxForMode[pal.m_mode][i];
        float sats[4] = { 1.0 - 0.65, 1.0 - 0.35, 1.0, 1.0 };
        float vals[4] = { 1.0, 1.0, 1.0 - 0.35, 1.0 - 0.65 };
        float baseTone = 0.15;
        float hue2 = pal.hue(idx);
        QColor c = QColor::fromHsvF(hue2, sat*(1.0 - baseTone), val*(1.0 - (baseTone*0.6)));

        int a = i;
        int w = 1;
        PrimaryHSVs[i]->setPalette(QPalette(c, c));
        while (idx == colIdxForMode[pal.m_mode][i + 1])
        {
          i++;
          w++;
          PrimaryHSVs[i]->setPalette(QPalette(c, c));
        }

        for (int s = 0; s < 4; s++)
        {
          QColor x = rgbFromHsvF(hue2, sat*sats[s], val*vals[s], 1.0);
          for (int t = 0; t < w; t++)
          {
            SecondaryHSVs[a * 4 + w * s + t]->setPalette(QPalette(x, x));
          }
        }
    }
}

void PaletteEditorDialog::setMode1()
{
    editor->colorWheel->setMode(0);
    updatePalette();
}

void PaletteEditorDialog::setMode2()
{
    editor->colorWheel->setMode(1);
    updatePalette();
}

void PaletteEditorDialog::setMode3()
{
    editor->colorWheel->setMode(2);
    updatePalette();
}

void PaletteEditorDialog::setMode4()
{
    editor->colorWheel->setMode(3);
    updatePalette();
}

void PaletteEditorDialog::setMode5()
{
    editor->colorWheel->setMode(4);
    updatePalette();
}