AppShell.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. """
  2. AppShell provides a GUI application framework.
  3. This is an adaption of AppShell.py found in Python and Tkinter Programming
  4. by John E. Grayson which is a streamlined adaptation of GuiAppD.py, originally
  5. created by Doug Hellmann ([email protected]).
  6. """
  7. __all__ = ['AppShell']
  8. from direct.showbase.DirectObject import DirectObject
  9. from direct.showbase.TkGlobal import *
  10. from tkFileDialog import *
  11. from Tkinter import *
  12. import Pmw
  13. import Dial
  14. import Floater
  15. import Slider
  16. import EntryScale
  17. import VectorWidgets
  18. import sys, string
  19. import ProgressBar
  20. """
  21. TO FIX:
  22. Radiobutton ordering change
  23. """
  24. # Create toplevel widget dictionary
  25. try:
  26. __builtins__["widgetDict"]
  27. except KeyError:
  28. __builtins__["widgetDict"] = {}
  29. # Create toplevel variable dictionary
  30. try:
  31. __builtins__["variableDict"]
  32. except KeyError:
  33. __builtins__["variableDict"] = {}
  34. def resetWidgetDict():
  35. __builtins__["widgetDict"] = {}
  36. def resetVariableDict():
  37. __builtins__["variableDict"] = {}
  38. # Inherit from MegaWidget instead of Toplevel so you can pass in a toplevel
  39. # to use as a container if you wish. If no toplevel passed in, create one
  40. class AppShell(Pmw.MegaWidget, DirectObject):
  41. appversion = '1.0'
  42. appname = 'Generic Application Frame'
  43. copyright = ('Copyright 2004 Walt Disney Imagineering.' +
  44. ' All Rights Reserved')
  45. contactname = 'Mark R. Mine'
  46. contactphone = '(818) 544-2921'
  47. contactemail = '[email protected]'
  48. frameWidth = 450
  49. frameHeight = 320
  50. padx = 5
  51. pady = 5
  52. usecommandarea = 0
  53. usestatusarea = 0
  54. balloonState = 'none'
  55. panelCount = 0
  56. def __init__(self, parent = None, **kw):
  57. optiondefs = (
  58. ('title', self.appname, None),
  59. ('padx', 1, Pmw.INITOPT),
  60. ('pady', 1, Pmw.INITOPT),
  61. ('framewidth', self.frameWidth, Pmw.INITOPT),
  62. ('frameheight', self.frameHeight, Pmw.INITOPT),
  63. ('usecommandarea', self.usecommandarea, Pmw.INITOPT),
  64. ('usestatusarea', self.usestatusarea, Pmw.INITOPT),
  65. )
  66. self.defineoptions(kw, optiondefs)
  67. # If no toplevel passed in, create one
  68. if parent == None:
  69. self.parent = Toplevel()
  70. else:
  71. self.parent = parent
  72. # Initialize the base class
  73. Pmw.MegaWidget.__init__(self, self.parent)
  74. # Set window size
  75. self.parent.geometry('%dx%d' % (self.frameWidth, self.frameHeight))
  76. self.parent.title(self['title'])
  77. # Create unique id
  78. AppShell.panelCount += 1
  79. self.id = self.appname + '-' + repr(AppShell.panelCount)
  80. # Create a dictionary in the widgetDict to hold this panel's widgets
  81. self.widgetDict = widgetDict[self.id] = {}
  82. # And one to hold this panel's variables
  83. self.variableDict = variableDict[self.id] = {}
  84. # Get handle to the toplevels hull
  85. self._hull = self.component('hull')
  86. # Initialize the application
  87. self.appInit()
  88. # create the interface
  89. self.__createInterface()
  90. # Set focus to ourselves
  91. self.focus_set()
  92. # initialize our options
  93. self.initialiseoptions(AppShell)
  94. self.pack(fill = BOTH, expand = 1)
  95. def __createInterface(self):
  96. self.__createBalloon()
  97. self.__createMenuBar()
  98. self.__createDataArea()
  99. self.__createCommandArea()
  100. self.__createMessageBar()
  101. self.__createAboutBox()
  102. # Add binding for panel cleanup code
  103. self.interior().bind('<Destroy>', self.onDestroy)
  104. #
  105. # Create the parts of the interface
  106. # which can be modified by subclasses
  107. #
  108. self.createMenuBar()
  109. self.createInterface()
  110. def __createBalloon(self):
  111. # Create the balloon help manager for the frame.
  112. # Create the manager for the balloon help
  113. self.__balloon = self.createcomponent('balloon', (), None,
  114. Pmw.Balloon, (self._hull,))
  115. self.__balloon.configure(state = self.balloonState)
  116. def __createMenuBar(self):
  117. self.menuFrame = Frame(self._hull)
  118. self.menuBar = self.createcomponent('menubar', (), None,
  119. Pmw.MenuBar,
  120. (self.menuFrame,),
  121. hull_relief=FLAT,
  122. hull_borderwidth=0,
  123. balloon=self.balloon())
  124. self.menuBar.addmenu('Help', 'About %s' % self.appname, side = 'right')
  125. self.menuBar.addmenu('File', 'File commands and Quit')
  126. self.menuBar.pack(fill=X, side = LEFT)
  127. # Force some space between pull down menus and other widgets
  128. spacer = Label(self.menuFrame, text = ' ')
  129. spacer.pack(side = LEFT, expand = 0)
  130. self.menuFrame.pack(fill = X)
  131. def __createDataArea(self):
  132. # Create data area where data entry widgets are placed.
  133. self.dataArea = self.createcomponent('dataarea',
  134. (), None,
  135. Frame, (self._hull,),
  136. relief=GROOVE,
  137. bd=1)
  138. self.dataArea.pack(side=TOP, fill=BOTH, expand=YES,
  139. padx=self['padx'], pady=self['pady'])
  140. def __createCommandArea(self):
  141. # Create a command area for application-wide buttons.
  142. self.__commandFrame = self.createcomponent('commandframe', (), None,
  143. Frame,
  144. (self._hull,),
  145. relief=SUNKEN,
  146. bd=1)
  147. self.__buttonBox = self.createcomponent('buttonbox', (), None,
  148. Pmw.ButtonBox,
  149. (self.__commandFrame,),
  150. padx=0, pady=0)
  151. self.__buttonBox.pack(side=TOP, expand=NO, fill=X)
  152. if self['usecommandarea']:
  153. self.__commandFrame.pack(side=TOP,
  154. expand=NO,
  155. fill=X,
  156. padx=self['padx'],
  157. pady=self['pady'])
  158. def __createMessageBar(self):
  159. # Create the message bar area for help and status messages.
  160. frame = self.createcomponent('bottomtray', (), None,
  161. Frame, (self._hull,), relief=SUNKEN)
  162. self.__messageBar = self.createcomponent('messagebar',
  163. (), None,
  164. Pmw.MessageBar,
  165. (frame,),
  166. #entry_width = 40,
  167. entry_relief=SUNKEN,
  168. entry_bd=1,
  169. labelpos=None)
  170. self.__messageBar.pack(side=LEFT, expand=YES, fill=X)
  171. self.__progressBar = ProgressBar.ProgressBar(
  172. frame,
  173. fillColor='slateblue',
  174. doLabel=1,
  175. width=150)
  176. self.__progressBar.frame.pack(side=LEFT, expand=NO, fill=NONE)
  177. self.updateProgress(0)
  178. if self['usestatusarea']:
  179. frame.pack(side=BOTTOM, expand=NO, fill=X)
  180. self.__balloon.configure(statuscommand = \
  181. self.__messageBar.helpmessage)
  182. def __createAboutBox(self):
  183. Pmw.aboutversion(self.appversion)
  184. Pmw.aboutcopyright(self.copyright)
  185. Pmw.aboutcontact(
  186. 'For more information, contact:\n %s\n Phone: %s\n Email: %s' %\
  187. (self.contactname, self.contactphone,
  188. self.contactemail))
  189. self.about = Pmw.AboutDialog(self._hull,
  190. applicationname=self.appname)
  191. self.about.withdraw()
  192. def toggleBalloon(self):
  193. if self.toggleBalloonVar.get():
  194. self.__balloon.configure(state = 'both')
  195. else:
  196. self.__balloon.configure(state = 'status')
  197. def showAbout(self):
  198. # Create the dialog to display about and contact information.
  199. self.about.show()
  200. self.about.focus_set()
  201. def quit(self):
  202. self.parent.destroy()
  203. ### USER METHODS ###
  204. # To be overridden
  205. def appInit(self):
  206. # Called before interface is created (should be overridden).
  207. pass
  208. def createInterface(self):
  209. # Override this method to create the interface for the app.
  210. pass
  211. def onDestroy(self, event):
  212. # Override this method with actions to be performed on panel shutdown
  213. pass
  214. def createMenuBar(self):
  215. # Creates default menus. Can be overridden or simply augmented
  216. # Using button Add below
  217. self.menuBar.addmenuitem('Help', 'command',
  218. 'Get information on application',
  219. label='About...', command=self.showAbout)
  220. self.toggleBalloonVar = IntVar()
  221. if self.balloonState == 'none':
  222. self.toggleBalloonVar.set(0)
  223. else:
  224. self.toggleBalloonVar.set(1)
  225. self.menuBar.addmenuitem('Help', 'checkbutton',
  226. 'Toggle balloon help',
  227. label='Balloon help',
  228. variable = self.toggleBalloonVar,
  229. command=self.toggleBalloon)
  230. self.menuBar.addmenuitem('File', 'command', 'Quit this application',
  231. label='Quit',
  232. command=self.quit)
  233. # Getters
  234. def interior(self):
  235. # Retrieve the interior site where widgets should go.
  236. return self.dataArea
  237. def balloon(self):
  238. # Retrieve the panel's balloon widget
  239. return self.__balloon
  240. def buttonBox(self):
  241. # Retrieve the button box.
  242. return self.__buttonBox
  243. def messageBar(self):
  244. # Retieve the message bar
  245. return self.__messageBar
  246. # Utility functions
  247. def buttonAdd(self, buttonName, helpMessage=None,
  248. statusMessage=None, **kw):
  249. # Add a button to the button box.
  250. newBtn = self.__buttonBox.add(buttonName)
  251. newBtn.configure(kw)
  252. if helpMessage:
  253. self.bind(newBtn, helpMessage, statusMessage)
  254. return newBtn
  255. def alignbuttons(self):
  256. """ Make all buttons wide as widest """
  257. self.__buttonBox.alignbuttons()
  258. def bind(self, child, balloonHelpMsg, statusHelpMsg=None):
  259. # Bind a help message and/or status message to a widget.
  260. self.__balloon.bind(child, balloonHelpMsg, statusHelpMsg)
  261. def updateProgress(self, newValue=0, newMax=0):
  262. # Used to update progress bar
  263. self.__progressBar.updateProgress(newValue, newMax)
  264. ## WIDGET UTILITY FUNCTIONS ##
  265. def addWidget(self, category, text, widget):
  266. self.widgetDict[category + '-' + text] = widget
  267. def getWidget(self, category, text):
  268. return self.widgetDict.get(category + '-' + text, None)
  269. def addVariable(self, category, text, variable):
  270. self.variableDict[category + '-' + text] = variable
  271. def getVariable(self, category, text):
  272. return self.variableDict.get(category + '-' + text, None)
  273. def createWidget(self, parent, category, text, widgetClass,
  274. help, command, side, fill, expand, kw):
  275. # Update kw to reflect user inputs
  276. kw['text'] = text
  277. # Create widget
  278. widget = apply(widgetClass, (parent,), kw)
  279. # Do this after so command isn't called on widget creation
  280. widget['command'] = command
  281. # Pack widget
  282. widget.pack(side = side, fill = fill, expand = expand)
  283. # Bind help
  284. self.bind(widget, help)
  285. # Record widget
  286. self.addWidget(category, text, widget)
  287. return widget
  288. def newCreateLabeledEntry(self, parent, category, text, help = '',
  289. command = None, value = '',
  290. width = 12, relief = SUNKEN,
  291. side = LEFT, fill = X, expand = 0):
  292. """ createLabeledEntry(parent, category, text, [options]) """
  293. # Create labeled entry
  294. frame = Frame(parent)
  295. variable = StringVar()
  296. variable.set(value)
  297. label = Label(frame, text = text)
  298. label.pack(side = LEFT, fill = X, expand = 0)
  299. entry = Entry(frame, width = width, relief = relief,
  300. textvariable = variable)
  301. entry.pack(side = LEFT, fill = X, expand = 1)
  302. frame.pack(side = side, fill = X, expand = expand)
  303. if command:
  304. entry.bind('<Return>', command)
  305. # Add balloon help
  306. self.bind(label, help)
  307. self.bind(entry, help)
  308. # Record widgets and variable
  309. self.addWidget(category, text, entry)
  310. self.addWidget(category, text + '-Label', label)
  311. self.addVariable(category, text, variable)
  312. return entry
  313. def newCreateButton(self, parent, category, text,
  314. help = '', command = None,
  315. side = LEFT, fill = X, expand = 0, **kw):
  316. """ createButton(parent, category, text, [options]) """
  317. # Create the widget
  318. widget = self.createWidget(parent, category, text, Button,
  319. help, command, side, fill, expand, kw)
  320. return widget
  321. def newCreateCheckbutton(self, parent, category, text,
  322. help = '', command = None,
  323. initialState = 0, anchor = W,
  324. side = LEFT, fill = X, expand = 0, **kw):
  325. """ createCheckbutton(parent, category, text, [options]) """
  326. # Create the widget
  327. widget = self.createWidget(parent, category, text, Checkbutton,
  328. help, command, side, fill, expand, kw)
  329. # Perform extra customization
  330. widget['anchor'] = anchor
  331. variable = BooleanVar()
  332. variable.set(initialState)
  333. self.addVariable(category, text, variable)
  334. widget['variable'] = variable
  335. return widget
  336. def newCreateRadiobutton(self, parent, category, text, variable, value,
  337. command = None, help = '', anchor = W,
  338. side = LEFT, fill = X, expand = 0, **kw):
  339. """
  340. createRadiobutton(parent, category, text, variable, value, [options])
  341. """
  342. # Create the widget
  343. widget = self.createWidget(parent, category, text, Radiobutton,
  344. help, command, side, fill, expand, kw)
  345. # Perform extra customization
  346. widget['anchor'] = anchor
  347. widget['value'] = value
  348. widget['variable'] = variable
  349. return widget
  350. def newCreateFloater(self, parent, category, text,
  351. help = '', command = None,
  352. side = LEFT, fill = X, expand = 0, **kw):
  353. # Create the widget
  354. widget = self.createWidget(parent, category, text,
  355. Floater.Floater,
  356. help, command, side, fill, expand, kw)
  357. return widget
  358. def newCreateDial(self, parent, category, text,
  359. help = '', command = None,
  360. side = LEFT, fill = X, expand = 0, **kw):
  361. # Create the widget
  362. widget = self.createWidget(parent, category, text,
  363. Dial.Dial,
  364. help, command, side, fill, expand, kw)
  365. return widget
  366. def newCreateSider(self, parent, category, text,
  367. help = '', command = None,
  368. side = LEFT, fill = X, expand = 0, **kw):
  369. # Create the widget
  370. widget = self.createWidget(parent, category, text,
  371. Slider.Slider,
  372. help, command, side, fill, expand, kw)
  373. return widget
  374. def newCreateEntryScale(self, parent, category, text,
  375. help = '', command = None,
  376. side = LEFT, fill = X, expand = 0, **kw):
  377. # Create the widget
  378. widget = self.createWidget(parent, category, text,
  379. EntryScale.EntryScale,
  380. help, command, side, fill, expand, kw)
  381. return widget
  382. def newCreateVector2Entry(self, parent, category, text,
  383. help = '', command = None,
  384. side = LEFT, fill = X, expand = 0, **kw):
  385. # Create the widget
  386. widget = self.createWidget(parent, category, text,
  387. VectorWidgets.Vector2Entry,
  388. help, command, side, fill, expand, kw)
  389. def newCreateVector3Entry(self, parent, category, text,
  390. help = '', command = None,
  391. side = LEFT, fill = X, expand = 0, **kw):
  392. # Create the widget
  393. widget = self.createWidget(parent, category, text,
  394. VectorWidgets.Vector3Entry,
  395. help, command, side, fill, expand, kw)
  396. return widget
  397. def newCreateColorEntry(self, parent, category, text,
  398. help = '', command = None,
  399. side = LEFT, fill = X, expand = 0, **kw):
  400. # Create the widget
  401. widget = self.createWidget(parent, category, text,
  402. VectorWidgets.ColorEntry,
  403. help, command, side, fill, expand, kw)
  404. return widget
  405. def newCreateOptionMenu(self, parent, category, text,
  406. help = '', command = None, items = [],
  407. labelpos = W, label_anchor = W,
  408. label_width = 16, menu_tearoff = 1,
  409. side = LEFT, fill = X, expand = 0, **kw):
  410. # Create variable
  411. variable = StringVar()
  412. if len(items) > 0:
  413. variable.set(items[0])
  414. # Update kw to reflect user inputs
  415. kw['items'] = items
  416. kw['label_text'] = text
  417. kw['labelpos'] = labelpos
  418. kw['label_anchor'] = label_anchor
  419. kw['label_width'] = label_width
  420. kw['menu_tearoff'] = menu_tearoff
  421. kw['menubutton_textvariable'] = variable
  422. # Create widget
  423. widget = apply(Pmw.OptionMenu, (parent,), kw)
  424. # Do this after so command isn't called on widget creation
  425. widget['command'] = command
  426. # Pack widget
  427. widget.pack(side = side, fill = fill, expand = expand)
  428. # Bind help
  429. self.bind(widget.component('menubutton'), help)
  430. # Record widget and variable
  431. self.addWidget(category, text, widget)
  432. self.addVariable(category, text, variable)
  433. return widget
  434. def newCreateComboBox(self, parent, category, text,
  435. help = '', command = None,
  436. items = [], state = DISABLED, history = 0,
  437. labelpos = W, label_anchor = W,
  438. label_width = 16, entry_width = 16,
  439. side = LEFT, fill = X, expand = 0, **kw):
  440. # Update kw to reflect user inputs
  441. kw['label_text'] = text
  442. kw['labelpos'] = labelpos
  443. kw['label_anchor'] = label_anchor
  444. kw['label_width'] = label_width
  445. kw['entry_width'] = entry_width
  446. kw['scrolledlist_items'] = items
  447. kw['entryfield_entry_state'] = state
  448. # Create widget
  449. widget = apply(Pmw.ComboBox, (parent,), kw)
  450. # Bind selection command
  451. widget['selectioncommand'] = command
  452. # Select first item if it exists
  453. if len(items) > 0:
  454. widget.selectitem(items[0])
  455. # Pack widget
  456. widget.pack(side = side, fill = fill, expand = expand)
  457. # Bind help
  458. self.bind(widget, help)
  459. # Record widget
  460. self.addWidget(category, text, widget)
  461. return widget
  462. def transformRGB(self, rgb, max = 1.0):
  463. retval = '#'
  464. for v in [rgb[0], rgb[1], rgb[2]]:
  465. v = (v/max)*255
  466. if v > 255:
  467. v = 255
  468. if v < 0:
  469. v = 0
  470. retval = "%s%02x" % (retval, int(v))
  471. return retval
  472. class TestAppShell(AppShell):
  473. # Override class variables here
  474. appname = 'Test Application Shell'
  475. usecommandarea = 1
  476. usestatusarea = 1
  477. def __init__(self, parent = None, **kw):
  478. # Call superclass initialization function
  479. AppShell.__init__(self)
  480. self.initialiseoptions(TestAppShell)
  481. def createButtons(self):
  482. self.buttonAdd('Ok',
  483. helpMessage='Exit',
  484. statusMessage='Exit',
  485. command=self.quit)
  486. def createMain(self):
  487. self.label = self.createcomponent('label', (), None,
  488. Label,
  489. (self.interior(),),
  490. text='Data Area')
  491. self.label.pack()
  492. self.bind(self.label, 'Space taker')
  493. def createInterface(self):
  494. self.createButtons()
  495. self.createMain()
  496. if __name__ == '__main__':
  497. test = TestAppShell(balloon_state='none')