--- gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp 2014/05/14 19:57:56 2553 +++ gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp 2017/07/30 18:57:35 3339 @@ -1,16 +1,17 @@ /* - Copyright (c) 2014 Christian Schoenebeck + Copyright (c) 2014-2017 Christian Schoenebeck This file is part of "gigedit" and released under the terms of the GNU General Public License version 2. */ +#include "global.h" #include "CombineInstrumentsDialog.h" // enable this for debug messages being printed while combining the instruments #define DEBUG_COMBINE_INSTRUMENTS 0 -#include "global.h" +#include "compat.h" #include #include @@ -21,17 +22,18 @@ #include #include #include +#include +#include // for gtk_widget_modify_*() -Glib::ustring gig_to_utf8(const gig::String& gig_string); +Glib::ustring dimTypeAsString(gig::dimension_t d); +typedef std::vector< std::pair > OrderedRegionGroup; typedef std::map RegionGroup; typedef std::map RegionGroups; typedef std::vector DimensionZones; typedef std::map Dimensions; -typedef std::map DimensionCase; - typedef std::map DimensionRegionUpperLimits; typedef std::set Warnings; @@ -93,7 +95,7 @@ * found with a range member point >= iStart */ static int findLowestRegionPoint(std::vector& instruments, int iStart) { - DLS::range_t searchRange = { iStart, 127 }; + DLS::range_t searchRange = { uint16_t(iStart), 127 }; int result = -1; for (uint i = 0; i < instruments.size(); ++i) { gig::Instrument* instr = instruments[i]; @@ -115,7 +117,7 @@ * with a range end >= iStart */ static int findFirstRegionEnd(std::vector& instruments, int iStart) { - DLS::range_t searchRange = { iStart, 127 }; + DLS::range_t searchRange = { uint16_t(iStart), 127 }; int result = -1; for (uint i = 0; i < instruments.size(); ++i) { gig::Instrument* instr = instruments[i]; @@ -168,10 +170,11 @@ /** @brief Identify required regions. * * Takes a list of @a instruments as argument (which are planned to be combined - * as layers in one single new instrument) and fulfills the following tasks: + * as separate dimension zones of a certain dimension into one single new + * instrument) and fulfills the following tasks: * * - 1. Identification of total amount of regions required to create a new - * instrument to become a layered version of the given instruments. + * instrument to become a combined version of the given instruments. * - 2. Precise key range of each of those identified required regions to be * created in that new instrument. * - 3. Grouping the original source regions of the given original instruments @@ -190,7 +193,7 @@ iStart = findLowestRegionPoint(instruments, iStart); if (iStart < 0) break; const int iEnd = findFirstRegionEnd(instruments, iStart); - DLS::range_t range = { iStart, iEnd }; + DLS::range_t range = { uint16_t(iStart), uint16_t(iEnd) }; intersections.push_back(range); iStart = iEnd + 1; } @@ -261,7 +264,7 @@ itNums != it->second.end(); ++itNums) { const int iUpperLimit = *itNums; - DLS::range_t range = { iLow, iUpperLimit }; + DLS::range_t range = { uint16_t(iLow), uint16_t(iUpperLimit) }; dims[type].push_back(range); iLow = iUpperLimit + 1; } @@ -270,13 +273,6 @@ return dims; } -inline int getDimensionIndex(gig::dimension_t type, gig::Region* rgn) { - for (uint i = 0; i < rgn->Dimensions; ++i) - if (rgn->pDimensionDefinitions[i].dimension == type) - return i; - return -1; -} - static void fillDimValues(uint* values/*[8]*/, DimensionCase dimCase, gig::Region* rgn, bool bShouldHaveAllDimensionsPassed) { #if DEBUG_COMBINE_INSTRUMENTS printf("dimvalues = { "); @@ -293,7 +289,7 @@ #endif } #if DEBUG_COMBINE_INSTRUMENTS - printf("\n"); + printf("}\n"); #endif } @@ -318,23 +314,6 @@ } } -/** - * Returns the sum of all bits of all dimensions defined before the given - * dimensions (@a type). This allows to access cases of that particular - * dimension directly. - * - * @param type - dimension that shall be used - * @param rgn - parent region of that dimension - */ -inline int baseBits(gig::dimension_t type, gig::Region* rgn) { - int previousBits = 0; - for (uint i = 0; i < rgn->Dimensions; ++i) { - if (rgn->pDimensionDefinitions[i].dimension == type) break; - previousBits += rgn->pDimensionDefinitions[i].bits; - } - return previousBits; -} - inline int dimensionRegionIndex(gig::DimensionRegion* dimRgn) { gig::Region* rgn = dimRgn->GetParent(); int sz = sizeof(rgn->pDimensionRegions) / sizeof(gig::DimensionRegion*); @@ -367,6 +346,7 @@ const gig::dimension_def_t& def = rgn->pDimensionDefinitions[iDimension]; int iDimRgn = dimensionRegionIndex(dimRgn); int iBaseBits = baseBits(type, rgn); + assert(iBaseBits >= 0); int mask = ~(((1 << def.bits) - 1) << iBaseBits); #if DEBUG_COMBINE_INSTRUMENTS @@ -378,7 +358,7 @@ gig::DimensionRegion* dimRgn2 = rgn->pDimensionRegions[ (iDimRgn & mask) | ( z << iBaseBits) ]; int iHigh = dimRgn2->DimensionUpperLimits[iDimension]; - DLS::range_t range = { iLow, iHigh}; + DLS::range_t range = { uint16_t(iLow), uint16_t(iHigh) }; #if DEBUG_COMBINE_INSTRUMENTS printf("%d..%d, ", iLow, iHigh); fflush(stdout); @@ -400,42 +380,42 @@ }; typedef std::vector CopyAssignSchedule; -/** @brief Copy all DimensionRegions from source Region to target Region. +/** @brief Schedule copying DimensionRegions from source Region to target Region. * - * Copies the entire articulation informations (including sample reference of - * course) from all individual DimensionRegions of source Region @a inRgn to - * target Region @a outRgn. There are no dimension regions created during this - * task. It is expected that the required dimensions (thus the required - * dimension regions) were already created before calling this function. - * - * To be precise, it does the task above only for the layer selected by - * @a iSrcLayer and @a iDstLayer. All dimensions regions of other layers that - * may exist, will not be copied by one single call of this function. So if - * there is a layer dimension, this function needs to be called several times. + * Schedules copying the entire articulation informations (including sample + * reference) from all individual DimensionRegions of source Region @a inRgn to + * target Region @a outRgn. It is expected that the required dimensions (thus + * the required dimension regions) were already created before calling this + * function. + * + * To be precise, it does the task above only for the dimension zones defined by + * the three arguments @a mainDim, @a iSrcMainBit, @a iDstMainBit, which reflect + * a selection which dimension zones shall be copied. All other dimension zones + * will not be scheduled to be copied by a single call of this function. So this + * function needs to be called several time in case all dimension regions shall + * be copied of the entire region (@a inRgn, @a outRgn). * * @param outRgn - where the dimension regions shall be copied to * @param inRgn - all dimension regions that shall be copied from * @param dims - precise dimension definitions of target region - * @param iDstLayer - layer index of destination region where the dimension - * regions shall be copied to - * @param iSrcLayer - layer index of the source region where the dimension - * regions shall be copied from + * @param mainDim - this dimension type, in combination with @a iSrcMainBit and + * @a iDstMainBit defines a selection which dimension region + * zones shall be copied by this call of this function + * @param iDstMainBit - destination bit of @a mainDim + * @param iSrcMainBit - source bit of @a mainDim + * @param schedule - list of all DimensionRegion copy operations which is filled + * during the nested loops / recursions of this function call * @param dimCase - just for internal purpose (function recursion), don't pass * anything here, this function will call itself recursively * will fill this container with concrete dimension values for * selecting the precise dimension regions during its task - * @param schedule - just for internal purpose (function recursion), don't pass - anything here: list of all DimensionRegion copy operations - * which is filled during the nested loops / recursions of - * this function call, they will be peformed after all - * function recursions have been completed */ -static void copyDimensionRegions(gig::Region* outRgn, gig::Region* inRgn, Dimensions dims, int iDstLayer, int iSrcLayer, DimensionCase dimCase = DimensionCase(), CopyAssignSchedule* schedule = NULL) { - const bool isHighestLevelOfRecursion = !schedule; - - if (isHighestLevelOfRecursion) - schedule = new CopyAssignSchedule; - +static void scheduleCopyDimensionRegions(gig::Region* outRgn, gig::Region* inRgn, + Dimensions dims, gig::dimension_t mainDim, + int iDstMainBit, int iSrcMainBit, + CopyAssignSchedule* schedule, + DimensionCase dimCase = DimensionCase()) +{ if (dims.empty()) { // reached deepest level of function recursion ... CopyAssignSchedEntry e; @@ -444,11 +424,12 @@ uint dstDimValues[8] = {}; DimensionCase srcDimCase = dimCase; DimensionCase dstDimCase = dimCase; - srcDimCase[gig::dimension_layer] = iSrcLayer; - dstDimCase[gig::dimension_layer] = iDstLayer; + srcDimCase[mainDim] = iSrcMainBit; + dstDimCase[mainDim] = iDstMainBit; #if DEBUG_COMBINE_INSTRUMENTS printf("-------------------------------\n"); + printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit); #endif // first select source & target dimension region with an arbitrary @@ -463,11 +444,11 @@ #if DEBUG_COMBINE_INSTRUMENTS printf("dst "); fflush(stdout); #endif - fillDimValues(dstDimValues, dstDimCase, outRgn, true); + fillDimValues(dstDimValues, dstDimCase, outRgn, false); gig::DimensionRegion* srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues); gig::DimensionRegion* dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues); #if DEBUG_COMBINE_INSTRUMENTS - printf("iDstLayer=%d iSrcLayer=%d\n", iDstLayer, iSrcLayer); + printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit); printf("srcDimRgn=%lx dstDimRgn=%lx\n", (uint64_t)srcDimRgn, (uint64_t)dstDimRgn); printf("srcSample='%s' dstSample='%s'\n", (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str()), @@ -486,23 +467,26 @@ // re-select target dimension region (with correct velocity zone) DimensionZones dstZones = preciseDimensionZonesFor(gig::dimension_velocity, dstDimRgn); assert(dstZones.size() > 1); - int iZoneIndex = dstDimCase[gig::dimension_velocity]; - e.velocityZone = iZoneIndex; + const int iDstZoneIndex = + (mainDim == gig::dimension_velocity) + ? iDstMainBit : dstDimCase[gig::dimension_velocity]; // (mainDim == gig::dimension_velocity) exception case probably unnecessary here + e.velocityZone = iDstZoneIndex; #if DEBUG_COMBINE_INSTRUMENTS - printf("dst velocity zone: %d/%d\n", iZoneIndex, (int)dstZones.size()); + printf("dst velocity zone: %d/%d\n", iDstZoneIndex, (int)dstZones.size()); #endif - assert(uint(iZoneIndex) < dstZones.size()); - dstDimCase[gig::dimension_velocity] = dstZones[iZoneIndex].low; // arbitrary value between low and high + assert(uint(iDstZoneIndex) < dstZones.size()); + dstDimCase[gig::dimension_velocity] = dstZones[iDstZoneIndex].low; // arbitrary value between low and high #if DEBUG_COMBINE_INSTRUMENTS printf("dst velocity value = %d\n", dstDimCase[gig::dimension_velocity]); printf("dst refilled "); fflush(stdout); #endif - fillDimValues(dstDimValues, dstDimCase, outRgn, true); + fillDimValues(dstDimValues, dstDimCase, outRgn, false); dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues); #if DEBUG_COMBINE_INSTRUMENTS printf("reselected dstDimRgn=%lx\n", (uint64_t)dstDimRgn); - printf("dstSample='%s'\n", - (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()) + printf("dstSample='%s'%s\n", + (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()), + (dstDimRgn->pSample ? " <--- ERROR ERROR ERROR !!!!!!!!! " : "") ); #endif @@ -515,9 +499,12 @@ if (srcZones.size() <= 1) { addWarning("Input region has a velocity dimension with only ONE zone!"); } - if (uint(iZoneIndex) >= srcZones.size()) - iZoneIndex = srcZones.size() - 1; - srcDimCase[gig::dimension_velocity] = srcZones[iZoneIndex].low; // same zone as used above for target dimension region (no matter what the precise zone ranges are) + int iSrcZoneIndex = + (mainDim == gig::dimension_velocity) + ? iSrcMainBit : iDstZoneIndex; + if (uint(iSrcZoneIndex) >= srcZones.size()) + iSrcZoneIndex = srcZones.size() - 1; + srcDimCase[gig::dimension_velocity] = srcZones[iSrcZoneIndex].low; // same zone as used above for target dimension region (no matter what the precise zone ranges are) #if DEBUG_COMBINE_INSTRUMENTS printf("src refilled "); fflush(stdout); #endif @@ -532,7 +519,7 @@ } } - // Schedule copy opertion of source -> target DimensionRegion for the + // Schedule copy operation of source -> target DimensionRegion for the // time after all nested loops have been traversed. We have to postpone // the actual copy operations this way, because otherwise it would // overwrite informations inside the destination DimensionRegion object @@ -563,52 +550,32 @@ dimCase[type] = (def->split_type == gig::split_type_bit) ? iZone : zoneRange.low; // recurse until 'dims' is exhausted (and dimCase filled up with concrete value) - copyDimensionRegions(outRgn, inRgn, dims, iDstLayer, iSrcLayer, dimCase, schedule); + scheduleCopyDimensionRegions(outRgn, inRgn, dims, mainDim, iDstMainBit, iSrcMainBit, schedule, dimCase); } +} - // if current function call is the (very first) entry point ... - if (isHighestLevelOfRecursion) { - // ... then perform all scheduled DimensionRegion copy operations - for (uint i = 0; i < schedule->size(); ++i) { - CopyAssignSchedEntry& e = (*schedule)[i]; - - // backup the target DimensionRegion's current dimension zones upper - // limits (because the target DimensionRegion's upper limits are - // already defined correctly since calling AddDimension(), and the - // CopyAssign() call next, will overwrite those upper limits - // unfortunately - DimensionRegionUpperLimits dstUpperLimits = getDimensionRegionUpperLimits(e.dst); - DimensionRegionUpperLimits srcUpperLimits = getDimensionRegionUpperLimits(e.src); - - // now actually copy over the current DimensionRegion - const gig::Region* const origRgn = e.dst->GetParent(); // just for sanity check below - e.dst->CopyAssign(e.src); - assert(origRgn == e.dst->GetParent()); // if gigedit is crashing here, then you must update libgig (to at least SVN r2547, v3.3.0.svn10) - - // restore all original dimension zone upper limits except of the - // velocity dimension, because the velocity dimension zone sizes are - // allowed to differ for individual DimensionRegions in gig v3 - // format - if (srcUpperLimits.count(gig::dimension_velocity)) { - if (!dstUpperLimits.count(gig::dimension_velocity)) { - addWarning("Source instrument seems to have a velocity dimension whereas new target instrument doesn't!"); - } else { - dstUpperLimits[gig::dimension_velocity] = - (e.velocityZone >= e.totalSrcVelocityZones) - ? 127 : srcUpperLimits[gig::dimension_velocity]; - } - } - restoreDimensionRegionUpperLimits(e.dst, dstUpperLimits); - } - delete schedule; +static OrderedRegionGroup sortRegionGroup(const RegionGroup& group, const std::vector& instruments) { + OrderedRegionGroup result; + for (std::vector::const_iterator it = instruments.begin(); + it != instruments.end(); ++it) + { + RegionGroup::const_iterator itRgn = group.find(*it); + if (itRgn == group.end()) continue; + result.push_back( + std::pair( + itRgn->first, itRgn->second + ) + ); } + return result; } /** @brief Combine given list of instruments to one instrument. * * Takes a list of @a instruments as argument and combines them to one single - * new @a output instrument. For this task, it will create a 'layer' dimension - * in the new instrument and copies the source instruments to those layers. + * new @a output instrument. For this task, it will create a dimension of type + * given by @a mainDimension in the new instrument and copies the source + * instruments to those dimension zones. * * @param instruments - (input) list of instruments that shall be combined, * they will only be read, so they will be left untouched @@ -616,9 +583,11 @@ * be created * @param output - (output) on success this pointer will be set to the new * instrument being created + * @param mainDimension - the dimension that shall be used to combine the + * instruments * @throw RIFF::Exception on any kinds of errors */ -static void combineInstruments(std::vector& instruments, gig::File* gig, gig::Instrument*& output) { +static void combineInstruments(std::vector& instruments, gig::File* gig, gig::Instrument*& output, gig::dimension_t mainDimension) { output = NULL; // divide the individual regions to (probably even smaller) groups of @@ -655,32 +624,36 @@ printf("---> Start target region %d..%d\n", itGroup->first.low, itGroup->first.high); #endif - // detect the total amount of layers required to build up this combi - // for current key range - int iTotalLayers = 0; + // detect the total amount of zones required for the given main + // dimension to build up this combi for current key range + int iTotalZones = 0; for (RegionGroup::iterator itRgn = itGroup->second.begin(); itRgn != itGroup->second.end(); ++itRgn) { gig::Region* inRgn = itRgn->second; - iTotalLayers += inRgn->Layers; + gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension); + iTotalZones += (def) ? def->zones : 1; } #if DEBUG_COMBINE_INSTRUMENTS - printf("Required total layers: %d\n", iTotalLayers); + printf("Required total zones: %d, vertical regions: %d\n", iTotalZones, itGroup->second.size()); #endif - + // create all required dimensions for this output region - // (except the layer dimension, which we create as next step) + // (except the main dimension used for separating the individual + // instruments, we create that particular dimension as next step) Dimensions dims = getDimensionsForRegionGroup(itGroup->second); + // the given main dimension which is used to combine the instruments is + // created separately after the next code block, and the main dimension + // should not be part of dims here, because it also used for iterating + // all dimensions zones, which would lead to this dimensions being + // iterated twice + dims.erase(mainDimension); { std::vector skipTheseDimensions; // used to prevent a misbehavior (i.e. crash) of the combine algorithm in case one of the source instruments has a dimension with only one zone, which is not standard conform for (Dimensions::iterator itDim = dims.begin(); itDim != dims.end(); ++itDim) { - // layer dimension is created separately in the next code block - // (outside of this loop) - if (itDim->first == gig::dimension_layer) continue; - gig::dimension_def_t def; def.dimension = itDim->first; // dimension type def.zones = itDim->second.size(); @@ -711,34 +684,92 @@ dims.erase(skipTheseDimensions[i]); } - // create the layer dimension (if necessary for current key range) - if (iTotalLayers > 1) { + // create the main dimension (if necessary for current key range) + if (iTotalZones > 1) { gig::dimension_def_t def; - def.dimension = gig::dimension_layer; // dimension type - def.zones = iTotalLayers; + def.dimension = mainDimension; // dimension type + def.zones = iTotalZones; def.bits = zoneCountToBits(def.zones); #if DEBUG_COMBINE_INSTRUMENTS - std::cout << "Adding new (layer) dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush; + std::cout << "Adding new main combi dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush; #endif outRgn->AddDimension(&def); #if DEBUG_COMBINE_INSTRUMENTS std::cout << "OK" << std::endl << std::flush; #endif + } else { + dims.erase(mainDimension); } - // now copy the source dimension regions to the target dimension regions - int iDstLayer = 0; - for (RegionGroup::iterator itRgn = itGroup->second.begin(); - itRgn != itGroup->second.end(); ++itRgn) // iterate over 'vertical' / source regions ... + // for the next task we need to have the current RegionGroup to be + // sorted by instrument in the same sequence as the 'instruments' vector + // argument passed to this function (because the std::map behind the + // 'RegionGroup' type sorts by memory address instead, and that would + // sometimes lead to the source instruments' region to be sorted into + // the wrong target layer) + OrderedRegionGroup currentGroup = sortRegionGroup(itGroup->second, instruments); + + // schedule copying the source dimension regions to the target dimension + // regions + CopyAssignSchedule schedule; + int iDstMainBit = 0; + for (OrderedRegionGroup::iterator itRgn = currentGroup.begin(); + itRgn != currentGroup.end(); ++itRgn) // iterate over 'vertical' / source regions ... { gig::Region* inRgn = itRgn->second; #if DEBUG_COMBINE_INSTRUMENTS printf("[source region of '%s']\n", inRgn->GetParent()->pInfo->Name.c_str()); #endif - for (uint iSrcLayer = 0; iSrcLayer < inRgn->Layers; ++iSrcLayer, ++iDstLayer) { - copyDimensionRegions(outRgn, inRgn, dims, iDstLayer, iSrcLayer); + + // determine how many main dimension zones this input region requires + gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension); + const int inRgnMainZones = (def) ? def->zones : 1; + + for (uint iSrcMainBit = 0; iSrcMainBit < inRgnMainZones; ++iSrcMainBit, ++iDstMainBit) { + scheduleCopyDimensionRegions( + outRgn, inRgn, dims, mainDimension, + iDstMainBit, iSrcMainBit, &schedule + ); } } + + // finally copy the scheduled source -> target dimension regions + for (uint i = 0; i < schedule.size(); ++i) { + CopyAssignSchedEntry& e = schedule[i]; + + // backup the target DimensionRegion's current dimension zones upper + // limits (because the target DimensionRegion's upper limits are + // already defined correctly since calling AddDimension(), and the + // CopyAssign() call next, will overwrite those upper limits + // unfortunately + DimensionRegionUpperLimits dstUpperLimits = getDimensionRegionUpperLimits(e.dst); + DimensionRegionUpperLimits srcUpperLimits = getDimensionRegionUpperLimits(e.src); + + // now actually copy over the current DimensionRegion + const gig::Region* const origRgn = e.dst->GetParent(); // just for sanity check below + e.dst->CopyAssign(e.src); + assert(origRgn == e.dst->GetParent()); // if gigedit is crashing here, then you must update libgig (to at least SVN r2547, v3.3.0.svn10) + + // restore all original dimension zone upper limits except of the + // velocity dimension, because the velocity dimension zone sizes are + // allowed to differ for individual DimensionRegions in gig v3 + // format + // + // if the main dinension is the 'velocity' dimension, then skip + // restoring the source's original velocity zone limits, because + // dealing with merging that is not implemented yet + // TODO: merge custom velocity splits if main dimension is the velocity dimension (for now equal sized velocity zones are used if mainDim is 'velocity') + if (srcUpperLimits.count(gig::dimension_velocity) && mainDimension != gig::dimension_velocity) { + if (!dstUpperLimits.count(gig::dimension_velocity)) { + addWarning("Source instrument seems to have a velocity dimension whereas new target instrument doesn't!"); + } else { + dstUpperLimits[gig::dimension_velocity] = + (e.velocityZone >= e.totalSrcVelocityZones) + ? 127 : srcUpperLimits[gig::dimension_velocity]; + } + } + restoreDimensionRegionUpperLimits(e.dst, dstUpperLimits); + } } // success @@ -749,23 +780,62 @@ // class 'CombineInstrumentsDialog' CombineInstrumentsDialog::CombineInstrumentsDialog(Gtk::Window& parent, gig::File* gig) - : Gtk::Dialog(_("Combine Instruments"), parent, true), + : ManagedDialog(_("Combine Instruments"), parent, true), m_gig(gig), m_fileWasChanged(false), m_newCombinedInstrument(NULL), m_cancelButton(Gtk::Stock::CANCEL), m_OKButton(Gtk::Stock::OK), - m_descriptionLabel() + m_descriptionLabel(), m_tableDimCombo(2, 2), m_comboDimType(), + m_labelDimType(Glib::ustring(_("Combine by Dimension:")) + " ", Gtk::ALIGN_END) { + if (!Settings::singleton()->autoRestoreWindowDimension) { + set_default_size(500, 600); + set_position(Gtk::WIN_POS_MOUSE); + } + + m_scrolledWindow.add(m_treeView); + m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + get_vbox()->pack_start(m_descriptionLabel, Gtk::PACK_SHRINK); - get_vbox()->pack_start(m_treeView); + get_vbox()->pack_start(m_tableDimCombo, Gtk::PACK_SHRINK); + get_vbox()->pack_start(m_scrolledWindow); + get_vbox()->pack_start(m_labelOrder, Gtk::PACK_SHRINK); + get_vbox()->pack_start(m_iconView, Gtk::PACK_SHRINK); get_vbox()->pack_start(m_buttonBox, Gtk::PACK_SHRINK); #if GTKMM_MAJOR_VERSION >= 3 - description.set_line_wrap(); + m_descriptionLabel.set_line_wrap(); #endif m_descriptionLabel.set_text(_( - "Select at least two instruments below that shall be combined " - "as layers (using a \"Layer\" dimension) to a new instrument. The " - "original instruments remain untouched.") - ); + "Select at least two instruments below that shall be combined (as " + "separate dimension zones of the selected dimension type) as a new " + "instrument. The original instruments remain untouched.\n\n" + "You may use this tool for example to combine solo instruments into " + "a combi sound arrangement by selecting the 'layer' dimension, or you " + "might combine similar sounding solo sounds into separate velocity " + "split layers by using the 'velocity' dimension, and so on." + )); + + // add dimension type combo box + { + int iLayerDimIndex = -1; + Glib::RefPtr refComboModel = Gtk::ListStore::create(m_comboDimsModel); + for (int i = 0x01, iRow = 0; i < 0xff; i++) { + Glib::ustring sType = + dimTypeAsString(static_cast(i)); + if (sType.find("Unknown") != 0) { + Gtk::TreeModel::Row row = *(refComboModel->append()); + row[m_comboDimsModel.m_type_id] = i; + row[m_comboDimsModel.m_type_name] = sType; + if (i == gig::dimension_layer) iLayerDimIndex = iRow; + iRow++; + } + } + m_comboDimType.set_model(refComboModel); + m_comboDimType.pack_start(m_comboDimsModel.m_type_id); + m_comboDimType.pack_start(m_comboDimsModel.m_type_name); + m_tableDimCombo.attach(m_labelDimType, 0, 1, 0, 1); + m_tableDimCombo.attach(m_comboDimType, 1, 2, 0, 1); + m_comboDimType.set_active(iLayerDimIndex); // preselect "layer" dimension + } m_refTreeModel = Gtk::ListStore::create(m_columns); m_treeView.set_model(m_refTreeModel); @@ -773,8 +843,9 @@ "Use SHIFT + left click or CTRL + left click to select the instruments " "you want to combine." )); - m_treeView.append_column("Instrument", m_columns.m_col_name); - m_treeView.set_headers_visible(false); + m_treeView.append_column(_("Nr"), m_columns.m_col_index); + m_treeView.append_column(_("Instrument"), m_columns.m_col_name); + m_treeView.set_headers_visible(true); m_treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); m_treeView.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &CombineInstrumentsDialog::onSelectionChanged) @@ -799,10 +870,49 @@ Glib::ustring name(gig_to_utf8(instr->pInfo->Name)); Gtk::TreeModel::iterator iter = m_refTreeModel->append(); Gtk::TreeModel::Row row = *iter; + row[m_columns.m_col_index] = i; row[m_columns.m_col_name] = name; row[m_columns.m_col_instr] = instr; } + m_refOrderModel = Gtk::ListStore::create(m_orderColumns); + m_iconView.set_model(m_refOrderModel); + m_iconView.set_tooltip_text(_("Use drag & drop to change the order.")); + m_iconView.set_markup_column(1); + m_iconView.set_selection_mode(Gtk::SELECTION_SINGLE); + // force background to retain white also on selections + // (this also fixes a bug with GTK 2 which often causes visibility issue + // with the text of the selected item) + { + Gdk::Color white; + white.set("#ffffff"); + GtkWidget* widget = (GtkWidget*) m_iconView.gobj(); + gtk_widget_modify_base(widget, GTK_STATE_SELECTED, white.gobj()); + gtk_widget_modify_base(widget, GTK_STATE_ACTIVE, white.gobj()); + gtk_widget_modify_bg(widget, GTK_STATE_SELECTED, white.gobj()); + gtk_widget_modify_bg(widget, GTK_STATE_ACTIVE, white.gobj()); + } + + m_labelOrder.set_text(_("Order of the instruments to be combined:")); + + // establish drag&drop within the instrument tree view, allowing to reorder + // the sequence of instruments within the gig file + { + std::vector drag_target_instrument; + drag_target_instrument.push_back(Gtk::TargetEntry("gig::Instrument")); + m_iconView.drag_source_set(drag_target_instrument); + m_iconView.drag_dest_set(drag_target_instrument); + m_iconView.signal_drag_begin().connect( + sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drag_begin) + ); + m_iconView.signal_drag_data_get().connect( + sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drag_data_get) + ); + m_iconView.signal_drag_data_received().connect( + sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drop_drag_data_received) + ); + } + m_buttonBox.set_layout(Gtk::BUTTONBOX_END); m_buttonBox.set_border_width(5); m_buttonBox.pack_start(m_cancelButton, Gtk::PACK_SHRINK); @@ -834,26 +944,173 @@ Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_WARNING); msg.run(); } + + // OK button should have focus by default for quick combining with Return key + m_OKButton.grab_focus(); +} + +void CombineInstrumentsDialog::on_order_drag_begin(const Glib::RefPtr& context) +{ + printf("Drag begin\n"); + first_call_to_drag_data_get = true; +} + +void CombineInstrumentsDialog::on_order_drag_data_get(const Glib::RefPtr& context, + Gtk::SelectionData& selection_data, guint, guint) +{ + printf("Drag data get\n"); + if (!first_call_to_drag_data_get) return; + first_call_to_drag_data_get = false; + + // get selected source instrument + gig::Instrument* src = NULL; + { + std::vector rows = m_iconView.get_selected_items(); + if (!rows.empty()) { + Gtk::TreeModel::iterator it = m_refOrderModel->get_iter(rows[0]); + if (it) { + Gtk::TreeModel::Row row = *it; + src = row[m_orderColumns.m_col_instr]; + } + } + } + if (!src) { + printf("Drag data get: !src\n"); + return; + } + printf("src=%ld\n", (size_t)src); + + // pass the source gig::Instrument as pointer + selection_data.set(selection_data.get_target(), 0/*unused*/, (const guchar*)&src, + sizeof(src)/*length of data in bytes*/); +} + +void CombineInstrumentsDialog::on_order_drop_drag_data_received( + const Glib::RefPtr& context, int x, int y, + const Gtk::SelectionData& selection_data, guint, guint time) +{ + printf("Drag data received\n"); + if (&selection_data == NULL) { + printf("!selection_data\n"); + return; + } + if (!selection_data.get_data()) { + printf("selection_data.get_data() == NULL\n"); + return; + } + + gig::Instrument* src = *((gig::Instrument**) selection_data.get_data()); + if (!src || selection_data.get_length() != sizeof(gig::Instrument*)) { + printf("!src\n"); + return; + } + printf("src=%d\n", src); + + gig::Instrument* dst = NULL; + { + Gtk::TreeModel::Path path = m_iconView.get_path_at_pos(x, y); + if (!path) return; + + Gtk::TreeModel::iterator iter = m_refOrderModel->get_iter(path); + if (!iter) return; + Gtk::TreeModel::Row row = *iter; + dst = row[m_orderColumns.m_col_instr]; + } + if (!dst) { + printf("!dst\n"); + return; + } + + printf("dragdrop received src='%s' dst='%s'\n", src->pInfo->Name.c_str(), dst->pInfo->Name.c_str()); + + // swap the two items + typedef Gtk::TreeModel::Children Children; + Children children = m_refOrderModel->children(); + Children::iterator itSrc, itDst; + int i = 0, iSrc = -1, iDst = -1; + for (Children::iterator iter = children.begin(); + iter != children.end(); ++iter, ++i) + { + Gtk::TreeModel::Row row = *iter; + if (row[m_orderColumns.m_col_instr] == src) { + itSrc = iter; + iSrc = i; + } else if (row[m_orderColumns.m_col_instr] == dst) { + itDst = iter; + iDst = i; + } + } + if (itSrc && itDst) { + // swap elements + m_refOrderModel->iter_swap(itSrc, itDst); + // update markup + Gtk::TreeModel::Row rowSrc = *itSrc; + Gtk::TreeModel::Row rowDst = *itDst; + { + Glib::ustring name = rowSrc[m_orderColumns.m_col_name]; + Glib::ustring markup = + "" + ToString(iDst+1) + ".\n" + name + ""; + rowSrc[m_orderColumns.m_col_markup] = markup; + } + { + Glib::ustring name = rowDst[m_orderColumns.m_col_name]; + Glib::ustring markup = + "" + ToString(iSrc+1) + ".\n" + name + ""; + rowDst[m_orderColumns.m_col_markup] = markup; + } + } +} + +void CombineInstrumentsDialog::setSelectedInstruments(const std::set& instrumentIndeces) { + typedef Gtk::TreeModel::Children Children; + Children children = m_refTreeModel->children(); + for (Children::iterator iter = children.begin(); + iter != children.end(); ++iter) + { + Gtk::TreeModel::Row row = *iter; + int index = row[m_columns.m_col_index]; + if (instrumentIndeces.count(index)) + m_treeView.get_selection()->select(iter); + } + // hack: OK button lost focus after doing the above, it should have focus by default for quick combining with Return key + m_OKButton.grab_focus(); } void CombineInstrumentsDialog::combineSelectedInstruments() { std::vector instruments; - std::vector v = m_treeView.get_selection()->get_selected_rows(); - for (uint i = 0; i < v.size(); ++i) { - Gtk::TreeModel::iterator it = m_refTreeModel->get_iter(v[i]); - Gtk::TreeModel::Row row = *it; - Glib::ustring name = row[m_columns.m_col_name]; - gig::Instrument* instrument = row[m_columns.m_col_instr]; - #if DEBUG_COMBINE_INSTRUMENTS - printf("Selection '%s' 0x%lx\n\n", name.c_str(), int64_t((void*)instrument)); - #endif - instruments.push_back(instrument); + { + typedef Gtk::TreeModel::Children Children; + int i = 0; + Children selection = m_refOrderModel->children(); + for (Children::iterator it = selection.begin(); + it != selection.end(); ++it, ++i) + { + Gtk::TreeModel::Row row = *it; + Glib::ustring name = row[m_orderColumns.m_col_name]; + gig::Instrument* instrument = row[m_orderColumns.m_col_instr]; + #if DEBUG_COMBINE_INSTRUMENTS + printf("Selection %d. '%s' %p\n\n", (i+1), name.c_str(), instrument)); + #endif + instruments.push_back(instrument); + } } g_warnings.clear(); try { - combineInstruments(instruments, m_gig, m_newCombinedInstrument); + // which main dimension was selected in the combo box? + gig::dimension_t mainDimension; + { + Gtk::TreeModel::iterator iterType = m_comboDimType.get_active(); + if (!iterType) throw gig::Exception("No dimension selected"); + Gtk::TreeModel::Row rowType = *iterType; + if (!rowType) throw gig::Exception("Something is wrong regarding dimension selection"); + int iTypeID = rowType[m_comboDimsModel.m_type_id]; + mainDimension = static_cast(iTypeID); + } + + // now start the actual combination task ... + combineInstruments(instruments, m_gig, m_newCombinedInstrument, mainDimension); } catch (RIFF::Exception e) {; Gtk::MessageDialog msg(*this, e.Message, false, Gtk::MESSAGE_ERROR); msg.run(); @@ -892,6 +1149,71 @@ void CombineInstrumentsDialog::onSelectionChanged() { std::vector v = m_treeView.get_selection()->get_selected_rows(); m_OKButton.set_sensitive(v.size() >= 2); + + typedef Gtk::TreeModel::Children Children; + + // update horizontal selection list (icon view) ... + + // remove items which are not part of the new selection anymore + { + Children allOrdered = m_refOrderModel->children(); + for (Children::iterator itOrder = allOrdered.begin(); + itOrder != allOrdered.end(); ++itOrder) + { + Gtk::TreeModel::Row rowOrder = *itOrder; + gig::Instrument* instr = rowOrder[m_orderColumns.m_col_instr]; + for (uint i = 0; i < v.size(); ++i) { + Gtk::TreeModel::iterator itSel = m_refTreeModel->get_iter(v[i]); + Gtk::TreeModel::Row rowSel = *itSel; + if (rowSel[m_columns.m_col_instr] == instr) + goto nextOrderedItem; + } + goto removeOrderedItem; + nextOrderedItem: + continue; + removeOrderedItem: + m_refOrderModel->erase(itOrder); + } + } + + // add items newly added to the selection + for (uint i = 0; i < v.size(); ++i) { + Gtk::TreeModel::iterator itSel = m_refTreeModel->get_iter(v[i]); + Gtk::TreeModel::Row rowSel = *itSel; + gig::Instrument* instr = rowSel[m_columns.m_col_instr]; + Children allOrdered = m_refOrderModel->children(); + for (Children::iterator itOrder = allOrdered.begin(); + itOrder != allOrdered.end(); ++itOrder) + { + Gtk::TreeModel::Row rowOrder = *itOrder; + if (rowOrder[m_orderColumns.m_col_instr] == instr) + goto nextSelectionItem; + } + goto addNewSelectionItem; + nextSelectionItem: + continue; + addNewSelectionItem: + Glib::ustring name = gig_to_utf8(instr->pInfo->Name); + Gtk::TreeModel::iterator iterOrder = m_refOrderModel->append(); + Gtk::TreeModel::Row rowOrder = *iterOrder; + rowOrder[m_orderColumns.m_col_name] = name; + rowOrder[m_orderColumns.m_col_instr] = instr; + } + + // update markup + { + int i = 0; + Children allOrdered = m_refOrderModel->children(); + for (Children::iterator itOrder = allOrdered.begin(); + itOrder != allOrdered.end(); ++itOrder, ++i) + { + Gtk::TreeModel::Row rowOrder = *itOrder; + Glib::ustring name = rowOrder[m_orderColumns.m_col_name]; + Glib::ustring markup = + "" + ToString(i+1) + ".\n" + name + ""; + rowOrder[m_orderColumns.m_col_markup] = markup; + } + } } bool CombineInstrumentsDialog::fileWasChanged() const {