| Trees | Indices | Help |
|---|
|
|
1 ################################################################################ 2 # Authors: Brian Schott (Sir Alaran) 3 # Copyright: Brian Schott (Sir Alaran) 4 # Date: Sep 29 2009 5 # License: 6 # 7 # This program is free software: you can redistribute it and/or modify 8 # it under the terms of the GNU General Public License as published by 9 # the Free Software Foundation, either version 3 of the License, or 10 # (at your option) any later version. 11 # 12 # This program is distributed in the hope that it will be useful, 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # GNU General Public License for more details. 16 # 17 # You should have received a copy of the GNU General Public License 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 19 ################################################################################ 20 21 22 23 """ 24 Code for the GUI tools that allow the user to edit the map. This is used by the 25 mapgrid module. 26 """ 27 28 29 import gtk 30 import cairo 31 32 import graphics 33 import mapcontroller 34 import shapes 35 import dialogs 36 import undo 37 38 # Constants for use in the UI code. 39 TILE_DRAW_ID = 0 40 TILE_DELETE_ID = 1 41 PHYSICS_SELECT_ID = 2 42 CIRCLE_DRAW_ID = 3 43 POLYGON_DRAW_ID = 4 44 LIGHT_DRAW_ID = 5 45 # Add new codes here for new tools 46 4749 """ 50 Return the ix and iy snapped to the nearest division between tiles 51 This is used for physics drawing 52 @type ix: int 53 @param ix: x-coordinate of mouse 54 @type iy: int 55 @param iy: y-coordinate of mouse 56 @type ts: int 57 @param ts: the size (in pixels) of a tile 58 """ 59 x = round(ix / ts) * ts 60 y = round(iy / ts) * ts 61 return x, y62 6365 """ 66 Translates mouse coordinates to tile coordinates 67 @type x: int 68 @param x: x-coordinate of the mouse 69 @type y: int 70 @param y: y-cooridnate of the mouse 71 @type ts: int 72 @param ts: size of a tile in pixels 73 """ 74 return x // ts, y // ts75 7678 """ 79 Returns the coordinates of a rectangle whose edges are snapped to the 80 divisions between tiles. The returned value is in pixel units in the form 81 (x, y, w, h) 82 @type x1: int 83 @param x1: left x-coordinate in tiles 84 @type y1: int 85 @param y1: top y-coordinate in tiles 86 @type x2: int 87 @param x2: right x-coordinate in tiles 88 @type y2: int 89 @param y2: bottom y-coordinate in tiles 90 @type ts: int 91 @param ts: size of tile in pixels 92 """ 93 rx = min(x1, x2) * ts 94 ry = min(y1, y2) * ts 95 rw = (abs(x2 - x1) + 1) * ts 96 rh = (abs(y2 - y1) + 1) * ts 97 return int(rx), int(ry), int(rw), int(rh)98 99101 """ 102 Base class for tools used in the map editor. Derive from this class to add 103 more functionality to the program 104 """232 233106 self.__x = 0 107 self.__y = 0 108 self.__instructions = None 109 self.__id = -1 110 self.__hasPointer = False 111 mapcontroller.MapListener.__init__(self, controller)112114 """ 115 @rtype: bool 116 @return: True if the mouse is over the TileGrid 117 """ 118 return self.__hasPointer119121 """ 122 @type text: string 123 @param text: the instructions for using this tool 124 """ 125 self.__instructions = text126128 """ 129 @rtype: string 130 @return: the instructions for using this tool. 131 """ 132 return self.__instructions133 140142 """ 143 Draws the tool to the given cairo context 144 @type context: cairo.Context 145 @param context: the drawing context 146 """ 147 pass148 155 162164 """ 165 @rtype: bool 166 @return: True if this event causes a redraw to be necessary. 167 """ 168 # This line about setting the __hasPointer to True should not be 169 # necessary. I don't know why gtk gives a leave notification when the 170 # mouse button is pressed or released. It makes no sense. 171 self.__hasPointer = True 172 self.__x = x 173 self.__y = y 174 return False175177 """ 178 @type button: int 179 @param button: button that was pressed 180 @type time: int 181 @param time: the time that the button press happened. Necessary for 182 popping up menus 183 @rtype: bool 184 @return: True if this event causes a redraw to be necessary. 185 """ 186 self.__hasPointer = True 187 return False188190 """ 191 @type button: int 192 @param button: button that was pressed 193 @param time: the time that the button press happened. Necessary for 194 popping up menus 195 @rtype: bool 196 @return: True if this event causes a redraw to be necessary. 197 """ 198 self.__hasPointer = True 199 return False200202 """ 203 Called when the mouse enters the area over the TileGrid 204 @rtype: bool 205 @return: True if the TileGrid should be redrawn 206 """ 207 self.__hasPointer = True 208 return True209211 """ 212 Called when the mouse leaves the area over the TileGrid 213 @rtype: bool 214 @return: True if the TileGrid should be redrawn 215 """ 216 self.__hasPointer = False 217 return True218220 """ 221 TODO: figure out and document the type of key 222 @rtype: bool 223 @return: True if this event causes a redraw to be necessary. 224 """ 225 return False226 229292236 EditorTool.__init__(self, controller) 237 self.setInstructions("Error: This is an abstract class") 238 self.selectX1 = -1 239 self.selectY1 = -1 240 self.selectX2 = -1 241 self.selectY2 = -1 242 self.lastX = -1 243 self.lastY = -1 244 self.buttonDown = False245247 if button != 1: 248 return False 249 self.selectX1, self.selectY1 = coordsToTiles(self.x(), self.y(), 250 self.getController().mapTileSize()) 251 self.selectY2 = self.selectY1 252 self.selectX2 = self.selectX1 253 self.lastX = self.selectX1 254 self.lastY = self.selectY1 255 self.buttonDown = True 256 return True257259 if button != 1: 260 return False 261 x1 = int(min(self.selectX1, self.selectX2)) 262 y1 = int(min(self.selectY1, self.selectY2)) 263 x2 = int(max(self.selectX1, self.selectX2)) 264 y2 = int(max(self.selectY1, self.selectY2)) 265 self.selectX1 = x1 266 self.selectY1 = y1 267 self.selectX2 = x2 268 self.selectY2 = y2 269 self.buttonDown = False 270 return True271273 """ See TileGrid.mouseMotion """ 274 275 def updateLast(a, b): 276 if a == self.lastX and b == self.lastY: 277 return False 278 else: 279 self.lastX = a 280 self.lastY = b 281 return True282 283 EditorTool.mouseMotion(self, x, y) 284 if self.buttonDown: 285 self.selectX2, self.selectY2 = coordsToTiles(self.x(), self.y(), 286 self.getController().mapTileSize()) 287 return updateLast(self.selectX2, self.selectY2) 288 else: 289 tmpx, tmpy = coordsToTiles(self.x(), self.y(), 290 self.getController().mapTileSize()) 291 return updateLast(tmpx, tmpy)334 335295 TileTool.__init__(self, controller) 296 self.setInstructions("Click and drag to select tiles to delete")297 301303 TileTool.mouseButtonRelease(self, button, time) 304 controller = self.getController() 305 z = controller.selectedLayer() 306 307 action = undo.TileRemoveAction(controller) 308 309 for x in range(self.selectX1, self.selectX2 + 1): 310 for y in range(self.selectY1, self.selectY2 + 1): 311 r = self.getController().removeTile(x, y, z) 312 action.appendTileRemove((x, y, z), r) 313 314 controller.addUndoAction(action)315 316318 if self.buttonDown == False: 319 return 320 321 ts = self.getController().mapTileSize() 322 x, y, w, h = rectangleSelect(self.selectX1, self.selectY1, 323 self.selectX2, self.selectY2, ts) 324 pattern = graphics.getDeletePattern(ts) 325 context.rectangle(x, y, w, h) 326 context.set_source(pattern) 327 pattern.set_extend(cairo.EXTEND_REPEAT) 328 matrix = cairo.Matrix() 329 matrix.translate(-x, -y) 330 pattern.set_matrix(matrix) 331 context.set_source(pattern) 332 context.rectangle(x, y, w, h) 333 context.fill()377 378338 TileTool.__init__(self, controller) 339 self.setInstructions("Left-click to select tiles. Click and drag to" 340 + " select multiple tiles.") 341 self.__index = index342 346348 TileTool.mouseButtonRelease(self, button, time) 349 if button != 1: 350 return False 351 else: 352 self.getController().setSelection(self.__index, self.selectX1, 353 self.selectY1, self.selectX2, self.selectY2) 354 return False355357 """ 358 Draws a rectangle around the selected tiles 359 """ 360 x, y, w, h = rectangleSelect(self.selectX1, self.selectY1, 361 self.selectX2, self.selectY2, self.getController().mapTileSize()) 362 363 # semi-transparent square 364 fc = graphics.getHighlightColor() 365 hc = graphics.getBackgroundColor() 366 context.set_source_rgba(fc.r, fc.g, fc.b, 0.5) 367 context.rectangle(x, y, w, h) 368 context.fill() 369 context.rectangle(x + 0.5, y + 0.5, w, h) 370 context.set_source_rgba(fc.r, fc.g, fc.b, 1.0) 371 context.set_line_width(3.0) 372 context.stroke() 373 context.rectangle(x + 0.5, y + 0.5, w, h) 374 context.set_source_rgba(hc.r, hc.g, hc.b, 1.0) 375 context.set_line_width(1.0) 376 context.stroke()503 504 514 515381 TileTool.__init__(self, controller) 382 self.setInstructions("Left-click to draw tiles on the map." 383 + " Right-click to delete them.") 384 385 # Index of the source image 386 self.__selectionIndex = None 387 # surface for drawing 388 self.__brush = None389391 if self.__selectionIndex == None: 392 return False 393 """ See TileGrid.mouseMotion """ 394 return TileTool.mouseMotion(self, x, y)395397 """ See TileGrid.buttonPress """ 398 if self.__selectionIndex == None or button != 1: 399 return False 400 TileTool.mouseButtonPress(self, button, time) 401 return True402404 """ See TileGrid.buttonRelease """ 405 if self.__selectionIndex == None or button != 1: 406 return False 407 TileTool.mouseButtonRelease(self, button, time) 408 self.__setTiles(self.selectX1, self.selectX2, self.selectY1, 409 self.selectY2) 410 return True411413 """ 414 Sets tiles on the map in the range defined by startX, endX, startY 415 and endY 416 """ 417 if self.__selectionIndex == None: 418 return 419 420 421 422 # Ensure that start < end 423 sX = int(min(startX, endX)) 424 eX = int(max(startX, endX)) 425 sY = int(min(startY, endY)) 426 eY = int(max(startY, endY)) 427 428 controller = self.getController() 429 z = controller.selectedLayer() 430 action = undo.TileAddAction(controller) 431 432 if startX == endX and startY == endY: 433 # Do not repeat the pattern. Overflow of the range is allowed if 434 # the starts and ends are the same 435 tempY = sY 436 tempX = sX 437 for i in range(self.selectionX1, self.selectionX2 + 1): 438 for j in range(self.selectionY1, self.selectionY2 + 1): 439 r = controller.addTile(tempX, tempY, z, i, j, 440 self.__selectionIndex) 441 action.appendTileAdd((tempX, tempY, z), 442 (i, j, self.__selectionIndex), r) 443 tempY += 1 444 tempY = sY 445 tempX += 1 446 else: 447 # Repeat the pattern but do not allow it to overflow the specified 448 # area 449 sdx = abs(self.selectionX2 - self.selectionX1) 450 sdy = abs(self.selectionY2 - self.selectionY1) 451 ddx = eX - sX 452 ddy = eY - sY 453 for i in range(ddx + 1): 454 for j in range(ddy + 1): 455 ix = (i % (sdx + 1)) + self.selectionX1 456 iy = (j % (sdy + 1)) + self.selectionY1 457 x = i + sX 458 y = j + sY 459 r = controller.addTile(x, y, z, ix, iy, 460 self.__selectionIndex) 461 action.appendTileAdd((x, y, z), (ix, iy, 462 self.__selectionIndex), r) 463 464 controller.addUndoAction(action)465467 if self.__brush == None or self.getPointer() == False: 468 return 469 ts = self.getController().mapTileSize() 470 if self.buttonDown: 471 x, y, w, h = rectangleSelect(self.selectX1, self.selectY1, 472 self.selectX2, self.selectY2, ts) 473 pattern = cairo.SurfacePattern(self.__brush) 474 pattern.set_extend(cairo.EXTEND_REPEAT) 475 matrix = cairo.Matrix() 476 matrix.translate(-x, -y) 477 pattern.set_matrix(matrix) 478 context.set_source(pattern) 479 context.rectangle(x, y, w, h) 480 context.fill() 481 else: 482 x = self.lastX * ts 483 y = self.lastY * ts 484 context.set_source_surface(self.__brush, x, y) 485 w = (abs(self.selectionX2 - self.selectionX1) + 1) * ts 486 h = (abs(self.selectionY2 - self.selectionY1) + 1) * ts 487 context.rectangle(x, y, w, h) 488 context.fill()489491 self.__selectionIndex = index 492 self.__brush = brush 493 self.selectionX1 = int(x1) 494 self.selectionY1 = int(y1) 495 self.selectionX2 = int(x2) 496 self.selectionY2 = int(y2)497549 550 if button == 1: 551 if self.__selectedShape != None: 552 selectHandle() 553 if self.__handleIndex == None: 554 startDrag(s) 555 else: 556 startDrag(s) 557 elif button == 3: 558 self.__selectedShape = s 559 if s != None: 560 self.popupShapeMenu(self.__selectedShape, button, time) 561 return True 562518 ShapeTool.__init__(self, controller) 519 self.__selectedShape = None 520 self.__dragLastX = -1 521 self.__dragLastY = -1 522 # Coordinate for the start of a drag operation. This is either the start 523 # point for dragging an entire shape, or for dragging a single handle. 524 self.__dragStartX = -1 525 self.__dragStartY = -1 526 self.__dragging = False 527 self.__handleIndex = None 528 self.setInstructions("Click and drag to move shapes")529531 s = self.selectShape(self.x(), self.y()) 532 533 def startDrag(shape): 534 self.__selectedShape = shape 535 self.__dragging = True 536 self.__dragLastX, self.__dragLastY = coordsToTileEdges( 537 self.x(), self.y(), 538 self.getController().mapTileSize()) 539 self.__dragStartX = self.__dragLastX 540 self.__dragStartY = self.__dragLastY541 542 def selectHandle(): 543 for i, p in enumerate(self.__selectedShape.getHandles()): 544 if shapes.pointInHandle(shapes.Point(self.x(), self.y()), p): 545 self.__handleIndex = i 546 self.__dragStartX, self.__dragStartY = coordsToTileEdges( 547 self.x(), self.y(), self.getController().mapTileSize()) 548 break564 if button == 1: 565 if (self.__dragging == True and 566 (self.__dragLastX != self.__dragStartX 567 or self.__dragLastY != self.__dragStartY)): 568 action = undo.ShapeMoveAction( 569 self.getController(), 570 self.__selectedShape, 571 self.__dragLastX - self.__dragStartX, 572 self.__dragLastY - self.__dragStartY, 573 ) 574 self.getController().addUndoAction(action) 575 if (self.__handleIndex != None and ( 576 self.__dragLastX != self.__dragStartX 577 or self.__dragLastY != self.__dragStartY)): 578 action = undo.ShapeAdjustAction( 579 self.getController(), 580 self.__selectedShape, 581 self.__handleIndex, 582 self.__dragStartX, 583 self.__dragStartY, 584 self.__dragLastX, 585 self.__dragLastY, 586 ) 587 self.getController().addUndoAction(action) 588 self.__dragging = False 589 self.__handleIndex = None590 591593 ShapeTool.mouseMotion(self, x, y) 594 if self.__selectedShape != None: 595 tmpX, tmpY = coordsToTileEdges(self.x(), self.y(), 596 self.getController().mapTileSize()) 597 if self.__handleIndex != None: 598 self.__selectedShape.adjust(tmpX, tmpY, 599 self.__handleIndex) 600 self.__dragLastX = tmpX 601 self.__dragLastY = tmpY 602 return True 603 elif self.__dragging: 604 dx = self.__dragLastX - tmpX 605 dy = self.__dragLastY - tmpY 606 if dx != 0 or dy != 0: 607 ts = self.getController().mapTileSize() 608 self.__selectedShape.shift(-dx, -dy) 609 self.__dragLastX = tmpX 610 self.__dragLastY = tmpY 611 self.getController().notifyModification(True) 612 return True 613 return False614616 ShapeTool.draw(self, context) 617 if self.__selectedShape != None: 618 if self.__selectedShape.friction == None: 619 # The friction being None (a completely bogus value) means that 620 # the shape was deleted. Because the shape was deleted in a 621 # callback function, this code to set __selectedShape to None 622 # can't be in the button event handler 623 self.__selectedShape = None 624 return 625 for p in self.__selectedShape.getHandles(): 626 graphics.drawPointHandle(context, p) 627 graphics.drawMoveHandle(context, self.__selectedShape.getCenter())628630 """ 631 @type shape: shapes.Shape 632 @param shape: the shape that this is a context menu for 633 @type button: int 634 @param button: the button pressed 635 @type time: int? 636 @param time: the time that this event was triggered 637 """ 638 menu = gtk.Menu() 639 deleteItem = gtk.ImageMenuItem(gtk.STOCK_DELETE) 640 deleteItem.connect("activate", self.deleteCB, shape) 641 propertiesItem = gtk.ImageMenuItem(gtk.STOCK_PROPERTIES) 642 propertiesItem.connect("activate", self.propertiesCB, shape) 643 menu.attach(deleteItem, 0, 1, 0, 1) 644 menu.attach(propertiesItem, 0, 1, 1, 2) 645 menu.show_all() 646 menu.popup(None, None, None, button, time)647649 """ 650 callback for the delete item in popupShapeMenu 651 @type menuitem: gtk.MenuItem 652 @param menuitem: ignored 653 @type shape: shapes.Shape 654 @param shape: the shape to delete 655 """ 656 self.__selectedShape = None 657 action = undo.ShapeDeleteAction(self.getController(), shape) 658 self.getController.addUndoAction(action) 659 self.getController().removeShape(shape)660662 """ 663 callback for the properties item in popupShapeMenu 664 @type menuitem: gtk.MenuItem 665 @param menuitem: ignored 666 @type shape: shapes.Shape 667 @param shape: the shape whose properties should be changed 668 """ 669 d = dialogs.StaticShapePropertiesDialog( 670 self.getController().getToplevel(), shape) 671 result = d.run() 672 if result == gtk.RESPONSE_ACCEPT: 673 shape.friction = d.getFriction() 674 shape.damage = d.getDamage() 675 shape.restitution = d.getRestitution() 676 self.getController().notifyModification(True) 677 d.destroy()678680 """ 681 @type x: int 682 @param x: x-coordinate to test 683 @type y: int 684 @param y: y-coordinate to test 685 @rtype: shapes.Shape 686 @return: the shape selected, or None 687 """ 688 shapeList = self.getController().getShapes() 689 for shape in shapeList: 690 if shape.intersects(shapes.Point(x, y)): 691 return shape 692 return None693 697 698 699742702 ShapeTool.__init__(self, controller) 703 self.__drawing = False 704 self.__circle = shapes.Circle(0, 705 int(self.getController().mapTileSize() / 2)) 706 # A good default value, since the radius can't be negative 707 self.__oldRadius = -1 708 self.setInstructions("Click to set the center point. Click" 709 + " again to set the radius")710712 if self.__drawing: 713 controller = self.getController() 714 action = undo.ShapeAddAction(controller, self.__circle) 715 controller.addUndoAction(action) 716 controller.addShape(self.__circle) 717 self.__circle = shapes.Circle(0.0, 718 int(controller.mapTileSize() / 2)) 719 self.__drawing = False 720 else: 721 if button == 1: 722 self.__circle.setCenter(shapes.Point(self.x(), self.y())) 723 self.__drawing = True724 727729 ShapeTool.mouseMotion(self, x, y) 730 if self.__drawing == True: 731 self.__circle.adjust(self.x(), self.y()) 732 if self.__circle.getRadius() == self.__oldRadius: 733 return False 734 else: 735 self.__oldRadius = self.__circle.getRadius() 736 return True737809 810 823745 ShapeTool.__init__(self, controller) 746 # True if a shape is in the middle of being drawn 747 self.__drawing = False 748 self.__polygon = shapes.Polygon() 749 # These keep the number of redraws down 750 self.__lastX = 0 751 self.__lastY = 0 752 self.setInstructions("Left click to add points, right click to finish.")753755 controller = self.getController() 756 if button == 1: 757 p = coordsToTileEdges(self.x(), self.y(), 758 controller.mapTileSize()) 759 if self.__drawing == True: 760 last = self.__polygon.getLastPoint() 761 if last != None and p[0] == last.x and p[1] == last.y \ 762 and self.__polygon.convex(): 763 self.__polygon.delExtraPoint() 764 self.__polygon.forceCClockwise() 765 action = undo.ShapeAddAction(controller, self.__polygon) 766 controller.addUndoAction(action) 767 controller.addShape(self.__polygon) 768 self.__polygon = shapes.Polygon() 769 self.__drawing = False 770 else: 771 self.__polygon.addPoint(shapes.Point(p[0], p[1])) 772 else: 773 self.__polygon.addPoint(shapes.Point(p[0], p[1])) 774 self.__drawing = True 775 elif button == 3: 776 if self.__drawing: 777 if self.__polygon.convex(): 778 self.__drawing = False 779 self.__polygon.forceCClockwise() 780 action = undo.ShapeAddAction(controller, self.__polygon) 781 controller.addUndoAction(action) 782 controller.addShape(self.__polygon) 783 self.__polygon = shapes.Polygon() 784 return True785 788790 ShapeTool.mouseMotion(self, x, y) 791 p = coordsToTileEdges(self.x(), self.y(), 792 self.getController().mapTileSize()) 793 self.__polygon.adjust(p[0], p[1]) 794 # Only force a redraw if the snapped mouse coordinates have actually 795 # changed. 796 if p[0] == self.__lastX and p[1] == self.__lastY: 797 self.__lastX = p[0] 798 self.__lastY = p[1] 799 return False 800 else: 801 self.__lastX = p[0] 802 self.__lastY = p[1] 803 return True804
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Tue Oct 6 15:18:47 2009 | http://epydoc.sourceforge.net |