Connector: more UI improvements
[calf.git] / bigbull / jackvis.py
blob939ba835b275ecdfc92b7d3a8066b27c7374a2d6
1 #!/usr/bin/python
2 import pygtk
3 pygtk.require('2.0')
4 import dbus
5 import dbus.mainloop.glib
6 import gtk
7 import conndiagram
8 from calfgtkutils import *
9 Colors = conndiagram.Colors
11 class JACKPortInfo(object):
12 id = 0
13 name = ""
14 flags = 0
15 format = 0
17 def __init__(self, full_name, port_id, name, flags, format):
18 self.full_name = full_name
19 self.client_name = full_name.split(":", 1)[0]
20 self.port_id = int(port_id)
21 self.name = str(name)
22 self.flags = int(flags)
23 self.format = int(format)
25 def get_name(self):
26 return self.name
28 def get_id(self):
29 return self.full_name
31 def is_audio(self):
32 return self.format == 0
34 def is_midi(self):
35 return self.format == 1
37 def is_port_input(self):
38 return (self.flags & 1) != 0
40 def is_port_output(self):
41 return (self.flags & 2) != 0
43 def get_port_color(self):
44 if self.is_audio():
45 return Colors.audioPort
46 elif self.is_midi():
47 return Colors.eventPort
48 else:
49 print "Unknown type %s" % self.format
50 return Colors.controlPort
52 class JACKClientInfo(object):
53 id = 0
54 name = ""
55 def __init__(self, id, name, ports):
56 self.id = id
57 self.name = name
58 self.ports = []
59 for p in ports:
60 self.add_port(*p)
61 def get_name(self):
62 return self.name
63 def add_port(self, port_id, name, flags, format):
64 model = JACKPortInfo("%s:%s" % (self.name, name), port_id, name, flags, format)
65 self.ports.append(model)
66 return model
68 class JACKGraphInfo(object):
69 version = 0
70 def __init__(self, version, clients, connections):
71 self.version = version
72 self.clients = []
73 self.client_map = {}
74 self.connections_id = set()
75 self.connections_name = set()
76 for c in clients:
77 self.add_client(*c)
78 for tuple in connections:
79 self.add_connection(*tuple)
81 def add_client(self, id, name, ports):
82 c = JACKClientInfo(id, name, ports)
83 self.clients.append(c)
84 self.client_map[name] = c
86 def add_connection(self, cid1, cname1, pid1, pname1, cid2, cname2, pid2, pname2, something):
87 self.connections_name.add(("%s:%s" % (cname1, pname1), "%s:%s" % (cname2, pname2)))
88 self.connections_id.add(("%s:%s" % (cid1, pid1), "%s:%s" % (cid2, pid2)))
90 class ClientModuleModel():
91 def __init__(self, client, checker):
92 (self.client, self.checker) = (client, checker)
93 def get_name(self):
94 return self.client.get_name()
95 def get_port_list(self):
96 return filter(self.checker, self.client.ports)
98 class JACKGraphController(object):
99 def __init__(self):
100 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
101 self.bus = dbus.SessionBus()
102 self.service = self.bus.get_object("org.jackaudio.service", "/org/jackaudio/Controller")
103 self.patchbay = dbus.Interface(self.service, "org.jackaudio.JackPatchbay")
104 self.graph = None
105 self.view = None
106 self.fetch_graph()
107 self.patchbay.connect_to_signal('PortAppeared', self.port_appeared)
108 self.patchbay.connect_to_signal('PortDisappeared', self.port_disappeared)
109 self.patchbay.connect_to_signal('PortsConnected', self.ports_connected)
110 self.patchbay.connect_to_signal('PortsDisconnected', self.ports_disconnected)
111 def port_appeared(self, seq_no, client_id, client_name, port_id, port_name, flags, format):
112 model = self.graph.client_map[str(client_name)].add_port(int(port_id), str(port_name), int(flags_format))
113 print "PortAppeared", model
114 def port_disappeared(self, seq_no, client_id, client_name, port_id, port_name):
115 print "PortDisappeared", str(client_name), str(port_name)
116 def ports_connected(self, *args):
117 print "PortsConnected", args
118 self.graph.add_connection(*args[1:])
119 def ports_disconnected(self, *args):
120 print "PortsDisconnected", args
121 def fetch_graph(self):
122 req_version = self.graph.version if self.graph is not None else 0
123 graphdata = self.patchbay.GetGraph(req_version)
124 if int(graphdata[0]) != req_version:
125 self.graph = JACKGraphInfo(*graphdata)
126 def can_connect(self, first, second):
127 if first.is_port_input() == second.is_port_input():
128 return False
129 if first.is_audio() and second.is_audio():
130 return True
131 if first.is_midi() and second.is_midi():
132 return True
133 return False
134 def is_connected(self, first, second):
135 return (first.get_id(), second.get_id()) in self.graph.connections_name
136 def connect(self, first, second):
137 if self.is_connected(first, second):
138 self.disconnect(first, second)
139 return
140 self.patchbay.ConnectPortsByName(first.client_name, first.name, second.client_name, second.name)
141 self.graph.connections_name.add((first.get_id(), second.get_id()))
142 self.view.connect(self.view.get_port_view(first), self.view.get_port_view(second))
143 def disconnect(self, first, second):
144 self.patchbay.DisconnectPortsByName(first.client_name, first.name, second.client_name, second.name)
145 self.graph.connections_name.remove((first.get_id(), second.get_id()))
146 self.view.disconnect(self.view.get_port_view(first), self.view.get_port_view(second))
147 def refresh(self):
148 self.fetch_graph()
149 self.view.clear()
150 self.add_all()
151 def add_all(self):
152 width, left_mods = self.add_clients(10.0, 10.0, lambda port: port.is_port_output())
153 width2, right_mods = self.add_clients(width + 20, 10.0, lambda port: port.is_port_input())
154 pmap = self.view.get_port_map()
155 for (c, p) in self.graph.connections_name:
156 if p in pmap and c in pmap:
157 print "Connect %s to %s" % (c, p)
158 self.view.connect(pmap[c], pmap[p])
159 else:
160 print "Connect %s to %s - not found" % (c, p)
161 for m in left_mods:
162 m.translate(width - m.width, 0)
163 for m in right_mods:
164 m.translate(950 - width - 30 - width2, 0)
165 def add_clients(self, x, y, checker):
166 margin = 10
167 mwidth = 0
168 mods = []
169 for cl in self.graph.clients:
170 mod = self.view.add_module(ClientModuleModel(cl, checker), x, y)
171 y += mod.rect.props.height + margin
172 if mod.rect.props.width > mwidth:
173 mwidth = mod.rect.props.width
174 mods.append(mod)
175 return mwidth, mods
177 class App:
178 def __init__(self):
179 self.controller = JACKGraphController()
180 self.cgraph = conndiagram.ConnectionGraphEditor(self, self.controller)
181 self.controller.view = self.cgraph
183 def canvas_popup_menu_handler(self, widget):
184 self.canvas_popup_menu(0, 0, 0)
185 return True
187 def canvas_popup_menu(self, x, y, time):
188 menu = gtk.Menu()
189 items = self.cgraph.get_data_items_at(x, y)
190 found = False
191 for i in items:
192 if i[0] == "wire":
193 wire = i[1]
194 add_option(menu, "Disconnect", self.disconnect, wire)
195 found = True
196 break
197 elif i[0] == "port":
198 port = i[1]
199 add_option(menu, "Disconnect All", self.disconnect_all, port, enabled = len(port.get_connections()))
200 found = True
201 break
202 if found:
203 menu.show_all()
204 menu.popup(None, None, None, 3, time)
206 def disconnect(self, wire):
207 self.controller.disconnect(wire.src.model, wire.dest.model)
208 wire.remove()
210 def disconnect_all(self, port):
211 for wire in list(port.module.wires):
212 if wire.src == port or wire.dest == port:
213 self.controller.disconnect(wire.src.model, wire.dest.model)
214 wire.remove()
216 def create(self):
217 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
218 self.window.connect("delete_event", self.delete_event)
219 self.window.connect("destroy", self.destroy)
220 self.main_vbox = gtk.VBox()
221 self.create_main_menu()
222 self.scroll = gtk.ScrolledWindow()
223 self.scroll.add(self.cgraph.create(950, 650))
224 self.main_vbox.pack_start(self.menu_bar, expand = False, fill = False)
225 self.main_vbox.add(self.scroll)
226 self.window.add(self.main_vbox)
227 self.window.show_all()
228 self.main_vbox.connect("popup-menu", self.canvas_popup_menu_handler)
229 self.controller.add_all()
230 self.cgraph.clear()
231 self.controller.add_all()
232 self.cgraph.canvas.update()
233 #this is broken
234 #self.cgraph.blow_up()
236 def create_main_menu(self):
237 self.menu_bar = gtk.MenuBar()
238 self.file_menu = add_submenu(self.menu_bar, "_File")
239 add_option(self.file_menu, "_Refresh", self.refresh)
240 add_option(self.file_menu, "_Blow-up", self.blow_up)
241 add_option(self.file_menu, "_Exit", self.exit)
243 def refresh(self, data):
244 self.controller.refresh()
246 def blow_up(self, data):
247 self.cgraph.blow_up()
249 def exit(self, data):
250 self.window.destroy()
252 def delete_event(self, widget, data = None):
253 gtk.main_quit()
254 def destroy(self, widget, data = None):
255 gtk.main_quit()
257 app = App()
258 app.create()
259 gtk.main()