#include "ItemPropertyWidget.h"
#include "PolymorphicItemFunctionSet.h"
#include "PutPropertyFunction.h"
#include "LazyCaller.h"
#include "Buttons.h"
#include "StringListComboBox.h"
#include "MenuManager.h"
#include "FileDialog.h"
#include "QtEventUtil.h"
#include "QVariantUtil.h"
#include <cnoid/ConnectionSet>
#include <cnoid/ExecutablePath>
#include <cnoid/UTF8>
#include <cnoid/Format>
#include <variant>
#include <filesystem>
#include <QTableWidget>
#include <QHeaderView>
#include <QBoxLayout>
#include <QDoubleSpinBox>
#include <QLineEdit>
#include <QStyledItemDelegate>
#include <QItemEditorFactory>
#include <QStandardItemEditorCreator>
#include <QKeyEvent>
#include <QApplication>
#include <regex>
#include <cmath>
#include "gettext.h"

using namespace std;
using namespace cnoid;

namespace {

struct Int {
    int value;
    int min, max;
    Int(int v) : value(v) { }
    Int(int v, int min, int max) : value(v), min(min), max(max) { }
};

struct Double {
    double value;
    double min, max;
    int decimals;
    Double(double v, int d) : value(v), decimals(d) { }
    Double(double v, int d, double min, double max)
        : value(v), decimals(d), min(min), max(max) { }
};

typedef std::variant<bool, Int, Double, string, Selection, FilePathProperty> ValueVariant;

typedef std::variant<
    std::function<bool(bool)>,
    std::function<bool(int)>,
    std::function<bool(double)>,
    std::function<bool(const string&)>
    > FunctionVariant;  // std::variant

enum TypeId { TYPE_BOOL, TYPE_INT, TYPE_DOUBLE, TYPE_STRING, TYPE_SELECTION, TYPE_FILEPATH };

class PropertyItem : public QTableWidgetItem
{
public:
    ItemPropertyWidget::Impl* view;
    ValueVariant value;
    FunctionVariant func;
    bool hasValidFunction;

    PropertyItem(ItemPropertyWidget::Impl* view, ValueVariant value);
    PropertyItem(ItemPropertyWidget::Impl* view, ValueVariant value, FunctionVariant func);
    virtual QVariant data(int role) const;
    virtual void setData(int role, const QVariant& qvalue);
};

class CustomizedItemDelegate;

class FilePathEditor : public QWidget
{
    CustomizedItemDelegate* itemDelegate;
    QLineEdit* lineEdit;
    FilePathProperty currentValue;
public:
    FilePathEditor(CustomizedItemDelegate* delegate, QWidget* parent);
    void setValue(const FilePathProperty& value);
    QString value() const;
};


class CustomizedTableWidget : public QTableWidget
{
    bool isResizing;
    int offsetX;
    
public:
    CustomizedTableWidget(QWidget* parent);
    PropertyItem* itemFromIndex(const QModelIndex& index) const;
    bool isPointingBorder(int x);
    virtual void mouseMoveEvent(QMouseEvent* event) override;
    virtual void mousePressEvent(QMouseEvent* event) override;
    virtual void mouseReleaseEvent(QMouseEvent* event) override;
    virtual void resizeEvent(QResizeEvent* event) override;
};

    
class CustomizedItemDelegate : public QStyledItemDelegate
{
public:
    CustomizedTableWidget* tableWidget;
    int decimals;
        
    CustomizedItemDelegate(CustomizedTableWidget* tableWidget);
    virtual QWidget* createEditor(
        QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    virtual void updateEditorGeometry(
        QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    virtual void setEditorData(QWidget* editor, const QModelIndex& index) const override;
    virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
    virtual QString displayText(const QVariant& value, const QLocale& locale) const override;
    virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    void openFileDialog(FilePathProperty value, FilePathEditor* editor);
};

}

namespace cnoid {

class ItemPropertyWidget::Impl : public PutPropertyFunction
{
public:
    ItemPropertyWidget* self;
    ItemPtr currentItem;
    ScopedConnectionSet itemConnections;
    PolymorphicItemFunctionSet  propertyFunctions;
    PolymorphicItemFunctionSet  additionalPropertyFunctions;
    bool isEditable;

