//
// canvas.cc
//
#include "wx/brush.h"
#include "wx/colour.h"
#include "wx/dc.h"
#include "wx/dcclient.h"
#include "wx/font.h"
#include "wx/gdicmn.h"
#include "wx/pen.h"
#include "wx/scrolwin.h"
#include "wx/textdlg.h"
#include "canvas.h"
#include "edge.h"
#include "graph.h"
#include "gui.h"
#include "lang.h"
#include "paramdialog.h"
#include "vertex.h"
BEGIN_EVENT_TABLE(Canvas, wxScrolledWindow)
EVT_PAINT (Canvas::OnPaint)
EVT_ERASE_BACKGROUND(Canvas::OnEraseBackground)
EVT_LEFT_DOWN (Canvas::OnClick)
EVT_LEFT_DCLICK (Canvas::OnClick)
EVT_RIGHT_DOWN (Canvas::OnClick)
EVT_MOTION (Canvas::OnMouseMove)
EVT_CHAR (Canvas::OnKeyPress)
END_EVENT_TABLE()
void Canvas::draw (wxDC &dc, Vertex *v)
{
int r = Canvas::vertex_radius;
wxColour col = black;
if (v->selected) {
unsigned int idx = v->selection_colour %
selection_colours.size ();
col = selection_colours[idx];
}
dc.SetBrush (wxBrush (col));
dc.SetPen (wxPen (col));
dc.DrawCircle (v->x, v->y, r);
if (do_labels) {
dc.SetFont (*bold_font);
dc.SetTextForeground (col);
dc.DrawText (v->label, v->x + r, v->y - 2 * r);
}
}
void Canvas::draw (wxDC &dc, Edge *e, bool curved)
{
double dx, dy, r = Canvas::vertex_radius;
double theta;
wxColour col = black;
if (e->selected) {
unsigned int idx = e->selection_colour %
selection_colours.size ();
col = selection_colours[idx];
}
wxPen pen (col, Canvas::edge_width);
pen.SetCap (wxCAP_BUTT);
pen.SetJoin (wxJOIN_MITER);
dc.SetPen (pen);
dc.SetBrush (wxBrush (col));
theta = atan2 (e->w->y - e->v->y, e->w->x - e->v->x);
dx = r * cos (theta);
dy = r * sin (theta);
if (e->v->y == e->w->y) // horizontal line (dy = 0)
dy = 0;
else if (e->v->x == e->w->x) // vertical line (dx = 0)
dx = 0;
if (!curved)
dc.DrawLine (int (e->v->x + dx), int (e->v->y + dy),
int (e->w->x - dx), int (e->w->y - dy));
else {
dc.SetBrush (wxBrush (white));
// TODO: get this right!
double cx, cy; // mid-spline control point
cx = (e->v->x + e->w->x) / 2 - 2 * dy;
cy = (e->v->y + e->w->y) / 2 - 2 * dx;
dc.DrawSpline (int (e->v->x + dx), int (e->v->y + dy),
int (cx), int (cy),
int (e->w->x - dx), int (e->w->y - dy));
dc.SetBrush (wxBrush (col));
}
if (e->directed) {
wxPoint tri[3];
double scale = 0.7;
tri[0] = wxPoint (0, 0);
tri[1] = wxPoint (int (scale * (dy - dx)),
int (-scale * (dx + dy)));
tri[2] = wxPoint (int (-scale * (dx + dy)),
int (scale * (dx - dy)));
dc.DrawPolygon (3, tri, int (e->w->x - dx), int (e->w->y - dy));
}
if (do_weights) {
wxString str;
if (!do_flows)
str = wxString::Format (wxT("%i"), e->weight);
else
str = wxString::Format (wxT("%i/%i"), e->flow, e->weight);
double rx, ry;
wxCoord w, h;
dc.SetFont (*normal_font);
dc.GetTextExtent (str, &w, &h);
rx = (e->v->x + e->w->x) / 2 + dy;
ry = (e->v->y + e->w->y) / 2 - dx;
if (curved) {
rx += dy;
ry -= dy;
}
if (theta < 0)
rx += w * sin (theta);
ry -= h / 2;
if (fabs (theta) > M_PI_2)
ry -= dx / 2;
dc.SetTextForeground (col);
dc.DrawText (str, int (rx), int (ry));
}
}
Vertex *Canvas::findVertex (int x, int y) const
{
Graph::v_const_iterator vit;
int r2 = Canvas::vertex_radius * Canvas::vertex_radius;
Graph *g = *gg;
for (vit = g->v_begin (); vit != g->v_end (); ++vit) {
int dx = x - (*vit)->x, dy = y - (*vit)->y;
if ((dx * dx + dy * dy) < r2)
return *vit;
}
return 0;
}
Edge *Canvas::findEdge (int x, int y) const
{
Graph::e_const_iterator eit;
double rad = Canvas::vertex_radius;
double r2 = rad * rad;
double best_dist = 0; // actually, best distance squared
Edge *best_edge = 0;
Graph *g = *gg;
// find straight-line edge closest to (x,y)
for (eit = g->e_begin (); eit != g->e_end (); ++eit) {
double x1, y1, x2, y2, s2, num, d2;
x1 = (*eit)->v->x;
y1 = (*eit)->v->y;
x2 = (*eit)->w->x;
y2 = (*eit)->w->y;
if (x1 < x2) {
if ((x < (x1 - rad)) || ((x2 + rad) < x))
continue;
} else if (x2 < x1) {
if ((x < (x2 - rad)) || ((x1 + rad) < x))
continue;
}
if (y1 < y2) {
if ((y < (y1 - rad)) || ((y2 + rad) < y))
continue;
} else if (y2 < y1) {
if ((y < (y2 - rad)) || ((y1 + rad) < y))
continue;
}
s2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (s2 < 0.01)
continue; // degenerate edge
num = (y2 - y1) * (x - x1) - (x2 - x1) * (y - y1);
d2 = num * num / s2;
if ((d2 < best_dist) || !best_edge) {
best_edge = *eit;
best_dist = d2;
}
}
// still only accept it if within a vertex of (x,y)
if (best_dist <= r2)
return best_edge;
return 0;
}
void Canvas::cb_Properties ()
{
Edge *e;
Graph *g = *gg;
Vertex *v;
v = g->v_selected_head;
e = g->e_selected_head;
if (v && !e) {
// Vertex Properties
wxString str = wxGetTextFromUser (_("Label:"),
_("Vertex Properties"),
v->label, gui);
if (str.IsEmpty ())
return;
// Handle duplicate labels
while (1) {
Vertex *f = g->find (str);
if (!f || (f == v))
break;
str += wxT("-dup");
}
if (str != v->label) {
gui->undoableAction (_("Rename vertex"));
g->rename (v, str);
redraw ();
}
} else if (e) {
// Edge Properties
ParamDialogEdge dlg (gui, _("Edge Properties"), e);
if (dlg.ShowModal () == wxID_CANCEL)
return;
int curr_dir = e->directed ? 1 : 0;
int new_wt = dlg.GetWeight (), new_dir = dlg.GetDirection ();
int new_flow = dlg.GetFlow ();
if ((new_wt == e->weight) && (new_dir == curr_dir)) {
if (e->flow != new_flow) {
e->flow = new_flow;
redraw ();
}
return;
}
if (new_wt == e->weight)
gui->undoableAction (_("Change edge direction"));
else if (new_dir == curr_dir)
gui->undoableAction (_("Change edge weight"));
else
gui->undoableAction (_("Change edge weight and direction"));
e->weight = new_wt;
if (new_dir == 0)
e->directed = false;
else if (new_dir == 1)
e->directed = true;
else {
e->directed = true;
Vertex *tmp = e->v;
e->v = e->w;
e->w = tmp;
}
redraw ();
}
}
Canvas::Canvas (GTFrame *gui, Graph **g)
: wxScrolledWindow (gui, -1, wxPoint (0, 0), wxDefaultSize,
// 0),
wxNO_FULL_REPAINT_ON_RESIZE | wxCLIP_CHILDREN),
gui (gui), gg (g)
{
vertex_mode = true;
do_labels = true;
do_weights = do_flows = false;
white = wxColour (255, 255, 255);
black = wxColour (0, 0, 0);
// Selection colours
const char *cols[] = {
"red", "blue", "magenta", "green",
"brown", "cyan", "black"
};
for (unsigned int i = 0; i < (sizeof (cols) / sizeof (cols[0])); ++i) {
wxColour col = wxTheColourDatabase->Find
(wxString (cols[i], wxConvUTF8));
selection_colours.push_back (col);
}
red = selection_colours[0];
int pointSize = Canvas::font_height;
normal_font = new wxFont (pointSize, wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
bold_font = new wxFont (pointSize, wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
SetScrollRate (10, 10);
}
#define USE_WXMEMORYDC
#ifdef USE_WXMEMORYDC
#include "wx/dcmemory.h"
#endif
void Canvas::OnPaint (wxPaintEvent &event)
{
#ifndef USE_WXMEMORYDC
wxPaintDC dc (this);
#else
wxPaintDC dest_dc (this);
int blit_width, blit_height;
GetClientSize (&blit_width, &blit_height);
wxBitmap bmp_blit (blit_width, blit_height);
wxMemoryDC dc;
dc.SelectObject (bmp_blit);
#endif
Graph *g = *gg;
#ifndef USE_WXMEMORYDC
DoPrepareDC (dc);
#else
DoPrepareDC (dc);
DoPrepareDC (dest_dc);
#endif
dc.SetBackground (wxBrush (white));
dc.Clear ();
Graph::e_const_iterator eit;
for (eit = g->e_begin (); eit != g->e_end (); ++eit) {
Edge *e = *eit;
bool curved = false;
if (e->directed) {
if (g->find (e->w, e->v, true))
curved = true;
}
draw (dc, e, curved);
}
Graph::v_const_iterator vit;
int maxx = 0, maxy = 0;
for (vit = g->v_begin (); vit != g->v_end (); ++vit) {
Vertex *v = *vit;
draw (dc, v);
if (v->x > maxx)
maxx = v->x;
if (v->y > maxy)
maxy = v->y;
}
#ifdef USE_WXMEMORYDC
// Blit!
dest_dc.Blit (0, 0, blit_width, blit_height, &dc, 0, 0);
#endif
SetVirtualSize (maxx + 30, maxy + 30);
}
void Canvas::OnEraseBackground (wxEraseEvent &event)
{
wxDC *dc = event.GetDC ();
dc->SetBackground (wxBrush (white));
dc->Clear ();
}
void Canvas::OnClick (wxMouseEvent &event)
{
Graph *g = *gg;
Vertex *v;
int m_x, m_y;
bool left_click = event.LeftDown () || event.LeftDClick (),
shifted = event.ShiftDown (),
double_click = event.ButtonDClick ();
CalcUnscrolledPosition (int (event.m_x), int (event.m_y), &m_x, &m_y);
v = findVertex (m_x, m_y);
// Assumptions: [lr]-click (or double click)
if (vertex_mode && left_click) {
if (!shifted) {
// no shift key
if (!double_click) {
// Single click
g->unselect_all ();
if (v)
g->select (v);
redraw ();
return;
} else {
// Double click
if (v)
cb_Properties ();
else {
gui->undoableAction (_("Add vertex"));
g->add (new Vertex ("", m_x, m_y));
redraw ();
}
return;
}
} else {
// shift key
if (!double_click) {
if (v) {
v->selected ?
g->unselect (v) :
g->select (v);
redraw ();
}
}
return;
}
} else if (vertex_mode && !left_click)
return; // do nothing
// edge mode
if (!v) {
Edge *e = findEdge (m_x, m_y);
if (!e) {
if (left_click && !shifted) {
g->unselect_all ();
redraw ();
}
return;
}
// clicking on an edge
if (!double_click) {
// Single click
if (left_click) {
if (!shifted) {
g->unselect_all ();
g->select (e);
} else {
e->selected ?
g->unselect (e) :
g->select (e);
}
} else
e->cycle_orientations ();
} else {
// Double click
if (left_click)
cb_Properties ();
}
redraw ();
return;
}
if (!left_click)
return;
if (!v->selected) {
Vertex *valt = g->v_selected_head;
if (!valt) {
g->select (v);
redraw ();
return;
}
if (!g->are_adjacent (valt, v)) {
gui->undoableAction (_("Add edge"));
g->add (new Edge (valt, v));
}
g->unselect_all ();
g->select (v);
redraw ();
return;
}
g->unselect (v);
redraw ();
}
void Canvas::OnMouseMove (wxMouseEvent &event)
{
Graph *g = *gg;
Vertex *v;
int m_x, m_y;
CalcUnscrolledPosition (int (event.m_x), int (event.m_y), &m_x, &m_y);
if (event.Moving ()) {
// Button not down => nothing to do
motion_last_x = -1;
motion_last_y = -1;
return;
}
if (motion_last_x == -1) {
// First motion event; record start only
motion_last_x = m_x;
motion_last_y = m_y;
return;
}
// Move stuff
for (v = g->v_selected_head; v; v = v->next) {
v->x += (m_x - motion_last_x);
v->y += (m_y - motion_last_y);
}
motion_last_x = m_x;
motion_last_y = m_y;
redraw ();
}
void Canvas::OnKeyPress (wxKeyEvent &event)
{
Graph *g = *gg;
if (!event.HasModifiers () && (event.GetKeyCode () == WXK_DELETE)) {
// delete selected objects
// TODO: make this message more accurate
gui->undoableAction (_("Delete objects"));
Edge *prevE, *e = g->e_selected_head;
while (e) {
prevE = e;
e = e->next;
g->remove (prevE);
}
Vertex *prevV, *v = g->v_selected_head;
while (v) {
prevV = v;
v = v->next;
g->remove (prevV);
}
redraw ();
return;
}
if (event.GetKeyCode () == ' ') {
gui->toggleMode ();
return;
}
// We don't want it - pass it back up
event.Skip ();
}
void Canvas::redraw ()
{
Refresh ();
Update ();
}
void Canvas::setVertexMode (bool v_mode)
{
vertex_mode = v_mode;
}
void Canvas::setEdgeMode ()
{
vertex_mode = false;
}
void Canvas::setParam (bool labels, bool weights, bool flows)
{
do_labels = labels;
do_weights = weights;
do_flows = flows;
}
syntax highlighted by Code2HTML, v. 0.9.1