/*
 * Copyright (C) 2006-2017 Andreas Persson
 * Copyright (C) 2007-2026 Christian Schoenebeck
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with program; see the file COPYING. If not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#include "compat.h"
#include "dimensionmanager.h"

#if HAS_GTKMM_STOCK
# include <gtkmm/stock.h>
#endif
#include <gtkmm/messagedialog.h>
#include <gtkmm/dialog.h>
#include <gtkmm/comboboxtext.h>
#include <gtkmm/spinbutton.h>
#include <gtkmm/label.h>
#if USE_GTKMM_GRID
# include <gtkmm/grid.h>
#else
# include <gtkmm/table.h>
#endif

#include "global.h"
#include "compat.h"

// returns a human readable name of the given dimension type
Glib::ustring dimTypeAsString(gig::dimension_t d) {
    char buf[32];
    switch (d) {
        case gig::dimension_none:
            return _("None");
        case gig::dimension_samplechannel:
            return _("Sample Channel");
        case gig::dimension_layer:
            return _("Layer");
        case gig::dimension_velocity:
            return _("Velocity");
        case gig::dimension_channelaftertouch:
            return _("Aftertouch");
        case gig::dimension_releasetrigger:
            return _("Release Trigger");
        case gig::dimension_keyboard:
            return _("Keyswitching");
        case gig::dimension_roundrobin:
            return _("Round Robin");
        case gig::dimension_random:
            return _("Random Generator");
        case gig::dimension_smartmidi:
            return _("Smart MIDI");
        case gig::dimension_roundrobinkeyboard:
            return _("Keyboard Round Robin");
        case gig::dimension_modwheel:
            return _("Modulation Wheel");
        case gig::dimension_breath:
            return _("Breath Ctrl.");
        case gig::dimension_foot:
            return _("Foot Ctrl.");
        case gig::dimension_portamentotime:
            return _("Portamento Time Ctrl.");
        case gig::dimension_effect1:
            return _("Effect Ctrl. 1");
        case gig::dimension_effect2:
            return _("Effect Ctrl. 2");
        case gig::dimension_genpurpose1:
            return _("General Purpose Ctrl. 1");
        case gig::dimension_genpurpose2:
            return _("General Purpose Ctrl. 2");
        case gig::dimension_genpurpose3:
            return _("General Purpose Ctrl. 3");
        case gig::dimension_genpurpose4:
            return _("General Purpose Ctrl. 4");
        case gig::dimension_sustainpedal:
            return _("Sustain Pedal");
        case gig::dimension_portamento:
            return _("Portamento Ctrl.");
        case gig::dimension_sostenutopedal:
            return _("Sostenuto Pedal");
        case gig::dimension_softpedal:
            return _("Soft Pedal");
        case gig::dimension_genpurpose5:
            return _("General Purpose Ctrl. 5");
        case gig::dimension_genpurpose6:
            return _("General Purpose Ctrl. 6");
        case gig::dimension_genpurpose7:
            return _("General Purpose Ctrl. 7");
        case gig::dimension_genpurpose8:
            return _("General Purpose Ctrl. 8");
        case gig::dimension_effect1depth:
            return _("Effect 1 Depth");
        case gig::dimension_effect2depth:
            return _("Effect 2 Depth");
        case gig::dimension_effect3depth:
            return _("Effect 3 Depth");
        case gig::dimension_effect4depth:
            return _("Effect 4 Depth");
        case gig::dimension_effect5depth:
            return _("Effect 5 Depth");
        default:
            snprintf(buf, sizeof(buf), "Unknown Type (0x%x) !!!", d);
            return buf;
    }
}

// returns a human readable description of the given dimension
static Glib::ustring __dimDescriptionAsString(gig::dimension_t d) {
    switch (d) {
        case gig::dimension_none:
            return _("Dimension not in use");
        case gig::dimension_samplechannel:
            return _("If used sample has more than one channel (thus is not mono)");
        case gig::dimension_layer:
            return _("For layering of up to 8 instruments (and eventually crossfading of 2 or 4 layers");
        case gig::dimension_velocity:
            return _("Key Velocity (this is the only dimension in gig2 where the ranges can exactly be defined)");
        case gig::dimension_channelaftertouch:
            return _("Channel Key Pressure");
        case gig::dimension_releasetrigger:
            return _("Special dimension for triggering samples on releasing a key");
        case gig::dimension_keyboard:
            return _("Dimension for keyswitching (keyboard)");
        case gig::dimension_roundrobin:
            return _("Different samples triggered each time a note is played, dimension regions selected in sequence");
        case gig::dimension_random:
            return _("Different samples triggered each time a note is played, random order");
        case gig::dimension_smartmidi:
            return _("For MIDI tools like legato and repetition mode");
        case gig::dimension_roundrobinkeyboard:
            return _("Different samples triggered each time a note is played, any key advances the counter");
        case gig::dimension_modwheel:
            return _("MIDI Controller 1");
        case gig::dimension_breath:
            return _("MIDI Controller 2");
        case gig::dimension_foot:
            return _("MIDI Controller 4");
        case gig::dimension_portamentotime:
            return _("MIDI Controller 5");
        case gig::dimension_effect1:
            return _("MIDI Controller 12");
        case gig::dimension_effect2:
            return _("MIDI Controller 13");
        case gig::dimension_genpurpose1:
            return _("Slider, MIDI Controller 16");
        case gig::dimension_genpurpose2:
            return _("Slider, MIDI Controller 17");
        case gig::dimension_genpurpose3:
            return _("Slider, MIDI Controller 18");
        case gig::dimension_genpurpose4:
            return _("Slider, MIDI Controller 19");
        case gig::dimension_sustainpedal:
            return _("MIDI Controller 64");
        case gig::dimension_portamento:
            return _("MIDI Controller 65");
        case gig::dimension_sostenutopedal:
            return _("MIDI Controller 66");
        case gig::dimension_softpedal:
            return _("MIDI Controller 67");
        case gig::dimension_genpurpose5:
            return _("Button, MIDI Controller 80");
        case gig::dimension_genpurpose6:
            return _("Button, MIDI Controller 81");
        case gig::dimension_genpurpose7:
            return _("Button, MIDI Controller 82");
        case gig::dimension_genpurpose8:
            return _("Button, MIDI Controller 83");
        case gig::dimension_effect1depth:
            return _("MIDI Controller 91");
        case gig::dimension_effect2depth:
            return _("MIDI Controller 92");
        case gig::dimension_effect3depth:
            return _("MIDI Controller 93");
        case gig::dimension_effect4depth:
            return _("MIDI Controller 94");
        case gig::dimension_effect5depth:
            return _("MIDI Controller 95");
        default:
            return _("Please report this !!!");
    }
}

static const char* _black() {
    return isDarkMode() ? "white" : "black";
}

static const char* _gray() {
    static const char* bronze = "#f2be00";
    return isDarkMode() ? bronze : "gray";
}

DimTypeCellRenderer::DimTypeCellRenderer() :
    Glib::ObjectBase(typeid(DimTypeCellRenderer)),
    Gtk::CellRendererText(),
    m_propertyDimType(*this, "gigdimension_t", gig::dimension_none),
    m_propertyUsageCount(*this, "intusagecount", 0),
    m_propertyTotalRegions(*this, "inttotalregions", 0)
{
    propertyDimType().signal_changed().connect(
        sigc::mem_fun(*this, &DimTypeCellRenderer::typeChanged)
    );
    propertyUsageCount().signal_changed().connect(
        sigc::mem_fun(*this, &DimTypeCellRenderer::statsChanged)
    );
    propertyTotalRegions().signal_changed().connect(
        sigc::mem_fun(*this, &DimTypeCellRenderer::statsChanged)
    );
}

void DimTypeCellRenderer::typeChanged() {
    gig::dimension_t type = propertyDimType();
    Glib::ustring s = dimTypeAsString(type);
    property_text() = s;
}

void DimTypeCellRenderer::statsChanged() {
    int usageCount   = propertyUsageCount();
    int totalRegions = propertyTotalRegions();
    bool bDimensionExistsOnAllRegions = (usageCount == totalRegions);
    property_foreground() = ((bDimensionExistsOnAllRegions) ? _black() : _gray());
}

IntSetCellRenderer::IntSetCellRenderer() :
    Glib::ObjectBase(typeid(IntSetCellRenderer)),
    Gtk::CellRendererText(),
    m_propertyValue(*this, "stdintset", std::set<int>())
{
    propertyValue().signal_changed().connect(
        sigc::mem_fun(*this, &IntSetCellRenderer::valueChanged)
    );
}

void IntSetCellRenderer::valueChanged() {
    Glib::ustring s;
    std::set<int> v = propertyValue();
    for (std::set<int>::const_iterator it = v.begin(); it != v.end(); ++it) {
        s += ToString(*it);
        if (*it != *v.rbegin()) s += "|";
    }
    property_text() = s;
    property_foreground() = (v.size() > 1) ? _gray() : _black();
}

DimensionManager::DimensionManager(Gtk::Window& parent) :
    ManagedWindow(parent),
#if HAS_GTKMM_STOCK
    addButton(Gtk::Stock::ADD), removeButton(Gtk::Stock::REMOVE),
#else
    addButton(_("_Add"), true), removeButton(_("_Remove"), true),
#endif
    allRegionsCheckBox(_("All Regions"))
{
    ignoreColumnClicked = true;

    if (!Settings::singleton()->autoRestoreWindowDimension) {
        set_default_size(630, 250);
        set_position(Gtk::WIN_POS_MOUSE);
    }

#if !HAS_GTKMM_STOCK
    // see : https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
    addButton.set_icon_name("list-add");
    removeButton.set_icon_name("list-remove");
#endif

    set_title(_("Dimensions of selected Region"));
    add(vbox);
    scrolledWindow.add(treeView);
    vbox.pack_start(scrolledWindow);
    scrolledWindow.show();
    vbox.pack_start(buttonBox, Gtk::PACK_SHRINK);
    buttonBox.set_layout(Gtk::BUTTONBOX_END);
#if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
    buttonBox.set_margin(5);
#else
    buttonBox.set_border_width(5);
#endif
    buttonBox.show();
    buttonBox.pack_start(allRegionsCheckBox, Gtk::PACK_EXPAND_PADDING);
    buttonBox.pack_start(addButton, Gtk::PACK_SHRINK);
    buttonBox.pack_start(removeButton, Gtk::PACK_SHRINK);
    addButton.show();
    removeButton.show();
    allRegionsCheckBox.set_tooltip_text(
        _("Enable this if you want to edit dimensions of all regions simultaniously.")
    );

    // setup the table
    refTableModel = Gtk::ListStore::create(tableModel);
    treeView.set_model(refTableModel);
    treeView.append_column(_("Dimension Type"), m_cellRendererDimType);
    treeView.append_column(_("Bits"), m_cellRendererIntSet);
    treeView.append_column(_("Zones"), m_cellRendererIntSet);
    treeView.append_column(_("Description"), tableModel.m_description);
    treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyDimType(), tableModel.m_type);
    treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyUsageCount(), tableModel.m_usageCount);
    treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyTotalRegions(), tableModel.m_totalRegions);
    treeView.get_column(1)->add_attribute(m_cellRendererIntSet.propertyValue(), tableModel.m_bits);
    treeView.get_column(2)->add_attribute(m_cellRendererIntSet.propertyValue(), tableModel.m_zones);
    treeView.show();

    treeView.signal_cursor_changed().connect(
        sigc::mem_fun(*this, &DimensionManager::onColumnClicked)
    );

    addButton.signal_clicked().connect(
        sigc::mem_fun(*this, &DimensionManager::addDimension)
    );

    removeButton.signal_clicked().connect(
        sigc::mem_fun(*this, &DimensionManager::removeDimension)
    );
    allRegionsCheckBox.signal_toggled().connect(
        sigc::mem_fun(*this, &DimensionManager::onAllRegionsCheckBoxToggled)
    );

#if HAS_GTKMM_SHOW_ALL_CHILDREN
    show_all_children();
#endif

    Settings::singleton()->showTooltips.get_proxy().signal_changed().connect(
        sigc::mem_fun(*this, &DimensionManager::on_show_tooltips_changed)
    );
    on_show_tooltips_changed();
}

void DimensionManager::on_show_tooltips_changed() {
    const bool b = Settings::singleton()->showTooltips;

    treeView.set_has_tooltip(b);
    allRegionsCheckBox.set_has_tooltip(b);

    set_has_tooltip(b);
}

bool DimensionManager::allRegions() const {
    return allRegionsCheckBox.get_active();
}

void DimensionManager::onAllRegionsCheckBoxToggled() {
    set_title(
        allRegions() ? _("Dimensions of all Regions") :  _("Dimensions of selected Region")
    );
    treeView.set_tooltip_text(
        allRegions() ?
            isDarkMode() ?
                _("Dimensions and numbers in bronze indicates a difference among the individual regions.") :
                _("Dimensions and numbers in gray indicates a difference among the individual regions.") :
            _("You are currently only viewing dimensions of the currently selected region.")
    );
    refreshManager();
}

// following two data types are just used in DimensionManager::refresManager(),
// due to the maps template nature however, they must be declared at global
// space to avoid compilation errors
struct _DimDef {
    std::set<int> bits;
    std::set<int> zones;
    int usageCount;
};
typedef std::map<gig::dimension_t, _DimDef> _Dimensions;

// update all GUI elements according to current gig::Region information
void DimensionManager::refreshManager() {
    set_sensitive(false);
    refTableModel->clear();
    if (allRegions()) {
        if (region) {
            _Dimensions dims;
            gig::Instrument* instr = (gig::Instrument*)region->GetParent();
            int iRegionsCount = 0;
            for (size_t r = 0; gig::Region* rgn = instr->GetRegionAt(r); ++r, ++iRegionsCount) {
                for (uint i = 0; i < rgn->Dimensions; i++) {
                    gig::dimension_def_t* dim = &rgn->pDimensionDefinitions[i];
                    dims[dim->dimension].bits.insert(dim->bits);
                    dims[dim->dimension].zones.insert(dim->zones);
                    dims[dim->dimension].usageCount++;
                }
            }
            for (_Dimensions::const_iterator it = dims.begin(); it != dims.end(); ++it) {
                Gtk::TreeModel::Row row = *(refTableModel->append());
                row[tableModel.m_type] = it->first;
                row[tableModel.m_bits] = it->second.bits;
                row[tableModel.m_zones] = it->second.zones;
                row[tableModel.m_description] = __dimDescriptionAsString(it->first);
                row[tableModel.m_usageCount] = it->second.usageCount;
                row[tableModel.m_totalRegions] = iRegionsCount;
            }
        }
    } else {
        if (region) {
            for (uint i = 0; i < region->Dimensions; i++) {
                gig::dimension_def_t* dim = &region->pDimensionDefinitions[i];
                Gtk::TreeModel::Row row = *(refTableModel->append());
                std::set<int> vBits;
                vBits.insert(dim->bits);
                row[tableModel.m_bits] = vBits;
                std::set<int> vZones;
                vZones.insert(dim->zones);
                row[tableModel.m_zones] = vZones;
                row[tableModel.m_description] = __dimDescriptionAsString(dim->dimension);
                row[tableModel.m_type] = dim->dimension;
                row[tableModel.m_usageCount] = 1;
                row[tableModel.m_totalRegions] = 1;
            }
        }
    }
    set_sensitive(region);
}

void DimensionManager::show(gig::Region* region) {
    ignoreColumnClicked = true;
    this->region = region;
    refreshManager();
    Gtk::Window::show();
    deiconify();
    ignoreColumnClicked = false;
}

void DimensionManager::set_region(gig::Region* region) {
    ignoreColumnClicked = true;
    this->region = region;
    refreshManager();
    ignoreColumnClicked = false;
}

void DimensionManager::onColumnClicked() {
    printf("DimensionManager::onColumnClicked()\n");

    //FIXME: BUG: this method is currently very unreliably called, it should actually be called when the user selects another column, it is ATM however also called when the table content changed programmatically causing the dialog below to popup at undesired times !

    //HACK: Prevents that onColumnClicked() gets called multiple times or at times where it is not desired
    if (ignoreColumnClicked) {
        ignoreColumnClicked = false;
        return;
    }
#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 18) || GTKMM_MAJOR_VERSION > 2
    // prevents app to crash if this dialog is closed
    if (!get_visible())
        return;
#else
# warning Your GTKMM version is too old; dimension manager dialog might crash when changing a dimension type !
#endif

#if (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 8) || GTKMM_MAJOR_VERSION > 3
    if (!is_visible()) return;
#endif

    Gtk::TreeModel::Path path;
    Gtk::TreeViewColumn* focus_column;
    treeView.get_cursor(path, focus_column);
    //const int row = path[0];
    if (focus_column == treeView.get_column(0)) {
        Gtk::TreeModel::iterator it = treeView.get_model()->get_iter(path);
        if (!it) return;
        Gtk::TreeModel::Row row = *it;
        gig::dimension_t oldType = row[tableModel.m_type];

        Gtk::Dialog dialog(_("Change Dimension"), true /*modal*/);
        int oldTypeIndex = -1;
        Glib::RefPtr<Gtk::ListStore> refComboModel = Gtk::ListStore::create(comboModel);
        for (int i = 0x01, count = 0; i < 0xff; i++) {
            Glib::ustring sType =
                dimTypeAsString(static_cast<gig::dimension_t>(i));
            if (i == oldType) oldTypeIndex = count;
            if (sType.find("Unknown") != 0) {
                Gtk::TreeModel::Row row = *(refComboModel->append());
                row[comboModel.m_type_id]   = i;
                row[comboModel.m_type_name] = sType;
                count++;
            }
        }
