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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3460 - (show annotations) (download)
Sat Feb 2 07:48:50 2019 UTC (20 months, 3 weeks ago) by persson
File size: 70473 byte(s)
* Use GDK Seat API if available to grab pointer
* Improve version checks for pangomm
* Fix the instrument list tooltip handling so it doesn't generate GTK
  assertion error messages
* Use English quotation marks in tooltips instead of German
* Use multiple columns in controller value popups, as tall popup menus
  work really badly in some GTK environments

1 /*
2 * Copyright (C) 2006-2019 Andreas Persson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2, or (at
7 * your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include "global.h"
21 #include "compat.h"
22 #include "Settings.h"
23 #include <gtkmm/box.h>
24 #include "dimregionchooser.h"
25 #include <cairomm/context.h>
26 #include <cairomm/surface.h>
27 #include <gdkmm/cursor.h>
28 #include <gdkmm/general.h>
29 #if HAS_GDKMM_SEAT
30 # include <gdkmm/seat.h>
31 #endif
32 #include <glibmm/stringutils.h>
33 #if HAS_GTKMM_STOCK
34 # include <gtkmm/stock.h>
35 #endif
36 #include <glibmm/ustring.h>
37 #include <gtkmm/messagedialog.h>
38 #include <assert.h>
39
40 #include "gfx/builtinpix.h"
41
42 //TODO: this function and dimensionCaseOf() from global.h are duplicates, eliminate either one of them!
43 static DimensionCase caseOfDimRegion(gig::DimensionRegion* dr, bool* isValidZone) {
44 DimensionCase dimCase;
45 if (!dr) {
46 *isValidZone = false;
47 return dimCase;
48 }
49
50 gig::Region* rgn = (gig::Region*) dr->GetParent();
51
52 // find the dimension region index of the passed dimension region
53 int drIndex;
54 for (drIndex = 0; drIndex < 256; ++drIndex)
55 if (rgn->pDimensionRegions[drIndex] == dr)
56 break;
57
58 // not found in region, something's horribly wrong
59 if (drIndex == 256) {
60 fprintf(stderr, "DimRegionChooser: ERROR: index of dim region not found!\n");
61 *isValidZone = false;
62 return DimensionCase();
63 }
64
65 for (int d = 0, baseBits = 0; d < rgn->Dimensions; ++d) {
66 const int bits = rgn->pDimensionDefinitions[d].bits;
67 dimCase[rgn->pDimensionDefinitions[d].dimension] =
68 (drIndex >> baseBits) & ((1 << bits) - 1);
69 baseBits += bits;
70 // there are also DimensionRegion objects of unused zones, skip them
71 if (dimCase[rgn->pDimensionDefinitions[d].dimension] >= rgn->pDimensionDefinitions[d].zones) {
72 *isValidZone = false;
73 return DimensionCase();
74 }
75 }
76
77 *isValidZone = true;
78 return dimCase;
79 }
80
81 DimRegionChooser::DimRegionChooser(Gtk::Window& window) :
82 red("#ff476e"),
83 blue("#4796ff"),
84 black("black"),
85 white("white")
86 {
87 // make sure blue hatched pattern pixmap is loaded
88 loadBuiltInPix();
89
90 // create blue hatched pattern 1
91 {
92 const int width = blueHatchedPattern->get_width();
93 const int height = blueHatchedPattern->get_height();
94 const int stride = blueHatchedPattern->get_rowstride();
95
96 // manually convert from RGBA to ARGB
97 this->blueHatchedPatternARGB = blueHatchedPattern->copy();
98 const int pixelSize = stride / width;
99 const int totalPixels = width * height;
100 assert(pixelSize == 4);
101 unsigned char* ptr = this->blueHatchedPatternARGB->get_pixels();
102 for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) {
103 const unsigned char r = ptr[0];
104 const unsigned char g = ptr[1];
105 const unsigned char b = ptr[2];
106 const unsigned char a = ptr[3];
107 ptr[0] = b;
108 ptr[1] = g;
109 ptr[2] = r;
110 ptr[3] = a;
111 }
112
113 Cairo::RefPtr<Cairo::ImageSurface> imageSurface = Cairo::ImageSurface::create(
114 #if HAS_CAIROMM_CPP11_ENUMS
115 this->blueHatchedPatternARGB->get_pixels(), Cairo::Surface::Format::ARGB32, width, height, stride
116 #else
117 this->blueHatchedPatternARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride
118 #endif
119 );
120 this->blueHatchedSurfacePattern = Cairo::SurfacePattern::create(imageSurface);
121 #if HAS_CAIROMM_CPP11_ENUMS
122 this->blueHatchedSurfacePattern->set_extend(Cairo::Pattern::Extend::REPEAT);
123 #else
124 this->blueHatchedSurfacePattern->set_extend(Cairo::EXTEND_REPEAT);
125 #endif
126 }
127
128 // create blue hatched pattern 2
129 {
130 const int width = blueHatchedPattern2->get_width();
131 const int height = blueHatchedPattern2->get_height();
132 const int stride = blueHatchedPattern2->get_rowstride();
133
134 // manually convert from RGBA to ARGB
135 this->blueHatchedPattern2ARGB = blueHatchedPattern2->copy();
136 const int pixelSize = stride / width;
137 const int totalPixels = width * height;
138 assert(pixelSize == 4);
139 unsigned char* ptr = this->blueHatchedPattern2ARGB->get_pixels();
140 for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) {
141 const unsigned char r = ptr[0];
142 const unsigned char g = ptr[1];
143 const unsigned char b = ptr[2];
144 const unsigned char a = ptr[3];
145 ptr[0] = b;
146 ptr[1] = g;
147 ptr[2] = r;
148 ptr[3] = a;
149 }
150
151 Cairo::RefPtr<Cairo::ImageSurface> imageSurface = Cairo::ImageSurface::create(
152 #if HAS_CAIROMM_CPP11_ENUMS
153 this->blueHatchedPattern2ARGB->get_pixels(), Cairo::Surface::Format::ARGB32, width, height, stride
154 #else
155 this->blueHatchedPattern2ARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride
156 #endif
157 );
158 this->blueHatchedSurfacePattern2 = Cairo::SurfacePattern::create(imageSurface);
159 #if HAS_CAIROMM_CPP11_ENUMS
160 this->blueHatchedSurfacePattern2->set_extend(Cairo::Pattern::Extend::REPEAT);
161 #else
162 this->blueHatchedSurfacePattern2->set_extend(Cairo::EXTEND_REPEAT);
163 #endif
164 }
165
166 // create gray blue hatched pattern
167 {
168 const int width = grayBlueHatchedPattern->get_width();
169 const int height = grayBlueHatchedPattern->get_height();
170 const int stride = grayBlueHatchedPattern->get_rowstride();
171
172 // manually convert from RGBA to ARGB
173 this->grayBlueHatchedPatternARGB = grayBlueHatchedPattern->copy();
174 const int pixelSize = stride / width;
175 const int totalPixels = width * height;
176 assert(pixelSize == 4);
177 unsigned char* ptr = this->grayBlueHatchedPatternARGB->get_pixels();
178 for (int iPixel = 0; iPixel < totalPixels; ++iPixel, ptr += pixelSize) {
179 const unsigned char r = ptr[0];
180 const unsigned char g = ptr[1];
181 const unsigned char b = ptr[2];
182 const unsigned char a = ptr[3];
183 ptr[0] = b;
184 ptr[1] = g;
185 ptr[2] = r;
186 ptr[3] = a;
187 }
188
189 Cairo::RefPtr<Cairo::ImageSurface> imageSurface = Cairo::ImageSurface::create(
190 #if HAS_CAIROMM_CPP11_ENUMS
191 this->grayBlueHatchedPatternARGB->get_pixels(), Cairo::Surface::Format::ARGB32, width, height, stride
192 #else
193 this->grayBlueHatchedPatternARGB->get_pixels(), Cairo::FORMAT_ARGB32, width, height, stride
194 #endif
195 );
196 this->grayBlueHatchedSurfacePattern = Cairo::SurfacePattern::create(imageSurface);
197 #if HAS_CAIROMM_CPP11_ENUMS
198 this->grayBlueHatchedSurfacePattern->set_extend(Cairo::Pattern::Extend::REPEAT);
199 #else
200 this->grayBlueHatchedSurfacePattern->set_extend(Cairo::EXTEND_REPEAT);
201 #endif
202 }
203
204 instrument = 0;
205 region = 0;
206 maindimregno = -1;
207 maindimtype = gig::dimension_none; // initialize with invalid dimension type
208 focus_line = 0;
209 resize.active = false;
210 cursor_is_resize = false;
211 h = 24;
212 multiSelectKeyDown = false;
213 primaryKeyDown = false;
214 shiftKeyDown = false;
215 modifybothchannels = modifyalldimregs = modifybothchannels = false;
216 set_can_focus();
217
218 const Glib::ustring txtUseCheckBoxAllRegions =
219 _("Use checkbox 'all regions' to control whether this should apply to all regions.");
220
221 actionGroup = ActionGroup::create();
222 #if USE_GLIB_ACTION
223 actionSplitDimZone = actionGroup->add_action(
224 "SplitDimZone", sigc::mem_fun(*this, &DimRegionChooser::split_dimension_zone)
225 );
226 actionDeleteDimZone = actionGroup->add_action(
227 "DeleteDimZone", sigc::mem_fun(*this, &DimRegionChooser::delete_dimension_zone)
228 );
229 insert_action_group("PopupMenuInsideDimRegion", actionGroup);
230 #else
231 actionSplitDimZone = Action::create("SplitDimZone", _("Split Dimensions Zone"), txtUseCheckBoxAllRegions);
232 actionSplitDimZone->set_tooltip(txtUseCheckBoxAllRegions); //FIXME: doesn't work? why???
233 actionGroup->add(
234 actionSplitDimZone,
235 sigc::mem_fun(*this, &DimRegionChooser::split_dimension_zone)
236 );
237 actionDeleteDimZone = Gtk::Action::create("DeleteDimZone", _("Delete Dimension Zone"), txtUseCheckBoxAllRegions);
238 actionDeleteDimZone->set_tooltip(txtUseCheckBoxAllRegions); //FIXME: doesn't work? why???
239 actionGroup->add(
240 actionDeleteDimZone,
241 sigc::mem_fun(*this, &DimRegionChooser::delete_dimension_zone)
242 );
243 #endif
244
245 #if USE_GTKMM_BUILDER
246 uiManager = Gtk::Builder::create();
247 Glib::ustring ui_info =
248 "<interface>"
249 " <menu id='menu-PopupMenuInsideDimRegion'>"
250 " <section>"
251 " <item id='item-split'>"
252 " <attribute name='label' translatable='yes'>Split Dimensions Zone</attribute>"
253 " <attribute name='action'>PopupMenuInsideDimRegion.SplitDimZone</attribute>"
254 " </item>"
255 " <item id='item-delete'>"
256 " <attribute name='label' translatable='yes'>Delete Dimension Zone</attribute>"
257 " <attribute name='action'>PopupMenuInsideDimRegion.DeleteDimZone</attribute>"
258 " </item>"
259 " </section>"
260 " </menu>"
261 "</interface>";
262 uiManager->add_from_string(ui_info);
263
264 popup_menu_inside_dimregion = new Gtk::Menu(
265 Glib::RefPtr<Gio::Menu>::cast_dynamic(
266 uiManager->get_object("menu-PopupMenuInsideDimRegion")
267 )
268 );
269 #else
270 uiManager = Gtk::UIManager::create();
271 uiManager->insert_action_group(actionGroup);
272 Glib::ustring ui_info =
273 "<ui>"
274 " <popup name='PopupMenuInsideDimRegion'>"
275 " <menuitem action='SplitDimZone'/>"
276 " <menuitem action='DeleteDimZone'/>"
277 " </popup>"
278 // " <popup name='PopupMenuOutsideDimRegion'>"
279 // " <menuitem action='Add'/>"
280 // " </popup>"
281 "</ui>";
282 uiManager->add_ui_from_string(ui_info);
283
284 popup_menu_inside_dimregion = dynamic_cast<Gtk::Menu*>(
285 uiManager->get_widget("/PopupMenuInsideDimRegion"));
286 // popup_menu_outside_dimregion = dynamic_cast<Gtk::Menu*>(
287 // uiManager->get_widget("/PopupMenuOutsideDimRegion"));
288
289 #endif // USE_GTKMM_BUILDER
290
291
292 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION > 24)
293 # warning GTKMM4 event registration code missing for dimregionchooser!
294 //add_events(Gdk::EventMask::BUTTON_PRESS_MASK);
295 #else
296 add_events(Gdk::BUTTON_PRESS_MASK | Gdk::POINTER_MOTION_MASK |
297 Gdk::POINTER_MOTION_HINT_MASK);
298 #endif
299
300 labels_changed = true;
301
302 set_tooltip_text(_(
303 "Right click here for options on altering dimension zones. Press and "
304 "hold CTRL key for selecting multiple dimension zones simultaniously."
305 ));
306
307 Settings::singleton()->showTooltips.get_proxy().signal_changed().connect(
308 sigc::mem_fun(*this, &DimRegionChooser::on_show_tooltips_changed)
309 );
310 on_show_tooltips_changed();
311
312 window.signal_key_press_event().connect(
313 sigc::mem_fun(*this, &DimRegionChooser::onKeyPressed)
314 );
315 window.signal_key_release_event().connect(
316 sigc::mem_fun(*this, &DimRegionChooser::onKeyReleased)
317 );
318 }
319
320 DimRegionChooser::~DimRegionChooser()
321 {
322 }
323
324 void DimRegionChooser::on_show_tooltips_changed() {
325 const bool b = Settings::singleton()->showTooltips;
326
327 set_has_tooltip(b);
328 }
329
330 void DimRegionChooser::setModifyBothChannels(bool b) {
331 modifybothchannels = b;
332 // redraw required parts
333 queue_draw();
334 }
335
336 void DimRegionChooser::setModifyAllDimensionRegions(bool b) {
337 modifyalldimregs = b;
338 // redraw required parts
339 queue_draw();
340 }
341
342 void DimRegionChooser::setModifyAllRegions(bool b) {
343 modifyallregions = b;
344
345 #if USE_GTKMM_BUILDER
346 auto menuItemSplit = Glib::RefPtr<Gio::MenuItem>::cast_dynamic(
347 uiManager->get_object("item-split")
348 );
349 auto menuItemDelete = Glib::RefPtr<Gio::MenuItem>::cast_dynamic(
350 uiManager->get_object("item-delete")
351 );
352 menuItemDelete->set_label(b ? _("Delete Dimension Zone [ALL REGIONS]") : _("Delete Dimension Zone"));
353 menuItemSplit->set_label(b ? _("Split Dimensions Zone [ALL REGIONS]") : _("Split Dimensions Zone"));
354 #else
355 actionDeleteDimZone->set_label(b ? _("Delete Dimension Zone [ALL REGIONS]") : _("Delete Dimension Zone"));
356 actionSplitDimZone->set_label(b ? _("Split Dimensions Zone [ALL REGIONS]") : _("Split Dimensions Zone"));
357 #endif
358
359 // redraw required parts
360 queue_draw();
361 }
362
363 void DimRegionChooser::drawIconsFor(
364 gig::dimension_t dimension, uint zone,
365 const Cairo::RefPtr<Cairo::Context>& cr,
366 int x, int y, int w, int h)
367 {
368 DimensionCase dimCase;
369 dimCase[dimension] = zone;
370
371 std::vector<gig::DimensionRegion*> dimregs =
372 dimensionRegionsMatching(dimCase, region, true);
373
374 if (dimregs.empty()) return;
375
376 int iSampleRefs = 0;
377 int iLoops = 0;
378
379 for (uint i = 0; i < dimregs.size(); ++i) {
380 if (dimregs[i]->pSample) iSampleRefs++;
381 if (dimregs[i]->SampleLoops) iLoops++;
382 }
383
384 bool bShowLoopSymbol = (iLoops > 0);
385 bool bShowSampleRefSymbol = (iSampleRefs < dimregs.size());
386
387 if (bShowLoopSymbol || bShowSampleRefSymbol) {
388 const int margin = 1;
389
390 cr->save();
391 cr->set_line_width(1);
392 cr->rectangle(x, y + margin, w, h - 2*margin);
393 cr->clip();
394 if (bShowSampleRefSymbol) {
395 const int wPic = 8;
396 const int hPic = 8;
397 Gdk::Cairo::set_source_pixbuf(
398 cr, (iSampleRefs) ? yellowDot : redDot,
399 x + (w-wPic)/2.f,
400 y + (
401 (bShowLoopSymbol) ? margin : (h-hPic)/2.f
402 )
403 );
404 cr->paint();
405 }
406 if (bShowLoopSymbol) {
407 const int wPic = 12;
408 const int hPic = 14;
409 Gdk::Cairo::set_source_pixbuf(
410 cr, (iLoops == dimregs.size()) ? blackLoop : grayLoop,
411 x + (w-wPic)/2.f,
412 y + (
413 (bShowSampleRefSymbol) ? h - hPic - margin : (h-hPic)/2.f
414 )
415 );
416 cr->paint();
417 }
418 cr->restore();
419 }
420 }
421
422 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
423 bool DimRegionChooser::on_expose_event(GdkEventExpose* e)
424 {
425 double clipx1 = e->area.x;
426 double clipx2 = e->area.x + e->area.width;
427 double clipy1 = e->area.y;
428 double clipy2 = e->area.y + e->area.height;
429
430 const Cairo::RefPtr<Cairo::Context>& cr =
431 get_window()->create_cairo_context();
432 #else
433 bool DimRegionChooser::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
434 {
435 double clipx1, clipx2, clipy1, clipy2;
436 cr->get_clip_extents(clipx1, clipy1, clipx2, clipy2);
437 #endif
438
439 if (!region) return true;
440
441 // This is where we draw on the window
442 int w = get_width();
443 Glib::RefPtr<Pango::Context> context = get_pango_context();
444
445 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(context);
446 cr->set_line_width(1);
447
448 int y = 0;
449 if (labels_changed || label_width - 10 > clipx1) {
450 // draw labels on the left (reflecting the dimension type)
451 double maxwidth = 0;
452 for (int i = 0 ; i < region->Dimensions ; i++) {
453 int nbZones = region->pDimensionDefinitions[i].zones;
454 if (nbZones) {
455 const char* dstr;
456 char dstrbuf[10];
457 switch (region->pDimensionDefinitions[i].dimension) {
458 case gig::dimension_none: dstr=_("none"); break;
459 case gig::dimension_samplechannel: dstr=_("samplechannel");
460 break;
461 case gig::dimension_layer: dstr=_("layer"); break;
462 case gig::dimension_velocity: dstr=_("velocity"); break;
463 case gig::dimension_channelaftertouch:
464 dstr=_("channelaftertouch"); break;
465 case gig::dimension_releasetrigger:
466 dstr=_("releasetrigger"); break;
467 case gig::dimension_keyboard: dstr=_("keyswitching"); break;
468 case gig::dimension_roundrobin: dstr=_("roundrobin"); break;
469 case gig::dimension_random: dstr=_("random"); break;
470 case gig::dimension_smartmidi: dstr=_("smartmidi"); break;
471 case gig::dimension_roundrobinkeyboard:
472 dstr=_("roundrobinkeyboard"); break;
473 case gig::dimension_modwheel: dstr=_("modwheel"); break;
474 case gig::dimension_breath: dstr=_("breath"); break;
475 case gig::dimension_foot: dstr=_("foot"); break;
476 case gig::dimension_portamentotime:
477 dstr=_("portamentotime"); break;
478 case gig::dimension_effect1: dstr=_("effect1"); break;
479 case gig::dimension_effect2: dstr=_("effect2"); break;
480 case gig::dimension_genpurpose1: dstr=_("genpurpose1"); break;
481 case gig::dimension_genpurpose2: dstr=_("genpurpose2"); break;
482 case gig::dimension_genpurpose3: dstr=_("genpurpose3"); break;
483 case gig::dimension_genpurpose4: dstr=_("genpurpose4"); break;
484 case gig::dimension_sustainpedal:
485 dstr=_("sustainpedal"); break;
486 case gig::dimension_portamento: dstr=_("portamento"); break;
487 case gig::dimension_sostenutopedal:
488 dstr=_("sostenutopedal"); break;
489 case gig::dimension_softpedal: dstr=_("softpedal"); break;
490 case gig::dimension_genpurpose5: dstr=_("genpurpose5"); break;
491 case gig::dimension_genpurpose6: dstr=_("genpurpose6"); break;
492 case gig::dimension_genpurpose7: dstr=_("genpurpose7"); break;
493 case gig::dimension_genpurpose8: dstr=_("genpurpose8"); break;
494 case gig::dimension_effect1depth:
495 dstr=_("effect1depth"); break;
496 case gig::dimension_effect2depth:
497 dstr=_("effect2depth"); break;
498 case gig::dimension_effect3depth:
499 dstr=_("effect3depth"); break;
500 case gig::dimension_effect4depth:
501 dstr=_("effect4depth"); break;
502 case gig::dimension_effect5depth:
503 dstr=_("effect5depth"); break;
504 default:
505 sprintf(dstrbuf, "%d",
506 region->pDimensionDefinitions[i].dimension);
507 dstr = dstrbuf;
508 break;
509 }
510
511 // Since bold font yields in larger label width, we first always
512 // set the bold text variant, retrieve its dimensions (as worst
513 // case dimensions of the label) ...
514 layout->set_markup("<b>" + Glib::ustring(dstr) + "</b>");
515 Pango::Rectangle rectangle = layout->get_logical_extents();
516 // ... and then reset the label to regular font style in case
517 // the line is not selected. Otherwise the right hand side
518 // actual dimension zones would jump around on selection change.
519 bool isSelectedLine = (focus_line == i);
520 if (!isSelectedLine)
521 layout->set_markup(dstr);
522
523 double text_w = double(rectangle.get_width()) / Pango::SCALE;
524 if (text_w > maxwidth) maxwidth = text_w;
525
526 if (y + h > clipy1 && y < clipy2 && text_w >= clipx1) {
527 double text_h = double(rectangle.get_height()) /
528 Pango::SCALE;
529 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
530 const Gdk::Color fg = get_style()->get_fg(get_state());
531 #else
532 const Gdk::RGBA fg =
533 # if GTKMM_MAJOR_VERSION >= 3
534 get_style_context()->get_color();
535 # else
536 get_style_context()->get_color(get_state_flags());
537 # endif
538 #endif
539 Gdk::Cairo::set_source_rgba(cr, fg);
540 cr->move_to(4, int(y + (h - text_h) / 2 + 0.5));
541 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 16) || GTKMM_MAJOR_VERSION < 2
542 pango_cairo_show_layout(cr->cobj(), layout->gobj());
543 #else
544 layout->show_in_cairo_context(cr);
545 #endif
546 }
547 }
548 y += h;
549 }
550 label_width = int(maxwidth + 10);
551 labels_changed = false;
552 }
553 if (label_width >= clipx2) return true;
554
555 // draw dimensions' zones areas
556 y = 0;
557 int bitpos = 0;
558 for (int i = 0 ; i < region->Dimensions ; i++) {
559 int nbZones = region->pDimensionDefinitions[i].zones;
560 if (nbZones) {
561 const gig::dimension_t dimension = region->pDimensionDefinitions[i].dimension;
562
563 if (y >= clipy2) break;
564 if (y + h > clipy1) {
565 // draw focus rectangle around dimension's label and zones
566 if (has_focus() && focus_line == i) {
567 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
568 Gdk::Rectangle farea(0, y, 150, h);
569 get_style()->paint_focus(get_window(), get_state(), farea,
570 *this, "",
571 0, y, label_width, h);
572 #else
573 get_style_context()->render_focus(cr,
574 0, y, label_width, h);
575 #endif
576 }
577
578 // draw top and bottom lines of dimension's zones
579 Gdk::Cairo::set_source_rgba(cr, black);
580 cr->move_to(label_width, y + 0.5);
581 cr->line_to(w, y + 0.5);
582 cr->move_to(w, y + h - 0.5);
583 cr->line_to(label_width, y + h - 0.5);
584 cr->stroke();
585
586 // erase whole dimension's zones area
587 Gdk::Cairo::set_source_rgba(cr, white);
588 cr->rectangle(label_width + 1, y + 1,
589 (w - label_width - 2), h - 2);
590 cr->fill();
591
592 int c = 0;
593 if (maindimregno >= 0) {
594 int mask =
595 ~(((1 << region->pDimensionDefinitions[i].bits) - 1) <<
596 bitpos);
597 c = maindimregno & mask; // mask away this dimension
598 }
599 bool customsplits =
600 ((region->pDimensionDefinitions[i].split_type ==
601 gig::split_type_normal &&
602 region->pDimensionRegions[c]->DimensionUpperLimits[i]) ||
603 (region->pDimensionDefinitions[i].dimension ==
604 gig::dimension_velocity &&
605 region->pDimensionRegions[c]->VelocityUpperLimit));
606
607 // draw dimension zones
608 Gdk::Cairo::set_source_rgba(cr, black);
609 if (customsplits) {
610 cr->move_to(label_width + 0.5, y + 1);
611 cr->line_to(label_width + 0.5, y + h - 1);
612 int prevX = label_width;
613 int prevUpperLimit = -1;
614
615 for (int j = 0 ; j < nbZones ; j++) {
616 // draw dimension zone's borders for custom splits
617 gig::DimensionRegion* d =
618 region->pDimensionRegions[c + (j << bitpos)];
619 int upperLimit = d->DimensionUpperLimits[i];
620 if (!upperLimit) upperLimit = d->VelocityUpperLimit;
621 int v = upperLimit + 1;
622 int x = int((w - label_width - 1) * v / 128.0 + 0.5) +
623 label_width;
624 if (x >= clipx2) break;
625 if (x < clipx1) continue;
626 Gdk::Cairo::set_source_rgba(cr, black);
627 cr->move_to(x + 0.5, y + 1);
628 cr->line_to(x + 0.5, y + h - 1);
629 cr->stroke();
630
631 // draw fill for zone
632 bool isSelectedZone = this->dimzones[dimension].count(j);
633 bool isMainSelection =
634 this->maindimcase.find(dimension) != this->maindimcase.end() &&
635 this->maindimcase[dimension] == j;
636 bool isCheckBoxSelected =
637 modifyalldimregs ||
638 (modifybothchannels &&
639 dimension == gig::dimension_samplechannel);
640 if (isMainSelection)
641 Gdk::Cairo::set_source_rgba(cr, blue);
642 else if (isSelectedZone)
643 cr->set_source(blueHatchedSurfacePattern2);
644 else if (isCheckBoxSelected)
645 cr->set_source(blueHatchedSurfacePattern);
646 else
647 Gdk::Cairo::set_source_rgba(cr, white);
648
649 const int wZone = x - prevX - 1;
650
651 cr->rectangle(prevX + 1, y + 1, wZone, h - 1);
652 cr->fill();
653
654 // draw icons
655 drawIconsFor(dimension, j, cr, prevX, y, wZone, h);
656
657 // draw text showing the beginning of the dimension zone
658 // as numeric value to the user
659 {
660 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(context);
661 layout->set_text(Glib::Ascii::dtostr(prevUpperLimit+1));
662 Gdk::Cairo::set_source_rgba(cr, black);
663 // get the text dimensions
664 int text_width, text_height;
665 layout->get_pixel_size(text_width, text_height);
666 // move text to the left end of the dimension zone
667 cr->move_to(prevX + 3, y + (h - text_height) / 2);
668 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 16) || GTKMM_MAJOR_VERSION < 2
669 pango_cairo_show_layout(cr->cobj(), layout->gobj());
670 #else
671 layout->show_in_cairo_context(cr);
672 #endif
673 }
674 // draw text showing the end of the dimension zone
675 // as numeric value to the user
676 {
677 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(context);
678 layout->set_text(Glib::Ascii::dtostr(upperLimit));
679 Gdk::Cairo::set_source_rgba(cr, black);
680 // get the text dimensions
681 int text_width, text_height;
682 layout->get_pixel_size(text_width, text_height);
683 // move text to the left end of the dimension zone
684 cr->move_to(x - 3 - text_width, y + (h - text_height) / 2);
685 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 16) || GTKMM_MAJOR_VERSION < 2
686 pango_cairo_show_layout(cr->cobj(), layout->gobj());
687 #else
688 layout->show_in_cairo_context(cr);
689 #endif
690 }
691
692 prevX = x;
693 prevUpperLimit = upperLimit;
694 }
695 } else {
696 int prevX = 0;
697 for (int j = 0 ; j <= nbZones ; j++) {
698 // draw dimension zone's borders for normal splits
699 int x = int((w - label_width - 1) * j /
700 double(nbZones) + 0.5) + label_width;
701 if (x >= clipx2) break;
702 if (x < clipx1) continue;
703 Gdk::Cairo::set_source_rgba(cr, black);
704 cr->move_to(x + 0.5, y + 1);
705 cr->line_to(x + 0.5, y + h - 1);
706 cr->stroke();
707
708 if (j != 0) {
709 const int wZone = x - prevX - 1;
710
711 // draw fill for zone
712 bool isSelectedZone = this->dimzones[dimension].count(j-1);
713 bool isMainSelection =
714 this->maindimcase.find(dimension) != this->maindimcase.end() &&
715 this->maindimcase[dimension] == (j-1);
716 bool isCheckBoxSelected =
717 modifyalldimregs ||
718 (modifybothchannels &&
719 dimension == gig::dimension_samplechannel);
720 if (isMainSelection)
721 Gdk::Cairo::set_source_rgba(cr, blue);
722 else if (isSelectedZone)
723 cr->set_source(blueHatchedSurfacePattern2);
724 else if (isCheckBoxSelected)
725 cr->set_source(blueHatchedSurfacePattern);
726 else
727 Gdk::Cairo::set_source_rgba(cr, white);
728 cr->rectangle(prevX + 1, y + 1, wZone, h - 1);
729 cr->fill();
730
731 // draw icons
732 drawIconsFor(dimension, j - 1, cr, prevX, y, wZone, h);
733
734 // draw text showing the beginning of the dimension zone
735 // as numeric value to the user
736 {
737 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(context);
738 layout->set_text(Glib::Ascii::dtostr((j-1) * 128/nbZones));
739 Gdk::Cairo::set_source_rgba(cr, black);
740 // get the text dimensions
741 int text_width, text_height;
742 layout->get_pixel_size(text_width, text_height);
743 // move text to the left end of the dimension zone
744 cr->move_to(prevX + 3, y + (h - text_height) / 2);
745 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 16) || GTKMM_MAJOR_VERSION < 2
746 pango_cairo_show_layout(cr->cobj(), layout->gobj());
747 #else
748 layout->show_in_cairo_context(cr);
749 #endif
750 }
751 // draw text showing the end of the dimension zone
752 // as numeric value to the user
753 {
754 Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(context);
755 layout->set_text(Glib::Ascii::dtostr(j * 128/nbZones - 1));
756 Gdk::Cairo::set_source_rgba(cr, black);
757 // get the text dimensions
758 int text_width, text_height;
759 layout->get_pixel_size(text_width, text_height);
760 // move text to the left end of the dimension zone
761 cr->move_to(x - 3 - text_width, y + (h - text_height) / 2);
762 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 16) || GTKMM_MAJOR_VERSION < 2
763 pango_cairo_show_layout(cr->cobj(), layout->gobj());
764 #else
765 layout->show_in_cairo_context(cr);
766 #endif
767 }
768 }
769 prevX = x;
770 }
771 }
772 }
773 y += h;
774 }
775 bitpos += region->pDimensionDefinitions[i].bits;
776 }
777
778 return true;
779 }
780
781 void DimRegionChooser::set_region(gig::Region* region)
782 {
783 this->region = region;
784 maindimregno = 0;
785 nbDimensions = 0;
786 if (region) {
787 int bitcount = 0;
788 for (int dim = 0 ; dim < region->Dimensions ; dim++) {
789 if (region->pDimensionDefinitions[dim].bits == 0) continue;
790 nbDimensions++;
791
792 int z = std::min(maindimcase[region->pDimensionDefinitions[dim].dimension],
793 region->pDimensionDefinitions[dim].zones - 1);
794 maindimregno |= (z << bitcount);
795 bitcount += region->pDimensionDefinitions[dim].bits;
796 }
797 }
798 dimregion_selected();
799 set_size_request(800, region ? nbDimensions * h : 0);
800
801 labels_changed = true;
802 queue_resize();
803 queue_draw();
804 }
805
806 void DimRegionChooser::refresh_all() {
807 set_region(region);
808 }
809
810 void DimRegionChooser::get_dimregions(const gig::Region* region, bool stereo,
811 std::set<gig::DimensionRegion*>& dimregs) const
812 {
813 for (int iDimRgn = 0; iDimRgn < 256; ++iDimRgn) {
814 gig::DimensionRegion* dimRgn = region->pDimensionRegions[iDimRgn];
815 if (!dimRgn) continue;
816 bool isValidZone;
817 std::map<gig::dimension_t,int> dimCase = caseOfDimRegion(dimRgn, &isValidZone);
818 if (!isValidZone) continue;
819 for (std::map<gig::dimension_t,int>::const_iterator it = dimCase.begin();
820 it != dimCase.end(); ++it)
821 {
822 if (stereo && it->first == gig::dimension_samplechannel) continue; // is selected
823
824 std::map<gig::dimension_t, std::set<int> >::const_iterator itSelectedDimension =
825 this->dimzones.find(it->first);
826 if (itSelectedDimension != this->dimzones.end() &&
827 itSelectedDimension->second.count(it->second)) continue; // is selected
828
829 goto notSelected;
830 }
831
832 dimregs.insert(dimRgn);
833
834 notSelected:
835 ;
836 }
837 }
838
839 void DimRegionChooser::update_after_resize()
840 {
841 const uint8_t upperLimit = resize.pos - 1;
842 gig::Instrument* instr = (gig::Instrument*)region->GetParent();
843
844 int bitpos = 0;
845 for (int j = 0 ; j < resize.dimension ; j++) {
846 bitpos += region->pDimensionDefinitions[j].bits;
847 }
848
849 const int stereobitpos =
850 (modifybothchannels) ? baseBits(gig::dimension_samplechannel, region) : -1;
851
852 // the velocity dimension must be handled differently than all other
853 // dimension types, because
854 // 1. it is currently the only dimension type which allows different zone
855 // sizes for different cases
856 // 2. for v2 format VelocityUpperLimit has to be set, DimensionUpperLimits for v3
857 if (region->pDimensionDefinitions[resize.dimension].dimension == gig::dimension_velocity) {
858 int mask =
859 ~(((1 << region->pDimensionDefinitions[resize.dimension].bits) - 1) << bitpos);
860 int c = maindimregno & mask; // mask away this dimension
861
862 if (region->pDimensionRegions[c]->DimensionUpperLimits[resize.dimension] == 0) {
863 // the velocity dimension didn't previously have
864 // custom v3 splits, so we initialize all splits with
865 // default values
866 int nbZones = region->pDimensionDefinitions[resize.dimension].zones;
867 for (int j = 0 ; j < nbZones ; j++) {
868 gig::DimensionRegion* d = region->pDimensionRegions[c + (j << bitpos)];
869 d->DimensionUpperLimits[resize.dimension] = int(128.0 * (j + 1) / nbZones - 1);
870 }
871 }
872 if (region->pDimensionRegions[c]->VelocityUpperLimit == 0) {
873 // the velocity dimension didn't previously have
874 // custom v2 splits, so we initialize all splits with
875 // default values
876 int nbZones = region->pDimensionDefinitions[resize.dimension].zones;
877 for (int j = 0 ; j < nbZones ; j++) {
878 gig::DimensionRegion* d = region->pDimensionRegions[c + (j << bitpos)];
879 d->VelocityUpperLimit = int(128.0 * (j + 1) / nbZones - 1);
880 }
881 }
882
883 int index = c + (resize.zone << bitpos);
884 gig::DimensionRegion* d = region->pDimensionRegions[index];
885 // update both v2 and v3 values
886 d->DimensionUpperLimits[resize.dimension] = upperLimit;
887 d->VelocityUpperLimit = upperLimit;
888 if (modifybothchannels && stereobitpos >= 0) { // do the same for the other audio channel's dimregion ...
889 gig::DimensionRegion* d = region->pDimensionRegions[index ^ (1 << stereobitpos)];
890 d->DimensionUpperLimits[resize.dimension] = upperLimit;
891 d->VelocityUpperLimit = upperLimit;
892 }
893
894 if (modifyalldimregs) {
895 gig::Region* rgn = NULL;
896 for (int key = 0; key < 128; ++key) {
897 if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue;
898 rgn = instr->GetRegion(key);
899 if (!modifyallregions && rgn != region) continue; // hack to reduce overall code amount a bit
900 gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension);
901 if (!dimdef) continue;
902 if (dimdef->zones != resize.dimensionDef.zones) continue;
903 const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn);
904 assert(iDim >= 0 && iDim < rgn->Dimensions);
905
906 // the dimension layout might be completely different in this
907 // region, so we have to recalculate bitpos etc for this region
908 const int bitpos = baseBits(resize.dimensionDef.dimension, rgn);
909 const int stencil = ~(((1 << dimdef->bits) - 1) << bitpos);
910 const int selection = resize.zone << bitpos;
911
912 // primitive and inefficient loop implementation, however due to
913 // this circumstance the loop code is much simpler, and its lack
914 // of runtime efficiency should not be notable in practice
915 for (int idr = 0; idr < 256; ++idr) {
916 const int index = (idr & stencil) | selection;
917 assert(index >= 0 && index < 256);
918 gig::DimensionRegion* dr = rgn->pDimensionRegions[index];
919 if (!dr) continue;
920 dr->DimensionUpperLimits[iDim] = upperLimit;
921 d->VelocityUpperLimit = upperLimit;
922 }
923 }
924 } else if (modifyallregions) { // implies modifyalldimregs is false ...
925 // resolve the precise case we need to modify for all other regions
926 DimensionCase dimCase = dimensionCaseOf(d);
927 // apply the velocity upper limit change to that resolved dim case
928 // of all regions ...
929 gig::Region* rgn = NULL;
930 for (int key = 0; key < 128; ++key) {
931 if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue;
932 rgn = instr->GetRegion(key);
933 gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension);
934 if (!dimdef) continue;
935 if (dimdef->zones != resize.dimensionDef.zones) continue;
936 const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn);
937 assert(iDim >= 0 && iDim < rgn->Dimensions);
938
939 std::vector<gig::DimensionRegion*> dimrgns = dimensionRegionsMatching(dimCase, rgn);
940 for (int i = 0; i < dimrgns.size(); ++i) {
941 gig::DimensionRegion* dr = dimrgns[i];
942 dr->DimensionUpperLimits[iDim] = upperLimit;
943 dr->VelocityUpperLimit = upperLimit;
944 }
945 }
946 }
947 } else {
948 for (int i = 0 ; i < region->DimensionRegions ; ) {
949 if (region->pDimensionRegions[i]->DimensionUpperLimits[resize.dimension] == 0) {
950 // the dimension didn't previously have custom
951 // limits, so we have to set default limits for
952 // all the dimension regions
953 int nbZones = region->pDimensionDefinitions[resize.dimension].zones;
954
955 for (int j = 0 ; j < nbZones ; j++) {
956 gig::DimensionRegion* d = region->pDimensionRegions[i + (j << bitpos)];
957 d->DimensionUpperLimits[resize.dimension] = int(128.0 * (j + 1) / nbZones - 1);
958 }
959 }
960 int index = i + (resize.zone << bitpos);
961 gig::DimensionRegion* d = region->pDimensionRegions[index];
962 d->DimensionUpperLimits[resize.dimension] = upperLimit;
963 #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
964 if (modifybothchannels && stereobitpos >= 0) { // do the same for the other audio channel's dimregion ...
965 gig::DimensionRegion* d = region->pDimensionRegions[index ^ (1 << stereobitpos)];
966 d->DimensionUpperLimits[resize.dimension] = upperLimit;
967 }
968 #endif
969 int bitpos = 0;
970 int j;
971 for (j = 0 ; j < region->Dimensions ; j++) {
972 if (j != resize.dimension) {
973 int maxzones = 1 << region->pDimensionDefinitions[j].bits;
974 int dimj = (i >> bitpos) & (maxzones - 1);
975 if (dimj + 1 < region->pDimensionDefinitions[j].zones) break;
976 }
977 bitpos += region->pDimensionDefinitions[j].bits;
978 }
979 if (j == region->Dimensions) break;
980 i = (i & ~((1 << bitpos) - 1)) + (1 << bitpos);
981 }
982
983 if (modifyallregions) { // TODO: this code block could be merged with the similar (and more generalized) code block of the velocity dimension above
984 gig::Region* rgn = NULL;
985 for (int key = 0; key < 128; ++key) {
986 if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue;
987 rgn = instr->GetRegion(key);
988 gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(resize.dimensionDef.dimension);
989 if (!dimdef) continue;
990 if (dimdef->zones != resize.dimensionDef.zones) continue;
991 const int iDim = getDimensionIndex(resize.dimensionDef.dimension, rgn);
992 assert(iDim >= 0 && iDim < rgn->Dimensions);
993
994 // the dimension layout might be completely different in this
995 // region, so we have to recalculate bitpos etc for this region
996 const int bitpos = baseBits(resize.dimensionDef.dimension, rgn);
997 const int stencil = ~(((1 << dimdef->bits) - 1) << bitpos);
998 const int selection = resize.zone << bitpos;
999
1000 // this loop implementation is less efficient than the above's
1001 // loop implementation (which skips unnecessary dimension regions)
1002 // however this code is much simpler, and its lack of runtime
1003 // efficiency should not be notable in practice
1004 for (int idr = 0; idr < 256; ++idr) {
1005 const int index = (idr & stencil) | selection;
1006 assert(index >= 0 && index < 256);
1007 gig::DimensionRegion* dr = rgn->pDimensionRegions[index];
1008 if (!dr) continue;
1009 dr->DimensionUpperLimits[iDim] = upperLimit;
1010 }
1011 }
1012 }
1013 }
1014 }
1015
1016 bool DimRegionChooser::on_button_release_event(GdkEventButton* event)
1017 {
1018 if (resize.active) {
1019 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
1020 get_window()->pointer_ungrab(event->time);
1021 #else
1022 # if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 20)
1023 Glib::wrap(event->device, true)->ungrab(event->time);
1024 # else
1025 Glib::wrap(event->device, true)->get_seat()->ungrab();
1026 # endif
1027 #endif
1028 resize.active = false;
1029
1030 region_changed();
1031
1032 if (!is_in_resize_zone(event->x, event->y) && cursor_is_resize) {
1033 get_window()->set_cursor();
1034 cursor_is_resize = false;
1035 }
1036 }
1037 return true;
1038 }
1039
1040 bool DimRegionChooser::on_button_press_event(GdkEventButton* event)
1041 {
1042 int w = get_width();
1043 if (region && event->y < nbDimensions * h &&
1044 event->x >= label_width && event->x < w) {
1045
1046 if (is_in_resize_zone(event->x, event->y)) {
1047 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
1048 get_window()->pointer_grab(false,
1049 Gdk::BUTTON_RELEASE_MASK |
1050 Gdk::POINTER_MOTION_MASK |
1051 Gdk::POINTER_MOTION_HINT_MASK,
1052 Gdk::Cursor(Gdk::SB_H_DOUBLE_ARROW),
1053 event->time);
1054 #else
1055 # if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 20)
1056 Glib::wrap(event->device, true)->grab(get_window(),
1057 Gdk::OWNERSHIP_NONE,
1058 false,
1059 Gdk::BUTTON_RELEASE_MASK |
1060 Gdk::POINTER_MOTION_MASK |
1061 Gdk::POINTER_MOTION_HINT_MASK,
1062 Gdk::Cursor::create(Gdk::SB_H_DOUBLE_ARROW),
1063 event->time);
1064 # else
1065 Glib::wrap(event->device, true)->get_seat()->grab(
1066 get_window(),
1067 Gdk::SeatCapabilities::SEAT_CAPABILITY_ALL_POINTING,
1068 false,
1069 Gdk::Cursor::create(
1070 Glib::wrap(event->device, true)->get_seat()->get_display(),
1071 Gdk::SB_H_DOUBLE_ARROW
1072 ),
1073 reinterpret_cast<GdkEvent*>(event)
1074 );
1075 # endif
1076 #endif
1077 resize.active = true;
1078 } else {
1079 int ydim = int(event->y / h);
1080 int dim;
1081 for (dim = 0 ; dim < region->Dimensions ; dim++) {
1082 if (region->pDimensionDefinitions[dim].bits == 0) continue;
1083 if (ydim == 0) break;
1084 ydim--;
1085 }
1086 int nbZones = region->pDimensionDefinitions[dim].zones;
1087
1088 int z = -1;
1089 int bitpos = 0;
1090 for (int i = 0 ; i < dim ; i++) {
1091 bitpos += region->pDimensionDefinitions[i].bits;
1092 }
1093
1094 int i = dim;
1095 if (maindimregno < 0) maindimregno = 0;
1096 int mask = ~(((1 << region->pDimensionDefinitions[i].bits) - 1) << bitpos);
1097 int c = this->maindimregno & mask; // mask away this dimension
1098
1099 bool customsplits =
1100 ((region->pDimensionDefinitions[i].split_type == gig::split_type_normal &&
1101 region->pDimensionRegions[c]->DimensionUpperLimits[i]) ||
1102 (region->pDimensionDefinitions[i].dimension == gig::dimension_velocity &&
1103 region->pDimensionRegions[c]->VelocityUpperLimit));
1104 if (customsplits) {
1105 int val = int((event->x - label_width) * 128 / (w - label_width - 1));
1106
1107 if (region->pDimensionRegions[c]->DimensionUpperLimits[i]) {
1108 for (z = 0 ; z < nbZones ; z++) {
1109 gig::DimensionRegion* d = region->pDimensionRegions[c + (z << bitpos)];
1110 if (val <= d->DimensionUpperLimits[i]) break;
1111 }
1112 } else {
1113 for (z = 0 ; z < nbZones ; z++) {
1114 gig::DimensionRegion* d = region->pDimensionRegions[c + (z << bitpos)];
1115 if (val <= d->VelocityUpperLimit) break;
1116 }
1117 }
1118 } else {
1119 z = int((event->x - label_width) * nbZones / (w - label_width - 1));
1120 }
1121
1122 printf("dim=%d z=%d dimensionsource=%d split_type=%d zones=%d zone_size=%f\n", dim, z,
1123 region->pDimensionDefinitions[dim].dimension,
1124 region->pDimensionDefinitions[dim].split_type,
1125 region->pDimensionDefinitions[dim].zones,
1126 region->pDimensionDefinitions[dim].zone_size);
1127 this->maindimcase[region->pDimensionDefinitions[dim].dimension] = z;
1128 this->maindimregno = c | (z << bitpos);
1129 this->maindimtype = region->pDimensionDefinitions[dim].dimension;
1130
1131 if (multiSelectKeyDown) {
1132 if (dimzones[this->maindimtype].count(z)) {
1133 if (dimzones[this->maindimtype].size() > 1) {
1134 dimzones[this->maindimtype].erase(z);
1135 }
1136 } else {
1137 dimzones[this->maindimtype].insert(z);
1138 }
1139 } else {
1140 this->dimzones.clear();
1141 for (std::map<gig::dimension_t,int>::const_iterator it = this->maindimcase.begin();
1142 it != this->maindimcase.end(); ++it)
1143 {
1144 this->dimzones[it->first].insert(it->second);
1145 }
1146 }
1147
1148 focus_line = dim;
1149 if (has_focus()) queue_draw();
1150 else grab_focus();
1151 dimregion_selected();
1152
1153 if (event->button == 3) {
1154 printf("dimregion right click\n");
1155 popup_menu_inside_dimregion->popup(event->button, event->time);
1156 }
1157
1158 queue_draw();
1159 }
1160 }
1161 return true;
1162 }
1163
1164 bool DimRegionChooser::on_motion_notify_event(GdkEventMotion* event)
1165 {
1166 Glib::RefPtr<Gdk::Window> window = get_window();
1167 int x, y;
1168 #if HAS_GDKMM_SEAT
1169 x = event->x;
1170 y = event->y;
1171 Gdk::ModifierType state = Gdk::ModifierType(event->state);
1172 #else
1173 Gdk::ModifierType state = Gdk::ModifierType(0);
1174 window->get_pointer(x, y, state);
1175 #endif
1176
1177 if (resize.active) {
1178 int w = get_width();
1179 int k = int((x - label_width) * 128.0 / (w - label_width - 1) + 0.5);
1180
1181 if (k < resize.min) k = resize.min;
1182 else if (k > resize.max) k = resize.max;
1183
1184 if (k < 2) k = 2; // k is upper limit + 1, upper limit 0 is forbidden
1185
1186 if (k != resize.pos) {
1187 int prevx = int((w - label_width - 1) * resize.pos / 128.0 + 0.5) + label_width;
1188 int x = int((w - label_width - 1) * k / 128.0 + 0.5) + label_width;
1189 int y = resize.dimension * h;
1190 int x1, x2;
1191 if (k > resize.pos) {
1192 x1 = prevx;
1193 x2 = x;
1194 } else {
1195 x1 = x;
1196 x2 = prevx;
1197 }
1198 Gdk::Rectangle rect(x1, y + 1, x2 - x1 + 1, h - 2);
1199
1200 resize.pos = k;
1201 update_after_resize();
1202 get_window()->invalidate_rect(rect, false); // not sufficient ...
1203 queue_draw(); // ... so do a complete redraw instead.
1204 }
1205 } else {
1206 if (is_in_resize_zone(x, y)) {
1207 if (!cursor_is_resize) {
1208 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
1209 window->set_cursor(Gdk::Cursor(Gdk::SB_H_DOUBLE_ARROW));
1210 #else
1211 window->set_cursor(
1212 # if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 20)
1213 Gdk::Cursor::create(Gdk::SB_H_DOUBLE_ARROW)
1214 # else
1215 Gdk::Cursor::create(
1216 Glib::wrap(event->device, true)->get_seat()->get_display(),
1217 Gdk::SB_H_DOUBLE_ARROW
1218 )
1219 # endif
1220 );
1221 #endif
1222 cursor_is_resize = true;
1223 }
1224 } else if (cursor_is_resize) {
1225 window->set_cursor();
1226 cursor_is_resize = false;
1227 }
1228 }
1229 return true;
1230 }
1231
1232 bool DimRegionChooser::is_in_resize_zone(double x, double y)
1233 {
1234 int w = get_width();
1235 if (region && y < nbDimensions * h && x >= label_width && x < w) {
1236 int ydim = int(y / h);
1237 int dim;
1238 int bitpos = 0;
1239 for (dim = 0 ; dim < region->Dimensions ; dim++) {
1240 if (region->pDimensionDefinitions[dim].bits == 0) continue;
1241 if (ydim == 0) break;
1242 ydim--;
1243 bitpos += region->pDimensionDefinitions[dim].bits;
1244 }
1245 int nbZones = region->pDimensionDefinitions[dim].zones;
1246
1247 int c = 0;
1248 if (maindimregno >= 0) {
1249 int mask = ~(((1 << region->pDimensionDefinitions[dim].bits) - 1) << bitpos);
1250 c = maindimregno & mask; // mask away this dimension
1251 }
1252 const bool customsplits =
1253 ((region->pDimensionDefinitions[dim].split_type == gig::split_type_normal &&
1254 region->pDimensionRegions[c]->DimensionUpperLimits[dim]) ||
1255 (region->pDimensionDefinitions[dim].dimension == gig::dimension_velocity &&
1256 region->pDimensionRegions[c]->VelocityUpperLimit));
1257
1258 // dimensions of split_type_bit cannot be resized
1259 if (region->pDimensionDefinitions[dim].split_type != gig::split_type_bit) {
1260 int prev_limit = 0;
1261 for (int iZone = 0 ; iZone < nbZones - 1 ; iZone++) {
1262 gig::DimensionRegion* d = region->pDimensionRegions[c + (iZone << bitpos)];
1263 const int upperLimit =
1264 (customsplits) ?
1265 (d->DimensionUpperLimits[dim]) ?
1266 d->DimensionUpperLimits[dim] : d->VelocityUpperLimit
1267 : (iZone+1) * (int)region->pDimensionDefinitions[dim].zone_size - 1;
1268 int limit = upperLimit + 1;
1269 int limitx = int((w - label_width - 1) * limit / 128.0 + 0.5) + label_width;
1270 if (x <= limitx - 2) break;
1271 if (x <= limitx + 2) {
1272 resize.dimension = dim;
1273 resize.dimensionDef = region->pDimensionDefinitions[dim];
1274 resize.zone = iZone;
1275 resize.pos = limit;
1276 resize.min = prev_limit;
1277
1278 int dr = (maindimregno >> bitpos) &
1279 ((1 << region->pDimensionDefinitions[dim].bits) - 1);
1280 resize.selected = dr == iZone ? resize.left :
1281 dr == iZone + 1 ? resize.right : resize.none;
1282
1283 iZone++;
1284 gig::DimensionRegion* d = region->pDimensionRegions[c + (iZone << bitpos)];
1285
1286 const int upperLimit =
1287 (customsplits) ?
1288 (d->DimensionUpperLimits[dim]) ?
1289 d->DimensionUpperLimits[dim] : d->VelocityUpperLimit
1290 : (iZone+1) * (int)region->pDimensionDefinitions[dim].zone_size - 1;
1291
1292 int limit = upperLimit + 1;
1293 resize.max = limit;
1294 return true;
1295 }
1296 prev_limit = limit;
1297 }
1298 }
1299 }
1300 return false;
1301 }
1302
1303 sigc::signal<void>& DimRegionChooser::signal_dimregion_selected()
1304 {
1305 return dimregion_selected;
1306 }
1307
1308 sigc::signal<void>& DimRegionChooser::signal_region_changed()
1309 {
1310 return region_changed;
1311 }
1312
1313 bool DimRegionChooser::on_focus(Gtk::DirectionType direction)
1314 {
1315 // TODO: check that region exists etc, that is, that it's possible
1316 // to set focus
1317 if (direction == Gtk::DIR_TAB_FORWARD ||
1318 direction == Gtk::DIR_DOWN) {
1319 if (!has_focus()) {
1320 focus_line = 0;
1321 grab_focus();
1322 return true;
1323 } else {
1324 if (focus_line + 1 < region->Dimensions) {
1325 focus_line++;
1326 queue_draw();
1327 return true;
1328 } else {
1329 return false;
1330 }
1331 }
1332 } else if (direction == Gtk::DIR_TAB_BACKWARD ||
1333 direction == Gtk::DIR_UP) {
1334 if (!has_focus()) {
1335 focus_line = region->Dimensions - 1;
1336 grab_focus();
1337 return true;
1338 } else {
1339 if (focus_line > 0) {
1340 focus_line--;
1341 queue_draw();
1342 return true;
1343 } else {
1344 return false;
1345 }
1346 }
1347 } else if (!has_focus()) {
1348 // TODO: check that focus_line exists
1349 grab_focus();
1350 return true;
1351 } else {
1352 // TODO: increase or decrease value
1353 }
1354 return false;
1355 }
1356
1357 void DimRegionChooser::split_dimension_zone() {
1358 printf("split_dimension_zone() type=%d, zone=%d\n", maindimtype, maindimcase[maindimtype]);
1359 try {
1360 if (!modifyallregions) {
1361 region->SplitDimensionZone(maindimtype, maindimcase[maindimtype]);
1362 } else {
1363 gig::Instrument* instr = (gig::Instrument*)region->GetParent();
1364 gig::dimension_def_t* pMaindimdef = region->GetDimensionDefinition(maindimtype);
1365 assert(pMaindimdef != NULL);
1366 // retain structure by value since the original region will be
1367 // modified in the loop below as well
1368 gig::dimension_def_t maindimdef = *pMaindimdef;
1369 std::vector<gig::Region*> ignoredAll;
1370 std::vector<gig::Region*> ignoredMinor;
1371 std::vector<gig::Region*> ignoredCritical;
1372 gig::Region* rgn = NULL;
1373 for (int key = 0; key < 128; ++key) {
1374 if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue;
1375 rgn = instr->GetRegion(key);
1376
1377 // ignore all regions which do not exactly match the dimension
1378 // layout of the selected region where this operation was emitted
1379 gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(maindimtype);
1380 if (!dimdef) {
1381 ignoredAll.push_back(rgn);
1382 ignoredMinor.push_back(rgn);
1383 continue;
1384 }
1385 if (dimdef->zones != maindimdef.zones) {
1386 ignoredAll.push_back(rgn);
1387 ignoredCritical.push_back(rgn);
1388 continue;
1389 }
1390
1391 rgn->SplitDimensionZone(maindimtype, maindimcase[maindimtype]);
1392 }
1393 if (!ignoredAll.empty()) {
1394 Glib::ustring txt;
1395 if (ignoredCritical.empty())
1396 txt = ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type.");
1397 else if (ignoredMinor.empty())
1398 txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones!");
1399 else
1400 txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones (and ") +
1401 ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type)!");
1402 Gtk::MessageType type = (ignoredCritical.empty()) ? Gtk::MESSAGE_INFO : Gtk::MESSAGE_WARNING;
1403 Gtk::MessageDialog msg(txt, false, type);
1404 msg.run();
1405 }
1406 }
1407 } catch (RIFF::Exception e) {
1408 Gtk::MessageDialog msg(e.Message, false, Gtk::MESSAGE_ERROR);
1409 msg.run();
1410 } catch (...) {
1411 Glib::ustring txt = _("An unknown exception occurred!");
1412 Gtk::MessageDialog msg(txt, false, Gtk::MESSAGE_ERROR);
1413 msg.run();
1414 }
1415 refresh_all();
1416 }
1417
1418 void DimRegionChooser::delete_dimension_zone() {
1419 printf("delete_dimension_zone() type=%d, zone=%d\n", maindimtype, maindimcase[maindimtype]);
1420 try {
1421 if (!modifyallregions) {
1422 region->DeleteDimensionZone(maindimtype, maindimcase[maindimtype]);
1423 } else {
1424 gig::Instrument* instr = (gig::Instrument*)region->GetParent();
1425 gig::dimension_def_t* pMaindimdef = region->GetDimensionDefinition(maindimtype);
1426 assert(pMaindimdef != NULL);
1427 // retain structure by value since the original region will be
1428 // modified in the loop below as well
1429 gig::dimension_def_t maindimdef = *pMaindimdef;
1430 std::vector<gig::Region*> ignoredAll;
1431 std::vector<gig::Region*> ignoredMinor;
1432 std::vector<gig::Region*> ignoredCritical;
1433 gig::Region* rgn = NULL;
1434 for (int key = 0; key < 128; ++key) {
1435 if (!instr->GetRegion(key) || instr->GetRegion(key) == rgn) continue;
1436 rgn = instr->GetRegion(key);
1437
1438 // ignore all regions which do not exactly match the dimension
1439 // layout of the selected region where this operation was emitted
1440 gig::dimension_def_t* dimdef = rgn->GetDimensionDefinition(maindimtype);
1441 if (!dimdef) {
1442 ignoredAll.push_back(rgn);
1443 ignoredMinor.push_back(rgn);
1444 continue;
1445 }
1446 if (dimdef->zones != maindimdef.zones) {
1447 ignoredAll.push_back(rgn);
1448 ignoredCritical.push_back(rgn);
1449 continue;
1450 }
1451
1452 rgn->DeleteDimensionZone(maindimtype, maindimcase[maindimtype]);
1453 }
1454 if (!ignoredAll.empty()) {
1455 Glib::ustring txt;
1456 if (ignoredCritical.empty())
1457 txt = ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type.");
1458 else if (ignoredMinor.empty())
1459 txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones!");
1460 else
1461 txt = ToString(ignoredCritical.size()) + _(" regions have been ignored due to different amount of dimension zones (and ") +
1462 ToString(ignoredMinor.size()) + _(" regions have been ignored since they don't have that dimension type)!");
1463 Gtk::MessageType type = (ignoredCritical.empty()) ? Gtk::MESSAGE_INFO : Gtk::MESSAGE_WARNING;
1464 Gtk::MessageDialog msg(txt, false, type);
1465 msg.run();
1466 }
1467 }
1468 } catch (RIFF::Exception e) {
1469 Gtk::MessageDialog msg(e.Message, false, Gtk::MESSAGE_ERROR);
1470 msg.run();
1471 } catch (...) {
1472 Glib::ustring txt = _("An unknown exception occurred!");
1473 Gtk::MessageDialog msg(txt, false, Gtk::MESSAGE_ERROR);
1474 msg.run();
1475 }
1476 refresh_all();
1477 }
1478
1479 // Cmd key on Mac, Ctrl key on all other OSs
1480 static const guint primaryKeyL =
1481 #if defined(__APPLE__)
1482 GDK_KEY_Meta_L;
1483 #else
1484 GDK_KEY_Control_L;
1485 #endif
1486
1487 static const guint primaryKeyR =
1488 #if defined(__APPLE__)
1489 GDK_KEY_Meta_R;
1490 #else
1491 GDK_KEY_Control_R;
1492 #endif
1493
1494 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
1495 bool DimRegionChooser::onKeyPressed(Gdk::EventKey& _key) {
1496 GdkEventKey* key = _key.gobj();
1497 #else
1498 bool DimRegionChooser::onKeyPressed(GdkEventKey* key) {
1499 #endif
1500 //printf("key down 0x%x\n", key->keyval);
1501 if (key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R)
1502 multiSelectKeyDown = true;
1503 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
1504 primaryKeyDown = true;
1505 if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R)
1506 shiftKeyDown = true;
1507
1508 //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
1509 /*if (key->keyval == GDK_KEY_Left)
1510 select_prev_dimzone();
1511 if (key->keyval == GDK_KEY_Right)
1512 select_next_dimzone();
1513 if (key->keyval == GDK_KEY_Up)
1514 select_prev_dimension();
1515 if (key->keyval == GDK_KEY_Down)
1516 select_next_dimension();*/
1517 return false;
1518 }
1519
1520 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
1521 bool DimRegionChooser::onKeyReleased(Gdk::EventKey& _key) {
1522 GdkEventKey* key = _key.gobj();
1523 #else
1524 bool DimRegionChooser::onKeyReleased(GdkEventKey* key) {
1525 #endif
1526 //printf("key up 0x%x\n", key->keyval);
1527 if (key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R)
1528 multiSelectKeyDown = false;
1529 if (key->keyval == primaryKeyL || key->keyval == primaryKeyR)
1530 primaryKeyDown = false;
1531 if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R)
1532 shiftKeyDown = false;
1533
1534 if (!has_focus()) return false;
1535
1536 // avoid conflict with Ctrl+Left and Ctrl+Right accelerators on mainwindow
1537 // (which is supposed to switch between regions)
1538 if (primaryKeyDown) return false;
1539
1540 // avoid conflict with Alt+Shift+Left and Alt+Shift+Right accelerators on
1541 // mainwindow
1542 if (shiftKeyDown) return false;
1543
1544 if (key->keyval == GDK_KEY_Left)
1545 select_prev_dimzone();
1546 if (key->keyval == GDK_KEY_Right)
1547 select_next_dimzone();
1548 if (key->keyval == GDK_KEY_Up)
1549 select_prev_dimension();
1550 if (key->keyval == GDK_KEY_Down)
1551 select_next_dimension();
1552
1553 return false;
1554 }
1555
1556 void DimRegionChooser::resetSelectedZones() {
1557 this->dimzones.clear();
1558 if (!region) {
1559 queue_draw(); // redraw required parts
1560 return;
1561 }
1562 if (maindimregno < 0 || maindimregno >= region->DimensionRegions) {
1563 queue_draw(); // redraw required parts
1564 return;
1565 }
1566 if (!region->pDimensionRegions[maindimregno]) {
1567 queue_draw(); // redraw required parts
1568 return;
1569 }
1570 gig::DimensionRegion* dimrgn = region->pDimensionRegions[maindimregno];
1571
1572 bool isValidZone;
1573 this->maindimcase = dimensionCaseOf(dimrgn);
1574
1575 for (std::map<gig::dimension_t,int>::const_iterator it = this->maindimcase.begin();
1576 it != this->maindimcase.end(); ++it)
1577 {
1578 this->dimzones[it->first].insert(it->second);
1579 }
1580
1581 // redraw required parts
1582 queue_draw();
1583 }
1584
1585 bool DimRegionChooser::select_dimregion(gig::DimensionRegion* dimrgn) {
1586 if (!region) return false; //.selection failed
1587
1588 for (int dr = 0; dr < region->DimensionRegions && region->pDimensionRegions[dr]; ++dr) {
1589 if (region->pDimensionRegions[dr] == dimrgn) {
1590 // reset dim region zone selection to the requested specific dim region case
1591 maindimregno = dr;
1592 resetSelectedZones();
1593
1594 // emit signal that dimregion selection has changed, for external entities
1595 dimregion_selected();
1596
1597 return true; // selection success
1598 }
1599 }
1600
1601 return false; //.selection failed
1602 }
1603
1604 void DimRegionChooser::select_next_dimzone(bool add) {
1605 select_dimzone_by_dir(+1, add);
1606 }
1607
1608 void DimRegionChooser::select_prev_dimzone(bool add) {
1609 select_dimzone_by_dir(-1, add);
1610 }
1611
1612 void DimRegionChooser::select_dimzone_by_dir(int dir, bool add) {
1613 if (!region) return;
1614 if (!region->Dimensions) return;
1615 if (focus_line < 0) focus_line = 0;
1616 if (focus_line >= region->Dimensions) focus_line = region->Dimensions - 1;
1617
1618 maindimtype = region->pDimensionDefinitions[focus_line].dimension;
1619 if (maindimtype == gig::dimension_none) {
1620 printf("maindimtype -> none\n");
1621 return;
1622 }
1623
1624 // commented out: re-evaluate maindimcase, since it might not been reset from a previous instrument which causes errors if it got different dimension types
1625 //if (maindimcase.empty()) {
1626 maindimcase = dimensionCaseOf(region->pDimensionRegions[maindimregno]);
1627 if (maindimcase.empty()) {
1628 printf("caseOfDimregion(%d) -> empty\n", maindimregno);
1629 return;
1630 }
1631 //}
1632
1633 int z = (dir > 0) ? maindimcase[maindimtype] + 1 : maindimcase[maindimtype] - 1;
1634 if (z < 0) z = 0;
1635 if (z >= region->pDimensionDefinitions[focus_line].zones)
1636 z = region->pDimensionDefinitions[focus_line].zones - 1;
1637
1638 maindimcase[maindimtype] = z;
1639
1640 ::gig::DimensionRegion* dr = dimensionRegionMatching(maindimcase, region);
1641 if (!dr) {
1642 printf("select_dimzone_by_dir(%d) -> !dr\n", dir);
1643 return;
1644 }
1645
1646 maindimregno = getDimensionRegionIndex(dr);
1647
1648 if (!add) {
1649 // reset selected dimregion zones
1650 dimzones.clear();
1651 }
1652 for (DimensionCase::const_iterator it = maindimcase.begin();
1653 it != maindimcase.end(); ++it)
1654 {
1655 dimzones[it->first].insert(it->second);
1656 }
1657
1658 dimregion_selected();
1659
1660 // disabled: would overwrite dimregno with wrong value
1661 //refresh_all();
1662 // so requesting just a raw repaint instead:
1663 queue_draw();
1664 }
1665
1666 void DimRegionChooser::select_next_dimension() {
1667 if (!region) return;
1668 focus_line++;
1669 if (focus_line >= region->Dimensions)
1670 focus_line = region->Dimensions - 1;
1671 this->maindimtype = region->pDimensionDefinitions[focus_line].dimension;
1672 queue_draw();
1673 }
1674
1675 void DimRegionChooser::select_prev_dimension() {
1676 if (!region) return;
1677 focus_line--;
1678 if (focus_line < 0)
1679 focus_line = 0;
1680 this->maindimtype = region->pDimensionDefinitions[focus_line].dimension;
1681 queue_draw();
1682 }
1683
1684 gig::DimensionRegion* DimRegionChooser::get_main_dimregion() const {
1685 if (!region) return NULL;
1686 return region->pDimensionRegions[maindimregno];
1687 }

  ViewVC Help
Powered by ViewVC