#include #include #include #include "jack.h" #include "portmanager.h" #include "matrix.h" #include "config.h" const Rect Rect::None = Rect(0, 0, -1, -1); Matrix::Matrix(PortType port_type) : m_client_width(0), m_client_height(0), m_port_width(0), m_port_height(0), m_separation(0), m_x_rect(Rect::None), m_y_rect(Rect::None), m_port_type(port_type) { set_size_request(0, 0); show(); add_events(Gdk::BUTTON_PRESS_MASK); add_events(Gdk::POINTER_MOTION_MASK | Gdk::POINTER_MOTION_HINT_MASK); } Matrix::~Matrix() { } void Matrix::Refresh() { Glib::RefPtr window = get_window(); if (!window) return; m_client_width = 0; m_client_height = 0; m_port_width = 0; m_port_height = 0; m_separation = 0; pm.Sort(); int cols = 0; int rows = 0; Cairo::RefPtr cr = window->create_cairo_context(); cr->select_font_face(cfg.FontFace, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL); cr->set_font_size(cfg.FontSize); PortList::iterator plit; for (plit = pm.m_ports.begin(); plit != pm.m_ports.end(); ++plit) { Port *p = *plit; if (m_port_type != PT_ALL && m_port_type != (p->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; p->rect = Rect::None; } ClientList::iterator clit; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; cr->get_text_extents(c->m_name, c->extents); int width = c->extents.width + cfg.CellPadding * 2; if (c->m_is_input) { if (width > m_client_height) m_client_height = width; } else { if (width > m_client_width) m_client_width = width; } if (c->extents.height > m_separation) m_separation = c->extents.height; if (!c->m_expanded) { if (c->m_is_input) { cols++; } else { rows++; } PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; pg->rect = Rect::None; } continue; } PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; cr->get_text_extents(pg->m_name, pg->extents); if (pg->extents.height > m_separation) m_separation = pg->extents.height; if (!pg->m_expanded && pg->m_name != "") { int width = pg->extents.width + cfg.CellPadding * 2; if (c->m_is_input) { if (width > m_port_height) m_port_height = width; cols++; } else { if (width > m_port_width) m_port_width = width; rows++; } continue; } PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; cr->get_text_extents(p->m_name, p->extents); int width = pg->extents.width + p->extents.width + cfg.CellPadding * (pg->extents.width > 0 ? 4 : 2); if (p->m_is_input) { if (width > m_port_height) m_port_height = width; cols++; } else { if (width > m_port_width) m_port_width = width; rows++; } if (p->extents.height > m_separation) m_separation = p->extents.height; } } } /* PortList::iterator plit; for (plit = pm.m_ports.begin(); plit != pm.m_ports.end(); ++plit) { Port *p = *plit; cr->get_text_extents(p->m_name, p->extents); if (p->m_flags & JackPortIsInput) { if (p->extents.width > m_port_height) m_port_height = p->extents.width; } else { if (p->extents.width > m_port_width) m_port_width = p->extents.width; } if (p->extents.height > m_separation) m_separation = p->extents.height; } */ m_separation += cfg.CellPadding * 2; // if (m_port_width > m_separation * 5) m_port_width = m_separation * 5; // if (m_port_height > m_separation * 5) m_port_height = m_separation * 5; int y = m_client_height + m_port_height + cfg.CellPadding; int x = m_client_width + m_port_width + cfg.CellPadding; across.clear(); down.clear(); Rect r; // ClientList::iterator clit; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; if (c->m_is_input) { r.left = x; r.top = cfg.CellPadding; r.width = -1; r.height = m_client_height; c->rect = r; if (!c->m_expanded) { c->rect.width = m_separation; c->rect.height = m_client_height + m_port_height; across.push_back(c); x += m_separation; continue; } PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; if (pg->m_name != "") { r.left = x; r.top = cfg.CellPadding + m_client_height; r.width = pg->m_expanded ? m_separation * pg->m_ports.size() : m_separation; r.height = pg->m_expanded ? pg->extents.width + cfg.CellPadding * 2 : m_port_height; pg->rect = r; if (!pg->m_expanded) { c->rect.width = pg->rect.left - c->rect.left + m_separation; across.push_back(pg); x += m_separation; continue; } r.top += r.height; r.width = m_separation; r.height = m_port_height - r.height; } else { pg->rect = Rect::None; r.top = cfg.CellPadding + m_client_height; r.width = m_separation; r.height = m_port_height; } PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; r.left = x; p->rect = r; c->rect.width = p->rect.left - c->rect.left + m_separation; if (pg->rect != Rect::None) { pg->rect.width = p->rect.left - pg->rect.left + m_separation; } across.push_back(p); x += m_separation; } } } else { r.left = cfg.CellPadding; r.top = y; r.width = m_client_width; r.height = -1; c->rect = r; if (!c->m_expanded) { c->rect.width = m_client_width + m_port_width; c->rect.height = m_separation; down.push_back(c); y += m_separation; continue; } PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; if (pg->m_name != "") { r.left = cfg.CellPadding + m_client_width; r.top = y; r.width = pg->m_expanded ? pg->extents.width + cfg.CellPadding * 2 : m_port_width; r.height = pg->m_expanded ? m_separation * pg->m_ports.size() : m_separation; pg->rect = r; if (!pg->m_expanded) { c->rect.height = pg->rect.top - c->rect.top + m_separation; down.push_back(pg); y += m_separation; continue; } r.left += r.width; r.width = m_port_width - r.width; r.height = m_separation; } else { pg->rect = Rect::None; r.left = cfg.CellPadding + m_client_width; r.width = m_port_width; r.height = m_separation; } PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; r.top = y; p->rect = r; c->rect.height = p->rect.top - c->rect.top + m_separation; if (pg->rect != Rect::None) { pg->rect.height = p->rect.top - pg->rect.top + m_separation; } down.push_back(p); y += m_separation; } } } } int w = cfg.CellPadding * 2 + m_client_width + m_port_width; int h = cfg.CellPadding * 2 + m_client_height + m_port_height; w += cols * m_separation; h += rows * m_separation; m_width = w; m_height = h; set_size_request(w, h); } static void SetColour(Cairo::RefPtr cr, Colour c) { cr->set_source_rgb(c.r, c.g, c.b); } void Matrix::Box(Cairo::RefPtr cr, Colour &c, Rect &rect, bool highlight) { SetColour(cr, c * (highlight ? 1.5 : 1.0)); cr->rectangle(rect.left, rect.top, rect.width, rect.height); cr->fill(); if (highlight) { cr->set_source_rgb(1.0, 1.0, 1.0); } else { cr->set_source_rgb(0.75, 0.75, 0.75); } cr->rectangle(rect.left, rect.top, rect.width, rect.height); cr->stroke(); SetColour(cr, cfg.Text * (highlight ? 1.5 : 1.0)); } bool Matrix::on_expose_event(GdkEventExpose *event) { Glib::RefPtr window = get_window(); if (!window) return true; if (m_client_width == 0) Refresh(); Gtk::Allocation allocation = get_allocation(); const int alloc_width = allocation.get_width(); const int alloc_height = allocation.get_height(); Cairo::RefPtr cr = window->create_cairo_context(); // Set clipping area cr->rectangle(event->area.x, event->area.y, event->area.width, event->area.height); cr->clip(); cr->set_line_width(1.0); if (!cfg.Antialias) { cr->set_antialias(Cairo::ANTIALIAS_NONE); } cr->translate(0.5, 0.5); // Clear background SetColour(cr, cfg.Background); cr->rectangle(0, 0, alloc_width, alloc_height); cr->fill(); cr->set_source_rgb(1, 1, 1); cr->select_font_face(cfg.FontFace, Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL); cr->set_font_size(cfg.FontSize); Rect tr; Rect &r = tr; ClientList::iterator clit; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; r = c->rect; if (c->m_is_input) { Box(cr, cfg.TabClient, r, m_y_rect == r); cr->save(); cr->move_to(r.left + cfg.CellPadding - c->extents.y_bearing, r.top + r.height - cfg.CellPadding - c->extents.x_bearing); cr->rotate(2 * M_PI * 0.75); cr->show_text(c->m_name); cr->restore(); if (!c->m_expanded) { SetColour(cr, cfg.GridLinesClient); cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(r.left + r.width, m_height - cfg.CellPadding); cr->stroke(); continue; } PortGroupList::iterator pglast = c->m_groups.end(); --pglast; PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; r = pg->rect; if (pg->m_name != "") { Box(cr, cfg.TabGroup, r, m_y_rect == r); cr->save(); cr->move_to(r.left + cfg.CellPadding - pg->extents.y_bearing, r.top + r.height - cfg.CellPadding - pg->extents.x_bearing); cr->rotate(2 * M_PI * 0.75); cr->show_text(pg->m_name); cr->restore(); if (!pg->m_expanded) { if (pglit == pglast) { SetColour(cr, cfg.GridLinesClient); } else { SetColour(cr, cfg.GridLinesGroup); } cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(r.left + r.width, m_height - cfg.CellPadding); cr->stroke(); continue; } } PortList::iterator plast = pg->m_ports.end(); --plast; PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; r = p->rect; Box(cr, p->m_is_midi ? cfg.TabPortMidi : cfg.TabPortAudio, r, m_y_rect == r); cr->save(); cr->move_to(r.left + cfg.CellPadding - p->extents.y_bearing, r.top + r.height - cfg.CellPadding - p->extents.x_bearing); cr->rotate(2 * M_PI * 0.75); cr->show_text(p->m_name); cr->restore(); if (pglit == pglast && plit == plast) { SetColour(cr, cfg.GridLinesClient); } else if (plit == plast) { SetColour(cr, cfg.GridLinesGroup); } else { SetColour(cr, cfg.GridLinesPort); } cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(r.left + r.width, m_height - cfg.CellPadding); cr->stroke(); } } } else { Box(cr, cfg.TabClient, r, m_x_rect == r); cr->move_to(r.left + cfg.CellPadding - c->extents.x_bearing, r.top + cfg.CellPadding - c->extents.y_bearing); cr->show_text(c->m_name); if (!c->m_expanded) { SetColour(cr, cfg.GridLinesClient); cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(m_width - cfg.CellPadding, r.top + r.height); cr->stroke(); continue; } PortGroupList::iterator pglast = c->m_groups.end(); --pglast; PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; r = pg->rect; if (pg->m_name != "") { Box(cr, cfg.TabGroup, r, m_x_rect == r); cr->move_to(r.left + cfg.CellPadding - pg->extents.x_bearing, r.top + cfg.CellPadding - pg->extents.y_bearing); cr->show_text(pg->m_name); if (!pg->m_expanded) { if (pglit == pglast) { SetColour(cr, cfg.GridLinesClient); } else { SetColour(cr, cfg.GridLinesGroup); } cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(m_width - cfg.CellPadding, r.top + r.height); cr->stroke(); continue; } } PortList::iterator plast = pg->m_ports.end(); --plast; PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; r = p->rect; Box(cr, p->m_is_midi ? cfg.TabPortMidi : cfg.TabPortAudio, r, m_x_rect == r); cr->move_to(r.left + cfg.CellPadding - p->extents.x_bearing, r.top + cfg.CellPadding - p->extents.y_bearing); cr->show_text(p->m_name); if (pglit == pglast && plit == plast) { SetColour(cr, cfg.GridLinesClient); } else if (plit == plast) { SetColour(cr, cfg.GridLinesGroup); } else { SetColour(cr, cfg.GridLinesPort); } cr->move_to(r.left + r.width, r.top + r.height); cr->line_to(m_width - cfg.CellPadding, r.top + r.height); cr->stroke(); } } } } float radius = (float)(m_separation - cfg.CellPadding) * 0.4; int x = m_client_width + m_port_width + cfg.CellPadding; BaseList::iterator bit1; for (bit1 = across.begin(); bit1 != across.end(); ++bit1) { BaseList::iterator bit2; Base *b1 = *bit1; int y = m_client_height + m_port_height + cfg.CellPadding; for (bit2 = down.begin(); bit2 != down.end(); ++bit2) { Base *b2 = *bit2; ConnectionMode cm = b1->ConnectedTo(b2); if (cm != CM_NONE) { if (b1->m_is_midi) { SetColour(cr, cfg.TabPortMidi * 1.5); } else { SetColour(cr, cfg.TabPortAudio * 1.5); } //cr->rectangle(x + 1, y + 1, m_separation - 2, m_separation - 2); cr->arc((float)x + m_separation * 0.5, (float)y + m_separation * 0.5, radius, 0, 2 * M_PI); cr->fill(); } y += m_separation; } x += m_separation; } if (m_x_rect && m_y_rect && m_x_is_midi == m_y_is_midi) { int x = m_client_width + m_port_width + cfg.CellPadding; int y = m_client_height + m_port_height + cfg.CellPadding; float line_x = (float)m_y_rect.left + m_separation * 0.5; float line_y = (float)m_x_rect.top + m_separation * 0.5; SetColour(cr, cfg.Text); cr->move_to(x, line_y); cr->line_to(line_x - radius, line_y); cr->move_to(line_x, y); cr->line_to(line_x, line_y - radius); cr->stroke(); cr->arc(line_x, line_y, radius, 0, 2 * M_PI); cr->stroke(); } return true; } bool Matrix::on_button_press_event(GdkEventButton *event) { // Adjust for window borders int x = event->x - 1; int y = event->y - 1; // Check expandable boxes ClientList::iterator clit; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; if (c->rect.Hit(x, y)) { c->m_expanded = !c->m_expanded; Refresh(); queue_draw(); return true; } PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; if (pg->rect.Hit(x, y)) { pg->m_expanded = !pg->m_expanded; Refresh(); queue_draw(); return true; } } } // Nothing selected... if (!m_x_rect || !m_y_rect) return false; // Check port hit PortGroup *pg1 = NULL; PortGroup *pg2 = NULL; Port *p1 = NULL; Port *p2 = NULL; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; PortGroupList::iterator pglit; for (pglit = c->m_groups.begin(); pglit != c->m_groups.end(); ++pglit) { PortGroup *pg = *pglit; PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end(); ++plit) { Port *p = *plit; if (p->rect.HitX(x)) p1 = p; if (p->rect.HitY(y)) p2 = p; if (p1 != NULL && p2 != NULL) break; if (p1 != NULL && p2 != NULL) { pm.ToggleConnect(p1, p2); return true; } } if (pg->rect.HitX(x)) pg1 = pg; if (pg->rect.HitY(y)) pg2 = pg; if (pg1 != NULL && pg2 != NULL) break; } } if (event->button == 3) { // Right button pressed // Try groups first if (pg1 != NULL && pg2 != NULL) { pm.ToggleConnect(pg1, pg2); return true; } if (pg1 != NULL && p2 != NULL) { pm.ToggleConnect(pg1, p2); return true; } if (p1 != NULL && pg2 != NULL) { pm.ToggleConnect(p1, pg2); return true; } if (p1 != NULL && p2 != NULL) { pm.ToggleConnect(p1, p2); return true; } } else if (event->button == 1) { // Left button pressed // Try ports first if (p1 != NULL && p2 != NULL) { pm.ToggleConnect(p1, p2); return true; } if (p1 != NULL && pg2 != NULL) { pm.ToggleConnect(p1, pg2); return true; } if (pg1 != NULL && p2 != NULL) { pm.ToggleConnect(pg1, p2); return true; } if (pg1 != NULL && pg2 != NULL) { pm.ToggleConnect(pg1, pg2); return true; } } return false; } bool Matrix::on_motion_notify_event(GdkEventMotion *event) { int x = event->x; int y = event->y; Gdk::ModifierType state = Gdk::ModifierType(event->state); if (event->is_hint) { get_window()->get_pointer(x, y, state); } x -= 1; y -= 1; Rect x_rect = Rect::None; Rect y_rect = Rect::None; bool x_set = false; bool y_set = false; ClientList::iterator clit; PortGroupList::iterator pglit; PortList::iterator plit; for (clit = pm.m_clients.begin(); clit != pm.m_clients.end(); ++clit) { Client *c = *clit; if (m_port_type != PT_ALL && m_port_type != (c->m_is_midi ? PT_MIDI : PT_AUDIO)) continue; if (c->m_is_input) { for (pglit = c->m_groups.begin(); pglit != c->m_groups.end() && !y_rect; ++pglit) { PortGroup *pg = *pglit; PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end() && !y_rect; ++plit) { Port *p = *plit; p->rect.SetIfHitX(x, y_rect); } pg->rect.SetIfHitX(x, y_rect); } c->rect.SetIfHitX(x, y_rect); if (y_rect && !y_set) { m_y_is_midi = c->m_is_midi; y_set = true; } } else { for (pglit = c->m_groups.begin(); pglit != c->m_groups.end() && !x_rect; ++pglit) { PortGroup *pg = *pglit; PortList::iterator plit; for (plit = pg->m_ports.begin(); plit != pg->m_ports.end() && !x_rect; ++plit) { Port *p = *plit; p->rect.SetIfHitY(y, x_rect); } pg->rect.SetIfHitY(y, x_rect); } c->rect.SetIfHitY(y, x_rect); if (x_rect && !x_set) { m_x_is_midi = c->m_is_midi; x_set = true; } } if (x_rect && y_rect) break; } // If either box is unselected, make sure both are. if (!x_rect || !y_rect) { x_rect = Rect::None; y_rect = Rect::None; } // If the selection has changed, redraw matrix. if (x_rect != m_x_rect || y_rect != m_y_rect) { m_x_rect = x_rect; m_y_rect = y_rect; queue_draw(); } return true; }