aboutsummaryrefslogtreecommitdiffstats
path: root/src/gui/MachinaGUI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/MachinaGUI.cpp')
-rw-r--r--src/gui/MachinaGUI.cpp750
1 files changed, 750 insertions, 0 deletions
diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp
new file mode 100644
index 0000000..ff71ca7
--- /dev/null
+++ b/src/gui/MachinaGUI.cpp
@@ -0,0 +1,750 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2013 David Robillard <http://drobilla.net>
+
+ Machina 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 3 of the License, or any later version.
+
+ Machina 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 Machina. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "machina_config.h"
+
+#include <cmath>
+#include <fstream>
+#include <limits.h>
+#include <pthread.h>
+#include "sord/sordmm.hpp"
+#include "machina/Controller.hpp"
+#include "machina/Engine.hpp"
+#include "machina/Machine.hpp"
+#include "machina/Mutation.hpp"
+#include "machina/Updates.hpp"
+#include "client/ClientModel.hpp"
+#include "WidgetFactory.hpp"
+#include "MachinaGUI.hpp"
+#include "MachinaCanvas.hpp"
+#include "NodeView.hpp"
+#include "EdgeView.hpp"
+
+#ifdef HAVE_EUGENE
+#include "machina/Evolver.hpp"
+#endif
+
+namespace machina {
+namespace gui {
+
+MachinaGUI::MachinaGUI(SPtr<machina::Engine> engine)
+ : _unit(TimeUnit::BEATS, 19200)
+ , _engine(engine)
+ , _client_model(new machina::client::ClientModel())
+ , _controller(new machina::Controller(_engine, *_client_model.get()))
+ , _maid(new Raul::Maid())
+ , _refresh(false)
+ , _evolve(false)
+ , _chain_mode(true)
+{
+ _canvas = SPtr<MachinaCanvas>(new MachinaCanvas(this, 1600*2, 1200*2));
+
+ Glib::RefPtr<Gtk::Builder> xml = WidgetFactory::create();
+
+ xml->get_widget("machina_win", _main_window);
+ xml->get_widget("about_win", _about_window);
+ xml->get_widget("help_dialog", _help_dialog);
+ xml->get_widget("toolbar", _toolbar);
+ xml->get_widget("open_menuitem", _menu_file_open);
+ xml->get_widget("save_menuitem", _menu_file_save);
+ xml->get_widget("save_as_menuitem", _menu_file_save_as);
+ xml->get_widget("quit_menuitem", _menu_file_quit);
+ xml->get_widget("zoom_in_menuitem", _menu_zoom_in);
+ xml->get_widget("zoom_out_menuitem", _menu_zoom_out);
+ xml->get_widget("zoom_normal_menuitem", _menu_zoom_normal);
+ xml->get_widget("arrange_menuitem", _menu_view_arrange);
+ xml->get_widget("import_midi_menuitem", _menu_import_midi);
+ xml->get_widget("export_midi_menuitem", _menu_export_midi);
+ xml->get_widget("export_graphviz_menuitem", _menu_export_graphviz);
+ xml->get_widget("view_toolbar_menuitem", _menu_view_toolbar);
+ xml->get_widget("view_labels_menuitem", _menu_view_labels);
+ xml->get_widget("help_about_menuitem", _menu_help_about);
+ xml->get_widget("help_help_menuitem", _menu_help_help);
+ xml->get_widget("canvas_scrolledwindow", _canvas_scrolledwindow);
+ xml->get_widget("bpm_spinbutton", _bpm_spinbutton);
+ xml->get_widget("quantize_checkbutton", _quantize_checkbutton);
+ xml->get_widget("quantize_spinbutton", _quantize_spinbutton);
+ xml->get_widget("stop_but", _stop_button);
+ xml->get_widget("play_but", _play_button);
+ xml->get_widget("record_but", _record_button);
+ xml->get_widget("step_record_but", _step_record_button);
+ xml->get_widget("chain_but", _chain_button);
+ xml->get_widget("fan_but", _fan_button);
+ xml->get_widget("load_target_but", _load_target_button);
+ xml->get_widget("evolve_toolbar", _evolve_toolbar);
+ xml->get_widget("evolve_but", _evolve_button);
+ xml->get_widget("mutate_but", _mutate_button);
+ xml->get_widget("compress_but", _compress_button);
+ xml->get_widget("add_node_but", _add_node_button);
+ xml->get_widget("remove_node_but", _remove_node_button);
+ xml->get_widget("adjust_node_but", _adjust_node_button);
+ xml->get_widget("add_edge_but", _add_edge_button);
+ xml->get_widget("remove_edge_but", _remove_edge_button);
+ xml->get_widget("adjust_edge_but", _adjust_edge_button);
+
+ _canvas_scrolledwindow->add(_canvas->widget());
+ _canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(this,
+ &MachinaGUI::scrolled_window_event));
+
+ _canvas_scrolledwindow->property_hadjustment().get_value()->set_step_increment(10);
+ _canvas_scrolledwindow->property_vadjustment().get_value()->set_step_increment(10);
+
+ _stop_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::stop_toggled));
+ _play_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::play_toggled));
+ _record_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::record_toggled));
+ _step_record_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::step_record_toggled));
+
+ _chain_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::chain_toggled));
+ _fan_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::fan_toggled));
+
+ _menu_file_open->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_open));
+ _menu_file_save->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_save));
+ _menu_file_save_as->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_save_as));
+ _menu_file_quit->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_quit));
+ _menu_zoom_in->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_in));
+ _menu_zoom_out->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_out));
+ _menu_zoom_normal->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_normal));
+ _menu_view_arrange->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::arrange));
+ _menu_import_midi->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_import_midi));
+ _menu_export_midi->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_export_midi));
+ _menu_export_graphviz->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_export_graphviz));
+ _menu_view_toolbar->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::show_toolbar_toggled));
+ _menu_view_labels->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::show_labels_toggled));
+ _menu_help_about->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_help_about));
+ _menu_help_help->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_help_help));
+ _bpm_spinbutton->signal_changed().connect(
+ sigc::mem_fun(this, &MachinaGUI::tempo_changed));
+ _quantize_checkbutton->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::quantize_record_changed));
+ _quantize_spinbutton->signal_changed().connect(
+ sigc::mem_fun(this, &MachinaGUI::quantize_changed));
+
+ _mutate_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::random_mutation),
+ SPtr<Machine>()));
+ _compress_button->signal_clicked().connect(
+ sigc::hide_return(sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 0)));
+ _add_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 1));
+ _remove_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 2));
+ _adjust_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 3));
+ _add_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 4));
+ _remove_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 5));
+ _adjust_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 6));
+
+ _canvas->widget().show();
+
+ _main_window->present();
+
+ _quantize_checkbutton->set_active(false);
+ update_toolbar();
+
+ // Idle callback to drive the maid (collect garbage)
+ Glib::signal_timeout().connect(
+ sigc::bind_return(sigc::mem_fun(_maid.get(), &Raul::Maid::cleanup),
+ true),
+ 1000);
+
+ // Idle callback to update node states
+ Glib::signal_timeout().connect(
+ sigc::mem_fun(this, &MachinaGUI::idle_callback), 100);
+
+#ifdef HAVE_EUGENE
+ _load_target_button->signal_clicked().connect(
+ sigc::mem_fun(this, &MachinaGUI::load_target_clicked));
+ _evolve_button->signal_clicked().connect(
+ sigc::mem_fun(this, &MachinaGUI::evolve_toggled));
+ Glib::signal_timeout().connect(
+ sigc::mem_fun(this, &MachinaGUI::evolve_callback), 1000);
+#else
+ _evolve_toolbar->hide();
+#endif
+
+ _client_model->signal_new_object.connect(
+ sigc::mem_fun(this, &MachinaGUI::on_new_object));
+ _client_model->signal_erase_object.connect(
+ sigc::mem_fun(this, &MachinaGUI::on_erase_object));
+
+ rebuild_canvas();
+}
+
+MachinaGUI::~MachinaGUI()
+{
+}
+
+#ifdef HAVE_EUGENE
+bool
+MachinaGUI::evolve_callback()
+{
+ if (_evolve && _evolver->improvement()) {
+ _engine->driver()->set_machine(
+ SPtr<Machine>(new Machine(_evolver->best())));
+ _controller->announce(_engine->machine());
+ }
+
+ return true;
+}
+#endif
+
+bool
+MachinaGUI::idle_callback()
+{
+ _controller->process_updates();
+ return true;
+}
+
+static void
+destroy_edge(GanvEdge* edge, void* data)
+{
+ MachinaGUI* gui = (MachinaGUI*)data;
+ EdgeView* view = dynamic_cast<EdgeView*>(Glib::wrap(edge));
+ if (view) {
+ NodeView* tail = dynamic_cast<NodeView*>(view->get_tail());
+ NodeView* head = dynamic_cast<NodeView*>(view->get_head());
+ gui->controller()->disconnect(tail->node()->id(), head->node()->id());
+ }
+}
+
+static void
+destroy_node(GanvNode* node, void* data)
+{
+ MachinaGUI* gui = (MachinaGUI*)data;
+ NodeView* view = dynamic_cast<NodeView*>(Glib::wrap(GANV_NODE(node)));
+ if (view) {
+ const SPtr<client::ClientObject> node = view->node();
+ gui->canvas()->for_each_edge_on(
+ GANV_NODE(view->gobj()), destroy_edge, gui);
+ gui->controller()->erase(node->id());
+ }
+}
+
+bool
+MachinaGUI::scrolled_window_event(GdkEvent* event)
+{
+ if (event->type == GDK_KEY_PRESS) {
+ if (event->key.keyval == GDK_Delete) {
+ _canvas->for_each_selected_node(destroy_node, this);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+MachinaGUI::arrange()
+{
+ _canvas->arrange();
+}
+
+void
+MachinaGUI::load_target_clicked()
+{
+ Gtk::FileChooserDialog dialog(*_main_window,
+ "Load MIDI file for evolution", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.set_local_only(false);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK)
+ _target_filename = dialog.get_filename();
+}
+
+#ifdef HAVE_EUGENE
+void
+MachinaGUI::evolve_toggled()
+{
+ if (_evolve_button->get_active()) {
+ _evolver = SPtr<Evolver>(
+ new Evolver(_unit, _target_filename, _engine->machine()));
+ _evolve = true;
+ stop_toggled();
+ _engine->driver()->set_machine(SPtr<Machine>());
+ _evolver->start();
+ } else {
+ _evolver->join();
+ _evolve = false;
+ SPtr<Machine> new_machine = SPtr<Machine>(
+ new Machine(_evolver->best()));
+ _engine->driver()->set_machine(new_machine);
+ _controller->announce(_engine->machine());
+ _engine->driver()->activate();
+ }
+}
+#endif
+
+void
+MachinaGUI::random_mutation(SPtr<Machine> machine)
+{
+ if (!machine)
+ machine = _engine->machine();
+
+ mutate(machine, machine->nodes().size() < 2 ? 1 : rand() % 7);
+}
+
+void
+MachinaGUI::mutate(SPtr<Machine> machine, unsigned mutation)
+{
+ #if 0
+ if (!machine)
+ machine = _engine->machine();
+
+ using namespace Mutation;
+
+ switch (mutation) {
+ case 0:
+ Compress().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 1:
+ AddNode().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 2:
+ RemoveNode().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 3:
+ AdjustNode().mutate(*machine.get());
+ idle_callback(); // update nodes
+ break;
+ case 4:
+ AddEdge().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 5:
+ RemoveEdge().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 6:
+ AdjustEdge().mutate(*machine.get());
+ _canvas->update_edges();
+ break;
+ default: throw;
+ }
+ #endif
+}
+
+void
+MachinaGUI::update_toolbar()
+{
+ const Driver::PlayState state = _engine->driver()->play_state();
+ _record_button->set_active(state == Driver::PlayState::RECORDING);
+ _step_record_button->set_active(state == Driver::PlayState::STEP_RECORDING);
+ _play_button->set_active(state == Driver::PlayState::PLAYING);
+}
+
+void
+MachinaGUI::rebuild_canvas()
+{
+ _controller->announce(_engine->machine());
+ _canvas->arrange();
+}
+
+void
+MachinaGUI::quantize_record_changed()
+{
+ _engine->driver()->set_quantize_record(_quantize_checkbutton->get_active());
+}
+
+void
+MachinaGUI::quantize_changed()
+{
+ _engine->set_quantization(1.0 / _quantize_spinbutton->get_value());
+}
+
+void
+MachinaGUI::tempo_changed()
+{
+ _engine->set_bpm(_bpm_spinbutton->get_value_as_int());
+}
+
+void
+MachinaGUI::menu_file_quit()
+{
+ _main_window->hide();
+}
+
+void
+MachinaGUI::menu_file_open()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Open Machine", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.set_local_only(false);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.machina.ttl");
+ filt.set_name("Machina Machines (Turtle/RDF)");
+ dialog.set_filter(filt);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ SPtr<machina::Machine> new_machine = _engine->load_machine(dialog.get_uri());
+ if (new_machine) {
+ rebuild_canvas();
+ _save_uri = dialog.get_uri();
+ }
+ }
+}
+
+void
+MachinaGUI::menu_file_save()
+{
+ if (_save_uri == "" || _save_uri.substr(0, 5) != "file:") {
+ menu_file_save_as();
+ } else {
+ if (_save_uri.substr(0, 5) != "file:")
+ menu_file_save_as();
+
+ Sord::Model model(_engine->rdf_world(), _save_uri);
+ _engine->machine()->write_state(model);
+ model.write_to_file(_save_uri, SERD_TURTLE);
+ }
+}
+
+void
+MachinaGUI::menu_file_save_as()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Save Machine",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ if (_save_uri.length() > 0)
+ dialog.set_uri(_save_uri);
+
+ const int result = dialog.run();
+
+ assert(result == Gtk::RESPONSE_OK
+ || result == Gtk::RESPONSE_CANCEL
+ || result == Gtk::RESPONSE_NONE);
+
+ if (result == Gtk::RESPONSE_OK) {
+ string filename = dialog.get_filename();
+
+ if (filename.length() < 13 || filename.substr(filename.length()-12) != ".machina.ttl")
+ filename += ".machina.ttl";
+
+ const std::string uri = Glib::filename_to_uri(filename);
+
+ bool confirm = false;
+ std::fstream fin;
+ fin.open(filename.c_str(), std::ios::in);
+ if (fin.is_open()) { // File exists
+ string msg = "A file named \"";
+ msg += filename + "\" already exists.\n\nDo you want to replace it?";
+ Gtk::MessageDialog confirm_dialog(dialog,
+ msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
+ if (confirm_dialog.run() == Gtk::RESPONSE_YES)
+ confirm = true;
+ else
+ confirm = false;
+ } else { // File doesn't exist
+ confirm = true;
+ }
+ fin.close();
+
+ if (confirm) {
+ _save_uri = uri;
+ Sord::Model model(_engine->rdf_world(), _save_uri);
+ _engine->machine()->write_state(model);
+ model.write_to_file(_save_uri, SERD_TURTLE);
+ }
+ }
+}
+
+void
+MachinaGUI::menu_import_midi()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Learn from MIDI file",
+ Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox());
+ Gtk::SpinButton* length_sb = Gtk::manage(new Gtk::SpinButton());
+ length_sb->set_increments(1, 10);
+ length_sb->set_range(0, INT_MAX);
+ length_sb->set_value(0);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Maximum Length (0 = unlimited): ")), false, false);
+ extra_widget->pack_start(*length_sb, false, false);
+ dialog.set_extra_widget(*extra_widget);
+ extra_widget->show_all();
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ const double length_dbl = length_sb->get_value_as_int();
+ const Raul::TimeStamp length(_unit, length_dbl);
+
+ SPtr<machina::Machine> machine = _engine->load_machine_midi(
+ dialog.get_filename(), 0.0, length);
+
+ if (machine) {
+ dialog.hide();
+ machine->reset(NULL, machine->time());
+ _engine->driver()->set_machine(machine);
+ _canvas->clear();
+ rebuild_canvas();
+ } else {
+ Gtk::MessageDialog msg_dialog(dialog, "Error loading MIDI file",
+ false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ msg_dialog.run();
+ }
+ }
+}
+
+void
+MachinaGUI::menu_export_midi()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Export to a MIDI file",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox());
+ Gtk::SpinButton* dur_sb = Gtk::manage(new Gtk::SpinButton());
+ dur_sb->set_increments(1, 10);
+ dur_sb->set_range(0, INT_MAX);
+ dur_sb->set_value(0);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Duration (beats): ")), false, false);
+ extra_widget->pack_start(*dur_sb, false, false);
+ dialog.set_extra_widget(*extra_widget);
+ extra_widget->show_all();
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ const double dur_dbl = dur_sb->get_value_as_int();
+ const Raul::TimeStamp dur(_unit, dur_dbl);
+ _engine->export_midi(dialog.get_filename(), dur);
+ }
+}
+
+void
+MachinaGUI::menu_export_graphviz()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Export to a GraphViz DOT file",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK)
+ _canvas->export_dot(dialog.get_filename().c_str());
+}
+
+void
+MachinaGUI::show_toolbar_toggled()
+{
+ if (_menu_view_toolbar->get_active())
+ _toolbar->show();
+ else
+ _toolbar->hide();
+}
+
+void
+MachinaGUI::on_zoom_in()
+{
+ _canvas->set_font_size(_canvas->get_font_size() + 1.0);
+}
+
+void
+MachinaGUI::on_zoom_out()
+{
+ _canvas->set_font_size(_canvas->get_font_size() - 1.0);
+}
+
+void
+MachinaGUI::on_zoom_normal()
+{
+ _canvas->set_zoom(1.0);
+}
+
+static void
+show_node_label(GanvNode* node, void* data)
+{
+ bool show = *(bool*)data;
+ Ganv::Node* nodemm = Glib::wrap(node);
+ NodeView* const nv = dynamic_cast<NodeView*>(nodemm);
+ if (nv) {
+ nv->show_label(show);
+ }
+}
+
+static void
+show_edge_label(GanvEdge* edge, void* data)
+{
+ bool show = *(bool*)data;
+ Ganv::Edge* edgemm = Glib::wrap(edge);
+ EdgeView* const ev = dynamic_cast<EdgeView*>(edgemm);
+ if (ev) {
+ ev->show_label(show);
+ }
+}
+
+void
+MachinaGUI::show_labels_toggled()
+{
+ bool show = _menu_view_labels->get_active();
+
+ _canvas->for_each_node(show_node_label, &show);
+ _canvas->for_each_edge(show_edge_label, &show);
+}
+
+void
+MachinaGUI::menu_help_about()
+{
+ _about_window->set_transient_for(*_main_window);
+ _about_window->show();
+}
+
+void
+MachinaGUI::menu_help_help()
+{
+ _help_dialog->set_transient_for(*_main_window);
+ _help_dialog->run();
+ _help_dialog->hide();
+}
+
+void
+MachinaGUI::stop_toggled()
+{
+ if (_stop_button->get_active()) {
+ const Driver::PlayState old_state = _engine->driver()->play_state();
+ _engine->driver()->set_play_state(Driver::PlayState::STOPPED);
+ if (old_state == Driver::PlayState::RECORDING ||
+ old_state == Driver::PlayState::STEP_RECORDING) {
+ rebuild_canvas();
+ }
+ }
+}
+
+void
+MachinaGUI::play_toggled()
+{
+ if (_play_button->get_active()) {
+ const Driver::PlayState old_state = _engine->driver()->play_state();
+ _engine->driver()->set_play_state(Driver::PlayState::PLAYING);
+ if (old_state == Driver::PlayState::RECORDING ||
+ old_state == Driver::PlayState::STEP_RECORDING) {
+ rebuild_canvas();
+ }
+ }
+}
+
+void
+MachinaGUI::record_toggled()
+{
+ if (_record_button->get_active()) {
+ _engine->driver()->set_play_state(Driver::PlayState::RECORDING);
+ }
+}
+
+void
+MachinaGUI::step_record_toggled()
+{
+ if (_step_record_button->get_active()) {
+ _engine->driver()->set_play_state(Driver::PlayState::STEP_RECORDING);
+ }
+}
+
+void
+MachinaGUI::chain_toggled()
+{
+ if (_chain_button->get_active()) {
+ _chain_mode = true;
+ }
+}
+
+void
+MachinaGUI::fan_toggled()
+{
+ if (_fan_button->get_active()) {
+ _chain_mode = false;
+ }
+}
+
+void
+MachinaGUI::on_new_object(SPtr<client::ClientObject> object)
+{
+ _canvas->on_new_object(object);
+}
+
+void
+MachinaGUI::on_erase_object(SPtr<client::ClientObject> object)
+{
+ _canvas->on_erase_object(object);
+}
+
+} // namespace machina
+} // namespace gui