class NonogramGUI(QtGui.QFrame): """ Implement QFrame, which is a subclass of QWidget """ status_message = pyqtSignal(str) def __init__(self, parent): QtGui.QFrame.__init__(self, parent) self.dx = self.dy = 1 self.widget_width_px = self.widget_height_px = 600 self.nonogram_state = None self.set_nonogram(True) self.delay = 50 self.mode = C.search_mode.A_STAR self.thread = SearchWorker(self) self.init_ui() def init_ui(self): """ Initialize the UI """ self.setFocusPolicy(QtCore.Qt.StrongFocus) size = QtCore.QSize(self.widget_width_px, self.widget_height_px) self.setMinimumSize(size) self.parent().adjustSize() def level_loaded(self, nonogram): """ Called whenever a level is loaded, adjust Widget size """ self.nonogram_state = NonogramState(nonogram, None) self.compute_tile_size() def compute_tile_size(self): """ Computes tile size based on widget size and nonogram """ self.dx = self.dy = 1 x, y = self.nonogram_state.state.x, self.nonogram_state.state.y self.dx *= (self.widget_width_px / float(x)) self.dy *= (self.widget_height_px / float(y)) def start_search(self): """ Start the search in the worker thread """ self.status_message.emit(str('Search started')) search = NonogramBfs(self.nonogram_state.state, self) self.thread.search(search) def set_solution(self, solution): """ Receives a solution from search and sets the node """ self.nonogram_state = NonogramState( solution.state, None, solution.domains, solution.solution_length ) def paint(self, state): """ Receives a node and tells Qt to update the graphics """ self.nonogram_state = state self.update() def paintEvent(self, _): # pylint: disable=invalid-name """ Called by the Qt event loop when the widget should be updated """ painter = QtGui.QPainter(self) self.draw_nonogram(painter) def resizeEvent(self, e): # pylint: disable=invalid-name """ Handles widget resize and scales Nonogram """ self.widget_width_px = e.size().width() self.widget_height_px = e.size().height() self.compute_tile_size() def draw_nonogram(self, painter): """ Draws a Nonogram, WHITE should be treated as an error. """ colors = { C.colors.GREY: QtGui.QColor(130, 130, 130), # Unset C.colors.PINK: QtGui.QColor(255, 255, 150), # Drawn C.colors.YELLOW: QtGui.QColor(255, 0, 150), # Space C.colors.WHITE: QtGui.QColor(255, 255, 255) # No domains } for y, row in enumerate(self.nonogram_state.representation()): for x, element in enumerate(row): painter.setBrush(colors[int(element)]) painter.drawRect(x*self.dx, y*self.dy, self.dx - 1, self.dy - 1) def set_nonogram(self, default=False): """ Load level with a QFileDialog """ folder = res.nonograms.__path__[0] if default: path = folder + '/nono-heart-1.txt' else: path = QtGui.QFileDialog.getOpenFileName( self.window(), "Nonogram Selector", folder, "Text files (*.txt)" ) if not path: return nonogram_file = open(path, 'r') contents = [line.strip() for line in nonogram_file.read().splitlines()] self.level_loaded(Nonogram(contents)) filename = path.split('/')[-1] title = 'Module 3 - Nonogram - {}'.format(filename) self.parent().setWindowTitle(title) self.status_message.emit(str('Loaded: {}'.format(filename))) self.update() def set_delay(self, delay): """ Change delay """ self.delay = delay self.status_message.emit('Delay: ' + str(delay))
class NavigationGUI(QtGui.QFrame): # pylint: disable=too-many-instance-attributes """ Implement QFrame, which is a subclass of QWidget """ status_message = pyqtSignal(str) def __init__(self, parent): QtGui.QFrame.__init__(self, parent) self.dx = self.dy = 0 self.widget_width_px = self.widget_height_px = 600 self.delay = 50 self.diagonal = False self.mode = C.search_mode.A_STAR self.heuristics_type = 'euclidean' self.node = None self.opened = None self.closed = None self.set_map(True) self.thread = SearchWorker(self) self.init_ui() def init_ui(self): """ Initialize the UI """ self.setFocusPolicy(QtCore.Qt.StrongFocus) minimum_size = QtCore.QSize(self.widget_width_px, self.widget_height_px) self.setMinimumSize(minimum_size) self.parent().adjustSize() def level_loaded(self, map_): """ Called whenever a level is loaded, adjust Widget size """ self.node = NavigationState(map_) self.opened, self.closed = None, None self.compute_tile_size() def compute_tile_size(self): """ Computes tile size based on widget size and map """ self.dx = self.widget_width_px / float(self.node.state.x_dim()) self.dy = self.widget_height_px / float(self.node.state.y_dim()) def start_search(self): """ Start the search in the worker thread """ navigation = NavigationBfs(self.node.state, self) self.status_message.emit(str('Search started')) self.thread.search(navigation) def set_solution(self, solution): """ Receives a solution from search and sets the node """ self.node = NavigationState( self.node.state, solution.visited, solution.current_pos ) def set_opened_closed(self, opened, closed): """ Sets the opened and closed lists of states """ self.opened = opened self.closed = closed def paint(self, node): """ Receives a node and tells Qt to update the graphics """ self.node = node self.update() def paintEvent(self, _): # pylint: disable=invalid-name """ Called by the Qt event loop when the widget should be updated """ if self.node is None: return painter = QtGui.QPainter(self) self.paint_map(painter) def resizeEvent(self, e): # pylint: disable=invalid-name """ Handles widget resize and scales Navigation """ self.widget_width_px = e.size().width() self.widget_height_px = e.size().height() self.compute_tile_size() def is_visited(self, x, y): """ Checks whether (x,y) is visited in the current state """ y_dim = self.node.state.y_dim() pos = [x, y_dim - y - 1] return pos in self.node.visited def is_on_frontier(self, x, y): """ Checks whether (x,y) is in the opened list """ if not self.opened: return False y_dim = self.node.state.y_dim() for node in self.opened: if isinstance(node, tuple): node = node[1] # Heapq for astar, normal list for BFS/DFS if [x, y_dim - y - 1] == node.visited[-1]: return True return False def is_closed(self, x, y): """ Checks whether (x,y) is in the closed list """ if not self.closed: return False y_dim = self.node.state.y_dim() for node in self.closed: if [x, y_dim - y - 1] == node.visited[-1]: return True return False def get_color(self, x, y, visited=False, frontier=False, closed=False): # pylint: disable=too-many-arguments """ Return a QColor based on the tile and whether it is visited """ if visited: color = { C.nav_tile.TILE: QtGui.QColor(80, 80, 80), C.nav_tile.START: QtGui.QColor(153, 204, 255), C.nav_tile.GOAL: QtGui.QColor(0, 255, 0) }[self.node.state.grid[y][x]] elif frontier: color = QtGui.QColor(255, 255, 80) elif closed: color = QtGui.QColor(210, 210, 210) else: color = { C.nav_tile.TILE: QtGui.QColor(255, 255, 255), C.nav_tile.OBSTACLE: QtGui.QColor(255, 0, 0), C.nav_tile.START: QtGui.QColor(0, 0, 255), C.nav_tile.GOAL: QtGui.QColor(51, 102, 0) }[self.node.state.grid[y][x]] return color def draw(self, x, y, painter): """ Draws rectangles, either with a black border or without a border. If the tile is visited we draw a smaller rectangle on top. """ color = self.get_color(x, y) dx, dy = self.dx, self.dy if self.node.state.grid[y][x] is C.nav_tile.OBSTACLE: painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1)) else: painter.setPen(QtGui.QPen(color, 1)) painter.setBrush(color) painter.drawRect((x * dx), (y * dy), dx - 1, dy - 1) is_visited = self.is_visited(x, y) is_on_frontier = self.is_on_frontier(x, y) is_closed = self.is_closed(x, y) if is_visited or is_on_frontier or is_closed: color = self.get_color(x, y, is_visited, is_on_frontier, is_closed) painter.setPen(QtGui.QPen(color, 1)) painter.setBrush(color) painter.drawRect((x * dx) + 4, (y * dy) + 4, dx - 9, dy - 9) def paint_map(self, painter): """ Called by the paintEvent, we iterate over the map and draw tiles """ for y in range(self.node.state.y_dim()): for x in range(self.node.state.x_dim()): self.draw(x, y, painter) def set_map(self, default=False): """ Load level with a QFileDialog """ folder = res.maps.__path__[0] if default: path = folder + '/ex0.txt' else: path = QtGui.QFileDialog.getOpenFileName( self.window(), "Open Map File", folder, "Text files (*.txt)" ) if not path: return map_file = open(path, 'r') contents = [line.strip() for line in map_file.read().splitlines()] self.level_loaded(Map(contents)) filename = path.split('/')[-1] self.parent().setWindowTitle( 'Module 1 - Navigation - {}'.format(filename) ) self.status_message.emit(str('Loaded: {}'.format(filename))) self.update() def set_mode(self, mode): """ Chainable method for use in lambdas, change mode """ self.mode = mode return True def set_delay(self, delay): """ Change delay """ self.delay = delay self.status_message.emit('Delay: ' + str(delay)) def set_diagonal(self, is_diagonal): """ Set diagonal mode """ self.diagonal = is_diagonal self.status_message.emit('Diagonal mode: ' + str(is_diagonal)) def set_heuristics_type(self, heuristics_type): """ Set heuristics """ self.heuristics_type = heuristics_type message = 'Heuristics: ' + str(heuristics_type) + ' distance' self.status_message.emit(message)
class GraphGUI(QtGui.QFrame): # pylint: disable=too-many-instance-attributes """ Implement QFrame, which is a subclass of QWidget """ status_message = pyqtSignal(str) def __init__(self, parent): QtGui.QFrame.__init__(self, parent) self.dx = self.dy = 1 self.widget_width_px = self.widget_height_px = 600 # Adjustments for negative coordinates in graphs. self.nc_adjust_x = self.nc_adjust_y = 0 self.total_height = 0 self.vertex_radii = 5 self.num_colors = 4 self.delay = 50 self.vertex_numbering = False self.node = None self.set_graph(True) self.mode = C.search_mode.A_STAR self.thread = SearchWorker(self) self.init_ui() def init_ui(self): """ Initialize the UI """ self.setFocusPolicy(QtCore.Qt.StrongFocus) size = QtCore.QSize(self.widget_width_px, self.widget_height_px) self.setMinimumSize(size) self.parent().adjustSize() def level_loaded(self, graph): """ Called whenever a level is loaded, adjust Widget size """ self.node = VertexColoringState(graph, None, self.num_colors) self.compute_tile_size() def compute_tile_size(self): """ Computes tile size based on widget size and graph """ self.nc_adjust_x = self.nc_adjust_y = 0 width_orig = self.node.state.width height_orig = self.node.state.height self.dx = self.dy = 1 self.dx = self.widget_width_px / float(width_orig) self.dy = self.widget_height_px / float(height_orig) self.nc_adjust_x = self.node.state.min_x * self.dx self.nc_adjust_y = self.node.state.min_y * self.dy def start_search(self): """ Start the search in the worker thread """ self.status_message.emit(str('Search started')) self.thread.search(VertexColoringBfs(self.node.state, self)) def set_solution(self, solution): """ Receives a solution from search and sets the node """ self.node = VertexColoringState( solution.state, None, self.num_colors, solution.domains, solution.solution_length ) def paint(self, node): """ Receives a node and tells Qt to update the graphics """ self.node = node self.update() def paintEvent(self, _): # pylint: disable=invalid-name """ Called by the Qt event loop when the widget should be updated """ if self.node is None: return painter = QtGui.QPainter(self) self.paint_graph(painter) def resizeEvent(self, e): # pylint: disable=invalid-name """ Handles widget resize and scales Graph """ self.widget_width_px = e.size().width() self.widget_height_px = e.size().height() self.compute_tile_size() def draw_vertex(self, vertex, painter, whites=False): """ Draws a vertex, either all the white nodes (with a domain length larger than 1) or all the colored nodes. Also draws optional vertex numbers. """ x = (vertex[1] * self.dx) - self.nc_adjust_x y = (vertex[2] * self.dy) - self.nc_adjust_y y = self.widget_height_px - y point = QtCore.QPoint(x, y) colors = { C.graph_colors.RED: QtGui.QColor(255, 128, 0), C.graph_colors.GREEN: QtGui.QColor(0, 200, 0), C.graph_colors.BLUE: QtGui.QColor(0, 0, 255), C.graph_colors.ORANGE: QtGui.QColor(175, 0, 100), C.graph_colors.PINK: QtGui.QColor(255, 20, 147), C.graph_colors.YELLOW: QtGui.QColor(255, 255, 0), C.graph_colors.PURPLE: QtGui.QColor(238, 130, 238), C.graph_colors.BROWN: QtGui.QColor(222, 184, 135), C.graph_colors.CYAN: QtGui.QColor(0, 255, 255), C.graph_colors.DARK_BROWN: QtGui.QColor(120, 60, 0), C.graph_colors.WHITE: QtGui.QColor(255, 255, 255), C.graph_colors.BLACK: QtGui.QColor(0, 0, 0), } vertex_color = self.node.vertex_color(vertex[0]) color = colors[vertex_color] painter.setPen(color) painter.setBrush(color) if whites and vertex_color is C.graph_colors.WHITE: painter.drawEllipse(point, self.vertex_radii, self.vertex_radii) elif not whites and vertex_color is not C.graph_colors.WHITE: painter.drawEllipse(point, self.vertex_radii, self.vertex_radii) if whites and self.vertex_numbering: painter.setPen(colors[C.graph_colors.BLACK]) painter.drawText(x, y, str(vertex[0])) def draw_edge(self, edge, painter): """ Draws edge between two vertices """ v1 = self.node.state.vertices[edge[0]] v2 = self.node.state.vertices[edge[1]] x1 = (v1[1] * self.dx) - self.nc_adjust_x y1 = (v1[2] * self.dy) - self.nc_adjust_y x2 = (v2[1] * self.dx) - self.nc_adjust_x y2 = (v2[2] * self.dy) - self.nc_adjust_y y1, y2 = self.widget_height_px - y1, self.widget_height_px - y2 p1, p2 = QtCore.QPoint(x1, y1), QtCore.QPoint(x2, y2) painter.setPen(QtGui.QColor(120, 120, 120)) painter.drawLine(p1, p2) def paint_graph(self, painter): """ The graph is painted by edges, unset and lastly colored vertices """ for edge in self.node.state.edges: self.draw_edge(edge, painter) # Draw white vertices first for vertex in self.node.state.vertices: self.draw_vertex(vertex, painter, True) for vertex in reversed(self.node.state.vertices): self.draw_vertex(vertex, painter) def set_graph(self, default=False): """ Load level with a QFileDialog """ folder = res.graphs.__path__[0] if default: path = folder + '/graph-color-1.txt' else: path = QtGui.QFileDialog.getOpenFileName( self.window(), "Open graph file", folder, "Text files (*.txt)" ) if not path: return graph_file = open(path, 'r') contents = [line.strip() for line in graph_file.read().splitlines()] self.level_loaded(Graph(contents)) filename = path.split('/')[-1] self.parent().setWindowTitle('Module 2 - A*-GAC - {}'.format(filename)) self.status_message.emit(str('Loaded: {}'.format(filename))) self.update() def set_color(self, colors): """ Change color and notify user """ self.num_colors = colors self.status_message.emit('Amount of colors available: ' + str(colors)) def set_vertex_numbering(self, numbering): """ Boolean numbering decides if we show vertex numbers. """ self.vertex_numbering = numbering if numbering: self.status_message.emit('Showing vertex numbers') else: self.status_message.emit('Hiding vertex numbers') self.update() def set_delay(self, delay): """ Change delay """ self.delay = delay self.status_message.emit('Delay: ' + str(delay))