Slider.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. """
  2. Slider Class: Velocity style controller for floating point values with
  3. a label, entry (validated), and min/max slider
  4. """
  5. from Tkinter import *
  6. from Valuator import *
  7. import Pmw
  8. import Task
  9. import math
  10. import string
  11. import operator
  12. from PandaModules import ClockObject
  13. SLIDER_FULL = 'full'
  14. SLIDER_MINI = 'mini'
  15. SLIDER_FULL_WIDTH = 50
  16. SLIDER_FULL_HEIGHT = 25
  17. SLIDER_MINI_WIDTH = 16
  18. SLIDER_MINI_HEIGHT = 16
  19. globalClock = ClockObject.getGlobalClock()
  20. class Slider(Valuator):
  21. """
  22. Valuator widget which includes an min/max slider and an entry for setting
  23. floating point values in a range
  24. """
  25. def __init__(self, parent = None, **kw):
  26. INITOPT = Pmw.INITOPT
  27. optiondefs = (
  28. ('style', SLIDER_FULL, INITOPT),
  29. )
  30. self.defineoptions(kw, optiondefs)
  31. Valuator.__init__(self, parent)
  32. self.initialiseoptions(Slider)
  33. def createValuator(self):
  34. self._valuator = self.createcomponent(
  35. 'valuator',
  36. (('slider', 'valuator'),),
  37. None,
  38. SliderWidget,
  39. (self.interior(),),
  40. style = self['style'],
  41. command = self.setEntry,
  42. value = self['value'])
  43. self._valuator._canvas.bind('<Double-ButtonPress-1>', self.mouseReset)
  44. def packValuator(self):
  45. if self['style'] == SLIDER_FULL:
  46. if self._label:
  47. self._label.grid(row = 0, col = 0, sticky = EW)
  48. self._entry.grid(row = 0, col = 1, sticky = EW)
  49. self._valuator.grid(row = 1, columnspan = 2,
  50. padx = 2, pady = 2)
  51. self.interior().columnconfigure(0, weight = 1)
  52. else:
  53. if self._label:
  54. self._label.grid(row=0,col=0, sticky = EW)
  55. self._entry.grid(row=0,col=1, sticky = EW)
  56. self._valuator.grid(row=0,col=2, padx = 2, pady = 2)
  57. self.interior().columnconfigure(0, weight = 1)
  58. class SliderWidget(Pmw.MegaWidget):
  59. def __init__(self, parent = None, **kw):
  60. #define the megawidget options
  61. INITOPT = Pmw.INITOPT
  62. optiondefs = (
  63. # Appearance
  64. ('style', SLIDER_MINI, INITOPT),
  65. ('width', SLIDER_MINI_WIDTH, INITOPT),
  66. ('height', SLIDER_MINI_HEIGHT, INITOPT),
  67. ('relief', SUNKEN, self.setRelief),
  68. ('borderwidth', 2, self.setBorderwidth),
  69. ('background', 'white', self.setBackground),
  70. # Behavior
  71. # Initial value of slider, use self.set to change value
  72. ('value', 0.0, INITOPT),
  73. ('numDigits', 2, self.setNumDigits),
  74. # Command to execute on slider updates
  75. ('command', None, None),
  76. # Extra data to be passed to command function
  77. ('commandData', [], None),
  78. # Callback's to execute during mouse interaction
  79. ('preCallback', None, None),
  80. ('postCallback', None, None),
  81. # Extra data to be passed to callback function, needs to be a list
  82. ('callbackData', [], None),
  83. )
  84. self.defineoptions(kw, optiondefs)
  85. # Initialize the superclass
  86. Pmw.MegaWidget.__init__(self, parent)
  87. # Set up some local and instance variables
  88. # Create the components
  89. interior = self.interior()
  90. # Current value
  91. self.value = self['value']
  92. # Base slider size on style, if size not specified,
  93. if not self['width']:
  94. if self['style'] == SLIDER_FULL_SIZE:
  95. width = SLIDER_FULL_WIDTH
  96. else:
  97. width = SLIDER_MINI_WIDTH
  98. else:
  99. width = self['width']
  100. if not self['height']:
  101. if self['style'] == SLIDER_FULL_SIZE:
  102. height = SLIDER_FULL_HEIGHT
  103. else:
  104. height = SLIDER_MINI_HEIGHT
  105. else:
  106. height = self['height']
  107. halfWidth = width/2.0
  108. halfHeight = height/2.0
  109. left = -(halfWidth - 2)
  110. right = halfWidth - 2
  111. top = -(halfHeight - 2)
  112. bottom = halfHeight - 2
  113. # The canvas
  114. self._canvas = self.createcomponent('canvas', (), None,
  115. Canvas, (interior,),
  116. width = width,
  117. height = height,
  118. background = self['background'],
  119. highlightthickness = 0,
  120. scrollregion = (-halfWidth,
  121. -halfHeight,
  122. halfWidth,
  123. halfHeight))
  124. self._canvas.pack(expand = 1, fill = BOTH)
  125. self._canvas.create_polygon(left,top,
  126. 0, bottom,
  127. right, top,
  128. fill = '#A0A0A0',
  129. tags = ('slider',))
  130. # The indicator
  131. self._canvas.create_line(left, bottom,
  132. right, bottom,
  133. width = 2)
  134. # Add event bindings
  135. self._canvas.bind('<ButtonPress-1>', self.mouseDown)
  136. self._canvas.bind('<B1-Motion>', self.updateSliderSF)
  137. self._canvas.bind('<ButtonRelease-1>', self.mouseUp)
  138. self._canvas.bind('<Enter>', self.highlightWidget)
  139. self._canvas.bind('<Leave>', self.restoreWidget)
  140. # Make sure input variables processed
  141. self.initialiseoptions(SliderWidget)
  142. def set(self, value, fCommand = 1):
  143. """
  144. self.set(value, fCommand = 1)
  145. Set slider to new value, execute command if fCommand == 1
  146. """
  147. # Send command if any
  148. if fCommand and (self['command'] != None):
  149. apply(self['command'], [value] + self['commandData'])
  150. # Record value
  151. self.value = value
  152. def updateIndicator(self, value):
  153. # Nothing visible to update on this type of widget
  154. pass
  155. def get(self):
  156. """
  157. self.get()
  158. Get current slider value
  159. """
  160. return self.value
  161. ## Canvas callback functions
  162. # Slider velocity controller
  163. def mouseDown(self,event):
  164. """ Begin mouse interaction """
  165. # Exectute user redefinable callback function (if any)
  166. if self['preCallback']:
  167. apply(self['preCallback'], self['callbackData'])
  168. self.velocitySF = 0.0
  169. self.updateTask = taskMgr.add(self.updateSliderTask,
  170. 'updateSlider')
  171. self.updateTask.lastTime = globalClock.getFrameTime()
  172. def updateSliderTask(self, state):
  173. """
  174. Update sliderWidget value based on current scaleFactor
  175. Adjust for time to compensate for fluctuating frame rates
  176. """
  177. currT = globalClock.getFrameTime()
  178. dt = currT - state.lastTime
  179. self.set(self.value + self.velocitySF * dt)
  180. state.lastTime = currT
  181. return Task.cont
  182. def updateSliderSF(self, event):
  183. """
  184. Update velocity scale factor based of mouse distance from origin
  185. """
  186. x = self._canvas.canvasx(event.x)
  187. y = self._canvas.canvasy(event.y)
  188. offset = max(0, abs(x) - Valuator.deadband)
  189. if offset == 0:
  190. return 0
  191. sf = math.pow(Valuator.sfBase,
  192. self.minExp + offset/Valuator.sfDist)
  193. if x > 0:
  194. self.velocitySF = sf
  195. else:
  196. self.velocitySF = -sf
  197. def mouseUp(self, event):
  198. taskMgr.remove(self.updateTask)
  199. self.velocitySF = 0.0
  200. # Execute user redefinable callback function (if any)
  201. if self['postCallback']:
  202. apply(self['postCallback'], self['callbackData'])
  203. def setNumDigits(self):
  204. """
  205. Adjust minimum exponent to use in velocity task based
  206. upon the number of digits to be displayed in the result
  207. """
  208. self.minExp = math.floor(-self['numDigits']/
  209. math.log10(Valuator.sfBase))
  210. # Methods to modify slider characteristics
  211. def setRelief(self):
  212. self.interior()['relief'] = self['relief']
  213. def setBorderwidth(self):
  214. self.interior()['borderwidth'] = self['borderwidth']
  215. def setBackground(self):
  216. self._canvas['background'] = self['background']
  217. def highlightWidget(self, event):
  218. self._canvas.itemconfigure('slider', fill = 'black')
  219. def restoreWidget(self, event):
  220. self._canvas.itemconfigure('slider', fill = '#A0A0A0')
  221. if __name__ == '__main__':
  222. tl = Toplevel()
  223. d = Slider(tl)
  224. d2 = Slider(tl, slider_numSegments = 12, max = 360,
  225. slider_fRollover = 0, value = 180)
  226. d3 = Slider(tl, slider_numSegments = 12, max = 90, min = -90,
  227. slider_fRollover = 0)
  228. d4 = Slider(tl, slider_numSegments = 16, max = 256,
  229. slider_fRollover = 0)
  230. d.pack(expand = 1, fill = X)
  231. d2.pack(expand = 1, fill = X)
  232. d3.pack(expand = 1, fill = X)
  233. d4.pack(expand = 1, fill = X)
  234. class PopupSliderWidget(Pmw.MegaToplevel):
  235. def __init__(self, parent = None, **kw):
  236. optiondefs = (
  237. ('width', 150, None),
  238. ('height', 25, None),
  239. ('xoffset', 0, None), # pixels
  240. ('yoffset', 1, None), # pixels
  241. )
  242. self.defineoptions(kw, optiondefs)
  243. Pmw.MegaToplevel.__init__(self, parent)
  244. interior = self.interior()
  245. self.withdraw()
  246. self.overrideredirect(1)
  247. interior['relief'] = RAISED
  248. interior['borderwidth'] = 2
  249. left = -self['width']/2.0
  250. right = self['width']/2.0
  251. top = -10
  252. bottom = top + self['height']
  253. self._canvas = self.createcomponent('canvas', (), None,
  254. Canvas, (interior,),
  255. relief = FLAT,
  256. width = self['width'],
  257. height = self['height'],
  258. highlightthickness = 0,
  259. scrollregion = (left, top,
  260. right, bottom)
  261. )
  262. self._canvas.pack(expand = 1, fill = BOTH)
  263. self.marker = self._canvas.create_polygon(-6.9 + 1,12,
  264. 6.9+1,12,
  265. 1, 0,
  266. fill = 'black',
  267. tags = ('slider',))
  268. self._canvas.create_polygon(-5.75,10,
  269. 5.75,10,
  270. 0, 0,
  271. fill = 'grey85',
  272. outline = 'black',
  273. tags = ('slider',))
  274. # The indicator
  275. self.lineLeft = lineLeft = left + 10
  276. self.lineRight = lineRight = right -10
  277. self._canvas.create_line(lineLeft, 1,
  278. lineRight, 1,
  279. width = 2)
  280. self.b = Button(interior, text = 'hello')
  281. self.b['command'] = self.withdraw
  282. self.b.pack()
  283. self.initialiseoptions(PopupSliderWidget)
  284. def showPopup(self, widget):
  285. x = widget.winfo_rootx() + widget.winfo_width() - self['width']
  286. y = widget.winfo_rooty() + widget.winfo_height()
  287. Pmw.setgeometryanddeiconify(self, '%dx%d+%d+%d' %
  288. (self['width'], self['height'],x,y))
  289. """
  290. pw = PopupSliderWidget()
  291. tl = Toplevel()
  292. b = Button(tl, text = 'V')
  293. b.pack()
  294. fCrossedLine = 0
  295. def move(event):
  296. global fCrossedLine
  297. if fCrossedLine:
  298. newX = pw._canvas.canvasx(event.x_root) - pw.winfo_rootx()
  299. if newX < pw.lineLeft:
  300. newX = pw.lineLeft
  301. elif newX > pw.lineRight:
  302. newX = pw.lineRight
  303. print (newX - pw.lineLeft)/(pw.lineRight - pw.lineLeft)
  304. startX = getX()
  305. dx = newX - startX
  306. pw._canvas.move('slider', dx, 0)
  307. else:
  308. if event.y_root >= pw.winfo_rooty() + 10:
  309. fCrossedLine = 1
  310. def getX():
  311. c = pw._canvas.coords(pw.marker)
  312. return c[4]
  313. def press(event):
  314. global fCrossedLine
  315. print 'press'
  316. fCrossedLine = 0
  317. pw.showPopup(b)
  318. b.bind('<Motion>', move)
  319. def unpostCanvas(event):
  320. print 'unpostCanvas', event.x_root, pw._canvas.winfo_rootx()
  321. if event.x_root < pw._canvas.winfo_rootx():
  322. print 'blah'
  323. Pmw.popgrab(pw._canvas)
  324. pw._canvas.withdraw()
  325. def popupPress(event):
  326. global fCrossedLine
  327. print 'popupPress'
  328. fCrossedLine = 1
  329. pw._canvas.bind('<Motion>', move)
  330. def release(event):
  331. if fCrossedLine:
  332. pw.withdraw()
  333. b.unbind('<Motion>')
  334. pw._canvas.unbind('<Motion>')
  335. else:
  336. Pmw.pushgrab(pw._canvas, 1, unpostCanvas)
  337. pw._canvas.focus_set()
  338. b.bind('<ButtonPress-1>', press)
  339. b.bind('<ButtonRelease-1>', release)
  340. pw._canvas.bind('<ButtonPress-1>', popupPress)
  341. pw._canvas.bind('<ButtonRelease-1>', release)
  342. """
  343. # Based on Pmw ComboBox code.
  344. class PopupSlider(Pmw.MegaWidget):
  345. def __init__(self, parent = None, **kw):
  346. # Define the megawidget options.
  347. INITOPT = Pmw.INITOPT
  348. optiondefs = (
  349. ('dropdown', 1, INITOPT),
  350. ('buttonaspect', 1.0, INITOPT),
  351. ('fliparrow', 0, INITOPT),
  352. ('labelmargin', 0, INITOPT),
  353. ('labelpos', None, INITOPT),
  354. # Behavior
  355. # Initial value of slider, use self.set to change value
  356. ('value', 0.0, INITOPT),
  357. ('numDigits', 2, self._setNumDigits),
  358. # Command to execute on slider updates
  359. ('command', None, None),
  360. # Extra data to be passed to command function
  361. ('commandData', [], None),
  362. # Callback's to execute during mouse interaction
  363. ('preCallback', None, None),
  364. ('postCallback', None, None),
  365. # Extra data to be passed to callback function, needs to be a list
  366. ('callbackData', [], None),
  367. )
  368. self.defineoptions(kw, optiondefs)
  369. # Initialise the base class (after defining the options).
  370. Pmw.MegaWidget.__init__(self, parent)
  371. # Create the components.
  372. interior = self.interior()
  373. # Current value
  374. self.value = self['value']
  375. # Interaction flags
  376. self._fUpdate = 0
  377. self._fUnpost = 0
  378. self._firstPress = 1
  379. self._fPressInsde = 0
  380. self._entryfield = self.createcomponent('entryfield',
  381. (('entry', 'entryfield_entry'),), None,
  382. Pmw.EntryField, (interior,))
  383. self._entryfield.grid(column=2, row=2, sticky='nsew')
  384. interior.grid_columnconfigure(2, weight = 1)
  385. self._entryWidget = self._entryfield.component('entry')
  386. # Slider dimensions
  387. width = 100
  388. xPad = 10
  389. canvasWidth = width + 2 * xPad
  390. height = 20
  391. self.left = left = -(width/2.0)
  392. self.right = right = (width/2.0)
  393. top = -5
  394. bottom = top + height
  395. # Create slider
  396. if self['dropdown']:
  397. self._isPosted = 0
  398. interior.grid_rowconfigure(2, weight = 1)
  399. # Create the arrow button.
  400. self._arrowBtn = self.createcomponent('arrowbutton',
  401. (), None,
  402. Canvas, (interior,), borderwidth = 2,
  403. relief = 'raised',
  404. width = 16, height = 16)
  405. self._arrowBtn.grid(column=3, row=2)
  406. self._arrowRelief = self._arrowBtn.cget('relief')
  407. # Create the label.
  408. self.createlabel(interior, childCols=2)
  409. # Create the dropdown window.
  410. self._popup = self.createcomponent(
  411. 'popup',
  412. (), None,
  413. Toplevel, (interior,),
  414. relief = RAISED, borderwidth = 2)
  415. self._popup.withdraw()
  416. self._popup.overrideredirect(1)
  417. # Create the canvas inside the dropdown window.
  418. # Min label
  419. self._minLabel = Label(self._popup, text = 'MINAAAAAA')
  420. self._minLabel.pack(side = LEFT)
  421. # Slider
  422. self._canvas = self.createcomponent(
  423. 'canvas', (), None,
  424. Canvas, (self._popup,),
  425. width = canvasWidth,
  426. height = height,
  427. bd = 3,
  428. highlightthickness = 0,
  429. scrollregion = (left - xPad, top, right + xPad, bottom))
  430. self._canvas.pack(side = LEFT, expand=1, fill='both')
  431. # Max label
  432. self._maxLabel = Label(self._popup, text = 'MAX')
  433. self._maxLabel.pack(side = LEFT)
  434. # Bind events to the arrow button.
  435. self._arrowBtn.bind('<1>', self._postCanvas)
  436. self._arrowBtn.bind('<Configure>', self._drawArrow)
  437. # Bind events to the dropdown window.
  438. self._popup.bind('<Escape>', self._unpostCanvas)
  439. self._popup.bind('<ButtonRelease-1>', self._dropdownBtnRelease)
  440. self._popup.bind('<ButtonPress-1>', self._dropdownBtnPress)
  441. self._popup.bind('<Motion>', self._dropdownMove)
  442. # Bind events to the Tk listbox.
  443. #self._canvas.bind('<Enter>', self._unpostOnNextRelease)
  444. # Bind events to the Tk entry widget.
  445. self._entryWidget.bind('<Configure>', self._resizeArrow)
  446. # Need to unpost the popup if the entryfield is unmapped (eg:
  447. # its toplevel window is withdrawn) while the popup canvas is
  448. # displayed.
  449. self._entryWidget.bind('<Unmap>', self._unpostCanvas)
  450. else:
  451. # Create the slider below the entry field.
  452. self._canvas = self.createcomponent(
  453. 'canvas', (), None,
  454. Canvas, (interior,),
  455. width = canvasWidth,
  456. height = height,
  457. highlightthickness = 0,
  458. scrollregion = (left - xPad, top, right + xPad, bottom))
  459. self._canvas.grid(column=2, row=3, sticky='nsew')
  460. # The scrolled canvas should expand vertically.
  461. interior.grid_rowconfigure(3, weight = 1)
  462. # Create the label.
  463. self.createlabel(interior, childRows=2)
  464. # Interaction marker
  465. xShift = 1
  466. # Shadow arrow
  467. self._marker = self._canvas.create_polygon(-7 + xShift, 12,
  468. 7 + xShift, 12,
  469. xShift, 0,
  470. fill = 'black',
  471. tags = ('slider',))
  472. # Arrow
  473. self._canvas.create_polygon(-6.0, 10,
  474. 6.0, 10,
  475. 0, 0,
  476. fill = 'grey85',
  477. outline = 'black',
  478. tags = ('slider',))
  479. # The indicator
  480. self._canvas.create_line(left, 0,
  481. right, 0,
  482. width = 2)
  483. # Check keywords and initialise options.
  484. self.initialiseoptions(PopupSlider)
  485. def destroy(self):
  486. if self['dropdown'] and self._isPosted:
  487. Pmw.popgrab(self._popup)
  488. Pmw.MegaWidget.destroy(self)
  489. #======================================================================
  490. # Public methods
  491. def set(self, value, fCommand = 1):
  492. """
  493. self.set(value, fCommand = 1)
  494. Set slider to new value, execute command if fCommand == 1
  495. """
  496. # Send command if any
  497. if fCommand and (self['command'] != None):
  498. apply(self['command'], [value] + self['commandData'])
  499. # Record value
  500. self.value = value
  501. def updateIndicator(self, value):
  502. # Nothing visible to update on this type of widget
  503. pass
  504. def get(self):
  505. """
  506. self.get()
  507. Get current slider value
  508. """
  509. return self.value
  510. #======================================================================
  511. # Private methods for dropdown canvas.
  512. def _setNumDigits(self):
  513. pass
  514. def _drawArrow(self, event=None, sunken=0):
  515. arrow = self._arrowBtn
  516. if sunken:
  517. self._arrowRelief = arrow.cget('relief')
  518. arrow.configure(relief = 'sunken')
  519. else:
  520. arrow.configure(relief = self._arrowRelief)
  521. if self._isPosted and self['fliparrow']:
  522. direction = 'up'
  523. else:
  524. direction = 'down'
  525. Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
  526. def _postCanvas(self, event = None):
  527. self._isPosted = 1
  528. self._fUpdate = 0
  529. self._drawArrow(sunken=1)
  530. # Make sure that the arrow is displayed sunken.
  531. self.update_idletasks()
  532. x = self._entryfield.winfo_rootx()
  533. y = self._entryfield.winfo_rooty() + self._entryfield.winfo_height()
  534. w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width()
  535. minW = self._minLabel.winfo_width()
  536. cw = self._canvas.winfo_width()
  537. maxW = self._maxLabel.winfo_width()
  538. pw = minW + cw + maxW
  539. ch = self._canvas.winfo_height()
  540. sh = self.winfo_screenheight()
  541. # Compensate if too close to edge of screen
  542. if y + ch > sh and y > sh / 2:
  543. y = self._entryfield.winfo_rooty() - ch
  544. Pmw.setgeometryanddeiconify(self._popup, '+%d+%d' % (x + w - pw, y))
  545. # Grab the popup, so that all events are delivered to it, and
  546. # set focus to the canvas, to make keyboard navigation
  547. # easier.
  548. Pmw.pushgrab(self._popup, 1, self._unpostCanvas)
  549. self._canvas.focus_set()
  550. self._drawArrow()
  551. # Ignore the first release of the mouse button after posting the
  552. # dropdown canvas, unless the mouse enters the dropdown canvas.
  553. self._fUpdate = 0
  554. self._fUnpost = 0
  555. self._firstPress = 1
  556. self._fPressInsde = 0
  557. def _updateValue(self,event):
  558. canvasX = self._canvas.canvasx(
  559. event.x_root - self._canvas.winfo_rootx())
  560. if canvasX < self.left:
  561. canvasX = self.left
  562. if canvasX > self.right:
  563. canvasX = self.right
  564. # Get current marker position
  565. currX = self._getMarkerX()
  566. dx = canvasX - currX
  567. self._canvas.move('slider', dx, 0)
  568. def _dropdownBtnPress(self, event):
  569. self._fUpdate = 1
  570. self._fPressInside = 1
  571. self._updateValue(event)
  572. def _dropdownMove(self, event):
  573. if self._firstPress and not self._fUpdate:
  574. canvasY = self._canvas.canvasy(
  575. event.y_root - self._canvas.winfo_rooty())
  576. if canvasY > 0:
  577. self._fUpdate = 1
  578. self._unpostOnNextRelease()
  579. elif self._fUpdate:
  580. self._updateValue(event)
  581. def _dropdownBtnRelease(self, event):
  582. if (self._fUnpost or
  583. (not (self._firstPress or self._fPressInside))):
  584. self._unpostCanvas()
  585. # Otherwise, continue
  586. self._fUpdate = 0
  587. self._firstPress = 0
  588. self._fPressInside = 0
  589. def _unpostOnNextRelease(self, event = None):
  590. self._fUnpost = 1
  591. def _resizeArrow(self, event):
  592. bw = (string.atoi(self._arrowBtn['borderwidth']) +
  593. string.atoi(self._arrowBtn['highlightthickness']))
  594. newHeight = self._entryfield.winfo_reqheight() - 2 * bw
  595. newWidth = int(newHeight * self['buttonaspect'])
  596. self._arrowBtn.configure(width=newWidth, height=newHeight)
  597. self._drawArrow()
  598. def _unpostCanvas(self, event=None):
  599. if not self._isPosted:
  600. # It is possible to get events on an unposted popup. For
  601. # example, by repeatedly pressing the space key to post
  602. # and unpost the popup. The <space> event may be
  603. # delivered to the popup window even though
  604. # Pmw.popgrab() has set the focus away from the
  605. # popup window. (Bug in Tk?)
  606. return
  607. # Restore the focus before withdrawing the window, since
  608. # otherwise the window manager may take the focus away so we
  609. # can't redirect it. Also, return the grab to the next active
  610. # window in the stack, if any.
  611. Pmw.popgrab(self._popup)
  612. self._popup.withdraw()
  613. self._isPosted = 0
  614. self._drawArrow()
  615. def _getMarkerX(self):
  616. # Get marker triangle coordinates
  617. c = self._canvas.coords(self._marker)
  618. # Marker postion defined as X position of third vertex
  619. return c[4]
  620. Pmw.forwardmethods(PopupSlider, Pmw.EntryField, '_entryfield')