    double minValue;
    double maxValue;
    int decimals_;

    CustomizedTableWidget* tableWidget;
    int fontPointSizeDiff;

    bool isEditingProperty;
    bool updateRequestedDuringPropertyEditing;

    Impl(ItemPropertyWidget* self);

    void setCurrentItem(Item* item);
    void clear();
    void updateProperties(bool isItemChanged = false);
    void addProperty(const std::string& name, PropertyItem* propertyItem);
    void onTargetItemSpecified(Item* item);
    void setEditable(bool on);
    void zoomFontSize(int pointSizeDiff);
    
    // PutPropertyFunction's virtual functions
    virtual PutPropertyFunction& decimals(int d) override;
    virtual PutPropertyFunction& min(double min) override;
    virtual PutPropertyFunction& max(double max) override;
    virtual PutPropertyFunction& range(double min, double max) override;
    virtual PutPropertyFunction& reset() override;

    virtual void operator()(
        const std::string& name, bool value) override;
    virtual void operator()(
        const std::string& name, bool value, const std::function<bool(bool)>& func) override;
    virtual void operator()(
        const std::string& name, int value) override;
    virtual void operator()(
        const std::string& name, int value, const std::function<bool(int)>& func) override;
    virtual void operator()(
        const std::string& name, double value) override;
    virtual void operator()(
        const std::string& name, double value, const std::function<bool(double)>& func) override;
    virtual void operator()(
        const std::string& name, const std::string& value) override;
    virtual void operator()(
        const std::string& name, const std::string& value,
        const std::function<bool(const std::string&)>& func) override;
    virtual void operator()(
        const std::string& name, const Selection& selection) override;
    virtual void operator()(
        const std::string& name, const Selection& selection,
        const std::function<bool(int which)>& func) override;
    virtual void operator()(
        const std::string& name, const FilePathProperty& filepath) override;
    virtual void operator()(
        const std::string& name, const FilePathProperty& filepath,
        const std::function<bool(const std::string&)>& func) override;
};

}


namespace {

PropertyItem::PropertyItem(ItemPropertyWidget::Impl* view, ValueVariant value)
    : view(view),
      value(value)
{
    setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
    hasValidFunction = false;
}


PropertyItem::PropertyItem(ItemPropertyWidget::Impl* view, ValueVariant value, FunctionVariant func)
    : view(view),
      value(value),
      func(func)
{
    int flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    if(view->isEditable){
        flags |= Qt::ItemIsEditable;
    }
    setFlags(static_cast<Qt::ItemFlag>(flags));
    hasValidFunction = true;
}


QVariant PropertyItem::data(int role) const
{
    if(role == Qt::DisplayRole || role == Qt::EditRole){
        switch(value.index()){

        case TYPE_BOOL:
            {
                /**
                   QTableWidget originally supports the boolean value, but the string and
                   QStringList types are used to show the boolean value in this implementation
                   so that the translation texts of "True" and "Fasle" can be fully controlled.
                */
                auto boolValue = get<bool>(value);
                if(role == Qt::DisplayRole){
                    return boolValue ? QString(_("True")) : QString(_("False"));
                } else if(role == Qt::EditRole){
                    return QStringList({ (boolValue ? "0" : "1"), _("True"),  _("False") });
                }
            }
            
        case TYPE_INT:       return std::get<Int>(value).value;
        case TYPE_DOUBLE:    return std::get<Double>(value).value;
        case TYPE_STRING:    return std::get<string>(value).c_str();

        case TYPE_SELECTION:
            {
                const Selection& s = std::get<Selection>(value);
                if(role == Qt::DisplayRole){
                    return s.selectedLabel();
                } else if(role == Qt::EditRole){
                    QStringList labels;
                    labels << QString::number(s.selectedIndex());
                    for(int i=0; i < s.size(); ++i){
                        labels << s.label(i);
                    }
                    return labels;
                }
            }

        case TYPE_FILEPATH:
            {
                const FilePathProperty& f = std::get<FilePathProperty>(value);
                string filename = f.filename();
                if(!f.isFullpathDisplayMode()){
                    filename = toUTF8(
                        filesystem::path(fromUTF8(filename)).filename().string());
                }
                return filename.c_str();
            }
        }
    } else if(role == Qt::ToolTipRole){
        if(value.index() == TYPE_FILEPATH){
            const FilePathProperty& f = std::get<FilePathProperty>(value);
            if(!f.isFullpathDisplayMode()){
                string fullpath = f.filename();
                string filename = toUTF8(
                    filesystem::path(fromUTF8(fullpath)).filename().string());
                if(filename != fullpath){
                    return fullpath.c_str();
                }
            }
        }
    }

    return QTableWidgetItem::data(role);
}


void PropertyItem::setData(int role, const QVariant& qvalue)
{
    view->isEditingProperty = true;
    view->updateRequestedDuringPropertyEditing = false;
    
    bool accepted = false;

    if(role == Qt::EditRole){

        switch(variantType(qvalue)){

        case QVariantBoolType:
            accepted = std::get<std::function<bool(bool)>>(func)(qvalue.toBool());
            break;
                
        case QVariantStringType:
            accepted = std::get<std::function<bool(const string&)>>(func)(qvalue.toString().toStdString());
            break;
                
        case QVariantIntType:
            accepted = std::get<std::function<bool(int)>>(func)(qvalue.toInt());
            break;
                
        case QVariantDoubleType:
            accepted = std::get<std::function<bool(double)>>(func)(qvalue.toDouble());
            break;
                
        case QVariantStringListType:
        {
            const QStringList& slist = qvalue.toStringList();
            if(!slist.empty()){
                int valueType = value.index();
                if(valueType == TYPE_BOOL){
                    bool value = (slist[0].toInt() == 0) ? true : false;
                    accepted = std::get<std::function<bool(bool)>>(func)(value);
                } else if(valueType == TYPE_SELECTION){
                    accepted = std::get<std::function<bool(int)>>(func)(slist[0].toInt());
                }
            }
        }
        break;
            
        default:
            break;
        }
            
        if(accepted){
            if(!view->updateRequestedDuringPropertyEditing){
                view->currentItem->notifyUpdate();
            }
        }
        if(view->updateRequestedDuringPropertyEditing){
            auto itemPropertyViewImpl = this->view;
            callLater([itemPropertyViewImpl](){ itemPropertyViewImpl->updateProperties(); });
        }
    }

    view->isEditingProperty = false;
}


FilePathEditor::FilePathEditor(CustomizedItemDelegate* delegate, QWidget* parent)
    : QWidget(parent),
      itemDelegate(delegate)
{
    setAutoFillBackground(true);
    auto hbox = new QHBoxLayout;
    setLayout(hbox);
    hbox->setContentsMargins(0, 0, 0, 0);
    hbox->setSpacing(0);
    lineEdit = new QLineEdit(this);
    lineEdit->setFrame(false);
    hbox->addWidget(lineEdit);
    setFocusProxy(lineEdit);
    auto button = new PushButton(QApplication::style()->standardIcon(QStyle::SP_FileDialogStart), "", this);
    button->sigClicked().connect([this](){ itemDelegate->openFileDialog(currentValue, this); });
    hbox->addWidget(button);
}


void FilePathEditor::setValue(const FilePathProperty& value)
{
    currentValue = value;
    lineEdit->setText(value.filename().c_str());
}


QString FilePathEditor::value() const
{
    if(currentValue.baseDirectory().empty()){
        return lineEdit->text();
    } else {
        return toUTF8(
            (filesystem::path(fromUTF8(currentValue.baseDirectory()))
            / fromUTF8(lineEdit->text().toStdString())).string()).c_str();
    }
}


CustomizedTableWidget::CustomizedTableWidget(QWidget* parent)
    : QTableWidget(parent)
{
    setMouseTracking(true);
    isResizing = false;
}
        

PropertyItem* CustomizedTableWidget::itemFromIndex(const QModelIndex& index) const
{
    return dynamic_cast<PropertyItem*>(QTableWidget::itemFromIndex(index));
}


bool CustomizedTableWidget::isPointingBorder(int x)
{
    int border = columnWidth(0);
    offsetX = border - x;
    return (x >= border - 2 && x <= border + 2);
}


void CustomizedTableWidget::mouseMoveEvent(QMouseEvent* event)
{
    if(isResizing){
        int border = std::max(0, getPosition(event).x() + offsetX);
        setColumnWidth(0, border);
    } else if(isPointingBorder(getPosition(event).x())){
        setCursor(Qt::SizeHorCursor);
    } else {
        setCursor(QCursor());
        QTableWidget::mouseMoveEvent(event);
    }
}


void CustomizedTableWidget::mousePressEvent(QMouseEvent* event)
{
    if(isPointingBorder(getPosition(event).x())){
        isResizing = true;
    } else {
        QTableWidget::mousePressEvent(event);
    }
}
    

void CustomizedTableWidget::mouseReleaseEvent(QMouseEvent* event)
{
    isResizing = false;
    QTableWidget::mouseReleaseEvent(event);
}


void CustomizedTableWidget::resizeEvent(QResizeEvent* event)
{
    QTableWidget::resizeEvent(event);
    horizontalHeader()->resizeSections(QHeaderView::Stretch);
}


CustomizedItemDelegate::CustomizedItemDelegate(CustomizedTableWidget* tableWidget)
    : tableWidget(tableWidget)
{
    decimals = 2;
}


QWidget* CustomizedItemDelegate::createEditor
(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QWidget* editor = 0;
    PropertyItem* item = tableWidget->itemFromIndex(index);
    if(item){
        ValueVariant& value = item->value;
        switch(value.index()){

        case TYPE_INT:
            editor = QStyledItemDelegate::createEditor(parent, option, index);
            if(QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor)){
                Int& v = std::get<Int>(value);
                spinBox->setRange(v.min, v.max);
            }
            break;
                    
        case TYPE_DOUBLE:
            editor = QStyledItemDelegate::createEditor(parent, option, index);
            if(QDoubleSpinBox* doubleSpinBox = dynamic_cast<QDoubleSpinBox*>(editor)){
                Double& v = std::get<Double>(value);
                if(v.decimals >= 0){
                    doubleSpinBox->setDecimals(v.decimals);
                    doubleSpinBox->setSingleStep(pow(10.0, -v.decimals));
                }
                doubleSpinBox->setRange(v.min, v.max);
            }
            break;

        case TYPE_FILEPATH:
            editor = new FilePathEditor(const_cast<CustomizedItemDelegate*>(this), parent);
            break;

        default:
            editor = QStyledItemDelegate::createEditor(parent, option, index);
            break;
        }
    }
    return editor;
}


void CustomizedItemDelegate::updateEditorGeometry
(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    editor->setGeometry(option.rect);

    /*
      if(auto combo = dynamic_cast<QComboBox*>(editor)){
      combo->showPopup();
      }
    */
}


void CustomizedItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
    PropertyItem* item = tableWidget->itemFromIndex(index);
    if(item && (item->value.index() == TYPE_FILEPATH)){
        if(FilePathEditor* fpEditor = dynamic_cast<FilePathEditor*>(editor)){
            fpEditor->setValue(std::get<FilePathProperty>(item->value));
            return;
        }
    }
    QStyledItemDelegate::setEditorData(editor, index);
}


void CustomizedItemDelegate::setModelData
(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
    PropertyItem* item = tableWidget->itemFromIndex(index);
    if(item && (item->value.index() == TYPE_FILEPATH)){
        if(FilePathEditor* fpEditor = dynamic_cast<FilePathEditor*>(editor)){
            item->setData(Qt::EditRole, fpEditor->value());
            return;
        }
    }
    QStyledItemDelegate::setModelData(editor, model, index);
}


QString CustomizedItemDelegate::displayText(const QVariant& value, const QLocale& locale) const
{
    if(checkIfDouble(value)){
        return QString::number(value.toDouble(), 'f', decimals);
    }
    return QStyledItemDelegate::displayText(value, locale);
}


void CustomizedItemDelegate::paint
(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    PropertyItem* item = tableWidget->itemFromIndex(index);
    if(item && (item->value.index() == TYPE_DOUBLE)){
        int& d = const_cast<int&>(decimals);
        d = std::get<Double>(item->value).decimals;
    }
    QStyledItemDelegate::paint(painter, option, index);
}


void CustomizedItemDelegate::openFileDialog(FilePathProperty value, FilePathEditor* editor)
{
    FileDialog dialog;
    dialog.setWindowTitle(_("Select File"));
    dialog.setViewMode(QFileDialog::List);
    if(value.isExistingFileMode()){
        dialog.setFileMode(QFileDialog::ExistingFile);
        dialog.setLabelText(QFileDialog::Accept, _("Select"));
    }else{
        dialog.setFileMode(QFileDialog::AnyFile);
        dialog.setLabelText(QFileDialog::Accept, _("OK"));
    }
    dialog.setLabelText(QFileDialog::Reject, _("Cancel"));

    dialog.updatePresetDirectories(true);

    filesystem::path directory;
    if(!value.baseDirectory().empty()){
        directory = fromUTF8(value.baseDirectory());
    } else {
        filesystem::path filenamePath(fromUTF8(value.filename()));
        if(filenamePath.is_absolute()){
            directory = filenamePath.parent_path();
        }
    }
    if(!directory.empty()){
        dialog.setDirectory(toUTF8(directory.string()));
    }
        
    QStringList filters;
    for(auto& filter : value.filters()){
        filters << filter.c_str();
    }
    filters << _("Any files (*)");
    dialog.setNameFilters(filters);
        
    if(dialog.exec()){
        QStringList filenames;
        filenames = dialog.selectedFiles();
        filesystem::path newDirectory(
            fromUTF8(dialog.directory().absolutePath().toStdString()));
        if(newDirectory != directory){
            value.setBaseDirectory("");
        }
        string filename(filenames.at(0).toStdString());
        if(!value.baseDirectory().empty()){
            filename = toUTF8(filesystem::path(fromUTF8(filename)).filename().string());
        }
        if(value.isExtensionRemovalModeForFileDialogSelection()){
            regex pattern1(".+\\(\\*\\.(.+)\\)");
            std::smatch match;
            for(auto& filter : value.filters()){
                if(regex_match(filter, match, pattern1)){
                    regex pattern2(formatC("^(.+)(\\.{})$", match.str(1)), std::regex_constants::icase);
                    if(regex_match(filename, match, pattern2)){
                        filename = regex_replace(filename, pattern2, "$1");
                        break;
                    }
                }
            }
        }
        value.setFilename(filename);
        editor->setValue(value);
        commitData(editor);
    }
}

}


