root/trunk/ide/ReportDesigner.py

Revision 4666, 68.4 kB (checked in by paul, 3 weeks ago)

Fixes the incompatability introduced in [4425] between ClassDesigner?'s app object
which has a Selection property and ReportDesigner?'s app object with has a
SelectedObjects? property.

If someone wants to do it, please change the name of the property in ReportDesigner?.py
to Selection everywhere it is mentioned. For now, this solves the issue.

Thanks John for reporting the problem!

  • Property svn:eol-style set to native
  • Property svn:executable set to
Line 
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import sys, os, copy
4 import dabo, dabo.ui
5 dabo.ui.loadUI("wx")
6 import dabo.dEvents as dEvents
7 from dabo.dReportWriter import dReportWriter
8 from dabo.lib.reportWriter import *
9 from dabo.dLocalize import _
10 from dabo.ui import dKeys
11 import ClassDesignerPropSheet
12
13
14 rdc = None
15
16 def DesignerController():
17     # Wrapper function to enforce singleton class instance
18     class DesignerController(dabo.dApp):
19         def initProperties(self):
20             self.BasePrefKey = "dabo.ide.reportdesigner"
21             self.setAppInfo("appName", "Dabo Report Designer")
22             self.MainFormClass = None
23
24         def beforeInit(self):
25             self._inSelection = False
26
27         def afterInit(self):
28             if sys.platform == "darwin":
29                 self.bindEvent(dEvents.KeyDown, self._onKeyDown)
30
31         def _onKeyDown(self, evt):
32             # Mac-specific behavior
33             self.ActiveEditor.onKeyDown(evt)
34
35
36         def getShortExpr(self, expr):
37             """Given an expression, return a shortened version for display in designer."""
38             if expr is None:
39                 return "None"
40             if len(expr) < 3:
41                 return expr
42            
43             def isVariable(name):
44                 for v in rdc.ReportForm["Variables"]:
45                     if v.get("Name", None) == name:
46                         return True
47                 return False
48
49             def isRecord(name):
50                 if name and rdc.ReportForm.has_key("TestCursor") \
51                         and len(rdc.ReportForm["TestCursor"]) > 0 \
52                         and rdc.ReportForm["TestCursor"][0].has_key(name):
53                     return True
54                 return False
55
56             import re
57             c = re.compile("self.(?P<type>Record|Variables)\[(?P<name>.*)\]")
58             m = c.match(expr)
59             if m:
60                 name = m.group("name")
61                 name = name[1:-1]  ## (remove outer quotes)
62             else:
63                 if "." in expr:
64                     name = expr.split(".")[-1]
65                 else:
66                     # No record or variable found: leave alone
67                     name = None
68
69             if not isVariable(name) and not isRecord(name):
70                 # This isn't a record or variable: don't shortcut the name as a
71                 # visual cue to the developer.
72                 name = None
73
74             if name is None:
75                 quotes = ('"', "'")
76                 if expr[0] in quotes and expr[-1] in quotes:
77                     # Remove outer quotes
78                     name = expr[1:-1]
79                 else:
80                     for quote in quotes:
81                         if expr.count(quote) >= 2:
82                             name = expr[expr.find(quote)+1:]
83                             name = name[:name.find(quote)]
84                             break
85             if name:
86                 expr = name
87             return expr
88
89            
90         def newObject(self, typ, mousePosition):
91             """Add a new object of the passed type to the selected band."""
92             rf = self.ReportForm
93             parents = []
94             objects = []
95
96             defaultProps = {}
97
98             if typ == Variable:
99                 parents.append(rf["Variables"])
100             elif typ == Group:
101                 parents.append(rf["Groups"])
102             else:
103                 # Normal report object. Place it in all selected bands.
104                 if isinstance(typ, basestring):
105                     if typ[:7] == "Field: ":
106                         # Testcursor field. Create string object with expr of this field.
107                         defaultProps["expr"] = "self.%s" % typ[7:].strip()
108                         typ = String
109                     elif typ[:10] == "Variable: ":
110                         # Report Variable: Create string object with expr of this variable.
111                         defaultProps["expr"] = "self.%s" % typ[10:].strip()
112                         typ = String
113
114                 ## Want to put the object where the mouse was, however there are things to
115                 ## consider such as zoom factor, and that the mouse position is absolute
116                 ## screen position. Also, we only want to do this if we were dealing with the
117                 ## context menu from the designer, as opposed to the one in the object tree.
118 #               defaultProps["x"] = "%s" % mousePosition[0]
119 #               defaultProps["y"] = "%s" % mousePosition[1]
120
121                 for selObj in self.SelectedObjects:
122                     if isinstance(selObj, Band):
123                         parents.append(selObj)
124
125             for parent in parents:         
126                 obj = parent.addObject(typ)
127                 obj.update(defaultProps.copy())
128                 objects.append(obj)
129
130             if objects:
131                 self.SelectedObjects = objects
132
133             dabo.ui.callAfter(self.ActiveEditor.Form.Raise)
134
135
136         def getContextMenu(self, mousePosition):
137             def onNewObject(evt):
138                 """Called from the context menu."""
139                 tag = evt.EventObject.Tag
140                 self.newObject(tag, mousePosition)
141
142             def onCopy(evt):
143                 self.copy()
144
145             def onPaste(evt):
146                 self.paste()
147
148             def onCut(evt):
149                 self.cut()
150
151             def onMoveToTop(evt):
152                 self.ActiveEditor.sendToBack()
153
154             def onMoveToBottom(evt):
155                 self.ActiveEditor.bringToFront()
156
157             menu = dabo.ui.dMenu()
158             newObjectMenuCreated = False
159             newVariableMenuCreated = False
160             newGroupMenuCreated = False
161             variableSelected, groupSelected = False, False
162
163             for robj in self.SelectedObjects:
164                 if isinstance(robj, Variable):
165                     variableSelected = True
166                 if isinstance(robj, Group):
167                     groupSelected = True
168                 if not newVariableMenuCreated and isinstance(robj, (Variables, Variable)):
169                     menu.append("New variable", OnHit=onNewObject, Tag=Variable)
170                     newVariableMenuCreated = True
171                 if not newGroupMenuCreated and isinstance(robj, (Groups, Group)):
172                     menu.append("New group", OnHit=onNewObject, Tag=Group)
173                     newGroupMenuCreated = True
174                 if not newObjectMenuCreated and isinstance(robj, Band):
175                     newObjectMenuCreated = True
176                     objectChoices = dabo.ui.dMenu(Caption="New object")
177                     for choice in (Image, Line, Rectangle, String):
178                         objectChoices.append(choice.__name__,
179                                 OnHit=onNewObject, Tag=choice)
180                     tc = self.ReportForm.get("TestCursor", [])
181                     var = self.ReportForm.get("Variables", [])
182                     if tc or var:
183                         objectChoices.appendSeparator()
184
185                     for typ, cap in ((tc, "Field"), (var, "Variable")):
186                         if typ:
187                             submenu = dabo.ui.dMenu(Caption=cap)
188                             fields = []
189                             if typ == tc:
190                                 if tc:
191                                     fields = tc[0].keys()
192                             elif typ == var:
193                                 for v in var:
194                                     fields.append(v["Name"])
195                             fields.sort()
196                             for field in fields:
197                                 submenu.append(field, OnHit=onNewObject,
198                                         Tag="%s: %s" % (cap, field))
199                             objectChoices.appendMenu(submenu)
200                     menu.appendMenu(objectChoices)
201
202             if len(menu.Children) > 0:
203                 menu.appendSeparator()
204
205             menu.append(_("Copy"), HotKey="Ctrl+C", OnHit=onCopy)
206             menu.append(_("Cut"), HotKey="Ctrl+X", OnHit=onCut)
207             menu.append(_("Paste"), HotKey="Ctrl+V", OnHit=onPaste)
208
209             if variableSelected or groupSelected:
210                 menu.appendSeparator()
211                 menu.append(_("Move to top"), HotKey="Ctrl+H", OnHit=onMoveToTop)
212                 menu.append(_("Move to bottom"), HotKey="Ctrl+J", OnHit=onMoveToBottom)
213
214             return menu
215
216
217         def showObjectTree(self, bringToTop=False, refresh=False):
218             ot = self.ObjectTree
219             if ot is None:
220                 refresh = True
221                 ot = self.loadObjectTree()
222                 self.refreshTree()
223                
224             ot.Form.Visible = True
225             if refresh:
226                 ot.refreshSelection()
227             if bringToTop:
228                 ot.Raise()
229
230         def hideObjectTree(self):
231             ot = self.ObjectTree
232             if ot is not None and ot.Form.Visible:
233                 ot.Form.Visible = False
234
235         def loadObjectTree(self):
236             otf = ObjectTreeForm()
237             ot = self.ObjectTree = otf.Editor
238             otf.bindEvent(dEvents.Close, self._onObjectTreeFormClose)
239             # Allow the activate to fire so that position is set:
240             otf.Visible = True
241             otf.Raise()
242             self.ActiveEditor.Form.Raise()
243             return ot
244
245
246         def showPropSheet(self, bringToTop=False, refresh=False, prop=None,
247                 enableEditor=False):
248             ps = self.PropSheet
249             if ps is None:
250                 refresh = True
251                 ps = self.loadPropSheet()
252             ps.Form.Visible = True
253
254             if refresh:
255                 ps.refreshSelection()
256
257             if prop:
258                 pg = ps.propGrid
259                 ds = pg.DataSet
260
261                 if enableEditor:
262                     # Select the value column and enable the editor for the prop. Note:
263                     # This needs to be done before changing rows, for some reason, or the
264                     # editor column isn't activated.
265                     pg.CurrentColumn = 1
266
267                 # Put the propsheet on the row for the passed prop.
268                 for idx, record in enumerate(ds):
269                     if record["prop"].lower() == prop.lower():
270                         pg.CurrentRow = idx
271                         break
272
273             if bringToTop:
274                 ps.Form.Raise()
275
276
277         def hidePropSheet(self):
278             ps = self.PropSheet
279             if ps is not None and ps.Form.Visible:
280                 ps.Form.Visible = False
281
282         def loadPropSheet(self):
283             psf = PropSheetForm()
284             ps = self.PropSheet = psf.Editor
285             psf.bindEvent(dEvents.Close, self._onPropSheetFormClose)
286             psf.Visible = True
287             psf.Raise()
288             self.ActiveEditor.Form.Raise()
289             return ps
290
291        
292         def refreshTree(self):
293             if self.ObjectTree:
294                 self.ObjectTree.refreshTree()
295                 self.ObjectTree.refreshSelection()
296
297
298         def refreshProps(self, refreshEditor=True):
299             if refreshEditor and self.ActiveEditor:
300                 self.ActiveEditor.refresh()
301             if self.PropSheet and self.PropSheet.Form.Visible:
302                 self.PropSheet.refreshSelection()
303
304            
305         def refreshSelection(self):
306             self._inSelection = True
307             for obj in (self.ActiveEditor, self.PropSheet, self.ObjectTree):
308                 if obj is not None:
309                     obj.refreshSelection()
310             self._inSelection = False
311
312
313         def isSelected(self, obj):
314             """Return True if the object is selected."""
315             for selObj in self.SelectedObjects:
316                 if id(selObj) == id(obj):
317                     return True
318             return False
319
320
321         def getNextDrawable(self, obj):
322             """Return the next drawable after the passed obj."""
323             collection = self.getParentBand(obj)["Objects"]
324             idx = collection.index(obj) + 1
325             if len(collection) <= idx:
326                 idx = 0
327             return collection[idx]
328
329
330         def getPriorDrawable(self, obj):
331             """Return the prior drawable before the passed obj."""
332             collection = self.getParentBand(obj)["Objects"]
333             idx = collection.index(obj) - 1
334             if len(collection) <= idx:
335                 idx = len(collection) - 1
336             return collection[idx]
337
338
339         def ReportObjectSelection(self):
340             import pickle
341             import wx
342             rw = self.ActiveEditor._rw
343
344             class ReportObjectSelection(wx.CustomDataObject):
345                 def __init__(self):
346                     wx.CustomDataObject.__init__(self, wx.CustomDataFormat("ReportObjectSelection"))
347                     self.setObject([])
348
349                 def setObject(self, objs):
350                     # We are receiving a sequence of selected objects. Convert to a list of
351                     # new dicts representing the object properties.
352                     copyObjs = []
353                     for obj in objs:
354                         copyObj = obj.getMemento()
355                         copyObjs.append(copyObj)
356                     self.SetData(pickle.dumps(copyObjs))
357
358                 def getObject(self):
359                     # We need to convert the representative object dicts back into report
360                     # objects
361                     copyObjs = pickle.loads(self.GetData())
362                     objs = []
363                     for copyObj in copyObjs:
364                         obj = self.getReportObjectFromMemento(copyObj)
365                         objs.append(obj)
366                     return objs
367
368                 def getReportObjectFromMemento(self, memento, parent=None):
369                     obj = rw._getReportObject(memento["type"], parent)
370                     del(memento["type"])
371                     for k, v in memento.items():
372                         if isinstance(v, dict):
373                             obj[k] = self.getReportObjectFromMemento(v, obj)
374                         elif isinstance(v, list):
375                             obj[k] = rw._getReportObject(k, obj)
376                             for c in v:
377                                 obj[k].append(self.getReportObjectFromMemento(c, obj))
378                         else:
379                             obj[k] = v
380                     return obj
381
382             return ReportObjectSelection()
383
384
385         def getSelectedBands(self):
386             """Return the list of bands that are currently selected."""
387             selBands = []
388             for selObj in self.SelectedObjects:
389                 if isinstance(selObj, Band):
390                     if selObj not in selBands:
391                         selBands.append(selObj)
392             return selBands
393
394
395         def copy(self, cut=False):
396             import wx
397             do = self.ReportObjectSelection()
398             copyObjs = [selObj for selObj in self.SelectedObjects \
399                     if not isinstance(selObj, (Report, Band, list))]
400             if not copyObjs:
401                 # don't override the current clipboard with an empty clipboard
402                 return
403             do.setObject(copyObjs)
404             if wx.TheClipboard.Open():
405                 wx.TheClipboard.SetData(do)
406                 wx.TheClipboard.Close()
407             if cut:
408                 parent = None
409                 for obj in copyObjs:
410                     parent = obj.parent
411                     if isinstance(parent, dict):
412                         for typ in ("Objects", "Variables", "Groups"):
413                             if parent.has_key(typ):
414                                 if obj in parent[typ]:
415                                     parent[typ].remove(obj)
416                     elif isinstance(parent, list):
417                         parent.remove(obj)
418                     else:
419                         print type(parent)
420                 if parent:
421                     self.SelectedObjects = [parent]
422                 else:
423                     self.SelectedObjects = []
424                 dabo.ui.callAfterInterval(self.refreshTree, 200)
425    
426         def cut(self):
427             self.copy(cut=True)
428            
429
430         def paste(self):
431             import wx
432             success = False
433             do = self.ReportObjectSelection()
434             if wx.TheClipboard.Open():
435                 success = wx.TheClipboard.GetData(do)
436                 wx.TheClipboard.Close()
437
438             if success:
439                 objs = do.getObject()
440             else:
441                 # nothing valid in the clipboard
442                 return
443
444             # Figure out the band to paste the obj(s) into:
445             selBands = self.getSelectedBands()
446             selBand = None
447
448             if len(selBands) > 0:
449                 # paste into the first selected band
450                 selBand = selBands[-1]
451             else:
452                 if len(self.SelectedObjects) > 0:
453                     # paste into the parent band of the first selected object:
454                     selBand = self.getParentBand(self.SelectedObjects[-1])
455
456            
457             if selBand is None:
458                 # Nowhere to paste to
459                 return
460
461             selectedObjects = []
462             for obj in objs:
463                 if isinstance(obj, Variable):
464                     pfObjects = self.ReportForm.setdefault("Variables", Variables(self._rw))
465                 elif isinstance(obj, Group):
466                     pfObjects = self.ReportForm.setdefault("Groups", Groups(self._rw))
467                 else:
468                     pfObjects = selBand.setdefault("Objects", [])
469                 obj.parent = selBand
470                 pfObjects.append(obj)
471                 selectedObjects.append(obj)
472
473             self.ActiveEditor.drawReportForm()
474             self.SelectedObjects = selectedObjects
475
476         def getParentBand(self, obj):
477             """Return the band that the obj is a member of."""
478             parent = obj
479             while parent is not None:
480                 if isinstance(parent, Band):
481                     return parent
482                 parent = parent.parent
483             return None
484
485         def _onObjectTreeFormClose(self, evt):
486             self.ObjectTree = None
487
488         def _onPropSheetFormClose(self, evt):
489             self.PropSheet = None
490
491
492         def _getActiveEditor(self):
493             return getattr(self, "_activeEditor", None)
494
495         def _setActiveEditor(self, val):
496             changed = (val != self.ActiveEditor)
497             if changed:
498                 self._activeEditor = val
499                 self.refreshTree()
500                 self.refreshProps()
501
502
503         def _getObjectTree(self):
504             try:
505                 val = self._objectTree
506             except AttributeError:
507                 val = self._objectTree = None
508             return val
509
510         def _setObjectTree(self, val):
511             self._objectTree = val
512
513
514         def _getPropSheet(self):
515             try:
516                 val = self._propSheet
517             except AttributeError:
518                 val = self._propSheet = None
519             return val
520
521         def _setPropSheet(self, val):
522             self._propSheet = val
523
524