//////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2018 Leo Tenenbaum // This file is part of GraphColoring. // // GraphColoring 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 // (at your option) any later version. // // GraphColoring 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 GraphColoring. If not, see . //////////////////////////////////////////////////////////////////////////////// #include "graph.hpp" #include #include #include #include "../level.hpp" #include "utils/errors.hpp" namespace graphcoloring { void Graph::ResetIDs() { Vertex::ResetID(); Edge::ResetID(); } Graph::Graph(gui::Window* window_, const gui::Position& viewport_position_, bool directed_) : window(window_), viewport_position(viewport_position_), edge_vertex(-1) { directed = directed_; can_add_new_vertices = true; v_keyup_callback = window->SetKeyupCallback([this] (gui::Window*) { if (can_add_new_vertices && !is_locked) AddVertex(window->GetMouseX()+viewport_position.X(), window->GetMouseY()+viewport_position.Y()); }, GDK_KEY_v); e_keyup_callback = window->SetKeyupCallback([this] (gui::Window*){EPressed();}, GDK_KEY_e); } Graph::~Graph() { Clear(); window->RemoveKeyupCallback(v_keyup_callback, GDK_KEY_v); window->RemoveKeyupCallback(e_keyup_callback, GDK_KEY_e); } int Graph::V() const { return vertices.size(); } int Graph::E() const { return edges.size(); } void Graph::AddOrigin(int id) { origins.push_back(id); } void Graph::Clear() { for (Vertex* v : vertices) delete v; for (Edge* e : edges) delete e; vertices.clear(); edges.clear(); ResetIDs(); } void Graph::Lock() { is_locked = true; for (Vertex* v : vertices) v->Lock(); for (Edge* e : edges) e->Lock(); } void Graph::Unlock() { is_locked = false; for (Vertex* v : vertices) v->Unlock(); for (Edge* e : edges) e->Unlock(); } void Graph::EPressed() { if (!can_add_new_edges || is_locked) return; if (edge_vertex != -1 && !HasVertexWithID(edge_vertex)) // Check if vertex was deleted. edge_vertex = -1; int hover_id = GetHoveringVertex(); if (edge_vertex == -1) { edge_vertex = hover_id; } else if (hover_id != -1) { if (edge_vertex == hover_id) return; // Disallow self-loops if (HasEdge(edge_vertex, hover_id)) return; // Disallow multiple edges between same vertices if (GetVertexByID(hover_id).is_edge_protected) return; AddEdge(edge_vertex, hover_id); edge_vertex = -1; // Reset edge_vertex } else // User pressed e with no second vertex { edge_vertex = -1; // Cancel } } bool Graph::HasVertexWithID(int id) const { for (const Vertex* v : vertices) if (v->id == id) return true; return false; } Vertex& Graph::GetVertexByID(int id) { for (Vertex* v : vertices) if (v->id == id) return *v; std::stringstream s; s << "Failed to find vertex with id: " << id; utils::errors::Die(s.str()); return *vertices[-1]; // So the compiler doesn't worry... } const Vertex& Graph::GetVertexByIDConst(int id) const { for (const Vertex* v : vertices) if (v->id == id) return *v; std::stringstream s; s << "Failed to find vertex with id: " << id; utils::errors::Die(s.str()); return *vertices[-1]; // So the compiler doesn't worry... } Edge& Graph::GetEdgeByEndpoints(int from, int to) { for (Edge* e : edges) if (e->from.id == from && e->to.id == to) return *e; std::stringstream s; s << "Failed to find edge with end-points: " << from << ", " << to; utils::errors::Die(s.str()); return *edges[-1]; // So the compiler doesn't worry... } const Edge& Graph::GetEdgeByEndpointsConst(int from, int to) const { for (const Edge* e : edges) if (e->HasEndpoints(from, to)) return *e; std::stringstream s; s << "Failed to find edge with end-points: " << from << ", " << to; utils::errors::Die(s.str()); return *edges[-1]; // So the compiler doesn't worry... } bool Graph::HasEdgeWithID(int id) const { for (const Edge* e : edges) if (e->id == id) return true; return false; } Edge& Graph::GetEdgeByID(int id) { for (Edge* e : edges) if (e->id == id) return *e; std::stringstream s; s << "Failed to find edge with id: " << id; utils::errors::Die(s.str()); return *edges[-1]; } const Edge& Graph::GetEdgeByIDConst(int id) const { for (const Edge* e : edges) if (e->id == id) return *e; std::stringstream s; s << "Failed to find edge with id: " << id; utils::errors::Die(s.str()); return *edges[-1]; } int Graph::GetHoveringVertex() const { for (const Vertex* v : vertices) if (v->IsHovering()) return v->id; return -1; } int Graph::GetHoveringEdge() const { for (const Edge* e : edges) if (e->IsHovering()) return e->id; return -1; } int Graph::AddVertex(Vertex* v) { vertices.push_back(v); std::function f = [this, v] () { RemoveVertex(v->id); }; v->SetDeleteCallback(f); DFS(); return v->id; } int Graph::AddVertex(Vertex& v) { return AddVertex(&v); } int Graph::AddVertex(int x, int y) { Vertex* v = new Vertex(window, Level::colors[0], x, y, viewport_position); return AddVertex(v); } int Graph::AddEdge(Edge* e) { edges.push_back(e); e->SetDeleteCallback([this, e] () { RemoveEdge(e->from.id, e->to.id); }); DFS(); return e->id; } int Graph::AddEdge(Edge& e) { return AddEdge(&e); } int Graph::AddEdge(int id1, int id2) { if (!HasVertexWithID(id1)|| !HasVertexWithID(id2)) utils::errors::Die("Trying to create edge with non-existent vertex."); Vertex& v1 = GetVertexByID(id1); Vertex& v2 = GetVertexByID(id2); Edge* e = new Edge(window, v1, v2, Level::colors[0], viewport_position, directed); return AddEdge(e); } bool Graph::HasEdge(int id1, int id2) const { for (const Edge* e : edges) if (e->HasEndpoints(id1, id2)) return true; return false; } void Graph::RemoveVertex(int id) { for (int i = 0; i < E(); i++) { if (edges[i]->from.id == id || edges[i]->to.id == id) { delete edges[i]; edges.erase(edges.begin() + i); i--; } } for (int i = 0; i < V(); i++) { if (vertices[i]->id == id) { delete vertices[i]; vertices.erase(vertices.begin()+i); break; } } DFS(); } void Graph::RemoveEdge(int id1, int id2) { for (int i = 0; i < E(); i++) { if (edges[i]->HasEndpoints(id1, id2)) { delete edges[i]; edges.erase(edges.begin() + i); break; } } DFS(); } int Graph::Degree(int id) const { int degree = 0; for (int i = 0; i < E(); i++) { if (edges[i]->from.id == id || edges[i]->to.id == id) degree++; } return degree; } void Graph::DFS() { std::stack vertices; for (int v : origins) vertices.push(v); connected.clear(); while (!vertices.empty()) { int v = vertices.top(); vertices.pop(); if (connected.count(v)) continue; connected.insert(v); for (Edge* e : edges) { if (e->from.id == v && !connected.count(e->to.id)) vertices.push(e->to.id); if (e->to.id == v && !connected.count(e->from.id)) vertices.push(e->from.id); } } } bool Graph::IsConnected(int id) const { return connected.count(id) > 0; } bool Graph::IsConnected() const { for (const Vertex* v : vertices) if (!IsConnected(v->id)) return false; return true; } void Graph::Render(const std::set& edges_in_path, const std::set& vertices_in_path, int last_vertex) { // Display counter window->SetTextSize(COUNTER_TEXT_SIZE); window->SetDrawColor(0xCCCCCCFF); std::stringstream counter_text; counter_text << "Vertices: " << V() << ", Edges: " << E(); window->DrawText(counter_text.str(), 10, COUNTER_TEXT_SIZE+10); if (edge_vertex != -1) // Draw line from edge vertex to mouse. { if (!HasVertexWithID(edge_vertex)) { edge_vertex = -1; } else { Vertex& v = GetVertexByID(edge_vertex); window->SetDrawColor(0x888888FF); window->DrawLine(v.RenderX(), v.RenderY(), window->GetMouseX(), window->GetMouseY()); } } for (Vertex* v : vertices) v->Render(Degree(v->id), IsConnected(v->id) > 0, vertices_in_path.count(v->id) > 0, last_vertex == v->id); for (Edge* e : edges) e->Render(edges_in_path.count(e->id) > 0); for (Vertex* v : vertices) v->RenderColorMenu(); for (Edge* e : edges) e->RenderColorMenu(); } void Graph::Render() { std::set edges_in_path; std::set vertices_in_path; Render(edges_in_path, vertices_in_path, -1); } } // namespace graphcoloring