ItemPropertyWidget::ItemPropertyWidget(QWidget* parent)
    : QWidget(parent)
{
    impl = new Impl(this);
}


ItemPropertyWidget::Impl::Impl(ItemPropertyWidget* self)
    : self(self)
{
    isEditingProperty = false;
    
    tableWidget = new CustomizedTableWidget(self);
    tableWidget->setFrameShape(QFrame::NoFrame);
    tableWidget->setColumnCount(2);
    tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
    tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
    tableWidget->setTabKeyNavigation(true);
    tableWidget->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
    tableWidget->setEditTriggers(
        /* QAbstractItemView::CurrentChanged | */
        QAbstractItemView::DoubleClicked |
        QAbstractItemView::SelectedClicked | 
        QAbstractItemView::EditKeyPressed |
        QAbstractItemView::AnyKeyPressed);

    QHeaderView* hh = tableWidget->horizontalHeader();
    QHeaderView* vh = tableWidget->verticalHeader();
    hh->hide();
    vh->hide();
    hh->setSectionResizeMode(QHeaderView::Interactive);
    vh->setSectionResizeMode(QHeaderView::ResizeToContents);
    hh->setStretchLastSection(true);

    QStyledItemDelegate* delegate = new CustomizedItemDelegate(tableWidget);
    QItemEditorFactory* factory = new QItemEditorFactory;
    
    QItemEditorCreatorBase* selectionEditorCreator =
        new QStandardItemEditorCreator<StringListComboBox>();

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    factory->registerEditor(QMetaType::QStringList, selectionEditorCreator);
#else
    factory->registerEditor(QVariant::StringList, selectionEditorCreator);
#endif
    
    delegate->setItemEditorFactory(factory);

    tableWidget->setItemDelegate(delegate);
        
    QVBoxLayout* vbox = new QVBoxLayout();
    vbox->setContentsMargins(0, 0, 0, 0);
    vbox->addWidget(tableWidget);
    self->setLayout(vbox);

    fontPointSizeDiff = 0;
}


