From 4543288514e1b0a83c6ed003dfb22e175e07bfaa Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Thu, 8 Feb 2007 03:56:42 +0000
Subject: Circular nodes in FlowCanvas, related necessary redesign work and
 changes for API update. Beginnings of a Machina GUI.

git-svn-id: http://svn.drobilla.net/lad/machina@290 a436a847-0d15-0410-975c-d299462d15a1
---
 src/Makefile.am           |   1 +
 src/gui/MachinaCanvas.cpp | 157 ++++++++++++++++++++
 src/gui/MachinaCanvas.hpp |  55 +++++++
 src/gui/MachinaGUI.cpp    | 370 ++++++++++++++++++++++++++++++++++++++++++++++
 src/gui/MachinaGUI.hpp    |  88 +++++++++++
 src/gui/Makefile.am       |  22 +++
 src/gui/machina-icon.svg  |  66 +++++++++
 src/gui/machina.glade     | 340 ++++++++++++++++++++++++++++++++++++++++++
 src/gui/machina.gladep    |   9 ++
 src/gui/main.cpp          |  44 ++++++
 10 files changed, 1152 insertions(+)
 create mode 100644 src/gui/MachinaCanvas.cpp
 create mode 100644 src/gui/MachinaCanvas.hpp
 create mode 100644 src/gui/MachinaGUI.cpp
 create mode 100644 src/gui/MachinaGUI.hpp
 create mode 100644 src/gui/Makefile.am
 create mode 100644 src/gui/machina-icon.svg
 create mode 100644 src/gui/machina.glade
 create mode 100644 src/gui/machina.gladep
 create mode 100644 src/gui/main.cpp

(limited to 'src')

diff --git a/src/Makefile.am b/src/Makefile.am
index 928262c..f6c047d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,7 @@
 #globalpixmapsdir = $(datadir)/pixmaps
 #dist_globalpixmaps_DATA = machina-icon.svg
 
+SUBDIRS = gui
 
 bin_PROGRAMS = machina
 
diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp
new file mode 100644
index 0000000..2309a5c
--- /dev/null
+++ b/src/gui/MachinaCanvas.cpp
@@ -0,0 +1,157 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 2 of the License, or (at your option) 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 details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+//#include "config.h"
+#include <raul/SharedPtr.h>
+#include <flowcanvas/Ellipse.h>
+#include "MachinaCanvas.hpp"
+#include "MachinaGUI.hpp"
+
+MachinaCanvas::MachinaCanvas(MachinaGUI* app, int width, int height)
+: FlowCanvas(width, height),
+  _app(app)
+{
+}
+
+
+void
+MachinaCanvas::connect(boost::shared_ptr<Connectable>, //item1,
+                       boost::shared_ptr<Connectable>) //item2)
+{
+#if 0
+	boost::shared_ptr<MachinaPort> p1 = boost::dynamic_pointer_cast<MachinaPort>(port1);
+	boost::shared_ptr<MachinaPort> p2 = boost::dynamic_pointer_cast<MachinaPort>(port2);
+	if (!p1 || !p2)
+		return;
+
+	if (p1->type() == JACK_AUDIO && p2->type() == JACK_AUDIO
+			|| (p1->type() == JACK_MIDI && p2->type() == JACK_MIDI))
+		_app->jack_driver()->connect(p1, p2);
+#ifdef HAVE_ALSA
+	else if (p1->type() == ALSA_MIDI && p2->type() == ALSA_MIDI)
+		_app->alsa_driver()->connect(p1, p2);
+#endif
+	else
+		status_message("WARNING: Cannot make connection, incompatible port types.");
+#endif
+}
+
+
+void
+MachinaCanvas::disconnect(boost::shared_ptr<Connectable>,// item1,
+                          boost::shared_ptr<Connectable>)// item2)
+{
+#if 0
+	boost::shared_ptr<MachinaPort> input
+		= boost::dynamic_pointer_cast<MachinaPort>(port1);
+	boost::shared_ptr<MachinaPort> output
+		= boost::dynamic_pointer_cast<MachinaPort>(port2);
+
+	if (!input || !output)
+		return;
+
+	if (input->is_output() && output->is_input()) {
+		// Damn, guessed wrong
+		boost::shared_ptr<MachinaPort> swap = input;
+		input = output;
+		output = swap;
+	}
+
+	if (!input || !output || input->is_output() || output->is_input()) {
+		status_message("ERROR: Attempt to disconnect mismatched/unknown ports");
+		return;
+	}
+	
+	if (input->type() == JACK_AUDIO && output->type() == JACK_AUDIO
+			|| input->type() == JACK_MIDI && output->type() == JACK_MIDI)
+		_app->jack_driver()->disconnect(output, input);
+#ifdef HAVE_ALSA
+	else if (input->type() == ALSA_MIDI && output->type() == ALSA_MIDI)
+		_app->alsa_driver()->disconnect(output, input);
+#endif
+	else
+		status_message("ERROR: Attempt to disconnect ports with mismatched types");
+#endif
+}
+
+
+void
+MachinaCanvas::status_message(const string& msg)
+{
+	_app->status_message(string("[Canvas] ").append(msg));
+}
+
+
+void
+MachinaCanvas::item_selected(SharedPtr<Item> i)
+{
+	SharedPtr<Connectable> item = PtrCast<Connectable>(i);
+	if (!item)
+		return;
+
+	cerr << "SELECTED: " << i->name() << endl;
+	
+	SharedPtr<Connectable> last = _last_selected.lock();
+
+	if (last) {
+		boost::shared_ptr<Connection> c(new Connection(shared_from_this(),
+			last, item, 0x9999AAFF, true));
+		last->add_connection(c);
+		item->add_connection(c);
+		add_connection(c);
+		i->raise_to_top();
+		_last_selected.reset();
+	} else {
+		_last_selected = item;
+	}
+}
+
+
+bool
+MachinaCanvas::canvas_event(GdkEvent* event)
+{
+	static int last = 0;
+	
+	assert(event);
+	
+	if (event->type == GDK_BUTTON_PRESS) {
+	
+		const double x = event->button.x;
+		const double y = event->button.y;
+
+		if (event->button.button == 1) {
+			SharedPtr<Ellipse> item(new Ellipse(shared_from_this(),
+				string("Note")+(char)(last++ +'0'), x, y, 30, 30, true));
+			item->signal_selected.connect(sigc::bind(sigc::mem_fun(this,
+				&MachinaCanvas::item_selected), item));
+			add_item(item);
+			item->raise_to_top();
+		} else if (event->button.button == 2) {
+			SharedPtr<Module> item(new Module(shared_from_this(),
+				string("Note")+(char)(last++ +'0'), x, y, true));
+			item->resize();
+			add_item(item);
+		} else if (event->button.button == 3) {
+			//_last_click_x = (int)event->button.x;
+			//_last_click_y = (int)event->button.y;
+			//show_menu(event);
+		}
+	}
+
+	return FlowCanvas::canvas_event(event);
+}
+
diff --git a/src/gui/MachinaCanvas.hpp b/src/gui/MachinaCanvas.hpp
new file mode 100644
index 0000000..85ae550
--- /dev/null
+++ b/src/gui/MachinaCanvas.hpp
@@ -0,0 +1,55 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 2 of the License, or (at your option) 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 details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef PATCHAGEPATCHBAYAREA_H
+#define PATCHAGEPATCHBAYAREA_H
+
+#include <string>
+#include <raul/SharedPtr.h>
+#include <raul/WeakPtr.h>
+#include <flowcanvas/FlowCanvas.h>
+
+using namespace LibFlowCanvas;
+
+class MachinaGUI;
+
+class MachinaCanvas : public FlowCanvas
+{
+public:
+	MachinaCanvas(MachinaGUI* _app, int width, int height);
+	
+	void connect(boost::shared_ptr<Connectable> port1,
+	             boost::shared_ptr<Connectable> port2);
+
+	void disconnect(boost::shared_ptr<Connectable> port1,
+	                boost::shared_ptr<Connectable> port2);
+
+	void status_message(const string& msg);
+
+protected:
+	bool canvas_event(GdkEvent* event);
+
+	void item_selected(SharedPtr<Item> item);
+
+private:
+	MachinaGUI* _app;
+
+	WeakPtr<Connectable> _last_selected;
+};
+
+
+#endif // PATCHAGEPATCHBAYAREA_H
diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp
new file mode 100644
index 0000000..a7602c1
--- /dev/null
+++ b/src/gui/MachinaGUI.cpp
@@ -0,0 +1,370 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 2 of the License, or (at your option) 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 details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <cmath>
+#include <sstream>
+#include <libgnomecanvasmm.h>
+#include <libglademm/xml.h>
+#include <fstream>
+#include <pthread.h>
+#include "MachinaGUI.hpp"
+#include "MachinaCanvas.hpp"
+
+//#include "config.h"
+
+// FIXME: include to avoid undefined reference to boost SP debug hooks stuff
+#include <raul/SharedPtr.h>
+
+
+
+/* Gtk helpers (resize combo boxes) */
+
+#if 0
+static void
+gtkmm_get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
+			       int& width,
+			       int& height)
+{
+	Pango::Rectangle ink_rect = layout->get_ink_extents ();
+	
+	width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE;
+	height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE;
+}
+
+
+static void
+gtkmm_set_width_for_given_text (Gtk::Widget &w, const gchar *text,
+						   gint hpadding/*, gint vpadding*/)
+	
+{
+	int old_width, old_height;
+	w.get_size_request(old_width, old_height);
+
+	int width, height;
+	w.ensure_style ();
+	
+	gtkmm_get_ink_pixel_size (w.create_pango_layout (text), width, height);
+	w.set_size_request(width + hpadding, old_height);//height + vpadding);
+}
+#endif
+/* end Gtk helpers */
+
+
+
+MachinaGUI::MachinaGUI(/*int argc, char** argv*/)
+: _pane_closed(false),
+  _update_pane_position(true),
+  _user_pane_position(0),
+  _refresh(false)
+{
+	/*_settings_filename = getenv("HOME");
+	_settings_filename += "/.machinarc";*/
+
+	_canvas = boost::shared_ptr<MachinaCanvas>(new MachinaCanvas(this, 1600*2, 1200*2));
+
+	Glib::RefPtr<Gnome::Glade::Xml> xml;
+
+	// Check for the .glade file in current directory
+	string glade_filename = "./machina.glade";
+	ifstream fs(glade_filename.c_str());
+	if (fs.fail()) { // didn't find it, check PKGDATADIR
+		fs.clear();
+		glade_filename = PKGDATADIR;
+		glade_filename += "/machina.glade";
+	
+		fs.open(glade_filename.c_str());
+		if (fs.fail()) {
+			cerr << "Unable to find machina.glade in current directory or " << PKGDATADIR << "." << endl;
+			exit(EXIT_FAILURE);
+		}
+		fs.close();
+	}
+	
+	try {
+		xml = Gnome::Glade::Xml::create(glade_filename);
+	} catch(const Gnome::Glade::XmlError& ex) {
+		std::cerr << ex.what() << std::endl;
+		throw;
+	}
+
+	xml->get_widget("machina_win", _main_window);
+	xml->get_widget("about_win", _about_window);
+	xml->get_widget("file_quit_menuitem", _menu_file_quit);
+	xml->get_widget("view_refresh_menuitem", _menu_view_refresh);
+	xml->get_widget("view_messages_menuitem", _menu_view_messages);
+	xml->get_widget("help_about_menuitem", _menu_help_about);
+	xml->get_widget("canvas_scrolledwindow", _canvas_scrolledwindow);
+	xml->get_widget("status_text", _status_text);
+	xml->get_widget("main_paned", _main_paned);
+	xml->get_widget("messages_expander", _messages_expander);
+	xml->get_widget("zoom_full_but", _zoom_full_button);
+	xml->get_widget("zoom_normal_but", _zoom_normal_button);
+	
+	_canvas_scrolledwindow->add(*_canvas);
+	//m_canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(_canvas, &FlowCanvas::scroll_event_handler));
+	_canvas->scroll_to(static_cast<int>(_canvas->width()/2 - 320),
+	                       static_cast<int>(_canvas->height()/2 - 240)); // FIXME: hardcoded
+
+	//_zoom_slider->signal_value_changed().connect(sigc::mem_fun(this, &MachinaGUI::zoom_changed));
+	
+	_zoom_normal_button->signal_clicked().connect(sigc::bind(
+		sigc::mem_fun(this, &MachinaGUI::zoom), 1.0));
+	
+	_zoom_full_button->signal_clicked().connect(sigc::mem_fun(_canvas.get(), &MachinaCanvas::zoom_full));
+
+	_menu_file_quit->signal_activate().connect(      sigc::mem_fun(this, &MachinaGUI::menu_file_quit));
+	_menu_view_refresh->signal_activate().connect(   sigc::mem_fun(this, &MachinaGUI::menu_view_refresh));
+	_menu_view_messages->signal_toggled().connect(   sigc::mem_fun(this, &MachinaGUI::show_messages_toggled));
+	_menu_help_about->signal_activate().connect(     sigc::mem_fun(this, &MachinaGUI::menu_help_about));
+
+	connect_widgets();
+	
+	//update_state();
+
+	_canvas->show();
+
+	_main_window->present();
+
+	_update_pane_position = false;
+	_main_paned->set_position(max_pane_position());
+
+	_main_paned->property_position().signal_changed().connect(
+		sigc::mem_fun(*this, &MachinaGUI::on_pane_position_changed));
+	
+	_messages_expander->property_expanded().signal_changed().connect(
+		sigc::mem_fun(*this, &MachinaGUI::on_messages_expander_changed));
+	
+	_main_paned->set_position(max_pane_position());
+	_user_pane_position = max_pane_position() - _main_window->get_height()/8;
+	_update_pane_position = true;
+	_pane_closed = true;
+
+	// Idle callback, check if we need to refresh
+	Glib::signal_timeout().connect(sigc::mem_fun(this, &MachinaGUI::idle_callback), 100);
+	
+	// Faster idle callback to update DSP load progress bar
+	//Glib::signal_timeout().connect(sigc::mem_fun(this, &MachinaGUI::update_load), 50);
+}
+
+
+MachinaGUI::~MachinaGUI() 
+{
+}
+
+
+void
+MachinaGUI::attach()
+{
+#if 0
+	_jack_driver->attach(true);
+
+	menu_view_refresh();
+
+	update_toolbar();
+
+	//m_status_bar->push("Connected to JACK server");
+#endif
+}
+	
+
+bool
+MachinaGUI::idle_callback() 
+{
+#if 0
+	if (_jack_driver) {
+		while (!_jack_driver->events().empty()) {
+			MachinaEvent& ev = _jack_driver->events().front();
+			_jack_driver->events().pop();
+			ev.execute();
+		}
+	}
+
+	
+	bool refresh = _refresh;
+
+	refresh = refresh || (_jack_driver && _jack_driver->is_dirty());
+
+	if (refresh) {
+		
+		_canvas->flag_all_connections();
+
+		_jack_driver->refresh();
+	}
+
+	if (refresh) {
+		_canvas->destroy_all_flagged_connections();
+		_refresh = false;
+	}
+
+	update_load();
+#endif
+	return true;
+}
+
+
+void
+MachinaGUI::zoom(double z)
+{
+	_canvas->set_zoom(z);
+}
+
+
+void
+MachinaGUI::zoom_changed() 
+{
+/*
+	static bool enable_signal = true;
+	if (enable_signal) {
+		enable_signal = false;
+		zoom(_zoom_slider->get_value());
+		enable_signal = true;
+	}
+	*/
+}
+
+
+#if 0
+void
+MachinaGUI::update_state()
+{
+	for (ModuleMap::iterator i = _canvas->modules().begin(); i != _canvas->modules().end(); ++i)
+		(*i).second->load_location();
+
+	//cerr << "[Machina] Resizing window: (" << _state_manager->get_window_size().x
+	//	<< "," << _state_manager->get_window_size().y << ")" << endl;
+
+	_main_window->resize(
+		static_cast<int>(_state_manager->get_window_size().x),
+		static_cast<int>(_state_manager->get_window_size().y));
+	
+	//cerr << "[Machina] Moving window: (" << _state_manager->get_window_location().x
+	//	<< "," << _state_manager->get_window_location().y << ")" << endl;
+	
+	_main_window->move(
+		static_cast<int>(_state_manager->get_window_location().x),
+		static_cast<int>(_state_manager->get_window_location().y));
+
+}
+
+#endif
+
+
+void
+MachinaGUI::status_message(const string& msg) 
+{
+	if (_status_text->get_buffer()->size() > 0)
+		_status_text->get_buffer()->insert(_status_text->get_buffer()->end(), "\n");
+
+	_status_text->get_buffer()->insert(_status_text->get_buffer()->end(), msg);
+	_status_text->scroll_to_mark(_status_text->get_buffer()->get_insert(), 0);
+}
+
+
+/** Update the sensitivity status of menus to reflect the present.
+ *
+ * (eg. disable "Connect to Jack" when Machina is already connected to Jack)
+ */
+void
+MachinaGUI::connect_widgets()
+{
+
+}
+
+
+void
+MachinaGUI::menu_file_quit() 
+{
+	_main_window->hide();
+}
+
+
+void
+MachinaGUI::on_pane_position_changed()
+{
+	// avoid infinite recursion...
+	if (!_update_pane_position)
+		return;
+
+	_update_pane_position = false;
+
+	int new_position = _main_paned->get_position();
+
+	if (_pane_closed && new_position < max_pane_position()) {
+		// Auto open
+		_user_pane_position = new_position;
+		_messages_expander->set_expanded(true);
+		_pane_closed = false;
+		_menu_view_messages->set_active(true);
+	} else if (new_position >= max_pane_position()) {
+		// Auto close
+		_pane_closed = true;
+
+		_messages_expander->set_expanded(false);
+		if (new_position > max_pane_position())
+			_main_paned->set_position(max_pane_position()); // ... here
+		_menu_view_messages->set_active(false);
+		
+		_user_pane_position = max_pane_position() - _main_window->get_height()/8;
+	}
+
+	_update_pane_position = true;
+}
+
+
+void
+MachinaGUI::on_messages_expander_changed()
+{
+	if (!_pane_closed) {
+		// Store pane position for restoring
+		_user_pane_position = _main_paned->get_position();
+		if (_update_pane_position) {
+			_update_pane_position = false;
+			_main_paned->set_position(max_pane_position());
+			_update_pane_position = true;
+		}
+		_pane_closed = true;
+	} else {
+		_main_paned->set_position(_user_pane_position);
+		_pane_closed = false;
+	}
+}
+
+
+void
+MachinaGUI::show_messages_toggled()
+{
+	if (_update_pane_position)
+		_messages_expander->set_expanded(_menu_view_messages->get_active());
+}
+
+
+void
+MachinaGUI::menu_view_refresh() 
+{
+	assert(_canvas);
+	
+	//_canvas->destroy();
+}
+
+
+void
+MachinaGUI::menu_help_about() 
+{
+	_about_window->show();
+}
+
+
diff --git a/src/gui/MachinaGUI.hpp b/src/gui/MachinaGUI.hpp
new file mode 100644
index 0000000..27b7f4f
--- /dev/null
+++ b/src/gui/MachinaGUI.hpp
@@ -0,0 +1,88 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 2 of the License, or (at your option) 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 details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef MACHINA_GUI_H
+#define MACHINA_GUI_H
+
+//#include "config.h"
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <libgnomecanvasmm.h>
+
+using namespace std;
+
+class MachinaCanvas;
+
+
+class MachinaGUI
+{
+public:
+	MachinaGUI(/*int argc, char** argv*/);
+	~MachinaGUI();
+
+	boost::shared_ptr<MachinaCanvas> canvas() { return _canvas; }
+	
+	Gtk::Window*  window()        { return _main_window; }
+	
+	void attach();
+	void quit() { _main_window->hide(); }
+
+	void status_message(const string& msg);
+	inline void queue_refresh() { _refresh = true; }
+
+	int max_pane_position()
+	{ return _main_paned->property_max_position() - _messages_expander->get_label_widget()->get_height() - 8; }
+
+protected:
+	void connect_widgets();
+
+	void menu_file_quit();
+	void show_messages_toggled();
+	void menu_view_refresh();
+	void menu_help_about();
+	void zoom(double z);
+	void zoom_changed();
+	bool idle_callback();
+
+	void on_pane_position_changed();
+	void on_messages_expander_changed();
+
+	bool _pane_closed;
+	bool _update_pane_position;
+	int  _user_pane_position;
+
+	boost::shared_ptr<MachinaCanvas> _canvas;
+
+	Gtk::Main* _gtk_main;
+
+	bool   _refresh;
+	
+	Gtk::Window*         _main_window;
+	Gtk::AboutDialog*    _about_window;
+	Gtk::MenuItem*       _menu_file_quit;
+	Gtk::CheckMenuItem*  _menu_view_messages;
+	Gtk::MenuItem*       _menu_view_refresh;
+	Gtk::MenuItem*       _menu_help_about;
+	Gtk::ScrolledWindow* _canvas_scrolledwindow;
+	Gtk::TextView*       _status_text;
+	Gtk::Paned*          _main_paned;
+	Gtk::Expander*       _messages_expander;
+	Gtk::ToolButton*         _zoom_normal_button;
+	Gtk::ToolButton*         _zoom_full_button;
+};
+
+#endif // MACHINA_GUI_H
diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am
new file mode 100644
index 0000000..e7a88b7
--- /dev/null
+++ b/src/gui/Makefile.am
@@ -0,0 +1,22 @@
+AM_CXXFLAGS = -DPKGDATADIR=\"$(pkgdatadir)\" @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @JACK_CFLAGS@ @FLOWCANVAS_CFLAGS@ @RAUL_CFLAGS@
+machina_gui_LDADD = @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @JACK_LIBS@ @FLOWCANVAS_LIBS@ @RAUL_CFLAGS@
+
+EXTRA_DIST = machina.gladep
+
+sharefilesdir = $(pkgdatadir)
+dist_sharefiles_DATA = machina.glade
+
+#globalpixmapsdir = $(datadir)/pixmaps
+#dist_globalpixmaps_DATA = machina-icon.svg
+
+if WITH_GUI
+
+bin_PROGRAMS = machina_gui
+machina_gui_SOURCES = \
+	main.cpp \
+	MachinaGUI.hpp \
+	MachinaGUI.cpp \
+	MachinaCanvas.hpp \
+	MachinaCanvas.cpp
+
+endif
diff --git a/src/gui/machina-icon.svg b/src/gui/machina-icon.svg
new file mode 100644
index 0000000..39f7be0
--- /dev/null
+++ b/src/gui/machina-icon.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45pre1"
+   width="256"
+   height="256"
+   version="1.0"
+   sodipodi:docbase="/home/dave/code/lad/machina/src/gui"
+   sodipodi:docname="machina-icon.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   sodipodi:modified="true">
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs5" />
+  <sodipodi:namedview
+     inkscape:window-height="771"
+     inkscape:window-width="1183"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     guidetolerance="10.0"
+     gridtolerance="10.0"
+     objecttolerance="10.0"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     inkscape:zoom="1.4142136"
+     inkscape:cx="310.87907"
+     inkscape:cy="40.263308"
+     inkscape:window-x="293"
+     inkscape:window-y="52"
+     inkscape:current-layer="svg2"
+     width="256px"
+     height="256px" />
+  <path
+     style="fill:#000000"
+     id="path2189"
+     d="M 9.0278551,221.40628 C 22.572047,206.65805 31.269455,188.04472 39.259152,169.8919 C 51.644822,140.58208 56.041288,109.13092 58.849953,77.700978 C 59.962909,56.867589 61.443535,35.864275 59.495794,15.031194 C 63.699947,13.959444 67.938586,10.617016 72.108252,11.815945 C 77.429885,13.346107 71.833885,22.901101 72.179608,28.42755 C 73.082013,42.852664 75.819667,56.648771 78.944682,70.727057 C 85.857753,99.135712 98.546656,125.08352 122.5541,142.3916 C 149.02904,155.16169 159.76491,132.36389 173.41824,112.97967 C 189.89026,87.286307 201.67487,59.107084 212.79124,30.80732 C 238.81023,-0.42720544 213.47911,64.070958 211.76716,70.33904 C 202.71922,108.04829 200.75405,146.89462 201.60034,185.52025 C 202.59785,202.30805 203.03716,219.51338 208.40658,235.60545 C 209.82998,239.98783 211.97855,244.0493 214.19967,248.06447 L 201.80123,255.17231 C 199.63995,251.03059 197.23968,246.9206 196.00991,242.38503 C 191.48082,225.58243 192.01187,207.89788 191.55783,190.64995 C 191.61755,152.05096 193.33386,113.23415 201.28376,75.349455 C 205.86472,56.717664 207.3011,28.642568 227.34088,20.082503 C 229.12544,19.320222 225.62336,23.562881 224.76461,25.303071 C 211.8352,53.436232 199.91298,82.147117 183.57547,108.55509 C 167.9424,131.6754 143.08989,165.0319 111.95557,149.10157 C 88.367319,131.04024 76.047527,104.6256 68.72284,76.231983 C 66.537387,67.180737 52.831088,26.99333 59.36089,18.303914 C 62.551525,14.058035 67.78841,11.838043 72.002169,8.6051067 C 71.542368,29.947073 70.679719,51.279977 69.135855,72.573834 C 66.386515,103.98497 62.160375,135.44301 50.178851,164.87632 C 42.654028,182.74324 34.458191,200.83743 22.235303,216.04826 L 9.0278551,221.40628 z " />
+  <path
+     style="fill:#000000"
+     id="path2191"
+     d="M 177.4044,227.49356 C 186.73664,226.83386 194.85497,230.46984 202.63542,235.17331 C 208.34182,239.12994 205.55279,237.07748 211.00405,241.32859 L 199.24569,248.73751 C 193.86468,244.57093 196.63478,246.5644 190.93236,242.7613 C 183.22589,238.37114 175.24676,235.06127 166.16497,235.78874 L 177.4044,227.49356 z " />
+  <path
+     style="fill:#000000"
+     id="path2193"
+     d="M 202.904,253.28805 C 206.25526,244.218 211.55226,236.16787 216.96397,228.23361 C 217.73013,227.17184 218.49629,226.11007 219.26244,225.0483 L 232.24972,219.44712 C 231.4278,220.51566 230.60588,221.58419 229.78396,222.65273 C 224.30649,230.41756 219.07262,238.3307 215.72758,247.29568 L 202.904,253.28805 z " />
+</svg>
diff --git a/src/gui/machina.glade b/src/gui/machina.glade
new file mode 100644
index 0000000..e1f6fa0
--- /dev/null
+++ b/src/gui/machina.glade
@@ -0,0 +1,340 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="machina_win">
+  <property name="border_width">1</property>
+  <property name="title" translatable="yes">Machina</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="default_width">640</property>
+  <property name="default_height">480</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="icon">machina-icon.svg</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+
+  <child>
+    <widget class="GtkVBox" id="main_vbox">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child>
+	<widget class="GtkMenuBar" id="menubar">
+	  <property name="visible">True</property>
+	  <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+	  <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+
+	  <child>
+	    <widget class="GtkMenuItem" id="file_menu">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">_File</property>
+	      <property name="use_underline">True</property>
+
+	      <child>
+		<widget class="GtkMenu" id="file_menu_menu">
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="open_session_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label">gtk-open</property>
+		      <property name="use_stock">True</property>
+		      <signal name="activate" handler="on_open_session_menuitem_activate" last_modification_time="Sun, 01 Oct 2006 07:00:37 GMT"/>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="save_session_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label">gtk-save</property>
+		      <property name="use_stock">True</property>
+		      <signal name="activate" handler="on_save_session_menuitem_activate" last_modification_time="Sun, 01 Oct 2006 07:01:40 GMT"/>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="save_session_as_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label">gtk-save-as</property>
+		      <property name="use_stock">True</property>
+		      <signal name="activate" handler="on_save_session_as_menuitem_activate" last_modification_time="Sun, 01 Oct 2006 07:01:40 GMT"/>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkSeparatorMenuItem" id="separator5">
+		      <property name="visible">True</property>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="file_quit_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label">gtk-quit</property>
+		      <property name="use_stock">True</property>
+		      <signal name="activate" handler="on_quit1_activate" last_modification_time="Sat, 11 Sep 2004 20:05:11 GMT"/>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkMenuItem" id="view_menu">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">_View</property>
+	      <property name="use_underline">True</property>
+
+	      <child>
+		<widget class="GtkMenu" id="view_menu_menu">
+
+		  <child>
+		    <widget class="GtkCheckMenuItem" id="view_messages_menuitem">
+		      <property name="visible">True</property>
+		      <property name="tooltip" translatable="yes">View &quot;console&quot; messages</property>
+		      <property name="label" translatable="yes">_Messages</property>
+		      <property name="use_underline">True</property>
+		      <property name="active">False</property>
+		      <signal name="activate" handler="on_messages1_activate" last_modification_time="Sat, 14 Oct 2006 15:43:59 GMT"/>
+		      <accelerator key="M" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+		    </widget>
+		  </child>
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="view_refresh_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">_Refresh</property>
+		      <property name="use_underline">True</property>
+		      <signal name="activate" handler="on_refresh2_activate" last_modification_time="Sat, 11 Sep 2004 20:05:50 GMT"/>
+		      <accelerator key="R" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+		      <child internal-child="image">
+			<widget class="GtkImage" id="image677">
+			  <property name="visible">True</property>
+			  <property name="stock">gtk-refresh</property>
+			  <property name="icon_size">1</property>
+			  <property name="xalign">0.5</property>
+			  <property name="yalign">0.5</property>
+			  <property name="xpad">0</property>
+			  <property name="ypad">0</property>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkMenuItem" id="help_menu">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">_Help</property>
+	      <property name="use_underline">True</property>
+
+	      <child>
+		<widget class="GtkMenu" id="help_menu_menu">
+
+		  <child>
+		    <widget class="GtkImageMenuItem" id="help_about_menuitem">
+		      <property name="visible">True</property>
+		      <property name="label">gtk-about</property>
+		      <property name="use_stock">True</property>
+		      <signal name="activate" handler="on_about1_activate" last_modification_time="Sat, 11 Sep 2004 20:05:11 GMT"/>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkToolbar" id="toolbar1">
+	  <property name="visible">True</property>
+	  <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+	  <property name="toolbar_style">GTK_TOOLBAR_BOTH_HORIZ</property>
+	  <property name="tooltips">True</property>
+	  <property name="show_arrow">True</property>
+
+	  <child>
+	    <widget class="GtkToolButton" id="zoom_normal_but">
+	      <property name="visible">True</property>
+	      <property name="stock_id">gtk-zoom-100</property>
+	      <property name="visible_horizontal">True</property>
+	      <property name="visible_vertical">True</property>
+	      <property name="is_important">False</property>
+	    </widget>
+	    <packing>
+	      <property name="expand">False</property>
+	      <property name="homogeneous">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkToolButton" id="zoom_full_but">
+	      <property name="visible">True</property>
+	      <property name="stock_id">gtk-zoom-fit</property>
+	      <property name="visible_horizontal">True</property>
+	      <property name="visible_vertical">True</property>
+	      <property name="is_important">False</property>
+	    </widget>
+	    <packing>
+	      <property name="expand">False</property>
+	      <property name="homogeneous">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkVPaned" id="main_paned">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="position">0</property>
+
+	  <child>
+	    <widget class="GtkScrolledWindow" id="canvas_scrolledwindow">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+	      <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+	      <property name="shadow_type">GTK_SHADOW_IN</property>
+	      <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+	      <child>
+		<placeholder/>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="shrink">False</property>
+	      <property name="resize">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkExpander" id="messages_expander">
+	      <property name="can_focus">True</property>
+	      <property name="expanded">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkScrolledWindow" id="scrolledwindow1">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+		  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="shadow_type">GTK_SHADOW_IN</property>
+		  <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+		  <child>
+		    <widget class="GtkTextView" id="status_text">
+		      <property name="visible">True</property>
+		      <property name="sensitive">False</property>
+		      <property name="can_focus">True</property>
+		      <property name="editable">False</property>
+		      <property name="overwrite">False</property>
+		      <property name="accepts_tab">True</property>
+		      <property name="justification">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap_mode">GTK_WRAP_WORD</property>
+		      <property name="cursor_visible">False</property>
+		      <property name="pixels_above_lines">0</property>
+		      <property name="pixels_below_lines">0</property>
+		      <property name="pixels_inside_wrap">0</property>
+		      <property name="left_margin">0</property>
+		      <property name="right_margin">0</property>
+		      <property name="indent">0</property>
+		      <property name="text" translatable="yes"></property>
+		    </widget>
+		  </child>
+		</widget>
+	      </child>
+
+	      <child>
+		<widget class="GtkLabel" id="label4">
+		  <property name="visible">True</property>
+		  <property name="label" translatable="yes">Messages</property>
+		  <property name="use_underline">False</property>
+		  <property name="use_markup">False</property>
+		  <property name="justify">GTK_JUSTIFY_LEFT</property>
+		  <property name="wrap">False</property>
+		  <property name="selectable">False</property>
+		  <property name="xalign">0.5</property>
+		  <property name="yalign">0.5</property>
+		  <property name="xpad">0</property>
+		  <property name="ypad">0</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		  <property name="width_chars">-1</property>
+		  <property name="single_line_mode">False</property>
+		  <property name="angle">0</property>
+		</widget>
+		<packing>
+		  <property name="type">label_item</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="shrink">True</property>
+	      <property name="resize">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">2</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkAboutDialog" id="about_win">
+  <property name="destroy_with_parent">True</property>
+  <property name="name" translatable="yes">Machina</property>
+  <property name="copyright" translatable="yes">© 2007 Dave Robillard &lt;http://drobilla.net&gt;</property>
+  <property name="comments" translatable="yes">It's a machine</property>
+  <property name="license" translatable="yes">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 2 of the License, or
+(at your option) 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, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+</property>
+  <property name="wrap_license">False</property>
+  <property name="website">http://drobilla.net/software/machina</property>
+  <property name="website_label" translatable="yes">Website:</property>
+  <property name="authors">Dave Robillard &lt;dave@drobilla.net&gt;</property>
+  <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>
+  <property name="logo">machina-icon.svg</property>
+</widget>
+
+</glade-interface>
diff --git a/src/gui/machina.gladep b/src/gui/machina.gladep
new file mode 100644
index 0000000..c27832f
--- /dev/null
+++ b/src/gui/machina.gladep
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
+
+<glade-project>
+  <name>Machina</name>
+  <program_name>machina</program_name>
+  <language>C++</language>
+  <gnome_support>FALSE</gnome_support>
+</glade-project>
diff --git a/src/gui/main.cpp b/src/gui/main.cpp
new file mode 100644
index 0000000..7250cb1
--- /dev/null
+++ b/src/gui/main.cpp
@@ -0,0 +1,44 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 2 of the License, or (at your option) 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 details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+//#include "../../config.h"
+
+#include <iostream>
+#include <libgnomecanvasmm.h>
+
+#include "MachinaGUI.hpp"
+
+int
+main(int argc, char** argv)
+{
+	try {
+	
+	Gnome::Canvas::init();
+	Gtk::Main app(argc, argv);
+	
+	MachinaGUI gui;
+	app.run(*gui.window());
+	
+	} catch (std::string msg) {
+		std::cerr << "Caught exception, aborting.  Error message was: " << msg << std::endl;
+		return 1;
+	}
+
+	return 0;
+}
+
+
-- 
cgit v1.2.1