#if USE_GTKMM_GRID
        Gtk::Grid table;
#else
        Gtk::Table table(1, 2);
#endif
        Gtk::Label labelDimType(_("Dimension:"), Gtk::ALIGN_START);
        Gtk::ComboBox comboDimType;
        comboDimType.set_model(refComboModel);
        comboDimType.pack_start(comboModel.m_type_id);
        comboDimType.pack_start(comboModel.m_type_name);
        table.attach(labelDimType, 0, 1, 0, 1);
        table.attach(comboDimType, 1, 2, 0, 1);
#if USE_GTKMM_BOX
        dialog.get_content_area()->pack_start(table);
#else
        dialog.get_vbox()->pack_start(table);
#endif

#if HAS_GTKMM_STOCK
        dialog.add_button(Gtk::Stock::OK, 0);
        dialog.add_button(Gtk::Stock::CANCEL, 1);
#else
        dialog.add_button(_("_OK"), 0);
        dialog.add_button(_("_Cancel"), 1);
#endif
#if HAS_GTKMM_SHOW_ALL_CHILDREN
        dialog.show_all_children();
#endif
        
        comboDimType.set_active(oldTypeIndex);

        if (!dialog.run()) { // OK selected ...
            ignoreColumnClicked = true;
            Gtk::TreeModel::iterator iterType = comboDimType.get_active();
            if (!iterType) return;
            Gtk::TreeModel::Row rowType = *iterType;
            if (!rowType) return;
            int iTypeID = rowType[comboModel.m_type_id];
            gig::dimension_t newType = static_cast<gig::dimension_t>(iTypeID);
            if (newType == oldType) return;
            //printf("change 0x%x -> 0x%x\n", oldType, newType);

            // assemble the list of regions where the selected dimension type
            // shall be changed
            std::vector<gig::Region*> vRegions;
            if (allRegions()) {
                gig::Instrument* instr = (gig::Instrument*)region->GetParent();
                for (size_t r = 0; gig::Region* rgn = instr->GetRegionAt(r); ++r) {
                    if (rgn->GetDimensionDefinition(oldType)) vRegions.push_back(rgn);
                }
            } else vRegions.push_back(region);

            std::set<Glib::ustring> errors;

            for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) {
                gig::Region* region = vRegions[iRgn];
                try {
                    // notify everybody that we're going to update the region
                    region_to_be_changed_signal.emit(region);
                    // change the dimension type on that region
                    region->SetDimensionType(oldType, newType);
                    // let everybody know there was a change
                    region_changed_signal.emit(region);
                } catch (RIFF::Exception e) {
                    // notify that the changes are over (i.e. to avoid dead locks)
                    region_changed_signal.emit(region);
                    Glib::ustring txt = _("Could not alter dimension: ") + e.Message;
                    if (vRegions.size() == 1) {
                        // show error message directly
                        Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
                        msg.run();
                    } else {
                        // remember error, they are shown after all regions have been processed
                        errors.insert(txt);
                    }
                }
            }
            // update all GUI elements
            refreshManager();

            if (!errors.empty()) {
                Glib::ustring txt = _(
                    "The following errors occurred while trying to change the dimension type on all regions:"
                );
                txt += "\n\n";
                for (std::set<Glib::ustring>::const_iterator it = errors.begin();
                    it != errors.end(); ++it)
                {
                    txt += "-> " + *it + "\n";
                }
                txt += "\n";
                txt += _(
                    "You might also want to check the console for further warnings and "
                    "error messages."
                );
                Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
                msg.run();
            }
        }
    } else if (focus_column == treeView.get_column(1) || focus_column == treeView.get_column(2)) {
        Glib::ustring txt = _("Right-click on a specific dimension zone of the dimension region selector to delete or split that particular dimension zone!");
        Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_INFO);
        msg.run();
    }
}