ItemPropertyWidget::~ItemPropertyWidget()
{
    delete impl;
}


void ItemPropertyWidget::setPropertyFunction_
(const std::type_info& type, std::function<void(Item* item, PutPropertyFunction& putProperty)> func)
{
    impl->propertyFunctions.setFunction(
        type,
        [this, func](Item* item){ func(item, *impl); });
}


void ItemPropertyWidget::addPropertyFunction_
(const std::type_info& type, std::function<void(Item* item, PutPropertyFunction& putProperty)> func)
{
    auto existingFunc = impl->additionalPropertyFunctions.getFunction(type);

    if(existingFunc){
        impl->additionalPropertyFunctions.setFunction(
            type,
            [this, existingFunc, func](Item* item){
                existingFunc(item);
                func(item, *impl);
            });
    } else {
        auto& apf = impl->additionalPropertyFunctions;
        impl->additionalPropertyFunctions.setFunction(
            type,
            [this, &apf, func](Item* item){
                apf.dispatchSuperClassFunction(item);
                func(item, *impl);
            });
    }
}


bool ItemPropertyWidget::hasPropertyFunctionFor(Item* item) const
{
    return item ? impl->propertyFunctions.hasFunctionFor(item) : false;
}


bool ItemPropertyWidget::hasAdditionalPropertyFunctionFor(Item* item) const
{
    return item ? impl->additionalPropertyFunctions.hasFunctionFor(item) : false;
}


