--- gigedit/trunk/src/gigedit/dimregionchooser.cpp 2015/09/20 10:18:22 2845 +++ gigedit/trunk/src/gigedit/dimregionchooser.cpp 2017/07/11 23:06:38 3307 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2015 Andreas Persson + * Copyright (C) 2006-2017 Andreas Persson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -17,28 +17,24 @@ * 02110-1301 USA. */ +#include "global.h" #include #include "dimregionchooser.h" #include +#include #include #include #include +#include #include #include +#include -#include "global.h" - -// taken from gdk/gdkkeysyms.h -// (define on demand, to avoid unnecessary dev lib package build dependency) -#ifndef GDK_KEY_Control_L -# define GDK_KEY_Control_L 0xffe3 -#endif -#ifndef GDK_KEY_Control_R -# define GDK_KEY_Control_R 0xffe4 -#endif +#include "gfx/builtinpix.h" -static std::map caseOfDimRegion(gig::DimensionRegion* dr, bool* isValidZone) { - std::map dimCase; +//TODO: this function and dimensionCaseOf() from global.h are duplicates, eliminate either one of them! +static DimensionCase caseOfDimRegion(gig::DimensionRegion* dr, bool* isValidZone) { + DimensionCase dimCase; if (!dr) { *isValidZone = false; return dimCase; @@ -56,7 +52,7 @@ if (drIndex == 256) { fprintf(stderr, "DimRegionChooser: ERROR: index of dim region not found!\n"); *isValidZone = false; - return std::map(); + return DimensionCase(); } for (int d = 0, baseBits = 0; d < rgn->Dimensions; ++d) { @@ -67,7 +63,7 @@ // there are also DimensionRegion objects of unused zones, skip them if (dimCase[rgn->pDimensionDefinitions[d].dimension] >= rgn->pDimensionDefinitions[d].zones) { *isValidZone = false; - return std::map(); + return DimensionCase(); } } @@ -76,27 +72,132 @@ } DimRegionChooser::DimRegionChooser(Gtk::Window& window) : - red("#8070ff"), + red("#ff476e"), + blue("#4796ff"), black("black"), white("white") { + // make sure blue hatched pattern pixmap is loaded + loadBuiltInPix(); + + // create blue hatched pattern 1 + { + const int width = blueHatchedPattern->get_width(); + const int height = blueHatchedPattern->get_height(); + const int stride = blueHatchedPattern->get_rowstride(); + + // manually convert from RGBA to ARGB + this->blueHatchedPatternARGB = blueHatchedPattern->copy(); + const int pixelSize = stride / width; + const int totalPixels = width * height; + assert(pixelSize == 4); + unsigned char* ptr = this->blueHatchedPatternARGB->get_pixels(); + for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) { + const unsigned char r = ptr[0]; + const unsigned char g = ptr[1]; + const unsigned char b = ptr[2]; + const unsigned char a = ptr[3]; + ptr[0] = b; + ptr[1] = g; + ptr[2] = r; + ptr[3] = a; + } + + Cairo::RefPtr imageSurface = Cairo::ImageSurface::create( + this->blueHatchedPatternARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride + ); + this->blueHatchedSurfacePattern = Cairo::SurfacePattern::create(imageSurface); + this->blueHatchedSurfacePattern->set_extend(Cairo::EXTEND_REPEAT); + } + + // create blue hatched pattern 2 + { + const int width = blueHatchedPattern2->get_width(); + const int height = blueHatchedPattern2->get_height(); + const int stride = blueHatchedPattern2->get_rowstride(); + + // manually convert from RGBA to ARGB + this->blueHatchedPattern2ARGB = blueHatchedPattern2->copy(); + const int pixelSize = stride / width; + const int totalPixels = width * height; + assert(pixelSize == 4); + unsigned char* ptr = this->blueHatchedPattern2ARGB->get_pixels(); + for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) { + const unsigned char r = ptr[0]; + const unsigned char g = ptr[1]; + const unsigned char b = ptr[2]; + const unsigned char a = ptr[3]; + ptr[0] = b; + ptr[1] = g; + ptr[2] = r; + ptr[3] = a; + } + + Cairo::RefPtr imageSurface = Cairo::ImageSurface::create( + this->blueHatchedPattern2ARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride + ); + this->blueHatchedSurfacePattern2 = Cairo::SurfacePattern::create(imageSurface); + this->blueHatchedSurfacePattern2->set_extend(Cairo::EXTEND_REPEAT); + } + + // create gray blue hatched pattern + { + const int width = grayBlueHatchedPattern->get_width(); + const int height = grayBlueHatchedPattern->get_height(); + const int stride = grayBlueHatchedPattern->get_rowstride(); + + // manually convert from RGBA to ARGB + this->grayBlueHatchedPatternARGB = grayBlueHatchedPattern->copy(); + const int pixelSize = stride / width; + const int totalPixels = width * height; + assert(pixelSize == 4); + unsigned char* ptr = this->grayBlueHatchedPatternARGB->get_pixels(); + for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) { + const unsigned char r = ptr[0]; + const unsigned char g = ptr[1]; + const unsigned char b = ptr[2]; + const unsigned char a = ptr[3]; + ptr[0] = b; + ptr[1] = g; + ptr[2] = r; + ptr[3] = a; + } + + Cairo::RefPtr imageSurface = Cairo::ImageSurface::create( + this->grayBlueHatchedPatternARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride + ); + this->grayBlueHatchedSurfacePattern = Cairo::SurfacePattern::create(imageSurface); + this->grayBlueHatchedSurfacePattern->set_extend(Cairo::EXTEND_REPEAT); + } + instrument = 0; region = 0; maindimregno = -1; + maindimtype = gig::dimension_none; // initialize with invalid dimension type focus_line = 0; resize.active = false; cursor_is_resize = false; h = 24; multiSelectKeyDown = false; + primaryKeyDown = false; + shiftKeyDown = false; + modifybothchannels = modifyalldimregs = modifybothchannels = false; set_can_focus(); + const Glib::ustring txtUseCheckBoxAllRegions = + _("Use checkbox 'all regions' to control whether this should apply to all regions."); + actionGroup = Gtk::ActionGroup::create(); + actionSplitDimZone = Gtk::Action::create("SplitDimZone", _("Split Dimensions Zone"), txtUseCheckBoxAllRegions); + actionSplitDimZone->set_tooltip(txtUseCheckBoxAllRegions); //FIXME: doesn't work? why??? actionGroup->add( - Gtk::Action::create("SplitDimZone", _("Split Dimensions Zone")), + actionSplitDimZone, sigc::mem_fun(*this, &DimRegionChooser::split_dimension_zone) ); + actionDeleteDimZone = Gtk::Action::create("DeleteDimZone", _("Delete Dimension Zone"), txtUseCheckBoxAllRegions); + actionDeleteDimZone->set_tooltip(txtUseCheckBoxAllRegions); //FIXME: doesn't work? why??? actionGroup->add( - Gtk::Action::create("DeleteDimZone", _("Delete Dimension Zone")), + actionDeleteDimZone, sigc::mem_fun(*this, &DimRegionChooser::delete_dimension_zone) ); @@ -141,6 +242,87 @@ { } +void DimRegionChooser::setModifyBothChannels(bool b) { + modifybothchannels = b; + // redraw required parts + queue_draw(); +} + +void DimRegionChooser::setModifyAllDimensionRegions(bool b) { + modifyalldimregs = b; + // redraw required parts + queue_draw(); +} + +void DimRegionChooser::setModifyAllRegions(bool b) { + modifyallregions = b; + + actionDeleteDimZone->set_label(b ? _("Delete Dimension Zone [ALL REGIONS]") : _("Delete Dimension Zone")); + actionSplitDimZone->set_label(b ? _("Split Dimensions Zone [ALL REGIONS]") : _("Split Dimensions Zone")); + + // redraw required parts + queue_draw(); +} + +void DimRegionChooser::drawIconsFor( + gig::dimension_t dimension, uint zone, + const Cairo::RefPtr& cr, + int x, int y, int w, int h) +{ + DimensionCase dimCase; + dimCase[dimension] = zone; + + std::vector dimregs = + dimensionRegionsMatching(dimCase, region, true); + + if (dimregs.empty()) return; + + int iSampleRefs = 0; + int iLoops = 0; + + for (uint i = 0; i < dimregs.size(); ++i) { + if (dimregs[i]->pSample) iSampleRefs++; + if (dimregs[i]->SampleLoops) iLoops++; + } + + bool bShowLoopSymbol = (iLoops > 0); + bool bShowSampleRefSymbol = (iSampleRefs < dimregs.size()); + + if (bShowLoopSymbol || bShowSampleRefSymbol) { + const int margin = 1; + + cr->save(); + cr->set_line_width(1); + cr->rectangle(x, y + margin, w, h - 2*margin); + cr->clip(); + if (bShowSampleRefSymbol) { + const int wPic = 8; + const int hPic = 8; + Gdk::Cairo::set_source_pixbuf( + cr, (iSampleRefs) ? yellowDot : redDot, + x + (w-wPic)/2.f, + y + ( + (bShowLoopSymbol) ? margin : (h-hPic)/2.f + ) + ); + cr->paint(); + } + if (bShowLoopSymbol) { + const int wPic = 12; + const int hPic = 14; + Gdk::Cairo::set_source_pixbuf( + cr, (iLoops == dimregs.size()) ? blackLoop : grayLoop, + x + (w-wPic)/2.f, + y + ( + (bShowSampleRefSymbol) ? h - hPic - margin : (h-hPic)/2.f + ) + ); + cr->paint(); + } + cr->restore(); + } +} + #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2 bool DimRegionChooser::on_expose_event(GdkEventExpose* e) { @@ -229,9 +411,19 @@ dstr = dstrbuf; break; } - layout->set_text(dstr); + // Since bold font yields in larger label width, we first always + // set the bold text variant, retrieve its dimensions (as worst + // case dimensions of the label) ... + layout->set_markup("" + Glib::ustring(dstr) + ""); Pango::Rectangle rectangle = layout->get_logical_extents(); + // ... and then reset the label to regular font style in case + // the line is not selected. Otherwise the right hand side + // actual dimension zones would jump around on selection change. + bool isSelectedLine = (focus_line == i); + if (!isSelectedLine) + layout->set_markup(dstr); + double text_w = double(rectangle.get_width()) / Pango::SCALE; if (text_w > maxwidth) maxwidth = text_w; @@ -338,10 +530,30 @@ // draw fill for zone bool isSelectedZone = this->dimzones[dimension].count(j); - Gdk::Cairo::set_source_rgba(cr, isSelectedZone ? red : white); - cr->rectangle(prevX + 1, y + 1, x - prevX - 1, h - 1); + bool isMainSelection = + this->maindimcase.find(dimension) != this->maindimcase.end() && + this->maindimcase[dimension] == j; + bool isCheckBoxSelected = + modifyalldimregs || + (modifybothchannels && + dimension == gig::dimension_samplechannel); + if (isMainSelection) + Gdk::Cairo::set_source_rgba(cr, blue); + else if (isSelectedZone) + cr->set_source(blueHatchedSurfacePattern2); + else if (isCheckBoxSelected) + cr->set_source(blueHatchedSurfacePattern); + else + Gdk::Cairo::set_source_rgba(cr, white); + + const int wZone = x - prevX - 1; + + cr->rectangle(prevX + 1, y + 1, wZone, h - 1); cr->fill(); + // draw icons + drawIconsFor(dimension, j, cr, prevX, y, wZone, h); + // draw text showing the beginning of the dimension zone // as numeric value to the user { @@ -394,12 +606,31 @@ cr->stroke(); if (j != 0) { + const int wZone = x - prevX - 1; + // draw fill for zone bool isSelectedZone = this->dimzones[dimension].count(j-1); - Gdk::Cairo::set_source_rgba(cr, isSelectedZone ? red : white); - cr->rectangle(prevX + 1, y + 1, x - prevX - 1, h - 1); + bool isMainSelection = + this->maindimcase.find(dimension) != this->maindimcase.end() && + this->maindimcase[dimension] == (j-1); + bool isCheckBoxSelected = + modifyalldimregs || + (modifybothchannels && + dimension == gig::dimension_samplechannel); + if (isMainSelection) + Gdk::Cairo::set_source_rgba(cr, blue); + else if (isSelectedZone) + cr->set_source(blueHatchedSurfacePattern2); + else if (isCheckBoxSelected) + cr->set_source(blueHatchedSurfacePattern); + else + Gdk::Cairo::set_source_rgba(cr, white); + cr->rectangle(prevX + 1, y + 1, wZone, h - 1); cr->fill(); + // draw icons + drawIconsFor(dimension, j - 1, cr, prevX, y, wZone, h); + // draw text showing the beginning of the dimension zone // as numeric value to the user { @@ -507,12 +738,23 @@ void DimRegionChooser::update_after_resize() { - if (region->pDimensionDefinitions[resize.dimension].dimension == gig::dimension_velocity) { + const uint8_t upperLimit = resize.pos - 1; + gig::Instrument* instr = (gig::Instrument*)region->GetParent(); - int bitpos = 0; - for (int j = 0 ; j < resize.dimension ; j++) { - bitpos += region->pDimensionDefinitions[j].bits; - } + int bitpos = 0; + for (int j = 0 ; j < resize.dimension ; j++) { + bitpos += region->pDimensionDefinitions[j].bits; + } + + const int stereobitpos = + (modifybothchannels) ? baseBits(gig::dimension_samplechannel, region) : -1; + + // the velocity dimension must be handled differently than all other + // dimension types, because + // 1. it is currently the only dimension type which allows different zone + // sizes for different cases + // 2. for v2 format VelocityUpperLimit has to be set, DimensionUpperLimits for v3 + if (region->pDimensionDefinitions[resize.dimension].dimension == gig::dimension_velocity) { int mask = ~(((1 << region->pDimensionDefinitions[resize.dimension].bits) - 1) << bitpos); int c = maindimregno & mask; // mask away this dimension @@ -538,22 +780,76 @@ } } - gig::DimensionRegion* d = region->pDimensionRegions[c + resize.offset]; + int index = c + (resize.zone << bitpos); + gig::DimensionRegion* d = region->pDimensionRegions[index]; // update both v2 and v3 values - d->DimensionUpperLimits[resize.dimension] = resize.pos - 1; - d->VelocityUpperLimit = resize.pos - 1; + d->DimensionUpperLimits[resize.dimension] = upperLimit; + d->VelocityUpperLimit = upperLimit; + if (modifybothchannels && stereobitpos >= 0) { // do the same for the other audio channel's dimregion ... + gig::DimensionRegion* d = region->pDimensionRegions[index ^ (1 << stereobitpos)]; + d->DimensionUpperLimits[resize.dimension] = upperLimit; + d->VelocityUpperLimit = upperLimit; + } + if (modifyalldimregs) { + gig::Region* rgn = NULL; + for (int key = 0; key < 128; ++key) { + if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue; + rgn = instr->GetRegion(key); + if (!modifyallregions && rgn != region) continue; // hack to reduce overall code amount a bit + gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension); + if (!dimdef) continue; + if (dimdef->zones != resize.dimensionDef.zones) continue; + const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn); + assert(iDim >= 0 && iDim < rgn->Dimensions); + + // the dimension layout might be completely different in this + // region, so we have to recalculate bitpos etc for this region + const int bitpos = baseBits(resize.dimensionDef.dimension, rgn); + const int stencil = ~(((1 << dimdef->bits) - 1) << bitpos); + const int selection = resize.zone << bitpos; + + // primitive and inefficient loop implementation, however due to + // this circumstance the loop code is much simpler, and its lack + // of runtime efficiency should not be notable in practice + for (int idr = 0; idr < 256; ++idr) { + const int index = (idr & stencil) | selection; + assert(index >= 0 && index < 256); + gig::DimensionRegion* dr = rgn->pDimensionRegions[index]; + if (!dr) continue; + dr->DimensionUpperLimits[iDim] = upperLimit; + d->VelocityUpperLimit = upperLimit; + } + } + } else if (modifyallregions) { // implies modifyalldimregs is false ... + // resolve the precise case we need to modify for all other regions + DimensionCase dimCase = dimensionCaseOf(d); + // apply the velocity upper limit change to that resolved dim case + // of all regions ... + gig::Region* rgn = NULL; + for (int key = 0; key < 128; ++key) { + if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue; + rgn = instr->GetRegion(key); + gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension); + if (!dimdef) continue; + if (dimdef->zones != resize.dimensionDef.zones) continue; + const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn); + assert(iDim >= 0 && iDim < rgn->Dimensions); + + std::vector dimrgns = dimensionRegionsMatching(dimCase, rgn); + for (int i = 0; i < dimrgns.size(); ++i) { + gig::DimensionRegion* dr = dimrgns[i]; + dr->DimensionUpperLimits[iDim] = upperLimit; + dr->VelocityUpperLimit = upperLimit; + } + } + } } else { for (int i = 0 ; i < region->DimensionRegions ; ) { - if (region->pDimensionRegions[i]->DimensionUpperLimits[resize.dimension] == 0) { // the dimension didn't previously have custom // limits, so we have to set default limits for // all the dimension regions - int bitpos = 0; - for (int j = 0 ; j < resize.dimension ; j++) { - bitpos += region->pDimensionDefinitions[j].bits; - } int nbZones = region->pDimensionDefinitions[resize.dimension].zones; for (int j = 0 ; j < nbZones ; j++) { @@ -561,9 +857,15 @@ d->DimensionUpperLimits[resize.dimension] = int(128.0 * (j + 1) / nbZones - 1); } } - gig::DimensionRegion* d = region->pDimensionRegions[i + resize.offset]; - d->DimensionUpperLimits[resize.dimension] = resize.pos - 1; - + int index = i + (resize.zone << bitpos); + gig::DimensionRegion* d = region->pDimensionRegions[index]; + d->DimensionUpperLimits[resize.dimension] = upperLimit; +#if 0 // the following is currently not necessary, because ATM the gig format uses for all dimension types except of the veleocity dimension the same zone sizes for all cases + if (modifybothchannels && stereobitpos >= 0) { // do the same for the other audio channel's dimregion ... + gig::DimensionRegion* d = region->pDimensionRegions[index ^ (1 << stereobitpos)]; + d->DimensionUpperLimits[resize.dimension] = upperLimit; + } +#endif int bitpos = 0; int j; for (j = 0 ; j < region->Dimensions ; j++) { @@ -577,6 +879,37 @@ if (j == region->Dimensions) break; i = (i & ~((1 << bitpos) - 1)) + (1 << bitpos); } + + if (modifyallregions) { // TODO: this code block could be merged with the similar (and more generalized) code block of the velocity dimension above + gig::Region* rgn = NULL; + for (int key = 0; key < 128; ++key) { + if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue; + rgn = instr->GetRegion(key); + gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension); + if (!dimdef) continue; + if (dimdef->zones != resize.dimensionDef.zones) continue; + const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn); + assert(iDim >= 0 && iDim < rgn->Dimensions); + + // the dimension layout might be completely different in this + // region, so we have to recalculate bitpos etc for this region + const int bitpos = baseBits(resize.dimensionDef.dimension, rgn); + const int stencil = ~(((1 << dimdef->bits) - 1) << bitpos); + const int selection = resize.zone << bitpos; + + // this loop implementation is less efficient than the above's + // loop implementation (which skips unnecessary dimension regions) + // however this code is much simpler, and its lack of runtime + // efficiency should not be notable in practice + for (int idr = 0; idr < 256; ++idr) { + const int index = (idr & stencil) | selection; + assert(index >= 0 && index < 256); + gig::DimensionRegion* dr = rgn->pDimensionRegions[index]; + if (!dr) continue; + dr->DimensionUpperLimits[iDim] = upperLimit; + } + } + } } } @@ -805,7 +1138,8 @@ if (x <= limitx - 2) break; if (x <= limitx + 2) { resize.dimension = dim; - resize.offset = iZone << bitpos; + resize.dimensionDef = region->pDimensionDefinitions[dim]; + resize.zone = iZone; resize.pos = limit; resize.min = prev_limit; @@ -891,7 +1225,53 @@ void DimRegionChooser::split_dimension_zone() { printf("split_dimension_zone() type=%d, zone=%d\n", maindimtype, maindimcase[maindimtype]); try { - region->SplitDimensionZone(maindimtype, maindimcase[maindimtype]); + if (!modifyallregions) { + region->SplitDimensionZone(maindimtype, maindimcase[maindimtype]); + } else { + gig::Instrument* instr = (gig::Instrument*)region->GetParent(); + gig::dimension_def_t* pMaindimdef = region->GetDimensionDefinition(maindimtype); + assert(pMaindimdef != NULL); + // retain structure by value since the original region will be + // modified in the loop below as well + gig::dimension_def_t maindimdef = *pMaindimdef; + std::vector ignoredAll; + std::vector ignoredMinor; + std::vector ignoredCritical; + gig::Region* rgn = NULL; + for (int key = 0; key < 128; ++key) { + if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue; + rgn = instr->GetRegion(key); + + // ignore all regions which do not exactly match the dimension + // layout of the selected region where this operation was emitted + gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(maindimtype); + if (!dimdef) { + ignoredAll.push_back(rgn); + ignoredMinor.push_back(rgn); + continue; + } + if (dimdef->zones != maindimdef.zones) { + ignoredAll.push_back(rgn); + ignoredCritical.push_back(rgn); + continue; + } + + rgn->SplitDimensionZone(maindimtype, maindimcase[maindimtype]); + } + if (!ignoredAll.empty()) { + Glib::ustring txt; + if (ignoredCritical.empty()) + txt = ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type."); + else if (ignoredMinor.empty()) + txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones!"); + else + txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones (and ") + + ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type)!"); + Gtk::MessageType type = (ignoredCritical.empty()) ? Gtk::MESSAGE_INFO : Gtk::MESSAGE_WARNING; + Gtk::MessageDialog msg(txt, false, type); + msg.run(); + } + } } catch (RIFF::Exception e) { Gtk::MessageDialog msg(e.Message, false, Gtk::MESSAGE_ERROR); msg.run(); @@ -906,7 +1286,53 @@ void DimRegionChooser::delete_dimension_zone() { printf("delete_dimension_zone() type=%d, zone=%d\n", maindimtype, maindimcase[maindimtype]); try { - region->DeleteDimensionZone(maindimtype, maindimcase[maindimtype]); + if (!modifyallregions) { + region->DeleteDimensionZone(maindimtype, maindimcase[maindimtype]); + } else { + gig::Instrument* instr = (gig::Instrument*)region->GetParent(); + gig::dimension_def_t* pMaindimdef = region->GetDimensionDefinition(maindimtype); + assert(pMaindimdef != NULL); + // retain structure by value since the original region will be + // modified in the loop below as well + gig::dimension_def_t maindimdef = *pMaindimdef; + std::vector ignoredAll; + std::vector ignoredMinor; + std::vector ignoredCritical; + gig::Region* rgn = NULL; + for (int key = 0; key < 128; ++key) { + if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue; + rgn = instr->GetRegion(key); + + // ignore all regions which do not exactly match the dimension + // layout of the selected region where this operation was emitted + gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(maindimtype); + if (!dimdef) { + ignoredAll.push_back(rgn); + ignoredMinor.push_back(rgn); + continue; + } + if (dimdef->zones != maindimdef.zones) { + ignoredAll.push_back(rgn); + ignoredCritical.push_back(rgn); + continue; + } + + rgn->DeleteDimensionZone(maindimtype, maindimcase[maindimtype]); + } + if (!ignoredAll.empty()) { + Glib::ustring txt; + if (ignoredCritical.empty()) + txt = ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type."); + else if (ignoredMinor.empty()) + txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones!"); + else + txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones (and ") + + ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type)!"); + Gtk::MessageType type = (ignoredCritical.empty()) ? Gtk::MESSAGE_INFO : Gtk::MESSAGE_WARNING; + Gtk::MessageDialog msg(txt, false, type); + msg.run(); + } + } } catch (RIFF::Exception e) { Gtk::MessageDialog msg(e.Message, false, Gtk::MESSAGE_ERROR); msg.run(); @@ -918,17 +1344,70 @@ refresh_all(); } +// Cmd key on Mac, Ctrl key on all other OSs +static const guint primaryKeyL = + #if defined(__APPLE__) + GDK_KEY_Meta_L; + #else + GDK_KEY_Control_L; + #endif + +static const guint primaryKeyR = + #if defined(__APPLE__) + GDK_KEY_Meta_R; + #else + GDK_KEY_Control_R; + #endif + bool DimRegionChooser::onKeyPressed(GdkEventKey* key) { - //printf("key down\n"); + //printf("key down 0x%x\n", key->keyval); if (key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R) multiSelectKeyDown = true; + if (key->keyval == primaryKeyL || key->keyval == primaryKeyR) + primaryKeyDown = true; + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R) + shiftKeyDown = true; + + //FIXME: hmm, for some reason GDKMM does not fire arrow key down events, so we are doing those handlers in the key up handler instead for now + /*if (key->keyval == GDK_KEY_Left) + select_prev_dimzone(); + if (key->keyval == GDK_KEY_Right) + select_next_dimzone(); + if (key->keyval == GDK_KEY_Up) + select_prev_dimension(); + if (key->keyval == GDK_KEY_Down) + select_next_dimension();*/ return false; } bool DimRegionChooser::onKeyReleased(GdkEventKey* key) { - //printf("key up\n"); + //printf("key up 0x%x\n", key->keyval); if (key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R) multiSelectKeyDown = false; + if (key->keyval == primaryKeyL || key->keyval == primaryKeyR) + primaryKeyDown = false; + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R) + shiftKeyDown = false; + + if (!has_focus()) return false; + + // avoid conflict with Ctrl+Left and Ctrl+Right accelerators on mainwindow + // (which is supposed to switch between regions) + if (primaryKeyDown) return false; + + // avoid conflict with Alt+Shift+Left and Alt+Shift+Right accelerators on + // mainwindow + if (shiftKeyDown) return false; + + if (key->keyval == GDK_KEY_Left) + select_prev_dimzone(); + if (key->keyval == GDK_KEY_Right) + select_next_dimzone(); + if (key->keyval == GDK_KEY_Up) + select_prev_dimension(); + if (key->keyval == GDK_KEY_Down) + select_next_dimension(); + return false; } @@ -949,11 +1428,7 @@ gig::DimensionRegion* dimrgn = region->pDimensionRegions[maindimregno]; bool isValidZone; - this->maindimcase = caseOfDimRegion(dimrgn, &isValidZone); - if (!isValidZone) { - queue_draw(); // redraw required parts - return; - } + this->maindimcase = dimensionCaseOf(dimrgn); for (std::map::const_iterator it = this->maindimcase.begin(); it != this->maindimcase.end(); ++it) @@ -984,6 +1459,85 @@ return false; //.selection failed } +void DimRegionChooser::select_next_dimzone(bool add) { + select_dimzone_by_dir(+1, add); +} + +void DimRegionChooser::select_prev_dimzone(bool add) { + select_dimzone_by_dir(-1, add); +} + +void DimRegionChooser::select_dimzone_by_dir(int dir, bool add) { + if (!region) return; + if (!region->Dimensions) return; + if (focus_line < 0) focus_line = 0; + if (focus_line >= region->Dimensions) focus_line = region->Dimensions - 1; + + maindimtype = region->pDimensionDefinitions[focus_line].dimension; + if (maindimtype == gig::dimension_none) { + printf("maindimtype -> none\n"); + return; + } + + if (maindimcase.empty()) { + maindimcase = dimensionCaseOf(region->pDimensionRegions[maindimregno]); + if (maindimcase.empty()) { + printf("caseOfDimregion(%d) -> empty\n", maindimregno); + return; + } + } + + int z = (dir > 0) ? maindimcase[maindimtype] + 1 : maindimcase[maindimtype] - 1; + if (z < 0) z = 0; + if (z >= region->pDimensionDefinitions[focus_line].zones) + z = region->pDimensionDefinitions[focus_line].zones - 1; + + maindimcase[maindimtype] = z; + + ::gig::DimensionRegion* dr = dimensionRegionMatching(maindimcase, region); + if (!dr) { + printf("select_dimzone_by_dir(%d) -> !dr\n", dir); + return; + } + + maindimregno = getDimensionRegionIndex(dr); + + if (!add) { + // reset selected dimregion zones + dimzones.clear(); + } + for (DimensionCase::const_iterator it = maindimcase.begin(); + it != maindimcase.end(); ++it) + { + dimzones[it->first].insert(it->second); + } + + dimregion_selected(); + + // disabled: would overwrite dimregno with wrong value + //refresh_all(); + // so requesting just a raw repaint instead: + queue_draw(); +} + +void DimRegionChooser::select_next_dimension() { + if (!region) return; + focus_line++; + if (focus_line >= region->Dimensions) + focus_line = region->Dimensions - 1; + this->maindimtype = region->pDimensionDefinitions[focus_line].dimension; + queue_draw(); +} + +void DimRegionChooser::select_prev_dimension() { + if (!region) return; + focus_line--; + if (focus_line < 0) + focus_line = 0; + this->maindimtype = region->pDimensionDefinitions[focus_line].dimension; + queue_draw(); +} + gig::DimensionRegion* DimRegionChooser::get_main_dimregion() const { if (!region) return NULL; return region->pDimensionRegions[maindimregno];