void DimensionManager::addDimension() {
    Gtk::Dialog dialog(_("New Dimension"), true /*modal*/);
    // add dimension type combo box to the dialog
    Glib::RefPtr<Gtk::ListStore> refComboModel = Gtk::ListStore::create(comboModel);
    for (int i = 0x01; i < 0xff; i++) {
        Glib::ustring sType =
            dimTypeAsString(static_cast<gig::dimension_t>(i));
        if (sType.find("Unknown") != 0) {
            Gtk::TreeModel::Row row = *(refComboModel->append());
            row[comboModel.m_type_id]   = i;
            row[comboModel.m_type_name] = sType;
        }
    }
#if USE_GTKMM_GRID
    Gtk::Grid table;
#else
    Gtk::Table table(2, 2);
#endif
    Gtk::Label labelDimType(_("Dimension:"), Gtk::ALIGN_START);
    Gtk::ComboBox comboDimType;
    comboDimType.set_model(refComboModel);
    comboDimType.pack_start(comboModel.m_type_id);
    comboDimType.pack_start(comboModel.m_type_name);
    Gtk::Label labelZones(_("Zones:"), Gtk::ALIGN_START);
#if USE_GTKMM_GRID
    table.attach(labelDimType, 0, 0);
    table.attach(comboDimType, 1, 0);
    table.attach(labelZones, 0, 1);
#else
    table.attach(labelDimType, 0, 1, 0, 1);
    table.attach(comboDimType, 1, 2, 0, 1);
    table.attach(labelZones, 0, 1, 1, 2);
#endif

#if USE_GTKMM_BOX
    dialog.get_content_area()->pack_start(table);
#else
    dialog.get_vbox()->pack_start(table);
#endif

    // number of zones: use a combo box with fix values for gig
    // v2 and a spin button for v3
    Gtk::ComboBoxText comboZones;
    Gtk::SpinButton spinZones;
    bool version2 = false;
    if (region) {
        gig::File* file = (gig::File*)region->GetParent()->GetParent();
        version2 = file->pVersion && file->pVersion->major == 2;
    }
    if (version2) {
        for (int i = 1; i <= 5; i++) {
            char buf[3];
            snprintf(buf, sizeof(buf), "%d", 1 << i);
#if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 24) || GTKMM_MAJOR_VERSION < 2
            comboZones.append_text(buf);
#else
            comboZones.append(buf);
#endif
        }
        table.attach(comboZones, 1, 2, 1, 2);
    } else {
        spinZones.set_increments(1, 8);
        spinZones.set_numeric(true);
        spinZones.set_range(2, 128);
        spinZones.set_value(2);
        table.attach(spinZones, 1, 2, 1, 2);
    }

#if HAS_GTKMM_STOCK
    dialog.add_button(Gtk::Stock::OK, 0);
    dialog.add_button(Gtk::Stock::CANCEL, 1);
#else
    dialog.add_button(_("_OK"), 0);
    dialog.add_button(_("_Cancel"), 1);
#endif
#if HAS_GTKMM_SHOW_ALL_CHILDREN
    dialog.show_all_children();
#endif

    if (!dialog.run()) { // OK selected ...
        Gtk::TreeModel::iterator iterType = comboDimType.get_active();
        if (!iterType) return;
        Gtk::TreeModel::Row rowType = *iterType;
        if (!rowType) return;
        int iTypeID = rowType[comboModel.m_type_id];
        gig::dimension_t type = static_cast<gig::dimension_t>(iTypeID);
        gig::dimension_def_t dim;
        dim.dimension = type;

        if (version2) {
            if (comboZones.get_active_row_number() < 0) return;
            dim.bits = comboZones.get_active_row_number() + 1;
            dim.zones = 1 << dim.bits;
        } else {
            dim.zones = spinZones.get_value_as_int();
            dim.bits = zoneCountToBits(dim.zones);
        }

        // assemble the list of regions where the selected dimension shall be
        // added to
        std::vector<gig::Region*> vRegions;
        if (allRegions()) {
            gig::Instrument* instr = (gig::Instrument*)region->GetParent();
            for (size_t r = 0; gig::Region* rgn = instr->GetRegionAt(r); ++r) {
                if (!rgn->GetDimensionDefinition(type)) vRegions.push_back(rgn);
            }
        } else vRegions.push_back(region);
            
        std::set<Glib::ustring> errors;

        for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) {
            gig::Region* region = vRegions[iRgn];
            try {
                printf(
                    "Adding dimension (type=0x%x, bits=%d, zones=%d)\n",
                    dim.dimension, dim.bits, dim.zones
                );
                // notify everybody that we're going to update the region
                region_to_be_changed_signal.emit(region);
                // add the new dimension to the region
                // (implicitly creates new dimension regions)
                region->AddDimension(&dim);
                // let everybody know there was a change
                region_changed_signal.emit(region);
            } catch (RIFF::Exception e) {
                // notify that the changes are over (i.e. to avoid dead locks)
                region_changed_signal.emit(region);
                Glib::ustring txt = _("Could not add dimension: ") + e.Message;
                if (vRegions.size() == 1) {
                    // show error message directly
                    Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
                    msg.run();
                } else {
                    // remember error, they are shown after all regions have been processed
                    errors.insert(txt);
                }
            }
        }
        // update all GUI elements
        refreshManager();

        if (!errors.empty()) {
            Glib::ustring txt = _(
                "The following errors occurred while trying to create the dimension on all regions:"
            );
            txt += "\n\n";
            for (std::set<Glib::ustring>::const_iterator it = errors.begin();
                 it != errors.end(); ++it)
            {
                txt += "-> " + *it + "\n";
            }
            txt += "\n";
            txt += _(
                "You might also want to check the console for further warnings and "
                "error messages."
            );
            Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
            msg.run();
        }
    }
}

