class GraphicsWindow(ActionListener, KeyListener, MouseInputListener): """ Creates a :py:class:`~jygsaw.graphicswindow.GraphicsWindow` with a :py:class:`~jygsaw.graphicswindow.Canvas` object that can be drawn on. Takes a title, window width, and window height. An optional background color can be specified. """ colors = [BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, ORANGE, PINK, RED, WHITE, YELLOW] def __init__(self, title, w, h, bg_color=WHITE): assert w > 0, "GraphicsWindow width must be greater than zero" assert h > 0, "GraphicsWindow height must be greater than zero" self.objs = [] # List of GraphicsObjects self.bg_color = bg_color self.frame = JFrame( title, defaultCloseOperation=JFrame.EXIT_ON_CLOSE, size=(w, h)) self.frame.setLocationRelativeTo(None) self.frame.contentPane = Canvas(self, self.objs, self.bg_color) self.frame.contentPane.setPreferredSize(Dimension(w, h)) self.frame.addMouseListener(self) self.frame.addMouseMotionListener(self) self.frame.addKeyListener(self) # MouseEvent attributes self.mouse_x = 0 self.mouse_y = 0 self.mouse_buttons = Set() # Mouse callbacks self.on_mouse_clicked = None self.on_mouse_dragged = None self.on_mouse_moved = None self.on_mouse_pressed = None self.on_mouse_released = None self.on_mouse_exited = None self.on_mouse_entered = None # Key callbacks self.on_key_pressed = None self.on_key_released = None self.on_key_typed = None # Key values self.last_key_char = None self.last_key_code = None self.chars_pressed = Set() self.codes_pressed = Set() # Event queue self.event_queue = Queue() self.main_running = False # not needed, user_draw is called directly from on_draw self.on_draw = None def set_visible(self, visible): """Sets the window to visible.""" assert isinstance(visible, bool), "Variable is not a boolean." self.frame.pack() self.frame.visible = visible def draw(self, *params): """ Takes any number of :py:class:`~jygsaw.graphicsobject.GraphicsObject` or :py:class:`~jygsaw.shape.Shape` objects, or :py:class:`~jygsaw.group.Group`, and draws them on the Canvas. If a shape is drawn without specifying a color the default color is used. The default stroke option (:py:class:`True` or :py:class:`False`) and *stroke_color* is saved in each object. """ for arg in params: if isinstance(arg, GraphicsObject) or isinstance(arg, Shape): if arg.color is None: arg.color = self.frame.contentPane.default_color arg.stroke_color = self.frame.contentPane.stroke_color arg.stroke = self.frame.contentPane.stroke arg.filled = self.frame.contentPane.filled arg.stroke_width = self.frame.contentPane.stroke_width if isinstance(arg, Text): arg.font = self.frame.contentPane.font arg.size = self.frame.contentPane.text_size self.objs.append(arg) elif isinstance(arg, Group): for obj in arg.group: if obj.color is None: obj.color = self.frame.contentPane.default_color obj.stroke_color = self.frame.contentPane.stroke_color obj.stroke = self.frame.contentPane.stroke obj.filled = self.frame.contentPane.filled arg.stroke_width = self.frame.contentPane.stroke_width if isinstance(arg, Text): arg.font = self.frame.contentPane.font arg.size = self.frame.contentPane.text_size self.objs.append(obj) else: raise Exception("Passed in something that's not a Group or GraphicsObject.") def set_default_color(self, c): """Sets the default color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.default_color = c def set_stroke_color(self, c): """Sets the stroke color in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.stroke_color = c def set_stroke(self, b): """Turns stroke on or off in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(b, bool), "Variable given is not a boolean." self.frame.contentPane.stroke = b def set_stroke_width(self, w): """Sets the width of the stroke in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(w, int), "Variable given is not an integer." self.frame.contentPane.stroke_width = w def set_filled(self, f): """Turns fill on or off in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(f, bool), "Variable given is not a boolean." self.frame.contentPane.filled = f def set_bg_color(self, c): """Sets the background color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.background_color = c self.background = c def set_font(self, f): """Sets the font of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" self.frame.contentPane.font = f def set_text_size(self, s): """Sets the text size of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert s >= 0 and isinstance( s, int), "Font size must be greater than or equal to 0" self.frame.contentPane.text_size = s def get_bg_color(self): """Returns the background color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" return self.background def redraw(self, delay=0.0): """ Redraws the :py:class:`~jygsaw.graphicswindow.Canvas`. Only returns when done. An optional float can also be used to sleep after redrawing. """ # Use non-blocking redraw because there is no one-to-one relation # between calling cavas.repaint() and execution of paintComponent() # if SwingUtilities.isEventDispatchThread(): self.frame.contentPane.repaint() else: self.frame.contentPane.blocking_redraw() sleep(delay) def clear(self): """ Clears the screen so that only the background is visible. Also deletes all :py:class:`~jygsaw.graphicsobject.GraphicsObject` and :py:class:`~jygsaw.shape.Shape` and objects. """ self.objs = [] self.frame.contentPane.objs = self.objs def mouseEntered(self, e): """ Runs when the mouse enters the window. Sets the mouse coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_entered` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y if self.main_running: if self.on_mouse_entered: self.on_mouse_entered() else: self.event_queue.put(e) if debug: print (self.mouse_x, self.mouse_y) def mouseClicked(self, e): """ Runs when the mouse is clicked. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_clicked` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y if self.main_running: if self.on_mouse_clicked: self.on_mouse_clicked() else: self.event_queue.put(e) def mouseExited(self, e): """ Runs when the mouse exits the window. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_exited` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y if self.main_running: if self.on_mouse_exited: self.on_mouse_exited() else: self.event_queue.put(e) def mousePressed(self, e): """ Runs when the mouse button is pressed. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_pressed` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y self.mouse_buttons.add(e.getButton()) if self.main_running: if self.on_mouse_pressed: self.on_mouse_pressed() else: self.event_queue.put(e) def mouseReleased(self, e): """ Runs when the mouse button is released. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_released` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y self.mouse_buttons.remove(e.getButton()) if self.main_running: if self.on_mouse_released: self.on_mouse_released() else: self.event_queue.put(e) def mouseMoved(self, e): """ Runs when the mouse is moved. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouse_moved` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y if self.main_running: if self.on_mouse_moved: self.on_mouse_moved() else: self.event_queue.put(e) if debug: print '(%d, %d)' % (self.mouse_x, self.mouse_y) def mouseDragged(self, e): """ Runs when the mouse is dragged. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseDragged` function, if any. """ pos = self.frame.contentPane.getMousePosition() if pos: self.mouse_x = pos.x self.mouse_y = pos.y if self.main_running: if self.on_mouse_dragged: self.on_mouse_dragged() else: self.event_queue.put(e) def keyTyped(self, e): """ Sets the last key character and code when a key is typed. Calls a user-provided :py:meth:`keyTyped` function, if any. """ if debug: print e.getKeyCode() if self.main_running: if self.on_key_typed: self.on_key_typed() else: self.event_queue.put(e) def keyPressed(self, e): """ Sets the last key character and code when a key is pressed. Calls a user-provided :py:meth:`key_pressed` function, if any. """ self.last_key_code = e.getKeyCode() self.last_key_char = e.getKeyChar() self.chars_pressed.add(e.getKeyText(e.getKeyCode()).upper()) self.codes_pressed.add(e.getKeyCode()) if debug: print "Key pressed:" print e.getKeyText(e.getKeyCode()) print e.getKeyCode() if self.main_running: if self.on_key_pressed: self.on_key_pressed() else: self.event_queue.put(e) def keyReleased(self, e): """ Sets the last key character and code when a key is released. Calls a user-provided :py:meth:`key_released` function, if any. """ self.chars_pressed.remove(e.getKeyText(e.getKeyCode()).upper()) self.codes_pressed.remove(e.getKeyCode()) if debug: print "Key released:" print e.getKeyText(e.getKeyCode()) print e.getKeyCode() if self.main_running: if self.on_key_released: self.on_key_released() else: self.event_queue.put(e) def _is_key_pressed(self): return bool(self.chars_pressed) isKeyPressed = property(_is_key_pressed, ':py:class:`True` if any keys are pressed, else :py:class:`False`.') def _get_width(self): """Get the width of the canvas.""" return self.frame.contentPane.width width = property(_get_width, 'Width of Canvas in pixels.') def _get_height(self): """Get the height of the canvas.""" return self.frame.contentPane.height height = property(_get_height, 'Height of Canvas in pixels.')
class GraphicsWindow(ActionListener, KeyListener, MouseInputListener): """ Creates a :py:class:`~jygsaw.graphicswindow.GraphicsWindow` with a :py:class:`~jygsaw.graphicswindow.Canvas` object that can be drawn on. Takes a title, window width, and window height. An optional background color can be specified. """ def __init__(self, title, w, h, backgroundColor=white): assert w > 0, "GraphicsWindow width must be greater than zero" assert h > 0, "GraphicsWindow height must be greater than zero" self._SHAPELIST_MAX_LENGTH = 200 self.objs = [] # List of GraphicsObjects self.backgroundColor = backgroundColor self.frame = JFrame( title, defaultCloseOperation=JFrame.EXIT_ON_CLOSE, size=(w, h)) self.frame.setLocationRelativeTo(None) self.frame.contentPane = Canvas(self, self.objs, self.backgroundColor) self.frame.contentPane.setPreferredSize(Dimension(w, h)) self.frame.addMouseListener(self) self.frame.addMouseMotionListener(self) self.frame.addKeyListener(self) # MouseEvent attributes self.mouseEventType = 0 self.mouseX = 0 self.mouseY = 0 # Mouse callbacks self.onMouseClicked = None self.onMouseDragged = None self.onMouseMoved = None self.onMousePressed = None self.onMouseReleased = None self.onMouseDragged = None self.onMouseExited = None self.onMouseEntered = None # KeyEvent booleans keyPressed, keyTyped self.keyEventType = 0 self.keyP = False self.keyT = False # Key callbacks self.onKeyPressed = None self.onKeyReleased = None self.onKeyTyped = None # Key values self.lastKeyChar = None self.lastKeyCode = None self.charsPressed = Set() # Event queue self.eventQueue = Queue() self.mainRunning = False # not needed, user_draw is /called directly from onDraw self.onDraw = None def setVisible(self, isVisible): """Sets the window to visible.""" assert isinstance(isVisible, bool), "Variable is not a boolean." self.frame.pack() self.frame.visible = isVisible def draw(self, *params): """ Takes any number of :py:class:`~jygsaw.graphicsobject.GraphicsObject` or :py:class:`~jygsaw.shape.Shape` objects, or :py:class:`~jygsaw.group.Group`, and draws them on the Canvas. If a shape is drawn without specifying a color the default color is used. The default stroke option (:py:class:`True` or :py:class:`False`) and *strokeColor* is saved in each object. """ for arg in params: if isinstance(arg, GraphicsObject) or isinstance(arg, Shape): if arg.color is None: arg.color = self.frame.contentPane.defaultColor arg.strokeColor = self.frame.contentPane.strokeColor arg.stroke = self.frame.contentPane.stroke arg.filled = self.frame.contentPane.filled arg.strokeWidth = self.frame.contentPane.strokeWidth if isinstance(arg, Text): arg.font = self.frame.contentPane.font arg.size = self.frame.contentPane.textSize self.objs.append(arg) if len(self.objs) > self._SHAPELIST_MAX_LENGTH: warn("You have more than " + str(self._SHAPELIST_MAX_LENGTH) + " to be drawn. You may want to add a clearHalf() call to your" + " code to avoid slowing your system.") elif isinstance(arg, Group): for obj in arg.group: if obj.color is None: obj.color = self.frame.contentPane.defaultColor obj.strokeColor = self.frame.contentPane.strokeColor obj.stroke = self.frame.contentPane.stroke obj.filled = self.frame.contentPane.filled arg.strokeWidth = self.frame.contentPane.strokeWidth if isinstance(arg, Text): arg.font = self.frame.contentPane.font arg.size = self.frame.contentPane.textSize self.objs.append(obj) if len(self.objs) > self._SHAPELIST_MAX_LENGTH: warn("You have more than " + str(self._SHAPELIST_MAX_LENGTH) + " to be drawn. You may want to add a clearHalf() call to your" + " code to avoid slowing your system.") else: raise Exception("Passed in something that's not a Group or GraphicsObject.") def setDefaultColor(self, c): """Sets the default color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.defaultColor = c def setStrokeColor(self, c): """Sets the stroke color in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.strokeColor = c def setStroke(self, b): """Turns stroke on or off in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(b, bool), "Variable given is not a boolean." self.frame.contentPane.stroke = b def setStrokeWidth(self, w): """Sets the width of the stroke in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(w, int), "Variable given is not an integer." self.frame.contentPane.strokeWidth = w def setFilled(self, f): """Turns fill on or off in the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(f, bool), "Variable given is not a boolean." self.frame.contentPane.filled = f def setBackgroundColor(self, c): """Sets the background color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert isinstance(c, Color), "The object passed is not a Color object." self.frame.contentPane.backgroundColor = c self.background = c def setFont(self, f): """Sets the font of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" self.frame.contentPane.font = f def setTextSize(self, s): """Sets the text size of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" assert s >= 0 and isinstance( s, int), "Font size must be greater than or equal to 0" self.frame.contentPane.textSize = s def getBackgroundColor(self): """Returns the background color of the :py:class:`~jygsaw.graphicswindow.Canvas`.""" return self.background def redraw(self, delay=0.0): """ Redraws the :py:class:`~jygsaw.graphicswindow.Canvas`. Only returns when done. An optional float can also be used to sleep after redrawing. """ # Use non-blocking redraw because there is no one-to-one relation # between calling cavas.repaint() and execution of paintComponent() # if SwingUtilities.isEventDispatchThread(): self.frame.contentPane.repaint() else: self.frame.contentPane.blocking_redraw() sleep(delay) def clear(self): """ Clears the screen so that only the background is visible. Also deletes all :py:class:`~jygsaw.graphicsobject.GraphicsObject` and :py:class:`~jygsaw.shape.Shape` and objects. """ self.objs = [] self.frame.contentPane.objs = self.objs def clearHalfIfFull(self): """Clears the window of half of its shapes if the shape list has more shapes than the window's limit. """ if len(self.objs) >= self._SHAPELIST_MAX_LENGTH: self.objs = self.objs[len(self.objs)/2:] self.frame.contentPane.objs = self.objs def mouseEntered(self, e): """ Runs when the mouse enters the window. Sets the mouse coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseEntered` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMouseEntered: self.onMouseEntered() else: self.eventQueue.put(e) if debug: print (self.mouseX, self.mouseY) def mouseClicked(self, e): """ Runs when the mouse is clicked. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseClicked` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMouseClicked: self.onMouseClicked() else: self.eventQueue.put(e) def mouseExited(self, e): """ Runs when the mouse exits the window. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseExited` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMouseExited: self.onMouseExited() else: self.eventQueue.put(e) def mousePressed(self, e): """ Runs when the mouse button is pressed. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mousePressed` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMousePressed: self.onMousePressed() else: self.eventQueue.put(e) def mouseReleased(self, e): """ Runs when the mouse button is released. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseReleased` function, if any. """ self.mouseEventType = e.getID() if self.mainRunning: if self.onMouseReleased: self.onMouseReleased() else: self.eventQueue.put(e) def mouseMoved(self, e): """ Runs when the mouse is moved. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseMoved` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMouseMoved: self.onMouseMoved() else: self.eventQueue.put(e) if debug: print '(%d, %d)' % (self.mouseX, self.mouseY) def mouseDragged(self, e): """ Runs when the mouse is dragged. Sets the mouse X and Y coordinates to the current mouse position. Calls a user-provided :py:meth:`mouseDragged` function, if any. """ self.mouseEventType = e.getID() self.mouseX = e.getX() self.mouseY = e.getY() - 25 if self.mainRunning: if self.onMouseDragged: self.onMouseDragged() else: self.eventQueue.put(e) def keyTyped(self, e): """ Sets the last key character and code when a key is typed. Calls a user-provided :py:meth:`keyTyped` function, if any. """ self.keyEventType = e.getID() if debug: print e.getKeyCode() if self.mainRunning: if self.onKeyTyped: self.onKeyTyped() else: self.eventQueue.put(e) def keyPressed(self, e): """ Sets the last key character and code when a key is pressed. Calls a user-provided :py:meth:`keyPressed` function, if any. """ self.keyEventType = e.getID() self.lastKeyCode = e.getKeyCode() self.lastKeyChar = e.getKeyChar() self.charsPressed.add(e.getKeyText(e.getKeyCode()).upper()) if debug: print "Key pressed:" print e.getKeyText(e.getKeyCode()) print e.getKeyCode() if self.mainRunning: if self.onKeyPressed: self.onKeyPressed() else: self.eventQueue.put(e) def keyReleased(self, e): """ Sets the last key character and code when a key is released. Calls a user-provided :py:meth:`keyReleased` function, if any. """ self.keyEventType = e.getID() self.charsPressed.remove(e.getKeyText(e.getKeyCode()).upper()) if debug: print "Key released:" print e.getKeyText(e.getKeyCode()) print e.getKeyCode() if self.mainRunning: if self.onKeyReleased: self.onKeyReleased() else: self.eventQueue.put(e) def _is_key_pressed(self): return bool(self.charsPressed) isKeyPressed = property(_is_key_pressed, ':py:class:`True` if any keys are pressed, else :py:class:`False`.') def _get_width(self): """Get the width of the canvas.""" return self.frame.contentPane.width width = property(_get_width, 'Width of Canvas in pixels.') def _get_height(self): """Get the height of the canvas.""" return self.frame.contentPane.height height = property(_get_height, 'Height of Canvas in pixels.')