1# --------------------------------------------------------------------------------- # 2# FLATMENU wxPython IMPLEMENTATION 3# 4# Andrea Gavana, @ 03 Nov 2006 5# Latest Revision: 27 Dec 2012, 21.00 GMT 6# 7# TODO List 8# 9# 1. Work is still in progress, so other functionalities may be added in the future; 10# 2. No shadows under MAC, but it may be possible to create them using Carbon. 11# 12# 13# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please 14# Write To Me At: 15# 16# andrea.gavana@maerskoil.com 17# andrea.gavana@gmail.com 18# 19# Or, Obviously, To The wxPython Mailing List!!! 20# 21# 22# Tags: phoenix-port, py3-port, unittest, documented 23# --------------------------------------------------------------------------------- # 24 25""" 26:class:`~wx.lib.agw.flatmenu.FlatMenu` is a generic menu implementation. 27 28 29Description 30=========== 31 32:class:`FlatMenu`, like the name implies, it is a generic menu implementation. 33I tried to provide a full functionality for menus, menubar and toolbar. 34 35 36:class:`FlatMenu` supports the following features: 37 38- Fires all the events (UI & Cmd); 39- Check items; 40- Separators; 41- Enabled / Disabled menu items; 42- Images on items; 43- Toolbar support, with images and separators; 44- Controls in toolbar (work in progress); 45- Toolbar tools tooltips (done: thanks to Peter Kort); 46- Accelerators for menus; 47- Accelerators for menubar; 48- Radio items in menus; 49- Integration with AUI; 50- Scrolling when menu is too big to fit the screen; 51- Menu navigation with keyboard; 52- Drop down arrow button to the right of the menu, it always contains the 53 "Customize" option, which will popup an options dialog. The dialog has the 54 following abilities: 55 56 (a) Ability to add/remove menus; 57 (b) Select different colour schemes for the menu bar / toolbar; 58 (c) Control various options, such as: colour for highlight menu item, draw 59 border around menus (classic look only); 60 (d) Toolbar floating appearance. 61 62- Allows user to specify grey bitmap for disabled menus/toolbar tools; 63- If no grey bitmap is provided, it generates one from the existing bitmap; 64- Hidden toolbar items / menu bar items - will appear in a small popmenu 65 to the right if they are hidden; 66- 4 different colour schemes for the menu bar (more can easily added); 67- Scrolling is available if the menu height is greater than the screen height; 68- Context menus for menu items; 69- Show/hide the drop down arrow which allows the customization of :class:`FlatMenu`; 70- Multiple columns menu window; 71- Tooltips for menus and toolbar items on a :class:`StatusBar` (if present); 72- Transparency (alpha channel) for menu windows (for platforms supporting it); 73- FileHistory support through a pure-Python :class:`FileHistory` implementation; 74- Possibility to set a background bitmap for a :class:`FlatMenu`; 75- First attempt in adding controls to FlatToolbar; 76- Added a MiniBar (thanks to Vladiuz); 77- Added :class:`ToolBar` methods AddCheckTool/AddRadioTool (thanks to Vladiuz). 78 79 80Usage 81===== 82 83Usage example:: 84 85 import wx 86 import wx.lib.agw.flatmenu as FM 87 88 class MyFrame(wx.Frame): 89 90 def __init__(self, parent): 91 92 wx.Frame.__init__(self, parent, -1, "FlatMenu Demo") 93 94 self.CreateMenu() 95 96 panel = wx.Panel(self, -1) 97 btn = wx.Button(panel, -1, "Hello", (15, 12), (100, 120)) 98 99 main_sizer = wx.BoxSizer(wx.VERTICAL) 100 main_sizer.Add(self.menubar, 0, wx.EXPAND) 101 main_sizer.Add(panel, 1, wx.EXPAND) 102 103 self.SetSizer(main_sizer) 104 main_sizer.Layout() 105 106 107 def CreateMenu(self): 108 109 self.menubar = FM.FlatMenuBar(self, -1) 110 111 f_menu = FM.FlatMenu() 112 e_menu = FM.FlatMenu() 113 v_menu = FM.FlatMenu() 114 t_menu = FM.FlatMenu() 115 w_menu = FM.FlatMenu() 116 117 # Append the menu items to the menus 118 f_menu.Append(-1, "Simple\tCtrl+N", "Text", None) 119 e_menu.Append(-1, "FlatMenu", "Text", None) 120 v_menu.Append(-1, "Example", "Text", None) 121 t_menu.Append(-1, "Hello", "Text", None) 122 w_menu.Append(-1, "World", "Text", None) 123 124 # Append menus to the menubar 125 self.menubar.Append(f_menu, "&File") 126 self.menubar.Append(e_menu, "&Edit") 127 self.menubar.Append(v_menu, "&View") 128 self.menubar.Append(t_menu, "&Options") 129 self.menubar.Append(w_menu, "&Help") 130 131 132 # our normal wxApp-derived class, as usual 133 134 app = wx.App(0) 135 136 frame = MyFrame(None) 137 app.SetTopWindow(frame) 138 frame.Show() 139 140 app.MainLoop() 141 142 143 144Supported Platforms 145=================== 146 147:class:`FlatMenu` has been tested on the following platforms: 148 * Windows (Windows XP, Vista); 149 * Linux Ubuntu (Dapper 6.06) 150 151 152 153Window Styles 154============= 155 156This class supports the following window styles: 157 158========================= =========== ================================================== 159Window Styles Hex Value Description 160========================= =========== ================================================== 161``FM_OPT_IS_LCD`` 0x1 Use this style if your computer uses a LCD screen. 162``FM_OPT_MINIBAR`` 0x2 Use this if you plan to use the toolbar only. 163``FM_OPT_SHOW_CUSTOMIZE`` 0x4 Show "customize link" in the `More` menu, you will need to write your own handler. See demo. 164``FM_OPT_SHOW_TOOLBAR`` 0x8 Set this option is you are planning to use the toolbar. 165========================= =========== ================================================== 166 167 168Events Processing 169================= 170 171This class processes the following events: 172 173================================= ================================================== 174Event Name Description 175================================= ================================================== 176``EVT_FLAT_MENU_DISMISSED`` Used internally. 177``EVT_FLAT_MENU_ITEM_MOUSE_OUT`` Fires an event when the mouse leaves a :class:`FlatMenuItem`. 178``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` Fires an event when the mouse enters a :class:`FlatMenuItem`. 179``EVT_FLAT_MENU_SELECTED`` Fires the :class:`EVT_MENU` event for :class:`FlatMenu`. 180================================= ================================================== 181 182 183License And Version 184=================== 185 186:class:`FlatMenu` is distributed under the wxPython license. 187 188Latest Revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT 189 190Version 1.0 191 192""" 193 194__docformat__ = "epytext" 195__version__ = "1.0" 196 197import wx 198import os 199import math 200 201import wx.lib.colourutils as colourutils 202 203import six 204 205from .fmcustomizedlg import FMCustomizeDlg 206from .artmanager import ArtManager, DCSaver 207from .fmresources import * 208 209# FlatMenu styles 210FM_OPT_IS_LCD = 1 211""" Use this style if your computer uses a LCD screen. """ 212FM_OPT_MINIBAR = 2 213""" Use this if you plan to use the toolbar only. """ 214FM_OPT_SHOW_CUSTOMIZE = 4 215""" Show "customize link" in the `More` menu, you will need to write your own handler. See demo. """ 216FM_OPT_SHOW_TOOLBAR = 8 217""" Set this option is you are planning to use the toolbar. """ 218 219# Some checking to see if we can draw shadows behind the popup menus 220# at least on Windows. *REQUIRES* Mark Hammond's win32all extensions 221# and ctypes, on Windows obviouly. Mac and GTK have no shadows under 222# the menus, and it has been reported that shadows don't work well 223# on Windows 2000 and previous. 224 225_libimported = None 226_DELAY = 5000 227 228# Define a translation string 229_ = wx.GetTranslation 230 231if wx.Platform == "__WXMSW__": 232 osVersion = wx.GetOsVersion() 233 # Shadows behind menus are supported only in XP 234 if osVersion[1] == 5 and osVersion[2] == 1: 235 try: 236 import win32api 237 import win32gui 238 _libimported = "MH" 239 except: 240 try: 241 import ctypes 242 _libimported = "ctypes" 243 except: 244 pass 245 else: 246 _libimported = None 247 248# Simple hack, but I don't know how to make it work on Mac 249# I don't have Mac ;-) 250#if wx.Platform == "__WXMAC__": 251# try: 252# import ctypes 253# _carbon_dll = ctypes.cdll.LoadLibrary(r'/System/Frameworks/Carbon.framework/Carbon') 254# except: 255# _carbon_dll = None 256 257 258# FIXME: No way to get shadows on Windows with the original code... 259# May anyone share some suggestion on how to make it work?? 260# Right now I am using win32api to create shadows behind wx.PopupWindow, 261# but this will result in *all* the popup windows in an application 262# to have shadows behind them, even the user defined wx.PopupWindow 263# that do not derive from FlatMenu. 264 265CPP_AUI = True 266 267try: 268 import wx.aui as AUI 269 AuiPaneInfo = AUI.AuiPaneInfo 270 """ Default AuiPaneInfo as in :class:`~wx.lib.agw.aui.AuiPaneInfo`. """ 271except ImportError: 272 CPP_AUI = False 273 274try: 275 from . import aui as PyAUI 276 PyAuiPaneInfo = PyAUI.AuiPaneInfo 277 """ Default AuiPaneInfo as in :class:`framemanager`. """ 278except ImportError: 279 pass 280 281# Check for the new method in 2.7 (not present in 2.6.3.3) 282if wx.VERSION_STRING < "2.7": 283 wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point) 284 285 286wxEVT_FLAT_MENU_DISMISSED = wx.NewEventType() 287wxEVT_FLAT_MENU_SELECTED = wx.wxEVT_COMMAND_MENU_SELECTED 288wxEVT_FLAT_MENU_ITEM_MOUSE_OVER = wx.NewEventType() 289wxEVT_FLAT_MENU_ITEM_MOUSE_OUT = wx.NewEventType() 290 291EVT_FLAT_MENU_DISMISSED = wx.PyEventBinder(wxEVT_FLAT_MENU_DISMISSED, 1) 292""" Used internally. """ 293EVT_FLAT_MENU_SELECTED = wx.PyEventBinder(wxEVT_FLAT_MENU_SELECTED, 2) 294""" Fires the wx.EVT_MENU event for :class:`FlatMenu`. """ 295EVT_FLAT_MENU_RANGE = wx.PyEventBinder(wxEVT_FLAT_MENU_SELECTED, 2) 296""" Fires the wx.EVT_MENU event for a series of :class:`FlatMenu`. """ 297EVT_FLAT_MENU_ITEM_MOUSE_OUT = wx.PyEventBinder(wxEVT_FLAT_MENU_ITEM_MOUSE_OUT, 1) 298""" Fires an event when the mouse leaves a :class:`FlatMenuItem`. """ 299EVT_FLAT_MENU_ITEM_MOUSE_OVER = wx.PyEventBinder(wxEVT_FLAT_MENU_ITEM_MOUSE_OVER, 1) 300""" Fires an event when the mouse enters a :class:`FlatMenuItem`. """ 301 302 303def GetAccelIndex(label): 304 """ 305 Returns the mnemonic index of the label and the label stripped of the ampersand mnemonic 306 (e.g. 'lab&el' ==> will result in 3 and labelOnly = label). 307 308 :param string `label`: a string possibly containining an ampersand. 309 """ 310 311 indexAccel = 0 312 while True: 313 indexAccel = label.find("&", indexAccel) 314 if indexAccel == -1: 315 return indexAccel, label 316 if label[indexAccel:indexAccel+2] == "&&": 317 label = label[0:indexAccel] + label[indexAccel+1:] 318 indexAccel += 1 319 else: 320 break 321 322 labelOnly = label[0:indexAccel] + label[indexAccel+1:] 323 324 return indexAccel, labelOnly 325 326 327def ConvertToMonochrome(bmp): 328 """ 329 Converts a bitmap to monochrome colour. 330 331 :param `bmp`: a valid :class:`wx.Bitmap` object. 332 """ 333 334 mem_dc = wx.MemoryDC() 335 shadow = wx.Bitmap(bmp.GetWidth(), bmp.GetHeight()) 336 mem_dc.SelectObject(shadow) 337 mem_dc.DrawBitmap(bmp, 0, 0, True) 338 mem_dc.SelectObject(wx.NullBitmap) 339 img = shadow.ConvertToImage() 340 img = img.ConvertToMono(0, 0, 0) 341 342 # we now have black where the original bmp was drawn, 343 # white elsewhere 344 shadow = wx.Bitmap(img) 345 shadow.SetMask(wx.Mask(shadow, wx.BLACK)) 346 347 # Convert the black to grey 348 tmp = wx.Bitmap(bmp.GetWidth(), bmp.GetHeight()) 349 col = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW) 350 mem_dc.SelectObject(tmp) 351 mem_dc.SetPen(wx.Pen(col)) 352 mem_dc.SetBrush(wx.Brush(col)) 353 mem_dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight()) 354 mem_dc.DrawBitmap(shadow, 0, 0, True) # now contains a bitmap with grey where the image was, white elsewhere 355 mem_dc.SelectObject(wx.NullBitmap) 356 shadow = tmp 357 shadow.SetMask(wx.Mask(shadow, wx.WHITE)) 358 359 return shadow 360 361 362# ---------------------------------------------------------------------------- # 363# Class FMRendererMgr 364# ---------------------------------------------------------------------------- # 365 366class FMRendererMgr(object): 367 """ 368 This class represents a manager that handles all the renderers defined. 369 Every instance of this class will share the same state, so everyone can 370 instantiate their own and a call to :meth:`FMRendererMgr.SetTheme() <FMRendererMgr.SetTheme>` anywhere will affect everyone. 371 """ 372 373 def __new__(cls, *p, **k): 374 if not '_instance' in cls.__dict__: 375 cls._instance = object.__new__(cls) 376 return cls._instance 377 378 379 def __init__(self): 380 """ Default class constructor. """ 381 382 # If we have already initialized don't do it again. There is only one 383 # FMRendererMgr process-wide. 384 385 if hasattr(self, '_alreadyInitialized'): 386 return 387 388 self._alreadyInitialized = True 389 390 self._currentTheme = StyleDefault 391 self._renderers = [] 392 self._renderers.append(FMRenderer()) 393 self._renderers.append(FMRendererXP()) 394 self._renderers.append(FMRendererMSOffice2007()) 395 self._renderers.append(FMRendererVista()) 396 397 398 def GetRenderer(self): 399 """ Returns the current theme's renderer. """ 400 401 return self._renderers[self._currentTheme] 402 403 404 def AddRenderer(self, renderer): 405 """ 406 Adds a user defined custom renderer. 407 408 :param `renderer`: a class derived from :class:`FMRenderer`. 409 """ 410 411 lastRenderer = len(self._renderers) 412 self._renderers.append(renderer) 413 414 return lastRenderer 415 416 417 def SetTheme(self, theme): 418 """ 419 Sets the current theme. 420 421 :param `theme`: an integer specifying the theme to use. 422 """ 423 424 if theme < 0 or theme > len(self._renderers): 425 raise ValueError("Error invalid theme specified.") 426 427 self._currentTheme = theme 428 429 430# ---------------------------------------------------------------------------- # 431# Class FMRenderer 432# ---------------------------------------------------------------------------- # 433 434class FMRenderer(object): 435 """ 436 Base class for the :class:`FlatMenu` renderers. This class implements the common 437 methods of all the renderers. 438 """ 439 440 def __init__(self): 441 """ Default class constructor. """ 442 443 self.separatorHeight = 5 444 self.drawLeftMargin = False 445 self.highlightCheckAndRadio = False 446 self.scrollBarButtons = False # Display scrollbar buttons if the menu doesn't fit on the screen 447 # otherwise default to up and down arrow menu items 448 449 self.itemTextColourDisabled = ArtManager.Get().LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT), 30) 450 451 # Background Colours 452 self.menuFaceColour = wx.WHITE 453 self.menuBarFaceColour = ArtManager.Get().LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE), 80) 454 455 self.menuBarFocusFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 456 self.menuBarFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 457 self.menuBarPressedFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 458 self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 459 460 self.menuFocusFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 461 self.menuFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 462 self.menuPressedFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 463 self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 464 465 self.buttonFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 466 self.buttonBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 467 self.buttonFocusFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 468 self.buttonFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 469 self.buttonPressedFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 470 self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 471 472 # create wxBitmaps from the xpm's 473 self._rightBottomCorner = self.ConvertToBitmap(shadow_center_xpm, shadow_center_alpha) 474 self._bottom = self.ConvertToBitmap(shadow_bottom_xpm, shadow_bottom_alpha) 475 self._bottomLeft = self.ConvertToBitmap(shadow_bottom_left_xpm, shadow_bottom_left_alpha) 476 self._rightTop = self.ConvertToBitmap(shadow_right_top_xpm, shadow_right_top_alpha) 477 self._right = self.ConvertToBitmap(shadow_right_xpm, shadow_right_alpha) 478 479 self._bitmaps = {} 480 bmp = self.ConvertToBitmap(arrow_down, alpha=None) 481 bmp.SetMask(wx.Mask(bmp, wx.Colour(0, 128, 128))) 482 self._bitmaps.update({"arrow_down": bmp}) 483 484 bmp = self.ConvertToBitmap(arrow_up, alpha=None) 485 bmp.SetMask(wx.Mask(bmp, wx.Colour(0, 128, 128))) 486 self._bitmaps.update({"arrow_up": bmp}) 487 488 self._toolbarSeparatorBitmap = wx.NullBitmap 489 self.raiseToolbar = False 490 491 492 def SetMenuBarHighlightColour(self, colour): 493 """ 494 Set the colour to highlight focus on the menu bar. 495 496 :param `colour`: a valid instance of :class:`wx.Colour`. 497 """ 498 499 self.menuBarFocusFaceColour = colour 500 self.menuBarFocusBorderColour = colour 501 self.menuBarPressedFaceColour = colour 502 self.menuBarPressedBorderColour= colour 503 504 505 def SetMenuHighlightColour(self,colour): 506 """ 507 Set the colour to highlight focus on the menu. 508 509 :param `colour`: a valid instance of :class:`wx.Colour`. 510 """ 511 512 self.menuFocusFaceColour = colour 513 self.menuFocusBorderColour = colour 514 self.menuPressedFaceColour = colour 515 self.menuPressedBorderColour = colour 516 517 518 def GetColoursAccordingToState(self, state): 519 """ 520 Returns a :class:`wx.Colour` according to the menu item state. 521 522 :param integer `state`: one of the following bits: 523 524 ==================== ======= ========================== 525 Item State Value Description 526 ==================== ======= ========================== 527 ``ControlPressed`` 0 The item is pressed 528 ``ControlFocus`` 1 The item is focused 529 ``ControlDisabled`` 2 The item is disabled 530 ``ControlNormal`` 3 Normal state 531 ==================== ======= ========================== 532 533 """ 534 535 # switch according to the status 536 if state == ControlFocus: 537 upperBoxTopPercent = 95 538 upperBoxBottomPercent = 50 539 lowerBoxTopPercent = 40 540 lowerBoxBottomPercent = 90 541 concaveUpperBox = True 542 concaveLowerBox = True 543 544 elif state == ControlPressed: 545 upperBoxTopPercent = 75 546 upperBoxBottomPercent = 90 547 lowerBoxTopPercent = 90 548 lowerBoxBottomPercent = 40 549 concaveUpperBox = True 550 concaveLowerBox = True 551 552 elif state == ControlDisabled: 553 upperBoxTopPercent = 100 554 upperBoxBottomPercent = 100 555 lowerBoxTopPercent = 70 556 lowerBoxBottomPercent = 70 557 concaveUpperBox = True 558 concaveLowerBox = True 559 560 else: 561 upperBoxTopPercent = 90 562 upperBoxBottomPercent = 50 563 lowerBoxTopPercent = 30 564 lowerBoxBottomPercent = 75 565 concaveUpperBox = True 566 concaveLowerBox = True 567 568 return upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \ 569 concaveUpperBox, concaveLowerBox 570 571 572 def ConvertToBitmap(self, xpm, alpha=None): 573 """ 574 Convert the given image to a bitmap, optionally overlaying an alpha 575 channel to it. 576 577 :param `xpm`: a list of strings formatted as XPM; 578 :param `alpha`: a list of alpha values, the same size as the xpm bitmap. 579 """ 580 581 if alpha is not None: 582 583 img = wx.Bitmap(xpm) 584 img = img.ConvertToImage() 585 x, y = img.GetWidth(), img.GetHeight() 586 img.InitAlpha() 587 for jj in range(y): 588 for ii in range(x): 589 img.SetAlpha(ii, jj, alpha[jj*x+ii]) 590 591 else: 592 593 stream = six.BytesIO(xpm) 594 img = wx.Image(stream) 595 596 return wx.Bitmap(img) 597 598 599 def DrawLeftMargin(self, item, dc, menuRect): 600 """ 601 Draws the menu left margin. 602 603 :param `item`: an instance of :class:`FlatMenuItem`; 604 :param `dc`: an instance of :class:`wx.DC`; 605 :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle. 606 """ 607 608 raise Exception("This style doesn't support Drawing a Left Margin") 609 610 611 def DrawToolbarSeparator(self, dc, rect): 612 """ 613 Draws a separator inside the toolbar in :class:`FlatMenuBar`. 614 615 :param `dc`: an instance of :class:`wx.DC`; 616 :param `rect`: an instance of :class:`wx.Rect`, representing the bitmap client rectangle. 617 """ 618 619 # Place a separator bitmap 620 bmp = wx.Bitmap(rect.width, rect.height) 621 mem_dc = wx.MemoryDC() 622 mem_dc.SelectObject(bmp) 623 mem_dc.SetPen(wx.BLACK_PEN) 624 mem_dc.SetBrush(wx.BLACK_BRUSH) 625 626 mem_dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight()) 627 628 col = self.menuBarFaceColour 629 col1 = ArtManager.Get().LightColour(col, 40) 630 col2 = ArtManager.Get().LightColour(col, 70) 631 632 mem_dc.SetPen(wx.Pen(col2)) 633 mem_dc.DrawLine(5, 0, 5, bmp.GetHeight()) 634 635 mem_dc.SetPen(wx.Pen(col1)) 636 mem_dc.DrawLine(6, 0, 6, bmp.GetHeight()) 637 638 mem_dc.SelectObject(wx.NullBitmap) 639 bmp.SetMask(wx.Mask(bmp, wx.BLACK)) 640 641 dc.DrawBitmap(bmp, rect.x, rect.y, True) 642 643 644 # assumption: the background was already drawn on the dc 645 def DrawBitmapShadow(self, dc, rect, where=BottomShadow|RightShadow): 646 """ 647 Draws a shadow using background bitmap. 648 649 :param `dc`: an instance of :class:`wx.DC`; 650 :param `rect`: an instance of :class:`wx.Rect`, representing the bitmap client rectangle; 651 :param integer `where`: where to draw the shadow. This can be any combination of the 652 following bits: 653 654 ========================== ======= ================================ 655 Shadow Settings Value Description 656 ========================== ======= ================================ 657 ``RightShadow`` 1 Right side shadow 658 ``BottomShadow`` 2 Not full bottom shadow 659 ``BottomShadowFull`` 4 Full bottom shadow 660 ========================== ======= ================================ 661 662 """ 663 664 shadowSize = 5 665 666 # the rect must be at least 5x5 pixles 667 if rect.height < 2*shadowSize or rect.width < 2*shadowSize: 668 return 669 670 # Start by drawing the right bottom corner 671 if where & BottomShadow or where & BottomShadowFull: 672 dc.DrawBitmap(self._rightBottomCorner, rect.x+rect.width, rect.y+rect.height, True) 673 674 # Draw right side shadow 675 xx = rect.x + rect.width 676 yy = rect.y + rect.height - shadowSize 677 678 if where & RightShadow: 679 while yy - rect.y > 2*shadowSize: 680 dc.DrawBitmap(self._right, xx, yy, True) 681 yy -= shadowSize 682 683 dc.DrawBitmap(self._rightTop, xx, yy - shadowSize, True) 684 685 if where & BottomShadow: 686 xx = rect.x + rect.width - shadowSize 687 yy = rect.height + rect.y 688 while xx - rect.x > 2*shadowSize: 689 dc.DrawBitmap(self._bottom, xx, yy, True) 690 xx -= shadowSize 691 692 dc.DrawBitmap(self._bottomLeft, xx - shadowSize, yy, True) 693 694 if where & BottomShadowFull: 695 xx = rect.x + rect.width - shadowSize 696 yy = rect.height + rect.y 697 while xx - rect.x >= 0: 698 dc.DrawBitmap(self._bottom, xx, yy, True) 699 xx -= shadowSize 700 701 dc.DrawBitmap(self._bottom, xx, yy, True) 702 703 704 def DrawToolBarBg(self, dc, rect): 705 """ 706 Draws the toolbar background 707 708 :param `dc`: an instance of :class:`wx.DC`; 709 :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle. 710 """ 711 712 if not self.raiseToolbar: 713 return 714 715 dcsaver = DCSaver(dc) 716 717 # fill with gradient 718 colour = self.menuBarFaceColour 719 720 dc.SetPen(wx.Pen(colour)) 721 dc.SetBrush(wx.Brush(colour)) 722 723 dc.DrawRectangle(rect) 724 self.DrawBitmapShadow(dc, rect) 725 726 727 def DrawSeparator(self, dc, xCoord, yCoord, textX, sepWidth): 728 """ 729 Draws a separator inside a :class:`FlatMenu`. 730 731 :param `dc`: an instance of :class:`wx.DC`; 732 :param integer `xCoord`: the current x position where to draw the separator; 733 :param integer `yCoord`: the current y position where to draw the separator; 734 :param integer `textX`: the menu item label x position; 735 :param integer `sepWidth`: the width of the separator, in pixels. 736 """ 737 738 dcsaver = DCSaver(dc) 739 sepRect1 = wx.Rect(xCoord + textX, yCoord + 1, sepWidth/2, 1) 740 sepRect2 = wx.Rect(xCoord + textX + sepWidth/2, yCoord + 1, sepWidth/2-1, 1) 741 742 artMgr = ArtManager.Get() 743 backColour = artMgr.GetMenuFaceColour() 744 lightColour = wx.Colour("LIGHT GREY") 745 746 artMgr.PaintStraightGradientBox(dc, sepRect1, backColour, lightColour, False) 747 artMgr.PaintStraightGradientBox(dc, sepRect2, lightColour, backColour, False) 748 749 750 def DrawMenuItem(self, item, dc, xCoord, yCoord, imageMarginX, markerMarginX, textX, rightMarginX, selected=False, backgroundImage=None): 751 """ 752 Draws the menu item. 753 754 :param `item`: a :class:`FlatMenuItem` instance; 755 :param `dc`: an instance of :class:`wx.DC`; 756 :param integer `xCoord`: the current x position where to draw the menu; 757 :param integer `yCoord`: the current y position where to draw the menu; 758 :param integer `imageMarginX`: the spacing between the image and the menu border; 759 :param integer `markerMarginX`: the spacing between the checkbox/radio marker and 760 the menu border; 761 :param integer `textX`: the menu item label x position; 762 :param integer `rightMarginX`: the right margin between the text and the menu border; 763 :param bool `selected`: ``True`` if this menu item is currentl hovered by the mouse, 764 ``False`` otherwise. 765 :param `backgroundImage`: if not ``None``, an instance of :class:`wx.Bitmap` which will 766 become the background image for this :class:`FlatMenu`. 767 """ 768 769 borderXSize = item._parentMenu.GetBorderXWidth() 770 itemHeight = item._parentMenu.GetItemHeight() 771 menuWidth = item._parentMenu.GetMenuWidth() 772 773 # Define the item actual rectangle area 774 itemRect = wx.Rect(xCoord, yCoord, menuWidth, itemHeight) 775 776 # Define the drawing area 777 rect = wx.Rect(xCoord+2, yCoord, menuWidth - 4, itemHeight) 778 779 # Draw the background 780 backColour = self.menuFaceColour 781 penColour = backColour 782 backBrush = wx.Brush(backColour) 783 leftMarginWidth = item._parentMenu.GetLeftMarginWidth() 784 785 if backgroundImage is None: 786 pen = wx.Pen(penColour) 787 dc.SetPen(pen) 788 dc.SetBrush(backBrush) 789 dc.DrawRectangle(rect) 790 791 # Draw the left margin gradient 792 if self.drawLeftMargin: 793 self.DrawLeftMargin(item, dc, itemRect) 794 795 # check if separator 796 if item.IsSeparator(): 797 # Separator is a small grey line separating between menu items. 798 sepWidth = xCoord + menuWidth - textX - 1 799 self.DrawSeparator(dc, xCoord, yCoord, textX, sepWidth) 800 return 801 802 # Keep the item rect 803 item._rect = itemRect 804 805 # Get the bitmap base on the item state (disabled, selected ..) 806 bmp = item.GetSuitableBitmap(selected) 807 808 # First we draw the selection rectangle 809 if selected: 810 self.DrawMenuButton(dc, rect.Deflate(1,0), ControlFocus) 811 #copy.Inflate(0, menubar._spacer) 812 813 if bmp.IsOk(): 814 815 # Calculate the postion to place the image 816 imgHeight = bmp.GetHeight() 817 imgWidth = bmp.GetWidth() 818 819 if imageMarginX == 0: 820 xx = rect.x + (leftMarginWidth - imgWidth)/2 821 else: 822 xx = rect.x + ((leftMarginWidth - rect.height) - imgWidth)/2 + rect.height 823 824 yy = rect.y + (rect.height - imgHeight)/2 825 dc.DrawBitmap(bmp, xx, yy, True) 826 827 if item.GetKind() == wx.ITEM_CHECK: 828 829 # Checkable item 830 if item.IsChecked(): 831 832 # Draw surrounding rectangle around the selection box 833 xx = rect.x + 1 834 yy = rect.y + 1 835 rr = wx.Rect(xx, yy, rect.height-2, rect.height-2) 836 837 if not selected and self.highlightCheckAndRadio: 838 self.DrawButton(dc, rr, ControlFocus) 839 840 dc.DrawBitmap(item._checkMarkBmp, rr.x + (rr.width - 16)/2, rr.y + (rr.height - 16)/2, True) 841 842 if item.GetKind() == wx.ITEM_RADIO: 843 844 # Checkable item 845 if item.IsChecked(): 846 847 # Draw surrounding rectangle around the selection box 848 xx = rect.x + 1 849 yy = rect.y + 1 850 rr = wx.Rect(xx, yy, rect.height-2, rect.height-2) 851 852 if not selected and self.highlightCheckAndRadio: 853 self.DrawButton(dc, rr, ControlFocus) 854 855 dc.DrawBitmap(item._radioMarkBmp, rr.x + (rr.width - 16)/2, rr.y + (rr.height - 16)/2, True) 856 857 # Draw text - without accelerators 858 text = item.GetLabel() 859 860 if text: 861 862 font = item.GetFont() 863 if font is None: 864 font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) 865 866 if selected: 867 enabledTxtColour = colourutils.BestLabelColour(self.menuFocusFaceColour, bw=True) 868 else: 869 enabledTxtColour = colourutils.BestLabelColour(self.menuFaceColour, bw=True) 870 871 disabledTxtColour = self.itemTextColourDisabled 872 textColour = (item.IsEnabled() and [enabledTxtColour] or [disabledTxtColour])[0] 873 874 if item.IsEnabled() and item.GetTextColour(): 875 textColour = item.GetTextColour() 876 877 dc.SetFont(font) 878 w, h = dc.GetTextExtent(text) 879 dc.SetTextForeground(textColour) 880 881 if item._mnemonicIdx != wx.NOT_FOUND: 882 883 # We divide the drawing to 3 parts 884 text1 = text[0:item._mnemonicIdx] 885 text2 = text[item._mnemonicIdx] 886 text3 = text[item._mnemonicIdx+1:] 887 888 w1, dummy = dc.GetTextExtent(text1) 889 w2, dummy = dc.GetTextExtent(text2) 890 w3, dummy = dc.GetTextExtent(text3) 891 892 posx = xCoord + textX + borderXSize 893 posy = (itemHeight - h)/2 + yCoord 894 895 # Draw first part 896 dc.DrawText(text1, posx, posy) 897 898 # mnemonic 899 if "__WXGTK__" not in wx.Platform: 900 font.SetUnderlined(True) 901 dc.SetFont(font) 902 903 posx += w1 904 dc.DrawText(text2, posx, posy) 905 906 # last part 907 font.SetUnderlined(False) 908 dc.SetFont(font) 909 posx += w2 910 dc.DrawText(text3, posx, posy) 911 912 else: 913 914 w, h = dc.GetTextExtent(text) 915 dc.DrawText(text, xCoord + textX + borderXSize, (itemHeight - h)/2 + yCoord) 916 917 918 # Now draw accelerator 919 # Accelerators are aligned to the right 920 if item.GetAccelString(): 921 922 accelWidth, accelHeight = dc.GetTextExtent(item.GetAccelString()) 923 dc.DrawText(item.GetAccelString(), xCoord + rightMarginX - accelWidth, (itemHeight - accelHeight)/2 + yCoord) 924 925 # Check if this item has sub-menu - if it does, draw 926 # right arrow on the right margin 927 if item.GetSubMenu(): 928 929 # Draw arrow 930 rightArrowBmp = wx.Bitmap(menu_right_arrow_xpm) 931 rightArrowBmp.SetMask(wx.Mask(rightArrowBmp, wx.WHITE)) 932 933 xx = xCoord + rightMarginX + borderXSize 934 rr = wx.Rect(xx, rect.y + 1, rect.height-2, rect.height-2) 935 dc.DrawBitmap(rightArrowBmp, rr.x + 4, rr.y +(rr.height-16)/2, True) 936 937 938 def DrawMenuBarButton(self, dc, rect, state): 939 """ 940 Draws the highlight on a :class:`FlatMenuBar`. 941 942 :param `dc`: an instance of :class:`wx.DC`; 943 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 944 :param integer `state`: the button state. 945 """ 946 947 # switch according to the status 948 if state == ControlFocus: 949 penColour = self.menuBarFocusBorderColour 950 brushColour = self.menuBarFocusFaceColour 951 elif state == ControlPressed: 952 penColour = self.menuBarPressedBorderColour 953 brushColour = self.menuBarPressedFaceColour 954 955 dcsaver = DCSaver(dc) 956 dc.SetPen(wx.Pen(penColour)) 957 dc.SetBrush(wx.Brush(brushColour)) 958 dc.DrawRectangle(rect) 959 960 961 def DrawMenuButton(self, dc, rect, state): 962 """ 963 Draws the highlight on a FlatMenu 964 965 :param `dc`: an instance of :class:`wx.DC`; 966 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 967 :param integer `state`: the button state. 968 """ 969 970 # switch according to the status 971 if state == ControlFocus: 972 penColour = self.menuFocusBorderColour 973 brushColour = self.menuFocusFaceColour 974 elif state == ControlPressed: 975 penColour = self.menuPressedBorderColour 976 brushColour = self.menuPressedFaceColour 977 978 dcsaver = DCSaver(dc) 979 dc.SetPen(wx.Pen(penColour)) 980 dc.SetBrush(wx.Brush(brushColour)) 981 dc.DrawRectangle(rect) 982 983 984 def DrawScrollButton(self, dc, rect, state): 985 """ 986 Draws the scroll button 987 988 :param `dc`: an instance of :class:`wx.DC`; 989 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 990 :param integer `state`: the button state. 991 """ 992 993 if not self.scrollBarButtons: 994 return 995 996 colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 997 colour = ArtManager.Get().LightColour(colour, 30) 998 999 artMgr = ArtManager.Get() 1000 1001 # Keep old pen and brush 1002 dcsaver = DCSaver(dc) 1003 1004 # Define the rounded rectangle base on the given rect 1005 # we need an array of 9 points for it 1006 baseColour = colour 1007 1008 # Define the middle points 1009 leftPt = wx.Point(rect.x, rect.y + (rect.height / 2)) 1010 rightPt = wx.Point(rect.x + rect.width-1, rect.y + (rect.height / 2)) 1011 1012 # Define the top region 1013 top = wx.Rect((rect.GetLeft(), rect.GetTop()), rightPt) 1014 bottom = wx.Rect(leftPt, (rect.GetRight(), rect.GetBottom())) 1015 1016 upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \ 1017 concaveUpperBox, concaveLowerBox = self.GetColoursAccordingToState(state) 1018 1019 topStartColour = artMgr.LightColour(baseColour, upperBoxTopPercent) 1020 topEndColour = artMgr.LightColour(baseColour, upperBoxBottomPercent) 1021 bottomStartColour = artMgr.LightColour(baseColour, lowerBoxTopPercent) 1022 bottomEndColour = artMgr.LightColour(baseColour, lowerBoxBottomPercent) 1023 1024 artMgr.PaintStraightGradientBox(dc, top, topStartColour, topEndColour) 1025 artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour) 1026 1027 rr = wx.Rect(rect.x, rect.y, rect.width, rect.height) 1028 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1029 1030 frameColour = artMgr.LightColour(baseColour, 60) 1031 dc.SetPen(wx.Pen(frameColour)) 1032 dc.DrawRectangle(rr) 1033 1034 wc = artMgr.LightColour(baseColour, 80) 1035 dc.SetPen(wx.Pen(wc)) 1036 rr.Deflate(1, 1) 1037 dc.DrawRectangle(rr) 1038 1039 1040 def DrawButton(self, dc, rect, state, colour=None): 1041 """ 1042 Draws a button. 1043 1044 :param `dc`: an instance of :class:`wx.DC`; 1045 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 1046 :param integer `state`: the button state; 1047 :param `colour`: if not ``None``, an instance of :class:`wx.Colour` to be used to draw 1048 the :class:`FlatMenuItem` background. 1049 """ 1050 1051 # switch according to the status 1052 if state == ControlFocus: 1053 if colour == None: 1054 penColour = self.buttonFocusBorderColour 1055 brushColour = self.buttonFocusFaceColour 1056 else: 1057 penColour = colour 1058 brushColour = ArtManager.Get().LightColour(colour, 75) 1059 1060 elif state == ControlPressed: 1061 if colour == None: 1062 penColour = self.buttonPressedBorderColour 1063 brushColour = self.buttonPressedFaceColour 1064 else: 1065 penColour = colour 1066 brushColour = ArtManager.Get().LightColour(colour, 60) 1067 else: 1068 if colour == None: 1069 penColour = self.buttonBorderColour 1070 brushColour = self.buttonFaceColour 1071 else: 1072 penColour = colour 1073 brushColour = ArtManager.Get().LightColour(colour, 75) 1074 1075 dcsaver = DCSaver(dc) 1076 dc.SetPen(wx.Pen(penColour)) 1077 dc.SetBrush(wx.Brush(brushColour)) 1078 dc.DrawRectangle(rect) 1079 1080 1081 def DrawMenuBarBackground(self, dc, rect): 1082 """ 1083 Draws the menu bar background colour according to the menubar.GetBackgroundColour 1084 1085 :param `dc`: an instance of :class:`wx.DC`; 1086 :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle. 1087 """ 1088 1089 dcsaver = DCSaver(dc) 1090 1091 # fill with gradient 1092 colour = self.menuBarFaceColour 1093 1094 dc.SetPen(wx.Pen(colour)) 1095 dc.SetBrush(wx.Brush(colour)) 1096 dc.DrawRectangle(rect) 1097 1098 1099 def DrawMenuBar(self, menubar, dc): 1100 """ 1101 Draws everything for :class:`FlatMenuBar`. 1102 1103 :param `menubar`: an instance of :class:`FlatMenuBar`. 1104 :param `dc`: an instance of :class:`wx.DC`. 1105 """ 1106 1107 #artMgr = ArtManager.Get() 1108 fnt = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) 1109 textColour = colourutils.BestLabelColour(menubar.GetBackgroundColour(), bw=True) 1110 highlightTextColour = colourutils.BestLabelColour(self.menuBarFocusFaceColour, bw=True) 1111 1112 dc.SetFont(fnt) 1113 dc.SetTextForeground(textColour) 1114 1115 clientRect = menubar.GetClientRect() 1116 1117 self.DrawMenuBarBackground(dc, clientRect) 1118 1119 padding, dummy = dc.GetTextExtent("W") 1120 1121 posx = 0 1122 posy = menubar._margin 1123 1124 # --------------------------------------------------------------------------- 1125 # Draw as much items as we can if the screen is not wide enough, add all 1126 # missing items to a drop down menu 1127 # --------------------------------------------------------------------------- 1128 menuBarRect = menubar.GetClientRect() 1129 1130 # mark all items as non-visibles at first 1131 for item in menubar._items: 1132 item.SetRect(wx.Rect()) 1133 1134 for item in menubar._items: 1135 1136 # Handle accelerator ('&') 1137 title = item.GetTitle() 1138 1139 fixedText = title 1140 location, labelOnly = GetAccelIndex(fixedText) 1141 1142 # Get the menu item rect 1143 textWidth, textHeight = dc.GetTextExtent(fixedText) 1144 #rect = wx.Rect(posx+menubar._spacer/2, posy, textWidth, textHeight) 1145 rect = wx.Rect(posx+padding/2, posy, textWidth, textHeight) 1146 1147 # Can we draw more?? 1148 # the +DROP_DOWN_ARROW_WIDTH is the width of the drop down arrow 1149 if posx + rect.width + DROP_DOWN_ARROW_WIDTH >= menuBarRect.width: 1150 break 1151 1152 # In this style the button highlight includes the menubar margin 1153 button_rect = wx.Rect(*rect) 1154 button_rect.height = menubar._menuBarHeight 1155 #button_rect.width = rect.width + menubar._spacer 1156 button_rect.width = rect.width + padding 1157 button_rect.x = posx 1158 button_rect.y = 0 1159 1160 # Keep the item rectangle, will be used later in functions such 1161 # as 'OnLeftDown', 'OnMouseMove' 1162 copy = wx.Rect(*button_rect) 1163 #copy.Inflate(0, menubar._spacer) 1164 item.SetRect(copy) 1165 1166 selected = False 1167 if item.GetState() == ControlFocus: 1168 self.DrawMenuBarButton(dc, button_rect, ControlFocus) 1169 dc.SetTextForeground(highlightTextColour) 1170 selected = True 1171 else: 1172 dc.SetTextForeground(textColour) 1173 1174 ww, hh = dc.GetTextExtent(labelOnly) 1175 textOffset = (rect.width - ww) / 2 1176 1177 if not menubar._isLCD and item.GetTextBitmap().IsOk() and not selected: 1178 dc.DrawBitmap(item.GetTextBitmap(), rect.x, rect.y, True) 1179 elif not menubar._isLCD and item.GetSelectedTextBitmap().IsOk() and selected: 1180 dc.DrawBitmap(item.GetSelectedTextBitmap(), rect.x, rect.y, True) 1181 else: 1182 if not menubar._isLCD: 1183 # Draw the text on a bitmap using memory dc, 1184 # so on following calls we will use this bitmap instead 1185 # of calculating everything from scratch 1186 bmp = wx.Bitmap(rect.width, rect.height) 1187 memDc = wx.MemoryDC() 1188 memDc.SelectObject(bmp) 1189 if selected: 1190 memDc.SetTextForeground(highlightTextColour) 1191 else: 1192 memDc.SetTextForeground(textColour) 1193 1194 # Fill the bitmap with the masking colour 1195 memDc.SetPen(wx.Pen(wx.Colour(255, 0, 0)) ) 1196 memDc.SetBrush(wx.Brush(wx.Colour(255, 0, 0)) ) 1197 memDc.DrawRectangle(0, 0, rect.width, rect.height) 1198 memDc.SetFont(fnt) 1199 1200 if location == wx.NOT_FOUND or location >= len(fixedText): 1201 # draw the text 1202 if not menubar._isLCD: 1203 memDc.DrawText(title, textOffset, 0) 1204 dc.DrawText(title, rect.x + textOffset, rect.y) 1205 1206 else: 1207 1208 # underline the first '&' 1209 before = labelOnly[0:location] 1210 underlineLetter = labelOnly[location] 1211 after = labelOnly[location+1:] 1212 1213 # before 1214 if not menubar._isLCD: 1215 memDc.DrawText(before, textOffset, 0) 1216 dc.DrawText(before, rect.x + textOffset, rect.y) 1217 1218 # underlineLetter 1219 if "__WXGTK__" not in wx.Platform: 1220 w1, h = dc.GetTextExtent(before) 1221 fnt.SetUnderlined(True) 1222 dc.SetFont(fnt) 1223 dc.DrawText(underlineLetter, rect.x + w1 + textOffset, rect.y) 1224 if not menubar._isLCD: 1225 memDc.SetFont(fnt) 1226 memDc.DrawText(underlineLetter, textOffset + w1, 0) 1227 1228 else: 1229 w1, h = dc.GetTextExtent(before) 1230 dc.DrawText(underlineLetter, rect.x + w1 + textOffset, rect.y) 1231 if not menubar._isLCD: 1232 memDc.DrawText(underlineLetter, textOffset + w1, 0) 1233 1234 # Draw the underline ourselves since using the Underline in GTK, 1235 # causes the line to be too close to the letter 1236 1237 uderlineLetterW, uderlineLetterH = dc.GetTextExtent(underlineLetter) 1238 dc.DrawLine(rect.x + w1 + textOffset, rect.y + uderlineLetterH - 2, 1239 rect.x + w1 + textOffset + uderlineLetterW, rect.y + uderlineLetterH - 2) 1240 1241 # after 1242 w2, h = dc.GetTextExtent(underlineLetter) 1243 fnt.SetUnderlined(False) 1244 dc.SetFont(fnt) 1245 dc.DrawText(after, rect.x + w1 + w2 + textOffset, rect.y) 1246 if not menubar._isLCD: 1247 memDc.SetFont(fnt) 1248 memDc.DrawText(after, w1 + w2 + textOffset, 0) 1249 1250 if not menubar._isLCD: 1251 memDc.SelectObject(wx.NullBitmap) 1252 # Set masking colour to the bitmap 1253 bmp.SetMask(wx.Mask(bmp, wx.Colour(255, 0, 0))) 1254 if selected: 1255 item.SetSelectedTextBitmap(bmp) 1256 else: 1257 item.SetTextBitmap(bmp) 1258 1259 posx += rect.width + padding # + menubar._spacer 1260 1261 # Get a background image of the more menu button 1262 moreMenubtnBgBmpRect = wx.Rect(*menubar.GetMoreMenuButtonRect()) 1263 if not menubar._moreMenuBgBmp: 1264 menubar._moreMenuBgBmp = wx.Bitmap(moreMenubtnBgBmpRect.width, moreMenubtnBgBmpRect.height) 1265 1266 if menubar._showToolbar and len(menubar._tbButtons) > 0: 1267 rectX = 0 1268 rectWidth = clientRect.width - moreMenubtnBgBmpRect.width 1269 if len(menubar._items) == 0: 1270 rectHeight = clientRect.height 1271 rectY = 0 1272 else: 1273 rectHeight = clientRect.height - menubar._menuBarHeight 1274 rectY = menubar._menuBarHeight 1275 rr = wx.Rect(rectX, rectY, rectWidth, rectHeight) 1276 self.DrawToolBarBg(dc, rr) 1277 menubar.DrawToolbar(dc, rr) 1278 1279 if menubar._showCustomize or menubar.GetInvisibleMenuItemCount() > 0 or menubar.GetInvisibleToolbarItemCount() > 0: 1280 memDc = wx.MemoryDC() 1281 memDc.SelectObject(menubar._moreMenuBgBmp) 1282 try: 1283 memDc.Blit(0, 0, menubar._moreMenuBgBmp.GetWidth(), menubar._moreMenuBgBmp.GetHeight(), dc, 1284 moreMenubtnBgBmpRect.x, moreMenubtnBgBmpRect.y) 1285 except: 1286 pass 1287 memDc.SelectObject(wx.NullBitmap) 1288 1289 # Draw the drop down arrow button 1290 menubar.DrawMoreButton(dc, menubar._dropDownButtonState) 1291 # Set the button rect 1292 menubar._dropDownButtonArea = moreMenubtnBgBmpRect 1293 1294 1295 def DrawMenu(self, flatmenu, dc): 1296 """ 1297 Draws the menu. 1298 1299 :param `flatmenu`: the :class:`FlatMenu` instance we need to paint; 1300 :param `dc`: an instance of :class:`wx.DC`. 1301 """ 1302 1303 menuRect = flatmenu.GetClientRect() 1304 menuBmp = wx.Bitmap(menuRect.width, menuRect.height) 1305 1306 mem_dc = wx.MemoryDC() 1307 mem_dc.SelectObject(menuBmp) 1308 1309 # colour the menu face with background colour 1310 backColour = self.menuFaceColour 1311 penColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW) 1312 1313 backBrush = wx.Brush(backColour) 1314 pen = wx.Pen(penColour) 1315 1316 mem_dc.SetPen(pen) 1317 mem_dc.SetBrush(backBrush) 1318 mem_dc.DrawRectangle(menuRect) 1319 1320 backgroundImage = flatmenu._backgroundImage 1321 1322 if backgroundImage: 1323 mem_dc.DrawBitmap(backgroundImage, flatmenu._leftMarginWidth, 0, True) 1324 1325 # draw items 1326 posy = 3 1327 nItems = len(flatmenu._itemsArr) 1328 1329 # make all items as non-visible first 1330 for item in flatmenu._itemsArr: 1331 item.Show(False) 1332 1333 visibleItems = 0 1334 screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) 1335 1336 numCols = flatmenu.GetNumberColumns() 1337 switch, posx, index = 1e6, 0, 0 1338 if numCols > 1: 1339 switch = int(math.ceil((nItems - flatmenu._first)/float(numCols))) 1340 1341 # If we have to scroll and are not using the scroll bar buttons we need to draw 1342 # the scroll up menu item at the top. 1343 if not self.scrollBarButtons and flatmenu._showScrollButtons: 1344 posy += flatmenu.GetItemHeight() 1345 1346 for nCount in range(flatmenu._first, nItems): 1347 1348 visibleItems += 1 1349 item = flatmenu._itemsArr[nCount] 1350 self.DrawMenuItem(item, mem_dc, posx, posy, 1351 flatmenu._imgMarginX, flatmenu._markerMarginX, 1352 flatmenu._textX, flatmenu._rightMarginPosX, 1353 nCount == flatmenu._selectedItem, 1354 backgroundImage=backgroundImage) 1355 posy += item.GetHeight() 1356 item.Show() 1357 1358 if visibleItems >= switch: 1359 posy = 2 1360 index += 1 1361 posx = flatmenu._menuWidth*index 1362 visibleItems = 0 1363 1364 # make sure we draw only visible items 1365 pp = flatmenu.ClientToScreen(wx.Point(0, posy)) 1366 1367 menuBottom = (self.scrollBarButtons and [pp.y] or [pp.y + flatmenu.GetItemHeight()*2])[0] 1368 1369 if menuBottom > screenHeight: 1370 break 1371 1372 if flatmenu._showScrollButtons: 1373 if flatmenu._upButton: 1374 flatmenu._upButton.Draw(mem_dc) 1375 if flatmenu._downButton: 1376 flatmenu._downButton.Draw(mem_dc) 1377 1378 dc.Blit(0, 0, menuBmp.GetWidth(), menuBmp.GetHeight(), mem_dc, 0, 0) 1379 1380 1381# ---------------------------------------------------------------------------- # 1382# Class FMRendererMSOffice2007 1383# ---------------------------------------------------------------------------- # 1384 1385class FMRendererMSOffice2007(FMRenderer): 1386 """ Windows Office 2007 style. """ 1387 1388 def __init__(self): 1389 """ Default class constructor. """ 1390 1391 FMRenderer.__init__(self) 1392 1393 self.drawLeftMargin = True 1394 self.separatorHeight = 3 1395 self.highlightCheckAndRadio = True 1396 self.scrollBarButtons = True # Display scrollbar buttons if the menu doesn't fit on the screen 1397 1398 self.menuBarFaceColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) 1399 1400 self.buttonBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1401 self.buttonFaceColour = ArtManager.Get().LightColour(self.buttonBorderColour, 75) 1402 self.buttonFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1403 self.buttonFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1404 self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1405 self.buttonPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1406 1407 self.menuFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1408 self.menuFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1409 self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1410 self.menuPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1411 1412 self.menuBarFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1413 self.menuBarFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1414 self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1415 self.menuBarPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1416 1417 1418 def DrawLeftMargin(self, item, dc, menuRect): 1419 """ 1420 Draws the menu left margin. 1421 1422 :param `item`: the :class:`FlatMenuItem` to paint; 1423 :param `dc`: an instance of :class:`wx.DC`; 1424 :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle. 1425 """ 1426 1427 # Construct the margin rectangle 1428 marginRect = wx.Rect(menuRect.x+1, menuRect.y, item._parentMenu.GetLeftMarginWidth(), menuRect.height) 1429 1430 # Set the gradient colours 1431 artMgr = ArtManager.Get() 1432 faceColour = self.menuFaceColour 1433 1434 dcsaver = DCSaver(dc) 1435 marginColour = artMgr.DarkColour(faceColour, 5) 1436 dc.SetPen(wx.Pen(marginColour)) 1437 dc.SetBrush(wx.Brush(marginColour)) 1438 dc.DrawRectangle(marginRect) 1439 1440 dc.SetPen(wx.WHITE_PEN) 1441 dc.DrawLine(marginRect.x + marginRect.width, marginRect.y, marginRect.x + marginRect.width, marginRect.y + marginRect.height) 1442 1443 borderColour = artMgr.DarkColour(faceColour, 10) 1444 dc.SetPen(wx.Pen(borderColour)) 1445 dc.DrawLine(marginRect.x + marginRect.width-1, marginRect.y, marginRect.x + marginRect.width-1, marginRect.y + marginRect.height) 1446 1447 1448 def DrawMenuButton(self, dc, rect, state): 1449 """ 1450 Draws the highlight on a :class:`FlatMenu`. 1451 1452 :param `dc`: an instance of :class:`wx.DC`; 1453 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 1454 :param integer `state`: the button state. 1455 """ 1456 1457 self.DrawButton(dc, rect, state) 1458 1459 1460 def DrawMenuBarButton(self, dc, rect, state): 1461 """ 1462 Draws the highlight on a :class:`FlatMenuBar`. 1463 1464 :param `dc`: an instance of :class:`wx.DC`; 1465 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 1466 :param integer `state`: the button state. 1467 """ 1468 1469 self.DrawButton(dc, rect, state) 1470 1471 1472 def DrawButton(self, dc, rect, state, colour=None): 1473 """ 1474 Draws a button using the Office 2007 theme. 1475 1476 :param `dc`: an instance of :class:`wx.DC`; 1477 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 1478 :param integer `state`: the button state; 1479 :param `colour`: if not ``None``, an instance of :class:`wx.Colour` to be used to draw 1480 the :class:`FlatMenuItem` background. 1481 """ 1482 1483 colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1484 colour = ArtManager.Get().LightColour(colour, 30) 1485 self.DrawButtonColour(dc, rect, state, colour) 1486 1487 1488 def DrawButtonColour(self, dc, rect, state, colour): 1489 """ 1490 Draws a button using the Office 2007 theme. 1491 1492 :param `dc`: an instance of :class:`wx.DC`; 1493 :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle; 1494 :param integer `state`: the button state; 1495 :param `colour`: a valid :class:`wx.Colour` instance. 1496 """ 1497 1498 artMgr = ArtManager.Get() 1499 1500 # Keep old pen and brush 1501 dcsaver = DCSaver(dc) 1502 1503 # Define the rounded rectangle base on the given rect 1504 # we need an array of 9 points for it 1505 baseColour = colour 1506 1507 # Define the middle points 1508 leftPt = wx.Point(rect.x, rect.y + (rect.height / 2)) 1509 rightPt = wx.Point(rect.x + rect.width-1, rect.y + (rect.height / 2)) 1510 1511 # Define the top region 1512 top = wx.Rect((rect.GetLeft(), rect.GetTop()), rightPt) 1513 bottom = wx.Rect(leftPt, (rect.GetRight(), rect.GetBottom())) 1514 1515 upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \ 1516 concaveUpperBox, concaveLowerBox = self.GetColoursAccordingToState(state) 1517 1518 topStartColour = artMgr.LightColour(baseColour, upperBoxTopPercent) 1519 topEndColour = artMgr.LightColour(baseColour, upperBoxBottomPercent) 1520 bottomStartColour = artMgr.LightColour(baseColour, lowerBoxTopPercent) 1521 bottomEndColour = artMgr.LightColour(baseColour, lowerBoxBottomPercent) 1522 1523 artMgr.PaintStraightGradientBox(dc, top, topStartColour, topEndColour) 1524 artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour) 1525 1526 rr = wx.Rect(rect.x, rect.y, rect.width, rect.height) 1527 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1528 1529 frameColour = artMgr.LightColour(baseColour, 60) 1530 dc.SetPen(wx.Pen(frameColour)) 1531 dc.DrawRectangle(rr) 1532 1533 wc = artMgr.LightColour(baseColour, 80) 1534 dc.SetPen(wx.Pen(wc)) 1535 rr.Deflate(1, 1) 1536 dc.DrawRectangle(rr) 1537 1538 1539 def DrawMenuBarBackground(self, dc, rect): 1540 """ 1541 Draws the menu bar background according to the active theme. 1542 1543 :param `dc`: an instance of :class:`wx.DC`; 1544 :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle. 1545 """ 1546 1547 # Keep old pen and brush 1548 dcsaver = DCSaver(dc) 1549 artMgr = ArtManager.Get() 1550 baseColour = self.menuBarFaceColour 1551 1552 dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) 1553 dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) 1554 dc.DrawRectangle(rect) 1555 1556 # Define the rounded rectangle base on the given rect 1557 # we need an array of 9 points for it 1558 regPts = [wx.Point() for ii in range(9)] 1559 radius = 2 1560 1561 regPts[0] = wx.Point(rect.x, rect.y + radius) 1562 regPts[1] = wx.Point(rect.x+radius, rect.y) 1563 regPts[2] = wx.Point(rect.x+rect.width-radius-1, rect.y) 1564 regPts[3] = wx.Point(rect.x+rect.width-1, rect.y + radius) 1565 regPts[4] = wx.Point(rect.x+rect.width-1, rect.y + rect.height - radius - 1) 1566 regPts[5] = wx.Point(rect.x+rect.width-radius-1, rect.y + rect.height-1) 1567 regPts[6] = wx.Point(rect.x+radius, rect.y + rect.height-1) 1568 regPts[7] = wx.Point(rect.x, rect.y + rect.height - radius - 1) 1569 regPts[8] = regPts[0] 1570 1571 # Define the middle points 1572 1573 factor = artMgr.GetMenuBgFactor() 1574 1575 leftPt1 = wx.Point(rect.x, rect.y + (rect.height / factor)) 1576 leftPt2 = wx.Point(rect.x, rect.y + (rect.height / factor)*(factor-1)) 1577 1578 rightPt1 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)) 1579 rightPt2 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)*(factor-1)) 1580 1581 # Define the top region 1582 topReg = [wx.Point() for ii in range(7)] 1583 topReg[0] = regPts[0] 1584 topReg[1] = regPts[1] 1585 topReg[2] = wx.Point(regPts[2].x+1, regPts[2].y) 1586 topReg[3] = wx.Point(regPts[3].x + 1, regPts[3].y) 1587 topReg[4] = wx.Point(rightPt1.x, rightPt1.y+1) 1588 topReg[5] = wx.Point(leftPt1.x, leftPt1.y+1) 1589 topReg[6] = topReg[0] 1590 1591 # Define the middle region 1592 middle = wx.Rect(leftPt1, wx.Point(rightPt2.x - 2, rightPt2.y)) 1593 1594 # Define the bottom region 1595 bottom = wx.Rect(leftPt2, wx.Point(rect.GetRight() - 1, rect.GetBottom())) 1596 1597 topStartColour = artMgr.LightColour(baseColour, 90) 1598 topEndColour = artMgr.LightColour(baseColour, 60) 1599 bottomStartColour = artMgr.LightColour(baseColour, 40) 1600 bottomEndColour = artMgr.LightColour(baseColour, 20) 1601 1602 topRegion = wx.Region(topReg) 1603 1604 artMgr.PaintGradientRegion(dc, topRegion, topStartColour, topEndColour) 1605 artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour) 1606 artMgr.PaintStraightGradientBox(dc, middle, topEndColour, bottomStartColour) 1607 1608 1609 def DrawToolBarBg(self, dc, rect): 1610 """ 1611 Draws the toolbar background according to the active theme. 1612 1613 :param `dc`: an instance of :class:`wx.DC`; 1614 :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle. 1615 """ 1616 1617 artMgr = ArtManager.Get() 1618 1619 if not artMgr.GetRaiseToolbar(): 1620 return 1621 1622 # Keep old pen and brush 1623 dcsaver = DCSaver(dc) 1624 1625 baseColour = self.menuBarFaceColour 1626 baseColour = artMgr.LightColour(baseColour, 20) 1627 1628 dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) 1629 dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) 1630 dc.DrawRectangle(rect) 1631 1632 radius = 2 1633 1634 # Define the rounded rectangle base on the given rect 1635 # we need an array of 9 points for it 1636 regPts = [None]*9 1637 1638 regPts[0] = wx.Point(rect.x, rect.y + radius) 1639 regPts[1] = wx.Point(rect.x+radius, rect.y) 1640 regPts[2] = wx.Point(rect.x+rect.width-radius-1, rect.y) 1641 regPts[3] = wx.Point(rect.x+rect.width-1, rect.y + radius) 1642 regPts[4] = wx.Point(rect.x+rect.width-1, rect.y + rect.height - radius - 1) 1643 regPts[5] = wx.Point(rect.x+rect.width-radius-1, rect.y + rect.height-1) 1644 regPts[6] = wx.Point(rect.x+radius, rect.y + rect.height-1) 1645 regPts[7] = wx.Point(rect.x, rect.y + rect.height - radius - 1) 1646 regPts[8] = regPts[0] 1647 1648 # Define the middle points 1649 factor = artMgr.GetMenuBgFactor() 1650 1651 leftPt1 = wx.Point(rect.x, rect.y + (rect.height / factor)) 1652 rightPt1 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)) 1653 1654 leftPt2 = wx.Point(rect.x, rect.y + (rect.height / factor)*(factor-1)) 1655 rightPt2 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)*(factor-1)) 1656 1657 # Define the top region 1658 topReg = [None]*7 1659 topReg[0] = regPts[0] 1660 topReg[1] = regPts[1] 1661 topReg[2] = wx.Point(regPts[2].x+1, regPts[2].y) 1662 topReg[3] = wx.Point(regPts[3].x + 1, regPts[3].y) 1663 topReg[4] = wx.Point(rightPt1.x, rightPt1.y+1) 1664 topReg[5] = wx.Point(leftPt1.x, leftPt1.y+1) 1665 topReg[6] = topReg[0] 1666 1667 # Define the middle region 1668 middle = wx.Rect(leftPt1, wx.Point(rightPt2.x - 2, rightPt2.y)) 1669 1670 # Define the bottom region 1671 bottom = wx.Rect(leftPt2, wx.Point(rect.GetRight() - 1, rect.GetBottom())) 1672 1673 topStartColour = artMgr.LightColour(baseColour, 90) 1674 topEndColour = artMgr.LightColour(baseColour, 60) 1675 bottomStartColour = artMgr.LightColour(baseColour, 40) 1676 bottomEndColour = artMgr.LightColour(baseColour, 20) 1677 1678 topRegion = wx.Region(topReg) 1679 1680 artMgr.PaintGradientRegion(dc, topRegion, topStartColour, topEndColour) 1681 artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour) 1682 artMgr.PaintStraightGradientBox(dc, middle, topEndColour, bottomStartColour) 1683 1684 artMgr.DrawBitmapShadow(dc, rect) 1685 1686 1687 def GetTextColourEnable(self): 1688 """ Returns the colour used for text colour when enabled. """ 1689 1690 return wx.Colour("MIDNIGHT BLUE") 1691 1692 1693# ---------------------------------------------------------------------------- # 1694# Class FMRendererVista 1695# ---------------------------------------------------------------------------- # 1696 1697class FMRendererVista(FMRendererMSOffice2007): 1698 """ Windows Vista-like style. """ 1699 1700 def __init__(self): 1701 """ Default class constructor. """ 1702 1703 FMRendererMSOffice2007.__init__(self) 1704 1705 1706 def DrawButtonColour(self, dc, rect, state, colour): 1707 """ 1708 Draws a button using the Vista theme. 1709 1710 :param `dc`: an instance of :class:`wx.DC`; 1711 :param `rect`: the an instance of :class:`wx.Rect`, representing the button client rectangle; 1712 :param integer `state`: the button state; 1713 :param `colour`: a valid :class:`wx.Colour` instance. 1714 """ 1715 1716 artMgr = ArtManager.Get() 1717 1718 # Keep old pen and brush 1719 dcsaver = DCSaver(dc) 1720 1721 outer = rgbSelectOuter 1722 inner = rgbSelectInner 1723 top = rgbSelectTop 1724 bottom = rgbSelectBottom 1725 1726 bdrRect = wx.Rect(*rect) 1727 filRect = wx.Rect(*rect) 1728 filRect.Deflate(1,1) 1729 1730 r1, g1, b1 = int(top.Red()), int(top.Green()), int(top.Blue()) 1731 r2, g2, b2 = int(bottom.Red()), int(bottom.Green()), int(bottom.Blue()) 1732 dc.GradientFillLinear(filRect, top, bottom, wx.SOUTH) 1733 1734 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1735 dc.SetPen(wx.Pen(outer)) 1736 dc.DrawRoundedRectangle(bdrRect, 3) 1737 bdrRect.Deflate(1, 1) 1738 dc.SetPen(wx.Pen(inner)) 1739 dc.DrawRoundedRectangle(bdrRect, 2) 1740 1741 1742# ---------------------------------------------------------------------------- # 1743# Class FMRendererXP 1744# ---------------------------------------------------------------------------- # 1745 1746class FMRendererXP(FMRenderer): 1747 """ Xp-Style renderer. """ 1748 1749 def __init__(self): 1750 """ Default class constructor. """ 1751 1752 FMRenderer.__init__(self) 1753 1754 self.drawLeftMargin = True 1755 self.separatorHeight = 3 1756 self.highlightCheckAndRadio = True 1757 self.scrollBarButtons = True # Display scrollbar buttons if the menu doesn't fit on the screen 1758 1759 self.buttonBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1760 self.buttonFaceColour = ArtManager.Get().LightColour(self.buttonBorderColour, 75) 1761 self.buttonFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1762 self.buttonFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1763 self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1764 self.buttonPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1765 1766 self.menuFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1767 self.menuFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1768 self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1769 self.menuPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1770 1771 self.menuBarFocusBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1772 self.menuBarFocusFaceColour = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75) 1773 self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) 1774 self.menuBarPressedFaceColour = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60) 1775 1776 1777 def DrawLeftMargin(self, item, dc, menuRect): 1778 """ 1779 Draws the menu left margin. 1780 1781 :param `item`: the :class:`FlatMenuItem` to paint; 1782 :param `dc`: an instance of :class:`wx.DC`; 1783 :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle. 1784 """ 1785 1786 # Construct the margin rectangle 1787 marginRect = wx.Rect(menuRect.x+1, menuRect.y, item._parentMenu.GetLeftMarginWidth(), menuRect.height) 1788 1789 # Set the gradient colours 1790 artMgr = ArtManager.Get() 1791 faceColour = self.menuFaceColour 1792 1793 startColour = artMgr.DarkColour(faceColour, 20) 1794 endColour = faceColour 1795 artMgr.PaintStraightGradientBox(dc, marginRect, startColour, endColour, False) 1796 1797 1798 def DrawMenuBarBackground(self, dc, rect): 1799 """ 1800 Draws the menu bar background according to the active theme. 1801 1802 :param `dc`: an instance of :class:`wx.DC`; 1803 :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle. 1804 """ 1805 1806 # For office style, we simple draw a rectangle with a gradient colouring 1807 artMgr = ArtManager.Get() 1808 vertical = artMgr.GetMBVerticalGradient() 1809 1810 dcsaver = DCSaver(dc) 1811 1812 # fill with gradient 1813 startColour = artMgr.GetMenuBarFaceColour() 1814 if artMgr.IsDark(startColour): 1815 startColour = artMgr.LightColour(startColour, 50) 1816 1817 endColour = artMgr.LightColour(startColour, 90) 1818 artMgr.PaintStraightGradientBox(dc, rect, startColour, endColour, vertical) 1819 1820 # Draw the border 1821 if artMgr.GetMenuBarBorder(): 1822 1823 dc.SetPen(wx.Pen(startColour)) 1824 dc.SetBrush(wx.TRANSPARENT_BRUSH) 1825 dc.DrawRectangle(rect) 1826 1827 1828 def DrawToolBarBg(self, dc, rect): 1829 """ 1830 Draws the toolbar background according to the active theme. 1831 1832 :param `dc`: an instance of :class:`wx.DC`; 1833 :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle. 1834 """ 1835 1836 artMgr = ArtManager.Get() 1837 1838 if not artMgr.GetRaiseToolbar(): 1839 return 1840 1841 # For office style, we simple draw a rectangle with a gradient colouring 1842 vertical = artMgr.GetMBVerticalGradient() 1843 1844 dcsaver = DCSaver(dc) 1845 1846 # fill with gradient 1847 startColour = artMgr.GetMenuBarFaceColour() 1848 if artMgr.IsDark(startColour): 1849 startColour = artMgr.LightColour(startColour, 50) 1850 1851 startColour = artMgr.LightColour(startColour, 20) 1852 1853 endColour = artMgr.LightColour(startColour, 90) 1854 artMgr.PaintStraightGradientBox(dc, rect, startColour, endColour, vertical) 1855 artMgr.DrawBitmapShadow(dc, rect) 1856 1857 1858 def GetTextColourEnable(self): 1859 """ Returns the colour used for text colour when enabled. """ 1860 1861 return wx.BLACK 1862 1863 1864# ---------------------------------------------------------------------------- 1865# File history (a.k.a. MRU, most recently used, files list) 1866# ---------------------------------------------------------------------------- 1867 1868def GetMRUEntryLabel(n, path): 1869 """ 1870 Returns the string used for the MRU list items in the menu. 1871 1872 :param integer `n`: the index of the file name in the MRU list; 1873 :param string `path`: the full path of the file name. 1874 1875 :note: The index `n` is 0-based, as usual, but the strings start from 1. 1876 """ 1877 1878 # we need to quote '&' characters which are used for mnemonics 1879 pathInMenu = path.replace("&", "&&") 1880 return "&%d %s"%(n + 1, pathInMenu) 1881 1882 1883# ---------------------------------------------------------------------------- 1884# File history management 1885# ---------------------------------------------------------------------------- 1886 1887class FileHistory(object): 1888 """ 1889 The :class:`FileHistory` encapsulates a user interface convenience, the list of most 1890 recently visited files as shown on a menu (usually the File menu). 1891 1892 :class:`FileHistory` can manage one or more file menus. More than one menu may be 1893 required in an MDI application, where the file history should appear on each MDI 1894 child menu as well as the MDI parent frame. 1895 """ 1896 1897 def __init__(self, maxFiles=9, idBase=wx.ID_FILE1): 1898 """ 1899 Default class constructor. 1900 1901 :param integer `maxFiles`: the maximum number of files that should be stored and displayed; 1902 :param integer `idBase`: defaults to ``wx.ID_FILE1`` and represents the id given to the first 1903 history menu item. 1904 1905 :note: Since menu items can't share the same ID you should change `idBase` to one of 1906 your own defined IDs when using more than one :class:`FileHistory` in your application. 1907 """ 1908 1909 # The ID of the first history menu item (Doesn't have to be wxID_FILE1) 1910 self._idBase = idBase 1911 1912 # Last n files 1913 self._fileHistory = [] 1914 1915 # Menus to maintain (may need several for an MDI app) 1916 self._fileMenus = [] 1917 1918 # Max files to maintain 1919 self._fileMaxFiles = maxFiles 1920 1921 1922 def GetMaxFiles(self): 1923 """ Returns the maximum number of files that can be stored. """ 1924 1925 return self._fileMaxFiles 1926 1927 1928 # Accessors 1929 def GetHistoryFile(self, index): 1930 """ 1931 Returns the file at this index (zero-based). 1932 1933 :param integer `index`: the index at which the file is stored in the file list (zero-based). 1934 """ 1935 1936 return self._fileHistory[index] 1937 1938 1939 def GetCount(self): 1940 """ Returns the number of files currently stored in the file history. """ 1941 1942 return len(self._fileHistory) 1943 1944 1945 def GetMenus(self): 1946 """ 1947 Returns the list of menus that are managed by this file history object. 1948 1949 :see: :meth:`~FileHistory.UseMenu`. 1950 """ 1951 1952 return self._fileMenus 1953 1954 1955 # Set/get base id 1956 def SetBaseId(self, baseId): 1957 """ 1958 Sets the base identifier for the range used for appending items. 1959 1960 :param integer `baseId`: the base identifier for the range used for appending items. 1961 """ 1962 1963 self._idBase = baseId 1964 1965 1966 def GetBaseId(self): 1967 """ Returns the base identifier for the range used for appending items. """ 1968 1969 return self._idBase 1970 1971 1972 def GetNoHistoryFiles(self): 1973 """ Returns the number of files currently stored in the file history. """ 1974 1975 return self.GetCount() 1976 1977 1978 def AddFileToHistory(self, fnNew): 1979 """ 1980 Adds a file to the file history list, if the object has a pointer to an 1981 appropriate file menu. 1982 1983 :param string `fnNew`: the file name to add to the history list. 1984 """ 1985 1986 # check if we don't already have this file 1987 numFiles = len(self._fileHistory) 1988 1989 for index, fileH in enumerate(self._fileHistory): 1990 if fnNew == fileH: 1991 # we do have it, move it to the top of the history 1992 self.RemoveFileFromHistory(index) 1993 numFiles -= 1 1994 break 1995 1996 # if we already have a full history, delete the one at the end 1997 if numFiles == self._fileMaxFiles: 1998 self.RemoveFileFromHistory(numFiles-1) 1999 2000 # add a new menu item to all file menus (they will be updated below) 2001 for menu in self._fileMenus: 2002 if numFiles == 0 and menu.GetMenuItemCount() > 0: 2003 menu.AppendSeparator() 2004 2005 # label doesn't matter, it will be set below anyhow, but it can't 2006 # be empty (this is supposed to indicate a stock item) 2007 menu.Append(self._idBase + numFiles, " ") 2008 2009 # insert the new file in the beginning of the file history 2010 self._fileHistory.insert(0, fnNew) 2011 numFiles += 1 2012 2013 # update the labels in all menus 2014 for index in range(numFiles): 2015 2016 # if in same directory just show the filename otherwise the full path 2017 fnOld = self._fileHistory[index] 2018 oldPath, newPath = os.path.split(fnOld)[0], os.path.split(fnNew)[0] 2019 2020 if oldPath == newPath: 2021 pathInMenu = os.path.split(fnOld)[1] 2022 2023 else: 2024 # file in different directory 2025 # absolute path could also set relative path 2026 pathInMenu = self._fileHistory[index] 2027 2028 for menu in self._fileMenus: 2029 menu.SetLabel(self._idBase + index, GetMRUEntryLabel(index, pathInMenu)) 2030 2031 2032 def RemoveFileFromHistory(self, index): 2033 """ 2034 Removes the specified file from the history. 2035 2036 :param integer `index`: the zero-based index indicating the file name position in 2037 the file list. 2038 """ 2039 2040 numFiles = len(self._fileHistory) 2041 if index >= numFiles: 2042 raise Exception("Invalid index in RemoveFileFromHistory: %d (only %d files)"%(index, numFiles)) 2043 2044 # delete the element from the array 2045 self._fileHistory.pop(index) 2046 numFiles -= 1 2047 2048 for menu in self._fileMenus: 2049 # shift filenames up 2050 for j in range(numFiles): 2051 menu.SetLabel(self._idBase + j, GetMRUEntryLabel(j, self._fileHistory[j])) 2052 2053 # delete the last menu item which is unused now 2054 lastItemId = self._idBase + numFiles 2055 if menu.FindItem(lastItemId): 2056 menu.Delete(lastItemId) 2057 2058 if not self._fileHistory: 2059 lastMenuItem = menu.GetMenuItems()[-1] 2060 if lastMenuItem.IsSeparator(): 2061 menu.Delete(lastMenuItem) 2062 2063 #else: menu is empty somehow 2064 2065 2066 def UseMenu(self, menu): 2067 """ 2068 Adds this menu to the list of those menus that are managed by this file history 2069 object. 2070 2071 :param `menu`: an instance of :class:`FlatMenu`. 2072 2073 :see: :meth:`~FileHistory.AddFilesToMenu` for initializing the menu with filenames that are already 2074 in the history when this function is called, as this is not done automatically. 2075 """ 2076 2077 if menu not in self._fileMenus: 2078 self._fileMenus.append(menu) 2079 2080 2081 def RemoveMenu(self, menu): 2082 """ 2083 Removes this menu from the list of those managed by this object. 2084 2085 :param `menu`: an instance of :class:`FlatMenu`. 2086 """ 2087 2088 self._fileMenus.remove(menu) 2089 2090 2091 def Load(self, config): 2092 """ 2093 Loads the file history from the given `config` object. 2094 2095 :param `config`: an instance of :class:`Config <ConfigBase>`. 2096 2097 :note: This function should be called explicitly by the application. 2098 2099 :see: :meth:`~FileHistory.Save`. 2100 """ 2101 2102 self._fileHistory = [] 2103 buffer = "file%d" 2104 count = 1 2105 2106 while 1: 2107 historyFile = config.Read(buffer%count) 2108 if not historyFile or len(self._fileHistory) >= self._fileMaxFiles: 2109 break 2110 2111 self._fileHistory.append(historyFile) 2112 count += 1 2113 2114 self.AddFilesToMenu() 2115 2116 2117 def Save(self, config): 2118 """ 2119 Saves the file history to the given `config` object. 2120 2121 :param `config`: an instance of :class:`Config <ConfigBase>`. 2122 2123 :note: This function should be called explicitly by the application. 2124 2125 :see: :meth:`~FileHistory.Load`. 2126 """ 2127 2128 buffer = "file%d" 2129 2130 for index in range(self._fileMaxFiles): 2131 2132 if index < len(self._fileHistory): 2133 config.Write(buffer%(index+1), self._fileHistory[i]) 2134 else: 2135 config.Write(buffer%(index+1), "") 2136 2137 2138 def AddFilesToMenu(self, menu=None): 2139 """ 2140 Appends the files in the history list, to all menus managed by the file history object 2141 if `menu` is ``None``. Otherwise it calls the auxiliary method :meth:`~FileHistory.AddFilesToMenu2`. 2142 2143 :param `menu`: if not ``None``, an instance of :class:`FlatMenu`. 2144 """ 2145 2146 if not self._fileHistory: 2147 return 2148 2149 if menu is not None: 2150 self.AddFilesToMenu2(menu) 2151 return 2152 2153 for menu in self._fileMenus: 2154 self.AddFilesToMenu2(menu) 2155 2156 2157 def AddFilesToMenu2(self, menu): 2158 """ 2159 Appends the files in the history list, to the given menu only. 2160 2161 :param `menu`: an instance of :class:`FlatMenu`. 2162 """ 2163 2164 if not self._fileHistory: 2165 return 2166 2167 if menu.GetMenuItemCount(): 2168 menu.AppendSeparator() 2169 2170 for index in range(len(self._fileHistory)): 2171 menu.Append(self._idBase + index, GetMRUEntryLabel(index, self._fileHistory[i])) 2172 2173 2174# ---------------------------------------------------------------------------- # 2175# Class FlatMenuEvent 2176# ---------------------------------------------------------------------------- # 2177 2178class FlatMenuEvent(wx.PyCommandEvent): 2179 """ 2180 Event class that supports the :class:`FlatMenu`-compatible event called 2181 ``EVT_FLAT_MENU_SELECTED``. 2182 """ 2183 2184 def __init__(self, eventType, eventId=1): 2185 """ 2186 Default class constructor. 2187 2188 :param integer `eventType`: the event type; 2189 :param integer `eventId`: the event identifier. 2190 """ 2191 2192 wx.PyCommandEvent.__init__(self, eventType, eventId) 2193 self._eventType = eventType 2194 2195 2196# ---------------------------------------------------------------------------- # 2197# Class MenuEntryInfo 2198# ---------------------------------------------------------------------------- # 2199 2200class MenuEntryInfo(object): 2201 """ 2202 Internal class which holds information about a menu. 2203 """ 2204 2205 def __init__(self, titleOrMenu="", menu=None, state=ControlNormal, cmd=wx.ID_ANY): 2206 """ 2207 Default class constructor. 2208 2209 Used internally. Do not call it in your code! 2210 2211 :param `titleOrMenu`: if it is a string, it represents the new menu label, 2212 otherwise it is another instance of :class:`wx.MenuEntryInfo` from which the attributes 2213 are copied; 2214 :param `menu`: the associated :class:`FlatMenu` object; 2215 :param integer `state`: the menu item state. This can be one of the following: 2216 2217 ==================== ======= ========================== 2218 Item State Value Description 2219 ==================== ======= ========================== 2220 ``ControlPressed`` 0 The item is pressed 2221 ``ControlFocus`` 1 The item is focused 2222 ``ControlDisabled`` 2 The item is disabled 2223 ``ControlNormal`` 3 Normal state 2224 ==================== ======= ========================== 2225 2226 :param integer `cmd`: the menu accelerator identifier. 2227 """ 2228 2229 if isinstance(titleOrMenu, six.string_types): 2230 2231 self._title = titleOrMenu 2232 self._menu = menu 2233 2234 self._rect = wx.Rect() 2235 self._state = state 2236 if cmd == wx.ID_ANY: 2237 cmd = wx.NewIdRef() 2238 2239 self._cmd = cmd # the menu itself accelerator id 2240 2241 else: 2242 2243 self._title = titleOrMenu._title 2244 self._menu = titleOrMenu._menu 2245 self._rect = titleOrMenu._rect 2246 self._state = titleOrMenu._state 2247 self._cmd = titleOrMenu._cmd 2248 2249 self._textBmp = wx.NullBitmap 2250 self._textSelectedBmp = wx.NullBitmap 2251 2252 2253 def GetTitle(self): 2254 """ Returns the associated menu title. """ 2255 2256 return self._title 2257 2258 2259 def GetMenu(self): 2260 """ Returns the associated menu. """ 2261 2262 return self._menu 2263 2264 2265 def SetRect(self, rect): 2266 """ 2267 Sets the associated menu client rectangle. 2268 2269 :param `rect`: an instance of :class:`wx.Rect`, representing the menu client rectangle. 2270 """ 2271 2272 self._rect = rect 2273 2274 2275 def GetRect(self): 2276 """ Returns the associated menu client rectangle. """ 2277 2278 return self._rect 2279 2280 2281 def SetState(self, state): 2282 """ 2283 Sets the associated menu state. 2284 2285 :param integer `state`: the menu item state. This can be one of the following: 2286 2287 ==================== ======= ========================== 2288 Item State Value Description 2289 ==================== ======= ========================== 2290 ``ControlPressed`` 0 The item is pressed 2291 ``ControlFocus`` 1 The item is focused 2292 ``ControlDisabled`` 2 The item is disabled 2293 ``ControlNormal`` 3 Normal state 2294 ==================== ======= ========================== 2295 """ 2296 2297 self._state = state 2298 2299 2300 def GetState(self): 2301 """ 2302 Returns the associated menu state. 2303 2304 :see: :meth:`~MenuEntryInfo.SetState` for a list of valid menu states. 2305 """ 2306 2307 return self._state 2308 2309 2310 def SetTextBitmap(self, bmp): 2311 """ 2312 Sets the associated menu bitmap. 2313 2314 :param `bmp`: a valid :class:`wx.Bitmap` object. 2315 """ 2316 2317 self._textBmp = bmp 2318 2319 2320 def SetSelectedTextBitmap(self, bmp): 2321 """ 2322 Sets the associated selected menu bitmap. 2323 2324 :param `bmp`: a valid :class:`wx.Bitmap` object. 2325 """ 2326 2327 self._textSelectedBmp = bmp 2328 2329 2330 def GetTextBitmap(self): 2331 """ Returns the associated menu bitmap. """ 2332 2333 return self._textBmp 2334 2335 2336 def GetSelectedTextBitmap(self): 2337 """ Returns the associated selected menu bitmap. """ 2338 2339 return self._textSelectedBmp 2340 2341 2342 def GetCmdId(self): 2343 """ Returns the associated menu accelerator identifier. """ 2344 2345 return self._cmd 2346 2347 2348# ---------------------------------------------------------------------------- # 2349# Class StatusBarTimer 2350# ---------------------------------------------------------------------------- # 2351 2352class StatusBarTimer(wx.Timer): 2353 """ Timer used for deleting :class:`StatusBar` long help after ``_DELAY`` seconds. """ 2354 2355 def __init__(self, owner): 2356 """ 2357 Default class constructor. 2358 For internal use: do not call it in your code! 2359 2360 :param `owner`: the :class:`Timer` owner (:class:`FlatMenuBar`). 2361 """ 2362 2363 wx.Timer.__init__(self) 2364 self._owner = owner 2365 2366 2367 def Notify(self): 2368 """ The timer has expired. """ 2369 2370 self._owner.OnStatusBarTimer() 2371 2372 2373# ---------------------------------------------------------------------------- # 2374# Class FlatMenuBar 2375# ---------------------------------------------------------------------------- # 2376 2377class FlatMenuBar(wx.Panel): 2378 """ 2379 Implements the generic owner-drawn menu bar for :class:`FlatMenu`. 2380 """ 2381 2382 def __init__(self, parent, id=wx.ID_ANY, iconSize=SmallIcons, 2383 spacer=SPACER, options=FM_OPT_SHOW_CUSTOMIZE|FM_OPT_IS_LCD): 2384 """ 2385 Default class constructor. 2386 2387 :param `parent`: the menu bar parent, must not be ``None``; 2388 :param integer `id`: the window identifier. If ``wx.ID_ANY``, will automatically create an identifier; 2389 :param integer `iconSize`: size of the icons in the toolbar. This can be one of the 2390 following values (in pixels): 2391 2392 ==================== ======= ============================= 2393 `iconSize` Bit Value Description 2394 ==================== ======= ============================= 2395 ``LargeIcons`` 32 Use large 32x32 icons 2396 ``SmallIcons`` 16 Use standard 16x16 icons 2397 ==================== ======= ============================= 2398 2399 :param integer `spacer`: the space between the menu bar text and the menu bar border; 2400 :param integer `options`: a combination of the following bits: 2401 2402 ========================= ========= ============================= 2403 `options` Bit Hex Value Description 2404 ========================= ========= ============================= 2405 ``FM_OPT_IS_LCD`` 0x1 Use this style if your computer uses a LCD screen 2406 ``FM_OPT_MINIBAR`` 0x2 Use this if you plan to use toolbar only 2407 ``FM_OPT_SHOW_CUSTOMIZE`` 0x4 Show "customize link" in more menus, you will need to write your own handler. See demo. 2408 ``FM_OPT_SHOW_TOOLBAR`` 0x8 Set this option is you are planing to use the toolbar 2409 ========================= ========= ============================= 2410 2411 """ 2412 2413 self._rendererMgr = FMRendererMgr() 2414 self._parent = parent 2415 self._curretHiliteItem = -1 2416 2417 self._items = [] 2418 self._dropDownButtonArea = wx.Rect() 2419 self._tbIconSize = iconSize 2420 self._tbButtons = [] 2421 self._interval = 20 # 20 milliseconds 2422 self._showTooltip = -1 2423 2424 self._haveTip = False 2425 self._statusTimer = None 2426 self._spacer = SPACER 2427 self._margin = spacer 2428 self._toolbarSpacer = TOOLBAR_SPACER 2429 self._toolbarMargin = TOOLBAR_MARGIN 2430 2431 self._showToolbar = options & FM_OPT_SHOW_TOOLBAR 2432 self._showCustomize = options & FM_OPT_SHOW_CUSTOMIZE 2433 self._isLCD = options & FM_OPT_IS_LCD 2434 self._isMinibar = options & FM_OPT_MINIBAR 2435 self._options = options 2436 2437 self._dropDownButtonState = ControlNormal 2438 self._moreMenu = None 2439 self._dlg = None 2440 self._tbMenu = None 2441 self._moreMenuBgBmp = None 2442 self._lastRadioGroup = 0 2443 self._mgr = None 2444 2445 self._barHeight = 0 2446 self._menuBarHeight = 0 2447 self.SetBarHeight() 2448 2449 wx.Panel.__init__(self, parent, id, size=(-1, self._barHeight), style=wx.WANTS_CHARS) 2450 2451 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) 2452 self.Bind(wx.EVT_PAINT, self.OnPaint) 2453 self.Bind(wx.EVT_SIZE, self.OnSize) 2454 self.Bind(wx.EVT_MOTION, self.OnMouseMove) 2455 self.Bind(EVT_FLAT_MENU_DISMISSED, self.OnMenuDismissed) 2456 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveMenuBar) 2457 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 2458 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) 2459 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) 2460 self.Bind(wx.EVT_IDLE, self.OnIdle) 2461 2462 if "__WXGTK__" in wx.Platform: 2463 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) 2464 2465 self.SetFocus() 2466 2467 # start the stop watch 2468 self._watch = wx.StopWatch() 2469 self._watch.Start() 2470 2471 2472 def Append(self, menu, title): 2473 """ 2474 Adds the item to the end of the menu bar. 2475 2476 :param `menu`: the menu to which we are appending a new item, an instance of :class:`FlatMenu`; 2477 :param string `title`: the menu item label, must not be empty. 2478 2479 :see: :meth:`~FlatMenuBar.Insert`. 2480 """ 2481 2482 menu._menuBarFullTitle = title 2483 position, label = GetAccelIndex(title) 2484 menu._menuBarLabelOnly = label 2485 2486 return self.Insert(len(self._items), menu, title) 2487 2488 2489 def OnIdle(self, event): 2490 """ 2491 Handles the ``wx.EVT_IDLE`` event for :class:`FlatMenuBar`. 2492 2493 :param `event`: a :class:`IdleEvent` event to be processed. 2494 """ 2495 2496 refresh = False 2497 2498 if self._watch.Time() > self._interval: 2499 2500 # it is time to process UpdateUIEvents 2501 for but in self._tbButtons: 2502 event = wx.UpdateUIEvent(but._tbItem.GetId()) 2503 event.Enable(but._tbItem.IsEnabled()) 2504 event.SetText(but._tbItem.GetLabel()) 2505 event.SetEventObject(self) 2506 2507 self.GetEventHandler().ProcessEvent(event) 2508 2509 if but._tbItem.GetLabel() != event.GetText() or but._tbItem.IsEnabled() != event.GetEnabled(): 2510 refresh = True 2511 2512 but._tbItem.SetLabel(event.GetText()) 2513 but._tbItem.Enable(event.GetEnabled()) 2514 2515 self._watch.Start() # Reset the timer 2516 2517 # we need to update the menu bar 2518 if refresh: 2519 self.Refresh() 2520 2521 2522 def SetBarHeight(self): 2523 """ Recalculates the :class:`FlatMenuBar` height when its settings change. """ 2524 2525 mem_dc = wx.MemoryDC() 2526 mem_dc.SelectObject(wx.Bitmap(1, 1)) 2527 dummy, self._barHeight = mem_dc.GetTextExtent("Tp") 2528 mem_dc.SelectObject(wx.NullBitmap) 2529 2530 if not self._isMinibar: 2531 self._barHeight += 2*self._margin # The menu bar margin 2532 else: 2533 self._barHeight = 0 2534 2535 self._menuBarHeight = self._barHeight 2536 2537 if self._showToolbar : 2538 # add the toolbar height to the menubar height 2539 self._barHeight += self._tbIconSize + 2*self._toolbarMargin 2540 2541 if self._mgr is None: 2542 return 2543 2544 pn = self._mgr.GetPane("flat_menu_bar") 2545 pn.MinSize(wx.Size(-1, self._barHeight)) 2546 self._mgr.Update() 2547 self.Refresh() 2548 2549 2550 def SetOptions(self, options): 2551 """ 2552 Sets the :class:`FlatMenuBar` options, whether to show a toolbar, to use LCD screen settings etc... 2553 2554 :param integer `options`: a combination of the following bits: 2555 2556 ========================= ========= ============================= 2557 `options` Bit Hex Value Description 2558 ========================= ========= ============================= 2559 ``FM_OPT_IS_LCD`` 0x1 Use this style if your computer uses a LCD screen 2560 ``FM_OPT_MINIBAR`` 0x2 Use this if you plan to use toolbar only 2561 ``FM_OPT_SHOW_CUSTOMIZE`` 0x4 Show "customize link" in more menus, you will need to write your own handler. See demo. 2562 ``FM_OPT_SHOW_TOOLBAR`` 0x8 Set this option is you are planing to use the toolbar 2563 ========================= ========= ============================= 2564 2565 """ 2566 2567 self._options = options 2568 2569 self._showToolbar = options & FM_OPT_SHOW_TOOLBAR 2570 self._showCustomize = options & FM_OPT_SHOW_CUSTOMIZE 2571 self._isLCD = options & FM_OPT_IS_LCD 2572 self._isMinibar = options & FM_OPT_MINIBAR 2573 2574 self.SetBarHeight() 2575 2576 self.Refresh() 2577 self.Update() 2578 2579 2580 def GetOptions(self): 2581 """ 2582 Returns the :class:`FlatMenuBar` options, whether to show a toolbar, to use LCD screen settings etc... 2583 2584 :see: :meth:`~FlatMenuBar.SetOptions` for a list of valid options. 2585 """ 2586 2587 return self._options 2588 2589 2590 def GetRendererManager(self): 2591 """ 2592 Returns the :class:`FlatMenuBar` renderer manager. 2593 """ 2594 2595 return self._rendererMgr 2596 2597 2598 def GetRenderer(self): 2599 """ 2600 Returns the renderer associated with this instance. 2601 """ 2602 2603 return self._rendererMgr.GetRenderer() 2604 2605 2606 def UpdateItem(self, item): 2607 """ 2608 An item was modified. This function is called by :class:`FlatMenu` in case 2609 an item was modified directly and not via a :class:`UpdateUIEvent` event. 2610 2611 :param `item`: an instance of :class:`FlatMenu`. 2612 """ 2613 2614 if not self._showToolbar: 2615 return 2616 2617 # search for a tool bar with id 2618 refresh = False 2619 2620 for but in self._tbButtons: 2621 if but._tbItem.GetId() == item.GetId(): 2622 if but._tbItem.IsEnabled() != item.IsEnabled(): 2623 refresh = True 2624 2625 but._tbItem.Enable(item.IsEnabled()) 2626 break 2627 2628 if refresh: 2629 self.Refresh() 2630 2631 2632 def OnPaint(self, event): 2633 """ 2634 Handles the ``wx.EVT_PAINT`` event for :class:`FlatMenuBar`. 2635 2636 :param `event`: a :class:`PaintEvent` event to be processed. 2637 """ 2638 2639 # on GTK, dont use the bitmap for drawing, 2640 # draw directly on the DC 2641 2642 if "__WXGTK__" in wx.Platform and not self._isLCD: 2643 self.ClearBitmaps(0) 2644 2645 dc = wx.BufferedPaintDC(self) 2646 self.GetRenderer().DrawMenuBar(self, dc) 2647 2648 2649 def DrawToolbar(self, dc, rect): 2650 """ 2651 Draws the toolbar (if present). 2652 2653 :param `dc`: an instance of :class:`wx.DC`; 2654 :param `rect`: the toolbar client rectangle, an instance of :class:`wx.Rect`. 2655 """ 2656 2657 highlight_width = self._tbIconSize + self._toolbarSpacer 2658 highlight_height = self._tbIconSize + self._toolbarMargin 2659 2660 xx = rect.x + self._toolbarMargin 2661 #yy = rect.y #+ self._toolbarMargin #+ (rect.height - height)/2 2662 2663 # by default set all toolbar items as invisible 2664 for but in self._tbButtons: 2665 but._visible = False 2666 2667 counter = 0 2668 # Get all the toolbar items 2669 for i in range(len(self._tbButtons)): 2670 2671 xx += self._toolbarSpacer 2672 2673 tbItem = self._tbButtons[i]._tbItem 2674 # the button width depends on its type 2675 if tbItem.IsSeparator(): 2676 hightlight_width = SEPARATOR_WIDTH 2677 elif tbItem.IsCustomControl(): 2678 control = tbItem.GetCustomControl() 2679 hightlight_width = control.GetSize().x + self._toolbarSpacer 2680 else: 2681 hightlight_width = self._tbIconSize + self._toolbarSpacer # normal bitmap's width 2682 2683 # can we keep drawing? 2684 if xx + highlight_width >= rect.width: 2685 break 2686 2687 counter += 1 2688 2689 # mark this item as visible 2690 self._tbButtons[i]._visible = True 2691 2692 bmp = wx.NullBitmap 2693 2694 #------------------------------------------ 2695 # special handling for separator 2696 #------------------------------------------ 2697 if tbItem.IsSeparator(): 2698 2699 # draw the separator 2700 buttonRect = wx.Rect(xx, rect.y+1, SEPARATOR_WIDTH, rect.height-2) 2701 self.GetRenderer().DrawToolbarSeparator(dc, buttonRect) 2702 2703 xx += buttonRect.width 2704 self._tbButtons[i]._rect = buttonRect 2705 continue 2706 2707 elif tbItem.IsCustomControl(): 2708 control = tbItem.GetCustomControl() 2709 ctrlSize = control.GetSize() 2710 ctrlPos = wx.Point(xx, rect.y + (rect.height - ctrlSize.y)/2) 2711 if control.GetPosition() != ctrlPos: 2712 control.SetPosition(ctrlPos) 2713 2714 if not control.IsShown(): 2715 control.Show() 2716 2717 buttonRect = wx.Rect(ctrlPos, ctrlSize) 2718 xx += buttonRect.width 2719 self._tbButtons[i]._rect = buttonRect 2720 continue 2721 else: 2722 if tbItem.IsEnabled(): 2723 bmp = tbItem.GetBitmap() 2724 else: 2725 bmp = tbItem.GetDisabledBitmap() 2726 2727 # Draw the toolbar image 2728 if bmp.IsOk(): 2729 2730 x = xx - self._toolbarSpacer/2 2731 #y = rect.y + (rect.height - bmp.GetHeight())/2 - 1 2732 y = rect.y + self._toolbarMargin/2 2733 2734 buttonRect = wx.Rect(x, y, highlight_width, highlight_height) 2735 2736 if i < len(self._tbButtons) and i >= 0: 2737 2738 if self._tbButtons[i]._tbItem.IsSelected(): 2739 tmpState = ControlPressed 2740 else: 2741 tmpState = ControlFocus 2742 2743 if self._tbButtons[i]._state == ControlFocus or self._tbButtons[i]._tbItem.IsSelected(): 2744 self.GetRenderer().DrawMenuBarButton(dc, buttonRect, tmpState) # TODO DrawToolbarButton? With separate toolbar colors 2745 else: 2746 self._tbButtons[i]._state = ControlNormal 2747 2748 imgx = buttonRect.x + (buttonRect.width - bmp.GetWidth())/2 2749 imgy = buttonRect.y + (buttonRect.height - bmp.GetHeight())/2 2750 2751 if self._tbButtons[i]._state == ControlFocus and not self._tbButtons[i]._tbItem.IsSelected(): 2752 2753 # in case we the button is in focus, place it 2754 # once pixle up and left 2755 # place a dark image under the original image to provide it 2756 # with some shadow 2757 # shadow = ConvertToMonochrome(bmp) 2758 # dc.DrawBitmap(shadow, imgx, imgy, True) 2759 2760 imgx -= 1 2761 imgy -= 1 2762 2763 dc.DrawBitmap(bmp, imgx, imgy, True) 2764 xx += buttonRect.width 2765 2766 self._tbButtons[i]._rect = buttonRect 2767 #Edited by P.Kort 2768 2769 if self._showTooltip == -1: 2770 self.RemoveHelp() 2771 else: 2772 try: 2773 self.DoGiveHelp(self._tbButtons[self._showTooltip]._tbItem) 2774 except: 2775 if _debug: 2776 print("FlatMenu.py; fn : DrawToolbar; Can't create Tooltip ") 2777 pass 2778 2779 for j in range(counter, len(self._tbButtons)): 2780 if self._tbButtons[j]._tbItem.IsCustomControl(): 2781 control = self._tbButtons[j]._tbItem.GetCustomControl() 2782 control.Hide() 2783 2784 2785 def GetMoreMenuButtonRect(self): 2786 """ Returns a rectangle region, as an instance of :class:`wx.Rect`, surrounding the menu button. """ 2787 2788 clientRect = self.GetClientRect() 2789 rect = wx.Rect(*clientRect) 2790 rect.SetWidth(DROP_DOWN_ARROW_WIDTH) 2791 rect.SetX(clientRect.GetWidth() + rect.GetX() - DROP_DOWN_ARROW_WIDTH - 3) 2792 rect.SetY(2) 2793 rect.SetHeight(rect.GetHeight() - self._spacer) 2794 2795 return rect 2796 2797 2798 def DrawMoreButton(self, dc, state): 2799 """ 2800 Draws 'more' button to the right side of the menu bar. 2801 2802 :param `dc`: an instance of :class:`wx.DC`; 2803 :param integer `state`: the 'more' button state. 2804 2805 :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid menu states. 2806 """ 2807 2808 if (not self._showCustomize) and self.GetInvisibleMenuItemCount() < 1 and self.GetInvisibleToolbarItemCount() < 1: 2809 return 2810 2811 # Draw a drop down menu at the right position of the menu bar 2812 # we use xpm file with 16x16 size, another 4 pixels we take as spacer 2813 # from the right side of the frame, this will create a DROP_DOWN_ARROW_WIDTH pixels width 2814 # of unwanted zone on the right side 2815 2816 rect = self.GetMoreMenuButtonRect() 2817 2818 # Draw the bitmap 2819 if state != ControlNormal: 2820 # Draw background according to state 2821 self.GetRenderer().DrawButton(dc, rect, state) 2822 else: 2823 # Delete current image 2824 if self._moreMenuBgBmp.IsOk(): 2825 dc.DrawBitmap(self._moreMenuBgBmp, rect.x, rect.y, True) 2826 2827 dropArrowBmp = self.GetRenderer()._bitmaps["arrow_down"] 2828 2829 # Calc the image coordinates 2830 xx = rect.x + (DROP_DOWN_ARROW_WIDTH - dropArrowBmp.GetWidth())/2 2831 yy = rect.y + (rect.height - dropArrowBmp.GetHeight())/2 2832 2833 dc.DrawBitmap(dropArrowBmp, xx, yy + self._spacer, True) 2834 self._dropDownButtonState = state 2835 2836 2837 def HitTest(self, pt): 2838 """ 2839 HitTest method for :class:`FlatMenuBar`. 2840 2841 :param `pt`: an instance of :class:`wx.Point`, specifying the hit test position. 2842 2843 :return: A tuple representing one of the following combinations: 2844 2845 ========================= ================================================== 2846 Return Tuple Description 2847 ========================= ================================================== 2848 (-1, 0) The :meth:`~FlatMenuBar.HitTest` method didn't find any item with the specified input point `pt` (``NoWhere`` = 0) 2849 (`integer`, 1) A menu item has been hit, its position specified by the tuple item `integer` (``MenuItem`` = 1) 2850 (`integer`, 2) A toolbar item has ben hit, its position specified by the tuple item `integer` (``ToolbarItem`` = 2) 2851 (-1, 3) The drop-down area button has been hit (``DropDownArrowButton`` = 3) 2852 ========================= ================================================== 2853 2854 """ 2855 2856 if self._dropDownButtonArea.Contains(pt): 2857 return -1, DropDownArrowButton 2858 2859 for ii, item in enumerate(self._items): 2860 if item.GetRect().Contains(pt): 2861 return ii, MenuItem 2862 2863 # check for tool bar items 2864 if self._showToolbar: 2865 for ii, but in enumerate(self._tbButtons): 2866 if but._rect.Contains(pt): 2867 # locate the corresponded menu item 2868 enabled = but._tbItem.IsEnabled() 2869 separator = but._tbItem.IsSeparator() 2870 visible = but._visible 2871 if enabled and not separator and visible: 2872 self._showTooltip = ii 2873 return ii, ToolbarItem 2874 2875 self._showTooltip = -1 2876 return -1, NoWhere 2877 2878 2879 def FindMenuItem(self, id): 2880 """ 2881 Finds the menu item object associated with the given menu item identifier. 2882 2883 :param integer `id`: the identifier for the sought :class:`FlatMenuItem`. 2884 2885 :return: The found menu item object, or ``None`` if one was not found. 2886 """ 2887 2888 for item in self._items: 2889 mi = item.GetMenu().FindItem(id) 2890 if mi: 2891 return mi 2892 return None 2893 2894 2895 def OnSize(self, event): 2896 """ 2897 Handles the ``wx.EVT_SIZE`` event for :class:`FlatMenuBar`. 2898 2899 :param `event`: a :class:`wx.SizeEvent` event to be processed. 2900 """ 2901 2902 self.ClearBitmaps(0) 2903 self.Refresh() 2904 2905 2906 def OnEraseBackground(self, event): 2907 """ 2908 Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`FlatMenuBar`. 2909 2910 :param `event`: a :class:`EraseEvent` event to be processed. 2911 2912 :note: This method is intentionally empty to reduce flicker. 2913 """ 2914 2915 pass 2916 2917 2918 def ShowCustomize(self, show=True): 2919 """ 2920 Shows/hides the drop-down arrow which allows customization of :class:`FlatMenu`. 2921 2922 :param bool `show`: ``True`` to show the customize menu, ``False`` to hide it. 2923 """ 2924 2925 if self._showCustomize == show: 2926 return 2927 2928 self._showCustomize = show 2929 self.Refresh() 2930 2931 2932 def SetMargin(self, margin): 2933 """ 2934 Sets the margin above and below the menu bar text. 2935 2936 :param integer `margin`: height in pixels of the margin. 2937 """ 2938 2939 self._margin = margin 2940 2941 2942 def SetSpacing(self, spacer): 2943 """ 2944 Sets the spacing between the menubar items. 2945 2946 :param integer `spacer`: number of pixels between each menu item. 2947 """ 2948 2949 self._spacer = spacer 2950 2951 2952 def SetToolbarMargin(self, margin): 2953 """ 2954 Sets the margin around the toolbar. 2955 2956 :param integer `margin`: width in pixels of the margin around the tools in the toolbar. 2957 """ 2958 2959 self._toolbarMargin = margin 2960 2961 2962 def SetToolbarSpacing(self, spacer): 2963 """ 2964 Sets the spacing between the toolbar tools. 2965 2966 :param integer `spacer`: number of pixels between each tool in the toolbar. 2967 """ 2968 2969 self._toolbarSpacer = spacer 2970 2971 2972 def SetLCDMonitor(self, lcd=True): 2973 """ 2974 Sets whether the PC monitor is an LCD or not. 2975 2976 :param bool `lcd`: ``True`` to use the settings appropriate for a LCD monitor, 2977 ``False`` otherwise. 2978 """ 2979 2980 if self._isLCD == lcd: 2981 return 2982 2983 self._isLCD = lcd 2984 self.Refresh() 2985 2986 2987 def ProcessMouseMoveFromMenu(self, pt): 2988 """ 2989 This function is called from child menus, this allow a child menu to 2990 pass the mouse movement event to the menu bar. 2991 2992 :param `pt`: an instance of :class:`wx.Point`. 2993 """ 2994 2995 idx, where = self.HitTest(pt) 2996 if where == MenuItem: 2997 self.ActivateMenu(self._items[idx]) 2998 2999 3000 def DoMouseMove(self, pt, leftIsDown): 3001 """ 3002 Handles mouse move event. 3003 3004 :param `pt`: an instance of :class:`wx.Point`; 3005 :param bool `leftIsDown`: ``True`` is the left mouse button is down, ``False`` otherwise. 3006 """ 3007 3008 # Reset items state 3009 for item in self._items: 3010 item.SetState(ControlNormal) 3011 3012 idx, where = self.HitTest(pt) 3013 3014 if where == DropDownArrowButton: 3015 self.RemoveHelp() 3016 if self._dropDownButtonState != ControlFocus and not leftIsDown: 3017 dc = wx.ClientDC(self) 3018 self.DrawMoreButton(dc, ControlFocus) 3019 3020 elif where == MenuItem: 3021 self._dropDownButtonState = ControlNormal 3022 # On Item 3023 self._items[idx].SetState(ControlFocus) 3024 3025 # If this item is already selected, dont draw it again 3026 if self._curretHiliteItem == idx: 3027 return 3028 3029 self._curretHiliteItem = idx 3030 if self._showToolbar: 3031 3032 # mark all toolbar items as non-hilited 3033 for but in self._tbButtons: 3034 but._state = ControlNormal 3035 3036 self.Refresh() 3037 3038 elif where == ToolbarItem: 3039 3040 if self._showToolbar: 3041 if idx < len(self._tbButtons) and idx >= 0: 3042 if self._tbButtons[idx]._state == ControlFocus: 3043 return 3044 3045 # we need to refresh the toolbar 3046 active = self.GetActiveToolbarItem() 3047 if active != wx.NOT_FOUND: 3048 self._tbButtons[active]._state = ControlNormal 3049 3050 for but in self._tbButtons: 3051 but._state = ControlNormal 3052 3053 self._tbButtons[idx]._state = ControlFocus 3054 self.DoGiveHelp(self._tbButtons[idx]._tbItem) 3055 self.Refresh() 3056 3057 elif where == NoWhere: 3058 3059 refresh = False 3060 self.RemoveHelp() 3061 3062 if self._dropDownButtonState != ControlNormal: 3063 refresh = True 3064 self._dropDownButtonState = ControlNormal 3065 3066 if self._showToolbar: 3067 tbActiveItem = self.GetActiveToolbarItem() 3068 if tbActiveItem != wx.NOT_FOUND: 3069 self._tbButtons[tbActiveItem]._state = ControlNormal 3070 refresh = True 3071 3072 if self._curretHiliteItem != -1: 3073 3074 self._items[self._curretHiliteItem].SetState(ControlNormal) 3075 self._curretHiliteItem = -1 3076 self.Refresh() 3077 3078 if refresh: 3079 self.Refresh() 3080 3081 3082 def OnMouseMove(self, event): 3083 """ 3084 Handles the ``wx.EVT_MOTION`` event for :class:`FlatMenuBar`. 3085 3086 :param `event`: a :class:`MouseEvent` event to be processed. 3087 """ 3088 3089 pt = event.GetPosition() 3090 self.DoMouseMove(pt, event.LeftIsDown()) 3091 3092 3093 def OnLeaveMenuBar(self, event): 3094 """ 3095 Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenuBar`. 3096 3097 :param `event`: a :class:`MouseEvent` event to be processed. 3098 3099 :note: This method is for MSW only. 3100 """ 3101 3102 pt = event.GetPosition() 3103 self.DoMouseMove(pt, event.LeftIsDown()) 3104 3105 3106 def ResetToolbarItems(self): 3107 """ Used internally. """ 3108 3109 for but in self._tbButtons: 3110 but._state = ControlNormal 3111 3112 3113 def GetActiveToolbarItem(self): 3114 """ Returns the active toolbar item. """ 3115 3116 for but in self._tbButtons: 3117 3118 if but._state == ControlFocus or but._state == ControlPressed: 3119 return self._tbButtons.index(but) 3120 3121 return wx.NOT_FOUND 3122 3123 3124 def GetBackgroundColour(self): 3125 """ Returns the menu bar background colour. """ 3126 3127 return self.GetRenderer().menuBarFaceColour 3128 3129 3130 def SetBackgroundColour(self, colour): 3131 """ 3132 Sets the menu bar background colour. 3133 3134 :param `colour`: a valid :class:`wx.Colour`. 3135 """ 3136 3137 self.GetRenderer().menuBarFaceColour = colour 3138 3139 3140 def OnLeaveWindow(self, event): 3141 """ 3142 Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenuBar`. 3143 3144 :param `event`: a :class:`MouseEvent` event to be processed. 3145 3146 :note: This method is for GTK only. 3147 """ 3148 3149 self._curretHiliteItem = -1 3150 self._dropDownButtonState = ControlNormal 3151 3152 # Reset items state 3153 for item in self._items: 3154 item.SetState(ControlNormal) 3155 3156 for but in self._tbButtons: 3157 but._state = ControlNormal 3158 3159 self.Refresh() 3160 3161 3162 def OnMenuDismissed(self, event): 3163 """ 3164 Handles the ``EVT_FLAT_MENU_DISMISSED`` event for :class:`FlatMenuBar`. 3165 3166 :param `event`: a :class:`FlatMenuEvent` event to be processed. 3167 """ 3168 3169 pt = wx.GetMousePosition() 3170 pt = self.ScreenToClient(pt) 3171 3172 idx, where = self.HitTest(pt) 3173 self.RemoveHelp() 3174 3175 if where not in [MenuItem, DropDownArrowButton]: 3176 self._dropDownButtonState = ControlNormal 3177 self._curretHiliteItem = -1 3178 for item in self._items: 3179 item.SetState(ControlNormal) 3180 3181 self.Refresh() 3182 3183 3184 def OnLeftDown(self, event): 3185 """ 3186 Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`FlatMenuBar`. 3187 3188 :param `event`: a :class:`MouseEvent` event to be processed. 3189 """ 3190 3191 pt = event.GetPosition() 3192 idx, where = self.HitTest(pt) 3193 3194 if where == DropDownArrowButton: 3195 dc = wx.ClientDC(self) 3196 self.DrawMoreButton(dc, ControlPressed) 3197 self.PopupMoreMenu() 3198 3199 elif where == MenuItem: 3200 # Position the menu, the GetPosition() return the coords 3201 # of the button relative to its parent, we need to translate 3202 # them into the screen coords 3203 self.ActivateMenu(self._items[idx]) 3204 3205 elif where == ToolbarItem: 3206 redrawAll = False 3207 item = self._tbButtons[idx]._tbItem 3208 # try to toggle if its a check item: 3209 item.Toggle() 3210 # switch is if its a unselected radio item 3211 if not item.IsSelected() and item.IsRadioItem(): 3212 group = item.GetGroup() 3213 for i in range(len(self._tbButtons)): 3214 if self._tbButtons[i]._tbItem.GetGroup() == group and \ 3215 i != idx and self._tbButtons[i]._tbItem.IsSelected(): 3216 self._tbButtons[i]._state = ControlNormal 3217 self._tbButtons[i]._tbItem.Select(False) 3218 redrawAll = True 3219 item.Select(True) 3220 # Over a toolbar item 3221 if redrawAll: 3222 self.Refresh() 3223 if "__WXMSW__" in wx.Platform: 3224 dc = wx.BufferedDC(wx.ClientDC(self)) 3225 else: 3226 dc = wx.ClientDC(self) 3227 else: 3228 dc = wx.ClientDC(self) 3229 self.DrawToolbarItem(dc, idx, ControlPressed) 3230 3231 # TODO:: Do the action specified in this button 3232 self.DoToolbarAction(idx) 3233 3234 3235 def OnLeftUp(self, event): 3236 """ 3237 Handles the ``wx.EVT_LEFT_UP`` event for :class:`FlatMenuBar`. 3238 3239 :param `event`: a :class:`MouseEvent` event to be processed. 3240 """ 3241 3242 pt = event.GetPosition() 3243 idx, where = self.HitTest(pt) 3244 3245 if where == ToolbarItem: 3246 # Over a toolbar item 3247 dc = wx.ClientDC(self) 3248 self.DrawToolbarItem(dc, idx, ControlFocus) 3249 3250 3251 def DrawToolbarItem(self, dc, idx, state): 3252 """ 3253 Draws a toolbar item button. 3254 3255 :param `dc`: an instance of :class:`wx.DC`; 3256 :param integer `idx`: the tool index in the toolbar; 3257 :param integer `state`: the button state. 3258 3259 :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid menu states. 3260 """ 3261 3262 if idx >= len(self._tbButtons) or idx < 0: 3263 return 3264 3265 if self._tbButtons[idx]._tbItem.IsSelected(): 3266 state = ControlPressed 3267 rect = self._tbButtons[idx]._rect 3268 self.GetRenderer().DrawButton(dc, rect, state) 3269 3270 # draw the bitmap over the highlight 3271 buttonRect = wx.Rect(*rect) 3272 x = rect.x + (buttonRect.width - self._tbButtons[idx]._tbItem.GetBitmap().GetWidth())/2 3273 y = rect.y + (buttonRect.height - self._tbButtons[idx]._tbItem.GetBitmap().GetHeight())/2 3274 3275 if state == ControlFocus: 3276 3277 # place a dark image under the original image to provide it 3278 # with some shadow 3279 # shadow = ConvertToMonochrome(self._tbButtons[idx]._tbItem.GetBitmap()) 3280 # dc.DrawBitmap(shadow, x, y, True) 3281 3282 # in case we the button is in focus, place it 3283 # once pixle up and left 3284 x -= 1 3285 y -= 1 3286 dc.DrawBitmap(self._tbButtons[idx]._tbItem.GetBitmap(), x, y, True) 3287 3288 3289 def ActivateMenu(self, menuInfo): 3290 """ 3291 Activates a menu. 3292 3293 :param `menuInfo`: an instance of :class:`wx.MenuEntryInfo`. 3294 """ 3295 3296 # first make sure all other menus are not popedup 3297 if menuInfo.GetMenu().IsShown(): 3298 return 3299 3300 idx = wx.NOT_FOUND 3301 3302 for item in self._items: 3303 item.GetMenu().Dismiss(False, True) 3304 if item.GetMenu() == menuInfo.GetMenu(): 3305 idx = self._items.index(item) 3306 3307 # Remove the popup menu as well 3308 if self._moreMenu and self._moreMenu.IsShown(): 3309 self._moreMenu.Dismiss(False, True) 3310 3311 # make sure that the menu item button is highlited 3312 if idx != wx.NOT_FOUND: 3313 self._dropDownButtonState = ControlNormal 3314 self._curretHiliteItem = idx 3315 for item in self._items: 3316 item.SetState(ControlNormal) 3317 3318 self._items[idx].SetState(ControlFocus) 3319 self.Refresh() 3320 3321 rect = menuInfo.GetRect() 3322 menuPt = self.ClientToScreen(wx.Point(rect.x, rect.y)) 3323 menuInfo.GetMenu().SetOwnerHeight(rect.height) 3324 menuInfo.GetMenu().Popup(wx.Point(menuPt.x, menuPt.y), self) 3325 3326 3327 def DoToolbarAction(self, idx): 3328 """ 3329 Performs a toolbar button pressed action. 3330 3331 :param integer `idx`: the tool index in the toolbar. 3332 """ 3333 3334 # we handle only button clicks 3335 tbItem = self._tbButtons[idx]._tbItem 3336 if tbItem.IsRegularItem() or tbItem.IsCheckItem() or tbItem.IsRadioItem(): 3337 # Create the event 3338 event = wx.CommandEvent(wxEVT_FLAT_MENU_SELECTED, tbItem.GetId()) 3339 event.SetEventObject(self) 3340 3341 # all events are handled by this control and its parents 3342 self.GetEventHandler().ProcessEvent(event) 3343 3344 3345 def FindMenu(self, title): 3346 """ 3347 Returns the index of the menu with the given title or ``wx.NOT_FOUND`` if 3348 no such menu exists in this menubar. 3349 3350 :param string `title`: may specify either the menu title (with accelerator characters, 3351 i.e. "&File") or just the menu label ("File") indifferently. 3352 """ 3353 3354 for ii, item in enumerate(self._items): 3355 accelIdx, labelOnly = GetAccelIndex(item.GetTitle()) 3356 3357 if labelOnly == title or item.GetTitle() == title: 3358 return ii 3359 3360 return wx.NOT_FOUND 3361 3362 3363 def GetMenu(self, menuIdx): 3364 """ 3365 Returns the menu at the specified index `menuIdx` (zero-based). 3366 3367 :param integer `menuIdx`: the index of the sought menu. 3368 3369 :return: The found menu item object, or ``None`` if one was not found. 3370 """ 3371 3372 if menuIdx >= len(self._items) or menuIdx < 0: 3373 return None 3374 3375 return self._items[menuIdx].GetMenu() 3376 3377 3378 def GetMenuCount(self): 3379 """ Returns the number of menus in the menubar. """ 3380 3381 return len(self._items) 3382 3383 3384 def Insert(self, pos, menu, title): 3385 """ 3386 Inserts the menu at the given position into the menu bar. 3387 3388 :param integer `pos`: the position of the new menu in the menu bar; 3389 :param `menu`: the menu to add, an instance of :class:`FlatMenu`. :class:`FlatMenuBar` owns the menu and will free it; 3390 :param string `title`: the title of the menu. 3391 3392 :note: Inserting menu at position 0 will insert it in the very beginning of it, 3393 inserting at position :meth:`~FlatMenuBar.GetMenuCount` is the same as calling :meth:`~FlatMenuBar.Append`. 3394 """ 3395 3396 menu.SetMenuBar(self) 3397 self._items.insert(pos, MenuEntryInfo(title, menu)) 3398 self.UpdateAcceleratorTable() 3399 3400 self.ClearBitmaps(pos) 3401 self.Refresh() 3402 return True 3403 3404 3405 def Remove(self, pos): 3406 """ 3407 Removes the menu from the menu bar and returns the menu object - the 3408 caller is responsible for deleting it. 3409 3410 :param integer `pos`: the position of the menu in the menu bar. 3411 3412 :note: This function may be used together with :meth:`~FlatMenuBar.Insert` to change the menubar 3413 dynamically. 3414 """ 3415 3416 if pos >= len(self._items): 3417 return None 3418 3419 menu = self._items[pos].GetMenu() 3420 self._items.pop(pos) 3421 self.UpdateAcceleratorTable() 3422 3423 # Since we use bitmaps to optimize our drawings, we need 3424 # to reset all bitmaps from pos and until end of vector 3425 # to force size/position changes to the menu bar 3426 self.ClearBitmaps(pos) 3427 self.Refresh() 3428 3429 # remove the connection to this menubar 3430 menu.SetMenuBar(None) 3431 return menu 3432 3433 3434 def UpdateAcceleratorTable(self): 3435 """ Updates the parent accelerator table. """ 3436 3437 # first get the number of items we have 3438 updatedTable = [] 3439 parent = self.GetParent() 3440 3441 for item in self._items: 3442 3443 updatedTable = item.GetMenu().GetAccelArray() + updatedTable 3444 3445 # create accelerator for every menu (if it exist) 3446 title = item.GetTitle() 3447 mnemonic, labelOnly = GetAccelIndex(title) 3448 3449 if mnemonic != wx.NOT_FOUND: 3450 3451 # Get the accelrator character 3452 accelChar = labelOnly[mnemonic] 3453 accelString = "\tAlt+" + accelChar 3454 title += accelString 3455 3456 accel = wx.AcceleratorEntry() 3457 accel.FromString(title) 3458 itemId = item.GetCmdId() 3459 3460 if accel: 3461 3462 # connect an event to this cmd 3463 parent.Connect(itemId, -1, wxEVT_FLAT_MENU_SELECTED, self.OnAccelCmd) 3464 accel.Set(accel.GetFlags(), accel.GetKeyCode(), itemId) 3465 updatedTable.append(accel) 3466 3467 entries = [wx.AcceleratorEntry() for ii in range(len(updatedTable))] 3468 3469 # Add the new menu items 3470 for i in range(len(updatedTable)): 3471 entries[i] = updatedTable[i] 3472 3473 table = wx.AcceleratorTable(entries) 3474 del entries 3475 3476 parent.SetAcceleratorTable(table) 3477 3478 3479 def ClearBitmaps(self, start=0): 3480 """ 3481 Restores a :class:`NullBitmap` for all the items in the menu. 3482 3483 :param integer `start`: the index at which to start resetting the bitmaps. 3484 """ 3485 3486 if self._isLCD: 3487 return 3488 3489 for item in self._items[start:]: 3490 item.SetTextBitmap(wx.NullBitmap) 3491 item.SetSelectedTextBitmap(wx.NullBitmap) 3492 3493 3494 def OnAccelCmd(self, event): 3495 """ 3496 Single function to handle any accelerator key used inside the menubar. 3497 3498 :param `event`: a :class:`FlatMenuEvent` event to be processed. 3499 """ 3500 3501 for item in self._items: 3502 if item.GetCmdId() == event.GetId(): 3503 self.ActivateMenu(item) 3504 3505 3506 def ActivateNextMenu(self): 3507 """ Activates next menu and make sure all others are non-active. """ 3508 3509 last_item = self.GetLastVisibleMenu() 3510 # find the current active menu 3511 for i in range(last_item+1): 3512 if self._items[i].GetMenu().IsShown(): 3513 nextMenu = i + 1 3514 if nextMenu >= last_item: 3515 nextMenu = 0 3516 self.ActivateMenu(self._items[nextMenu]) 3517 return 3518 3519 3520 def GetLastVisibleMenu(self): 3521 """ Returns the index of the last visible menu on the menu bar. """ 3522 3523 last_item = 0 3524 3525 # find the last visible item 3526 rect = wx.Rect() 3527 3528 for item in self._items: 3529 3530 if item.GetRect() == rect: 3531 break 3532 3533 last_item += 1 3534 3535 return last_item 3536 3537 3538 def ActivatePreviousMenu(self): 3539 """ Activates previous menu and make sure all others are non-active. """ 3540 3541 # find the current active menu 3542 last_item = self.GetLastVisibleMenu() 3543 3544 for i in range(last_item): 3545 if self._items[i].GetMenu().IsShown(): 3546 prevMenu = i - 1 3547 if prevMenu < 0: 3548 prevMenu = last_item - 1 3549 3550 if prevMenu < 0: 3551 return 3552 3553 self.ActivateMenu(self._items[prevMenu]) 3554 return 3555 3556 3557 def CreateMoreMenu(self): 3558 """ Creates the drop down menu and populate it. """ 3559 3560 if not self._moreMenu: 3561 # first time 3562 self._moreMenu = FlatMenu(self) 3563 self._popupDlgCmdId = wx.NewIdRef() 3564 3565 # Connect an event handler for this event 3566 self.Connect(self._popupDlgCmdId, -1, wxEVT_FLAT_MENU_SELECTED, self.OnCustomizeDlg) 3567 3568 # Remove all items from the popup menu 3569 self._moreMenu.Clear() 3570 3571 invM = self.GetInvisibleMenuItemCount() 3572 3573 for i in range(len(self._items) - invM, len(self._items)): 3574 item = FlatMenuItem(self._moreMenu, wx.ID_ANY, self._items[i].GetTitle(), 3575 "", wx.ITEM_NORMAL, self._items[i].GetMenu()) 3576 self._moreMenu.AppendItem(item) 3577 3578 # Add invisible toolbar items 3579 invT = self.GetInvisibleToolbarItemCount() 3580 3581 if self._showToolbar and invT > 0: 3582 if self.GetInvisibleMenuItemCount() > 0: 3583 self._moreMenu.AppendSeparator() 3584 3585 for i in range(len(self._tbButtons) - invT, len(self._tbButtons)): 3586 if self._tbButtons[i]._tbItem.IsSeparator(): 3587 self._moreMenu.AppendSeparator() 3588 elif not self._tbButtons[i]._tbItem.IsCustomControl(): 3589 tbitem = self._tbButtons[i]._tbItem 3590 item = FlatMenuItem(self._tbMenu, tbitem.GetId(), tbitem.GetLabel(), "", wx.ITEM_NORMAL, None, tbitem.GetBitmap(), tbitem.GetDisabledBitmap()) 3591 item.Enable(tbitem.IsEnabled()) 3592 self._moreMenu.AppendItem(item) 3593 3594 3595 if self._showCustomize: 3596 if invT + invM > 0: 3597 self._moreMenu.AppendSeparator() 3598 item = FlatMenuItem(self._moreMenu, self._popupDlgCmdId, _(six.u("Customize..."))) 3599 self._moreMenu.AppendItem(item) 3600 3601 3602 def GetInvisibleMenuItemCount(self): 3603 """ 3604 Returns the number of invisible menu items. 3605 3606 :note: Valid only after the :class:`PaintEvent` has been processed after a resize. 3607 """ 3608 3609 return len(self._items) - self.GetLastVisibleMenu() 3610 3611 3612 def GetInvisibleToolbarItemCount(self): 3613 """ 3614 Returns the number of invisible toolbar items. 3615 3616 :note: Valid only after the :class:`PaintEvent` has been processed after a resize. 3617 """ 3618 3619 count = 0 3620 for i in range(len(self._tbButtons)): 3621 if self._tbButtons[i]._visible == False: 3622 break 3623 count = i 3624 3625 return len(self._tbButtons) - count - 1 3626 3627 3628 def PopupMoreMenu(self): 3629 """ Pops up the 'more' menu. """ 3630 3631 if (not self._showCustomize) and self.GetInvisibleMenuItemCount() + self.GetInvisibleToolbarItemCount() < 1: 3632 return 3633 3634 self.CreateMoreMenu() 3635 3636 pt = self._dropDownButtonArea.GetTopLeft() 3637 pt = self.ClientToScreen(pt) 3638 pt.y += self._dropDownButtonArea.GetHeight() 3639 self._moreMenu.Popup(pt, self) 3640 3641 3642 def OnCustomizeDlg(self, event): 3643 """ 3644 Handles the customize dialog here. 3645 3646 :param `event`: a :class:`FlatMenuEvent` event to be processed. 3647 """ 3648 3649 if not self._dlg: 3650 self._dlg = FMCustomizeDlg(self) 3651 else: 3652 # intialize the dialog 3653 self._dlg.Initialise() 3654 3655 if self._dlg.ShowModal() == wx.ID_OK: 3656 # Handle customize requests here 3657 pass 3658 3659 if "__WXGTK__" in wx.Platform: 3660 # Reset the more button 3661 dc = wx.ClientDC(self) 3662 self.DrawMoreButton(dc, ControlNormal) 3663 3664 3665 def AppendToolbarItem(self, item): 3666 """ 3667 Appends a tool to the :class:`FlatMenuBar`. 3668 3669 .. deprecated:: 0.9.5 3670 This method is now deprecated. 3671 3672 :see: :meth:`~FlatMenuBar.AddTool` 3673 """ 3674 3675 newItem = ToolBarItem(item, wx.Rect(), ControlNormal) 3676 self._tbButtons.append(newItem) 3677 3678 3679 def AddTool(self, toolId, label="", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap, 3680 kind=wx.ITEM_NORMAL, shortHelp="", longHelp=""): 3681 """ 3682 Adds a tool to the toolbar. 3683 3684 :param integer `toolId`: an integer by which the tool may be identified in subsequent 3685 operations; 3686 :param string `label`: the tool label string; 3687 :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default), 3688 ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been 3689 toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio 3690 group of tools each of which is automatically unchecked whenever another button 3691 in the group is checked; 3692 :param `bitmap1`: the primary tool bitmap, an instance of :class:`wx.Bitmap`; 3693 :param `bitmap2`: the bitmap used when the tool is disabled. If it is equal to 3694 :class:`NullBitmap`, the disabled bitmap is automatically generated by greing out 3695 the normal one; 3696 :param string `shortHelp`: a string used for the tools tooltip; 3697 :param string `longHelp`: this string is shown in the :class:`StatusBar` (if any) of the 3698 parent frame when the mouse pointer is inside the tool. 3699 """ 3700 3701 self._tbButtons.append(ToolBarItem(FlatToolbarItem(bitmap1, toolId, label, bitmap2, kind, shortHelp, longHelp), wx.Rect(), ControlNormal)) 3702 3703 3704 def AddSeparator(self): 3705 """ Adds a separator for spacing groups of tools in toolbar. """ 3706 3707 if len(self._tbButtons) > 0 and not self._tbButtons[len(self._tbButtons)-1]._tbItem.IsSeparator(): 3708 self._tbButtons.append(ToolBarItem(FlatToolbarItem(), wx.Rect(), ControlNormal)) 3709 3710 3711 def AddControl(self, control): 3712 """ 3713 Adds any control to the toolbar, typically e.g. a combobox. 3714 3715 :param `control`: the control to be added, a subclass of :class:`wx.Window` (but no :class:`TopLevelWindow`). 3716 """ 3717 3718 self._tbButtons.append(ToolBarItem(FlatToolbarItem(control), wx.Rect(), ControlNormal)) 3719 3720 3721 def AddCheckTool(self, toolId, label="", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap, shortHelp="", longHelp=""): 3722 """ 3723 Adds a new check (or toggle) tool to the toolbar. 3724 3725 :see: :meth:`~FlatMenuBar.AddTool` for parameter descriptions. 3726 """ 3727 3728 self.AddTool(toolId, label, bitmap1, bitmap2, kind=wx.ITEM_CHECK, shortHelp=shortHelp, longHelp=longHelp) 3729 3730 3731 def AddRadioTool(self, toolId, label= "", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap, shortHelp="", longHelp=""): 3732 """ 3733 Adds a new radio tool to the toolbar. 3734 3735 Consecutive radio tools form a radio group such that exactly one button in the 3736 group is pressed at any moment, in other words whenever a button in the group is 3737 pressed the previously pressed button is automatically released. 3738 3739 You should avoid having the radio groups of only one element as it would be 3740 impossible for the user to use such button. 3741 3742 By default, the first button in the radio group is initially pressed, the others are not. 3743 3744 :see: :meth:`~FlatMenuBar.AddTool` for parameter descriptions. 3745 """ 3746 3747 self.AddTool(toolId, label, bitmap1, bitmap2, kind=wx.ITEM_RADIO, shortHelp=shortHelp, longHelp=longHelp) 3748 3749 if len(self._tbButtons)<1 or not self._tbButtons[len(self._tbButtons)-2]._tbItem.IsRadioItem(): 3750 self._tbButtons[len(self._tbButtons)-1]._tbItem.Select(True) 3751 self._lastRadioGroup += 1 3752 3753 self._tbButtons[len(self._tbButtons)-1]._tbItem.SetGroup(self._lastRadioGroup) 3754 3755 3756 def SetUpdateInterval(self, interval): 3757 """ 3758 Sets the UpdateUI interval for toolbar items. All UpdateUI events are 3759 sent from within :meth:`~FlatMenuBar.OnIdle` handler, the default is 20 milliseconds. 3760 3761 :param integer `interval`: the updateUI interval in milliseconds. 3762 """ 3763 3764 self._interval = interval 3765 3766 3767 def PositionAUI(self, mgr, fixToolbar=True): 3768 """ 3769 Positions the control inside a wxAUI / PyAUI frame manager. 3770 3771 :param `mgr`: an instance of :class:`~wx.lib.agw.aui.framemanager.AuiManager` or :class:`framemanager`; 3772 :param bool `fixToolbar`: ``True`` if :class:`FlatMenuBar` can not be floated. 3773 """ 3774 3775 if CPP_AUI and isinstance(mgr, wx.aui.AuiManager): 3776 pn = AuiPaneInfo() 3777 else: 3778 pn = PyAuiPaneInfo() 3779 3780 xx = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) 3781 3782 # We add our menu bar as a toolbar, with the following settings 3783 3784 pn.Name("flat_menu_bar") 3785 pn.Caption("Menu Bar") 3786 pn.Top() 3787 pn.MinSize(wx.Size(xx/2, self._barHeight)) 3788 pn.LeftDockable(False) 3789 pn.RightDockable(False) 3790 pn.ToolbarPane() 3791 3792 if not fixToolbar: 3793 # We add our menu bar as a toolbar, with the following settings 3794 pn.BestSize(wx.Size(xx, self._barHeight)) 3795 pn.FloatingSize(wx.Size(300, self._barHeight)) 3796 pn.Floatable(True) 3797 pn.MaxSize(wx.Size(xx, self._barHeight)) 3798 pn.Gripper(True) 3799 3800 else: 3801 pn.BestSize(wx.Size(xx, self._barHeight)) 3802 pn.Gripper(False) 3803 3804 pn.Resizable(False) 3805 pn.PaneBorder(False) 3806 mgr.AddPane(self, pn) 3807 3808 self._mgr = mgr 3809 3810 3811 def DoGiveHelp(self, hit): 3812 """ 3813 Gives tooltips and help in :class:`StatusBar`. 3814 3815 :param `hit`: the toolbar tool currently hovered by the mouse. 3816 """ 3817 3818 shortHelp = hit.GetShortHelp() 3819 if shortHelp: 3820 self.SetToolTip(shortHelp) 3821 self._haveTip = True 3822 3823 longHelp = hit.GetLongHelp() 3824 if not longHelp: 3825 return 3826 3827 topLevel = wx.GetTopLevelParent(self) 3828 3829 if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar(): 3830 statusBar = topLevel.GetStatusBar() 3831 3832 if self._statusTimer and self._statusTimer.IsRunning(): 3833 self._statusTimer.Stop() 3834 statusBar.PopStatusText(0) 3835 3836 statusBar.PushStatusText(longHelp, 0) 3837 self._statusTimer = StatusBarTimer(self) 3838 self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT) 3839 3840 3841 def RemoveHelp(self): 3842 """ Removes the tooltips and statusbar help (if any) for a button. """ 3843 3844 if self._haveTip: 3845 self.SetToolTip("") 3846 self._haveTip = False 3847 3848 if self._statusTimer and self._statusTimer.IsRunning(): 3849 topLevel = wx.GetTopLevelParent(self) 3850 statusBar = topLevel.GetStatusBar() 3851 self._statusTimer.Stop() 3852 statusBar.PopStatusText(0) 3853 self._statusTimer = None 3854 3855 3856 def OnStatusBarTimer(self): 3857 """ Handles the timer expiring to delete the `longHelp` string in the :class:`StatusBar`. """ 3858 3859 topLevel = wx.GetTopLevelParent(self) 3860 statusBar = topLevel.GetStatusBar() 3861 statusBar.PopStatusText(0) 3862 3863 3864 3865class mcPopupWindow(wx.MiniFrame): 3866 """ Since Max OS does not support :class:`PopupWindow`, this is an alternative. """ 3867 3868 def __init__(self, parent): 3869 """ 3870 Default class constructor. 3871 3872 :param `parent`: the :class:`mcPopupWindow` parent window. 3873 """ 3874 3875 wx.MiniFrame.__init__(self, parent, style = wx.POPUP_WINDOW) 3876 self.SetExtraStyle(wx.WS_EX_TRANSIENT) 3877 self._parent = parent 3878 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) 3879 3880 3881 def OnLeaveWindow(self, event): 3882 """ 3883 Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`mcPopupWindow`. 3884 3885 :param `event`: a :class:`MouseEvent` event to be processed. 3886 """ 3887 3888 event.Skip() 3889 3890 3891havePopupWindow = 1 3892""" Flag used to indicate whether the platform supports the native :class:`PopupWindow`. """ 3893 3894if wx.Platform == '__WXMAC__': 3895 havePopupWindow = 0 3896 wx.PopupWindow = mcPopupWindow 3897 3898 3899# ---------------------------------------------------------------------------- # 3900# Class ShadowPopupWindow 3901# ---------------------------------------------------------------------------- # 3902 3903class ShadowPopupWindow(wx.PopupWindow): 3904 """ Base class for generic :class:`FlatMenu` derived from :class:`PopupWindow`. """ 3905 3906 def __init__(self, parent=None): 3907 """ 3908 Default class constructor. 3909 3910 :param `parent`: the :class:`ShadowPopupWindow` parent (tipically your main frame). 3911 """ 3912 3913 if not parent: 3914 parent = wx.GetApp().GetTopWindow() 3915 3916 if not parent: 3917 raise Exception("Can't create menu without parent!") 3918 3919 wx.PopupWindow.__init__(self, parent) 3920 3921 if "__WXMSW__" in wx.Platform and _libimported == "MH": 3922 3923 GCL_STYLE= -26 3924 cstyle= win32gui.GetClassLong(self.GetHandle(), GCL_STYLE) 3925 if cstyle & CS_DROPSHADOW == 0: 3926 win32api.SetClassLong(self.GetHandle(), 3927 GCL_STYLE, cstyle | CS_DROPSHADOW) 3928 3929 # popup windows are created hidden by default 3930 self.Hide() 3931 3932 3933#-------------------------------------------------------- 3934# Class FlatMenuButton 3935#-------------------------------------------------------- 3936 3937class FlatMenuButton(object): 3938 """ 3939 A nice small class that functions like :class:`wx.BitmapButton`, the reason I did 3940 not used :class:`wx.BitmapButton` is that on Linux, it has some extra margins that 3941 I can't seem to be able to remove. 3942 """ 3943 3944 def __init__(self, menu, up, normalBmp, disabledBmp=wx.NullBitmap, scrollOnHover=False): 3945 """ 3946 Default class constructor. 3947 3948 :param `menu`: the parent menu associated with this button, an instance of :class:`FlatMenu`; 3949 :param bool `up`: ``True`` for up arrow or ``False`` for down arrow; 3950 :param `normalBmp`: normal state bitmap, an instance of :class:`wx.Bitmap`; 3951 :param `disabledBmp`: disabled state bitmap, an instance of :class:`wx.Bitmap`. 3952 """ 3953 3954 self._normalBmp = normalBmp 3955 self._up = up 3956 self._parent = menu 3957 self._pos = wx.Point() 3958 self._size = wx.Size() 3959 self._timerID = wx.NewIdRef() 3960 self._scrollOnHover = scrollOnHover 3961 3962 if not disabledBmp.IsOk(): 3963 self._disabledBmp = wx.Bitmap(self._normalBmp.ConvertToImage().ConvertToGreyscale()) 3964 else: 3965 self._disabledBmp = disabledBmp 3966 3967 self._state = ControlNormal 3968 self._timer = wx.Timer(self._parent, self._timerID) 3969 self._timer.Stop() 3970 3971 3972 def __del__(self): 3973 """ Used internally. """ 3974 3975 if self._timer: 3976 if self._timer.IsRunning(): 3977 self._timer.Stop() 3978 3979 del self._timer 3980 3981 3982 def Contains(self, pt): 3983 """ Used internally. """ 3984 3985 rect = wx.Rect(self._pos, self._size) 3986 if not rect.Contains(pt): 3987 return False 3988 3989 return True 3990 3991 3992 def Draw(self, dc): 3993 """ 3994 Draws self at rect using dc. 3995 3996 :param `dc`: an instance of :class:`wx.DC`. 3997 """ 3998 3999 rect = wx.Rect(self._pos, self._size) 4000 xx = rect.x + (rect.width - self._normalBmp.GetWidth())/2 4001 yy = rect.y + (rect.height - self._normalBmp.GetHeight())/2 4002 4003 self._parent.GetRenderer().DrawScrollButton(dc, rect, self._state) 4004 dc.DrawBitmap(self._normalBmp, xx, yy, True) 4005 4006 4007 def ProcessLeftDown(self, pt): 4008 """ 4009 Handles left down mouse events. 4010 4011 :param `pt`: an instance of :class:`wx.Point` where the left mouse button was pressed. 4012 """ 4013 4014 if not self.Contains(pt): 4015 return False 4016 4017 self._state = ControlPressed 4018 self._parent.Refresh() 4019 4020 if self._up: 4021 self._parent.ScrollUp() 4022 else: 4023 self._parent.ScrollDown() 4024 4025 self._timer.Start(100) 4026 return True 4027 4028 4029 def ProcessLeftUp(self, pt): 4030 """ 4031 Handles left up mouse events. 4032 4033 :param `pt`: an instance of :class:`wx.Point` where the left mouse button was released. 4034 """ 4035 4036 # always stop the timer 4037 self._timer.Stop() 4038 4039 if not self.Contains(pt): 4040 return False 4041 4042 self._state = ControlFocus 4043 self._parent.Refresh() 4044 4045 return True 4046 4047 4048 def ProcessMouseMove(self, pt): 4049 """ 4050 Handles mouse motion events. This is called any time the mouse moves in the parent menu, 4051 so we must check to see if the mouse is over the button. 4052 4053 :param `pt`: an instance of :class:`wx.Point` where the mouse pointer was moved. 4054 """ 4055 4056 if not self.Contains(pt): 4057 4058 self._timer.Stop() 4059 if self._state != ControlNormal: 4060 4061 self._state = ControlNormal 4062 self._parent.Refresh() 4063 4064 return False 4065 4066 if self._scrollOnHover and not self._timer.IsRunning(): 4067 self._timer.Start(100) 4068 4069 # Process mouse move event 4070 if self._state != ControlFocus: 4071 if self._state != ControlPressed: 4072 self._state = ControlFocus 4073 self._parent.Refresh() 4074 4075 return True 4076 4077 4078 def GetTimerId(self): 4079 """ Returns the timer object identifier. """ 4080 4081 return self._timerID 4082 4083 4084 def GetTimer(self): 4085 """ Returns the timer object. """ 4086 4087 return self._timer 4088 4089 4090 def Move(self, input1, input2=None): 4091 """ 4092 Moves :class:`FlatMenuButton` to the specified position. 4093 4094 :param `input1`: if it is an instance of :class:`wx.Point`, it represents the :class:`FlatMenuButton` 4095 position and the `input2` parameter is not used. Otherwise it is an integer representing 4096 the button `x` position; 4097 :param `input2`: if not ``None``, it is an integer representing the button `y` position. 4098 """ 4099 4100 if type(input) == type(1): 4101 self._pos = wx.Point(input1, input2) 4102 else: 4103 self._pos = input1 4104 4105 4106 def SetSize(self, input1, input2=None): 4107 """ 4108 Sets the size for :class:`FlatMenuButton`. 4109 4110 :param `input1`: if it is an instance of :class:`wx.Size`, it represents the :class:`FlatMenuButton` 4111 size and the `input2` parameter is not used. Otherwise it is an integer representing 4112 the button width; 4113 :param `input2`: if not ``None``, it is an integer representing the button height. 4114 """ 4115 4116 if type(input) == type(1): 4117 self._size = wx.Size(input1, input2) 4118 else: 4119 self._size = input1 4120 4121 4122 def GetClientRect(self): 4123 """ Returns the client rectangle for :class:`FlatMenuButton`. """ 4124 4125 return wx.Rect(self._pos, self._size) 4126 4127 4128#-------------------------------------------------------- 4129# Class FlatMenuItemGroup 4130#-------------------------------------------------------- 4131 4132class FlatMenuItemGroup(object): 4133 """ 4134 A class that manages a group of radio menu items. 4135 """ 4136 4137 def __init__(self): 4138 """ Default class constructor. """ 4139 4140 self._items = [] 4141 4142 4143 def GetSelectedItem(self): 4144 """ Returns the selected item. """ 4145 4146 for item in self._items: 4147 if item.IsChecked(): 4148 return item 4149 4150 return None 4151 4152 4153 def Add(self, item): 4154 """ 4155 Adds a new item to the group. 4156 4157 :param `item`: an instance of :class:`FlatMenu`. 4158 """ 4159 4160 if item.IsChecked(): 4161 # uncheck all other items 4162 for exitem in self._items: 4163 exitem._bIsChecked = False 4164 4165 self._items.append(item) 4166 4167 4168 def Exist(self, item): 4169 """ 4170 Checks if an item is in the group. 4171 4172 :param `item`: an instance of :class:`FlatMenu`. 4173 """ 4174 4175 if item in self._items: 4176 return True 4177 4178 return False 4179 4180 4181 def SetSelection(self, item): 4182 """ 4183 Selects a particular item. 4184 4185 :param `item`: an instance of :class:`FlatMenu`. 4186 """ 4187 4188 # make sure this item exist in our group 4189 if not self.Exist(item): 4190 return 4191 4192 # uncheck all other items 4193 for exitem in self._items: 4194 exitem._bIsChecked = False 4195 4196 item._bIsChecked = True 4197 4198 4199 def Remove(self, item): 4200 """ 4201 Removes a particular item. 4202 4203 :param `item`: an instance of :class:`FlatMenu`. 4204 """ 4205 4206 if item not in self._items: 4207 return 4208 4209 self._items.remove(item) 4210 4211 if item.IsChecked() and len(self._items) > 0: 4212 #if the removed item was the selected one, 4213 # select the first one in the group 4214 self._items[0]._bIsChecked = True 4215 4216 4217#-------------------------------------------------------- 4218# Class FlatMenuBase 4219#-------------------------------------------------------- 4220 4221class FlatMenuBase(ShadowPopupWindow): 4222 """ 4223 Base class for generic flat menu derived from :class:`PopupWindow`. 4224 """ 4225 4226 def __init__(self, parent=None): 4227 """ 4228 Default class constructor. 4229 4230 :param `parent`: the :class:`ShadowPopupWindow` parent window. 4231 """ 4232 4233 self._rendererMgr = FMRendererMgr() 4234 self._parentMenu = parent 4235 self._openedSubMenu = None 4236 self._owner = None 4237 self._popupPtOffset = 0 4238 self._showScrollButtons = False 4239 self._upButton = None 4240 self._downButton = None 4241 self._is_dismiss = False 4242 4243 ShadowPopupWindow.__init__(self, parent) 4244 4245 4246 def OnDismiss(self): 4247 """ Fires an event ``EVT_FLAT_MENU_DISMISSED`` and handle menu dismiss. """ 4248 4249 # Release mouse capture if needed 4250 if self.HasCapture(): 4251 self.ReleaseMouse() 4252 4253 self._is_dismiss = True 4254 4255 # send an event about our dismissal to the parent (unless we are a sub menu) 4256 if self.IsShown() and not self._parentMenu: 4257 4258 event = FlatMenuEvent(wxEVT_FLAT_MENU_DISMISSED, self.GetId()) 4259 event.SetEventObject(self) 4260 4261 # Send it 4262 if self.GetMenuOwner(): 4263 self.GetMenuOwner().GetEventHandler().ProcessEvent(event) 4264 else: 4265 self.GetEventHandler().ProcessEvent(event) 4266 4267 4268 def Popup(self, pt, parent): 4269 """ 4270 Popups menu at the specified point. 4271 4272 :param `pt`: an instance of :class:`wx.Point`, assumed to be in screen coordinates. However, 4273 if `parent` is not ``None``, `pt` is translated into the screen coordinates using 4274 `parent.ClientToScreen()`; 4275 :param `parent`: if not ``None``, an instance of :class:`wx.Window`. 4276 """ 4277 4278 # some controls update themselves from OnIdle() call - let them do it 4279 if wx.GetApp().GetMainLoop(): 4280 wx.GetApp().GetMainLoop().ProcessIdle() 4281 4282 # The mouse was pressed in the parent coordinates, 4283 # e.g. pressing on the left top of a text ctrl 4284 # will result in (1, 1), these coordinates needs 4285 # to be converted into screen coords 4286 self._parentMenu = parent 4287 4288 # If we are topmost menu, we use the given pt 4289 # else we use the logical 4290 # parent (second argument provided to this function) 4291 4292 if self._parentMenu: 4293 pos = self._parentMenu.ClientToScreen(pt) 4294 else: 4295 pos = pt 4296 4297 # Fit the menu into screen 4298 pos = self.AdjustPosition(pos) 4299 if self._showScrollButtons: 4300 4301 sz = self.GetSize() 4302 # Get the screen height 4303 scrHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) 4304 4305 4306 # position the scrollbar - If we are doing scroll bar buttons put them in the top right and 4307 # bottom right or else place them as menu items at the top and bottom. 4308 if self.GetRenderer().scrollBarButtons: 4309 if not self._upButton: 4310 self._upButton = FlatMenuButton(self, True, ArtManager.Get().GetStockBitmap("arrow_up")) 4311 4312 if not self._downButton: 4313 self._downButton = FlatMenuButton(self, False, ArtManager.Get().GetStockBitmap("arrow_down")) 4314 4315 self._upButton.SetSize((SCROLL_BTN_HEIGHT, SCROLL_BTN_HEIGHT)) 4316 self._downButton.SetSize((SCROLL_BTN_HEIGHT, SCROLL_BTN_HEIGHT)) 4317 4318 self._upButton.Move((sz.x - SCROLL_BTN_HEIGHT - 4, 4)) 4319 self._downButton.Move((sz.x - SCROLL_BTN_HEIGHT - 4, scrHeight - pos.y - 2 - SCROLL_BTN_HEIGHT)) 4320 else: 4321 if not self._upButton: 4322 self._upButton = FlatMenuButton(self, True, getMenuUpArrowBitmap(), scrollOnHover=True) 4323 4324 if not self._downButton: 4325 self._downButton = FlatMenuButton(self, False, getMenuDownArrowBitmap(), scrollOnHover=True) 4326 4327 self._upButton.SetSize((sz.x-2, self.GetItemHeight())) 4328 self._downButton.SetSize((sz.x-2, self.GetItemHeight())) 4329 4330 self._upButton.Move((1, 3)) 4331 self._downButton.Move((1, scrHeight - pos.y - 3 - self.GetItemHeight())) 4332 4333 self.Move(pos) 4334 self.Show() 4335 4336 # Capture mouse event and direct them to us 4337 if not self.HasCapture(): 4338 self.CaptureMouse() 4339 4340 self._is_dismiss = False 4341 4342 4343 def AdjustPosition(self, pos): 4344 """ 4345 Adjusts position so the menu will be fully visible on screen. 4346 4347 :param `pos`: an instance of :class:`wx.Point` specifying the menu position. 4348 """ 4349 4350 # Check that the menu can fully appear in the screen 4351 scrWidth = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) 4352 scrHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) 4353 4354 scrollBarButtons = self.GetRenderer().scrollBarButtons 4355 scrollBarMenuItems = not scrollBarButtons 4356 4357 size = self.GetSize() 4358 if scrollBarMenuItems: 4359 size.y += self.GetItemHeight()*2 4360 4361 # always assume that we have scrollbuttons on 4362 self._showScrollButtons = False 4363 pos.y += self._popupPtOffset 4364 4365 if size.y + pos.y > scrHeight: 4366 # the menu will be truncated 4367 if self._parentMenu is None: 4368 # try to flip the menu 4369 flippedPosy = pos.y - size.y 4370 flippedPosy -= self._popupPtOffset 4371 4372 if flippedPosy >= 0 and flippedPosy + size.y < scrHeight: 4373 pos.y = flippedPosy 4374 return pos 4375 else: 4376 # We need to popup scrollbuttons! 4377 self._showScrollButtons = True 4378 4379 else: 4380 # we are a submenu 4381 # try to decrease the y value of the menu position 4382 newy = pos.y 4383 newy -= (size.y + pos.y) - scrHeight 4384 4385 if newy + size.y > scrHeight: 4386 # probably the menu size is too high to fit 4387 # the screen, we need scrollbuttons 4388 self._showScrollButtons = True 4389 else: 4390 pos.y = newy 4391 4392 menuMaxX = pos.x + size.x 4393 4394 if menuMaxX > scrWidth and pos.x < scrWidth: 4395 4396 if self._parentMenu: 4397 4398 # We are submenu 4399 self._shiftePos = (size.x + self._parentMenu.GetSize().x) 4400 pos.x -= self._shiftePos 4401 pos.x += 10 4402 4403 else: 4404 4405 self._shiftePos = ((size.x + pos.x) - scrWidth) 4406 pos.x -= self._shiftePos 4407 4408 else: 4409 4410 if self._parentMenu: 4411 pos.x += 5 4412 4413 return pos 4414 4415 4416 def Dismiss(self, dismissParent, resetOwner): 4417 """ 4418 Dismisses the popup window. 4419 4420 :param bool `dismissParent`: whether to dismiss the parent menu or not; 4421 :param bool `resetOwner`: ``True`` to delete the link between this menu and the 4422 owner menu, ``False`` otherwise. 4423 """ 4424 4425 # Check if child menu is poped, if so, dismiss it 4426 if self._openedSubMenu: 4427 self._openedSubMenu.Dismiss(False, resetOwner) 4428 4429 self.OnDismiss() 4430 4431 # Reset menu owner 4432 if resetOwner: 4433 self._owner = None 4434 4435 self.Show(False) 4436 4437 if self._parentMenu and dismissParent: 4438 4439 self._parentMenu.OnChildDismiss() 4440 self._parentMenu.Dismiss(dismissParent, resetOwner) 4441 4442 self._parentMenu = None 4443 4444 4445 def OnChildDismiss(self): 4446 """ Handles children dismiss. """ 4447 4448 self._openedSubMenu = None 4449 4450 4451 def GetRenderer(self): 4452 """ Returns the renderer for this class. """ 4453 4454 return self._rendererMgr.GetRenderer() 4455 4456 4457 def GetRootMenu(self): 4458 """ Returns the top level menu. """ 4459 4460 root = self 4461 while root._parentMenu: 4462 root = root._parentMenu 4463 4464 return root 4465 4466 4467 def SetOwnerHeight(self, height): 4468 """ 4469 Sets the menu owner height, this will be used to position the menu below 4470 or above the owner. 4471 4472 :param integer `height`: an integer representing the menu owner height. 4473 """ 4474 4475 self._popupPtOffset = height 4476 4477 4478 # by default do nothing 4479 def ScrollDown(self): 4480 """ 4481 Scroll one unit down. 4482 By default this function is empty, let derived class do something. 4483 """ 4484 4485 pass 4486 4487 4488 # by default do nothing 4489 def ScrollUp(self): 4490 """ 4491 Scroll one unit up. 4492 By default this function is empty, let derived class do something. 4493 """ 4494 4495 pass 4496 4497 4498 def GetMenuOwner(self): 4499 """ 4500 Returns the menu logical owner, the owner does not necessarly mean the 4501 menu parent, it can also be the window that popped up it. 4502 """ 4503 4504 return self._owner 4505 4506 4507#-------------------------------------------------------- 4508# Class ToolBarItem 4509#-------------------------------------------------------- 4510 4511class ToolBarItem(object): 4512 """ 4513 A simple class that holds information about a toolbar item. 4514 """ 4515 4516 def __init__(self, tbItem, rect, state): 4517 """ 4518 Default class constructor. 4519 4520 :param `tbItem`: an instance of :class:`FlatToolbarItem`; 4521 :param `rect`: the client rectangle for the toolbar item, an instance of :class:`wx.Rect`; 4522 :param integer `state`: the toolbar item state. 4523 4524 :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid item states. 4525 """ 4526 4527 self._tbItem = tbItem 4528 self._rect = rect 4529 self._state = state 4530 self._visible = True 4531 4532 4533#-------------------------------------------------------- 4534# Class FlatToolBarItem 4535#-------------------------------------------------------- 4536 4537class FlatToolbarItem(object): 4538 """ 4539 This class represents a toolbar item. 4540 """ 4541 4542 def __init__(self, controlType=None, id=wx.ID_ANY, label="", disabledBmp=wx.NullBitmap, kind=wx.ITEM_NORMAL, 4543 shortHelp="", longHelp=""): 4544 """ 4545 Default class constructor. 4546 4547 :param `controlType`: can be ``None`` for a toolbar separator, an instance 4548 of :class:`wx.Window` for a control or an instance of :class:`wx.Bitmap` for a standard 4549 toolbar tool; 4550 :param integer `id`: the toolbar tool id. If set to ``wx.ID_ANY``, a new id is 4551 automatically assigned; 4552 :param string `label`: the toolbar tool label; 4553 :param `disabledBmp`: the bitmap used when the tool is disabled. If the tool 4554 is a standard one (i.e., not a control or a separator), and `disabledBmp` 4555 is equal to :class:`NullBitmap`, the disabled bitmap is automatically generated 4556 by greing the normal one; 4557 :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default), 4558 ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been 4559 toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio 4560 group of tools each of which is automatically unchecked whenever another button 4561 in the group is checked; 4562 :param string `shortHelp`: a string used for the tool's tooltip; 4563 :param string `longHelp`: this string is shown in the :class:`StatusBar` (if any) of the 4564 parent frame when the mouse pointer is inside the tool. 4565 """ 4566 4567 if id == wx.ID_ANY: 4568 id = wx.NewIdRef() 4569 4570 if controlType is None: # Is a separator 4571 self._normalBmp = wx.NullBitmap 4572 self._id = wx.NewIdRef() 4573 self._label = "" 4574 self._disabledImg = wx.NullBitmap 4575 self._customCtrl = None 4576 kind = wx.ITEM_SEPARATOR 4577 4578 elif isinstance(controlType, wx.Window): # is a wxControl 4579 self._normalBmp = wx.NullBitmap 4580 self._id = id 4581 self._label = "" 4582 self._disabledImg = wx.NullBitmap 4583 self._customCtrl = controlType 4584 kind = FTB_ITEM_CUSTOM 4585 4586 elif isinstance(controlType, wx.Bitmap): # Bitmap construction, simple tool 4587 self._normalBmp = controlType 4588 self._id = id 4589 self._label = label 4590 self._disabledImg = disabledBmp 4591 self._customCtrl = None 4592 4593 if not self._disabledImg.IsOk(): 4594 # Create a grey bitmap from the normal bitmap 4595 self._disabledImg = wx.Bitmap(self._normalBmp.ConvertToImage().ConvertToGreyscale()) 4596 4597 self._kind = kind 4598 self._enabled = True 4599 self._selected = False 4600 self._group = -1 # group id for radio items 4601 4602 if not shortHelp: 4603 shortHelp = label 4604 4605 self._shortHelp = shortHelp 4606 self._longHelp = longHelp 4607 4608 4609 def GetLabel(self): 4610 """ Returns the tool label. """ 4611 4612 return self._label 4613 4614 4615 def SetLabel(self, label): 4616 """ 4617 Sets the tool label. 4618 4619 :param string `label`: the new tool string. 4620 """ 4621 4622 self._label = label 4623 4624 4625 def GetBitmap(self): 4626 """ Returns the tool bitmap. """ 4627 4628 return self._normalBmp 4629 4630 4631 def SetBitmap(self, bmp): 4632 """ 4633 Sets the tool bitmap. 4634 4635 :param `bmp`: the new tool bitmap, a valid :class:`wx.Bitmap` object. 4636 """ 4637 4638 self._normalBmp = bmp 4639 4640 4641 def GetDisabledBitmap(self): 4642 """ Returns the tool disabled bitmap. """ 4643 4644 return self._disabledImg 4645 4646 4647 def SetDisabledBitmap(self, bmp): 4648 """ 4649 Sets the tool disabled bitmap. 4650 4651 :param `bmp`: the new tool disabled bitmap, a valid :class:`wx.Bitmap` object. 4652 """ 4653 4654 self._disabledImg = bmp 4655 4656 4657 def GetId(self): 4658 """ Gets the tool id. """ 4659 4660 return self._id 4661 4662 4663 def IsSeparator(self): 4664 """ Returns whether the tool is a separator or not. """ 4665 4666 return self._kind == wx.ITEM_SEPARATOR 4667 4668 4669 def IsRadioItem(self): 4670 """ Returns ``True`` if the item is a radio item. """ 4671 4672 return self._kind == wx.ITEM_RADIO 4673 4674 4675 def IsCheckItem(self): 4676 """ Returns ``True`` if the item is a radio item. """ 4677 4678 return self._kind == wx.ITEM_CHECK 4679 4680 4681 def IsCustomControl(self): 4682 """ Returns whether the tool is a custom control or not. """ 4683 4684 return self._kind == FTB_ITEM_CUSTOM 4685 4686 4687 def IsRegularItem(self): 4688 """ Returns whether the tool is a standard tool or not. """ 4689 4690 return self._kind == wx.ITEM_NORMAL 4691 4692 4693 def GetCustomControl(self): 4694 """ Returns the associated custom control. """ 4695 4696 return self._customCtrl 4697 4698 4699 def IsSelected(self): 4700 """ Returns whether the tool is selected or checked.""" 4701 4702 return self._selected 4703 4704 4705 def IsChecked(self): 4706 """ Same as :meth:`~FlatToolbarItem.IsSelected`. More intuitive for check items though. """ 4707 4708 return self._selected 4709 4710 4711 def Select(self, select=True): 4712 """ 4713 Selects or checks a radio or check item. 4714 4715 :param bool `select`: ``True`` to select or check a tool, ``False`` to unselect 4716 or uncheck it. 4717 """ 4718 4719 self._selected = select 4720 4721 4722 def Toggle(self): 4723 """ Toggles a check item. """ 4724 4725 if self.IsCheckItem(): 4726 self._selected = not self._selected 4727 4728 4729 def SetGroup(self, group): 4730 """ 4731 Sets group id for a radio item, for other items does nothing. 4732 4733 :param `group`: an instance of :class:`FlatMenuItemGroup`. 4734 """ 4735 4736 if self.IsRadioItem(): 4737 self._group = group 4738 4739 4740 def GetGroup(self): 4741 """ Returns group id for radio item, or -1 for other item types. """ 4742 4743 return self._group 4744 4745 4746 def IsEnabled(self): 4747 """ Returns whether the tool is enabled or not. """ 4748 4749 return self._enabled 4750 4751 4752 def Enable(self, enable=True): 4753 """ 4754 Enables or disables the tool. 4755 4756 :param bool `enable`: ``True`` to enable the tool, ``False`` to disable it. 4757 """ 4758 4759 self._enabled = enable 4760 4761 4762 def GetShortHelp(self): 4763 """ Returns the tool short help string (displayed in the tool's tooltip). """ 4764 4765 if self._kind == wx.ITEM_NORMAL: 4766 return self._shortHelp 4767 4768 return "" 4769 4770 4771 def SetShortHelp(self, help): 4772 """ 4773 Sets the tool short help string (displayed in the tool's tooltip). 4774 4775 :param string `help`: the new tool short help string. 4776 """ 4777 4778 if self._kind == wx.ITEM_NORMAL: 4779 self._shortHelp = help 4780 4781 4782 def SetLongHelp(self, help): 4783 """ 4784 Sets the tool long help string (displayed in the parent frame :class:`StatusBar`). 4785 4786 :param string `help`: the new tool long help string. 4787 """ 4788 4789 if self._kind == wx.ITEM_NORMAL: 4790 self._longHelp = help 4791 4792 4793 def GetLongHelp(self): 4794 """ Returns the tool long help string (displayed in the parent frame :class:`StatusBar`). """ 4795 4796 if self._kind == wx.ITEM_NORMAL: 4797 return self._longHelp 4798 4799 return "" 4800 4801 4802#-------------------------------------------------------- 4803# Class FlatMenuItem 4804#-------------------------------------------------------- 4805 4806class FlatMenuItem(object): 4807 """ 4808 A class that represents an item in a menu. 4809 """ 4810 4811 def __init__(self, parent, id=wx.ID_SEPARATOR, label="", helpString="", 4812 kind=wx.ITEM_NORMAL, subMenu=None, normalBmp=wx.NullBitmap, 4813 disabledBmp=wx.NullBitmap, 4814 hotBmp=wx.NullBitmap): 4815 """ 4816 Default class constructor. 4817 4818 :param `parent`: menu that the menu item belongs to, an instance of :class:`FlatMenu`; 4819 :param integer `id`: the menu item identifier; 4820 :param string `label`: text for the menu item, as shown on the menu. An accelerator 4821 key can be specified using the ampersand '&' character. In order to embed 4822 an ampersand character in the menu item text, the ampersand must be doubled; 4823 :param string `helpString`: optional help string that will be shown on the status bar; 4824 :param integer `kind`: may be ``wx.ITEM_SEPARATOR``, ``wx.ITEM_NORMAL``, ``wx.ITEM_CHECK`` 4825 or ``wx.ITEM_RADIO``; 4826 :param `subMenu`: if not ``None``, the sub menu this item belongs to (an instance of :class:`FlatMenu`); 4827 :param `normalBmp`: normal bitmap to draw to the side of the text, this bitmap 4828 is used when the menu is enabled (an instance of :class:`wx.Bitmap`); 4829 :param `disabledBmp`: 'greyed' bitmap to draw to the side of the text, this 4830 bitmap is used when the menu is disabled, if none supplied normal is used (an instance of :class:`wx.Bitmap`); 4831 :param `hotBmp`: hot bitmap to draw to the side of the text, this bitmap is 4832 used when the menu is hovered, if non supplied, normal is used (an instance of :class:`wx.Bitmap`). 4833 """ 4834 4835 self._text = label 4836 self._kind = kind 4837 self._helpString = helpString 4838 4839 if id == wx.ID_ANY: 4840 id = wx.NewIdRef() 4841 4842 self._id = id 4843 self._parentMenu = parent 4844 self._subMenu = subMenu 4845 self._normalBmp = normalBmp 4846 self._disabledBmp = disabledBmp 4847 self._hotBmp = hotBmp 4848 self._bIsChecked = False 4849 self._bIsEnabled = True 4850 self._mnemonicIdx = wx.NOT_FOUND 4851 self._isAttachedToMenu = False 4852 self._accelStr = "" 4853 self._rect = wx.Rect() 4854 self._groupPtr = None 4855 self._visible = False 4856 self._contextMenu = None 4857 self._font = None 4858 self._textColour = None 4859 4860 self.SetLabel(self._text) 4861 self.SetMenuBar() 4862 4863 self._checkMarkBmp = wx.Bitmap(check_mark_xpm) 4864 self._checkMarkBmp.SetMask(wx.Mask(self._checkMarkBmp, wx.WHITE)) 4865 self._radioMarkBmp = wx.Bitmap(radio_item_xpm) 4866 self._radioMarkBmp.SetMask(wx.Mask(self._radioMarkBmp, wx.WHITE)) 4867 4868 4869 def SetLongHelp(self, help): 4870 """ 4871 Sets the item long help string (displayed in the parent frame :class:`StatusBar`). 4872 4873 :param string `help`: the new item long help string. 4874 """ 4875 4876 self._helpString = help 4877 4878 4879 def GetLongHelp(self): 4880 """ Returns the item long help string (displayed in the parent frame :class:`StatusBar`). """ 4881 4882 return self._helpString 4883 4884 4885 def GetShortHelp(self): 4886 """ Returns the item short help string (displayed in the tool's tooltip). """ 4887 4888 return "" 4889 4890 4891 def Enable(self, enable=True): 4892 """ 4893 Enables or disables a menu item. 4894 4895 :param bool `enable`: ``True`` to enable the menu item, ``False`` to disable it. 4896 """ 4897 4898 self._bIsEnabled = enable 4899 if self._parentMenu: 4900 self._parentMenu.UpdateItem(self) 4901 4902 4903 def GetBitmap(self): 4904 """ 4905 Returns the normal bitmap associated to the menu item or :class:`NullBitmap` if 4906 none has been supplied. 4907 """ 4908 4909 return self._normalBmp 4910 4911 4912 def GetDisabledBitmap(self): 4913 """ 4914 Returns the disabled bitmap associated to the menu item or :class:`NullBitmap` 4915 if none has been supplied. 4916 """ 4917 4918 return self._disabledBmp 4919 4920 4921 def GetHotBitmap(self): 4922 """ 4923 Returns the hot bitmap associated to the menu item or :class:`NullBitmap` if 4924 none has been supplied. 4925 """ 4926 4927 return self._hotBmp 4928 4929 4930 def GetHelp(self): 4931 """ Returns the item help string. """ 4932 4933 return self._helpString 4934 4935 4936 def GetId(self): 4937 """ Returns the item id. """ 4938 4939 return self._id 4940 4941 4942 def GetKind(self): 4943 """ 4944 Returns the menu item kind, can be one of ``wx.ITEM_SEPARATOR``, ``wx.ITEM_NORMAL``, 4945 ``wx.ITEM_CHECK`` or ``wx.ITEM_RADIO``. 4946 """ 4947 4948 return self._kind 4949 4950 4951 def GetLabel(self): 4952 """ Returns the menu item label (without the accelerator if it is part of the string). """ 4953 4954 return self._label 4955 4956 4957 def GetMenu(self): 4958 """ Returns the parent menu. """ 4959 4960 return self._parentMenu 4961 4962 4963 def GetContextMenu(self): 4964 """ Returns the context menu associated with this item (if any). """ 4965 4966 return self._contextMenu 4967 4968 4969 def SetContextMenu(self, context_menu): 4970 """ 4971 Assigns a context menu to this item. 4972 4973 :param `context_menu`: an instance of :class:`FlatMenu`. 4974 """ 4975 4976 self._contextMenu = context_menu 4977 4978 4979 def GetText(self): 4980 """ Returns the text associated with the menu item including the accelerator. """ 4981 4982 return self._text 4983 4984 4985 def GetSubMenu(self): 4986 """ Returns the sub-menu of this menu item (if any). """ 4987 4988 return self._subMenu 4989 4990 4991 def IsCheckable(self): 4992 """ Returns ``True`` if this item is of type ``wx.ITEM_CHECK``, ``False`` otherwise. """ 4993 4994 return self._kind == wx.ITEM_CHECK 4995 4996 4997 def IsChecked(self): 4998 """ 4999 Returns whether an item is checked or not. 5000 5001 :note: This method is meaningful only for items of kind ``wx.ITEM_CHECK`` or 5002 ``wx.ITEM_RADIO``. 5003 """ 5004 5005 return self._bIsChecked 5006 5007 5008 def IsRadioItem(self): 5009 """ Returns ``True`` if this item is of type ``wx.ITEM_RADIO``, ``False`` otherwise. """ 5010 5011 return self._kind == wx.ITEM_RADIO 5012 5013 5014 def IsEnabled(self): 5015 """ Returns whether an item is enabled or not. """ 5016 5017 return self._bIsEnabled 5018 5019 5020 def IsSeparator(self): 5021 """ Returns ``True`` if this item is of type ``wx.ITEM_SEPARATOR``, ``False`` otherwise. """ 5022 5023 return self._id == wx.ID_SEPARATOR 5024 5025 5026 def IsSubMenu(self): 5027 """ Returns whether an item is a sub-menu or not. """ 5028 5029 return self._subMenu != None 5030 5031 5032 def SetNormalBitmap(self, bmp): 5033 """ 5034 Sets the menu item normal bitmap. 5035 5036 :param `bmp`: an instance of :class:`wx.Bitmap`. 5037 """ 5038 5039 self._normalBmp = bmp 5040 5041 5042 def SetDisabledBitmap(self, bmp): 5043 """ 5044 Sets the menu item disabled bitmap. 5045 5046 :param `bmp`: an instance of :class:`wx.Bitmap`. 5047 """ 5048 5049 self._disabledBmp = bmp 5050 5051 5052 def SetHotBitmap(self, bmp): 5053 """ 5054 Sets the menu item hot bitmap. 5055 5056 :param `bmp`: an instance of :class:`wx.Bitmap`. 5057 """ 5058 5059 self._hotBmp = bmp 5060 5061 5062 def SetHelp(self, helpString): 5063 """ 5064 Sets the menu item help string. 5065 5066 :param string `helpString`: the new menu item help string. 5067 """ 5068 5069 self._helpString = helpString 5070 5071 5072 def SetMenu(self, menu): 5073 """ 5074 Sets the menu item parent menu. 5075 5076 :param `menu`: an instance of :class:`FlatMenu`. 5077 """ 5078 5079 self._parentMenu = menu 5080 5081 5082 def SetSubMenu(self, menu): 5083 """ 5084 Sets the menu item sub-menu. 5085 5086 :param `menu`: an instance of :class:`FlatMenu`. 5087 """ 5088 5089 self._subMenu = menu 5090 5091 # Fix toolbar update 5092 self.SetMenuBar() 5093 5094 5095 def GetAccelString(self): 5096 """ Returns the accelerator string. """ 5097 5098 return self._accelStr 5099 5100 5101 def SetRect(self, rect): 5102 """ 5103 Sets the menu item client rectangle. 5104 5105 :param `rect`: the menu item client rectangle, an instance of :class:`wx.Rect`. 5106 """ 5107 5108 self._rect = rect 5109 5110 5111 def GetRect(self): 5112 """ Returns the menu item client rectangle. """ 5113 5114 return self._rect 5115 5116 5117 def IsShown(self): 5118 """ Returns whether an item is shown or not. """ 5119 5120 return self._visible 5121 5122 5123 def Show(self, show=True): 5124 """ 5125 Actually shows/hides the menu item. 5126 5127 :param bool `show`: ``True`` to show the menu item, ``False`` to hide it. 5128 """ 5129 5130 self._visible = show 5131 5132 5133 def GetHeight(self): 5134 """ Returns the menu item height, in pixels. """ 5135 5136 if self.IsSeparator(): 5137 return self._parentMenu.GetRenderer().separatorHeight 5138 else: 5139 return self._parentMenu._itemHeight 5140 5141 5142 def GetSuitableBitmap(self, selected): 5143 """ 5144 Gets the bitmap that should be used based on the item state. 5145 5146 :param bool `selected`: ``True`` if this menu item is currently hovered by the mouse, 5147 ``False`` otherwise. 5148 """ 5149 5150 normalBmp = self._normalBmp 5151 gBmp = (self._disabledBmp.IsOk() and [self._disabledBmp] or [self._normalBmp])[0] 5152 hotBmp = (self._hotBmp.IsOk() and [self._hotBmp] or [self._normalBmp])[0] 5153 5154 if not self.IsEnabled(): 5155 return gBmp 5156 elif selected: 5157 return hotBmp 5158 else: 5159 return normalBmp 5160 5161 5162 def SetLabel(self, text): 5163 """ 5164 Sets the label text for this item from the text (excluding the accelerator). 5165 5166 :param string `text`: the new item label (excluding the accelerator). 5167 """ 5168 5169 if text: 5170 5171 indx = text.find("\t") 5172 if indx >= 0: 5173 self._accelStr = text[indx+1:] 5174 label = text[0:indx] 5175 else: 5176 self._accelStr = "" 5177 label = text 5178 5179 self._mnemonicIdx, self._label = GetAccelIndex(label) 5180 5181 else: 5182 5183 self._mnemonicIdx = wx.NOT_FOUND 5184 self._label = "" 5185 5186 if self._parentMenu: 5187 self._parentMenu.UpdateItem(self) 5188 5189 5190 def SetText(self, text): 5191 """ 5192 Sets the text for this menu item (including accelerators). 5193 5194 :param string `text`: the new item label (including the accelerator). 5195 """ 5196 5197 self._text = text 5198 self.SetLabel(self._text) 5199 5200 5201 def SetMenuBar(self): 5202 """ Links the current menu item with the main :class:`FlatMenuBar`. """ 5203 5204 # Fix toolbar update 5205 if self._subMenu and self._parentMenu: 5206 self._subMenu.SetSubMenuBar(self._parentMenu.GetMenuBarForSubMenu()) 5207 5208 5209 def GetAcceleratorEntry(self): 5210 """ Returns the accelerator entry associated to this menu item. """ 5211 5212 if '\t' in self.GetText(): 5213 accel = wx.AcceleratorEntry() 5214 accel.FromString(self.GetText()) 5215 return accel 5216 elif self.GetAccelString(): 5217 accel = wx.AcceleratorEntry() 5218 accel.FromString(self.GetAccelString()) 5219 return accel 5220 else: 5221 return None 5222 5223 5224 def GetMnemonicChar(self): 5225 """ Returns the shortcut char for this menu item. """ 5226 5227 if self._mnemonicIdx == wx.NOT_FOUND: 5228 return 0 5229 5230 mnemonic = self._label[self._mnemonicIdx] 5231 return mnemonic.lower() 5232 5233 5234 def Check(self, check=True): 5235 """ 5236 Checks or unchecks the menu item. 5237 5238 :param bool `check`: ``True`` to check the menu item, ``False`` to uncheck it. 5239 5240 :note: This method is meaningful only for menu items of ``wx.ITEM_CHECK`` 5241 or ``wx.ITEM_RADIO`` kind. 5242 """ 5243 5244 if self.IsRadioItem() and not self._isAttachedToMenu: 5245 5246 # radio items can be checked only after they are attached to menu 5247 return 5248 5249 self._bIsChecked = check 5250 5251 # update group 5252 if self.IsRadioItem() and check: 5253 self._groupPtr.SetSelection(self) 5254 5255 # Our parent menu might want to do something with this change 5256 if self._parentMenu: 5257 self._parentMenu.UpdateItem(self) 5258 5259 5260 def SetFont(self, font=None): 5261 """ 5262 Sets the :class:`FlatMenuItem` font. 5263 5264 :param `font`: an instance of a valid :class:`wx.Font`. 5265 """ 5266 5267 self._font = font 5268 5269 if self._parentMenu: 5270 self._parentMenu.UpdateItem(self) 5271 5272 5273 def GetFont(self): 5274 """ Returns this :class:`FlatMenuItem` font. """ 5275 5276 return self._font 5277 5278 5279 def SetTextColour(self, colour=None): 5280 """ 5281 Sets the :class:`FlatMenuItem` foreground colour for the menu label. 5282 5283 :param `colour`: an instance of a valid :class:`wx.Colour`. 5284 """ 5285 5286 self._textColour = colour 5287 5288 5289 def GetTextColour(self): 5290 """ Returns this :class:`FlatMenuItem` foreground text colour. """ 5291 5292 return self._textColour 5293 5294 5295#-------------------------------------------------------- 5296# Class FlatMenu 5297#-------------------------------------------------------- 5298 5299class FlatMenu(FlatMenuBase): 5300 """ 5301 A Flat popup menu generic implementation. 5302 """ 5303 5304 def __init__(self, parent=None): 5305 """ 5306 Default class constructor. 5307 5308 :param `parent`: the :class:`FlatMenu` parent window (used to initialize the 5309 underlying :class:`ShadowPopupWindow`). 5310 """ 5311 5312 self._menuWidth = 2*26 5313 self._leftMarginWidth = 26 5314 self._rightMarginWidth = 30 5315 self._borderXWidth = 1 5316 self._borderYWidth = 2 5317 self._activeWin = None 5318 self._focusWin = None 5319 self._imgMarginX = 0 5320 self._markerMarginX = 0 5321 self._textX = 26 5322 self._rightMarginPosX = -1 5323 self._itemHeight = 20 5324 self._selectedItem = -1 5325 self._clearCurrentSelection = True 5326 self._textPadding = 8 5327 self._marginHeight = 20 5328 self._marginWidth = 26 5329 self._accelWidth = 0 5330 self._mb = None 5331 self._itemsArr = [] 5332 self._accelArray = [] 5333 self._ptLast = wx.Point() 5334 self._resizeMenu = True 5335 self._shiftePos = 0 5336 self._first = 0 5337 self._mb_submenu = 0 5338 self._is_dismiss = False 5339 self._numCols = 1 5340 self._backgroundImage = None 5341 self._originalBackgroundImage = None 5342 5343 FlatMenuBase.__init__(self, parent) 5344 5345 self.SetSize(wx.Size(self._menuWidth, self._itemHeight+4)) 5346 5347 self.Bind(wx.EVT_PAINT, self.OnPaint) 5348 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) 5349 self.Bind(wx.EVT_MOTION, self.OnMouseMove) 5350 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow) 5351 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeaveWindow) 5352 self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) 5353 self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp) 5354 self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseLeftDown) 5355 self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown) 5356 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) 5357 self.Bind(wx.EVT_TIMER, self.OnTimer) 5358 5359 5360 def SetMenuBar(self, mb): 5361 """ 5362 Attaches this menu to a menubar. 5363 5364 :param `mb`: an instance of :class:`FlatMenuBar`. 5365 """ 5366 5367 self._mb = mb 5368 5369 5370 def SetSubMenuBar(self, mb): 5371 """ 5372 Attaches this menu to a menubar. 5373 5374 :param `mb`: an instance of :class:`FlatMenuBar`. 5375 """ 5376 5377 self._mb_submenu = mb 5378 5379 5380 def GetMenuBar(self): 5381 """ Returns the menubar associated with this menu item. """ 5382 5383 if self._mb_submenu: 5384 return self._mb_submenu 5385 5386 return self._mb 5387 5388 5389 def GetMenuBarForSubMenu(self): 5390 """ Returns the menubar associated with this menu item. """ 5391 5392 return self._mb 5393 5394 5395 def Popup(self, pt, owner=None, parent=None): 5396 """ 5397 Pops up the menu. 5398 5399 :param `pt`: the point at which the menu should be popped up (an instance 5400 of :class:`wx.Point`); 5401 :param `owner`: the owner of the menu. The owner does not necessarly mean the 5402 menu parent, it can also be the window that popped up it; 5403 :param `parent`: the menu parent window. 5404 """ 5405 5406 if "__WXMSW__" in wx.Platform: 5407 self._mousePtAtStartup = wx.GetMousePosition() 5408 5409 # each time we popup, need to reset the starting index 5410 self._first = 0 5411 5412 # Loop over self menu and send update UI event for 5413 # every item in the menu 5414 numEvents = len(self._itemsArr) 5415 cc = 0 5416 self._shiftePos = 0 5417 5418 # Set the owner of the menu. All events will be directed to it. 5419 # If owner is None, the Default GetParent() is used as the owner 5420 self._owner = owner 5421 5422 for cc in range(numEvents): 5423 self.SendUIEvent(cc) 5424 5425 # Adjust menu position and show it 5426 FlatMenuBase.Popup(self, pt, parent) 5427 5428 artMgr = ArtManager.Get() 5429 artMgr.MakeWindowTransparent(self, artMgr.GetTransparency()) 5430 5431 # Replace the event handler of the active window to direct 5432 # all keyboard events to us and the focused window to direct char events to us 5433 self._activeWin = wx.GetActiveWindow() 5434 if self._activeWin: 5435 5436 oldHandler = self._activeWin.GetEventHandler() 5437 newEvtHandler = MenuKbdRedirector(self, oldHandler) 5438 self._activeWin.PushEventHandler(newEvtHandler) 5439 5440 if "__WXMSW__" in wx.Platform: 5441 self._focusWin = wx.Window.FindFocus() 5442 elif "__WXGTK__" in wx.Platform: 5443 self._focusWin = self 5444 else: 5445 self._focusWin = None 5446 5447 if self._focusWin: 5448 newEvtHandler = FocusHandler(self) 5449 self._focusWin.PushEventHandler(newEvtHandler) 5450 5451 5452 def Append(self, id, item, helpString="", kind=wx.ITEM_NORMAL): 5453 """ 5454 Appends an item to this menu. 5455 5456 :param integer `id`: the menu item identifier; 5457 :param string `item`: the string to appear on the menu item; 5458 :param string `helpString`: an optional help string associated with the item. By default, 5459 the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string 5460 in the status line; 5461 :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default), 5462 ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been 5463 toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio 5464 group of tools each of which is automatically unchecked whenever another button 5465 in the group is checked; 5466 """ 5467 5468 newItem = FlatMenuItem(self, id, item, helpString, kind) 5469 return self.AppendItem(newItem) 5470 5471 5472 def Prepend(self, id, item, helpString="", kind=wx.ITEM_NORMAL): 5473 """ 5474 Prepends an item to this menu. 5475 5476 :param integer `id`: the menu item identifier; 5477 :param string `item`: the string to appear on the menu item; 5478 :param string `helpString`: an optional help string associated with the item. By default, 5479 the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string 5480 in the status line; 5481 :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default), 5482 ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been 5483 toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio 5484 group of tools each of which is automatically unchecked whenever another button 5485 in the group is checked; 5486 """ 5487 5488 newItem = FlatMenuItem(self, id, item, helpString, kind) 5489 return self.PrependItem(newItem) 5490 5491 5492 def AppendSubMenu(self, subMenu, item, helpString=""): 5493 """ 5494 Adds a pull-right submenu to the end of the menu. 5495 5496 This function is added to duplicate the API of :class:`wx.Menu`. 5497 5498 :see: :meth:`~FlatMenu.AppendMenu` for an explanation of the input parameters. 5499 """ 5500 5501 return self.AppendMenu(wx.ID_ANY, item, subMenu, helpString) 5502 5503 5504 def AppendMenu(self, id, item, subMenu, helpString=""): 5505 """ 5506 Adds a pull-right submenu to the end of the menu. 5507 5508 :param integer `id`: the menu item identifier; 5509 :param string `item`: the string to appear on the menu item; 5510 :param `subMenu`: an instance of :class:`FlatMenu`, the submenu to append; 5511 :param string `helpString`: an optional help string associated with the item. By default, 5512 the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string 5513 in the status line. 5514 """ 5515 5516 newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_NORMAL, subMenu) 5517 return self.AppendItem(newItem) 5518 5519 5520 def AppendItem(self, menuItem): 5521 """ 5522 Appends an item to this menu. 5523 5524 :param `menuItem`: an instance of :class:`FlatMenuItem`. 5525 """ 5526 5527 self._itemsArr.append(menuItem) 5528 return self.AddItem(menuItem) 5529 5530 5531 def PrependItem(self, menuItem): 5532 """ 5533 Prepends an item to this menu. 5534 5535 :param `menuItem`: an instance of :class:`FlatMenuItem`. 5536 """ 5537 5538 self._itemsArr.insert(0,menuItem) 5539 return self.AddItem(menuItem) 5540 5541 5542 def AddItem(self, menuItem): 5543 """ 5544 Internal function to add the item to this menu. The item must 5545 already be in the `self._itemsArr` attribute. 5546 5547 :param `menuItem`: an instance of :class:`FlatMenuItem`. 5548 """ 5549 5550 if not menuItem: 5551 raise Exception("Adding None item?") 5552 5553 # Reparent to us 5554 menuItem.SetMenu(self) 5555 menuItem._isAttachedToMenu = True 5556 5557 # Update the menu width if necessary 5558 menuItemWidth = self.GetMenuItemWidth(menuItem) 5559 self._menuWidth = (self._menuWidth > menuItemWidth + self._accelWidth and \ 5560 [self._menuWidth] or [menuItemWidth + self._accelWidth])[0] 5561 5562 menuHeight = 0 5563 switch = 1e6 5564 5565 if self._numCols > 1: 5566 nItems = len(self._itemsArr) 5567 switch = int(math.ceil((nItems - self._first)/float(self._numCols))) 5568 5569 for indx, item in enumerate(self._itemsArr): 5570 5571 if indx >= switch: 5572 break 5573 5574 if item.IsSeparator(): 5575 menuHeight += self.GetRenderer().separatorHeight 5576 else: 5577 menuHeight += self._itemHeight 5578 5579 self.SetSize(wx.Size(self._menuWidth*self._numCols, menuHeight+4)) 5580 5581 if self._originalBackgroundImage: 5582 img = self._originalBackgroundImage.ConvertToImage() 5583 img = img.Scale(self._menuWidth*self._numCols-2-self._leftMarginWidth, menuHeight, wx.IMAGE_QUALITY_HIGH) 5584 self._backgroundImage = img.ConvertToBitmap() 5585 5586 # Add accelerator entry to the menu if needed 5587 accel = menuItem.GetAcceleratorEntry() 5588 5589 if accel: 5590 accel.Set(accel.GetFlags(), accel.GetKeyCode(), menuItem.GetId()) 5591 self._accelArray.append(accel) 5592 5593 self.UpdateRadioGroup(menuItem) 5594 5595 return menuItem 5596 5597 5598 def GetMenuItems(self): 5599 """ Returns the list of menu items in the menu. """ 5600 5601 return self._itemsArr 5602 5603 5604 def GetMenuItemWidth(self, menuItem): 5605 """ 5606 Returns the width of a particular item. 5607 5608 :param `menuItem`: an instance of :class:`FlatMenuItem`. 5609 """ 5610 5611 menuItemWidth = 0 5612 text = menuItem.GetLabel() # Without accelerator 5613 accel = menuItem.GetAccelString() 5614 5615 dc = wx.ClientDC(self) 5616 5617 font = menuItem.GetFont() 5618 if font is None: 5619 font = ArtManager.Get().GetFont() 5620 5621 dc.SetFont(font) 5622 5623 accelFiller = "XXXX" # 4 spaces betweem text and accel column 5624 5625 # Calc text length/height 5626 dummy, itemHeight = dc.GetTextExtent("Tp") 5627 width, height = dc.GetTextExtent(text) 5628 accelWidth, accelHeight = dc.GetTextExtent(accel) 5629 filler, dummy = dc.GetTextExtent(accelFiller) 5630 5631 bmpHeight = bmpWidth = 0 5632 5633 if menuItem.GetBitmap().IsOk(): 5634 bmpHeight = menuItem.GetBitmap().GetHeight() 5635 bmpWidth = menuItem.GetBitmap().GetWidth() 5636 5637 if itemHeight < self._marginHeight: 5638 itemHeight = self._marginHeight 5639 5640 itemHeight = (bmpHeight > self._itemHeight and [bmpHeight] or [itemHeight])[0] 5641 itemHeight += 2*self._borderYWidth 5642 5643 # Update the global menu item height if needed 5644 self._itemHeight = (self._itemHeight > itemHeight and [self._itemHeight] or [itemHeight])[0] 5645 self._marginWidth = (self._marginWidth > bmpWidth and [self._marginWidth] or [bmpWidth])[0] 5646 5647 # Update the accel width 5648 accelWidth += filler 5649 if accel: 5650 self._accelWidth = (self._accelWidth > accelWidth and [self._accelWidth] or [accelWidth])[0] 5651 5652 # In case the item has image & is type radio or check, we need double size 5653 # left margin 5654 factor = (((menuItem.GetBitmap() != wx.NullBitmap) and \ 5655 (menuItem.IsCheckable() or (menuItem.GetKind() == wx.ITEM_RADIO))) and [2] or [1])[0] 5656 5657 if factor == 2: 5658 5659 self._imgMarginX = self._marginWidth + 2*self._borderXWidth 5660 self._leftMarginWidth = 2 * self._marginWidth + 2*self._borderXWidth 5661 5662 else: 5663 5664 self._leftMarginWidth = ((self._leftMarginWidth > self._marginWidth + 2*self._borderXWidth) and \ 5665 [self._leftMarginWidth] or [self._marginWidth + 2*self._borderXWidth])[0] 5666 5667 menuItemWidth = self.GetLeftMarginWidth() + 2*self.GetBorderXWidth() + width + self.GetRightMarginWidth() 5668 self._textX = self._imgMarginX + self._marginWidth + self._textPadding 5669 5670 # update the rightMargin X position 5671 self._rightMarginPosX = ((self._textX + width + self._accelWidth> self._rightMarginPosX) and \ 5672 [self._textX + width + self._accelWidth] or [self._rightMarginPosX])[0] 5673 5674 return menuItemWidth 5675 5676 5677 def GetMenuWidth(self): 5678 """ Returns the menu width in pixels. """ 5679 5680 return self._menuWidth 5681 5682 5683 def GetLeftMarginWidth(self): 5684 """ Returns the menu left margin width, in pixels. """ 5685 5686 return self._leftMarginWidth 5687 5688 5689 def GetRightMarginWidth(self): 5690 """ Returns the menu right margin width, in pixels. """ 5691 5692 return self._rightMarginWidth 5693 5694 5695 def GetBorderXWidth(self): 5696 """ Returns the menu border x-width, in pixels. """ 5697 5698 return self._borderXWidth 5699 5700 5701 def GetBorderYWidth(self): 5702 """ Returns the menu border y-width, in pixels. """ 5703 5704 return self._borderYWidth 5705 5706 5707 def GetItemHeight(self): 5708 """ Returns the height of a particular item, in pixels. """ 5709 5710 return self._itemHeight 5711 5712 5713 def AppendCheckItem(self, id, item, helpString=""): 5714 """ 5715 Adds a checkable item to the end of the menu. 5716 5717 :see: :meth:`~FlatMenu.Append` for the explanation of the input parameters. 5718 """ 5719 5720 newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_CHECK) 5721 return self.AppendItem(newItem) 5722 5723 5724 def AppendRadioItem(self, id, item, helpString=""): 5725 """ 5726 Adds a radio item to the end of the menu. 5727 5728 All consequent radio items form a group and when an item in the group is 5729 checked, all the others are automatically unchecked. 5730 5731 :see: :meth:`~FlatMenu.Append` for the explanation of the input parameters. 5732 """ 5733 5734 newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_RADIO) 5735 return self.AppendItem(newItem) 5736 5737 5738 def AppendSeparator(self): 5739 """ Appends a separator item to the end of this menu. """ 5740 5741 newItem = FlatMenuItem(self) 5742 return self.AppendItem(newItem) 5743 5744 5745 def InsertSeparator(self, pos): 5746 """ 5747 Inserts a separator at the given position. 5748 5749 :param integer `pos`: the index at which we want to insert the separator. 5750 """ 5751 5752 newItem = FlatMenuItem(self) 5753 return self.InsertItem(pos, newItem) 5754 5755 5756 def Dismiss(self, dismissParent, resetOwner): 5757 """ 5758 Dismisses the popup window. 5759 5760 :param bool `dismissParent`: whether to dismiss the parent menu or not; 5761 :param bool `resetOwner`: ``True`` to delete the link between this menu and the 5762 owner menu, ``False`` otherwise. 5763 """ 5764 5765 if self._activeWin: 5766 5767 self._activeWin.PopEventHandler(True) 5768 self._activeWin = None 5769 5770 if self._focusWin: 5771 5772 self._focusWin.PopEventHandler(True) 5773 self._focusWin = None 5774 5775 self._selectedItem = -1 5776 5777 if self._mb: 5778 self._mb.RemoveHelp() 5779 5780 FlatMenuBase.Dismiss(self, dismissParent, resetOwner) 5781 5782 5783 def OnPaint(self, event): 5784 """ 5785 Handles the ``wx.EVT_PAINT`` event for :class:`FlatMenu`. 5786 5787 :param `event`: a :class:`PaintEvent` event to be processed. 5788 """ 5789 5790 dc = wx.PaintDC(self) 5791 self.GetRenderer().DrawMenu(self, dc) 5792 5793 # We need to redraw all our child menus 5794 self.RefreshChilds() 5795 5796 5797 def UpdateItem(self, item): 5798 """ 5799 Updates an item. 5800 5801 :param `item`: an instance of :class:`FlatMenuItem`. 5802 """ 5803 5804 # notify menu bar that an item was modified directly 5805 if item and self._mb: 5806 self._mb.UpdateItem(item) 5807 5808 5809 def OnEraseBackground(self, event): 5810 """ 5811 Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`FlatMenu`. 5812 5813 :param `event`: a :class:`EraseEvent` event to be processed. 5814 5815 :note: This method is intentionally empty to avoid flicker. 5816 """ 5817 5818 pass 5819 5820 5821 def DrawSelection(self, dc, oldSelection=-1): 5822 """ 5823 Redraws the menu. 5824 5825 :param `dc`: an instance of :class:`wx.DC`; 5826 :param integer `oldSelection`: if non-negative, the index representing the previous selected 5827 menu item. 5828 """ 5829 5830 self.Refresh() 5831 5832 5833 def RefreshChilds(self): 5834 """ 5835 In some cases, we need to perform a recursive refresh for all opened submenu 5836 from this. 5837 """ 5838 5839 # Draw all childs menus of self menu as well 5840 child = self._openedSubMenu 5841 while child: 5842 dc = wx.ClientDC(child) 5843 self.GetRenderer().DrawMenu(child, dc) 5844 child = child._openedSubMenu 5845 5846 5847 def GetMenuRect(self): 5848 """ Returns the menu client rectangle. """ 5849 5850 clientRect = self.GetClientRect() 5851 return wx.Rect(clientRect.x, clientRect.y, clientRect.width, clientRect.height) 5852 5853 5854 def OnKeyDown(self, event): 5855 """ 5856 Handles the ``wx.EVT_KEY_DOWN`` event for :class:`FlatMenu`. 5857 5858 :param `event`: a :class:`KeyEvent` event to be processed. 5859 """ 5860 5861 self.OnChar(event.GetKeyCode()) 5862 5863 5864 def OnChar(self, key): 5865 """ 5866 Handles key events for :class:`FlatMenu`. 5867 5868 :param `key`: the keyboard key integer code. 5869 """ 5870 5871 processed = True 5872 5873 if key == wx.WXK_ESCAPE: 5874 5875 if self._parentMenu: 5876 self._parentMenu.CloseSubMenu(-1) 5877 else: 5878 self.Dismiss(True, True) 5879 5880 elif key == wx.WXK_LEFT: 5881 5882 if self._parentMenu: 5883 # We are a submenu, dismiss us. 5884 self._parentMenu.CloseSubMenu(-1) 5885 else: 5886 # try to find our root menu, if we are attached to menubar, 5887 # let it try and open the previous menu 5888 root = self.GetRootMenu() 5889 if root: 5890 if root._mb: 5891 root._mb.ActivatePreviousMenu() 5892 5893 elif key == wx.WXK_RIGHT: 5894 5895 if not self.TryOpenSubMenu(self._selectedItem, True): 5896 # try to find our root menu, if we are attached to menubar, 5897 # let it try and open the previous menu 5898 root = self.GetRootMenu() 5899 if root: 5900 if root._mb: 5901 root._mb.ActivateNextMenu() 5902 5903 elif key == wx.WXK_UP: 5904 self.AdvanceSelection(False) 5905 5906 elif key == wx.WXK_DOWN: 5907 5908 self.AdvanceSelection() 5909 5910 elif key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: 5911 self.DoAction(self._selectedItem) 5912 5913 elif key == wx.WXK_HOME: 5914 5915 # Select first item of the menu 5916 if self._selectedItem != 0: 5917 oldSel = self._selectedItem 5918 self._selectedItem = 0 5919 dc = wx.ClientDC(self) 5920 self.DrawSelection(dc, oldSel) 5921 5922 elif key == wx.WXK_END: 5923 5924 # Select last item of the menu 5925 if self._selectedItem != len(self._itemsArr)-1: 5926 oldSel = self._selectedItem 5927 self._selectedItem = len(self._itemsArr)-1 5928 dc = wx.ClientDC(self) 5929 self.DrawSelection(dc, oldSel) 5930 5931 elif key in [wx.WXK_CONTROL, wx.WXK_ALT]: 5932 # Alt was pressed 5933 root = self.GetRootMenu() 5934 root.Dismiss(False, True) 5935 5936 else: 5937 try: 5938 chrkey = chr(key) 5939 except: 5940 return processed 5941 5942 if chrkey.isalnum(): 5943 5944 ch = chrkey.lower() 5945 5946 # Iterate over all the menu items 5947 itemIdx = -1 5948 occur = 0 5949 5950 for i in range(len(self._itemsArr)): 5951 5952 item = self._itemsArr[i] 5953 mnemonic = item.GetMnemonicChar() 5954 5955 if mnemonic == ch: 5956 5957 if itemIdx == -1: 5958 5959 itemIdx = i 5960 # We keep the index of only 5961 # the first occurence 5962 5963 occur += 1 5964 5965 # Keep on looping until no more items for self menu 5966 5967 if itemIdx != -1: 5968 5969 if occur > 1: 5970 5971 # We select the first item 5972 if self._selectedItem == itemIdx: 5973 return processed 5974 5975 oldSel = self._selectedItem 5976 self._selectedItem = itemIdx 5977 dc = wx.ClientDC(self) 5978 self.DrawSelection(dc, oldSel) 5979 5980 elif occur == 1: 5981 5982 # Activate the item, if self is a submenu item we first select it 5983 item = self._itemsArr[itemIdx] 5984 if item.IsSubMenu() and self._selectedItem != itemIdx: 5985 5986 oldSel = self._selectedItem 5987 self._selectedItem = itemIdx 5988 dc = wx.ClientDC(self) 5989 self.DrawSelection(dc, oldSel) 5990 5991 self.DoAction(itemIdx) 5992 5993 else: 5994 5995 processed = False 5996 5997 return processed 5998 5999 6000 def AdvanceSelection(self, down=True): 6001 """ 6002 Advance forward or backward the current selection. 6003 6004 :param bool `down`: ``True`` to advance the selection forward, ``False`` otherwise. 6005 """ 6006 6007 # make sure we have at least two items in the menu (which are not 6008 # separators) 6009 num=0 6010 singleItemIdx = -1 6011 6012 for i in range(len(self._itemsArr)): 6013 6014 item = self._itemsArr[i] 6015 if item.IsSeparator(): 6016 continue 6017 num += 1 6018 singleItemIdx = i 6019 6020 if num < 1: 6021 return 6022 6023 if num == 1: 6024 # Select the current one 6025 self._selectedItem = singleItemIdx 6026 dc = wx.ClientDC(self) 6027 self.DrawSelection(dc, -1) 6028 return 6029 6030 oldSelection = self._selectedItem 6031 6032 if not down: 6033 6034 # find the next valid item 6035 while 1: 6036 6037 self._selectedItem -= 1 6038 if self._selectedItem < 0: 6039 self._selectedItem = len(self._itemsArr)-1 6040 if not self._itemsArr[self._selectedItem].IsSeparator(): 6041 break 6042 6043 else: 6044 6045 # find the next valid item 6046 while 1: 6047 6048 self._selectedItem += 1 6049 if self._selectedItem > len(self._itemsArr)-1: 6050 self._selectedItem = 0 6051 if not self._itemsArr[self._selectedItem].IsSeparator(): 6052 break 6053 6054 dc = wx.ClientDC(self) 6055 self.DrawSelection(dc, oldSelection) 6056 6057 6058 def HitTest(self, pos): 6059 """ 6060 HitTest method for :class:`FlatMenu`. 6061 6062 :param `pos`: an instance of :class:`wx.Point`, a point to test for hits. 6063 6064 :return: A tuple representing one of the following combinations: 6065 6066 ========================= ================================================== 6067 Return Tuple Description 6068 ========================= ================================================== 6069 (0, -1) The :meth:`~FlatMenu.HitTest` method didn't find any item with the specified input point `pt` (``MENU_HT_NONE`` = 0) 6070 (1, `integer`) A menu item has been hit (``MENU_HT_ITEM`` = 1) 6071 (2, -1) The `Scroll Up` button has been hit (``MENU_HT_SCROLL_UP`` = 2) 6072 (3, -1) The `Scroll Down` button has been hit (``MENU_HT_SCROLL_DOWN`` = 3) 6073 ========================= ================================================== 6074 6075 """ 6076 6077 if self._showScrollButtons: 6078 6079 if self._upButton and self._upButton.GetClientRect().Contains(pos): 6080 return MENU_HT_SCROLL_UP, -1 6081 6082 if self._downButton and self._downButton.GetClientRect().Contains(pos): 6083 return MENU_HT_SCROLL_DOWN, -1 6084 6085 for ii, item in enumerate(self._itemsArr): 6086 6087 if item.GetRect().Contains(pos) and item.IsEnabled() and item.IsShown(): 6088 return MENU_HT_ITEM, ii 6089 6090 return MENU_HT_NONE, -1 6091 6092 6093 def OnMouseMove(self, event): 6094 """ 6095 Handles the ``wx.EVT_MOTION`` event for :class:`FlatMenu`. 6096 6097 :param `event`: a :class:`MouseEvent` event to be processed. 6098 """ 6099 6100 if "__WXMSW__" in wx.Platform: 6101 # Ignore dummy mouse move events 6102 pt = wx.GetMousePosition() 6103 if self._mousePtAtStartup == pt: 6104 return 6105 6106 pos = event.GetPosition() 6107 6108 # we need to ignore extra mouse events: example when this happens is when 6109 # the mouse is on the menu and we open a submenu from keyboard - Windows 6110 # then sends us a dummy mouse move event, we (correctly) determine that it 6111 # happens in the parent menu and so immediately close the just opened 6112 # submenunot 6113 6114 if "__WXMSW__" in wx.Platform: 6115 6116 ptCur = self.ClientToScreen(pos) 6117 if ptCur == self._ptLast: 6118 return 6119 6120 self._ptLast = ptCur 6121 6122 # first let the scrollbar handle it 6123 self.TryScrollButtons(event) 6124 self.ProcessMouseMove(pos) 6125 6126 6127 def OnMouseLeftDown(self, event): 6128 """ 6129 Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`FlatMenu`. 6130 6131 :param `event`: a :class:`MouseEvent` event to be processed. 6132 """ 6133 6134 if self.TryScrollButtons(event): 6135 return 6136 6137 pos = event.GetPosition() 6138 self.ProcessMouseLClick(pos) 6139 6140 6141 def OnMouseLeftUp(self, event): 6142 """ 6143 Handles the ``wx.EVT_LEFT_UP`` event for :class:`FlatMenu`. 6144 6145 :param `event`: a :class:`MouseEvent` event to be processed. 6146 """ 6147 6148 if self.TryScrollButtons(event): 6149 return 6150 6151 pos = event.GetPosition() 6152 rect = self.GetClientRect() 6153 6154 if not rect.Contains(pos): 6155 6156 # The event is not in our coords, 6157 # so we try our parent 6158 win = self._parentMenu 6159 6160 while win: 6161 6162 # we need to translate our client coords to the client coords of the 6163 # window we forward this event to 6164 ptScreen = self.ClientToScreen(pos) 6165 p = win.ScreenToClient(ptScreen) 6166 6167 if win.GetClientRect().Contains(p): 6168 6169 event.SetX(p.x) 6170 event.SetY(p.y) 6171 win.OnMouseLeftUp(event) 6172 return 6173 6174 else: 6175 # try the grandparent 6176 win = win._parentMenu 6177 6178 else: 6179 self.ProcessMouseLClickEnd(pos) 6180 6181 if self._showScrollButtons: 6182 6183 if self._upButton: 6184 self._upButton.ProcessLeftUp(pos) 6185 if self._downButton: 6186 self._downButton.ProcessLeftUp(pos) 6187 6188 6189 def OnMouseRightDown(self, event): 6190 """ 6191 Handles the ``wx.EVT_RIGHT_DOWN`` event for :class:`FlatMenu`. 6192 6193 :param `event`: a :class:`MouseEvent` event to be processed. 6194 """ 6195 6196 if self.TryScrollButtons(event): 6197 return 6198 6199 pos = event.GetPosition() 6200 self.ProcessMouseRClick(pos) 6201 6202 6203 def ProcessMouseRClick(self, pos): 6204 """ 6205 Processes mouse right clicks. 6206 6207 :param `pos`: the position at which the mouse right button was pressed, 6208 an instance of :class:`wx.Point`. 6209 """ 6210 6211 rect = self.GetClientRect() 6212 6213 if not rect.Contains(pos): 6214 6215 # The event is not in our coords, 6216 # so we try our parent 6217 6218 win = self._parentMenu 6219 while win: 6220 6221 # we need to translate our client coords to the client coords of the 6222 # window we forward self event to 6223 ptScreen = self.ClientToScreen(pos) 6224 p = win.ScreenToClient(ptScreen) 6225 6226 if win.GetClientRect().Contains(p): 6227 win.ProcessMouseRClick(p) 6228 return 6229 6230 else: 6231 # try the grandparent 6232 win = win._parentMenu 6233 6234 # At this point we can assume that the event was not 6235 # processed, so we dismiss the menu and its children 6236 self.Dismiss(True, True) 6237 return 6238 6239 # test if we are on a menu item 6240 res, itemIdx = self.HitTest(pos) 6241 if res == MENU_HT_ITEM: 6242 self.OpenItemContextMenu(itemIdx) 6243 6244 6245 def OpenItemContextMenu(self, itemIdx): 6246 """ 6247 Open an item's context menu (if any). 6248 6249 :param integer `itemIdx`: the index of the item for which we want to open the context menu. 6250 """ 6251 6252 item = self._itemsArr[itemIdx] 6253 context_menu = item.GetContextMenu() 6254 6255 # If we have a context menu, close any opened submenu 6256 if context_menu: 6257 self.CloseSubMenu(itemIdx, True) 6258 6259 if context_menu and not context_menu.IsShown(): 6260 # Popup child menu 6261 pos = wx.Point() 6262 pos.x = item.GetRect().GetWidth() + item.GetRect().GetX() - 5 6263 pos.y = item.GetRect().GetY() 6264 self._clearCurrentSelection = False 6265 self._openedSubMenu = context_menu 6266 context_menu.Popup(self.ScreenToClient(wx.GetMousePosition()), self._owner, self) 6267 return True 6268 6269 return False 6270 6271 6272 def ProcessMouseLClick(self, pos): 6273 """ 6274 Processes mouse left clicks. 6275 6276 :param `pos`: the position at which the mouse left button was pressed, 6277 an instance of :class:`wx.Point`. 6278 """ 6279 6280 rect = self.GetClientRect() 6281 6282 if not rect.Contains(pos): 6283 6284 # The event is not in our coords, 6285 # so we try our parent 6286 6287 win = self._parentMenu 6288 while win: 6289 6290 # we need to translate our client coords to the client coords of the 6291 # window we forward self event to 6292 ptScreen = self.ClientToScreen(pos) 6293 p = win.ScreenToClient(ptScreen) 6294 6295 if win.GetClientRect().Contains(p): 6296 win.ProcessMouseLClick(p) 6297 return 6298 6299 else: 6300 # try the grandparent 6301 win = win._parentMenu 6302 6303 # At this point we can assume that the event was not 6304 # processed, so we dismiss the menu and its children 6305 self.Dismiss(True, True) 6306 return 6307 6308 6309 def ProcessMouseLClickEnd(self, pos): 6310 """ 6311 Processes mouse left clicks. 6312 6313 :param `pos`: the position at which the mouse left button was pressed, 6314 an instance of :class:`wx.Point`. 6315 """ 6316 6317 self.ProcessMouseLClick(pos) 6318 6319 # test if we are on a menu item 6320 res, itemIdx = self.HitTest(pos) 6321 6322 if res == MENU_HT_ITEM: 6323 self.DoAction(itemIdx) 6324 6325 elif res == MENU_HT_SCROLL_UP: 6326 if self._upButton: 6327 self._upButton.ProcessLeftDown(pos) 6328 6329 elif res == MENU_HT_SCROLL_DOWN: 6330 if self._downButton: 6331 self._downButton.ProcessLeftDown(pos) 6332 6333 else: 6334 self._selectedItem = -1 6335 6336 6337 def ProcessMouseMove(self, pos): 6338 """ 6339 Processes mouse movements. 6340 6341 :param `pos`: the position at which the mouse was moved, an instance of :class:`wx.Point`. 6342 """ 6343 6344 rect = self.GetClientRect() 6345 6346 if not rect.Contains(pos): 6347 6348 # The event is not in our coords, 6349 # so we try our parent 6350 6351 win = self._parentMenu 6352 while win: 6353 6354 # we need to translate our client coords to the client coords of the 6355 # window we forward self event to 6356 ptScreen = self.ClientToScreen(pos) 6357 p = win.ScreenToClient(ptScreen) 6358 6359 if win.GetClientRect().Contains(p): 6360 win.ProcessMouseMove(p) 6361 return 6362 6363 else: 6364 # try the grandparent 6365 win = win._parentMenu 6366 6367 # If we are attached to a menu bar, 6368 # let him process the event as well 6369 if self._mb: 6370 6371 ptScreen = self.ClientToScreen(pos) 6372 p = self._mb.ScreenToClient(ptScreen) 6373 6374 if self._mb.GetClientRect().Contains(p): 6375 6376 # let the menu bar process it 6377 self._mb.ProcessMouseMoveFromMenu(p) 6378 return 6379 6380 if self._mb_submenu: 6381 ptScreen = self.ClientToScreen(pos) 6382 p = self._mb_submenu.ScreenToClient(ptScreen) 6383 if self._mb_submenu.GetClientRect().Contains(p): 6384 # let the menu bar process it 6385 self._mb_submenu.ProcessMouseMoveFromMenu(p) 6386 return 6387 6388 return 6389 6390 # test if we are on a menu item 6391 res, itemIdx = self.HitTest(pos) 6392 6393 if res == MENU_HT_SCROLL_DOWN: 6394 6395 if self._downButton: 6396 self._downButton.ProcessMouseMove(pos) 6397 6398 elif res == MENU_HT_SCROLL_UP: 6399 6400 if self._upButton: 6401 self._upButton.ProcessMouseMove(pos) 6402 6403 elif res == MENU_HT_ITEM: 6404 6405 if self._downButton: 6406 self._downButton.ProcessMouseMove(pos) 6407 6408 if self._upButton: 6409 self._upButton.ProcessMouseMove(pos) 6410 6411 if self._selectedItem == itemIdx: 6412 return 6413 6414 # Message to send when out of last selected item 6415 if self._selectedItem != -1: 6416 self.SendOverItem(self._selectedItem, False) 6417 self.SendOverItem(itemIdx, True) # Message to send when over an item 6418 6419 oldSelection = self._selectedItem 6420 self._selectedItem = itemIdx 6421 self.CloseSubMenu(self._selectedItem) 6422 6423 dc = wx.ClientDC(self) 6424 self.DrawSelection(dc, oldSelection) 6425 6426 self.TryOpenSubMenu(self._selectedItem) 6427 6428 if self._mb: 6429 self._mb.RemoveHelp() 6430 if itemIdx >= 0: 6431 self._mb.DoGiveHelp(self._itemsArr[itemIdx]) 6432 6433 else: 6434 6435 # Message to send when out of last selected item 6436 if self._selectedItem != -1: 6437 item = self._itemsArr[self._selectedItem] 6438 if item.IsSubMenu() and item.GetSubMenu().IsShown(): 6439 return 6440 6441 # Message to send when out of last selected item 6442 if self._selectedItem != -1: 6443 self.SendOverItem(self._selectedItem, False) 6444 6445 oldSelection = self._selectedItem 6446 self._selectedItem = -1 6447 dc = wx.ClientDC(self) 6448 self.DrawSelection(dc, oldSelection) 6449 6450 6451 def OnMouseLeaveWindow(self, event): 6452 """ 6453 Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenu`. 6454 6455 :param `event`: a :class:`MouseEvent` event to be processed. 6456 """ 6457 6458 if self._mb: 6459 self._mb.RemoveHelp() 6460 6461 if self._clearCurrentSelection: 6462 6463 # Message to send when out of last selected item 6464 if self._selectedItem != -1: 6465 item = self._itemsArr[self._selectedItem] 6466 if item.IsSubMenu() and item.GetSubMenu().IsShown(): 6467 return 6468 6469 # Message to send when out of last selected item 6470 if self._selectedItem != -1: 6471 self.SendOverItem(self._selectedItem, False) 6472 6473 oldSelection = self._selectedItem 6474 self._selectedItem = -1 6475 dc = wx.ClientDC(self) 6476 self.DrawSelection(dc, oldSelection) 6477 6478 self._clearCurrentSelection = True 6479 6480 if "__WXMSW__" in wx.Platform: 6481 self.SetCursor(self._oldCur) 6482 6483 6484 def OnMouseEnterWindow(self, event): 6485 """ 6486 Handles the ``wx.EVT_ENTER_WINDOW`` event for :class:`FlatMenu`. 6487 6488 :param `event`: a :class:`MouseEvent` event to be processed. 6489 """ 6490 6491 if "__WXMSW__" in wx.Platform: 6492 self._oldCur = self.GetCursor() 6493 self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) 6494 6495 event.Skip() 6496 6497 6498 def OnKillFocus(self, event): 6499 """ 6500 Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`FlatMenu`. 6501 6502 :param `event`: a :class:`FocusEvent` event to be processed. 6503 """ 6504 6505 self.Dismiss(True, True) 6506 6507 6508 def CloseSubMenu(self, itemIdx, alwaysClose=False): 6509 """ 6510 Closes a child sub-menu. 6511 6512 :param integer `itemIdx`: the index of the item for which we want to close the submenu; 6513 :param bool `alwaysClose`: if ``True``, always close the submenu irrespectively of 6514 other conditions. 6515 """ 6516 6517 item = None 6518 subMenu = None 6519 6520 if itemIdx >= 0 and itemIdx < len(self._itemsArr): 6521 item = self._itemsArr[itemIdx] 6522 6523 # Close sub-menu first 6524 if item: 6525 subMenu = item.GetSubMenu() 6526 6527 if self._openedSubMenu: 6528 if self._openedSubMenu != subMenu or alwaysClose: 6529 # We have another sub-menu open, close it 6530 self._openedSubMenu.Dismiss(False, True) 6531 self._openedSubMenu = None 6532 6533 6534 def DoAction(self, itemIdx): 6535 """ 6536 Performs an action based on user selection. 6537 6538 :param integer `itemIdx`: the index of the item for which we want to perform the action. 6539 """ 6540 6541 if itemIdx < 0 or itemIdx >= len(self._itemsArr): 6542 raise Exception("Invalid menu item") 6543 return 6544 6545 item = self._itemsArr[itemIdx] 6546 6547 if not item.IsEnabled() or item.IsSeparator(): 6548 return 6549 6550 # Close sub-menu if needed 6551 self.CloseSubMenu(itemIdx) 6552 6553 if item.IsSubMenu() and not item.GetSubMenu().IsShown(): 6554 6555 # Popup child menu 6556 self.TryOpenSubMenu(itemIdx) 6557 return 6558 6559 if item.IsRadioItem(): 6560 # if the radio item is already checked, 6561 # just send command event. Else, check it, uncheck the current 6562 # checked item in the radio item group, and send command event 6563 if not item.IsChecked(): 6564 item._groupPtr.SetSelection(item) 6565 6566 elif item.IsCheckable(): 6567 6568 item.Check(not item.IsChecked()) 6569 dc = wx.ClientDC(self) 6570 self.DrawSelection(dc) 6571 6572 if not item.IsSubMenu(): 6573 6574 self.Dismiss(True, False) 6575 6576 # Send command event 6577 self.SendCmdEvent(itemIdx) 6578 6579 6580 def TryOpenSubMenu(self, itemIdx, selectFirst=False): 6581 """ 6582 If `itemIdx` is an item with submenu, open it. 6583 6584 :param integer `itemIdx`: the index of the item for which we want to open the submenu; 6585 :param bool `selectFirst`: if ``True``, the first item of the submenu will be shown 6586 as selected. 6587 """ 6588 6589 if itemIdx < 0 or itemIdx >= len(self._itemsArr): 6590 return False 6591 6592 item = self._itemsArr[itemIdx] 6593 if item.IsSubMenu() and not item.GetSubMenu().IsShown(): 6594 6595 pos = wx.Point() 6596 6597 # Popup child menu 6598 pos.x = item.GetRect().GetWidth()+ item.GetRect().GetX()-5 6599 pos.y = item.GetRect().GetY() 6600 self._clearCurrentSelection = False 6601 self._openedSubMenu = item.GetSubMenu() 6602 item.GetSubMenu().Popup(pos, self._owner, self) 6603 6604 # Select the first child 6605 if selectFirst: 6606 6607 dc = wx.ClientDC(item.GetSubMenu()) 6608 item.GetSubMenu()._selectedItem = 0 6609 item.GetSubMenu().DrawSelection(dc) 6610 6611 return True 6612 6613 return False 6614 6615 6616 def _RemoveById(self, id): 6617 """ Used internally. """ 6618 6619 # First we search for the menu item (recursively) 6620 menuParent = None 6621 item = None 6622 idx = wx.NOT_FOUND 6623 idx, menuParent = self.FindMenuItemPos(id) 6624 6625 if idx != wx.NOT_FOUND: 6626 6627 # Remove the menu item 6628 item = menuParent._itemsArr[idx] 6629 menuParent._itemsArr.pop(idx) 6630 6631 # update group 6632 if item._groupPtr and item.IsRadioItem(): 6633 item._groupPtr.Remove(item) 6634 6635 # Resize the menu 6636 menuParent.ResizeMenu() 6637 6638 return item 6639 6640 6641 def Remove(self, item): 6642 """ 6643 Removes the menu item from the menu but doesn't delete the associated menu 6644 object. This allows to reuse the same item later by adding it back to the 6645 menu (especially useful with submenus). 6646 6647 :param `item`: can be either a menu item identifier or a plain :class:`FlatMenuItem`. 6648 """ 6649 6650 if type(item) != type(1): 6651 item = item.GetId() 6652 6653 return self._RemoveById(item) 6654 6655 Delete = Remove 6656 6657 6658 def _DestroyById(self, id): 6659 """ Used internally. """ 6660 6661 item = None 6662 item = self.Remove(id) 6663 6664 if item: 6665 del item 6666 6667 6668 def DestroyItem(self, item): 6669 """ 6670 Deletes the menu item from the menu. If the item is a submenu, it will be 6671 deleted. Use :meth:`~FlatMenu.Remove` if you want to keep the submenu (for example, to reuse 6672 it later). 6673 6674 :param `item`: can be either a menu item identifier or a plain :class:`FlatMenuItem`. 6675 """ 6676 6677 if type(item) != type(1): 6678 item = item.GetId() 6679 6680 self._DestroyById(item) 6681 6682 6683 def Insert(self, pos, id, item, helpString="", kind=wx.ITEM_NORMAL): 6684 """ 6685 Inserts the given `item` before the position `pos`. 6686 6687 :param integer `pos`: the position at which to insert the new menu item; 6688 :param integer `id`: the menu item identifier; 6689 :param string `item`: the string to appear on the menu item; 6690 :param string `helpString`: an optional help string associated with the item. By default, 6691 the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string 6692 in the status line; 6693 :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default), 6694 ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been 6695 toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio 6696 group of tools each of which is automatically unchecked whenever another button 6697 in the group is checked; 6698 """ 6699 6700 newitem = FlatMenuItem(self, id, item, helpString, kind) 6701 return self.InsertItem(pos, newitem) 6702 6703 6704 def InsertItem(self, pos, item): 6705 """ 6706 Inserts an item into the menu. 6707 6708 :param integer `pos`: the position at which to insert the new menu item; 6709 :param `item`: an instance of :class:`FlatMenuItem`. 6710 """ 6711 6712 if pos == len(self._itemsArr): 6713 # Append it 6714 return self.AppendItem(item) 6715 6716 # Insert the menu item 6717 self._itemsArr.insert(pos, item) 6718 item._isAttachedToMenu = True 6719 6720 # Recalculate the menu geometry 6721 self.ResizeMenu() 6722 6723 # Update radio groups 6724 self.UpdateRadioGroup(item) 6725 6726 return item 6727 6728 6729 def UpdateRadioGroup(self, item): 6730 """ 6731 Updates a group of radio items. 6732 6733 :param `item`: an instance of :class:`FlatMenuItem`. 6734 """ 6735 6736 if item.IsRadioItem(): 6737 6738 # Udpate radio groups in case this item is a radio item 6739 sibling = self.GetSiblingGroupItem(item) 6740 if sibling: 6741 6742 item._groupPtr = sibling._groupPtr 6743 item._groupPtr.Add(item) 6744 6745 if item.IsChecked(): 6746 6747 item._groupPtr.SetSelection(item) 6748 6749 else: 6750 6751 # first item in group 6752 item._groupPtr = FlatMenuItemGroup() 6753 item._groupPtr.Add(item) 6754 item._groupPtr.SetSelection(item) 6755 6756 6757 def ResizeMenu(self): 6758 """ Resizes the menu to the correct size. """ 6759 6760 # can we do the resize? 6761 if not self._resizeMenu: 6762 return 6763 6764 items = self._itemsArr 6765 self._itemsArr = [] 6766 6767 # Clear accelerator table 6768 self._accelArray = [] 6769 6770 # Reset parameters and menu size 6771 self._menuWidth = 2*self._marginWidth 6772 self._imgMarginX = 0 6773 self._markerMarginX = 0 6774 self._textX = self._marginWidth 6775 self._rightMarginPosX = -1 6776 self._itemHeight = self._marginHeight 6777 self.SetSize(wx.Size(self._menuWidth*self._numCols, self._itemHeight+4)) 6778 6779 # Now we simply add the items 6780 for item in items: 6781 self.AppendItem(item) 6782 6783 6784 def SetNumberColumns(self, numCols): 6785 """ 6786 Sets the number of columns for a menu window. 6787 6788 :param integer `numCols`: the number of columns for this :class:`FlatMenu` window. 6789 """ 6790 6791 if self._numCols == numCols: 6792 return 6793 6794 self._numCols = numCols 6795 self.ResizeMenu() 6796 self.Refresh() 6797 6798 6799 def GetNumberColumns(self): 6800 """ Returns the number of columns for a menu window. """ 6801 6802 return self._numCols 6803 6804 6805 def FindItem(self, itemId, menu=None): 6806 """ 6807 Finds the menu item object associated with the given menu item identifier and, 6808 optionally, the (sub)menu it belongs to. 6809 6810 :param integer `itemId`: menu item identifier; 6811 :param `menu`: if not ``None``, it will be filled with the item's parent menu 6812 (if the item was found). 6813 6814 :return: The found menu item object, or ``None`` if one was not found. 6815 """ 6816 6817 idx = wx.NOT_FOUND 6818 6819 if menu: 6820 6821 idx, menu = self.FindMenuItemPos(itemId, menu) 6822 if idx != wx.NOT_FOUND: 6823 return menu._itemsArr[idx] 6824 else: 6825 return None 6826 6827 else: 6828 6829 idx, parentMenu = self.FindMenuItemPos(itemId, None) 6830 if idx != wx.NOT_FOUND: 6831 return parentMenu._itemsArr[idx] 6832 else: 6833 return None 6834 6835 6836 def SetItemFont(self, itemId, font=None): 6837 """ 6838 Sets the :class:`FlatMenuItem` font. 6839 6840 :param integer `itemId`: the menu item identifier; 6841 :param `font`: an instance of a valid :class:`wx.Font`. 6842 """ 6843 6844 item = self.FindItem(itemId) 6845 item.SetFont(font) 6846 6847 6848 def GetItemFont(self, itemId): 6849 """ 6850 Returns this :class:`FlatMenuItem` font. 6851 6852 :param integer `itemId`: the menu item identifier. 6853 """ 6854 6855 item = self.FindItem(itemId) 6856 return item.GetFont() 6857 6858 6859 def SetItemTextColour(self, itemId, colour=None): 6860 """ 6861 Sets the :class:`FlatMenuItem` foreground text colour. 6862 6863 :param integer `itemId`: the menu item identifier; 6864 :param `colour`: an instance of a valid :class:`wx.Colour`. 6865 """ 6866 6867 item = self.FindItem(itemId) 6868 item.SetTextColour(colour) 6869 6870 6871 def GetItemTextColour(self, itemId): 6872 """ 6873 Returns this :class:`FlatMenuItem` foreground text colour. 6874 6875 :param integer `itemId`: the menu item identifier. 6876 """ 6877 6878 item = self.FindItem(itemId) 6879 return item.GetTextColour() 6880 6881 6882 def SetLabel(self, itemId, label): 6883 """ 6884 Sets the label of a :class:`FlatMenuItem`. 6885 6886 :param integer `itemId`: the menu item identifier; 6887 :param string `label`: the menu item label to set. 6888 6889 :see: :meth:`~FlatMenu.GetLabel`. 6890 """ 6891 6892 item = self.FindItem(itemId) 6893 item.SetLabel(label) 6894 item.SetText(label) 6895 6896 self.ResizeMenu() 6897 6898 6899 def GetLabel(self, itemId): 6900 """ 6901 Returns the label of a :class:`FlatMenuItem`. 6902 6903 :param integer `id`: the menu item identifier; 6904 6905 :see: :meth:`~FlatMenu.SetLabel`. 6906 """ 6907 6908 item = self.FindItem(itemId) 6909 return item.GetText() 6910 6911 6912 def FindMenuItemPos(self, itemId, menu=None): 6913 """ 6914 Finds an item and its position inside the menu based on its id. 6915 6916 :param integer `itemId`: menu item identifier; 6917 :param `menu`: if not ``None``, it will be filled with the item's parent menu 6918 (if the item was found). 6919 6920 :return: The found menu item object, or ``None`` if one was not found. 6921 """ 6922 6923 menu = None 6924 item = None 6925 6926 idx = wx.NOT_FOUND 6927 6928 for i in range(len(self._itemsArr)): 6929 6930 item = self._itemsArr[i] 6931 6932 if item.GetId() == itemId: 6933 6934 menu = self 6935 idx = i 6936 break 6937 6938 elif item.IsSubMenu(): 6939 6940 idx, menu = item.GetSubMenu().FindMenuItemPos(itemId, menu) 6941 if idx != wx.NOT_FOUND: 6942 break 6943 6944 else: 6945 6946 item = None 6947 6948 return idx, menu 6949 6950 6951 def GetAccelTable(self): 6952 """ Returns the menu accelerator table, an instance of :class:`AcceleratorTable`. """ 6953 6954 n = len(self._accelArray) 6955 if n == 0: 6956 return wx.NullAcceleratorTable 6957 6958 entries = [wx.AcceleratorEntry() for ii in range(n)] 6959 6960 for counter in len(entries): 6961 entries[counter] = self._accelArray[counter] 6962 6963 table = wx.AcceleratorTable(entries) 6964 del entries 6965 6966 return table 6967 6968 6969 def GetMenuItemCount(self): 6970 """ Returns the number of items in the :class:`FlatMenu`. """ 6971 6972 return len(self._itemsArr) 6973 6974 6975 def GetAccelArray(self): 6976 """ Returns a list filled with the accelerator entries for the menu. """ 6977 6978 return self._accelArray 6979 6980 6981 # events 6982 def SendCmdEvent(self, itemIdx): 6983 """ 6984 Actually sends menu command events. 6985 6986 :param integer `itemIdx`: the menu item index for which we want to send a command event. 6987 """ 6988 6989 if itemIdx < 0 or itemIdx >= len(self._itemsArr): 6990 raise Exception("Invalid menu item") 6991 return 6992 6993 item = self._itemsArr[itemIdx] 6994 6995 # Create the event 6996 event = wx.CommandEvent(wxEVT_FLAT_MENU_SELECTED, item.GetId()) 6997 6998 # For checkable item, set the IsChecked() value 6999 if item.IsCheckable(): 7000 event.SetInt((item.IsChecked() and [1] or [0])[0]) 7001 7002 event.SetEventObject(self) 7003 7004 if self._owner: 7005 self._owner.GetEventHandler().ProcessEvent(event) 7006 else: 7007 self.GetEventHandler().ProcessEvent(event) 7008 7009 7010 def SendOverItem(self, itemIdx, over): 7011 """ 7012 Sends the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` and ``EVT_FLAT_MENU_ITEM_MOUSE_OUT`` 7013 events. 7014 7015 :param integer `itemIdx`: the menu item index for which we want to send an event; 7016 :param bool `over`: ``True`` to send a ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event, ``False`` to 7017 send a ``EVT_FLAT_MENU_ITEM_MOUSE_OUT`` event. 7018 """ 7019 7020 item = self._itemsArr[itemIdx] 7021 7022 # Create the event 7023 event = FlatMenuEvent((over and [wxEVT_FLAT_MENU_ITEM_MOUSE_OVER] or [wxEVT_FLAT_MENU_ITEM_MOUSE_OUT])[0], item.GetId()) 7024 7025 # For checkable item, set the IsChecked() value 7026 if item.IsCheckable(): 7027 event.SetInt((item.IsChecked() and [1] or [0])[0]) 7028 7029 event.SetEventObject(self) 7030 7031 if self._owner: 7032 self._owner.GetEventHandler().ProcessEvent(event) 7033 else: 7034 self.GetEventHandler().ProcessEvent(event) 7035 7036 7037 def SendUIEvent(self, itemIdx): 7038 """ 7039 Actually sends menu UI events. 7040 7041 :param integer `itemIdx`: the menu item index for which we want to send a UI event. 7042 """ 7043 7044 if itemIdx < 0 or itemIdx >= len(self._itemsArr): 7045 raise Exception("Invalid menu item") 7046 return 7047 7048 item = self._itemsArr[itemIdx] 7049 event = wx.UpdateUIEvent(item.GetId()) 7050 7051 event.Check(item.IsChecked()) 7052 event.Enable(item.IsEnabled()) 7053 event.SetText(item.GetText()) 7054 event.SetEventObject(self) 7055 7056 if self._owner: 7057 self._owner.GetEventHandler().ProcessEvent(event) 7058 else: 7059 self.GetEventHandler().ProcessEvent(event) 7060 7061 item.Check(event.GetChecked()) 7062 item.SetLabel(event.GetText()) 7063 item.Enable(event.GetEnabled()) 7064 7065 7066 def Clear(self): 7067 """ Clears the menu items. """ 7068 7069 # since Destroy() call ResizeMenu(), we turn this flag on 7070 # to avoid resizing the menu for every item removed 7071 self._resizeMenu = False 7072 7073 lenItems = len(self._itemsArr) 7074 for ii in range(lenItems): 7075 self.DestroyItem(self._itemsArr[0].GetId()) 7076 7077 # Now we can resize the menu 7078 self._resizeMenu = True 7079 self.ResizeMenu() 7080 7081 7082 def FindMenuItemPosSimple(self, item): 7083 """ 7084 Finds an item and its position inside the menu based on its id. 7085 7086 :param `item`: an instance of :class:`FlatMenuItem`. 7087 7088 :return: An integer specifying the index found menu item object, or 7089 ``wx.NOT_FOUND`` if one was not found. 7090 """ 7091 7092 if item == None or len(self._itemsArr) == 0: 7093 return wx.NOT_FOUND 7094 7095 for i in range(len(self._itemsArr)): 7096 if self._itemsArr[i] == item: 7097 return i 7098 7099 return wx.NOT_FOUND 7100 7101 7102 def SetBackgroundBitmap(self, bitmap=None): 7103 """ 7104 Sets a background bitmap for this particular :class:`FlatMenu`. 7105 7106 :param `bitmap`: an instance of :class:`wx.Bitmap`. Set `bitmap` to ``None`` if you 7107 wish to remove the background bitmap altogether. 7108 7109 :note: the bitmap is rescaled to fit the menu width and height. 7110 """ 7111 7112 self._originalBackgroundImage = bitmap 7113 # Now we can resize the menu 7114 self._resizeMenu = True 7115 self.ResizeMenu() 7116 7117 7118 def GetBackgroundBitmap(self): 7119 """ Returns the background bitmap for this particular :class:`FlatMenu`, if any. """ 7120 7121 return self._originalBackgroundImage 7122 7123 7124 def GetAllItems(self, menu=None, items=[]): 7125 """ 7126 Internal function to help recurse through all the menu items. 7127 7128 :param `menu`: the menu from which we start accumulating items; 7129 :param list `items`: the array which is recursively filled with menu items. 7130 7131 :return: a list of :class:`FlatMenuItem`. 7132 """ 7133 7134 # first copy the current menu items 7135 newitems = [item for item in items] 7136 7137 if not menu: 7138 return newitems 7139 7140 # if any item in this menu has sub-menu, copy them as well 7141 for i in range(len(menu._itemsArr)): 7142 if menu._itemsArr[i].IsSubMenu(): 7143 newitems = self.GetAllItems(menu._itemsArr[i].GetSubMenu(), newitems) 7144 7145 return newitems 7146 7147 7148 def GetSiblingGroupItem(self, item): 7149 """ 7150 Used internally. 7151 7152 :param `item`: an instance of :class:`FlatMenuItem`. 7153 """ 7154 7155 pos = self.FindMenuItemPosSimple(item) 7156 if pos in [wx.NOT_FOUND, 0]: 7157 return None 7158 7159 if self._itemsArr[pos-1].IsRadioItem(): 7160 return self._itemsArr[pos-1] 7161 7162 return None 7163 7164 7165 def ScrollDown(self): 7166 """ Scrolls the menu down (for very tall menus). """ 7167 7168 # increase the self._from index 7169 if not self._itemsArr[-1].IsShown(): 7170 self._first += 1 7171 self.Refresh() 7172 7173 return True 7174 7175 else: 7176 if self._downButton: 7177 self._downButton.GetTimer().Stop() 7178 7179 return False 7180 7181 7182 def ScrollUp(self): 7183 """ Scrolls the menu up (for very tall menus). """ 7184 7185 if self._first == 0: 7186 if self._upButton: 7187 self._upButton.GetTimer().Stop() 7188 7189 return False 7190 7191 else: 7192 7193 self._first -= 1 7194 self.Refresh() 7195 return True 7196 7197 7198 # Not used anymore 7199 def TryScrollButtons(self, event): 7200 """ Used internally. """ 7201 7202 return False 7203 7204 7205 def OnTimer(self, event): 7206 """ 7207 Handles the ``wx.EVT_TIMER`` event for :class:`FlatMenu`. 7208 7209 :param `event`: a :class:`TimerEvent` event to be processed. 7210 """ 7211 7212 if self._upButton and self._upButton.GetTimerId() == event.GetId(): 7213 7214 self.ScrollUp() 7215 7216 elif self._downButton and self._downButton.GetTimerId() == event.GetId(): 7217 7218 self.ScrollDown() 7219 7220 else: 7221 7222 event.Skip() 7223 7224 7225#-------------------------------------------------------- 7226# Class MenuKbdRedirector 7227#-------------------------------------------------------- 7228 7229class MenuKbdRedirector(wx.EvtHandler): 7230 """ A keyboard event handler. """ 7231 7232 def __init__(self, menu, oldHandler): 7233 """ 7234 Default class constructor. 7235 7236 :param `menu`: an instance of :class:`FlatMenu` for which we want to redirect 7237 keyboard inputs; 7238 :param `oldHandler`: a previous (if any) :class:`EvtHandler` associated with 7239 the menu. 7240 """ 7241 7242 self._oldHandler = oldHandler 7243 self.SetMenu(menu) 7244 wx.EvtHandler.__init__(self) 7245 7246 7247 def SetMenu(self, menu): 7248 """ 7249 Sets the listener menu. 7250 7251 :param `menu`: an instance of :class:`FlatMenu`. 7252 """ 7253 7254 self._menu = menu 7255 7256 7257 def ProcessEvent(self, event): 7258 """ 7259 Processes the inout event. 7260 7261 :param `event`: any kind of keyboard-generated events. 7262 """ 7263 7264 if event.GetEventType() in [wx.EVT_KEY_DOWN, wx.EVT_CHAR, wx.EVT_CHAR_HOOK]: 7265 return self._menu.OnChar(event.GetKeyCode()) 7266 else: 7267 return self._oldHandler.ProcessEvent(event) 7268 7269 7270#-------------------------------------------------------- 7271# Class FocusHandler 7272#-------------------------------------------------------- 7273 7274class FocusHandler(wx.EvtHandler): 7275 """ A focus event handler. """ 7276 7277 def __init__(self, menu): 7278 """ 7279 Default class constructor. 7280 7281 :param `menu`: an instance of :class:`FlatMenu` for which we want to redirect 7282 focus inputs. 7283 """ 7284 7285 wx.EvtHandler.__init__(self) 7286 self.SetMenu(menu) 7287 7288 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 7289 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) 7290 7291 7292 def SetMenu(self, menu): 7293 """ 7294 Sets the listener menu. 7295 7296 :param `menu`: an instance of :class:`FlatMenu`. 7297 """ 7298 7299 self._menu = menu 7300 7301 7302 def OnKeyDown(self, event): 7303 """ 7304 Handles the ``wx.EVT_KEY_DOWN`` event for :class:`FocusHandler`. 7305 7306 :param `event`: a :class:`KeyEvent` event to be processed. 7307 """ 7308 7309 # Let parent process it 7310 self._menu.OnKeyDown(event) 7311 7312 7313 def OnKillFocus(self, event): 7314 """ 7315 Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`FocusHandler`. 7316 7317 :param `event`: a :class:`FocusEvent` event to be processed. 7318 """ 7319 7320 wx.PostEvent(self._menu, event) 7321 7322 7323