void DimensionManager::removeDimension() {        
    Glib::RefPtr<Gtk::TreeSelection> sel = treeView.get_selection();
    Gtk::TreeModel::iterator it = sel->get_selected();
    if (it) {
        Gtk::TreeModel::Row row = *it;
        gig::dimension_t type = row[tableModel.m_type];

        // assemble the list of regions where the selected dimension shall be
        // added to
        std::vector<gig::Region*> vRegions;
        if (allRegions()) {
            gig::Instrument* instr = (gig::Instrument*)region->GetParent();
            for (size_t r = 0; gig::Region* rgn = instr->GetRegionAt(r); ++r) {
                if (rgn->GetDimensionDefinition(type)) vRegions.push_back(rgn);
            }
        } else vRegions.push_back(region);

        std::set<Glib::ustring> errors;

        for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) {
            gig::Region* region = vRegions[iRgn];
            gig::dimension_def_t* dim = region->GetDimensionDefinition(type);
            try {
                // notify everybody that we're going to update the region
                region_to_be_changed_signal.emit(region);
                // remove selected dimension    
                region->DeleteDimension(dim);
                // let everybody know there was a change
                region_changed_signal.emit(region);
            } catch (RIFF::Exception e) {
                // notify that the changes are over (i.e. to avoid dead locks)
                region_changed_signal.emit(region);
                Glib::ustring txt = _("Could not remove dimension: ") + e.Message;
                if (vRegions.size() == 1) {
                    // show error message directly
                    Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
                    msg.run();
                } else {
                    // remember error, they are shown after all regions have been processed
                    errors.insert(txt);
                }
            }
        }
        // update all GUI elements
        refreshManager();

        if (!errors.empty()) {
            Glib::ustring txt = _(
                "The following errors occurred while trying to remove the dimension from all regions:"
            );
            txt += "\n\n";
            for (std::set<Glib::ustring>::const_iterator it = errors.begin();
                 it != errors.end(); ++it)
            {
                txt += "-> " + *it + "\n";
            }
            txt += "\n";
            txt += _(
                "You might also want to check the console for further warnings and "
                "error messages."
            );
            Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
            msg.run();
        }
    }
}