void ItemPropertyWidget::dispatchAdditionalPropertyFunctions(Item* item)
{
    impl->additionalPropertyFunctions.dispatch(item);
}


void ItemPropertyWidget::setCurrentItem(Item* item)
{
    impl->setCurrentItem(item);
}


void ItemPropertyWidget::Impl::setCurrentItem(Item* item)
{
    if(item != currentItem){
        itemConnections.disconnect();
        isEditable = false;
        
        if(item){
            itemConnections.add(
                item->sigUpdated().connect(
                    [this]{ updateProperties(); }));
            itemConnections.add(
                item->sigNameChanged().connect(
                    [this](const std::string& /* oldName */){ updateProperties(); }));
            itemConnections.add(
                item->sigDisconnectedFromRoot().connect(
                    [this]{ setCurrentItem(nullptr); }));
            itemConnections.add(
                item->sigContinuousUpdateStateChanged().connect(
                    [this](bool on){ setEditable(!on); }));

            isEditable = !item->isContinuousUpdateState();
                
        }
        
        currentItem = item;
        updateProperties(true);
    }
}


void ItemPropertyWidget::updateProperties()
{
    impl->updateProperties(false);
}


void ItemPropertyWidget::Impl::updateProperties(bool isItemChanged)
{
    if(isEditingProperty){
        updateRequestedDuringPropertyEditing = true;

    } else {
        int currentRow;
        int currentColumn;
        if(!isItemChanged){
            currentRow = tableWidget->currentRow();
            currentColumn = tableWidget->currentColumn();
        }

        tableWidget->setRowCount(0);
        if(currentItem){
            reset();
            propertyFunctions.dispatch(currentItem);
        }

        if(!isItemChanged){
            tableWidget->setCurrentCell(currentRow, currentColumn);
        }
    }
}


