]> git.saurik.com Git - wxWidgets.git/blame - wxPython/samples/pySketch/pySketch.py
Implemented Enable for wxMenuBar to avoid assert
[wxWidgets.git] / wxPython / samples / pySketch / pySketch.py
CommitLineData
431f4c16
RD
1""" pySketch
2
3 A simple object-oriented drawing program.
4
5 This is completely free software; please feel free to adapt or use this in
6 any way you like.
7
8 Author: Erik Westra (ewestra@wave.co.nz)
9
10 #########################################################################
11
12 NOTE
13
14 pySketch requires wxPython version 2.3. If you are running an earlier
15 version, you need to patch your copy of wxPython to fix a bug which will
16 cause the "Edit Text Object" dialog box to crash.
17
18 To patch an earlier version of wxPython, edit the wxPython/windows.py file,
19 find the wxPyValidator.__init__ method and change the line which reads:
20
21 self._setSelf(self, wxPyValidator, 0)
22
23 to:
24
25 self._setSelf(self, wxPyValidator, 1)
26
27 This fixes a known bug in wxPython 2.2.5 (and possibly earlier) which has
28 now been fixed in wxPython 2.3.
29
30 #########################################################################
31
32 TODO:
33
34 * Add ARGV checking to see if a document was double-clicked on.
35
36 Known Bugs:
37
38 * Scrolling the window causes the drawing panel to be mucked up until you
1e4a197e 39 refresh it. I've got no idea why.
431f4c16
RD
40
41 * I suspect that the reference counting for some wxPoint objects is
42 getting mucked up; when the user quits, we get errors about being
1e4a197e 43 unable to call del on a 'None' object.
431f4c16 44"""
de51258f 45import sys
1e4a197e 46import cPickle, os.path
431f4c16
RD
47from wxPython.wx import *
48
49import traceback, types
50
51#----------------------------------------------------------------------------
52# System Constants
53#----------------------------------------------------------------------------
54
55# Our menu item IDs:
56
57menu_UNDO = 10001 # Edit menu items.
58menu_SELECT_ALL = 10002
59menu_DUPLICATE = 10003
60menu_EDIT_TEXT = 10004
61menu_DELETE = 10005
62
63menu_SELECT = 10101 # Tools menu items.
64menu_LINE = 10102
65menu_RECT = 10103
66menu_ELLIPSE = 10104
67menu_TEXT = 10105
68
69menu_MOVE_FORWARD = 10201 # Object menu items.
70menu_MOVE_TO_FRONT = 10202
71menu_MOVE_BACKWARD = 10203
72menu_MOVE_TO_BACK = 10204
73
74menu_ABOUT = 10205 # Help menu items.
75
76# Our tool IDs:
77
78id_SELECT = 11001
79id_LINE = 11002
80id_RECT = 11003
81id_ELLIPSE = 11004
82id_TEXT = 11005
83
84# Our tool option IDs:
85
86id_FILL_OPT = 12001
87id_PEN_OPT = 12002
88id_LINE_OPT = 12003
89
90id_LINESIZE_0 = 13001
91id_LINESIZE_1 = 13002
92id_LINESIZE_2 = 13003
93id_LINESIZE_3 = 13004
94id_LINESIZE_4 = 13005
95id_LINESIZE_5 = 13006
96
97# DrawObject type IDs:
98
99obj_LINE = 1
100obj_RECT = 2
101obj_ELLIPSE = 3
102obj_TEXT = 4
103
104# Selection handle IDs:
105
106handle_NONE = 1
107handle_TOP_LEFT = 2
108handle_TOP_RIGHT = 3
109handle_BOTTOM_LEFT = 4
110handle_BOTTOM_RIGHT = 5
111handle_START_POINT = 6
112handle_END_POINT = 7
113
114# Dragging operations:
115
116drag_NONE = 1
117drag_RESIZE = 2
118drag_MOVE = 3
119drag_DRAG = 4
120
121# Visual Feedback types:
122
123feedback_LINE = 1
124feedback_RECT = 2
125feedback_ELLIPSE = 3
126
127# Mouse-event action parameter types:
128
129param_RECT = 1
130param_LINE = 2
131
132# Size of the drawing page, in pixels.
133
134PAGE_WIDTH = 1000
135PAGE_HEIGHT = 1000
136
137#----------------------------------------------------------------------------
138
139class DrawingFrame(wxFrame):
140 """ A frame showing the contents of a single document. """
141
142 # ==========================================
143 # == Initialisation and Window Management ==
144 # ==========================================
145
146 def __init__(self, parent, id, title, fileName=None):
1e4a197e 147 """ Standard constructor.
431f4c16 148
1e4a197e
RD
149 'parent', 'id' and 'title' are all passed to the standard wxFrame
150 constructor. 'fileName' is the name and path of a saved file to
151 load into this frame, if any.
152 """
431f4c16 153 wxFrame.__init__(self, parent, id, title,
1e4a197e
RD
154 style = wxDEFAULT_FRAME_STYLE | wxWANTS_CHARS |
155 wxNO_FULL_REPAINT_ON_RESIZE)
156
157 # Setup our menu bar.
158
159 menuBar = wxMenuBar()
160
161 self.fileMenu = wxMenu()
162 self.fileMenu.Append(wxID_NEW, "New\tCTRL-N")
163 self.fileMenu.Append(wxID_OPEN, "Open...\tCTRL-O")
164 self.fileMenu.Append(wxID_CLOSE, "Close\tCTRL-W")
165 self.fileMenu.AppendSeparator()
166 self.fileMenu.Append(wxID_SAVE, "Save\tCTRL-S")
167 self.fileMenu.Append(wxID_SAVEAS, "Save As...")
168 self.fileMenu.Append(wxID_REVERT, "Revert...")
169 self.fileMenu.AppendSeparator()
170 self.fileMenu.Append(wxID_EXIT, "Quit\tCTRL-Q")
171
172 menuBar.Append(self.fileMenu, "File")
173
174 self.editMenu = wxMenu()
175 self.editMenu.Append(menu_UNDO, "Undo\tCTRL-Z")
176 self.editMenu.AppendSeparator()
177 self.editMenu.Append(menu_SELECT_ALL, "Select All\tCTRL-A")
178 self.editMenu.AppendSeparator()
179 self.editMenu.Append(menu_DUPLICATE, "Duplicate\tCTRL-D")
180 self.editMenu.Append(menu_EDIT_TEXT, "Edit...\tCTRL-E")
181 self.editMenu.Append(menu_DELETE, "Delete\tDEL")
182
183 menuBar.Append(self.editMenu, "Edit")
184
185 self.toolsMenu = wxMenu()
186 self.toolsMenu.Append(menu_SELECT, "Selection", kind=wxITEM_CHECK)
187 self.toolsMenu.Append(menu_LINE, "Line", kind=wxITEM_CHECK)
188 self.toolsMenu.Append(menu_RECT, "Rectangle", kind=wxITEM_CHECK)
189 self.toolsMenu.Append(menu_ELLIPSE, "Ellipse", kind=wxITEM_CHECK)
190 self.toolsMenu.Append(menu_TEXT, "Text", kind=wxITEM_CHECK)
191
192 menuBar.Append(self.toolsMenu, "Tools")
193
194 self.objectMenu = wxMenu()
195 self.objectMenu.Append(menu_MOVE_FORWARD, "Move Forward")
196 self.objectMenu.Append(menu_MOVE_TO_FRONT, "Move to Front\tCTRL-F")
197 self.objectMenu.Append(menu_MOVE_BACKWARD, "Move Backward")
198 self.objectMenu.Append(menu_MOVE_TO_BACK, "Move to Back\tCTRL-B")
199
200 menuBar.Append(self.objectMenu, "Object")
201
202 self.helpMenu = wxMenu()
203 self.helpMenu.Append(menu_ABOUT, "About pySketch...")
204
205 menuBar.Append(self.helpMenu, "Help")
206
207 self.SetMenuBar(menuBar)
208
209 # Create our toolbar.
210
211 self.toolbar = self.CreateToolBar(wxTB_HORIZONTAL |
212 wxNO_BORDER | wxTB_FLAT)
213
214 self.toolbar.AddSimpleTool(wxID_NEW,
215 wxBitmap("images/new.bmp",
216 wxBITMAP_TYPE_BMP),
217 "New")
218 self.toolbar.AddSimpleTool(wxID_OPEN,
219 wxBitmap("images/open.bmp",
220 wxBITMAP_TYPE_BMP),
221 "Open")
222 self.toolbar.AddSimpleTool(wxID_SAVE,
223 wxBitmap("images/save.bmp",
224 wxBITMAP_TYPE_BMP),
225 "Save")
226 self.toolbar.AddSeparator()
227 self.toolbar.AddSimpleTool(menu_UNDO,
228 wxBitmap("images/undo.bmp",
229 wxBITMAP_TYPE_BMP),
230 "Undo")
231 self.toolbar.AddSeparator()
232 self.toolbar.AddSimpleTool(menu_DUPLICATE,
233 wxBitmap("images/duplicate.bmp",
234 wxBITMAP_TYPE_BMP),
235 "Duplicate")
236 self.toolbar.AddSeparator()
237 self.toolbar.AddSimpleTool(menu_MOVE_FORWARD,
238 wxBitmap("images/moveForward.bmp",
239 wxBITMAP_TYPE_BMP),
240 "Move Forward")
241 self.toolbar.AddSimpleTool(menu_MOVE_BACKWARD,
242 wxBitmap("images/moveBack.bmp",
243 wxBITMAP_TYPE_BMP),
244 "Move Backward")
245
246 self.toolbar.Realize()
247
248 # Associate each menu/toolbar item with the method that handles that
249 # item.
250
251 EVT_MENU(self, wxID_NEW, self.doNew)
252 EVT_MENU(self, wxID_OPEN, self.doOpen)
253 EVT_MENU(self, wxID_CLOSE, self.doClose)
254 EVT_MENU(self, wxID_SAVE, self.doSave)
255 EVT_MENU(self, wxID_SAVEAS, self.doSaveAs)
256 EVT_MENU(self, wxID_REVERT, self.doRevert)
257 EVT_MENU(self, wxID_EXIT, self.doExit)
258
259 EVT_MENU(self, menu_UNDO, self.doUndo)
260 EVT_MENU(self, menu_SELECT_ALL, self.doSelectAll)
261 EVT_MENU(self, menu_DUPLICATE, self.doDuplicate)
262 EVT_MENU(self, menu_EDIT_TEXT, self.doEditText)
263 EVT_MENU(self, menu_DELETE, self.doDelete)
264
265 EVT_MENU(self, menu_SELECT, self.doChooseSelectTool)
266 EVT_MENU(self, menu_LINE, self.doChooseLineTool)
267 EVT_MENU(self, menu_RECT, self.doChooseRectTool)
268 EVT_MENU(self, menu_ELLIPSE, self.doChooseEllipseTool)
269 EVT_MENU(self, menu_TEXT, self.doChooseTextTool)
270
271 EVT_MENU(self, menu_MOVE_FORWARD, self.doMoveForward)
272 EVT_MENU(self, menu_MOVE_TO_FRONT, self.doMoveToFront)
273 EVT_MENU(self, menu_MOVE_BACKWARD, self.doMoveBackward)
274 EVT_MENU(self, menu_MOVE_TO_BACK, self.doMoveToBack)
275
276 EVT_MENU(self, menu_ABOUT, self.doShowAbout)
277
278 # Install our own method to handle closing the window. This allows us
279 # to ask the user if he/she wants to save before closing the window, as
280 # well as keeping track of which windows are currently open.
281
282 EVT_CLOSE(self, self.doClose)
283
284 # Install our own method for handling keystrokes. We use this to let
285 # the user move the selected object(s) around using the arrow keys.
286
287 EVT_CHAR_HOOK(self, self.onKeyEvent)
288
289 # Setup our top-most panel. This holds the entire contents of the
290 # window, excluding the menu bar.
291
292 self.topPanel = wxPanel(self, -1, style=wxSIMPLE_BORDER)
293
294 # Setup our tool palette, with all our drawing tools and option icons.
295
296 self.toolPalette = wxBoxSizer(wxVERTICAL)
297
298 self.selectIcon = ToolPaletteIcon(self.topPanel, id_SELECT,
299 "select", "Selection Tool")
300 self.lineIcon = ToolPaletteIcon(self.topPanel, id_LINE,
301 "line", "Line Tool")
302 self.rectIcon = ToolPaletteIcon(self.topPanel, id_RECT,
303 "rect", "Rectangle Tool")
304 self.ellipseIcon = ToolPaletteIcon(self.topPanel, id_ELLIPSE,
305 "ellipse", "Ellipse Tool")
306 self.textIcon = ToolPaletteIcon(self.topPanel, id_TEXT,
307 "text", "Text Tool")
308
309 toolSizer = wxGridSizer(0, 2, 5, 5)
310 toolSizer.Add(self.selectIcon)
de51258f 311 toolSizer.Add((0, 0)) # Gap to make tool icons line up nicely.
1e4a197e
RD
312 toolSizer.Add(self.lineIcon)
313 toolSizer.Add(self.rectIcon)
314 toolSizer.Add(self.ellipseIcon)
315 toolSizer.Add(self.textIcon)
316
317 self.optionIndicator = ToolOptionIndicator(self.topPanel)
318 self.optionIndicator.SetToolTip(
319 wxToolTip("Shows Current Pen/Fill/Line Size Settings"))
320
321 optionSizer = wxBoxSizer(wxHORIZONTAL)
322
323 self.penOptIcon = ToolPaletteIcon(self.topPanel, id_PEN_OPT,
324 "penOpt", "Set Pen Colour")
325 self.fillOptIcon = ToolPaletteIcon(self.topPanel, id_FILL_OPT,
326 "fillOpt", "Set Fill Colour")
327 self.lineOptIcon = ToolPaletteIcon(self.topPanel, id_LINE_OPT,
328 "lineOpt", "Set Line Size")
329
330 margin = wxLEFT | wxRIGHT
331 optionSizer.Add(self.penOptIcon, 0, margin, 1)
332 optionSizer.Add(self.fillOptIcon, 0, margin, 1)
333 optionSizer.Add(self.lineOptIcon, 0, margin, 1)
334
335 margin = wxTOP | wxLEFT | wxRIGHT | wxALIGN_CENTRE
336 self.toolPalette.Add(toolSizer, 0, margin, 5)
de51258f 337 self.toolPalette.Add((0, 0), 0, margin, 5) # Spacer.
1e4a197e
RD
338 self.toolPalette.Add(self.optionIndicator, 0, margin, 5)
339 self.toolPalette.Add(optionSizer, 0, margin, 5)
431f4c16 340
1e4a197e 341 # Make the tool palette icons respond when the user clicks on them.
431f4c16 342
de51258f
RD
343 EVT_BUTTON(self.selectIcon, -1, self.onToolIconClick)
344 EVT_BUTTON(self.lineIcon, -1, self.onToolIconClick)
345 EVT_BUTTON(self.rectIcon, -1, self.onToolIconClick)
346 EVT_BUTTON(self.ellipseIcon, -1, self.onToolIconClick)
347 EVT_BUTTON(self.textIcon, -1, self.onToolIconClick)
348 EVT_BUTTON(self.penOptIcon, -1, self.onPenOptionIconClick)
349 EVT_BUTTON(self.fillOptIcon, -1, self.onFillOptionIconClick)
350 EVT_BUTTON(self.lineOptIcon, -1, self.onLineOptionIconClick)
431f4c16 351
1e4a197e 352 # Setup the main drawing area.
431f4c16 353
1e4a197e
RD
354 self.drawPanel = wxScrolledWindow(self.topPanel, -1,
355 style=wxSUNKEN_BORDER)
356 self.drawPanel.SetBackgroundColour(wxWHITE)
431f4c16 357
1e4a197e
RD
358 self.drawPanel.EnableScrolling(True, True)
359 self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)
431f4c16 360
1e4a197e
RD
361 EVT_LEFT_DOWN(self.drawPanel, self.onMouseEvent)
362 EVT_LEFT_DCLICK(self.drawPanel, self.onDoubleClickEvent)
363 EVT_RIGHT_DOWN(self.drawPanel, self.onRightClick)
364 EVT_MOTION(self.drawPanel, self.onMouseEvent)
365 EVT_LEFT_UP(self.drawPanel, self.onMouseEvent)
366 EVT_PAINT(self.drawPanel, self.onPaintEvent)
431f4c16 367
1e4a197e 368 # Position everything in the window.
431f4c16 369
1e4a197e
RD
370 topSizer = wxBoxSizer(wxHORIZONTAL)
371 topSizer.Add(self.toolPalette, 0)
372 topSizer.Add(self.drawPanel, 1, wxEXPAND)
431f4c16 373
1e4a197e
RD
374 self.topPanel.SetAutoLayout(True)
375 self.topPanel.SetSizer(topSizer)
431f4c16 376
1e4a197e
RD
377 self.SetSizeHints(minW=250, minH=200)
378 self.SetSize(wxSize(600, 400))
431f4c16 379
1e4a197e 380 # Select an initial tool.
431f4c16 381
1e4a197e
RD
382 self.curTool = None
383 self._setCurrentTool(self.selectIcon)
431f4c16 384
1e4a197e 385 # Setup our frame to hold the contents of a sketch document.
431f4c16 386
1e4a197e
RD
387 self.dirty = False
388 self.fileName = fileName
389 self.contents = [] # front-to-back ordered list of DrawingObjects.
390 self.selection = [] # List of selected DrawingObjects.
391 self.undoInfo = None # Saved contents for undo.
392 self.dragMode = drag_NONE # Current mouse-drag mode.
431f4c16 393
1e4a197e
RD
394 if self.fileName != None:
395 self.loadContents()
396
397 self._adjustMenus()
398
399 # Finally, set our initial pen, fill and line options.
400
401 self.penColour = wxBLACK
402 self.fillColour = wxWHITE
403 self.lineSize = 1
431f4c16
RD
404
405 # ============================
406 # == Event Handling Methods ==
407 # ============================
408
409 def onToolIconClick(self, event):
1e4a197e
RD
410 """ Respond to the user clicking on one of our tool icons.
411 """
de51258f
RD
412 iconID = event.GetEventObject().GetId()
413 print iconID
1e4a197e
RD
414 if iconID == id_SELECT: self.doChooseSelectTool()
415 elif iconID == id_LINE: self.doChooseLineTool()
416 elif iconID == id_RECT: self.doChooseRectTool()
417 elif iconID == id_ELLIPSE: self.doChooseEllipseTool()
418 elif iconID == id_TEXT: self.doChooseTextTool()
de51258f 419 else: wxBell(); print "1"
431f4c16
RD
420
421
422 def onPenOptionIconClick(self, event):
1e4a197e
RD
423 """ Respond to the user clicking on the "Pen Options" icon.
424 """
425 data = wxColourData()
426 if len(self.selection) == 1:
427 data.SetColour(self.selection[0].getPenColour())
428 else:
429 data.SetColour(self.penColour)
430
431 dialog = wxColourDialog(self, data)
432 if dialog.ShowModal() == wxID_OK:
433 c = dialog.GetColourData().GetColour()
434 self._setPenColour(wxColour(c.Red(), c.Green(), c.Blue()))
431f4c16
RD
435 dialog.Destroy()
436
437
438 def onFillOptionIconClick(self, event):
1e4a197e
RD
439 """ Respond to the user clicking on the "Fill Options" icon.
440 """
441 data = wxColourData()
442 if len(self.selection) == 1:
443 data.SetColour(self.selection[0].getFillColour())
444 else:
445 data.SetColour(self.fillColour)
446
447 dialog = wxColourDialog(self, data)
448 if dialog.ShowModal() == wxID_OK:
449 c = dialog.GetColourData().GetColour()
450 self._setFillColour(wxColour(c.Red(), c.Green(), c.Blue()))
431f4c16
RD
451 dialog.Destroy()
452
453 def onLineOptionIconClick(self, event):
1e4a197e
RD
454 """ Respond to the user clicking on the "Line Options" icon.
455 """
456 if len(self.selection) == 1:
457 menu = self._buildLineSizePopup(self.selection[0].getLineSize())
458 else:
459 menu = self._buildLineSizePopup(self.lineSize)
460
461 pos = self.lineOptIcon.GetPosition()
462 pos.y = pos.y + self.lineOptIcon.GetSize().height
431f4c16
RD
463 self.PopupMenu(menu, pos)
464 menu.Destroy()
465
466
467 def onKeyEvent(self, event):
1e4a197e
RD
468 """ Respond to a keypress event.
469
470 We make the arrow keys move the selected object(s) by one pixel in
471 the given direction.
472 """
473 if event.GetKeyCode() == WXK_UP:
474 self._moveObject(0, -1)
475 elif event.GetKeyCode() == WXK_DOWN:
476 self._moveObject(0, 1)
477 elif event.GetKeyCode() == WXK_LEFT:
478 self._moveObject(-1, 0)
479 elif event.GetKeyCode() == WXK_RIGHT:
480 self._moveObject(1, 0)
481 else:
482 event.Skip()
431f4c16
RD
483
484
485 def onMouseEvent(self, event):
1e4a197e
RD
486 """ Respond to the user clicking on our main drawing panel.
487
488 How we respond depends on the currently selected tool.
489 """
490 if not (event.LeftDown() or event.Dragging() or event.LeftUp()):
491 return # Ignore mouse movement without click/drag.
492
493 if self.curTool == self.selectIcon:
494 feedbackType = feedback_RECT
495 action = self.selectByRectangle
496 actionParam = param_RECT
497 selecting = True
498 dashedLine = True
499 elif self.curTool == self.lineIcon:
500 feedbackType = feedback_LINE
501 action = self.createLine
502 actionParam = param_LINE
503 selecting = False
504 dashedLine = False
505 elif self.curTool == self.rectIcon:
506 feedbackType = feedback_RECT
507 action = self.createRect
508 actionParam = param_RECT
509 selecting = False
510 dashedLine = False
511 elif self.curTool == self.ellipseIcon:
512 feedbackType = feedback_ELLIPSE
513 action = self.createEllipse
514 actionParam = param_RECT
515 selecting = False
516 dashedLine = False
517 elif self.curTool == self.textIcon:
518 feedbackType = feedback_RECT
519 action = self.createText
520 actionParam = param_RECT
521 selecting = False
522 dashedLine = True
523 else:
de51258f 524 wxBell(); print "2"
1e4a197e
RD
525 return
526
527 if event.LeftDown():
528 mousePt = self._getEventCoordinates(event)
529 if selecting:
530 obj, handle = self._getObjectAndSelectionHandleAt(mousePt)
531
532 if selecting and (obj != None) and (handle != handle_NONE):
533
534 # The user clicked on an object's selection handle. Let the
535 # user resize the clicked-on object.
536
537 self.dragMode = drag_RESIZE
538 self.resizeObject = obj
539
540 if obj.getType() == obj_LINE:
541 self.resizeFeedback = feedback_LINE
542 pos = obj.getPosition()
543 startPt = wxPoint(pos.x + obj.getStartPt().x,
544 pos.y + obj.getStartPt().y)
545 endPt = wxPoint(pos.x + obj.getEndPt().x,
546 pos.y + obj.getEndPt().y)
547 if handle == handle_START_POINT:
548 self.resizeAnchor = endPt
549 self.resizeFloater = startPt
550 else:
551 self.resizeAnchor = startPt
552 self.resizeFloater = endPt
553 else:
554 self.resizeFeedback = feedback_RECT
555 pos = obj.getPosition()
556 size = obj.getSize()
557 topLeft = wxPoint(pos.x, pos.y)
558 topRight = wxPoint(pos.x + size.width, pos.y)
559 botLeft = wxPoint(pos.x, pos.y + size.height)
560 botRight = wxPoint(pos.x + size.width, pos.y + size.height)
561
562 if handle == handle_TOP_LEFT:
563 self.resizeAnchor = botRight
564 self.resizeFloater = topLeft
565 elif handle == handle_TOP_RIGHT:
566 self.resizeAnchor = botLeft
567 self.resizeFloater = topRight
568 elif handle == handle_BOTTOM_LEFT:
569 self.resizeAnchor = topRight
570 self.resizeFloater = botLeft
571 elif handle == handle_BOTTOM_RIGHT:
572 self.resizeAnchor = topLeft
573 self.resizeFloater = botRight
574
575 self.curPt = mousePt
576 self.resizeOffsetX = self.resizeFloater.x - mousePt.x
577 self.resizeOffsetY = self.resizeFloater.y - mousePt.y
578 endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
579 self.curPt.y + self.resizeOffsetY)
580 self._drawVisualFeedback(self.resizeAnchor, endPt,
581 self.resizeFeedback, False)
582
583 elif selecting and (self._getObjectAt(mousePt) != None):
584
585 # The user clicked on an object to select it. If the user
586 # drags, he/she will move the object.
587
588 self.select(self._getObjectAt(mousePt))
589 self.dragMode = drag_MOVE
590 self.moveOrigin = mousePt
591 self.curPt = mousePt
592 self._drawObjectOutline(0, 0)
593
594 else:
595
596 # The user is dragging out a selection rect or new object.
597
598 self.dragOrigin = mousePt
599 self.curPt = mousePt
600 self.drawPanel.SetCursor(wxCROSS_CURSOR)
601 self.drawPanel.CaptureMouse()
602 self._drawVisualFeedback(mousePt, mousePt, feedbackType,
603 dashedLine)
604 self.dragMode = drag_DRAG
605
606 event.Skip()
607 return
608
609 if event.Dragging():
610 if self.dragMode == drag_RESIZE:
611
612 # We're resizing an object.
613
614 mousePt = self._getEventCoordinates(event)
615 if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
616 # Erase previous visual feedback.
617 endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
618 self.curPt.y + self.resizeOffsetY)
619 self._drawVisualFeedback(self.resizeAnchor, endPt,
620 self.resizeFeedback, False)
621 self.curPt = mousePt
622 # Draw new visual feedback.
623 endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
624 self.curPt.y + self.resizeOffsetY)
625 self._drawVisualFeedback(self.resizeAnchor, endPt,
626 self.resizeFeedback, False)
627
628 elif self.dragMode == drag_MOVE:
629
630 # We're moving a selected object.
631
632 mousePt = self._getEventCoordinates(event)
633 if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
634 # Erase previous visual feedback.
635 self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
636 self.curPt.y - self.moveOrigin.y)
637 self.curPt = mousePt
638 # Draw new visual feedback.
639 self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
640 self.curPt.y - self.moveOrigin.y)
641
642 elif self.dragMode == drag_DRAG:
643
644 # We're dragging out a new object or selection rect.
645
646 mousePt = self._getEventCoordinates(event)
647 if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
648 # Erase previous visual feedback.
649 self._drawVisualFeedback(self.dragOrigin, self.curPt,
650 feedbackType, dashedLine)
651 self.curPt = mousePt
652 # Draw new visual feedback.
653 self._drawVisualFeedback(self.dragOrigin, self.curPt,
654 feedbackType, dashedLine)
655
656 event.Skip()
657 return
658
659 if event.LeftUp():
660 if self.dragMode == drag_RESIZE:
661
662 # We're resizing an object.
663
664 mousePt = self._getEventCoordinates(event)
665 # Erase last visual feedback.
666 endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
667 self.curPt.y + self.resizeOffsetY)
668 self._drawVisualFeedback(self.resizeAnchor, endPt,
669 self.resizeFeedback, False)
670
671 resizePt = wxPoint(mousePt.x + self.resizeOffsetX,
672 mousePt.y + self.resizeOffsetY)
673
674 if (self.resizeFloater.x != resizePt.x) or \
675 (self.resizeFloater.y != resizePt.y):
676 self._resizeObject(self.resizeObject,
677 self.resizeAnchor,
678 self.resizeFloater,
679 resizePt)
680 else:
681 self.drawPanel.Refresh() # Clean up after empty resize.
682
683 elif self.dragMode == drag_MOVE:
684
685 # We're moving a selected object.
686
687 mousePt = self._getEventCoordinates(event)
688 # Erase last visual feedback.
689 self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
690 self.curPt.y - self.moveOrigin.y)
691 if (self.moveOrigin.x != mousePt.x) or \
692 (self.moveOrigin.y != mousePt.y):
693 self._moveObject(mousePt.x - self.moveOrigin.x,
694 mousePt.y - self.moveOrigin.y)
695 else:
696 self.drawPanel.Refresh() # Clean up after empty drag.
697
698 elif self.dragMode == drag_DRAG:
699
700 # We're dragging out a new object or selection rect.
701
702 mousePt = self._getEventCoordinates(event)
703 # Erase last visual feedback.
704 self._drawVisualFeedback(self.dragOrigin, self.curPt,
705 feedbackType, dashedLine)
706 self.drawPanel.ReleaseMouse()
707 self.drawPanel.SetCursor(wxSTANDARD_CURSOR)
708 # Perform the appropriate action for the current tool.
709 if actionParam == param_RECT:
710 x1 = min(self.dragOrigin.x, self.curPt.x)
711 y1 = min(self.dragOrigin.y, self.curPt.y)
712 x2 = max(self.dragOrigin.x, self.curPt.x)
713 y2 = max(self.dragOrigin.y, self.curPt.y)
714
715 startX = x1
716 startY = y1
717 width = x2 - x1
718 height = y2 - y1
719
720 if not selecting:
721 if ((x2-x1) < 8) or ((y2-y1) < 8): return # Too small.
722
723 action(x1, y1, x2-x1, y2-y1)
724 elif actionParam == param_LINE:
725 action(self.dragOrigin.x, self.dragOrigin.y,
726 self.curPt.x, self.curPt.y)
727
728 self.dragMode = drag_NONE # We've finished with this mouse event.
729 event.Skip()
431f4c16
RD
730
731
732 def onDoubleClickEvent(self, event):
1e4a197e
RD
733 """ Respond to a double-click within our drawing panel.
734 """
735 mousePt = self._getEventCoordinates(event)
736 obj = self._getObjectAt(mousePt)
737 if obj == None: return
431f4c16 738
1e4a197e 739 # Let the user edit the given object.
431f4c16 740
1e4a197e
RD
741 if obj.getType() == obj_TEXT:
742 editor = EditTextObjectDialog(self, "Edit Text Object")
743 editor.objectToDialog(obj)
744 if editor.ShowModal() == wxID_CANCEL:
745 editor.Destroy()
746 return
431f4c16 747
1e4a197e 748 self._saveUndoInfo()
431f4c16 749
1e4a197e
RD
750 editor.dialogToObject(obj)
751 editor.Destroy()
431f4c16 752
1e4a197e
RD
753 self.dirty = True
754 self.drawPanel.Refresh()
755 self._adjustMenus()
756 else:
de51258f 757 wxBell(); print "3"
431f4c16
RD
758
759
760 def onRightClick(self, event):
1e4a197e
RD
761 """ Respond to the user right-clicking within our drawing panel.
762
763 We select the clicked-on item, if necessary, and display a pop-up
764 menu of available options which can be applied to the selected
765 item(s).
766 """
767 mousePt = self._getEventCoordinates(event)
768 obj = self._getObjectAt(mousePt)
769
770 if obj == None: return # Nothing selected.
771
772 # Select the clicked-on object.
773
774 self.select(obj)
775
776 # Build our pop-up menu.
777
778 menu = wxMenu()
779 menu.Append(menu_DUPLICATE, "Duplicate")
780 menu.Append(menu_EDIT_TEXT, "Edit...")
781 menu.Append(menu_DELETE, "Delete")
782 menu.AppendSeparator()
783 menu.Append(menu_MOVE_FORWARD, "Move Forward")
784 menu.Append(menu_MOVE_TO_FRONT, "Move to Front")
785 menu.Append(menu_MOVE_BACKWARD, "Move Backward")
786 menu.Append(menu_MOVE_TO_BACK, "Move to Back")
787
788 menu.Enable(menu_EDIT_TEXT, obj.getType() == obj_TEXT)
789 menu.Enable(menu_MOVE_FORWARD, obj != self.contents[0])
790 menu.Enable(menu_MOVE_TO_FRONT, obj != self.contents[0])
791 menu.Enable(menu_MOVE_BACKWARD, obj != self.contents[-1])
792 menu.Enable(menu_MOVE_TO_BACK, obj != self.contents[-1])
793
794 EVT_MENU(self, menu_DUPLICATE, self.doDuplicate)
795 EVT_MENU(self, menu_EDIT_TEXT, self.doEditText)
796 EVT_MENU(self, menu_DELETE, self.doDelete)
797 EVT_MENU(self, menu_MOVE_FORWARD, self.doMoveForward)
798 EVT_MENU(self, menu_MOVE_TO_FRONT, self.doMoveToFront)
799 EVT_MENU(self, menu_MOVE_BACKWARD, self.doMoveBackward)
800 EVT_MENU(self, menu_MOVE_TO_BACK, self.doMoveToBack)
801
802 # Show the pop-up menu.
803
804 clickPt = wxPoint(mousePt.x + self.drawPanel.GetPosition().x,
805 mousePt.y + self.drawPanel.GetPosition().y)
806 self.drawPanel.PopupMenu(menu, clickPt)
807 menu.Destroy()
431f4c16
RD
808
809
810 def onPaintEvent(self, event):
1e4a197e
RD
811 """ Respond to a request to redraw the contents of our drawing panel.
812 """
813 dc = wxPaintDC(self.drawPanel)
814 self.drawPanel.PrepareDC(dc)
815 dc.BeginDrawing()
431f4c16 816
1e4a197e
RD
817 for i in range(len(self.contents)-1, -1, -1):
818 obj = self.contents[i]
819 if obj in self.selection:
820 obj.draw(dc, True)
821 else:
822 obj.draw(dc, False)
431f4c16 823
1e4a197e 824 dc.EndDrawing()
431f4c16
RD
825
826 # ==========================
827 # == Menu Command Methods ==
828 # ==========================
829
830 def doNew(self, event):
1e4a197e
RD
831 """ Respond to the "New" menu command.
832 """
833 global _docList
834 newFrame = DrawingFrame(None, -1, "Untitled")
835 newFrame.Show(True)
836 _docList.append(newFrame)
431f4c16
RD
837
838
839 def doOpen(self, event):
1e4a197e
RD
840 """ Respond to the "Open" menu command.
841 """
842 global _docList
843
844 curDir = os.getcwd()
845 fileName = wxFileSelector("Open File", default_extension="psk",
846 flags = wxOPEN | wxFILE_MUST_EXIST)
847 if fileName == "": return
848 fileName = os.path.join(os.getcwd(), fileName)
849 os.chdir(curDir)
850
851 title = os.path.basename(fileName)
852
853 if (self.fileName == None) and (len(self.contents) == 0):
854 # Load contents into current (empty) document.
855 self.fileName = fileName
856 self.SetTitle(os.path.basename(fileName))
857 self.loadContents()
858 else:
859 # Open a new frame for this document.
860 newFrame = DrawingFrame(None, -1, os.path.basename(fileName),
861 fileName=fileName)
862 newFrame.Show(True)
863 _docList.append(newFrame)
431f4c16
RD
864
865
866 def doClose(self, event):
1e4a197e
RD
867 """ Respond to the "Close" menu command.
868 """
869 global _docList
431f4c16 870
1e4a197e
RD
871 if self.dirty:
872 if not self.askIfUserWantsToSave("closing"): return
431f4c16 873
1e4a197e
RD
874 _docList.remove(self)
875 self.Destroy()
431f4c16
RD
876
877
878 def doSave(self, event):
1e4a197e
RD
879 """ Respond to the "Save" menu command.
880 """
881 if self.fileName != None:
882 self.saveContents()
431f4c16
RD
883
884
885 def doSaveAs(self, event):
1e4a197e
RD
886 """ Respond to the "Save As" menu command.
887 """
888 if self.fileName == None:
889 default = ""
890 else:
891 default = self.fileName
892
893 curDir = os.getcwd()
894 fileName = wxFileSelector("Save File As", "Saving",
895 default_filename=default,
896 default_extension="psk",
897 wildcard="*.psk",
898 flags = wxSAVE | wxOVERWRITE_PROMPT)
899 if fileName == "": return # User cancelled.
900 fileName = os.path.join(os.getcwd(), fileName)
901 os.chdir(curDir)
902
903 title = os.path.basename(fileName)
904 self.SetTitle(title)
905
906 self.fileName = fileName
907 self.saveContents()
431f4c16
RD
908
909
910 def doRevert(self, event):
1e4a197e
RD
911 """ Respond to the "Revert" menu command.
912 """
913 if not self.dirty: return
431f4c16 914
1e4a197e
RD
915 if wxMessageBox("Discard changes made to this document?", "Confirm",
916 style = wxOK | wxCANCEL | wxICON_QUESTION,
917 parent=self) == wxCANCEL: return
918 self.loadContents()
431f4c16
RD
919
920
921 def doExit(self, event):
1e4a197e
RD
922 """ Respond to the "Quit" menu command.
923 """
924 global _docList, _app
925 for doc in _docList:
926 if not doc.dirty: continue
927 doc.Raise()
928 if not doc.askIfUserWantsToSave("quitting"): return
929 _docList.remove(doc)
930 doc.Destroy()
431f4c16 931
1e4a197e 932 _app.ExitMainLoop()
431f4c16
RD
933
934
935 def doUndo(self, event):
1e4a197e
RD
936 """ Respond to the "Undo" menu command.
937 """
938 if self.undoInfo == None: return
431f4c16 939
1e4a197e
RD
940 undoData = self.undoInfo
941 self._saveUndoInfo() # For undoing the undo...
431f4c16 942
1e4a197e 943 self.contents = []
431f4c16 944
1e4a197e
RD
945 for type, data in undoData["contents"]:
946 obj = DrawingObject(type)
947 obj.setData(data)
948 self.contents.append(obj)
431f4c16 949
1e4a197e
RD
950 self.selection = []
951 for i in undoData["selection"]:
952 self.selection.append(self.contents[i])
431f4c16 953
1e4a197e
RD
954 self.dirty = True
955 self.drawPanel.Refresh()
956 self._adjustMenus()
431f4c16
RD
957
958
959 def doSelectAll(self, event):
1e4a197e
RD
960 """ Respond to the "Select All" menu command.
961 """
962 self.selectAll()
431f4c16
RD
963
964
965 def doDuplicate(self, event):
1e4a197e
RD
966 """ Respond to the "Duplicate" menu command.
967 """
968 self._saveUndoInfo()
431f4c16 969
1e4a197e
RD
970 objs = []
971 for obj in self.contents:
972 if obj in self.selection:
973 newObj = DrawingObject(obj.getType())
974 newObj.setData(obj.getData())
975 pos = obj.getPosition()
976 newObj.setPosition(wxPoint(pos.x + 10, pos.y + 10))
977 objs.append(newObj)
431f4c16 978
1e4a197e 979 self.contents = objs + self.contents
431f4c16 980
1e4a197e 981 self.selectMany(objs)
431f4c16
RD
982
983
984 def doEditText(self, event):
1e4a197e
RD
985 """ Respond to the "Edit Text" menu command.
986 """
987 if len(self.selection) != 1: return
431f4c16 988
1e4a197e
RD
989 obj = self.selection[0]
990 if obj.getType() != obj_TEXT: return
431f4c16 991
1e4a197e
RD
992 editor = EditTextObjectDialog(self, "Edit Text Object")
993 editor.objectToDialog(obj)
994 if editor.ShowModal() == wxID_CANCEL:
995 editor.Destroy()
996 return
431f4c16 997
1e4a197e 998 self._saveUndoInfo()
431f4c16 999
1e4a197e
RD
1000 editor.dialogToObject(obj)
1001 editor.Destroy()
431f4c16 1002
1e4a197e
RD
1003 self.dirty = True
1004 self.drawPanel.Refresh()
1005 self._adjustMenus()
431f4c16
RD
1006
1007
1008 def doDelete(self, event):
1e4a197e
RD
1009 """ Respond to the "Delete" menu command.
1010 """
1011 self._saveUndoInfo()
431f4c16 1012
1e4a197e
RD
1013 for obj in self.selection:
1014 self.contents.remove(obj)
1015 del obj
1016 self.deselectAll()
431f4c16
RD
1017
1018
1019 def doChooseSelectTool(self, event=None):
1e4a197e
RD
1020 """ Respond to the "Select Tool" menu command.
1021 """
1022 self._setCurrentTool(self.selectIcon)
1023 self.drawPanel.SetCursor(wxSTANDARD_CURSOR)
1024 self._adjustMenus()
431f4c16
RD
1025
1026
1027 def doChooseLineTool(self, event=None):
1e4a197e
RD
1028 """ Respond to the "Line Tool" menu command.
1029 """
1030 self._setCurrentTool(self.lineIcon)
1031 self.drawPanel.SetCursor(wxCROSS_CURSOR)
1032 self.deselectAll()
1033 self._adjustMenus()
431f4c16
RD
1034
1035
1036 def doChooseRectTool(self, event=None):
1e4a197e
RD
1037 """ Respond to the "Rect Tool" menu command.
1038 """
1039 self._setCurrentTool(self.rectIcon)
1040 self.drawPanel.SetCursor(wxCROSS_CURSOR)
1041 self.deselectAll()
1042 self._adjustMenus()
431f4c16
RD
1043
1044
1045 def doChooseEllipseTool(self, event=None):
1e4a197e
RD
1046 """ Respond to the "Ellipse Tool" menu command.
1047 """
1048 self._setCurrentTool(self.ellipseIcon)
1049 self.drawPanel.SetCursor(wxCROSS_CURSOR)
1050 self.deselectAll()
1051 self._adjustMenus()
431f4c16
RD
1052
1053
1054 def doChooseTextTool(self, event=None):
1e4a197e
RD
1055 """ Respond to the "Text Tool" menu command.
1056 """
1057 self._setCurrentTool(self.textIcon)
1058 self.drawPanel.SetCursor(wxCROSS_CURSOR)
1059 self.deselectAll()
1060 self._adjustMenus()
431f4c16
RD
1061
1062
1063 def doMoveForward(self, event):
1e4a197e
RD
1064 """ Respond to the "Move Forward" menu command.
1065 """
1066 if len(self.selection) != 1: return
431f4c16 1067
1e4a197e 1068 self._saveUndoInfo()
431f4c16 1069
1e4a197e
RD
1070 obj = self.selection[0]
1071 index = self.contents.index(obj)
1072 if index == 0: return
431f4c16 1073
1e4a197e
RD
1074 del self.contents[index]
1075 self.contents.insert(index-1, obj)
431f4c16 1076
1e4a197e
RD
1077 self.drawPanel.Refresh()
1078 self._adjustMenus()
431f4c16
RD
1079
1080
1081 def doMoveToFront(self, event):
1e4a197e
RD
1082 """ Respond to the "Move to Front" menu command.
1083 """
1084 if len(self.selection) != 1: return
431f4c16 1085
1e4a197e 1086 self._saveUndoInfo()
431f4c16 1087
1e4a197e
RD
1088 obj = self.selection[0]
1089 self.contents.remove(obj)
1090 self.contents.insert(0, obj)
431f4c16 1091
1e4a197e
RD
1092 self.drawPanel.Refresh()
1093 self._adjustMenus()
431f4c16
RD
1094
1095
1096 def doMoveBackward(self, event):
1e4a197e
RD
1097 """ Respond to the "Move Backward" menu command.
1098 """
1099 if len(self.selection) != 1: return
431f4c16 1100
1e4a197e 1101 self._saveUndoInfo()
431f4c16 1102
1e4a197e
RD
1103 obj = self.selection[0]
1104 index = self.contents.index(obj)
1105 if index == len(self.contents) - 1: return
431f4c16 1106
1e4a197e
RD
1107 del self.contents[index]
1108 self.contents.insert(index+1, obj)
431f4c16 1109
1e4a197e
RD
1110 self.drawPanel.Refresh()
1111 self._adjustMenus()
431f4c16
RD
1112
1113
1114 def doMoveToBack(self, event):
1e4a197e
RD
1115 """ Respond to the "Move to Back" menu command.
1116 """
1117 if len(self.selection) != 1: return
431f4c16 1118
1e4a197e 1119 self._saveUndoInfo()
431f4c16 1120
1e4a197e
RD
1121 obj = self.selection[0]
1122 self.contents.remove(obj)
1123 self.contents.append(obj)
431f4c16 1124
1e4a197e
RD
1125 self.drawPanel.Refresh()
1126 self._adjustMenus()
431f4c16
RD
1127
1128
1129 def doShowAbout(self, event):
1e4a197e
RD
1130 """ Respond to the "About pySketch" menu command.
1131 """
1132 dialog = wxDialog(self, -1, "About pySketch") # ,
1133 #style=wxDIALOG_MODAL | wxSTAY_ON_TOP)
1134 dialog.SetBackgroundColour(wxWHITE)
1135
1136 panel = wxPanel(dialog, -1)
1137 panel.SetBackgroundColour(wxWHITE)
1138
1139 panelSizer = wxBoxSizer(wxVERTICAL)
1140
1141 boldFont = wxFont(panel.GetFont().GetPointSize(),
1142 panel.GetFont().GetFamily(),
1143 wxNORMAL, wxBOLD)
1144
1145 logo = wxStaticBitmap(panel, -1, wxBitmap("images/logo.bmp",
1146 wxBITMAP_TYPE_BMP))
1147
1148 lab1 = wxStaticText(panel, -1, "pySketch")
1149 lab1.SetFont(wxFont(36, boldFont.GetFamily(), wxITALIC, wxBOLD))
1150 lab1.SetSize(lab1.GetBestSize())
1151
1152 imageSizer = wxBoxSizer(wxHORIZONTAL)
1153 imageSizer.Add(logo, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5)
1154 imageSizer.Add(lab1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5)
1155
1156 lab2 = wxStaticText(panel, -1, "A simple object-oriented drawing " + \
1157 "program.")
1158 lab2.SetFont(boldFont)
1159 lab2.SetSize(lab2.GetBestSize())
1160
1161 lab3 = wxStaticText(panel, -1, "pySketch is completely free " + \
1162 "software; please")
1163 lab3.SetFont(boldFont)
1164 lab3.SetSize(lab3.GetBestSize())
1165
1166 lab4 = wxStaticText(panel, -1, "feel free to adapt or use this " + \
1167 "in any way you like.")
1168 lab4.SetFont(boldFont)
1169 lab4.SetSize(lab4.GetBestSize())
1170
1171 lab5 = wxStaticText(panel, -1, "Author: Erik Westra " + \
1172 "(ewestra@wave.co.nz)")
1173 lab5.SetFont(boldFont)
1174 lab5.SetSize(lab5.GetBestSize())
1175
1176 btnOK = wxButton(panel, wxID_OK, "OK")
1177
1178 panelSizer.Add(imageSizer, 0, wxALIGN_CENTRE)
de51258f 1179 panelSizer.Add((10, 10)) # Spacer.
1e4a197e 1180 panelSizer.Add(lab2, 0, wxALIGN_CENTRE)
de51258f 1181 panelSizer.Add((10, 10)) # Spacer.
1e4a197e
RD
1182 panelSizer.Add(lab3, 0, wxALIGN_CENTRE)
1183 panelSizer.Add(lab4, 0, wxALIGN_CENTRE)
de51258f 1184 panelSizer.Add((10, 10)) # Spacer.
1e4a197e 1185 panelSizer.Add(lab5, 0, wxALIGN_CENTRE)
de51258f 1186 panelSizer.Add((10, 10)) # Spacer.
1e4a197e
RD
1187 panelSizer.Add(btnOK, 0, wxALL | wxALIGN_CENTRE, 5)
1188
1189 panel.SetAutoLayout(True)
1190 panel.SetSizer(panelSizer)
1191 panelSizer.Fit(panel)
1192
1193 topSizer = wxBoxSizer(wxHORIZONTAL)
1194 topSizer.Add(panel, 0, wxALL, 10)
1195
1196 dialog.SetAutoLayout(True)
1197 dialog.SetSizer(topSizer)
1198 topSizer.Fit(dialog)
1199
1200 dialog.Centre()
1201
1202 btn = dialog.ShowModal()
1203 dialog.Destroy()
431f4c16
RD
1204
1205 # =============================
1206 # == Object Creation Methods ==
1207 # =============================
1208
1209 def createLine(self, x1, y1, x2, y2):
1e4a197e
RD
1210 """ Create a new line object at the given position and size.
1211 """
1212 self._saveUndoInfo()
1213
1214 topLeftX = min(x1, x2)
1215 topLeftY = min(y1, y2)
1216 botRightX = max(x1, x2)
1217 botRightY = max(y1, y2)
1218
1219 obj = DrawingObject(obj_LINE, position=wxPoint(topLeftX, topLeftY),
1220 size=wxSize(botRightX-topLeftX,
1221 botRightY-topLeftY),
1222 penColour=self.penColour,
1223 fillColour=self.fillColour,
1224 lineSize=self.lineSize,
1225 startPt = wxPoint(x1 - topLeftX, y1 - topLeftY),
1226 endPt = wxPoint(x2 - topLeftX, y2 - topLeftY))
1227 self.contents.insert(0, obj)
1228 self.dirty = True
1229 self.doChooseSelectTool()
1230 self.select(obj)
431f4c16
RD
1231
1232
1233 def createRect(self, x, y, width, height):
1e4a197e
RD
1234 """ Create a new rectangle object at the given position and size.
1235 """
1236 self._saveUndoInfo()
431f4c16 1237
1e4a197e
RD
1238 obj = DrawingObject(obj_RECT, position=wxPoint(x, y),
1239 size=wxSize(width, height),
1240 penColour=self.penColour,
1241 fillColour=self.fillColour,
1242 lineSize=self.lineSize)
1243 self.contents.insert(0, obj)
1244 self.dirty = True
1245 self.doChooseSelectTool()
1246 self.select(obj)
431f4c16
RD
1247
1248
1249 def createEllipse(self, x, y, width, height):
1e4a197e
RD
1250 """ Create a new ellipse object at the given position and size.
1251 """
1252 self._saveUndoInfo()
431f4c16 1253
1e4a197e
RD
1254 obj = DrawingObject(obj_ELLIPSE, position=wxPoint(x, y),
1255 size=wxSize(width, height),
1256 penColour=self.penColour,
1257 fillColour=self.fillColour,
1258 lineSize=self.lineSize)
1259 self.contents.insert(0, obj)
1260 self.dirty = True
1261 self.doChooseSelectTool()
1262 self.select(obj)
431f4c16
RD
1263
1264
1265 def createText(self, x, y, width, height):
1e4a197e
RD
1266 """ Create a new text object at the given position and size.
1267 """
1268 editor = EditTextObjectDialog(self, "Create Text Object")
1269 if editor.ShowModal() == wxID_CANCEL:
1270 editor.Destroy()
1271 return
431f4c16 1272
1e4a197e 1273 self._saveUndoInfo()
431f4c16 1274
1e4a197e
RD
1275 obj = DrawingObject(obj_TEXT, position=wxPoint(x, y),
1276 size=wxSize(width, height))
1277 editor.dialogToObject(obj)
1278 editor.Destroy()
431f4c16 1279
1e4a197e
RD
1280 self.contents.insert(0, obj)
1281 self.dirty = True
1282 self.doChooseSelectTool()
1283 self.select(obj)
431f4c16
RD
1284
1285 # =======================
1286 # == Selection Methods ==
1287 # =======================
1288
1289 def selectAll(self):
1e4a197e
RD
1290 """ Select every DrawingObject in our document.
1291 """
1292 self.selection = []
1293 for obj in self.contents:
1294 self.selection.append(obj)
1295 self.drawPanel.Refresh()
1296 self._adjustMenus()
431f4c16
RD
1297
1298
1299 def deselectAll(self):
1e4a197e
RD
1300 """ Deselect every DrawingObject in our document.
1301 """
1302 self.selection = []
1303 self.drawPanel.Refresh()
1304 self._adjustMenus()
431f4c16
RD
1305
1306
1307 def select(self, obj):
1e4a197e
RD
1308 """ Select the given DrawingObject within our document.
1309 """
1310 self.selection = [obj]
1311 self.drawPanel.Refresh()
1312 self._adjustMenus()
431f4c16
RD
1313
1314
1315 def selectMany(self, objs):
1e4a197e
RD
1316 """ Select the given list of DrawingObjects.
1317 """
1318 self.selection = objs
1319 self.drawPanel.Refresh()
1320 self._adjustMenus()
431f4c16
RD
1321
1322
1323 def selectByRectangle(self, x, y, width, height):
1e4a197e
RD
1324 """ Select every DrawingObject in the given rectangular region.
1325 """
1326 self.selection = []
1327 for obj in self.contents:
1328 if obj.objectWithinRect(x, y, width, height):
1329 self.selection.append(obj)
1330 self.drawPanel.Refresh()
1331 self._adjustMenus()
431f4c16
RD
1332
1333 # ======================
1334 # == File I/O Methods ==
1335 # ======================
1336
1337 def loadContents(self):
1e4a197e
RD
1338 """ Load the contents of our document into memory.
1339 """
1340 f = open(self.fileName, "rb")
1341 objData = cPickle.load(f)
1342 f.close()
431f4c16 1343
1e4a197e
RD
1344 for type, data in objData:
1345 obj = DrawingObject(type)
1346 obj.setData(data)
1347 self.contents.append(obj)
431f4c16 1348
1e4a197e
RD
1349 self.dirty = False
1350 self.selection = []
1351 self.undoInfo = None
431f4c16 1352
1e4a197e
RD
1353 self.drawPanel.Refresh()
1354 self._adjustMenus()
431f4c16
RD
1355
1356
1357 def saveContents(self):
1e4a197e
RD
1358 """ Save the contents of our document to disk.
1359 """
1360 objData = []
1361 for obj in self.contents:
1362 objData.append([obj.getType(), obj.getData()])
431f4c16 1363
1e4a197e
RD
1364 f = open(self.fileName, "wb")
1365 cPickle.dump(objData, f)
1366 f.close()
431f4c16 1367
1e4a197e 1368 self.dirty = False
431f4c16
RD
1369
1370
1371 def askIfUserWantsToSave(self, action):
1e4a197e
RD
1372 """ Give the user the opportunity to save the current document.
1373
1374 'action' is a string describing the action about to be taken. If
1375 the user wants to save the document, it is saved immediately. If
1376 the user cancels, we return False.
1377 """
1378 if not self.dirty: return True # Nothing to do.
1379
1380 response = wxMessageBox("Save changes before " + action + "?",
1381 "Confirm", wxYES_NO | wxCANCEL, self)
1382
1383 if response == wxYES:
1384 if self.fileName == None:
1385 fileName = wxFileSelector("Save File As", "Saving",
1386 default_extension="psk",
1387 wildcard="*.psk",
1388 flags = wxSAVE | wxOVERWRITE_PROMPT)
1389 if fileName == "": return False # User cancelled.
1390 self.fileName = fileName
1391
1392 self.saveContents()
1393 return True
1394 elif response == wxNO:
1395 return True # User doesn't want changes saved.
1396 elif response == wxCANCEL:
1397 return False # User cancelled.
431f4c16
RD
1398
1399 # =====================
1400 # == Private Methods ==
1401 # =====================
1402
1403 def _adjustMenus(self):
1e4a197e
RD
1404 """ Adjust our menus and toolbar to reflect the current state of the
1405 world.
1406 """
1407 canSave = (self.fileName != None) and self.dirty
1408 canRevert = (self.fileName != None) and self.dirty
1409 canUndo = self.undoInfo != None
1410 selection = len(self.selection) > 0
1411 onlyOne = len(self.selection) == 1
1412 isText = onlyOne and (self.selection[0].getType() == obj_TEXT)
1413 front = onlyOne and (self.selection[0] == self.contents[0])
1414 back = onlyOne and (self.selection[0] == self.contents[-1])
1415
1416 # Enable/disable our menu items.
1417
1418 self.fileMenu.Enable(wxID_SAVE, canSave)
1419 self.fileMenu.Enable(wxID_REVERT, canRevert)
1420
1421 self.editMenu.Enable(menu_UNDO, canUndo)
1422 self.editMenu.Enable(menu_DUPLICATE, selection)
1423 self.editMenu.Enable(menu_EDIT_TEXT, isText)
1424 self.editMenu.Enable(menu_DELETE, selection)
1425
1426 self.toolsMenu.Check(menu_SELECT, self.curTool == self.selectIcon)
1427 self.toolsMenu.Check(menu_LINE, self.curTool == self.lineIcon)
1428 self.toolsMenu.Check(menu_RECT, self.curTool == self.rectIcon)
1429 self.toolsMenu.Check(menu_ELLIPSE, self.curTool == self.ellipseIcon)
1430 self.toolsMenu.Check(menu_TEXT, self.curTool == self.textIcon)
1431
1432 self.objectMenu.Enable(menu_MOVE_FORWARD, onlyOne and not front)
1433 self.objectMenu.Enable(menu_MOVE_TO_FRONT, onlyOne and not front)
1434 self.objectMenu.Enable(menu_MOVE_BACKWARD, onlyOne and not back)
1435 self.objectMenu.Enable(menu_MOVE_TO_BACK, onlyOne and not back)
1436
1437 # Enable/disable our toolbar icons.
1438
1439 self.toolbar.EnableTool(wxID_NEW, True)
1440 self.toolbar.EnableTool(wxID_OPEN, True)
1441 self.toolbar.EnableTool(wxID_SAVE, canSave)
1442 self.toolbar.EnableTool(menu_UNDO, canUndo)
1443 self.toolbar.EnableTool(menu_DUPLICATE, selection)
1444 self.toolbar.EnableTool(menu_MOVE_FORWARD, onlyOne and not front)
1445 self.toolbar.EnableTool(menu_MOVE_BACKWARD, onlyOne and not back)
431f4c16
RD
1446
1447
1448 def _setCurrentTool(self, newToolIcon):
1e4a197e
RD
1449 """ Set the currently selected tool.
1450 """
1451 if self.curTool == newToolIcon: return # Nothing to do.
431f4c16 1452
1e4a197e
RD
1453 if self.curTool != None:
1454 self.curTool.deselect()
431f4c16 1455
1e4a197e
RD
1456 newToolIcon.select()
1457 self.curTool = newToolIcon
431f4c16
RD
1458
1459
1460 def _setPenColour(self, colour):
1e4a197e
RD
1461 """ Set the default or selected object's pen colour.
1462 """
1463 if len(self.selection) > 0:
1464 self._saveUndoInfo()
1465 for obj in self.selection:
1466 obj.setPenColour(colour)
1467 self.drawPanel.Refresh()
1468 else:
1469 self.penColour = colour
1470 self.optionIndicator.setPenColour(colour)
431f4c16
RD
1471
1472
1473 def _setFillColour(self, colour):
1e4a197e
RD
1474 """ Set the default or selected object's fill colour.
1475 """
1476 if len(self.selection) > 0:
1477 self._saveUndoInfo()
1478 for obj in self.selection:
1479 obj.setFillColour(colour)
1480 self.drawPanel.Refresh()
1481 else:
1482 self.fillColour = colour
1483 self.optionIndicator.setFillColour(colour)
431f4c16
RD
1484
1485
1486 def _setLineSize(self, size):
1e4a197e
RD
1487 """ Set the default or selected object's line size.
1488 """
1489 if len(self.selection) > 0:
1490 self._saveUndoInfo()
1491 for obj in self.selection:
1492 obj.setLineSize(size)
1493 self.drawPanel.Refresh()
1494 else:
1495 self.lineSize = size
1496 self.optionIndicator.setLineSize(size)
431f4c16
RD
1497
1498
1499 def _saveUndoInfo(self):
1e4a197e 1500 """ Remember the current state of the document, to allow for undo.
431f4c16 1501
1e4a197e
RD
1502 We make a copy of the document's contents, so that we can return to
1503 the previous contents if the user does something and then wants to
1504 undo the operation.
1505 """
1506 savedContents = []
1507 for obj in self.contents:
1508 savedContents.append([obj.getType(), obj.getData()])
431f4c16 1509
1e4a197e
RD
1510 savedSelection = []
1511 for i in range(len(self.contents)):
1512 if self.contents[i] in self.selection:
1513 savedSelection.append(i)
431f4c16 1514
1e4a197e
RD
1515 self.undoInfo = {"contents" : savedContents,
1516 "selection" : savedSelection}
431f4c16
RD
1517
1518
1519 def _resizeObject(self, obj, anchorPt, oldPt, newPt):
1e4a197e 1520 """ Resize the given object.
431f4c16 1521
1e4a197e
RD
1522 'anchorPt' is the unchanging corner of the object, while the
1523 opposite corner has been resized. 'oldPt' are the current
1524 coordinates for this corner, while 'newPt' are the new coordinates.
1525 The object should fit within the given dimensions, though if the
1526 new point is less than the anchor point the object will need to be
1527 moved as well as resized, to avoid giving it a negative size.
1528 """
1529 if obj.getType() == obj_TEXT:
1530 # Not allowed to resize text objects -- they're sized to fit text.
de51258f 1531 wxBell(); print "4"
1e4a197e 1532 return
431f4c16 1533
1e4a197e 1534 self._saveUndoInfo()
431f4c16 1535
1e4a197e
RD
1536 topLeft = wxPoint(min(anchorPt.x, newPt.x),
1537 min(anchorPt.y, newPt.y))
1538 botRight = wxPoint(max(anchorPt.x, newPt.x),
1539 max(anchorPt.y, newPt.y))
431f4c16 1540
1e4a197e
RD
1541 newWidth = botRight.x - topLeft.x
1542 newHeight = botRight.y - topLeft.y
431f4c16 1543
1e4a197e
RD
1544 if obj.getType() == obj_LINE:
1545 # Adjust the line so that its start and end points match the new
1546 # overall object size.
431f4c16 1547
1e4a197e
RD
1548 startPt = obj.getStartPt()
1549 endPt = obj.getEndPt()
431f4c16 1550
1e4a197e
RD
1551 slopesDown = ((startPt.x < endPt.x) and (startPt.y < endPt.y)) or \
1552 ((startPt.x > endPt.x) and (startPt.y > endPt.y))
431f4c16 1553
1e4a197e 1554 # Handle the user flipping the line.
431f4c16 1555
1e4a197e
RD
1556 hFlip = ((anchorPt.x < oldPt.x) and (anchorPt.x > newPt.x)) or \
1557 ((anchorPt.x > oldPt.x) and (anchorPt.x < newPt.x))
1558 vFlip = ((anchorPt.y < oldPt.y) and (anchorPt.y > newPt.y)) or \
1559 ((anchorPt.y > oldPt.y) and (anchorPt.y < newPt.y))
431f4c16 1560
1e4a197e
RD
1561 if (hFlip and not vFlip) or (vFlip and not hFlip):
1562 slopesDown = not slopesDown # Line flipped.
431f4c16 1563
1e4a197e
RD
1564 if slopesDown:
1565 obj.setStartPt(wxPoint(0, 0))
1566 obj.setEndPt(wxPoint(newWidth, newHeight))
1567 else:
1568 obj.setStartPt(wxPoint(0, newHeight))
1569 obj.setEndPt(wxPoint(newWidth, 0))
431f4c16 1570
1e4a197e 1571 # Finally, adjust the bounds of the object to match the new dimensions.
431f4c16 1572
1e4a197e
RD
1573 obj.setPosition(topLeft)
1574 obj.setSize(wxSize(botRight.x - topLeft.x, botRight.y - topLeft.y))
431f4c16 1575
1e4a197e 1576 self.drawPanel.Refresh()
431f4c16
RD
1577
1578
1579 def _moveObject(self, offsetX, offsetY):
1e4a197e
RD
1580 """ Move the currently selected object(s) by the given offset.
1581 """
1582 self._saveUndoInfo()
431f4c16 1583
1e4a197e
RD
1584 for obj in self.selection:
1585 pos = obj.getPosition()
1586 pos.x = pos.x + offsetX
1587 pos.y = pos.y + offsetY
1588 obj.setPosition(pos)
431f4c16 1589
1e4a197e 1590 self.drawPanel.Refresh()
431f4c16
RD
1591
1592
1593 def _buildLineSizePopup(self, lineSize):
1e4a197e
RD
1594 """ Build the pop-up menu used to set the line size.
1595
1596 'lineSize' is the current line size value. The corresponding item
1597 is checked in the pop-up menu.
1598 """
1599 menu = wxMenu()
1600 menu.Append(id_LINESIZE_0, "no line", kind=wxITEM_CHECK)
1601 menu.Append(id_LINESIZE_1, "1-pixel line", kind=wxITEM_CHECK)
1602 menu.Append(id_LINESIZE_2, "2-pixel line", kind=wxITEM_CHECK)
1603 menu.Append(id_LINESIZE_3, "3-pixel line", kind=wxITEM_CHECK)
1604 menu.Append(id_LINESIZE_4, "4-pixel line", kind=wxITEM_CHECK)
1605 menu.Append(id_LINESIZE_5, "5-pixel line", kind=wxITEM_CHECK)
1606
1607 if lineSize == 0: menu.Check(id_LINESIZE_0, True)
1608 elif lineSize == 1: menu.Check(id_LINESIZE_1, True)
1609 elif lineSize == 2: menu.Check(id_LINESIZE_2, True)
1610 elif lineSize == 3: menu.Check(id_LINESIZE_3, True)
1611 elif lineSize == 4: menu.Check(id_LINESIZE_4, True)
1612 elif lineSize == 5: menu.Check(id_LINESIZE_5, True)
1613
1614 EVT_MENU(self, id_LINESIZE_0, self._lineSizePopupSelected)
1615 EVT_MENU(self, id_LINESIZE_1, self._lineSizePopupSelected)
1616 EVT_MENU(self, id_LINESIZE_2, self._lineSizePopupSelected)
1617 EVT_MENU(self, id_LINESIZE_3, self._lineSizePopupSelected)
1618 EVT_MENU(self, id_LINESIZE_4, self._lineSizePopupSelected)
1619 EVT_MENU(self, id_LINESIZE_5, self._lineSizePopupSelected)
1620
1621 return menu
431f4c16
RD
1622
1623
1624 def _lineSizePopupSelected(self, event):
1e4a197e
RD
1625 """ Respond to the user selecting an item from the line size popup menu
1626 """
1627 id = event.GetId()
1628 if id == id_LINESIZE_0: self._setLineSize(0)
1629 elif id == id_LINESIZE_1: self._setLineSize(1)
1630 elif id == id_LINESIZE_2: self._setLineSize(2)
1631 elif id == id_LINESIZE_3: self._setLineSize(3)
1632 elif id == id_LINESIZE_4: self._setLineSize(4)
1633 elif id == id_LINESIZE_5: self._setLineSize(5)
1634 else:
de51258f 1635 wxBell(); print "5"
1e4a197e
RD
1636 return
1637
1638 self.optionIndicator.setLineSize(self.lineSize)
431f4c16
RD
1639
1640
1641 def _getEventCoordinates(self, event):
1e4a197e 1642 """ Return the coordinates associated with the given mouse event.
431f4c16 1643
1e4a197e
RD
1644 The coordinates have to be adjusted to allow for the current scroll
1645 position.
1646 """
431f4c16
RD
1647 originX, originY = self.drawPanel.GetViewStart()
1648 unitX, unitY = self.drawPanel.GetScrollPixelsPerUnit()
1649 return wxPoint(event.GetX() + (originX * unitX),
1e4a197e 1650 event.GetY() + (originY * unitY))
431f4c16
RD
1651
1652
1653 def _getObjectAndSelectionHandleAt(self, pt):
1e4a197e 1654 """ Return the object and selection handle at the given point.
431f4c16 1655
1e4a197e
RD
1656 We draw selection handles (small rectangles) around the currently
1657 selected object(s). If the given point is within one of the
1658 selection handle rectangles, we return the associated object and a
1659 code indicating which selection handle the point is in. If the
1660 point isn't within any selection handle at all, we return the tuple
1661 (None, handle_NONE).
1662 """
1663 for obj in self.selection:
1664 handle = obj.getSelectionHandleContainingPoint(pt.x, pt.y)
1665 if handle != handle_NONE:
1666 return obj, handle
431f4c16 1667
1e4a197e 1668 return None, handle_NONE
431f4c16
RD
1669
1670
1671 def _getObjectAt(self, pt):
1e4a197e
RD
1672 """ Return the first object found which is at the given point.
1673 """
1674 for obj in self.contents:
1675 if obj.objectContainsPoint(pt.x, pt.y):
1676 return obj
1677 return None
431f4c16
RD
1678
1679
1680 def _drawObjectOutline(self, offsetX, offsetY):
1e4a197e 1681 """ Draw an outline of the currently selected object.
431f4c16 1682
1e4a197e
RD
1683 The selected object's outline is drawn at the object's position
1684 plus the given offset.
431f4c16 1685
1e4a197e
RD
1686 Note that the outline is drawn by *inverting* the window's
1687 contents, so calling _drawObjectOutline twice in succession will
1688 restore the window's contents back to what they were previously.
1689 """
1690 if len(self.selection) != 1: return
431f4c16 1691
1e4a197e
RD
1692 position = self.selection[0].getPosition()
1693 size = self.selection[0].getSize()
431f4c16 1694
1e4a197e
RD
1695 dc = wxClientDC(self.drawPanel)
1696 self.drawPanel.PrepareDC(dc)
1697 dc.BeginDrawing()
1698 dc.SetPen(wxBLACK_DASHED_PEN)
1699 dc.SetBrush(wxTRANSPARENT_BRUSH)
1700 dc.SetLogicalFunction(wxINVERT)
431f4c16 1701
1e4a197e
RD
1702 dc.DrawRectangle(position.x + offsetX, position.y + offsetY,
1703 size.width, size.height)
431f4c16 1704
1e4a197e 1705 dc.EndDrawing()
431f4c16
RD
1706
1707
1708 def _drawVisualFeedback(self, startPt, endPt, type, dashedLine):
1e4a197e
RD
1709 """ Draw visual feedback for a drawing operation.
1710
1711 The visual feedback consists of a line, ellipse, or rectangle based
1712 around the two given points. 'type' should be one of the following
1713 predefined feedback type constants:
1714
1715 feedback_RECT -> draw rectangular feedback.
1716 feedback_LINE -> draw line feedback.
1717 feedback_ELLIPSE -> draw elliptical feedback.
1718
1719 if 'dashedLine' is True, the feedback is drawn as a dashed rather
1720 than a solid line.
1721
1722 Note that the feedback is drawn by *inverting* the window's
1723 contents, so calling _drawVisualFeedback twice in succession will
1724 restore the window's contents back to what they were previously.
1725 """
1726 dc = wxClientDC(self.drawPanel)
1727 self.drawPanel.PrepareDC(dc)
1728 dc.BeginDrawing()
1729 if dashedLine:
1730 dc.SetPen(wxBLACK_DASHED_PEN)
1731 else:
1732 dc.SetPen(wxBLACK_PEN)
1733 dc.SetBrush(wxTRANSPARENT_BRUSH)
1734 dc.SetLogicalFunction(wxINVERT)
1735
1736 if type == feedback_RECT:
1737 dc.DrawRectangle(startPt.x, startPt.y,
1738 endPt.x - startPt.x,
1739 endPt.y - startPt.y)
1740 elif type == feedback_LINE:
1741 dc.DrawLine(startPt.x, startPt.y, endPt.x, endPt.y)
1742 elif type == feedback_ELLIPSE:
1743 dc.DrawEllipse(startPt.x, startPt.y,
1744 endPt.x - startPt.x,
1745 endPt.y - startPt.y)
1746
1747 dc.EndDrawing()
431f4c16
RD
1748
1749#----------------------------------------------------------------------------
1750
1751class DrawingObject:
1752 """ An object within the drawing panel.
1753
1e4a197e
RD
1754 A pySketch document consists of a front-to-back ordered list of
1755 DrawingObjects. Each DrawingObject has the following properties:
1756
1757 'type' What type of object this is (text, line, etc).
1758 'position' The position of the object within the document.
1759 'size' The size of the object within the document.
1760 'penColour' The colour to use for drawing the object's outline.
1761 'fillColour' Colour to use for drawing object's interior.
1762 'lineSize' Line width (in pixels) to use for object's outline.
1763 'startPt' The point, relative to the object's position, where
1764 an obj_LINE object's line should start.
1765 'endPt' The point, relative to the object's position, where
1766 an obj_LINE object's line should end.
1767 'text' The object's text (obj_TEXT objects only).
1768 'textFont' The text object's font name.
1769 'textSize' The text object's point size.
1770 'textBoldface' If True, this text object will be drawn in
1771 boldface.
1772 'textItalic' If True, this text object will be drawn in italic.
1773 'textUnderline' If True, this text object will be drawn underlined.
1774 """
431f4c16
RD
1775
1776 # ==================
1777 # == Constructors ==
1778 # ==================
1779
1780 def __init__(self, type, position=wxPoint(0, 0), size=wxSize(0, 0),
1e4a197e
RD
1781 penColour=wxBLACK, fillColour=wxWHITE, lineSize=1,
1782 text=None, startPt=wxPoint(0, 0), endPt=wxPoint(0,0)):
1783 """ Standard constructor.
1784
1785 'type' is the type of object being created. This should be one of
1786 the following constants:
1787
1788 obj_LINE
1789 obj_RECT
1790 obj_ELLIPSE
1791 obj_TEXT
1792
1793 The remaining parameters let you set various options for the newly
1794 created DrawingObject.
1795 """
1796 self.type = type
1797 self.position = position
1798 self.size = size
1799 self.penColour = penColour
1800 self.fillColour = fillColour
1801 self.lineSize = lineSize
1802 self.startPt = startPt
1803 self.endPt = endPt
1804 self.text = text
1805 self.textFont = wxSystemSettings_GetSystemFont(
1806 wxSYS_DEFAULT_GUI_FONT).GetFaceName()
1807 self.textSize = 12
1808 self.textBoldface = False
1809 self.textItalic = False
1810 self.textUnderline = False
431f4c16
RD
1811
1812 # =============================
1813 # == Object Property Methods ==
1814 # =============================
1815
1816 def getData(self):
1e4a197e
RD
1817 """ Return a copy of the object's internal data.
1818
1819 This is used to save this DrawingObject to disk.
1820 """
1821 return [self.type, self.position.x, self.position.y,
1822 self.size.width, self.size.height,
1823 self.penColour.Red(),
1824 self.penColour.Green(),
1825 self.penColour.Blue(),
1826 self.fillColour.Red(),
1827 self.fillColour.Green(),
1828 self.fillColour.Blue(),
1829 self.lineSize,
1830 self.startPt.x, self.startPt.y,
1831 self.endPt.x, self.endPt.y,
1832 self.text,
1833 self.textFont,
1834 self.textSize,
1835 self.textBoldface,
1836 self.textItalic,
1837 self.textUnderline]
431f4c16
RD
1838
1839
1840 def setData(self, data):
1e4a197e
RD
1841 """ Set the object's internal data.
1842
1843 'data' is a copy of the object's saved data, as returned by
1844 getData() above. This is used to restore a previously saved
1845 DrawingObject.
1846 """
1847 #data = copy.deepcopy(data) # Needed?
1848
1849 self.type = data[0]
1850 self.position = wxPoint(data[1], data[2])
1851 self.size = wxSize(data[3], data[4])
1852 self.penColour = wxColour(red=data[5],
1853 green=data[6],
1854 blue=data[7])
1855 self.fillColour = wxColour(red=data[8],
1856 green=data[9],
1857 blue=data[10])
1858 self.lineSize = data[11]
1859 self.startPt = wxPoint(data[12], data[13])
1860 self.endPt = wxPoint(data[14], data[15])
1861 self.text = data[16]
1862 self.textFont = data[17]
1863 self.textSize = data[18]
1864 self.textBoldface = data[19]
1865 self.textItalic = data[20]
1866 self.textUnderline = data[21]
431f4c16
RD
1867
1868
1869 def getType(self):
1e4a197e
RD
1870 """ Return this DrawingObject's type.
1871 """
1872 return self.type
431f4c16
RD
1873
1874
1875 def setPosition(self, position):
1e4a197e
RD
1876 """ Set the origin (top-left corner) for this DrawingObject.
1877 """
1878 self.position = position
431f4c16
RD
1879
1880
1881 def getPosition(self):
1e4a197e
RD
1882 """ Return this DrawingObject's position.
1883 """
1884 return self.position
431f4c16
RD
1885
1886
1887 def setSize(self, size):
1e4a197e
RD
1888 """ Set the size for this DrawingObject.
1889 """
1890 self.size = size
431f4c16
RD
1891
1892
1893 def getSize(self):
1e4a197e
RD
1894 """ Return this DrawingObject's size.
1895 """
1896 return self.size
431f4c16
RD
1897
1898
1899 def setPenColour(self, colour):
1e4a197e
RD
1900 """ Set the pen colour used for this DrawingObject.
1901 """
1902 self.penColour = colour
431f4c16
RD
1903
1904
1905 def getPenColour(self):
1e4a197e
RD
1906 """ Return this DrawingObject's pen colour.
1907 """
1908 return self.penColour
431f4c16
RD
1909
1910
1911 def setFillColour(self, colour):
1e4a197e
RD
1912 """ Set the fill colour used for this DrawingObject.
1913 """
1914 self.fillColour = colour
431f4c16
RD
1915
1916
1917 def getFillColour(self):
1e4a197e
RD
1918 """ Return this DrawingObject's fill colour.
1919 """
1920 return self.fillColour
431f4c16
RD
1921
1922
1923 def setLineSize(self, lineSize):
1e4a197e
RD
1924 """ Set the linesize used for this DrawingObject.
1925 """
1926 self.lineSize = lineSize
431f4c16
RD
1927
1928
1929 def getLineSize(self):
1e4a197e
RD
1930 """ Return this DrawingObject's line size.
1931 """
1932 return self.lineSize
431f4c16
RD
1933
1934
1935 def setStartPt(self, startPt):
1e4a197e
RD
1936 """ Set the starting point for this line DrawingObject.
1937 """
1938 self.startPt = startPt
431f4c16
RD
1939
1940
1941 def getStartPt(self):
1e4a197e
RD
1942 """ Return the starting point for this line DrawingObject.
1943 """
1944 return self.startPt
431f4c16
RD
1945
1946
1947 def setEndPt(self, endPt):
1e4a197e
RD
1948 """ Set the ending point for this line DrawingObject.
1949 """
1950 self.endPt = endPt
431f4c16
RD
1951
1952
1953 def getEndPt(self):
1e4a197e
RD
1954 """ Return the ending point for this line DrawingObject.
1955 """
1956 return self.endPt
431f4c16
RD
1957
1958
1959 def setText(self, text):
1e4a197e
RD
1960 """ Set the text for this DrawingObject.
1961 """
1962 self.text = text
431f4c16
RD
1963
1964
1965 def getText(self):
1e4a197e
RD
1966 """ Return this DrawingObject's text.
1967 """
1968 return self.text
431f4c16
RD
1969
1970
1971 def setTextFont(self, font):
1e4a197e
RD
1972 """ Set the typeface for this text DrawingObject.
1973 """
1974 self.textFont = font
431f4c16
RD
1975
1976
1977 def getTextFont(self):
1e4a197e
RD
1978 """ Return this text DrawingObject's typeface.
1979 """
1980 return self.textFont
431f4c16
RD
1981
1982
1983 def setTextSize(self, size):
1e4a197e
RD
1984 """ Set the point size for this text DrawingObject.
1985 """
1986 self.textSize = size
431f4c16
RD
1987
1988
1989 def getTextSize(self):
1e4a197e
RD
1990 """ Return this text DrawingObject's text size.
1991 """
1992 return self.textSize
431f4c16
RD
1993
1994
1995 def setTextBoldface(self, boldface):
1e4a197e
RD
1996 """ Set the boldface flag for this text DrawingObject.
1997 """
1998 self.textBoldface = boldface
431f4c16
RD
1999
2000
2001 def getTextBoldface(self):
1e4a197e
RD
2002 """ Return this text DrawingObject's boldface flag.
2003 """
2004 return self.textBoldface
431f4c16
RD
2005
2006
2007 def setTextItalic(self, italic):
1e4a197e
RD
2008 """ Set the italic flag for this text DrawingObject.
2009 """
2010 self.textItalic = italic
431f4c16
RD
2011
2012
2013 def getTextItalic(self):
1e4a197e
RD
2014 """ Return this text DrawingObject's italic flag.
2015 """
2016 return self.textItalic
431f4c16
RD
2017
2018
2019 def setTextUnderline(self, underline):
1e4a197e
RD
2020 """ Set the underling flag for this text DrawingObject.
2021 """
2022 self.textUnderline = underline
431f4c16
RD
2023
2024
2025 def getTextUnderline(self):
1e4a197e
RD
2026 """ Return this text DrawingObject's underline flag.
2027 """
2028 return self.textUnderline
431f4c16
RD
2029
2030 # ============================
2031 # == Object Drawing Methods ==
2032 # ============================
2033
2034 def draw(self, dc, selected):
1e4a197e
RD
2035 """ Draw this DrawingObject into our window.
2036
2037 'dc' is the device context to use for drawing. If 'selected' is
2038 True, the object is currently selected and should be drawn as such.
2039 """
2040 if self.type != obj_TEXT:
2041 if self.lineSize == 0:
2042 dc.SetPen(wxPen(self.penColour, self.lineSize, wxTRANSPARENT))
2043 else:
2044 dc.SetPen(wxPen(self.penColour, self.lineSize, wxSOLID))
2045 dc.SetBrush(wxBrush(self.fillColour, wxSOLID))
2046 else:
2047 dc.SetTextForeground(self.penColour)
2048 dc.SetTextBackground(self.fillColour)
2049
2050 self._privateDraw(dc, self.position, selected)
431f4c16
RD
2051
2052 # =======================
2053 # == Selection Methods ==
2054 # =======================
2055
2056 def objectContainsPoint(self, x, y):
1e4a197e
RD
2057 """ Returns True iff this object contains the given point.
2058
2059 This is used to determine if the user clicked on the object.
2060 """
2061 # Firstly, ignore any points outside of the object's bounds.
2062
2063 if x < self.position.x: return False
2064 if x > self.position.x + self.size.x: return False
2065 if y < self.position.y: return False
2066 if y > self.position.y + self.size.y: return False
2067
2068 if self.type in [obj_RECT, obj_TEXT]:
2069 # Rectangles and text are easy -- they're always selected if the
2070 # point is within their bounds.
2071 return True
2072
2073 # Now things get tricky. There's no straightforward way of knowing
2074 # whether the point is within the object's bounds...to get around this,
2075 # we draw the object into a memory-based bitmap and see if the given
2076 # point was drawn. This could no doubt be done more efficiently by
2077 # some tricky maths, but this approach works and is simple enough.
2078
2079 bitmap = wxEmptyBitmap(self.size.x + 10, self.size.y + 10)
2080 dc = wxMemoryDC()
2081 dc.SelectObject(bitmap)
2082 dc.BeginDrawing()
2083 dc.SetBackground(wxWHITE_BRUSH)
2084 dc.Clear()
2085 dc.SetPen(wxPen(wxBLACK, self.lineSize + 5, wxSOLID))
2086 dc.SetBrush(wxBLACK_BRUSH)
2087 self._privateDraw(dc, wxPoint(5, 5), True)
2088 dc.EndDrawing()
2089 pixel = dc.GetPixel(x - self.position.x + 5, y - self.position.y + 5)
2090 if (pixel.Red() == 0) and (pixel.Green() == 0) and (pixel.Blue() == 0):
2091 return True
2092 else:
2093 return False
431f4c16
RD
2094
2095
2096 def getSelectionHandleContainingPoint(self, x, y):
1e4a197e
RD
2097 """ Return the selection handle containing the given point, if any.
2098
2099 We return one of the predefined selection handle ID codes.
2100 """
2101 if self.type == obj_LINE:
2102 # We have selection handles at the start and end points.
2103 if self._pointInSelRect(x, y, self.position.x + self.startPt.x,
2104 self.position.y + self.startPt.y):
2105 return handle_START_POINT
2106 elif self._pointInSelRect(x, y, self.position.x + self.endPt.x,
2107 self.position.y + self.endPt.y):
2108 return handle_END_POINT
2109 else:
2110 return handle_NONE
2111 else:
2112 # We have selection handles at all four corners.
2113 if self._pointInSelRect(x, y, self.position.x, self.position.y):
2114 return handle_TOP_LEFT
2115 elif self._pointInSelRect(x, y, self.position.x + self.size.width,
2116 self.position.y):
2117 return handle_TOP_RIGHT
2118 elif self._pointInSelRect(x, y, self.position.x,
2119 self.position.y + self.size.height):
2120 return handle_BOTTOM_LEFT
2121 elif self._pointInSelRect(x, y, self.position.x + self.size.width,
2122 self.position.y + self.size.height):
2123 return handle_BOTTOM_RIGHT
2124 else:
2125 return handle_NONE
431f4c16
RD
2126
2127
2128 def objectWithinRect(self, x, y, width, height):
1e4a197e
RD
2129 """ Return True iff this object falls completely within the given rect.
2130 """
2131 if x > self.position.x: return False
2132 if x + width < self.position.x + self.size.width: return False
2133 if y > self.position.y: return False
2134 if y + height < self.position.y + self.size.height: return False
2135 return True
431f4c16
RD
2136
2137 # =====================
2138 # == Utility Methods ==
2139 # =====================
2140
2141 def fitToText(self):
1e4a197e
RD
2142 """ Resize a text DrawingObject so that it fits it's text exactly.
2143 """
2144 if self.type != obj_TEXT: return
431f4c16 2145
1e4a197e
RD
2146 if self.textBoldface: weight = wxBOLD
2147 else: weight = wxNORMAL
2148 if self.textItalic: style = wxITALIC
2149 else: style = wxNORMAL
2150 font = wxFont(self.textSize, wxDEFAULT, style, weight,
2151 self.textUnderline, self.textFont)
431f4c16 2152
1e4a197e
RD
2153 dummyWindow = wxFrame(None, -1, "")
2154 dummyWindow.SetFont(font)
2155 width, height = dummyWindow.GetTextExtent(self.text)
2156 dummyWindow.Destroy()
431f4c16 2157
1e4a197e 2158 self.size = wxSize(width, height)
431f4c16
RD
2159
2160 # =====================
2161 # == Private Methods ==
2162 # =====================
2163
2164 def _privateDraw(self, dc, position, selected):
1e4a197e
RD
2165 """ Private routine to draw this DrawingObject.
2166
2167 'dc' is the device context to use for drawing, while 'position' is
2168 the position in which to draw the object. If 'selected' is True,
2169 the object is drawn with selection handles. This private drawing
2170 routine assumes that the pen and brush have already been set by the
2171 caller.
2172 """
2173 if self.type == obj_LINE:
2174 dc.DrawLine(position.x + self.startPt.x,
2175 position.y + self.startPt.y,
2176 position.x + self.endPt.x,
2177 position.y + self.endPt.y)
2178 elif self.type == obj_RECT:
2179 dc.DrawRectangle(position.x, position.y,
2180 self.size.width, self.size.height)
2181 elif self.type == obj_ELLIPSE:
2182 dc.DrawEllipse(position.x, position.y,
2183 self.size.width, self.size.height)
2184 elif self.type == obj_TEXT:
2185 if self.textBoldface: weight = wxBOLD
2186 else: weight = wxNORMAL
2187 if self.textItalic: style = wxITALIC
2188 else: style = wxNORMAL
2189 font = wxFont(self.textSize, wxDEFAULT, style, weight,
2190 self.textUnderline, self.textFont)
2191 dc.SetFont(font)
2192 dc.DrawText(self.text, position.x, position.y)
2193
2194 if selected:
2195 dc.SetPen(wxTRANSPARENT_PEN)
2196 dc.SetBrush(wxBLACK_BRUSH)
2197
2198 if self.type == obj_LINE:
2199 # Draw selection handles at the start and end points.
2200 self._drawSelHandle(dc, position.x + self.startPt.x,
2201 position.y + self.startPt.y)
2202 self._drawSelHandle(dc, position.x + self.endPt.x,
2203 position.y + self.endPt.y)
2204 else:
2205 # Draw selection handles at all four corners.
2206 self._drawSelHandle(dc, position.x, position.y)
2207 self._drawSelHandle(dc, position.x + self.size.width,
2208 position.y)
2209 self._drawSelHandle(dc, position.x,
2210 position.y + self.size.height)
2211 self._drawSelHandle(dc, position.x + self.size.width,
2212 position.y + self.size.height)
431f4c16
RD
2213
2214
2215 def _drawSelHandle(self, dc, x, y):
1e4a197e 2216 """ Draw a selection handle around this DrawingObject.
431f4c16 2217
1e4a197e
RD
2218 'dc' is the device context to draw the selection handle within,
2219 while 'x' and 'y' are the coordinates to use for the centre of the
2220 selection handle.
2221 """
2222 dc.DrawRectangle(x - 3, y - 3, 6, 6)
431f4c16
RD
2223
2224
2225 def _pointInSelRect(self, x, y, rX, rY):
1e4a197e
RD
2226 """ Return True iff (x, y) is within the selection handle at (rX, ry).
2227 """
2228 if x < rX - 3: return False
2229 elif x > rX + 3: return False
2230 elif y < rY - 3: return False
2231 elif y > rY + 3: return False
2232 else: return True
431f4c16
RD
2233
2234#----------------------------------------------------------------------------
2235
de51258f 2236class ToolPaletteIcon(wxBitmapButton):
431f4c16
RD
2237 """ An icon appearing in the tool palette area of our sketching window.
2238
de51258f 2239 Note that this is actually implemented as a wxBitmap rather
1e4a197e
RD
2240 than as a wxIcon. wxIcon has a very specific meaning, and isn't
2241 appropriate for this more general use.
431f4c16
RD
2242 """
2243
2244 def __init__(self, parent, iconID, iconName, toolTip):
1e4a197e 2245 """ Standard constructor.
431f4c16 2246
1e4a197e
RD
2247 'parent' is the parent window this icon will be part of.
2248 'iconID' is the internal ID used for this icon.
2249 'iconName' is the name used for this icon.
2250 'toolTip' is the tool tip text to show for this icon.
431f4c16 2251
1e4a197e
RD
2252 The icon name is used to get the appropriate bitmap for this icon.
2253 """
2254 bmp = wxBitmap("images/" + iconName + "Icon.bmp", wxBITMAP_TYPE_BMP)
de51258f 2255 wxBitmapButton.__init__(self, parent, iconID, bmp, wxDefaultPosition,
1e4a197e
RD
2256 wxSize(bmp.GetWidth(), bmp.GetHeight()))
2257 self.SetToolTip(wxToolTip(toolTip))
431f4c16 2258
1e4a197e
RD
2259 self.iconID = iconID
2260 self.iconName = iconName
2261 self.isSelected = False
431f4c16
RD
2262
2263
2264 def select(self):
1e4a197e 2265 """ Select the icon.
431f4c16 2266
1e4a197e
RD
2267 The icon's visual representation is updated appropriately.
2268 """
2269 if self.isSelected: return # Nothing to do!
431f4c16 2270
1e4a197e
RD
2271 bmp = wxBitmap("images/" + self.iconName + "IconSel.bmp",
2272 wxBITMAP_TYPE_BMP)
de51258f 2273 self.SetBitmapLabel(bmp)
1e4a197e 2274 self.isSelected = True
431f4c16
RD
2275
2276
2277 def deselect(self):
1e4a197e 2278 """ Deselect the icon.
431f4c16 2279
1e4a197e
RD
2280 The icon's visual representation is updated appropriately.
2281 """
2282 if not self.isSelected: return # Nothing to do!
431f4c16 2283
1e4a197e
RD
2284 bmp = wxBitmap("images/" + self.iconName + "Icon.bmp",
2285 wxBITMAP_TYPE_BMP)
de51258f 2286 self.SetBitmapLabel(bmp)
1e4a197e 2287 self.isSelected = False
431f4c16
RD
2288
2289#----------------------------------------------------------------------------
2290
2291class ToolOptionIndicator(wxWindow):
2292 """ A visual indicator which shows the current tool options.
2293 """
2294 def __init__(self, parent):
1e4a197e
RD
2295 """ Standard constructor.
2296 """
2297 wxWindow.__init__(self, parent, -1, wxDefaultPosition, wxSize(52, 32))
431f4c16 2298
1e4a197e
RD
2299 self.penColour = wxBLACK
2300 self.fillColour = wxWHITE
2301 self.lineSize = 1
431f4c16 2302
1e4a197e 2303 EVT_PAINT(self, self.OnPaint)
431f4c16
RD
2304
2305
2306 def setPenColour(self, penColour):
1e4a197e
RD
2307 """ Set the indicator's current pen colour.
2308 """
2309 self.penColour = penColour
2310 self.Refresh()
431f4c16
RD
2311
2312
2313 def setFillColour(self, fillColour):
1e4a197e
RD
2314 """ Set the indicator's current fill colour.
2315 """
2316 self.fillColour = fillColour
2317 self.Refresh()
431f4c16
RD
2318
2319
2320 def setLineSize(self, lineSize):
1e4a197e
RD
2321 """ Set the indicator's current pen colour.
2322 """
2323 self.lineSize = lineSize
2324 self.Refresh()
431f4c16
RD
2325
2326
2327 def OnPaint(self, event):
1e4a197e
RD
2328 """ Paint our tool option indicator.
2329 """
2330 dc = wxPaintDC(self)
2331 dc.BeginDrawing()
431f4c16 2332
1e4a197e
RD
2333 if self.lineSize == 0:
2334 dc.SetPen(wxPen(self.penColour, self.lineSize, wxTRANSPARENT))
2335 else:
2336 dc.SetPen(wxPen(self.penColour, self.lineSize, wxSOLID))
2337 dc.SetBrush(wxBrush(self.fillColour, wxSOLID))
431f4c16 2338
1e4a197e
RD
2339 dc.DrawRectangle(5, 5, self.GetSize().width - 10,
2340 self.GetSize().height - 10)
431f4c16 2341
1e4a197e 2342 dc.EndDrawing()
431f4c16
RD
2343
2344#----------------------------------------------------------------------------
2345
2346class EditTextObjectDialog(wxDialog):
2347 """ Dialog box used to edit the properties of a text object.
2348
1e4a197e 2349 The user can edit the object's text, font, size, and text style.
431f4c16
RD
2350 """
2351
2352 def __init__(self, parent, title):
1e4a197e
RD
2353 """ Standard constructor.
2354 """
2355 wxDialog.__init__(self, parent, -1, title)
431f4c16 2356
1e4a197e
RD
2357 self.textCtrl = wxTextCtrl(self, 1001, "", style=wxTE_PROCESS_ENTER,
2358 validator=TextObjectValidator())
2359 extent = self.textCtrl.GetFullTextExtent("Hy")
2360 lineHeight = extent[1] + extent[3]
2361 self.textCtrl.SetSize(wxSize(-1, lineHeight * 4))
431f4c16 2362
1e4a197e 2363 EVT_TEXT_ENTER(self, 1001, self._doEnter)
431f4c16 2364
1e4a197e
RD
2365 fonts = wxFontEnumerator()
2366 fonts.EnumerateFacenames()
2367 self.fontList = fonts.GetFacenames()
2368 self.fontList.sort()
431f4c16 2369
1e4a197e
RD
2370 fontLabel = wxStaticText(self, -1, "Font:")
2371 self._setFontOptions(fontLabel, weight=wxBOLD)
431f4c16 2372
1e4a197e
RD
2373 self.fontCombo = wxComboBox(self, -1, "", wxDefaultPosition,
2374 wxDefaultSize, self.fontList,
2375 style = wxCB_READONLY)
2376 self.fontCombo.SetSelection(0) # Default to first available font.
431f4c16 2377
1e4a197e
RD
2378 self.sizeList = ["8", "9", "10", "12", "14", "16",
2379 "18", "20", "24", "32", "48", "72"]
431f4c16 2380
1e4a197e
RD
2381 sizeLabel = wxStaticText(self, -1, "Size:")
2382 self._setFontOptions(sizeLabel, weight=wxBOLD)
431f4c16 2383
1e4a197e
RD
2384 self.sizeCombo = wxComboBox(self, -1, "", wxDefaultPosition,
2385 wxDefaultSize, self.sizeList,
2386 style=wxCB_READONLY)
2387 self.sizeCombo.SetSelection(3) # Default to 12 point text.
431f4c16 2388
1e4a197e 2389 gap = wxLEFT | wxTOP | wxRIGHT
431f4c16 2390
1e4a197e
RD
2391 comboSizer = wxBoxSizer(wxHORIZONTAL)
2392 comboSizer.Add(fontLabel, 0, gap | wxALIGN_CENTRE_VERTICAL, 5)
2393 comboSizer.Add(self.fontCombo, 0, gap, 5)
de51258f 2394 comboSizer.Add((5, 5)) # Spacer.
1e4a197e
RD
2395 comboSizer.Add(sizeLabel, 0, gap | wxALIGN_CENTRE_VERTICAL, 5)
2396 comboSizer.Add(self.sizeCombo, 0, gap, 5)
431f4c16 2397
1e4a197e
RD
2398 self.boldCheckbox = wxCheckBox(self, -1, "Bold")
2399 self.italicCheckbox = wxCheckBox(self, -1, "Italic")
2400 self.underlineCheckbox = wxCheckBox(self, -1, "Underline")
431f4c16 2401
1e4a197e
RD
2402 self._setFontOptions(self.boldCheckbox, weight=wxBOLD)
2403 self._setFontOptions(self.italicCheckbox, style=wxITALIC)
2404 self._setFontOptions(self.underlineCheckbox, underline=True)
431f4c16 2405
1e4a197e
RD
2406 styleSizer = wxBoxSizer(wxHORIZONTAL)
2407 styleSizer.Add(self.boldCheckbox, 0, gap, 5)
2408 styleSizer.Add(self.italicCheckbox, 0, gap, 5)
2409 styleSizer.Add(self.underlineCheckbox, 0, gap, 5)
431f4c16 2410
1e4a197e
RD
2411 self.okButton = wxButton(self, wxID_OK, "OK")
2412 self.cancelButton = wxButton(self, wxID_CANCEL, "Cancel")
431f4c16 2413
1e4a197e
RD
2414 btnSizer = wxBoxSizer(wxHORIZONTAL)
2415 btnSizer.Add(self.okButton, 0, gap, 5)
2416 btnSizer.Add(self.cancelButton, 0, gap, 5)
431f4c16 2417
1e4a197e
RD
2418 sizer = wxBoxSizer(wxVERTICAL)
2419 sizer.Add(self.textCtrl, 1, gap | wxEXPAND, 5)
de51258f 2420 sizer.Add((10, 10)) # Spacer.
1e4a197e
RD
2421 sizer.Add(comboSizer, 0, gap | wxALIGN_CENTRE, 5)
2422 sizer.Add(styleSizer, 0, gap | wxALIGN_CENTRE, 5)
de51258f 2423 sizer.Add((10, 10)) # Spacer.
1e4a197e 2424 sizer.Add(btnSizer, 0, gap | wxALIGN_CENTRE, 5)
431f4c16 2425
1e4a197e
RD
2426 self.SetAutoLayout(True)
2427 self.SetSizer(sizer)
2428 sizer.Fit(self)
431f4c16 2429
1e4a197e 2430 self.textCtrl.SetFocus()
431f4c16
RD
2431
2432
2433 def objectToDialog(self, obj):
1e4a197e
RD
2434 """ Copy the properties of the given text object into the dialog box.
2435 """
2436 self.textCtrl.SetValue(obj.getText())
2437 self.textCtrl.SetSelection(0, len(obj.getText()))
431f4c16 2438
1e4a197e
RD
2439 for i in range(len(self.fontList)):
2440 if self.fontList[i] == obj.getTextFont():
2441 self.fontCombo.SetSelection(i)
2442 break
431f4c16 2443
1e4a197e
RD
2444 for i in range(len(self.sizeList)):
2445 if self.sizeList[i] == str(obj.getTextSize()):
2446 self.sizeCombo.SetSelection(i)
2447 break
431f4c16 2448
1e4a197e
RD
2449 self.boldCheckbox.SetValue(obj.getTextBoldface())
2450 self.italicCheckbox.SetValue(obj.getTextItalic())
2451 self.underlineCheckbox.SetValue(obj.getTextUnderline())
431f4c16
RD
2452
2453
2454 def dialogToObject(self, obj):
1e4a197e
RD
2455 """ Copy the properties from the dialog box into the given text object.
2456 """
2457 obj.setText(self.textCtrl.GetValue())
2458 obj.setTextFont(self.fontCombo.GetValue())
2459 obj.setTextSize(int(self.sizeCombo.GetValue()))
2460 obj.setTextBoldface(self.boldCheckbox.GetValue())
2461 obj.setTextItalic(self.italicCheckbox.GetValue())
2462 obj.setTextUnderline(self.underlineCheckbox.GetValue())
2463 obj.fitToText()
431f4c16
RD
2464
2465 # ======================
2466 # == Private Routines ==
2467 # ======================
2468
2469 def _setFontOptions(self, ctrl, family=None, pointSize=-1,
1e4a197e
RD
2470 style=wxNORMAL, weight=wxNORMAL,
2471 underline=False):
2472 """ Change the font settings for the given control.
431f4c16 2473
1e4a197e
RD
2474 The meaning of the 'family', 'pointSize', 'style', 'weight' and
2475 'underline' parameters are the same as for the wxFont constructor.
2476 If the family and/or pointSize isn't specified, the current default
2477 value is used.
2478 """
2479 if family == None: family = ctrl.GetFont().GetFamily()
2480 if pointSize == -1: pointSize = ctrl.GetFont().GetPointSize()
431f4c16 2481
1e4a197e
RD
2482 ctrl.SetFont(wxFont(pointSize, family, style, weight, underline))
2483 ctrl.SetSize(ctrl.GetBestSize()) # Adjust size to reflect font change.
431f4c16
RD
2484
2485
2486 def _doEnter(self, event):
1e4a197e 2487 """ Respond to the user hitting the ENTER key.
431f4c16 2488
1e4a197e
RD
2489 We simulate clicking on the "OK" button.
2490 """
2491 if self.Validate(): self.Show(False)
431f4c16
RD
2492
2493#----------------------------------------------------------------------------
2494
2495class TextObjectValidator(wxPyValidator):
2496 """ This validator is used to ensure that the user has entered something
1e4a197e 2497 into the text object editor dialog's text field.
431f4c16
RD
2498 """
2499 def __init__(self):
1e4a197e
RD
2500 """ Standard constructor.
2501 """
2502 wxPyValidator.__init__(self)
431f4c16
RD
2503
2504
2505 def Clone(self):
1e4a197e 2506 """ Standard cloner.
431f4c16 2507
1e4a197e
RD
2508 Note that every validator must implement the Clone() method.
2509 """
2510 return TextObjectValidator()
431f4c16
RD
2511
2512
2513 def Validate(self, win):
1e4a197e
RD
2514 """ Validate the contents of the given text control.
2515 """
de51258f 2516 textCtrl = self.GetWindow()
1e4a197e 2517 text = textCtrl.GetValue()
431f4c16 2518
1e4a197e
RD
2519 if len(text) == 0:
2520 wxMessageBox("A text object must contain some text!", "Error")
2521 return False
2522 else:
2523 return True
431f4c16
RD
2524
2525
2526 def TransferToWindow(self):
1e4a197e 2527 """ Transfer data from validator to window.
431f4c16 2528
1e4a197e
RD
2529 The default implementation returns False, indicating that an error
2530 occurred. We simply return True, as we don't do any data transfer.
2531 """
2532 return True # Prevent wxDialog from complaining.
431f4c16
RD
2533
2534
2535 def TransferFromWindow(self):
1e4a197e 2536 """ Transfer data from window to validator.
431f4c16 2537
1e4a197e
RD
2538 The default implementation returns False, indicating that an error
2539 occurred. We simply return True, as we don't do any data transfer.
2540 """
2541 return True # Prevent wxDialog from complaining.
431f4c16
RD
2542
2543#----------------------------------------------------------------------------
2544
2545class ExceptionHandler:
2546 """ A simple error-handling class to write exceptions to a text file.
2547
1e4a197e
RD
2548 Under MS Windows, the standard DOS console window doesn't scroll and
2549 closes as soon as the application exits, making it hard to find and
2550 view Python exceptions. This utility class allows you to handle Python
2551 exceptions in a more friendly manner.
431f4c16
RD
2552 """
2553
2554 def __init__(self):
1e4a197e
RD
2555 """ Standard constructor.
2556 """
2557 self._buff = ""
2558 if os.path.exists("errors.txt"):
2559 os.remove("errors.txt") # Delete previous error log, if any.
431f4c16
RD
2560
2561
2562 def write(self, s):
1e4a197e
RD
2563 """ Write the given error message to a text file.
2564
2565 Note that if the error message doesn't end in a carriage return, we
2566 have to buffer up the inputs until a carriage return is received.
2567 """
2568 if (s[-1] != "\n") and (s[-1] != "\r"):
2569 self._buff = self._buff + s
2570 return
2571
2572 try:
2573 s = self._buff + s
2574 self._buff = ""
2575
2576 if s[:9] == "Traceback":
2577 # Tell the user than an exception occurred.
2578 wxMessageBox("An internal error has occurred.\nPlease " + \
2579 "refer to the 'errors.txt' file for details.",
2580 "Error", wxOK | wxCENTRE | wxICON_EXCLAMATION)
2581
2582 f = open("errors.txt", "a")
2583 f.write(s)
2584 f.close()
2585 except:
2586 pass # Don't recursively crash on errors.
431f4c16
RD
2587
2588#----------------------------------------------------------------------------
2589
2590class SketchApp(wxApp):
2591 """ The main pySketch application object.
2592 """
2593 def OnInit(self):
1e4a197e
RD
2594 """ Initialise the application.
2595 """
431f4c16
RD
2596 wxInitAllImageHandlers()
2597
1e4a197e
RD
2598 global _docList
2599 _docList = []
2600
2601 if len(sys.argv) == 1:
2602 # No file name was specified on the command line -> start with a
2603 # blank document.
2604 frame = DrawingFrame(None, -1, "Untitled")
2605 frame.Centre()
2606 frame.Show(True)
2607 _docList.append(frame)
2608 else:
2609 # Load the file(s) specified on the command line.
2610 for arg in sys.argv[1:]:
2611 fileName = os.path.join(os.getcwd(), arg)
2612 if os.path.isfile(fileName):
2613 frame = DrawingFrame(None, -1,
2614 os.path.basename(fileName),
2615 fileName=fileName)
2616 frame.Show(True)
2617 _docList.append(frame)
2618
2619 return True
431f4c16
RD
2620
2621#----------------------------------------------------------------------------
2622
2623def main():
2624 """ Start up the pySketch application.
2625 """
2626 global _app
2627
2628 # Redirect python exceptions to a log file.
2629
2630 sys.stderr = ExceptionHandler()
2631
2632 # Create and start the pySketch application.
2633
2634 _app = SketchApp(0)
2635 _app.MainLoop()
2636
2637
2638if __name__ == "__main__":
2639 main()
2640