Home | History | Annotate | Download | only in plat-mac
      1 "A sort of application framework for the Mac"
      2 
      3 DEBUG=0
      4 
      5 from warnings import warnpy3k
      6 warnpy3k("In 3.x, the FrameWork module is removed.", stacklevel=2)
      7 
      8 import MacOS
      9 import traceback
     10 
     11 from Carbon.AE import *
     12 from Carbon.AppleEvents import *
     13 from Carbon.Ctl import *
     14 from Carbon.Controls import *
     15 from Carbon.Dlg import *
     16 from Carbon.Dialogs import *
     17 from Carbon.Evt import *
     18 from Carbon.Events import *
     19 from Carbon.Help import *
     20 from Carbon.Menu import *
     21 from Carbon.Menus import *
     22 from Carbon.Qd import *
     23 from Carbon.QuickDraw import *
     24 #from Carbon.Res import *
     25 #from Carbon.Resources import *
     26 #from Carbon.Snd import *
     27 #from Carbon.Sound import *
     28 from Carbon.Win import *
     29 from Carbon.Windows import *
     30 import types
     31 
     32 import EasyDialogs
     33 
     34 try:
     35     MyFrontWindow = FrontNonFloatingWindow
     36 except NameError:
     37     MyFrontWindow = FrontWindow
     38 
     39 kHighLevelEvent = 23    # Don't know what header file this should come from
     40 SCROLLBARWIDTH = 16         # Again, not a clue...
     41 
     42 # Trick to forestall a set of SIOUX menus being added to our menubar
     43 SIOUX_APPLEMENU_ID=32000
     44 
     45 
     46 # Map event 'what' field to strings
     47 eventname = {}
     48 eventname[1] = 'mouseDown'
     49 eventname[2] = 'mouseUp'
     50 eventname[3] = 'keyDown'
     51 eventname[4] = 'keyUp'
     52 eventname[5] = 'autoKey'
     53 eventname[6] = 'updateEvt'
     54 eventname[7] = 'diskEvt'
     55 eventname[8] = 'activateEvt'
     56 eventname[15] = 'osEvt'
     57 eventname[23] = 'kHighLevelEvent'
     58 
     59 # Map part codes returned by WhichWindow() to strings
     60 partname = {}
     61 partname[0] = 'inDesk'
     62 partname[1] = 'inMenuBar'
     63 partname[2] = 'inSysWindow'
     64 partname[3] = 'inContent'
     65 partname[4] = 'inDrag'
     66 partname[5] = 'inGrow'
     67 partname[6] = 'inGoAway'
     68 partname[7] = 'inZoomIn'
     69 partname[8] = 'inZoomOut'
     70 
     71 #
     72 # The useable portion of the screen
     73 #       ## but what happens with multiple screens? jvr
     74 screenbounds = GetQDGlobalsScreenBits().bounds
     75 screenbounds = screenbounds[0]+4, screenbounds[1]+4, \
     76     screenbounds[2]-4, screenbounds[3]-4
     77 
     78 next_window_x = 16          # jvr
     79 next_window_y = 44          # jvr
     80 
     81 def windowbounds(width, height):
     82     "Return sensible window bounds"
     83     global next_window_x, next_window_y
     84     r, b = next_window_x+width, next_window_y+height
     85     if r > screenbounds[2]:
     86         next_window_x = 16
     87     if b > screenbounds[3]:
     88         next_window_y = 44
     89     l, t = next_window_x, next_window_y
     90     r, b = next_window_x+width, next_window_y+height
     91     next_window_x, next_window_y = next_window_x + 8, next_window_y + 20    # jvr
     92     return l, t, r, b
     93 
     94 _watch = None
     95 def setwatchcursor():
     96     global _watch
     97 
     98     if _watch is None:
     99         _watch = GetCursor(4).data
    100     SetCursor(_watch)
    101 
    102 def setarrowcursor():
    103     SetCursor(GetQDGlobalsArrow())
    104 
    105 class Application:
    106 
    107     "Application framework -- your application should be a derived class"
    108 
    109     def __init__(self, nomenubar=0):
    110         self._doing_asyncevents = 0
    111         self.quitting = 0
    112         self.needmenubarredraw = 0
    113         self._windows = {}
    114         self._helpmenu = None
    115         if nomenubar:
    116             self.menubar = None
    117         else:
    118             self.makemenubar()
    119 
    120     def __del__(self):
    121         if self._doing_asyncevents:
    122             self._doing_asyncevents = 0
    123             MacOS.SetEventHandler()
    124 
    125     def makemenubar(self):
    126         self.menubar = MenuBar(self)
    127         AppleMenu(self.menubar, self.getabouttext(), self.do_about)
    128         self.makeusermenus()
    129 
    130     def makeusermenus(self):
    131         self.filemenu = m = Menu(self.menubar, "File")
    132         self._quititem = MenuItem(m, "Quit", "Q", self._quit)
    133 
    134     def gethelpmenu(self):
    135         if self._helpmenu is None:
    136             self._helpmenu = HelpMenu(self.menubar)
    137         return self._helpmenu
    138 
    139     def _quit(self, *args):
    140         self.quitting = 1
    141 
    142     def cleanup(self):
    143         for w in self._windows.values():
    144             w.do_close()
    145         return self._windows == {}
    146 
    147     def appendwindow(self, wid, window):
    148         self._windows[wid] = window
    149 
    150     def removewindow(self, wid):
    151         del self._windows[wid]
    152 
    153     def getabouttext(self):
    154         return "About %s..." % self.__class__.__name__
    155 
    156     def do_about(self, id, item, window, event):
    157         EasyDialogs.Message("Hello, world!" + "\015(%s)" % self.__class__.__name__)
    158 
    159     # The main event loop is broken up in several simple steps.
    160     # This is done so you can override each individual part,
    161     # if you have a need to do extra processing independent of the
    162     # event type.
    163     # Normally, however, you'd just define handlers for individual
    164     # events.
    165 
    166     schedparams = (0, 0)    # By default disable Python's event handling
    167     default_wait = None         # By default we wait GetCaretTime in WaitNextEvent
    168 
    169     def mainloop(self, mask = everyEvent, wait = None):
    170         self.quitting = 0
    171         if hasattr(MacOS, 'SchedParams'):
    172             saveparams = MacOS.SchedParams(*self.schedparams)
    173         try:
    174             while not self.quitting:
    175                 try:
    176                     self.do1event(mask, wait)
    177                 except (Application, SystemExit):
    178                     # Note: the raising of "self" is old-fashioned idiom to
    179                     # exit the mainloop. Calling _quit() is better for new
    180                     # applications.
    181                     break
    182         finally:
    183             if hasattr(MacOS, 'SchedParams'):
    184                 MacOS.SchedParams(*saveparams)
    185 
    186     def dopendingevents(self, mask = everyEvent):
    187         """dopendingevents - Handle all pending events"""
    188         while self.do1event(mask, wait=0):
    189             pass
    190 
    191     def do1event(self, mask = everyEvent, wait = None):
    192         ok, event = self.getevent(mask, wait)
    193         if IsDialogEvent(event):
    194             if self.do_dialogevent(event):
    195                 return
    196         if ok:
    197             self.dispatch(event)
    198         else:
    199             self.idle(event)
    200 
    201     def idle(self, event):
    202         pass
    203 
    204     def getevent(self, mask = everyEvent, wait = None):
    205         if self.needmenubarredraw:
    206             DrawMenuBar()
    207             self.needmenubarredraw = 0
    208         if wait is None:
    209             wait = self.default_wait
    210             if wait is None:
    211                 wait = GetCaretTime()
    212         ok, event = WaitNextEvent(mask, wait)
    213         return ok, event
    214 
    215     def dispatch(self, event):
    216         # The following appears to be double work (already done in do1event)
    217         # but we need it for asynchronous event handling
    218         if IsDialogEvent(event):
    219             if self.do_dialogevent(event):
    220                 return
    221         (what, message, when, where, modifiers) = event
    222         if what in eventname:
    223             name = "do_" + eventname[what]
    224         else:
    225             name = "do_%d" % what
    226         try:
    227             handler = getattr(self, name)
    228         except AttributeError:
    229             handler = self.do_unknownevent
    230         handler(event)
    231 
    232     def asyncevents(self, onoff):
    233         """asyncevents - Set asynchronous event handling on or off"""
    234         if MacOS.runtimemodel == 'macho':
    235             raise 'Unsupported in MachoPython'
    236         old = self._doing_asyncevents
    237         if old:
    238             MacOS.SetEventHandler()
    239             MacOS.SchedParams(*self.schedparams)
    240         if onoff:
    241             MacOS.SetEventHandler(self.dispatch)
    242             doint, dummymask, benice, howoften, bgyield = \
    243                    self.schedparams
    244             MacOS.SchedParams(doint, everyEvent, benice,
    245                       howoften, bgyield)
    246         self._doing_asyncevents = onoff
    247         return old
    248 
    249     def do_dialogevent(self, event):
    250         gotone, dlg, item = DialogSelect(event)
    251         if gotone:
    252             window = dlg.GetDialogWindow()
    253             if window in self._windows:
    254                 self._windows[window].do_itemhit(item, event)
    255             else:
    256                 print 'Dialog event for unknown dialog'
    257             return 1
    258         return 0
    259 
    260     def do_mouseDown(self, event):
    261         (what, message, when, where, modifiers) = event
    262         partcode, wid = FindWindow(where)
    263 
    264         #
    265         # Find the correct name.
    266         #
    267         if partcode in partname:
    268             name = "do_" + partname[partcode]
    269         else:
    270             name = "do_%d" % partcode
    271 
    272         if wid is None:
    273             # No window, or a non-python window
    274             try:
    275                 handler = getattr(self, name)
    276             except AttributeError:
    277                 # Not menubar or something, so assume someone
    278                 # else's window
    279                 if hasattr(MacOS, 'HandleEvent'):
    280                     MacOS.HandleEvent(event)
    281                 return
    282         elif wid in self._windows:
    283             # It is a window. Hand off to correct window.
    284             window = self._windows[wid]
    285             try:
    286                 handler = getattr(window, name)
    287             except AttributeError:
    288                 handler = self.do_unknownpartcode
    289         else:
    290             # It is a python-toolbox window, but not ours.
    291             handler = self.do_unknownwindow
    292         handler(partcode, wid, event)
    293 
    294     def do_inSysWindow(self, partcode, window, event):
    295         if hasattr(MacOS, 'HandleEvent'):
    296             MacOS.HandleEvent(event)
    297 
    298     def do_inDesk(self, partcode, window, event):
    299         if hasattr(MacOS, 'HandleEvent'):
    300             MacOS.HandleEvent(event)
    301 
    302     def do_inMenuBar(self, partcode, window, event):
    303         if not self.menubar:
    304             if hasattr(MacOS, 'HandleEvent'):
    305                 MacOS.HandleEvent(event)
    306             return
    307         (what, message, when, where, modifiers) = event
    308         result = MenuSelect(where)
    309         id = (result>>16) & 0xffff      # Hi word
    310         if id >= 0x8000:
    311             id = -65536 + id
    312         item = result & 0xffff      # Lo word
    313         self.do_rawmenu(id, item, window, event)
    314 
    315     def do_rawmenu(self, id, item, window, event):
    316         try:
    317             self.do_menu(id, item, window, event)
    318         finally:
    319             HiliteMenu(0)
    320 
    321     def do_menu(self, id, item, window, event):
    322         if hasattr(MacOS, 'OutputSeen'):
    323             MacOS.OutputSeen()
    324         self.menubar.dispatch(id, item, window, event)
    325 
    326 
    327     def do_unknownpartcode(self, partcode, window, event):
    328         (what, message, when, where, modifiers) = event
    329         if DEBUG: print "Mouse down at global:", where
    330         if DEBUG: print "\tUnknown part code:", partcode
    331         if DEBUG: print "\tEvent:", self.printevent(event)
    332         if hasattr(MacOS, 'HandleEvent'):
    333             MacOS.HandleEvent(event)
    334 
    335     def do_unknownwindow(self, partcode, window, event):
    336         if DEBUG: print 'Unknown window:', window
    337         if hasattr(MacOS, 'HandleEvent'):
    338             MacOS.HandleEvent(event)
    339 
    340     def do_keyDown(self, event):
    341         self.do_key(event)
    342 
    343     def do_autoKey(self, event):
    344         if not event[-1] & cmdKey:
    345             self.do_key(event)
    346 
    347     def do_key(self, event):
    348         (what, message, when, where, modifiers) = event
    349         c = chr(message & charCodeMask)
    350         if self.menubar:
    351             result = MenuEvent(event)
    352             id = (result>>16) & 0xffff      # Hi word
    353             item = result & 0xffff      # Lo word
    354             if id:
    355                 self.do_rawmenu(id, item, None, event)
    356                 return
    357             # Otherwise we fall-through
    358         if modifiers & cmdKey:
    359             if c == '.':
    360                 raise self
    361             else:
    362                 if not self.menubar:
    363                     if hasattr(MacOS, 'HandleEvent'):
    364                         MacOS.HandleEvent(event)
    365                 return
    366         else:
    367             # See whether the front window wants it
    368             w = MyFrontWindow()
    369             if w and w in self._windows:
    370                 window = self._windows[w]
    371                 try:
    372                     do_char = window.do_char
    373                 except AttributeError:
    374                     do_char = self.do_char
    375                 do_char(c, event)
    376             # else it wasn't for us, sigh...
    377 
    378     def do_char(self, c, event):
    379         if DEBUG: print "Character", repr(c)
    380 
    381     def do_updateEvt(self, event):
    382         (what, message, when, where, modifiers) = event
    383         wid = WhichWindow(message)
    384         if wid and wid in self._windows:
    385             window = self._windows[wid]
    386             window.do_rawupdate(wid, event)
    387         else:
    388             if hasattr(MacOS, 'HandleEvent'):
    389                 MacOS.HandleEvent(event)
    390 
    391     def do_activateEvt(self, event):
    392         (what, message, when, where, modifiers) = event
    393         wid = WhichWindow(message)
    394         if wid and wid in self._windows:
    395             window = self._windows[wid]
    396             window.do_activate(modifiers & 1, event)
    397         else:
    398             if hasattr(MacOS, 'HandleEvent'):
    399                 MacOS.HandleEvent(event)
    400 
    401     def do_osEvt(self, event):
    402         (what, message, when, where, modifiers) = event
    403         which = (message >> 24) & 0xff
    404         if which == 1:  # suspend/resume
    405             self.do_suspendresume(event)
    406         else:
    407             if DEBUG:
    408                 print 'unknown osEvt:',
    409                 self.printevent(event)
    410 
    411     def do_suspendresume(self, event):
    412         (what, message, when, where, modifiers) = event
    413         wid = MyFrontWindow()
    414         if wid and wid in self._windows:
    415             window = self._windows[wid]
    416             window.do_activate(message & 1, event)
    417 
    418     def do_kHighLevelEvent(self, event):
    419         (what, message, when, where, modifiers) = event
    420         if DEBUG:
    421             print "High Level Event:",
    422             self.printevent(event)
    423         try:
    424             AEProcessAppleEvent(event)
    425         except:
    426             pass
    427             #print "AEProcessAppleEvent error:"
    428             #traceback.print_exc()
    429 
    430     def do_unknownevent(self, event):
    431         if DEBUG:
    432             print "Unhandled event:",
    433             self.printevent(event)
    434 
    435     def printevent(self, event):
    436         (what, message, when, where, modifiers) = event
    437         nicewhat = repr(what)
    438         if what in eventname:
    439             nicewhat = eventname[what]
    440         print nicewhat,
    441         if what == kHighLevelEvent:
    442             h, v = where
    443             print repr(ostypecode(message)), hex(when), repr(ostypecode(h | (v<<16))),
    444         else:
    445             print hex(message), hex(when), where,
    446         print hex(modifiers)
    447 
    448 
    449 class MenuBar:
    450     """Represent a set of menus in a menu bar.
    451 
    452     Interface:
    453 
    454     - (constructor)
    455     - (destructor)
    456     - addmenu
    457     - addpopup (normally used internally)
    458     - dispatch (called from Application)
    459     """
    460 
    461     nextid = 1      # Necessarily a class variable
    462 
    463     def getnextid(self):
    464         id = MenuBar.nextid
    465         MenuBar.nextid = id+1
    466         return id
    467 
    468     def __init__(self, parent=None):
    469         self.parent = parent
    470         ClearMenuBar()
    471         self.bar = GetMenuBar()
    472         self.menus = {}
    473 
    474     # XXX necessary?
    475     def close(self):
    476         self.parent = None
    477         self.bar = None
    478         self.menus = None
    479 
    480     def addmenu(self, title, after = 0, id=None):
    481         if id is None:
    482             id = self.getnextid()
    483         if DEBUG: print 'Newmenu', title, id # XXXX
    484         m = NewMenu(id, title)
    485         m.InsertMenu(after)
    486         if after >= 0:
    487             if self.parent:
    488                 self.parent.needmenubarredraw = 1
    489             else:
    490                 DrawMenuBar()
    491         return id, m
    492 
    493     def delmenu(self, id):
    494         if DEBUG: print 'Delmenu', id # XXXX
    495         DeleteMenu(id)
    496 
    497     def addpopup(self, title = ''):
    498         return self.addmenu(title, -1)
    499 
    500 # Useless:
    501 #       def install(self):
    502 #           if not self.bar: return
    503 #           SetMenuBar(self.bar)
    504 #           if self.parent:
    505 #               self.parent.needmenubarredraw = 1
    506 #           else:
    507 #               DrawMenuBar()
    508 
    509     def fixmenudimstate(self):
    510         for m in self.menus.keys():
    511             menu = self.menus[m]
    512             if menu.__class__ == FrameWork.AppleMenu:
    513                 continue
    514             for i in range(len(menu.items)):
    515                 label, shortcut, callback, kind = menu.items[i]
    516                 if type(callback) == types.StringType:
    517                     wid = MyFrontWindow()
    518                     if wid and wid in self.parent._windows:
    519                         window = self.parent._windows[wid]
    520                         if hasattr(window, "domenu_" + callback):
    521                             menu.menu.EnableMenuItem(i + 1)
    522                         elif hasattr(self.parent, "domenu_" + callback):
    523                             menu.menu.EnableMenuItem(i + 1)
    524                         else:
    525                             menu.menu.DisableMenuItem(i + 1)
    526                     elif hasattr(self.parent, "domenu_" + callback):
    527                         menu.menu.EnableMenuItem(i + 1)
    528                     else:
    529                         menu.menu.DisableMenuItem(i + 1)
    530                 elif callback:
    531                     pass
    532 
    533     def dispatch(self, id, item, window, event):
    534         if id in self.menus:
    535             self.menus[id].dispatch(id, item, window, event)
    536         else:
    537             if DEBUG: print "MenuBar.dispatch(%d, %d, %s, %s)" % \
    538                 (id, item, window, event)
    539 
    540 
    541 # XXX Need a way to get menus as resources and bind them to callbacks
    542 
    543 class Menu:
    544     "One menu."
    545 
    546     def __init__(self, bar, title, after=0, id=None):
    547         self.bar = bar
    548         self.id, self.menu = self.bar.addmenu(title, after, id)
    549         bar.menus[self.id] = self
    550         self.items = []
    551         self._parent = None
    552 
    553     def delete(self):
    554         self.bar.delmenu(self.id)
    555         del self.bar.menus[self.id]
    556         self.menu.DisposeMenu()
    557         del self.bar
    558         del self.items
    559         del self.menu
    560         del self.id
    561         del self._parent
    562 
    563     def additem(self, label, shortcut=None, callback=None, kind=None):
    564         self.menu.AppendMenu('x')           # add a dummy string
    565         self.items.append((label, shortcut, callback, kind))
    566         item = len(self.items)
    567         if isinstance(label, unicode):
    568             self.menu.SetMenuItemTextWithCFString(item, label)
    569         else:
    570             self.menu.SetMenuItemText(item, label)
    571         if shortcut and type(shortcut) == type(()):
    572             modifiers, char = shortcut[:2]
    573             self.menu.SetItemCmd(item, ord(char))
    574             self.menu.SetMenuItemModifiers(item, modifiers)
    575             if len(shortcut) > 2:
    576                 self.menu.SetMenuItemKeyGlyph(item, shortcut[2])
    577         elif shortcut:
    578             self.menu.SetItemCmd(item, ord(shortcut))
    579         return item
    580 
    581     def delitem(self, item):
    582         if item != len(self.items):
    583             raise 'Can only delete last item of a menu'
    584         self.menu.DeleteMenuItem(item)
    585         del self.items[item-1]
    586 
    587     def addcheck(self, label, shortcut=None, callback=None):
    588         return self.additem(label, shortcut, callback, 'check')
    589 
    590     def addradio(self, label, shortcut=None, callback=None):
    591         return self.additem(label, shortcut, callback, 'radio')
    592 
    593     def addseparator(self):
    594         self.menu.AppendMenu('(-')
    595         self.items.append(('', None, None, 'separator'))
    596 
    597     def addsubmenu(self, label, title=''):
    598         sub = Menu(self.bar, title, -1)
    599         item = self.additem(label, '\x1B', None, 'submenu')
    600         self.menu.SetItemMark(item, sub.id)
    601         sub._parent = self
    602         sub._parent_item = item
    603         return sub
    604 
    605     def dispatch(self, id, item, window, event):
    606         title, shortcut, callback, mtype = self.items[item-1]
    607         if callback:
    608             if not self.bar.parent or type(callback) != types.StringType:
    609                 menuhandler = callback
    610             else:
    611                 # callback is string
    612                 wid = MyFrontWindow()
    613                 if wid and wid in self.bar.parent._windows:
    614                     window = self.bar.parent._windows[wid]
    615                     if hasattr(window, "domenu_" + callback):
    616                         menuhandler = getattr(window, "domenu_" + callback)
    617                     elif hasattr(self.bar.parent, "domenu_" + callback):
    618                         menuhandler = getattr(self.bar.parent, "domenu_" + callback)
    619                     else:
    620                         # nothing we can do. we shouldn't have come this far
    621                         # since the menu item should have been disabled...
    622                         return
    623                 elif hasattr(self.bar.parent, "domenu_" + callback):
    624                     menuhandler = getattr(self.bar.parent, "domenu_" + callback)
    625                 else:
    626                     # nothing we can do. we shouldn't have come this far
    627                     # since the menu item should have been disabled...
    628                     return
    629             menuhandler(id, item, window, event)
    630 
    631     def enable(self, onoff):
    632         if onoff:
    633             self.menu.EnableMenuItem(0)
    634             if self._parent:
    635                 self._parent.menu.EnableMenuItem(self._parent_item)
    636         else:
    637             self.menu.DisableMenuItem(0)
    638             if self._parent:
    639                 self._parent.menu.DisableMenuItem(self._parent_item)
    640         if self.bar and self.bar.parent:
    641             self.bar.parent.needmenubarredraw = 1
    642 
    643 class PopupMenu(Menu):
    644     def __init__(self, bar):
    645         Menu.__init__(self, bar, '(popup)', -1)
    646 
    647     def popup(self, x, y, event, default=1, window=None):
    648         # NOTE that x and y are global coordinates, and they should probably
    649         # be topleft of the button the user clicked (not mouse-coordinates),
    650         # so the popup nicely overlaps.
    651         reply = self.menu.PopUpMenuSelect(x, y, default)
    652         if not reply:
    653             return
    654         id = (reply >> 16) & 0xffff
    655         item = reply & 0xffff
    656         if not window:
    657             wid = MyFrontWindow()
    658             try:
    659                 window = self.bar.parent._windows[wid]
    660             except:
    661                 pass # If we can't find the window we pass None
    662         self.dispatch(id, item, window, event)
    663 
    664 class MenuItem:
    665     def __init__(self, menu, title, shortcut=None, callback=None, kind=None):
    666         self.item = menu.additem(title, shortcut, callback)
    667         self.menu = menu
    668 
    669     def delete(self):
    670         self.menu.delitem(self.item)
    671         del self.menu
    672         del self.item
    673 
    674     def check(self, onoff):
    675         self.menu.menu.CheckMenuItem(self.item, onoff)
    676 
    677     def enable(self, onoff):
    678         if onoff:
    679             self.menu.menu.EnableMenuItem(self.item)
    680         else:
    681             self.menu.menu.DisableMenuItem(self.item)
    682 
    683     def settext(self, text):
    684         self.menu.menu.SetMenuItemText(self.item, text)
    685 
    686     def setstyle(self, style):
    687         self.menu.menu.SetItemStyle(self.item, style)
    688 
    689     def seticon(self, icon):
    690         self.menu.menu.SetItemIcon(self.item, icon)
    691 
    692     def setcmd(self, cmd):
    693         self.menu.menu.SetItemCmd(self.item, cmd)
    694 
    695     def setmark(self, cmd):
    696         self.menu.menu.SetItemMark(self.item, cmd)
    697 
    698 
    699 class RadioItem(MenuItem):
    700     def __init__(self, menu, title, shortcut=None, callback=None):
    701         MenuItem.__init__(self, menu, title, shortcut, callback, 'radio')
    702 
    703 class CheckItem(MenuItem):
    704     def __init__(self, menu, title, shortcut=None, callback=None):
    705         MenuItem.__init__(self, menu, title, shortcut, callback, 'check')
    706 
    707 def Separator(menu):
    708     menu.addseparator()
    709 
    710 def SubMenu(menu, label, title=''):
    711     return menu.addsubmenu(label, title)
    712 
    713 
    714 class AppleMenu(Menu):
    715 
    716     def __init__(self, bar, abouttext="About me...", aboutcallback=None):
    717         Menu.__init__(self, bar, "\024", id=SIOUX_APPLEMENU_ID)
    718         if MacOS.runtimemodel == 'ppc':
    719             self.additem(abouttext, None, aboutcallback)
    720             self.addseparator()
    721             self.menu.AppendResMenu('DRVR')
    722         else:
    723             # Additem()'s tricks do not work for "apple" menu under Carbon
    724             self.menu.InsertMenuItem(abouttext, 0)
    725             self.items.append((abouttext, None, aboutcallback, None))
    726 
    727     def dispatch(self, id, item, window, event):
    728         if item == 1:
    729             Menu.dispatch(self, id, item, window, event)
    730         elif MacOS.runtimemodel == 'ppc':
    731             name = self.menu.GetMenuItemText(item)
    732             OpenDeskAcc(name)
    733 
    734 class HelpMenu(Menu):
    735     def __init__(self, bar):
    736         # Note we don't call Menu.__init__, we do the necessary things by hand
    737         self.bar = bar
    738         self.menu, index = HMGetHelpMenu()
    739         self.id = self.menu.GetMenuID()
    740         bar.menus[self.id] = self
    741         # The next line caters for the entries the system already handles for us
    742         self.items = [None]*(index-1)
    743         self._parent = None
    744 
    745 
    746 class Window:
    747     """A single window belonging to an application"""
    748 
    749     def __init__(self, parent):
    750         self.wid = None
    751         self.parent = parent
    752 
    753     def open(self, bounds=(40, 40, 400, 400), resid=None):
    754         if resid != None:
    755             self.wid = GetNewWindow(resid, -1)
    756         else:
    757             self.wid = NewWindow(bounds, self.__class__.__name__, 1,
    758                 8, -1, 1, 0)    # changed to proc id 8 to include zoom box. jvr
    759         self.do_postopen()
    760 
    761     def do_postopen(self):
    762         """Tell our parent we exist"""
    763         self.parent.appendwindow(self.wid, self)
    764 
    765     def close(self):
    766         self.do_postclose()
    767 
    768     def do_postclose(self):
    769         self.parent.removewindow(self.wid)
    770         self.parent = None
    771         self.wid = None
    772 
    773     def SetPort(self):
    774         # Convinience method
    775         SetPort(self.wid)
    776 
    777     def GetWindow(self):
    778         return self.wid
    779 
    780     def do_inDrag(self, partcode, window, event):
    781         where = event[3]
    782         window.DragWindow(where, self.draglimit)
    783 
    784     draglimit = screenbounds
    785 
    786     def do_inGoAway(self, partcode, window, event):
    787         where = event[3]
    788         if window.TrackGoAway(where):
    789             self.close()
    790 
    791     def do_inZoom(self, partcode, window, event):
    792         (what, message, when, where, modifiers) = event
    793         if window.TrackBox(where, partcode):
    794             window.ZoomWindow(partcode, 1)
    795             rect = window.GetWindowUserState()                  # so that zoom really works... jvr
    796             self.do_postresize(rect[2] - rect[0], rect[3] - rect[1], window)    # jvr
    797 
    798     def do_inZoomIn(self, partcode, window, event):
    799         SetPort(window) # !!!
    800         self.do_inZoom(partcode, window, event)
    801 
    802     def do_inZoomOut(self, partcode, window, event):
    803         SetPort(window) # !!!
    804         self.do_inZoom(partcode, window, event)
    805 
    806     def do_inGrow(self, partcode, window, event):
    807         (what, message, when, where, modifiers) = event
    808         result = window.GrowWindow(where, self.growlimit)
    809         if result:
    810             height = (result>>16) & 0xffff  # Hi word
    811             width = result & 0xffff     # Lo word
    812             self.do_resize(width, height, window)
    813 
    814     growlimit = (50, 50, screenbounds[2] - screenbounds[0], screenbounds[3] - screenbounds[1])      # jvr
    815 
    816     def do_resize(self, width, height, window):
    817         l, t, r, b = self.wid.GetWindowPort().GetPortBounds()           # jvr, forGrowIcon
    818         self.SetPort()                          # jvr
    819         self.wid.InvalWindowRect((r - SCROLLBARWIDTH + 1, b - SCROLLBARWIDTH + 1, r, b))    # jvr
    820         window.SizeWindow(width, height, 1)         # changed updateFlag to true jvr
    821         self.do_postresize(width, height, window)
    822 
    823     def do_postresize(self, width, height, window):
    824         SetPort(window)
    825         self.wid.InvalWindowRect(window.GetWindowPort().GetPortBounds())
    826 
    827     def do_inContent(self, partcode, window, event):
    828         #
    829         # If we're not frontmost, select ourselves and wait for
    830         # the activate event.
    831         #
    832         if MyFrontWindow() != window:
    833             window.SelectWindow()
    834             return
    835         # We are. Handle the event.
    836         (what, message, when, where, modifiers) = event
    837         SetPort(window)
    838         local = GlobalToLocal(where)
    839         self.do_contentclick(local, modifiers, event)
    840 
    841     def do_contentclick(self, local, modifiers, event):
    842         if DEBUG:
    843             print 'Click in contents at %s, modifiers %s'%(local, modifiers)
    844 
    845     def do_rawupdate(self, window, event):
    846         if DEBUG: print "raw update for", window
    847         SetPort(window)
    848         window.BeginUpdate()
    849         self.do_update(window, event)
    850         window.EndUpdate()
    851 
    852     def do_update(self, window, event):
    853         if DEBUG:
    854             import time
    855             for i in range(8):
    856                 time.sleep(0.1)
    857                 InvertRgn(window.GetWindowPort().visRgn)
    858             FillRgn(window.GetWindowPort().visRgn, GetQDGlobalsGray())
    859         else:
    860             EraseRgn(window.GetWindowPort().visRgn)
    861 
    862     def do_activate(self, activate, event):
    863         if DEBUG: print 'Activate %d for %s'%(activate, self.wid)
    864 
    865 class ControlsWindow(Window):
    866 
    867     def do_rawupdate(self, window, event):
    868         if DEBUG: print "raw update for", window
    869         SetPort(window)
    870         window.BeginUpdate()
    871         self.do_update(window, event)
    872         #DrawControls(window)                   # jvr
    873         UpdateControls(window, window.GetWindowPort().visRgn)   # jvr
    874         window.DrawGrowIcon()
    875         window.EndUpdate()
    876 
    877     def do_controlhit(self, window, control, pcode, event):
    878         if DEBUG: print "control hit in", window, "on", control, "; pcode =", pcode
    879 
    880     def do_inContent(self, partcode, window, event):
    881         if MyFrontWindow() != window:
    882             window.SelectWindow()
    883             return
    884         (what, message, when, where, modifiers) = event
    885         SetPort(window)  # XXXX Needed?
    886         local = GlobalToLocal(where)
    887         pcode, control = FindControl(local, window)
    888         if pcode and control:
    889             self.do_rawcontrolhit(window, control, pcode, local, event)
    890         else:
    891             if DEBUG: print "FindControl(%s, %s) -> (%s, %s)" % \
    892                 (local, window, pcode, control)
    893             self.do_contentclick(local, modifiers, event)
    894 
    895     def do_rawcontrolhit(self, window, control, pcode, local, event):
    896         pcode = control.TrackControl(local)
    897         if pcode:
    898             self.do_controlhit(window, control, pcode, event)
    899 
    900 class ScrolledWindow(ControlsWindow):
    901     def __init__(self, parent):
    902         self.barx = self.bary = None
    903         self.barx_enabled = self.bary_enabled = 1
    904         self.activated = 1
    905         ControlsWindow.__init__(self, parent)
    906 
    907     def scrollbars(self, wantx=1, wanty=1):
    908         SetPort(self.wid)
    909         self.barx = self.bary = None
    910         self.barx_enabled = self.bary_enabled = 1
    911         x0, y0, x1, y1 = self.wid.GetWindowPort().GetPortBounds()
    912         vx, vy = self.getscrollbarvalues()
    913         if vx is None: self.barx_enabled, vx = 0, 0
    914         if vy is None: self.bary_enabled, vy = 0, 0
    915         if wantx:
    916             rect = x0-1, y1-(SCROLLBARWIDTH-1), x1-(SCROLLBARWIDTH-2), y1+1
    917             self.barx = NewControl(self.wid, rect, "", 1, vx, 0, 32767, 16, 0)
    918             if not self.barx_enabled: self.barx.HiliteControl(255)
    919 ##              self.wid.InvalWindowRect(rect)
    920         if wanty:
    921             rect = x1-(SCROLLBARWIDTH-1), y0-1, x1+1, y1-(SCROLLBARWIDTH-2)
    922             self.bary = NewControl(self.wid, rect, "", 1, vy, 0, 32767, 16, 0)
    923             if not self.bary_enabled: self.bary.HiliteControl(255)
    924 ##              self.wid.InvalWindowRect(rect)
    925 
    926     def do_postclose(self):
    927         self.barx = self.bary = None
    928         ControlsWindow.do_postclose(self)
    929 
    930     def do_activate(self, onoff, event):
    931         self.activated = onoff
    932         if onoff:
    933             if self.barx and self.barx_enabled:
    934                 self.barx.ShowControl() # jvr
    935             if self.bary and self.bary_enabled:
    936                 self.bary.ShowControl() # jvr
    937         else:
    938             if self.barx:
    939                 self.barx.HideControl() # jvr; An inactive window should have *hidden*
    940                             # scrollbars, not just dimmed (no matter what
    941                             # BBEdit does... look at the Finder)
    942             if self.bary:
    943                 self.bary.HideControl() # jvr
    944         self.wid.DrawGrowIcon()         # jvr
    945 
    946     def do_postresize(self, width, height, window):
    947         l, t, r, b = self.wid.GetWindowPort().GetPortBounds()
    948         self.SetPort()
    949         if self.barx:
    950             self.barx.HideControl()     # jvr
    951             self.barx.MoveControl(l-1, b-(SCROLLBARWIDTH-1))
    952             self.barx.SizeControl((r-l)-(SCROLLBARWIDTH-3), SCROLLBARWIDTH) # jvr
    953         if self.bary:
    954             self.bary.HideControl()     # jvr
    955             self.bary.MoveControl(r-(SCROLLBARWIDTH-1), t-1)
    956             self.bary.SizeControl(SCROLLBARWIDTH, (b-t)-(SCROLLBARWIDTH-3)) # jvr
    957         if self.barx:
    958             self.barx.ShowControl()     # jvr
    959             self.wid.ValidWindowRect((l, b - SCROLLBARWIDTH + 1, r - SCROLLBARWIDTH + 2, b))    # jvr
    960         if self.bary:
    961             self.bary.ShowControl()     # jvr
    962             self.wid.ValidWindowRect((r - SCROLLBARWIDTH + 1, t, r, b - SCROLLBARWIDTH + 2))    # jvr
    963         self.wid.InvalWindowRect((r - SCROLLBARWIDTH + 1, b - SCROLLBARWIDTH + 1, r, b))    # jvr, growicon
    964 
    965 
    966     def do_rawcontrolhit(self, window, control, pcode, local, event):
    967         if control == self.barx:
    968             which = 'x'
    969         elif control == self.bary:
    970             which = 'y'
    971         else:
    972             return 0
    973         if pcode in (inUpButton, inDownButton, inPageUp, inPageDown):
    974             # We do the work for the buttons and grey area in the tracker
    975             dummy = control.TrackControl(local, self.do_controltrack)
    976         else:
    977             # but the thumb is handled here
    978             pcode = control.TrackControl(local)
    979             if pcode == inThumb:
    980                 value = control.GetControlValue()
    981                 print 'setbars', which, value #DBG
    982                 self.scrollbar_callback(which, 'set', value)
    983                 self.updatescrollbars()
    984             else:
    985                 print 'funny part', pcode #DBG
    986         return 1
    987 
    988     def do_controltrack(self, control, pcode):
    989         if control == self.barx:
    990             which = 'x'
    991         elif control == self.bary:
    992             which = 'y'
    993         else:
    994             return
    995 
    996         if pcode == inUpButton:
    997             what = '-'
    998         elif pcode == inDownButton:
    999             what = '+'
   1000         elif pcode == inPageUp:
   1001             what = '--'
   1002         elif pcode == inPageDown:
   1003             what = '++'
   1004         else:
   1005             return
   1006         self.scrollbar_callback(which, what, None)
   1007         self.updatescrollbars()
   1008 
   1009     def updatescrollbars(self):
   1010         SetPort(self.wid)
   1011         vx, vy = self.getscrollbarvalues()
   1012         if self.barx:
   1013             if vx is None:
   1014                 self.barx.HiliteControl(255)
   1015                 self.barx_enabled = 0
   1016             else:
   1017                 if not self.barx_enabled:
   1018                     self.barx_enabled = 1
   1019                     if self.activated:
   1020                         self.barx.HiliteControl(0)
   1021                 self.barx.SetControlValue(vx)
   1022         if self.bary:
   1023             if vy is None:
   1024                 self.bary.HiliteControl(255)
   1025                 self.bary_enabled = 0
   1026             else:
   1027                 if not self.bary_enabled:
   1028                     self.bary_enabled = 1
   1029                     if self.activated:
   1030                         self.bary.HiliteControl(0)
   1031                 self.bary.SetControlValue(vy)
   1032 
   1033     # Auxiliary function: convert standard text/image/etc coordinate
   1034     # to something palatable as getscrollbarvalues() return
   1035     def scalebarvalue(self, absmin, absmax, curmin, curmax):
   1036         if curmin <= absmin and curmax >= absmax:
   1037             return None
   1038         if curmin <= absmin:
   1039             return 0
   1040         if curmax >= absmax:
   1041             return 32767
   1042         perc = float(curmin-absmin)/float(absmax-absmin)
   1043         return int(perc*32767)
   1044 
   1045     # To be overridden:
   1046 
   1047     def getscrollbarvalues(self):
   1048         return 0, 0
   1049 
   1050     def scrollbar_callback(self, which, what, value):
   1051         print 'scroll', which, what, value
   1052 
   1053 class DialogWindow(Window):
   1054     """A modeless dialog window"""
   1055 
   1056     def open(self, resid):
   1057         self.dlg = GetNewDialog(resid, -1)
   1058         self.wid = self.dlg.GetDialogWindow()
   1059         self.do_postopen()
   1060 
   1061     def close(self):
   1062         self.do_postclose()
   1063 
   1064     def do_postclose(self):
   1065         self.dlg = None
   1066         Window.do_postclose(self)
   1067 
   1068     def do_itemhit(self, item, event):
   1069         print 'Dialog %s, item %d hit'%(self.dlg, item)
   1070 
   1071     def do_rawupdate(self, window, event):
   1072         pass
   1073 
   1074 def ostypecode(x):
   1075     "Convert a long int to the 4-character code it really is"
   1076     s = ''
   1077     for i in range(4):
   1078         x, c = divmod(x, 256)
   1079         s = chr(c) + s
   1080     return s
   1081 
   1082 
   1083 class TestApp(Application):
   1084 
   1085     "This class is used by the test() function"
   1086 
   1087     def makeusermenus(self):
   1088         self.filemenu = m = Menu(self.menubar, "File")
   1089         self.saveitem = MenuItem(m, "Save", "S", self.save)
   1090         Separator(m)
   1091         self.optionsmenu = mm = SubMenu(m, "Options")
   1092         self.opt1 = CheckItem(mm, "Arguments", "A")
   1093         self.opt2 = CheckItem(mm, "Being hit on the head lessons", (kMenuOptionModifier, "A"))
   1094         self.opt3 = CheckItem(mm, "Complaints", (kMenuOptionModifier|kMenuNoCommandModifier, "A"))
   1095         Separator(m)
   1096         self.itemeh = MenuItem(m, "Enable Help", None, self.enablehelp)
   1097         self.itemdbg = MenuItem(m, "Debug", None, self.debug)
   1098         Separator(m)
   1099         self.quititem = MenuItem(m, "Quit", "Q", self.quit)
   1100 
   1101     def save(self, *args):
   1102         print "Save"
   1103 
   1104     def quit(self, *args):
   1105         raise self
   1106 
   1107     def enablehelp(self, *args):
   1108         hm = self.gethelpmenu()
   1109         self.nohelpitem = MenuItem(hm, "There isn't any", None, self.nohelp)
   1110 
   1111     def nohelp(self, *args):
   1112         print "I told you there isn't any!"
   1113 
   1114     def debug(self, *args):
   1115         import pdb
   1116         pdb.set_trace()
   1117 
   1118 
   1119 def test():
   1120     "Test program"
   1121     app = TestApp()
   1122     app.mainloop()
   1123 
   1124 
   1125 if __name__ == '__main__':
   1126     test()
   1127