void ItemPropertyWidget::resetColumnSizes()
{
    impl->tableWidget->resizeColumnsToContents();
}


void ItemPropertyWidget::setOperationMenu(MenuManager& menuManager)
{
    menuManager.addItem(_("Update"))->sigTriggered().connect(
        [this](){ updateProperties(); });
    
    menuManager.addItem(_("Reset Column Sizes"))->sigTriggered().connect(
        [this](){ resetColumnSizes(); });
}


void ItemPropertyWidget::Impl::addProperty(const std::string& name, PropertyItem* propertyItem)
{
    int row = tableWidget->rowCount();
    tableWidget->setRowCount(row + 1);

    QTableWidgetItem* nameItem = new QTableWidgetItem(name.c_str());
    nameItem->setFlags(Qt::ItemIsEnabled);
    tableWidget->setItem(row, 0, nameItem);
    
    tableWidget->setItem(row, 1, propertyItem);
}


PutPropertyFunction& ItemPropertyWidget::Impl::decimals(int d)
{
    decimals_ = d;
    return *this;
}


PutPropertyFunction& ItemPropertyWidget::Impl::min(double min)
{
    minValue = min;
    return *this;
}


PutPropertyFunction& ItemPropertyWidget::Impl::max(double max)
{
    maxValue = max;
    return *this;
}


