/* * Copyright (C) 2006-2014 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 * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with program; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA. */ #include // threads.h must be included first to be able to build with // G_DISABLE_DEPRECATED #if (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION == 31 && GLIBMM_MICRO_VERSION >= 2) || \ (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION > 31) || GLIBMM_MAJOR_VERSION > 2 #include #endif #include "dimensionmanager.h" #include #include #include #include #include #include "global.h" #include "compat.h" // returns a human readable name of the given dimension type Glib::ustring dimTypeAsString(gig::dimension_t d) { char buf[32]; switch (d) { case gig::dimension_none: return _("None"); case gig::dimension_samplechannel: return _("Sample Channel"); case gig::dimension_layer: return _("Layer"); case gig::dimension_velocity: return _("Velocity"); case gig::dimension_channelaftertouch: return _("Aftertouch"); case gig::dimension_releasetrigger: return _("Release Trigger"); case gig::dimension_keyboard: return _("Keyswitching"); case gig::dimension_roundrobin: return _("Round Robin"); case gig::dimension_random: return _("Random Generator"); case gig::dimension_smartmidi: return _("Smart MIDI"); case gig::dimension_roundrobinkeyboard: return _("Keyboard Round Robin"); case gig::dimension_modwheel: return _("Modulation Wheel"); case gig::dimension_breath: return _("Breath Ctrl."); case gig::dimension_foot: return _("Foot Ctrl."); case gig::dimension_portamentotime: return _("Portamento Time Ctrl."); case gig::dimension_effect1: return _("Effect Ctrl. 1"); case gig::dimension_effect2: return _("Effect Ctrl. 2"); case gig::dimension_genpurpose1: return _("General Purpose Ctrl. 1"); case gig::dimension_genpurpose2: return _("General Purpose Ctrl. 2"); case gig::dimension_genpurpose3: return _("General Purpose Ctrl. 3"); case gig::dimension_genpurpose4: return _("General Purpose Ctrl. 4"); case gig::dimension_sustainpedal: return _("Sustain Pedal"); case gig::dimension_portamento: return _("Portamento Ctrl."); case gig::dimension_sostenutopedal: return _("Sostenuto Pedal"); case gig::dimension_softpedal: return _("Soft Pedal"); case gig::dimension_genpurpose5: return _("General Purpose Ctrl. 5"); case gig::dimension_genpurpose6: return _("General Purpose Ctrl. 6"); case gig::dimension_genpurpose7: return _("General Purpose Ctrl. 7"); case gig::dimension_genpurpose8: return _("General Purpose Ctrl. 8"); case gig::dimension_effect1depth: return _("Effect 1 Depth"); case gig::dimension_effect2depth: return _("Effect 2 Depth"); case gig::dimension_effect3depth: return _("Effect 3 Depth"); case gig::dimension_effect4depth: return _("Effect 4 Depth"); case gig::dimension_effect5depth: return _("Effect 5 Depth"); default: sprintf(buf, "Unknown Type (0x%x) !!!", d); return buf; } } // returns a human readable description of the given dimension static Glib::ustring __dimDescriptionAsString(gig::dimension_t d) { switch (d) { case gig::dimension_none: return _("Dimension not in use"); case gig::dimension_samplechannel: return _("If used sample has more than one channel (thus is not mono)"); case gig::dimension_layer: return _("For layering of up to 8 instruments (and eventually crossfading of 2 or 4 layers"); case gig::dimension_velocity: return _("Key Velocity (this is the only dimension in gig2 where the ranges can exactly be defined)"); case gig::dimension_channelaftertouch: return _("Channel Key Pressure"); case gig::dimension_releasetrigger: return _("Special dimension for triggering samples on releasing a key"); case gig::dimension_keyboard: return _("Dimension for keyswitching (keyboard)"); case gig::dimension_roundrobin: return _("Different samples triggered each time a note is played, dimension regions selected in sequence"); case gig::dimension_random: return _("Different samples triggered each time a note is played, random order"); case gig::dimension_smartmidi: return _("For MIDI tools like legato and repetition mode"); case gig::dimension_roundrobinkeyboard: return _("Different samples triggered each time a note is played, any key advances the counter"); case gig::dimension_modwheel: return _("MIDI Controller 1"); case gig::dimension_breath: return _("MIDI Controller 2"); case gig::dimension_foot: return _("MIDI Controller 4"); case gig::dimension_portamentotime: return _("MIDI Controller 5"); case gig::dimension_effect1: return _("MIDI Controller 12"); case gig::dimension_effect2: return _("MIDI Controller 13"); case gig::dimension_genpurpose1: return _("Slider, MIDI Controller 16"); case gig::dimension_genpurpose2: return _("Slider, MIDI Controller 17"); case gig::dimension_genpurpose3: return _("Slider, MIDI Controller 18"); case gig::dimension_genpurpose4: return _("Slider, MIDI Controller 19"); case gig::dimension_sustainpedal: return _("MIDI Controller 64"); case gig::dimension_portamento: return _("MIDI Controller 65"); case gig::dimension_sostenutopedal: return _("MIDI Controller 66"); case gig::dimension_softpedal: return _("MIDI Controller 67"); case gig::dimension_genpurpose5: return _("Button, MIDI Controller 80"); case gig::dimension_genpurpose6: return _("Button, MIDI Controller 81"); case gig::dimension_genpurpose7: return _("Button, MIDI Controller 82"); case gig::dimension_genpurpose8: return _("Button, MIDI Controller 83"); case gig::dimension_effect1depth: return _("MIDI Controller 91"); case gig::dimension_effect2depth: return _("MIDI Controller 92"); case gig::dimension_effect3depth: return _("MIDI Controller 93"); case gig::dimension_effect4depth: return _("MIDI Controller 94"); case gig::dimension_effect5depth: return _("MIDI Controller 95"); default: return _("Please report this !!!"); } } DimTypeCellRenderer::DimTypeCellRenderer() : Glib::ObjectBase(typeid(DimTypeCellRenderer)), Gtk::CellRendererText(), m_propertyDimType(*this, "gigdimension_t", gig::dimension_none), m_propertyUsageCount(*this, "intusagecount", 0), m_propertyTotalRegions(*this, "inttotalregions", 0) { propertyDimType().signal_changed().connect( sigc::mem_fun(*this, &DimTypeCellRenderer::typeChanged) ); propertyUsageCount().signal_changed().connect( sigc::mem_fun(*this, &DimTypeCellRenderer::statsChanged) ); propertyTotalRegions().signal_changed().connect( sigc::mem_fun(*this, &DimTypeCellRenderer::statsChanged) ); } void DimTypeCellRenderer::typeChanged() { gig::dimension_t type = propertyDimType(); Glib::ustring s = dimTypeAsString(type); property_text() = s; } void DimTypeCellRenderer::statsChanged() { int usageCount = propertyUsageCount(); int totalRegions = propertyTotalRegions(); bool bDimensionExistsOnAllRegions = (usageCount == totalRegions); property_foreground() = ((bDimensionExistsOnAllRegions) ? "black" : "gray"); } IntSetCellRenderer::IntSetCellRenderer() : Glib::ObjectBase(typeid(IntSetCellRenderer)), Gtk::CellRendererText(), m_propertyValue(*this, "stdintset", std::set()) { propertyValue().signal_changed().connect( sigc::mem_fun(*this, &IntSetCellRenderer::valueChanged) ); } void IntSetCellRenderer::valueChanged() { Glib::ustring s; std::set v = propertyValue(); for (std::set::const_iterator it = v.begin(); it != v.end(); ++it) { s += ToString(*it); if (*it != *v.rbegin()) s += "|"; } property_text() = s; property_foreground() = (v.size() > 1) ? "gray" : "black"; } DimensionManager::DimensionManager() : addButton(_("_Add"), true), removeButton(_("_Remove"), true), allRegionsCheckBox(_("All Regions")) { ignoreColumnClicked = true; set_title(_("Dimensions of selected Region")); add(vbox); scrolledWindow.add(treeView); vbox.pack_start(scrolledWindow); scrolledWindow.show(); vbox.pack_start(buttonBox, Gtk::PACK_SHRINK); buttonBox.set_layout(Gtk::BUTTONBOX_END); buttonBox.set_border_width(5); buttonBox.show(); buttonBox.pack_start(allRegionsCheckBox, Gtk::PACK_EXPAND_PADDING); buttonBox.pack_start(addButton, Gtk::PACK_SHRINK); buttonBox.pack_start(removeButton, Gtk::PACK_SHRINK); addButton.show(); removeButton.show(); allRegionsCheckBox.set_tooltip_text( _("Enable this if you want to edit dimensions of all regions simultaniously.") ); // setup the table refTableModel = Gtk::ListStore::create(tableModel); treeView.set_model(refTableModel); treeView.append_column(_("Dimension Type"), m_cellRendererDimType); treeView.append_column(_("Bits"), m_cellRendererIntSet); treeView.append_column(_("Zones"), m_cellRendererIntSet); treeView.append_column(_("Description"), tableModel.m_description); treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyDimType(), tableModel.m_type); treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyUsageCount(), tableModel.m_usageCount); treeView.get_column(0)->add_attribute(m_cellRendererDimType.propertyTotalRegions(), tableModel.m_totalRegions); treeView.get_column(1)->add_attribute(m_cellRendererIntSet.propertyValue(), tableModel.m_bits); treeView.get_column(2)->add_attribute(m_cellRendererIntSet.propertyValue(), tableModel.m_zones); treeView.show(); treeView.signal_cursor_changed().connect( sigc::mem_fun(*this, &DimensionManager::onColumnClicked) ); addButton.signal_clicked().connect( sigc::mem_fun(*this, &DimensionManager::addDimension) ); removeButton.signal_clicked().connect( sigc::mem_fun(*this, &DimensionManager::removeDimension) ); allRegionsCheckBox.signal_toggled().connect( sigc::mem_fun(*this, &DimensionManager::onAllRegionsCheckBoxToggled) ); show_all_children(); resize(460,300); } bool DimensionManager::allRegions() const { return allRegionsCheckBox.get_active(); } void DimensionManager::onAllRegionsCheckBoxToggled() { set_title( allRegions() ? _("Dimensions of all Regions") : _("Dimensions of selected Region") ); treeView.set_tooltip_text( allRegions() ? _("Dimensions and numbers in gray indicates a difference among the individual regions.") : _("You are currently only viewing dimensions of the currently selected region.") ); refreshManager(); } // following two data types are just used in DimensionManager::refresManager(), // due to the maps template nature however, they must be declared at global // space to avoid compilation errors struct _DimDef { std::set bits; std::set zones; int usageCount; }; typedef std::map _Dimensions; // update all GUI elements according to current gig::Region informations void DimensionManager::refreshManager() { set_sensitive(false); refTableModel->clear(); if (allRegions()) { if (region) { _Dimensions dims; gig::Instrument* instr = (gig::Instrument*)region->GetParent(); int iRegionsCount = 0; for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion(), ++iRegionsCount) { for (uint i = 0; i < rgn->Dimensions; i++) { gig::dimension_def_t* dim = &rgn->pDimensionDefinitions[i]; dims[dim->dimension].bits.insert(dim->bits); dims[dim->dimension].zones.insert(dim->zones); dims[dim->dimension].usageCount++; } } for (_Dimensions::const_iterator it = dims.begin(); it != dims.end(); ++it) { Gtk::TreeModel::Row row = *(refTableModel->append()); row[tableModel.m_type] = it->first; row[tableModel.m_bits] = it->second.bits; row[tableModel.m_zones] = it->second.zones; row[tableModel.m_description] = __dimDescriptionAsString(it->first); row[tableModel.m_usageCount] = it->second.usageCount; row[tableModel.m_totalRegions] = iRegionsCount; } } } else { if (region) { for (uint i = 0; i < region->Dimensions; i++) { gig::dimension_def_t* dim = ®ion->pDimensionDefinitions[i]; Gtk::TreeModel::Row row = *(refTableModel->append()); std::set vBits; vBits.insert(dim->bits); row[tableModel.m_bits] = vBits; std::set vZones; vZones.insert(dim->zones); row[tableModel.m_zones] = vZones; row[tableModel.m_description] = __dimDescriptionAsString(dim->dimension); row[tableModel.m_type] = dim->dimension; row[tableModel.m_usageCount] = 1; row[tableModel.m_totalRegions] = 1; } } } set_sensitive(region); } void DimensionManager::show(gig::Region* region) { ignoreColumnClicked = true; this->region = region; refreshManager(); Gtk::Window::show(); deiconify(); ignoreColumnClicked = false; } void DimensionManager::set_region(gig::Region* region) { ignoreColumnClicked = true; this->region = region; refreshManager(); ignoreColumnClicked = false; } void DimensionManager::onColumnClicked() { printf("DimensionManager::onColumnClicked()\n"); //FIXME: BUG: this method is currently very unreliably called, it should actually be called when the user selects another column, it is ATM however also called when the table content changed programmatically causing the dialog below to popup at undesired times ! //HACK: Prevents that onColumnClicked() gets called multiple times or at times where it is not desired if (ignoreColumnClicked) { ignoreColumnClicked = false; return; } #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION >= 18) || GTKMM_MAJOR_VERSION > 2 // prevents app to crash if this dialog is closed if (!get_visible()) return; #else # warning Your GTKMM version is too old; dimension manager dialog might crash when changing a dimension type ! #endif #if (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 8) || GTKMM_MAJOR_VERSION > 3 if (!is_visible()) return; #endif Gtk::TreeModel::Path path; Gtk::TreeViewColumn* focus_column; treeView.get_cursor(path, focus_column); //const int row = path[0]; if (focus_column == treeView.get_column(0)) { Gtk::TreeModel::iterator it = treeView.get_model()->get_iter(path); if (!it) return; Gtk::TreeModel::Row row = *it; gig::dimension_t oldType = row[tableModel.m_type]; Gtk::Dialog dialog(_("Change Dimension"), true /*modal*/); int oldTypeIndex = -1; Glib::RefPtr refComboModel = Gtk::ListStore::create(comboModel); for (int i = 0x01, count = 0; i < 0xff; i++) { Glib::ustring sType = dimTypeAsString(static_cast(i)); if (i == oldType) oldTypeIndex = count; if (sType.find("Unknown") != 0) { Gtk::TreeModel::Row row = *(refComboModel->append()); row[comboModel.m_type_id] = i; row[comboModel.m_type_name] = sType; count++; } } Gtk::Table table(1, 2); Gtk::Label labelDimType(_("Dimension:"), Gtk::ALIGN_START); Gtk::ComboBox comboDimType; comboDimType.set_model(refComboModel); comboDimType.pack_start(comboModel.m_type_id); comboDimType.pack_start(comboModel.m_type_name); table.attach(labelDimType, 0, 1, 0, 1); table.attach(comboDimType, 1, 2, 0, 1); dialog.get_vbox()->pack_start(table); dialog.add_button(_("_OK"), 0); dialog.add_button(_("_Cancel"), 1); dialog.show_all_children(); comboDimType.set_active(oldTypeIndex); if (!dialog.run()) { // OK selected ... ignoreColumnClicked = true; Gtk::TreeModel::iterator iterType = comboDimType.get_active(); if (!iterType) return; Gtk::TreeModel::Row rowType = *iterType; if (!rowType) return; int iTypeID = rowType[comboModel.m_type_id]; gig::dimension_t newType = static_cast(iTypeID); if (newType == oldType) return; //printf("change 0x%x -> 0x%x\n", oldType, newType); // assemble the list of regions where the selected dimension type // shall be changed std::vector vRegions; if (allRegions()) { gig::Instrument* instr = (gig::Instrument*)region->GetParent(); for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) { if (rgn->GetDimensionDefinition(oldType)) vRegions.push_back(rgn); } } else vRegions.push_back(region); std::set errors; for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) { gig::Region* region = vRegions[iRgn]; try { // notify everybody that we're going to update the region region_to_be_changed_signal.emit(region); // change the dimension type on that region region->SetDimensionType(oldType, newType); // let everybody know there was a change region_changed_signal.emit(region); } catch (RIFF::Exception e) { // notify that the changes are over (i.e. to avoid dead locks) region_changed_signal.emit(region); Glib::ustring txt = _("Could not alter dimension: ") + e.Message; if (vRegions.size() == 1) { // show error message directly Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } else { // remember error, they are shown after all regions have been processed errors.insert(txt); } } } // update all GUI elements refreshManager(); if (!errors.empty()) { Glib::ustring txt = _( "The following errors occurred while trying to change the dimension type on all regions:" ); txt += "\n\n"; for (std::set::const_iterator it = errors.begin(); it != errors.end(); ++it) { txt += "-> " + *it + "\n"; } txt += "\n"; txt += _( "You might also want to check the console for further warnings and " "error messages." ); Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } } } else if (focus_column == treeView.get_column(1) || focus_column == treeView.get_column(2)) { Glib::ustring txt = _("Right-click on a specific dimension zone of the dimension region selector to delete or split that particular dimension zone!"); Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_INFO); msg.run(); } } void DimensionManager::addDimension() { Gtk::Dialog dialog(_("New Dimension"), true /*modal*/); // add dimension type combo box to the dialog Glib::RefPtr refComboModel = Gtk::ListStore::create(comboModel); for (int i = 0x01; i < 0xff; i++) { Glib::ustring sType = dimTypeAsString(static_cast(i)); if (sType.find("Unknown") != 0) { Gtk::TreeModel::Row row = *(refComboModel->append()); row[comboModel.m_type_id] = i; row[comboModel.m_type_name] = sType; } } Gtk::Table table(2, 2); Gtk::Label labelDimType(_("Dimension:"), Gtk::ALIGN_START); Gtk::ComboBox comboDimType; comboDimType.set_model(refComboModel); comboDimType.pack_start(comboModel.m_type_id); comboDimType.pack_start(comboModel.m_type_name); Gtk::Label labelZones(_("Zones:"), Gtk::ALIGN_START); table.attach(labelDimType, 0, 1, 0, 1); table.attach(comboDimType, 1, 2, 0, 1); table.attach(labelZones, 0, 1, 1, 2); dialog.get_vbox()->pack_start(table); // number of zones: use a combo box with fix values for gig // v2 and a spin button for v3 Gtk::ComboBoxText comboZones; Gtk::SpinButton spinZones; bool version2 = false; if (region) { gig::File* file = (gig::File*)region->GetParent()->GetParent(); version2 = file->pVersion && file->pVersion->major == 2; } if (version2) { for (int i = 1; i <= 5; i++) { char buf[3]; sprintf(buf, "%d", 1 << i); #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 24) || GTKMM_MAJOR_VERSION < 2 comboZones.append_text(buf); #else comboZones.append(buf); #endif } table.attach(comboZones, 1, 2, 1, 2); } else { spinZones.set_increments(1, 8); spinZones.set_numeric(true); spinZones.set_range(2, 128); spinZones.set_value(2); table.attach(spinZones, 1, 2, 1, 2); } dialog.add_button(_("_OK"), 0); dialog.add_button(_("_Cancel"), 1); dialog.show_all_children(); if (!dialog.run()) { // OK selected ... Gtk::TreeModel::iterator iterType = comboDimType.get_active(); if (!iterType) return; Gtk::TreeModel::Row rowType = *iterType; if (!rowType) return; int iTypeID = rowType[comboModel.m_type_id]; gig::dimension_t type = static_cast(iTypeID); gig::dimension_def_t dim; dim.dimension = type; if (version2) { if (comboZones.get_active_row_number() < 0) return; dim.bits = comboZones.get_active_row_number() + 1; dim.zones = 1 << dim.bits; } else { dim.zones = spinZones.get_value_as_int(); dim.bits = zoneCountToBits(dim.zones); } // assemble the list of regions where the selected dimension shall be // added to std::vector vRegions; if (allRegions()) { gig::Instrument* instr = (gig::Instrument*)region->GetParent(); for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) { if (!rgn->GetDimensionDefinition(type)) vRegions.push_back(rgn); } } else vRegions.push_back(region); std::set errors; for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) { gig::Region* region = vRegions[iRgn]; try { printf( "Adding dimension (type=0x%x, bits=%d, zones=%d)\n", dim.dimension, dim.bits, dim.zones ); // notify everybody that we're going to update the region region_to_be_changed_signal.emit(region); // add the new dimension to the region // (implicitly creates new dimension regions) region->AddDimension(&dim); // let everybody know there was a change region_changed_signal.emit(region); } catch (RIFF::Exception e) { // notify that the changes are over (i.e. to avoid dead locks) region_changed_signal.emit(region); Glib::ustring txt = _("Could not add dimension: ") + e.Message; if (vRegions.size() == 1) { // show error message directly Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } else { // remember error, they are shown after all regions have been processed errors.insert(txt); } } } // update all GUI elements refreshManager(); if (!errors.empty()) { Glib::ustring txt = _( "The following errors occurred while trying to create the dimension on all regions:" ); txt += "\n\n"; for (std::set::const_iterator it = errors.begin(); it != errors.end(); ++it) { txt += "-> " + *it + "\n"; } txt += "\n"; txt += _( "You might also want to check the console for further warnings and " "error messages." ); Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } } } void DimensionManager::removeDimension() { Glib::RefPtr sel = treeView.get_selection(); Gtk::TreeModel::iterator it = sel->get_selected(); if (it) { Gtk::TreeModel::Row row = *it; gig::dimension_t type = row[tableModel.m_type]; // assemble the list of regions where the selected dimension shall be // added to std::vector vRegions; if (allRegions()) { gig::Instrument* instr = (gig::Instrument*)region->GetParent(); for (gig::Region* rgn = instr->GetFirstRegion(); rgn; rgn = instr->GetNextRegion()) { if (rgn->GetDimensionDefinition(type)) vRegions.push_back(rgn); } } else vRegions.push_back(region); std::set errors; for (uint iRgn = 0; iRgn < vRegions.size(); ++iRgn) { gig::Region* region = vRegions[iRgn]; gig::dimension_def_t* dim = region->GetDimensionDefinition(type); try { // notify everybody that we're going to update the region region_to_be_changed_signal.emit(region); // remove selected dimension region->DeleteDimension(dim); // let everybody know there was a change region_changed_signal.emit(region); } catch (RIFF::Exception e) { // notify that the changes are over (i.e. to avoid dead locks) region_changed_signal.emit(region); Glib::ustring txt = _("Could not remove dimension: ") + e.Message; if (vRegions.size() == 1) { // show error message directly Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } else { // remember error, they are shown after all regions have been processed errors.insert(txt); } } } // update all GUI elements refreshManager(); if (!errors.empty()) { Glib::ustring txt = _( "The following errors occurred while trying to remove the dimension from all regions:" ); txt += "\n\n"; for (std::set::const_iterator it = errors.begin(); it != errors.end(); ++it) { txt += "-> " + *it + "\n"; } txt += "\n"; txt += _( "You might also want to check the console for further warnings and " "error messages." ); Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); msg.run(); } } }