/[svn]/gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/CombineInstrumentsDialog.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3677 - (show annotations) (download)
Thu Dec 26 23:01:09 2019 UTC (7 months, 1 week ago) by schoenebeck
File size: 53974 byte(s)
* Combine Tool: Fixed user's selection set reduction not being reflected
  correctly as it supposed to.

* Bumped version (1.1.1.svn9).

1 /*
2 Copyright (c) 2014-2019 Christian Schoenebeck
3
4 This file is part of "gigedit" and released under the terms of the
5 GNU General Public License version 2.
6 */
7
8 #include "global.h"
9 #include "CombineInstrumentsDialog.h"
10
11 // enable this for debug messages being printed while combining the instruments
12 #define DEBUG_COMBINE_INSTRUMENTS 0
13
14 #include "compat.h"
15
16 #include <set>
17 #include <iostream>
18 #include <assert.h>
19 #include <stdarg.h>
20 #include <string.h>
21
22 #include <glibmm/ustring.h>
23 #if HAS_GTKMM_STOCK
24 # include <gtkmm/stock.h>
25 #endif
26 #include <gtkmm/messagedialog.h>
27 #include <gtkmm/label.h>
28 #include <gtk/gtkwidget.h> // for gtk_widget_modify_*()
29
30 Glib::ustring dimTypeAsString(gig::dimension_t d);
31
32 typedef std::vector< std::pair<gig::Instrument*, gig::Region*> > OrderedRegionGroup;
33 typedef std::map<gig::Instrument*, gig::Region*> RegionGroup;
34 typedef std::map<DLS::range_t,RegionGroup> RegionGroups;
35
36 typedef std::vector<DLS::range_t> DimensionZones;
37 typedef std::map<gig::dimension_t,DimensionZones> Dimensions;
38
39 typedef std::map<gig::dimension_t, int> DimensionRegionUpperLimits;
40
41 typedef std::set<Glib::ustring> Warnings;
42
43 ///////////////////////////////////////////////////////////////////////////
44 // private static data
45
46 static Warnings g_warnings;
47
48 ///////////////////////////////////////////////////////////////////////////
49 // private functions
50
51 #if DEBUG_COMBINE_INSTRUMENTS
52 static void printRanges(const RegionGroups& regions) {
53 std::cout << "{ ";
54 for (RegionGroups::const_iterator it = regions.begin(); it != regions.end(); ++it) {
55 if (it != regions.begin()) std::cout << ", ";
56 std::cout << (int)it->first.low << ".." << (int)it->first.high;
57 }
58 std::cout << " }" << std::flush;
59 }
60 #endif
61
62 /**
63 * Store a warning message that shall be stored and displayed to the user as a
64 * list of warnings after the overall operation has finished. Duplicate warning
65 * messages are automatically eliminated.
66 */
67 inline void addWarning(const char* fmt, ...) {
68 va_list arg;
69 va_start(arg, fmt);
70 const int SZ = 255 + strlen(fmt);
71 char* buf = new char[SZ];
72 vsnprintf(buf, SZ, fmt, arg);
73 Glib::ustring s = buf;
74 delete [] buf;
75 va_end(arg);
76 std::cerr << _("WARNING:") << " " << s << std::endl << std::flush;
77 g_warnings.insert(s);
78 }
79
80 /**
81 * If the two ranges overlap, then this function returns the smallest point
82 * within that overlapping zone. If the two ranges do not overlap, then this
83 * function will return -1 instead.
84 */
85 inline int smallestOverlapPoint(const DLS::range_t& r1, const DLS::range_t& r2) {
86 if (r1.overlaps(r2.low)) return r2.low;
87 if (r2.overlaps(r1.low)) return r1.low;
88 return -1;
89 }
90
91 /**
92 * Get the most smallest region point (not necessarily its region start point)
93 * of all regions of the given instruments, start searching at keyboard
94 * position @a iStart.
95 *
96 * @returns very first region point >= iStart, or -1 if no region could be
97 * found with a range member point >= iStart
98 */
99 static int findLowestRegionPoint(std::vector<gig::Instrument*>& instruments, int iStart) {
100 DLS::range_t searchRange = { uint16_t(iStart), 127 };
101 int result = -1;
102 for (uint i = 0; i < instruments.size(); ++i) {
103 gig::Instrument* instr = instruments[i];
104 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
105 if (rgn->KeyRange.overlaps(searchRange)) {
106 int lowest = smallestOverlapPoint(rgn->KeyRange, searchRange);
107 if (result == -1 || lowest < result) result = lowest;
108 }
109 }
110 }
111 return result;
112 }
113
114 /**
115 * Get the most smallest region end of all regions of the given instruments,
116 * start searching at keyboard position @a iStart.
117 *
118 * @returns very first region end >= iStart, or -1 if no region could be found
119 * with a range end >= iStart
120 */
121 static int findFirstRegionEnd(std::vector<gig::Instrument*>& instruments, int iStart) {
122 DLS::range_t searchRange = { uint16_t(iStart), 127 };
123 int result = -1;
124 for (uint i = 0; i < instruments.size(); ++i) {
125 gig::Instrument* instr = instruments[i];
126 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
127 if (rgn->KeyRange.overlaps(searchRange)) {
128 if (result == -1 || rgn->KeyRange.high < result)
129 result = rgn->KeyRange.high;
130 }
131 }
132 }
133 return result;
134 }
135
136 /**
137 * Returns a list of all regions of the given @a instrument where the respective
138 * region's key range overlaps the given @a range.
139 */
140 static std::vector<gig::Region*> getAllRegionsWhichOverlapRange(gig::Instrument* instrument, DLS::range_t range) {
141 //std::cout << "All regions which overlap { " << (int)range.low << ".." << (int)range.high << " } : " << std::flush;
142 std::vector<gig::Region*> v;
143 for (gig::Region* rgn = instrument->GetFirstRegion(); rgn; rgn = instrument->GetNextRegion()) {
144 if (rgn->KeyRange.overlaps(range)) {
145 v.push_back(rgn);
146 //std::cout << (int)rgn->KeyRange.low << ".." << (int)rgn->KeyRange.high << ", " << std::flush;
147 }
148 }
149 //std::cout << " END." << std::endl;
150 return v;
151 }
152
153 /**
154 * Returns all regions of the given @a instruments where the respective region's
155 * key range overlaps the given @a range. The regions returned are ordered (in a
156 * map) by their instrument pointer.
157 */
158 static RegionGroup getAllRegionsWhichOverlapRange(std::vector<gig::Instrument*>& instruments, DLS::range_t range) {
159 RegionGroup group;
160 for (uint i = 0; i < instruments.size(); ++i) {
161 gig::Instrument* instr = instruments[i];
162 std::vector<gig::Region*> v = getAllRegionsWhichOverlapRange(instr, range);
163 if (v.empty()) continue;
164 if (v.size() > 1) {
165 addWarning("More than one region found!");
166 }
167 group[instr] = v[0];
168 }
169 return group;
170 }
171
172 /** @brief Identify required regions.
173 *
174 * Takes a list of @a instruments as argument (which are planned to be combined
175 * as separate dimension zones of a certain dimension into one single new
176 * instrument) and fulfills the following tasks:
177 *
178 * - 1. Identification of total amount of regions required to create a new
179 * instrument to become a combined version of the given instruments.
180 * - 2. Precise key range of each of those identified required regions to be
181 * created in that new instrument.
182 * - 3. Grouping the original source regions of the given original instruments
183 * to the respective target key range (new region) of the instrument to be
184 * created.
185 *
186 * @param instruments - list of instruments that are planned to be combined
187 * @returns structured result of the tasks described above
188 */
189 static RegionGroups groupByRegionIntersections(std::vector<gig::Instrument*>& instruments) {
190 RegionGroups groups;
191
192 // find all region intersections of all instruments
193 std::vector<DLS::range_t> intersections;
194 for (int iStart = 0; iStart <= 127; ) {
195 iStart = findLowestRegionPoint(instruments, iStart);
196 if (iStart < 0) break;
197 const int iEnd = findFirstRegionEnd(instruments, iStart);
198 DLS::range_t range = { uint16_t(iStart), uint16_t(iEnd) };
199 intersections.push_back(range);
200 iStart = iEnd + 1;
201 }
202
203 // now sort all regions to those found intersections
204 for (uint i = 0; i < intersections.size(); ++i) {
205 const DLS::range_t& range = intersections[i];
206 RegionGroup group = getAllRegionsWhichOverlapRange(instruments, range);
207 if (!group.empty())
208 groups[range] = group;
209 else
210 addWarning("Empty region group!");
211 }
212
213 return groups;
214 }
215
216 /** @brief Identify required dimensions.
217 *
218 * Takes a planned new region (@a regionGroup) as argument and identifies which
219 * precise dimensions would have to be created for that new region, along with
220 * the amount of dimension zones and their precise individual zone sizes.
221 *
222 * @param regionGroup - planned new region for a new instrument
223 * @returns set of dimensions that shall be created for the given planned region
224 */
225 static Dimensions getDimensionsForRegionGroup(RegionGroup& regionGroup) {
226 std::map<gig::dimension_t, std::set<int> > dimUpperLimits;
227
228 // collect all dimension region zones' upper limits
229 for (RegionGroup::iterator it = regionGroup.begin();
230 it != regionGroup.end(); ++it)
231 {
232 gig::Region* rgn = it->second;
233 int previousBits = 0;
234 for (uint d = 0; d < rgn->Dimensions; ++d) {
235 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[d];
236 for (uint z = 0; z < def.zones; ++z) {
237 int dr = z << previousBits;
238 gig::DimensionRegion* dimRgn = rgn->pDimensionRegions[dr];
239 // Store the individual dimension zone sizes (or actually their
240 // upper limits here) for each dimension.
241 // HACK: Note that the velocity dimension is specially handled
242 // here. Instead of taking over custom velocity split sizes
243 // here, only a bogus number (zone index number) is stored for
244 // each velocity zone, that way only the maxiumum amount of
245 // velocity splits of all regions is stored here, and when their
246 // individual DimensionRegions are finally copied (later), the
247 // individual velocity split size are copied by that.
248 dimUpperLimits[def.dimension].insert(
249 (def.dimension == gig::dimension_velocity) ?
250 z : (def.split_type == gig::split_type_bit) ?
251 ((z+1) * 128/def.zones - 1) : dimRgn->DimensionUpperLimits[dr]
252 );
253 }
254 previousBits += def.bits;
255 }
256 }
257
258 // convert upper limit set to range vector
259 Dimensions dims;
260 for (std::map<gig::dimension_t, std::set<int> >::const_iterator it = dimUpperLimits.begin();
261 it != dimUpperLimits.end(); ++it)
262 {
263 gig::dimension_t type = it->first;
264 int iLow = 0;
265 for (std::set<int>::const_iterator itNums = it->second.begin();
266 itNums != it->second.end(); ++itNums)
267 {
268 const int iUpperLimit = *itNums;
269 DLS::range_t range = { uint16_t(iLow), uint16_t(iUpperLimit) };
270 dims[type].push_back(range);
271 iLow = iUpperLimit + 1;
272 }
273 }
274
275 return dims;
276 }
277
278 static void fillDimValues(uint* values/*[8]*/, DimensionCase dimCase, gig::Region* rgn, bool bShouldHaveAllDimensionsPassed) {
279 #if DEBUG_COMBINE_INSTRUMENTS
280 printf("dimvalues = { ");
281 fflush(stdout);
282 #endif
283 for (DimensionCase::iterator it = dimCase.begin(); it != dimCase.end(); ++it) {
284 gig::dimension_t type = it->first;
285 int iDimIndex = getDimensionIndex(type, rgn);
286 if (bShouldHaveAllDimensionsPassed) assert(iDimIndex >= 0);
287 else if (iDimIndex < 0) continue;
288 values[iDimIndex] = it->second;
289 #if DEBUG_COMBINE_INSTRUMENTS
290 printf("%x=%d, ", type, it->second);
291 #endif
292 }
293 #if DEBUG_COMBINE_INSTRUMENTS
294 printf("}\n");
295 #endif
296 }
297
298 static DimensionRegionUpperLimits getDimensionRegionUpperLimits(gig::DimensionRegion* dimRgn) {
299 DimensionRegionUpperLimits limits;
300 gig::Region* rgn = dimRgn->GetParent();
301 for (uint d = 0; d < rgn->Dimensions; ++d) {
302 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[d];
303 limits[def.dimension] = dimRgn->DimensionUpperLimits[d];
304 }
305 return limits;
306 }
307
308 static void restoreDimensionRegionUpperLimits(gig::DimensionRegion* dimRgn, const DimensionRegionUpperLimits& limits) {
309 gig::Region* rgn = dimRgn->GetParent();
310 for (DimensionRegionUpperLimits::const_iterator it = limits.begin();
311 it != limits.end(); ++it)
312 {
313 int index = getDimensionIndex(it->first, rgn);
314 assert(index >= 0);
315 dimRgn->DimensionUpperLimits[index] = it->second;
316 }
317 }
318
319 inline int dimensionRegionIndex(gig::DimensionRegion* dimRgn) {
320 gig::Region* rgn = dimRgn->GetParent();
321 int sz = sizeof(rgn->pDimensionRegions) / sizeof(gig::DimensionRegion*);
322 for (int i = 0; i < sz; ++i)
323 if (rgn->pDimensionRegions[i] == dimRgn)
324 return i;
325 return -1;
326 }
327
328 /** @brief Get exact zone ranges of given dimension.
329 *
330 * This function is useful for the velocity type dimension. In contrast to other
331 * dimension types, this dimension can have different zone ranges (that is
332 * different individual start and end points of its dimension zones) depending
333 * on which zones of other dimensions (on that gig::Region) are currently
334 * selected.
335 *
336 * @param type - dimension where the zone ranges should be retrieved for
337 * (usually the velocity dimension in this context)
338 * @param dimRgn - reflects the exact cases (zone selections) of all other
339 * dimensions than the given one in question
340 * @returns individual ranges for each zone of the questioned dimension type,
341 * it returns an empty result on errors instead
342 */
343 static DimensionZones preciseDimensionZonesFor(gig::dimension_t type, gig::DimensionRegion* dimRgn) {
344 DimensionZones zones;
345 gig::Region* rgn = dimRgn->GetParent();
346 int iDimension = getDimensionIndex(type, rgn);
347 if (iDimension < 0) return zones;
348 const gig::dimension_def_t& def = rgn->pDimensionDefinitions[iDimension];
349 int iDimRgn = dimensionRegionIndex(dimRgn);
350 int iBaseBits = baseBits(type, rgn);
351 assert(iBaseBits >= 0);
352 int mask = ~(((1 << def.bits) - 1) << iBaseBits);
353
354 #if DEBUG_COMBINE_INSTRUMENTS
355 printf("velo zones { ");
356 fflush(stdout);
357 #endif
358 int iLow = 0;
359 for (int z = 0; z < def.zones; ++z) {
360 gig::DimensionRegion* dimRgn2 =
361 rgn->pDimensionRegions[ (iDimRgn & mask) | ( z << iBaseBits) ];
362 int iHigh = dimRgn2->DimensionUpperLimits[iDimension];
363 DLS::range_t range = { uint16_t(iLow), uint16_t(iHigh) };
364 #if DEBUG_COMBINE_INSTRUMENTS
365 printf("%d..%d, ", iLow, iHigh);
366 fflush(stdout);
367 #endif
368 zones.push_back(range);
369 iLow = iHigh + 1;
370 }
371 #if DEBUG_COMBINE_INSTRUMENTS
372 printf("}\n");
373 #endif
374 return zones;
375 }
376
377 struct CopyAssignSchedEntry {
378 gig::DimensionRegion* src;
379 gig::DimensionRegion* dst;
380 int velocityZone;
381 int totalSrcVelocityZones;
382 };
383 typedef std::vector<CopyAssignSchedEntry> CopyAssignSchedule;
384
385 /** @brief Schedule copying DimensionRegions from source Region to target Region.
386 *
387 * Schedules copying the entire articulation informations (including sample
388 * reference) from all individual DimensionRegions of source Region @a inRgn to
389 * target Region @a outRgn. It is expected that the required dimensions (thus
390 * the required dimension regions) were already created before calling this
391 * function.
392 *
393 * To be precise, it does the task above only for the dimension zones defined by
394 * the three arguments @a mainDim, @a iSrcMainBit, @a iDstMainBit, which reflect
395 * a selection which dimension zones shall be copied. All other dimension zones
396 * will not be scheduled to be copied by a single call of this function. So this
397 * function needs to be called several time in case all dimension regions shall
398 * be copied of the entire region (@a inRgn, @a outRgn).
399 *
400 * @param outRgn - where the dimension regions shall be copied to
401 * @param inRgn - all dimension regions that shall be copied from
402 * @param dims - precise dimension definitions of target region
403 * @param mainDim - this dimension type, in combination with @a iSrcMainBit and
404 * @a iDstMainBit defines a selection which dimension region
405 * zones shall be copied by this call of this function
406 * @param iDstMainBit - destination bit of @a mainDim
407 * @param iSrcMainBit - source bit of @a mainDim
408 * @param schedule - list of all DimensionRegion copy operations which is filled
409 * during the nested loops / recursions of this function call
410 * @param dimCase - just for internal purpose (function recursion), don't pass
411 * anything here, this function will call itself recursively
412 * will fill this container with concrete dimension values for
413 * selecting the precise dimension regions during its task
414 */
415 static void scheduleCopyDimensionRegions(gig::Region* outRgn, gig::Region* inRgn,
416 Dimensions dims, gig::dimension_t mainDim,
417 int iDstMainBit, int iSrcMainBit,
418 CopyAssignSchedule* schedule,
419 DimensionCase dimCase = DimensionCase())
420 {
421 if (dims.empty()) { // reached deepest level of function recursion ...
422 CopyAssignSchedEntry e;
423
424 // resolve the respective source & destination DimensionRegion ...
425 uint srcDimValues[8] = {};
426 uint dstDimValues[8] = {};
427 DimensionCase srcDimCase = dimCase;
428 DimensionCase dstDimCase = dimCase;
429 srcDimCase[mainDim] = iSrcMainBit;
430 dstDimCase[mainDim] = iDstMainBit;
431
432 #if DEBUG_COMBINE_INSTRUMENTS
433 printf("-------------------------------\n");
434 printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit);
435 #endif
436
437 // first select source & target dimension region with an arbitrary
438 // velocity split zone, to get access to the precise individual velocity
439 // split zone sizes (if there is actually a velocity dimension at all,
440 // otherwise we already select the desired source & target dimension
441 // region here)
442 #if DEBUG_COMBINE_INSTRUMENTS
443 printf("src "); fflush(stdout);
444 #endif
445 fillDimValues(srcDimValues, srcDimCase, inRgn, false);
446 #if DEBUG_COMBINE_INSTRUMENTS
447 printf("dst "); fflush(stdout);
448 #endif
449 fillDimValues(dstDimValues, dstDimCase, outRgn, false);
450 gig::DimensionRegion* srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues);
451 gig::DimensionRegion* dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues);
452 #if DEBUG_COMBINE_INSTRUMENTS
453 printf("iDstMainBit=%d iSrcMainBit=%d\n", iDstMainBit, iSrcMainBit);
454 printf("srcDimRgn=%lx dstDimRgn=%lx\n", (uint64_t)srcDimRgn, (uint64_t)dstDimRgn);
455 printf("srcSample='%s' dstSample='%s'\n",
456 (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str()),
457 (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str())
458 );
459 #endif
460
461 assert(srcDimRgn->GetParent() == inRgn);
462 assert(dstDimRgn->GetParent() == outRgn);
463
464 // now that we have access to the precise velocity split zone upper
465 // limits, we can select the actual source & destination dimension
466 // regions we need to copy (assuming that source or target region has
467 // a velocity dimension)
468 if (outRgn->GetDimensionDefinition(gig::dimension_velocity)) {
469 // re-select target dimension region (with correct velocity zone)
470 DimensionZones dstZones = preciseDimensionZonesFor(gig::dimension_velocity, dstDimRgn);
471 assert(dstZones.size() > 1);
472 const int iDstZoneIndex =
473 (mainDim == gig::dimension_velocity)
474 ? iDstMainBit : dstDimCase[gig::dimension_velocity]; // (mainDim == gig::dimension_velocity) exception case probably unnecessary here
475 e.velocityZone = iDstZoneIndex;
476 #if DEBUG_COMBINE_INSTRUMENTS
477 printf("dst velocity zone: %d/%d\n", iDstZoneIndex, (int)dstZones.size());
478 #endif
479 assert(uint(iDstZoneIndex) < dstZones.size());
480 dstDimCase[gig::dimension_velocity] = dstZones[iDstZoneIndex].low; // arbitrary value between low and high
481 #if DEBUG_COMBINE_INSTRUMENTS
482 printf("dst velocity value = %d\n", dstDimCase[gig::dimension_velocity]);
483 printf("dst refilled "); fflush(stdout);
484 #endif
485 fillDimValues(dstDimValues, dstDimCase, outRgn, false);
486 dstDimRgn = outRgn->GetDimensionRegionByValue(dstDimValues);
487 #if DEBUG_COMBINE_INSTRUMENTS
488 printf("reselected dstDimRgn=%lx\n", (uint64_t)dstDimRgn);
489 printf("dstSample='%s'%s\n",
490 (!dstDimRgn->pSample ? "NULL" : dstDimRgn->pSample->pInfo->Name.c_str()),
491 (dstDimRgn->pSample ? " <--- ERROR ERROR ERROR !!!!!!!!! " : "")
492 );
493 #endif
494
495 // re-select source dimension region with correct velocity zone
496 // (if it has a velocity dimension that is)
497 if (inRgn->GetDimensionDefinition(gig::dimension_velocity)) {
498 DimensionZones srcZones = preciseDimensionZonesFor(gig::dimension_velocity, srcDimRgn);
499 e.totalSrcVelocityZones = srcZones.size();
500 assert(srcZones.size() > 0);
501 if (srcZones.size() <= 1) {
502 addWarning("Input region has a velocity dimension with only ONE zone!");
503 }
504 int iSrcZoneIndex =
505 (mainDim == gig::dimension_velocity)
506 ? iSrcMainBit : iDstZoneIndex;
507 if (uint(iSrcZoneIndex) >= srcZones.size())
508 iSrcZoneIndex = srcZones.size() - 1;
509 srcDimCase[gig::dimension_velocity] = srcZones[iSrcZoneIndex].low; // same zone as used above for target dimension region (no matter what the precise zone ranges are)
510 #if DEBUG_COMBINE_INSTRUMENTS
511 printf("src refilled "); fflush(stdout);
512 #endif
513 fillDimValues(srcDimValues, srcDimCase, inRgn, false);
514 srcDimRgn = inRgn->GetDimensionRegionByValue(srcDimValues);
515 #if DEBUG_COMBINE_INSTRUMENTS
516 printf("reselected srcDimRgn=%lx\n", (uint64_t)srcDimRgn);
517 printf("srcSample='%s'\n",
518 (!srcDimRgn->pSample ? "NULL" : srcDimRgn->pSample->pInfo->Name.c_str())
519 );
520 #endif
521 }
522 }
523
524 // Schedule copy operation of source -> target DimensionRegion for the
525 // time after all nested loops have been traversed. We have to postpone
526 // the actual copy operations this way, because otherwise it would
527 // overwrite informations inside the destination DimensionRegion object
528 // that we need to read in the code block above.
529 e.src = srcDimRgn;
530 e.dst = dstDimRgn;
531 schedule->push_back(e);
532
533 return; // returning from deepest level of function recursion
534 }
535
536 // Copying n dimensions requires n nested loops. That's why this function
537 // is calling itself recursively to provide the required amount of nested
538 // loops. With each call it pops from argument 'dims' and pushes to
539 // argument 'dimCase'.
540
541 Dimensions::iterator itDimension = dims.begin();
542 gig::dimension_t type = itDimension->first;
543 DimensionZones zones = itDimension->second;
544 dims.erase(itDimension);
545
546 int iZone = 0;
547 for (DimensionZones::iterator itZone = zones.begin();
548 itZone != zones.end(); ++itZone, ++iZone)
549 {
550 DLS::range_t zoneRange = *itZone;
551 gig::dimension_def_t* def = outRgn->GetDimensionDefinition(type);
552 dimCase[type] = (def->split_type == gig::split_type_bit) ? iZone : zoneRange.low;
553
554 // recurse until 'dims' is exhausted (and dimCase filled up with concrete value)
555 scheduleCopyDimensionRegions(outRgn, inRgn, dims, mainDim, iDstMainBit, iSrcMainBit, schedule, dimCase);
556 }
557 }
558
559 static OrderedRegionGroup sortRegionGroup(const RegionGroup& group, const std::vector<gig::Instrument*>& instruments) {
560 OrderedRegionGroup result;
561 for (std::vector<gig::Instrument*>::const_iterator it = instruments.begin();
562 it != instruments.end(); ++it)
563 {
564 RegionGroup::const_iterator itRgn = group.find(*it);
565 if (itRgn == group.end()) continue;
566 result.push_back(
567 std::pair<gig::Instrument*, gig::Region*>(
568 itRgn->first, itRgn->second
569 )
570 );
571 }
572 return result;
573 }
574
575 /** @brief Combine given list of instruments to one instrument.
576 *
577 * Takes a list of @a instruments as argument and combines them to one single
578 * new @a output instrument. For this task, it will create a dimension of type
579 * given by @a mainDimension in the new instrument and copies the source
580 * instruments to those dimension zones.
581 *
582 * @param instruments - (input) list of instruments that shall be combined,
583 * they will only be read, so they will be left untouched
584 * @param gig - (input/output) .gig file where the new combined instrument shall
585 * be created
586 * @param output - (output) on success this pointer will be set to the new
587 * instrument being created
588 * @param mainDimension - the dimension that shall be used to combine the
589 * instruments
590 * @throw RIFF::Exception on any kinds of errors
591 */
592 static void combineInstruments(std::vector<gig::Instrument*>& instruments, gig::File* gig, gig::Instrument*& output, gig::dimension_t mainDimension) {
593 output = NULL;
594
595 // divide the individual regions to (probably even smaller) groups of
596 // regions, coping with the fact that the source regions of the instruments
597 // might have quite different range sizes and start and end points
598 RegionGroups groups = groupByRegionIntersections(instruments);
599 #if DEBUG_COMBINE_INSTRUMENTS
600 std::cout << std::endl << "New regions: " << std::flush;
601 printRanges(groups);
602 std::cout << std::endl;
603 #endif
604
605 if (groups.empty())
606 throw gig::Exception(_("No regions found to create a new instrument with."));
607
608 // create a new output instrument
609 gig::Instrument* outInstr = gig->AddInstrument();
610 outInstr->pInfo->Name = _("NEW COMBINATION");
611
612 // Distinguishing in the following code block between 'horizontal' and
613 // 'vertical' regions. The 'horizontal' ones are meant to be the key ranges
614 // in the output instrument, while the 'vertical' regions are meant to be
615 // the set of source regions that shall be layered to that 'horizontal'
616 // region / key range. It is important to know, that the key ranges defined
617 // in the 'horizontal' and 'vertical' regions might differ.
618
619 // merge the instruments to the new output instrument
620 for (RegionGroups::iterator itGroup = groups.begin();
621 itGroup != groups.end(); ++itGroup) // iterate over 'horizontal' / target regions ...
622 {
623 gig::Region* outRgn = outInstr->AddRegion();
624 outRgn->SetKeyRange(itGroup->first.low, itGroup->first.high);
625 #if DEBUG_COMBINE_INSTRUMENTS
626 printf("---> Start target region %d..%d\n", itGroup->first.low, itGroup->first.high);
627 #endif
628
629 // detect the total amount of zones required for the given main
630 // dimension to build up this combi for current key range
631 int iTotalZones = 0;
632 for (RegionGroup::iterator itRgn = itGroup->second.begin();
633 itRgn != itGroup->second.end(); ++itRgn)
634 {
635 gig::Region* inRgn = itRgn->second;
636 gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension);
637 iTotalZones += (def) ? def->zones : 1;
638 }
639 #if DEBUG_COMBINE_INSTRUMENTS
640 printf("Required total zones: %d, vertical regions: %d\n", iTotalZones, itGroup->second.size());
641 #endif
642
643 // create all required dimensions for this output region
644 // (except the main dimension used for separating the individual
645 // instruments, we create that particular dimension as next step)
646 Dimensions dims = getDimensionsForRegionGroup(itGroup->second);
647 // the given main dimension which is used to combine the instruments is
648 // created separately after the next code block, and the main dimension
649 // should not be part of dims here, because it also used for iterating
650 // all dimensions zones, which would lead to this dimensions being
651 // iterated twice
652 dims.erase(mainDimension);
653 {
654 std::vector<gig::dimension_t> 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
655
656 for (Dimensions::iterator itDim = dims.begin();
657 itDim != dims.end(); ++itDim)
658 {
659 gig::dimension_def_t def;
660 def.dimension = itDim->first; // dimension type
661 def.zones = itDim->second.size();
662 def.bits = zoneCountToBits(def.zones);
663 if (def.zones < 2) {
664 addWarning(
665 "Attempt to create dimension with type=0x%x with only "
666 "ONE zone (because at least one of the source "
667 "instruments seems to have such a velocity dimension "
668 "with only ONE zone, which is odd)! Skipping this "
669 "dimension for now.",
670 (int)itDim->first
671 );
672 skipTheseDimensions.push_back(itDim->first);
673 continue;
674 }
675 #if DEBUG_COMBINE_INSTRUMENTS
676 std::cout << "Adding new regular dimension type=" << std::hex << (int)def.dimension << std::dec << ", zones=" << (int)def.zones << ", bits=" << (int)def.bits << " ... " << std::flush;
677 #endif
678 outRgn->AddDimension(&def);
679 #if DEBUG_COMBINE_INSTRUMENTS
680 std::cout << "OK" << std::endl << std::flush;
681 #endif
682 }
683 // prevent the following dimensions to be processed further below
684 // (since the respective dimension was not created above)
685 for (int i = 0; i < skipTheseDimensions.size(); ++i)
686 dims.erase(skipTheseDimensions[i]);
687 }
688
689 // create the main dimension (if necessary for current key range)
690 if (iTotalZones > 1) {
691 gig::dimension_def_t def;
692 def.dimension = mainDimension; // dimension type
693 def.zones = iTotalZones;
694 def.bits = zoneCountToBits(def.zones);
695 #if DEBUG_COMBINE_INSTRUMENTS
696 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;
697 #endif
698 outRgn->AddDimension(&def);
699 #if DEBUG_COMBINE_INSTRUMENTS
700 std::cout << "OK" << std::endl << std::flush;
701 #endif
702 } else {
703 dims.erase(mainDimension);
704 }
705
706 // for the next task we need to have the current RegionGroup to be
707 // sorted by instrument in the same sequence as the 'instruments' vector
708 // argument passed to this function (because the std::map behind the
709 // 'RegionGroup' type sorts by memory address instead, and that would
710 // sometimes lead to the source instruments' region to be sorted into
711 // the wrong target layer)
712 OrderedRegionGroup currentGroup = sortRegionGroup(itGroup->second, instruments);
713
714 // schedule copying the source dimension regions to the target dimension
715 // regions
716 CopyAssignSchedule schedule;
717 int iDstMainBit = 0;
718 for (OrderedRegionGroup::iterator itRgn = currentGroup.begin();
719 itRgn != currentGroup.end(); ++itRgn) // iterate over 'vertical' / source regions ...
720 {
721 gig::Region* inRgn = itRgn->second;
722 #if DEBUG_COMBINE_INSTRUMENTS
723 printf("[source region of '%s']\n", inRgn->GetParent()->pInfo->Name.c_str());
724 #endif
725
726 // determine how many main dimension zones this input region requires
727 gig::dimension_def_t* def = inRgn->GetDimensionDefinition(mainDimension);
728 const int inRgnMainZones = (def) ? def->zones : 1;
729
730 for (uint iSrcMainBit = 0; iSrcMainBit < inRgnMainZones; ++iSrcMainBit, ++iDstMainBit) {
731 scheduleCopyDimensionRegions(
732 outRgn, inRgn, dims, mainDimension,
733 iDstMainBit, iSrcMainBit, &schedule
734 );
735 }
736 }
737
738 // finally copy the scheduled source -> target dimension regions
739 for (uint i = 0; i < schedule.size(); ++i) {
740 CopyAssignSchedEntry& e = schedule[i];
741
742 // backup the target DimensionRegion's current dimension zones upper
743 // limits (because the target DimensionRegion's upper limits are
744 // already defined correctly since calling AddDimension(), and the
745 // CopyAssign() call next, will overwrite those upper limits
746 // unfortunately
747 DimensionRegionUpperLimits dstUpperLimits = getDimensionRegionUpperLimits(e.dst);
748 DimensionRegionUpperLimits srcUpperLimits = getDimensionRegionUpperLimits(e.src);
749
750 // now actually copy over the current DimensionRegion
751 const gig::Region* const origRgn = e.dst->GetParent(); // just for sanity check below
752 e.dst->CopyAssign(e.src);
753 assert(origRgn == e.dst->GetParent()); // if gigedit is crashing here, then you must update libgig (to at least SVN r2547, v3.3.0.svn10)
754
755 // restore all original dimension zone upper limits except of the
756 // velocity dimension, because the velocity dimension zone sizes are
757 // allowed to differ for individual DimensionRegions in gig v3
758 // format
759 //
760 // if the main dinension is the 'velocity' dimension, then skip
761 // restoring the source's original velocity zone limits, because
762 // dealing with merging that is not implemented yet
763 // TODO: merge custom velocity splits if main dimension is the velocity dimension (for now equal sized velocity zones are used if mainDim is 'velocity')
764 if (srcUpperLimits.count(gig::dimension_velocity) && mainDimension != gig::dimension_velocity) {
765 if (!dstUpperLimits.count(gig::dimension_velocity)) {
766 addWarning("Source instrument seems to have a velocity dimension whereas new target instrument doesn't!");
767 } else {
768 dstUpperLimits[gig::dimension_velocity] =
769 (e.velocityZone >= e.totalSrcVelocityZones)
770 ? 127 : srcUpperLimits[gig::dimension_velocity];
771 }
772 }
773 restoreDimensionRegionUpperLimits(e.dst, dstUpperLimits);
774 }
775 }
776
777 // success
778 output = outInstr;
779 }
780
781 ///////////////////////////////////////////////////////////////////////////
782 // class 'CombineInstrumentsDialog'
783
784 CombineInstrumentsDialog::CombineInstrumentsDialog(Gtk::Window& parent, gig::File* gig)
785 : ManagedDialog(_("Combine Instruments"), parent, true),
786 m_gig(gig), m_fileWasChanged(false), m_newCombinedInstrument(NULL),
787 #if HAS_GTKMM_STOCK
788 m_cancelButton(Gtk::Stock::CANCEL), m_OKButton(Gtk::Stock::OK),
789 #else
790 m_cancelButton(_("_Cancel"), true), m_OKButton(_("_OK"), true),
791 #endif
792 m_descriptionLabel(),
793 #if USE_GTKMM_GRID
794 m_tableDimCombo(),
795 #else
796 m_tableDimCombo(2, 2),
797 #endif
798 m_comboDimType(),
799 m_labelDimType(Glib::ustring(_("Combine by Dimension:")) + " ", Gtk::ALIGN_END)
800 {
801 if (!Settings::singleton()->autoRestoreWindowDimension) {
802 set_default_size(500, 600);
803 set_position(Gtk::WIN_POS_MOUSE);
804 }
805
806 m_scrolledWindow.add(m_treeView);
807 m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
808
809 #if USE_GTKMM_BOX
810 get_content_area()->pack_start(m_descriptionLabel, Gtk::PACK_SHRINK);
811 get_content_area()->pack_start(m_tableDimCombo, Gtk::PACK_SHRINK);
812 get_content_area()->pack_start(m_scrolledWindow);
813 get_content_area()->pack_start(m_labelOrder, Gtk::PACK_SHRINK);
814 get_content_area()->pack_start(m_iconView, Gtk::PACK_SHRINK);
815 get_content_area()->pack_start(m_buttonBox, Gtk::PACK_SHRINK);
816 #else
817 get_vbox()->pack_start(m_descriptionLabel, Gtk::PACK_SHRINK);
818 get_vbox()->pack_start(m_tableDimCombo, Gtk::PACK_SHRINK);
819 get_vbox()->pack_start(m_scrolledWindow);
820 get_vbox()->pack_start(m_labelOrder, Gtk::PACK_SHRINK);
821 get_vbox()->pack_start(m_iconView, Gtk::PACK_SHRINK);
822 get_vbox()->pack_start(m_buttonBox, Gtk::PACK_SHRINK);
823 #endif
824
825 #if GTKMM_MAJOR_VERSION >= 3
826 m_descriptionLabel.set_line_wrap();
827 #endif
828 m_descriptionLabel.set_text(_(
829 "Select at least two instruments below that shall be combined (as "
830 "separate dimension zones of the selected dimension type) as a new "
831 "instrument. The original instruments remain untouched.\n\n"
832 "You may use this tool for example to combine solo instruments into "
833 "a combi sound arrangement by selecting the 'layer' dimension, or you "
834 "might combine similar sounding solo sounds into separate velocity "
835 "split layers by using the 'velocity' dimension, and so on."
836 ));
837
838 // add dimension type combo box
839 {
840 int iLayerDimIndex = -1;
841 Glib::RefPtr<Gtk::ListStore> refComboModel = Gtk::ListStore::create(m_comboDimsModel);
842 for (int i = 0x01, iRow = 0; i < 0xff; i++) {
843 Glib::ustring sType =
844 dimTypeAsString(static_cast<gig::dimension_t>(i));
845 if (sType.find("Unknown") != 0) {
846 Gtk::TreeModel::Row row = *(refComboModel->append());
847 row[m_comboDimsModel.m_type_id] = i;
848 row[m_comboDimsModel.m_type_name] = sType;
849 if (i == gig::dimension_layer) iLayerDimIndex = iRow;
850 iRow++;
851 }
852 }
853 m_comboDimType.set_model(refComboModel);
854 m_comboDimType.pack_start(m_comboDimsModel.m_type_id);
855 m_comboDimType.pack_start(m_comboDimsModel.m_type_name);
856 m_tableDimCombo.attach(m_labelDimType, 0, 1, 0, 1);
857 m_tableDimCombo.attach(m_comboDimType, 1, 2, 0, 1);
858 m_comboDimType.set_active(iLayerDimIndex); // preselect "layer" dimension
859 }
860
861 m_refTreeModel = Gtk::ListStore::create(m_columns);
862 m_treeView.set_model(m_refTreeModel);
863 m_treeView.set_tooltip_text(_(
864 "Use SHIFT + left click or CTRL + left click to select the instruments "
865 "you want to combine."
866 ));
867 m_treeView.append_column(_("Nr"), m_columns.m_col_index);
868 m_treeView.append_column(_("Instrument"), m_columns.m_col_name);
869 m_treeView.set_headers_visible(true);
870 m_treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
871 m_treeView.get_selection()->signal_changed().connect(
872 sigc::mem_fun(*this, &CombineInstrumentsDialog::onSelectionChanged)
873 );
874 m_treeView.show();
875
876 for (int i = 0; true; ++i) {
877 gig::Instrument* instr = gig->GetInstrument(i);
878 if (!instr) break;
879
880 #if DEBUG_COMBINE_INSTRUMENTS
881 {
882 std::cout << "Instrument (" << i << ") '" << instr->pInfo->Name << "' Regions: " << std::flush;
883 for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) {
884 std::cout << rgn->KeyRange.low << ".." << rgn->KeyRange.high << ", " << std::flush;
885 }
886 std::cout << std::endl;
887 }
888 std::cout << std::endl;
889 #endif
890
891 Glib::ustring name(gig_to_utf8(instr->pInfo->Name));
892 Gtk::TreeModel::iterator iter = m_refTreeModel->append();
893 Gtk::TreeModel::Row row = *iter;
894 row[m_columns.m_col_index] = i;
895 row[m_columns.m_col_name] = name;
896 row[m_columns.m_col_instr] = instr;
897 }
898
899 m_refOrderModel = Gtk::ListStore::create(m_orderColumns);
900 m_iconView.set_model(m_refOrderModel);
901 m_iconView.set_tooltip_text(_("Use drag & drop to change the order."));
902 m_iconView.set_markup_column(1);
903 m_iconView.set_selection_mode(Gtk::SELECTION_SINGLE);
904 // force background to retain white also on selections
905 // (this also fixes a bug with GTK 2 which often causes visibility issue
906 // with the text of the selected item)
907 {
908 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
909 Gdk::Color white;
910 #else
911 Gdk::RGBA white;
912 #endif
913 white.set("#ffffff");
914 GtkWidget* widget = (GtkWidget*) m_iconView.gobj();
915 #if GTK_MAJOR_VERSION < 3
916 gtk_widget_modify_base(widget, GTK_STATE_SELECTED, white.gobj());
917 gtk_widget_modify_base(widget, GTK_STATE_ACTIVE, white.gobj());
918 gtk_widget_modify_bg(widget, GTK_STATE_SELECTED, white.gobj());
919 gtk_widget_modify_bg(widget, GTK_STATE_ACTIVE, white.gobj());
920 #endif
921 }
922
923 m_labelOrder.set_text(_("Order of the instruments to be combined:"));
924
925 // establish drag&drop within the instrument tree view, allowing to reorder
926 // the sequence of instruments within the gig file
927 {
928 std::vector<Gtk::TargetEntry> drag_target_instrument;
929 drag_target_instrument.push_back(Gtk::TargetEntry("gig::Instrument"));
930 m_iconView.drag_source_set(drag_target_instrument);
931 m_iconView.drag_dest_set(drag_target_instrument);
932 m_iconView.signal_drag_begin().connect(
933 sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drag_begin)
934 );
935 m_iconView.signal_drag_data_get().connect(
936 sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drag_data_get)
937 );
938 m_iconView.signal_drag_data_received().connect(
939 sigc::mem_fun(*this, &CombineInstrumentsDialog::on_order_drop_drag_data_received)
940 );
941 }
942
943 m_buttonBox.set_layout(Gtk::BUTTONBOX_END);
944 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
945 m_buttonBox.set_margin(5);
946 #else
947 m_buttonBox.set_border_width(5);
948 #endif
949 m_buttonBox.pack_start(m_cancelButton, Gtk::PACK_SHRINK);
950 m_buttonBox.pack_start(m_OKButton, Gtk::PACK_SHRINK);
951 m_buttonBox.show();
952
953 m_cancelButton.show();
954 m_OKButton.set_sensitive(false);
955 m_OKButton.show();
956
957 m_cancelButton.signal_clicked().connect(
958 sigc::mem_fun(*this, &CombineInstrumentsDialog::hide)
959 );
960
961 m_OKButton.signal_clicked().connect(
962 sigc::mem_fun(*this, &CombineInstrumentsDialog::combineSelectedInstruments)
963 );
964
965 #if HAS_GTKMM_SHOW_ALL_CHILDREN
966 show_all_children();
967 #endif
968
969 Settings::singleton()->showTooltips.get_proxy().signal_changed().connect(
970 sigc::mem_fun(*this, &CombineInstrumentsDialog::on_show_tooltips_changed)
971 );
972 on_show_tooltips_changed();
973
974 // show a warning to user if he uses a .gig in v2 format
975 if (gig->pVersion->major < 3) {
976 Glib::ustring txt = _(
977 "You are currently using a .gig file in old v2 format. The current "
978 "combine algorithm will most probably fail trying to combine "
979 "instruments in this old format. So better save the file in new v3 "
980 "format before trying to combine your instruments."
981 );
982 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_WARNING);
983 msg.run();
984 }
985
986 // OK button should have focus by default for quick combining with Return key
987 m_OKButton.grab_focus();
988 }
989
990 void CombineInstrumentsDialog::on_order_drag_begin(const Glib::RefPtr<Gdk::DragContext>& context)
991 {
992 printf("Drag begin\n");
993 first_call_to_drag_data_get = true;
994 }
995
996 void CombineInstrumentsDialog::on_order_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context,
997 Gtk::SelectionData& selection_data, guint, guint)
998 {
999 printf("Drag data get\n");
1000 if (!first_call_to_drag_data_get) return;
1001 first_call_to_drag_data_get = false;
1002
1003 // get selected source instrument
1004 gig::Instrument* src = NULL;
1005 {
1006 std::vector<Gtk::TreeModel::Path> rows = m_iconView.get_selected_items();
1007 if (!rows.empty()) {
1008 Gtk::TreeModel::iterator it = m_refOrderModel->get_iter(rows[0]);
1009 if (it) {
1010 Gtk::TreeModel::Row row = *it;
1011 src = row[m_orderColumns.m_col_instr];
1012 }
1013 }
1014 }
1015 if (!src) {
1016 printf("Drag data get: !src\n");
1017 return;
1018 }
1019 printf("src=%ld\n", (size_t)src);
1020
1021 // pass the source gig::Instrument as pointer
1022 selection_data.set(selection_data.get_target(), 0/*unused*/, (const guchar*)&src,
1023 sizeof(src)/*length of data in bytes*/);
1024 }
1025
1026 void CombineInstrumentsDialog::on_order_drop_drag_data_received(
1027 const Glib::RefPtr<Gdk::DragContext>& context, int x, int y,
1028 const Gtk::SelectionData& selection_data, guint, guint time)
1029 {
1030 printf("Drag data received\n");
1031 if (!selection_data.get_data()) {
1032 printf("selection_data.get_data() == NULL\n");
1033 return;
1034 }
1035
1036 gig::Instrument* src = *((gig::Instrument**) selection_data.get_data());
1037 if (!src || selection_data.get_length() != sizeof(gig::Instrument*)) {
1038 printf("!src\n");
1039 return;
1040 }
1041 printf("src=%ld\n", (size_t)src);
1042
1043 gig::Instrument* dst = NULL;
1044 {
1045 Gtk::TreeModel::Path path = m_iconView.get_path_at_pos(x, y);
1046 if (!path) return;
1047
1048 Gtk::TreeModel::iterator iter = m_refOrderModel->get_iter(path);
1049 if (!iter) return;
1050 Gtk::TreeModel::Row row = *iter;
1051 dst = row[m_orderColumns.m_col_instr];
1052 }
1053 if (!dst) {
1054 printf("!dst\n");
1055 return;
1056 }
1057
1058 printf("dragdrop received src='%s' dst='%s'\n", src->pInfo->Name.c_str(), dst->pInfo->Name.c_str());
1059
1060 // swap the two items
1061 typedef Gtk::TreeModel::Children Children;
1062 Children children = m_refOrderModel->children();
1063 Children::iterator itSrc, itDst;
1064 int i = 0, iSrc = -1, iDst = -1;
1065 for (Children::iterator iter = children.begin();
1066 iter != children.end(); ++iter, ++i)
1067 {
1068 Gtk::TreeModel::Row row = *iter;
1069 if (row[m_orderColumns.m_col_instr] == src) {
1070 itSrc = iter;
1071 iSrc = i;
1072 } else if (row[m_orderColumns.m_col_instr] == dst) {
1073 itDst = iter;
1074 iDst = i;
1075 }
1076 }
1077 if (itSrc && itDst) {
1078 // swap elements
1079 m_refOrderModel->iter_swap(itSrc, itDst);
1080 // update markup
1081 Gtk::TreeModel::Row rowSrc = *itSrc;
1082 Gtk::TreeModel::Row rowDst = *itDst;
1083 {
1084 Glib::ustring name = rowSrc[m_orderColumns.m_col_name];
1085 Glib::ustring markup =
1086 "<span foreground='black' background='white'>" + ToString(iDst+1) + ".</span>\n<span foreground='green' background='white'>" + name + "</span>";
1087 rowSrc[m_orderColumns.m_col_markup] = markup;
1088 }
1089 {
1090 Glib::ustring name = rowDst[m_orderColumns.m_col_name];
1091 Glib::ustring markup =
1092 "<span foreground='black' background='white'>" + ToString(iSrc+1) + ".</span>\n<span foreground='green' background='white'>" + name + "</span>";
1093 rowDst[m_orderColumns.m_col_markup] = markup;
1094 }
1095 }
1096 }
1097
1098 void CombineInstrumentsDialog::setSelectedInstruments(const std::set<int>& instrumentIndeces) {
1099 typedef Gtk::TreeModel::Children Children;
1100 Children children = m_refTreeModel->children();
1101 for (Children::iterator iter = children.begin();
1102 iter != children.end(); ++iter)
1103 {
1104 Gtk::TreeModel::Row row = *iter;
1105 int index = row[m_columns.m_col_index];
1106 if (instrumentIndeces.count(index))
1107 m_treeView.get_selection()->select(iter);
1108 }
1109 // hack: OK button lost focus after doing the above, it should have focus by default for quick combining with Return key
1110 m_OKButton.grab_focus();
1111 }
1112
1113 void CombineInstrumentsDialog::combineSelectedInstruments() {
1114 std::vector<gig::Instrument*> instruments;
1115 {
1116 typedef Gtk::TreeModel::Children Children;
1117 int i = 0;
1118 Children selection = m_refOrderModel->children();
1119 for (Children::iterator it = selection.begin();
1120 it != selection.end(); ++it, ++i)
1121 {
1122 Gtk::TreeModel::Row row = *it;
1123 Glib::ustring name = row[m_orderColumns.m_col_name];
1124 gig::Instrument* instrument = row[m_orderColumns.m_col_instr];
1125 #if DEBUG_COMBINE_INSTRUMENTS
1126 printf("Selection %d. '%s' %p\n\n", (i+1), name.c_str(), instrument));
1127 #endif
1128 instruments.push_back(instrument);
1129 }
1130 }
1131
1132 g_warnings.clear();
1133
1134 try {
1135 // which main dimension was selected in the combo box?
1136 gig::dimension_t mainDimension;
1137 {
1138 Gtk::TreeModel::iterator iterType = m_comboDimType.get_active();
1139 if (!iterType) throw gig::Exception("No dimension selected");
1140 Gtk::TreeModel::Row rowType = *iterType;
1141 if (!rowType) throw gig::Exception("Something is wrong regarding dimension selection");
1142 int iTypeID = rowType[m_comboDimsModel.m_type_id];
1143 mainDimension = static_cast<gig::dimension_t>(iTypeID);
1144 }
1145
1146 // now start the actual combination task ...
1147 combineInstruments(instruments, m_gig, m_newCombinedInstrument, mainDimension);
1148 } catch (RIFF::Exception e) {;
1149 Gtk::MessageDialog msg(*this, e.Message, false, Gtk::MESSAGE_ERROR);
1150 msg.run();
1151 return;
1152 } catch (...) {
1153 Glib::ustring txt = _("An unknown exception occurred!");
1154 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR);
1155 msg.run();
1156 return;
1157 }
1158
1159 if (!g_warnings.empty()) {
1160 Glib::ustring txt = _(
1161 "Combined instrument was created successfully, but there were warnings:"
1162 );
1163 txt += "\n\n";
1164 for (Warnings::const_iterator itWarn = g_warnings.begin();
1165 itWarn != g_warnings.end(); ++itWarn)
1166 {
1167 txt += "-> " + *itWarn + "\n";
1168 }
1169 txt += "\n";
1170 txt += _(
1171 "You might also want to check the console for further warnings and "
1172 "error messages."
1173 );
1174 Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_WARNING);
1175 msg.run();
1176 }
1177
1178 // no error occurred
1179 m_fileWasChanged = true;
1180 hide();
1181 }
1182
1183 void CombineInstrumentsDialog::onSelectionChanged() {
1184 std::vector<Gtk::TreeModel::Path> v = m_treeView.get_selection()->get_selected_rows();
1185 m_OKButton.set_sensitive(v.size() >= 2);
1186
1187 typedef Gtk::TreeModel::Children Children;
1188
1189 // update horizontal selection list (icon view) ...
1190
1191 // remove items which are not part of the new selection anymore
1192 {
1193 Children allOrdered = m_refOrderModel->children();
1194 for (Children::iterator itOrder = allOrdered.begin();
1195 itOrder != allOrdered.end(); )
1196 {
1197 Gtk::TreeModel::Row rowOrder = *itOrder;
1198 gig::Instrument* instr = rowOrder[m_orderColumns.m_col_instr];
1199 for (uint i = 0; i < v.size(); ++i) {
1200 Gtk::TreeModel::iterator itSel = m_refTreeModel->get_iter(v[i]);
1201 Gtk::TreeModel::Row rowSel = *itSel;
1202 if (rowSel[m_columns.m_col_instr] == instr)
1203 goto nextOrderedItem;
1204 }
1205 goto removeOrderedItem;
1206 nextOrderedItem:
1207 ++itOrder;
1208 continue;
1209 removeOrderedItem:
1210 // postfix increment here to avoid iterator invalidation
1211 m_refOrderModel->erase(itOrder++);
1212 }
1213 }
1214
1215 // add items newly added to the selection
1216 for (uint i = 0; i < v.size(); ++i) {
1217 Gtk::TreeModel::iterator itSel = m_refTreeModel->get_iter(v[i]);
1218 Gtk::TreeModel::Row rowSel = *itSel;
1219 gig::Instrument* instr = rowSel[m_columns.m_col_instr];
1220 Children allOrdered = m_refOrderModel->children();
1221 for (Children::iterator itOrder = allOrdered.begin();
1222 itOrder != allOrdered.end(); ++itOrder)
1223 {
1224 Gtk::TreeModel::Row rowOrder = *itOrder;
1225 if (rowOrder[m_orderColumns.m_col_instr] == instr)
1226 goto nextSelectionItem;
1227 }
1228 goto addNewSelectionItem;
1229 nextSelectionItem:
1230 continue;
1231 addNewSelectionItem:
1232 Glib::ustring name = gig_to_utf8(instr->pInfo->Name);
1233 Gtk::TreeModel::iterator iterOrder = m_refOrderModel->append();
1234 Gtk::TreeModel::Row rowOrder = *iterOrder;
1235 rowOrder[m_orderColumns.m_col_name] = name;
1236 rowOrder[m_orderColumns.m_col_instr] = instr;
1237 }
1238
1239 // update markup
1240 {
1241 int i = 0;
1242 Children allOrdered = m_refOrderModel->children();
1243 for (Children::iterator itOrder = allOrdered.begin();
1244 itOrder != allOrdered.end(); ++itOrder, ++i)
1245 {
1246 Gtk::TreeModel::Row rowOrder = *itOrder;
1247 Glib::ustring name = rowOrder[m_orderColumns.m_col_name];
1248 Glib::ustring markup =
1249 "<span foreground='black' background='white'>" + ToString(i+1) + ".</span>\n<span foreground='green' background='white'>" + name + "</span>";
1250 rowOrder[m_orderColumns.m_col_markup] = markup;
1251 }
1252 }
1253 }
1254
1255 void CombineInstrumentsDialog::on_show_tooltips_changed() {
1256 const bool b = Settings::singleton()->showTooltips;
1257
1258 m_treeView.set_has_tooltip(b);
1259 m_iconView.set_has_tooltip(b);
1260
1261 set_has_tooltip(b);
1262 }
1263
1264 bool CombineInstrumentsDialog::fileWasChanged() const {
1265 return m_fileWasChanged;
1266 }
1267
1268 gig::Instrument* CombineInstrumentsDialog::newCombinedInstrument() const {
1269 return m_newCombinedInstrument;
1270 }

  ViewVC Help
Powered by ViewVC