PutPropertyFunction& ItemPropertyWidget::Impl::range(double min, double max)
{
    minValue = min;
    maxValue = max;
    return *this;
}


PutPropertyFunction& ItemPropertyWidget::Impl::reset()
{
    minValue = -999999999;
    maxValue =  999999999;
    decimals_ = 2;
    return *this;
}
        
void ItemPropertyWidget::Impl::operator()(const std::string& name, bool value)
{
    addProperty(name, new PropertyItem(this, value));
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, bool value, const std::function<bool(bool)>& func)
{
    addProperty(name, new PropertyItem(this, value, func));
}


void ItemPropertyWidget::Impl::operator()(const std::string& name, int value)
{
    addProperty(name, new PropertyItem(this, Int(value)));
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, int value, const std::function<bool(int)>& func)
{
    addProperty(name, new PropertyItem(this, Int(value, minValue, maxValue), func));
}


void ItemPropertyWidget::Impl::operator()(const std::string& name, double value)
{
    addProperty(name, new PropertyItem(this, Double(value, decimals_)));
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, double value, const std::function<bool(double)>& func)
{
    addProperty(name, new PropertyItem(this, Double(value, decimals_, minValue, maxValue), func));
}


void ItemPropertyWidget::Impl::operator()(const std::string& name, const std::string& value)
{
    addProperty(name, new PropertyItem(this, value));
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, const std::string& value, const std::function<bool(const std::string&)>& func)
{
    addProperty(name, new PropertyItem(this, value, func));
}


void ItemPropertyWidget::Impl::operator()(const std::string& name, const Selection& selection)
{
    addProperty(name, new PropertyItem(this, selection));
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, const Selection& selection, const std::function<bool(int which)>& func)
{
    addProperty(name, new PropertyItem(this, selection, func));
}


void ItemPropertyWidget::Impl::operator()(const std::string& name, const FilePathProperty& filepath)
{
    addProperty(name, new PropertyItem(this, filepath) );
}


void ItemPropertyWidget::Impl::operator()
(const std::string& name, const FilePathProperty& filepath, const std::function<bool(const std::string&)>& func)
{
    addProperty(name, new PropertyItem(this, filepath, func) );
}


void ItemPropertyWidget::Impl::setEditable(bool on)
{
    isEditable = on;
    updateProperties(false);
}


void ItemPropertyWidget::keyPressEvent(QKeyEvent* event)
{
    if(event->modifiers() & Qt::ControlModifier){
        switch(event->key()){
        case Qt::Key_Plus:
        case Qt::Key_Semicolon:
            impl->zoomFontSize(1);
            return;
        case Qt::Key_Minus:
            impl->zoomFontSize(-1);
            return;
        default:
            break;
        }
    }
    QWidget::keyPressEvent(event);
}


void ItemPropertyWidget::Impl::zoomFontSize(int pointSizeDiff)
{
    QFont font = tableWidget->font();
    font.setPointSize(font.pointSize() + pointSizeDiff);
    tableWidget->setFont(font);
    fontPointSizeDiff += pointSizeDiff;
}
