From: Pierre Date: Sat, 12 Jan 2013 23:23:12 +0000 (+0100) Subject: remove numpy + matrix X-Git-Url: http://iramuteq.org/git?a=commitdiff_plain;h=7fb5b2b86f6c9a0617208ee85211177c23d12f47;p=iramuteq remove numpy + matrix --- diff --git a/OptionAlceste.py b/OptionAlceste.py index 6465e29..49ff0f1 100755 --- a/OptionAlceste.py +++ b/OptionAlceste.py @@ -21,8 +21,8 @@ class OptionAlc(wx.Dialog): self.AlcesteConf = parametres self.choose = False - self.label_1 = wx.StaticText(self, -1, u"Lemmatisation") - self.radio_1 = wx.RadioBox(self, -1, u"", choices=['oui', 'non'], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + #self.label_1 = wx.StaticText(self, -1, u"Lemmatisation") + #self.radio_1 = wx.RadioBox(self, -1, u"", choices=['oui', 'non'], majorDimension=0, style=wx.RA_SPECIFY_ROWS) self.label_12 = wx.StaticText(self, -1, u"Classification") self.radio_box_2 = wx.RadioBox(self, -1, u"", choices=[u"double sur UC", u"simple sur UCE", u"simple sur UCI"], majorDimension=0, style=wx.RA_SPECIFY_ROWS) #, u"simple sur UCE (non implemente)" @@ -42,8 +42,8 @@ analysée (2 = automatique)""" self.spin_ctrl_5 = wx.SpinCtrl(self, -1, "",size = (100,30), min=2, max=1000) self.label_max_actives = wx.StaticText(self, -1, u"Nombre maximum de formes analysées") self.spin_max_actives = wx.SpinCtrl(self, -1, "",size = (100,30), min=20, max=10000) - self.label_4 = wx.StaticText(self, -1, u"Configuration \ndes clés d'analyse") - self.button_5 = wx.Button(self, wx.ID_PREFERENCES, "") + #self.label_4 = wx.StaticText(self, -1, u"Configuration \ndes clés d'analyse") + #self.button_5 = wx.Button(self, wx.ID_PREFERENCES, "") self.button_1 = wx.Button(self, wx.ID_CANCEL, "") self.button_2 = wx.Button(self, wx.ID_DEFAULT, u"Valeurs par défaut") self.button_4 = wx.Button(self, wx.ID_OK, "") @@ -52,18 +52,18 @@ analysée (2 = automatique)""" self.__set_properties() self.__do_layout() - self.Bind(wx.EVT_BUTTON, self.OnKeyPref, self.button_5) + #self.Bind(wx.EVT_BUTTON, self.OnKeyPref, self.button_5) self.Bind(wx.EVT_BUTTON, self.OnDef, self.button_2) def __set_properties(self): self.SetTitle("Options") #lang = self.AlcesteConf.get('ALCESTE', 'lang') #self.choice_dict.SetSelection(self.langues.index(lang)) - DefaultLem = self.parametres['lem'] - if DefaultLem : - self.radio_1.SetSelection(0) - else: - self.radio_1.SetSelection(1) + #DefaultLem = self.parametres['lem'] + #if DefaultLem : + # self.radio_1.SetSelection(0) + #else: + # self.radio_1.SetSelection(1) self.radio_box_2.SetSelection(int(self.parametres['classif_mode'])) self.spin_ctrl_1.SetValue(int(self.parametres['tailleuc1'])) self.spin_ctrl_2.SetValue(int(self.parametres['tailleuc2'])) @@ -82,10 +82,10 @@ analysée (2 = automatique)""" #grid_sizer2.Add(self.label_dict, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) #grid_sizer2.Add(self.choice_dict, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) - grid_sizer2.Add(self.label_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) - grid_sizer2.Add(self.radio_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) - grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) - grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + #grid_sizer2.Add(self.label_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(self.radio_1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + #grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) grid_sizer2.Add(self.label_12, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) grid_sizer2.Add(self.radio_box_2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) @@ -122,10 +122,10 @@ analysée (2 = automatique)""" grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) - grid_sizer2.Add(self.label_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) - grid_sizer2.Add(self.button_5, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) - grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) - grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) + #grid_sizer2.Add(self.label_4, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(self.button_5, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL, 0) + #grid_sizer2.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.ALL, 1) + #grid_sizer2.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 1) grid_button.Add(self.button_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) grid_button.Add(self.button_2, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) diff --git a/PrintRScript.py b/PrintRScript.py index db2eeac..9a92096 100644 --- a/PrintRScript.py +++ b/PrintRScript.py @@ -275,7 +275,7 @@ def RchdQuest(DicoPath, RscriptPath, nbcl = 10, mincl = 10): chd.result<-Rchdquest("%s","%s","%s", nbt = nbt, mincl = mincl) n1 <- chd.result$n1 classeuce1 <- chd.result$cuce1 - """ % (DicoPath['Act01'], DicoPath['listeuce1'], DicoPath['uce']) + """ % (DicoPath['mat01'], DicoPath['listeuce1'], DicoPath['uce']) txt += """ tree_tot1 <- make_tree_tot(chd.result$chd) diff --git a/ProfList.py b/ProfList.py index db822cf..600dbcf 100644 --- a/ProfList.py +++ b/ProfList.py @@ -49,13 +49,17 @@ class ProfListctrlPanel(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.Col self.lenact = profclasse.index([u'*****', u'*', u'*', u'*', u'*', u'*', '', '']) profclasse.pop(self.lenact) except ValueError: - self.lenact = len(profclasse) + try : + self.lenact = profclasse.index([u'*', u'*', u'*', u'*', u'*', u'*', '', '']) + profclasse.pop(self.lenact) + except ValueError: + self.lenact = len(profclasse) try : self.lensup = profclasse.index([u'*', u'*', u'*', u'*', u'*', u'*', '', '']) self.lensup = self.lensup - self.lenact profclasse.pop(self.lensup) except ValueError: - self.lensup = 0 + self.lensup = len(profclasse) - self.lenact self.lenet = len(profclasse) - (self.lenact + self.lensup) # print self.lenact, self.lensup, self.lenet for i, line in enumerate(classen) : diff --git a/Rscripts/chdfunct.R b/Rscripts/chdfunct.R index dd86dc1..a2dc502 100644 --- a/Rscripts/chdfunct.R +++ b/Rscripts/chdfunct.R @@ -293,95 +293,12 @@ AsLexico2<- function(mat, chip = FALSE) { out } - -##from textometrieR -##http://txm.sourceforge.net/doc/R/textometrieR-package.html -##Sylvain Loiseau -#specificites.probabilities <- function (lexicaltable, types = NULL, parts = NULL) -#{ -# rowMargin <- rowSums(lexicaltable) -# colMargin <- colSums(lexicaltable) -# F <- sum(lexicaltable) -# if (!is.null(types)) { -# if (is.character(types)) { -# if (is.null(rownames(lexicaltable))) -# stop("The lexical table has no row names and the \"types\" argument is a character vector.") -# if (!all(types %in% rownames(lexicaltable))) -# stop(paste("Some requested types are not known in the lexical table: ", -# paste(types[!(types %in% rownames(lexicaltable))], -# collapse = " "))) -# } -# else { -# if (any(types < 1)) -# stop("The row index must be greater than 0.") -# if (max(types) > nrow(lexicaltable)) -# stop("Row index must be smaller than the number of rows.") -# } -# lexicaltable <- lexicaltable[types, , drop = FALSE] -# rowMargin <- rowMargin[types] -# } -# if (!is.null(parts)) { -# if (is.character(parts)) { -# if (is.null(colnames(lexicaltable))) -# stop("The lexical table has no col names and the \"parts\" argument is a character vector.") -# if (!all(parts %in% colnames(lexicaltable))) -# stop(paste("Some requested parts are not known in the lexical table: ", -# paste(parts[!(parts %in% colnames(lexicaltable))], -# collapse = " "))) -# } -# else { -# if (max(parts) > ncol(lexicaltable)) -# stop("Column index must be smaller than the number of cols.") -# if (any(parts < 1)) -# stop("The col index must be greater than 0.") -# } -# lexicaltable <- lexicaltable[, parts, drop = FALSE] -# colMargin <- colMargin[parts] -# } -# if (nrow(lexicaltable) == 0 | ncol(lexicaltable) == 0) { -# stop("The lexical table must contains at least one row and one column.") -# } -# specif <- matrix(0, nrow = nrow(lexicaltable), ncol = ncol(lexicaltable)) -# for (i in 1:ncol(lexicaltable)) { -# whiteDrawn <- lexicaltable[, i] -# white <- rowMargin -# black <- F - white -# drawn <- colMargin[i] -# independance <- (white * drawn)/F -# specif_negative <- whiteDrawn < independance -# specif_positive <- whiteDrawn >= independance -# specif[specif_negative, i] <- phyper(whiteDrawn[specif_negative], -# white[specif_negative], black[specif_negative], drawn) -# specif[specif_positive, i] <- phyper(whiteDrawn[specif_positive] - -# 1, white[specif_positive], black[specif_positive], -# drawn) -# } -# dimnames(specif) <- dimnames(lexicaltable) -# return(specif) -#} -# -##from textometrieR -##http://txm.sourceforge.net/doc/R/textometrieR-package.html -##Sylvain Loiseau -#specificites <- function (lexicaltable, types = NULL, parts = NULL) -#{ -# spe <- specificites.probabilities(lexicaltable, types, parts) -# spelog <- matrix(0, nrow = nrow(spe), ncol = ncol(spe)) -# spelog[spe < 0.5] <- log10(spe[spe < 0.5]) -# spelog[spe > 0.5] <- abs(log10(1 - spe[spe > 0.5])) -# spelog[spe == 0.5] <- 0 -# spelog[is.infinite(spe)] <- 0 -# spelog <- round(spelog, digits = 4) -# rownames(spelog) <- rownames(spe) -# colnames(spelog) <- colnames(spe) -# return(spelog) -#} - make.spec.hypergeo <- function(mat) { library(textometrieR) spec <- specificites(mat) sumcol<-colSums(mat) eff_relatif<-round(t(apply(mat,1,function(x) {(x/t(as.matrix(sumcol))*1000)})),2) + colnames(eff_relatif) <- colnames(mat) out <-list() out[[1]]<-spec out[[3]]<-eff_relatif diff --git a/analysematrix.py b/analysematrix.py new file mode 100644 index 0000000..1a14c4a --- /dev/null +++ b/analysematrix.py @@ -0,0 +1,60 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +#Author: Pierre Ratinaud +#Copyright (c) 2013 Pierre Ratinaud +#Lisense: GNU GPL + + + +import logging +import os +from time import time +from uuid import uuid4 + + +from chemins import PathOut +from functions import exec_rcode, check_Rresult, DoConf +from time import time, sleep +from openanalyse import OpenAnalyse + +class AnalyseMatrix : + def __init__(self, ira, tableau, parametres = None, dlg = False) : + self.tableau = tableau + self.ira = ira + self.parent = ira + self.dlg = dlg + self.parametres = parametres + self.val = False + if not 'pathout' in self.parametres : + self.pathout = PathOut(tableau.parametres['filename'], analyse_type = parametres['type'], dirout = parametres['pathout']) + else : + self.pathout = PathOut(filename = tableau.parametres['filename'], dirout = self.parametres['pathout'], analyse_type = self.parametres['type']) + + self.parametres['pathout'] = self.pathout.dirout + self.parametres['uuid'] = str(uuid4()) + self.parametres['name'] = os.path.split(self.parametres['pathout'])[1] + self.parametres['encoding'] = self.ira.syscoding + + self.t1 = time() + result_analyse = self.doanalyse() + if result_analyse is None : + self.time = time() - self.t1 + minutes, seconds = divmod(self.time, 60) + hours, minutes = divmod(minutes, 60) + self.parametres['time'] = '%.0fh %.0fm %.0fs' % (hours, minutes, seconds) + self.parametres['ira'] = self.pathout['Analyse.ira'] + DoConf().makeoptions([self.parametres['type']], [self.parametres], self.pathout['Analyse.ira']) + self.ira.history.addMatrix(self.parametres) + if dlg : + dlg.Destroy() + OpenAnalyse(self.parent, self.parametres['ira']) + #self.ira.tree.AddAnalyse(self.parametres) + self.val = 5100 + else : + self.val = False + if dlg : + dlg.Destroy() + + def doanalyse(self) : + pass + diff --git a/analysetxt.py b/analysetxt.py index 3edf0a9..db69d01 100644 --- a/analysetxt.py +++ b/analysetxt.py @@ -13,6 +13,7 @@ from PrintRScript import RchdTxt, AlcesteTxtProf from OptionAlceste import OptionAlc from layout import PrintRapport from openanalyse import OpenAnalyse +from dialog import StatDialog from time import time log = logging.getLogger('iramuteq.analyse') @@ -30,15 +31,14 @@ class AnalyseText : self.pathout = PathOut(corpus.parametres['originalpath'], analyse_type = parametres['type'], dirout = corpus.parametres['pathout']) else : self.pathout = PathOut(filename = corpus.parametres['originalpath'], dirout = self.parametres['pathout'], analyse_type = self.parametres['name']) - self.parametres = self.make_config(parametres) + self.parametres = self.lemparam() + if self.parametres is not None : + self.parametres = self.make_config(parametres) log.info(self.pathout.dirout) if self.parametres is not None : self.keys = DoConf(self.ira.ConfigPath['key']).getoptions() gramact = [k for k in keys if keys[k] == 1] gramsup = [k for k in keys if keys[k] == 2] - #FIXME - if not 'lem' in self.parametres : - self.parametres['lem'] = 1 self.parametres['pathout'] = self.pathout.mkdirout() self.pathout = PathOut(dirout = self.parametres['pathout']) self.pathout.createdir(self.parametres['pathout']) @@ -76,18 +76,39 @@ class AnalyseText : def doanalyse(self) : pass + def lemparam(self) : + if self.dlg : + dial = StatDialog(self, self.parent) + dial.CenterOnParent() + val = dial.ShowModal() + if val == 5100 : + if dial.radio_lem.GetSelection() == 0 : + lem = 1 + else : + lem = 0 + self.parametres['lem'] = lem + dial.Destroy() + return self.parametres + else : + dial.Destroy() + return None + else : + return self.parametres + def make_config(self, config) : if config is not None : if not self.dlg : return config else : return self.preferences() + else : + return None def readconfig(self, config) : return config def preferences(self) : - return {} + return self.parametres def printRscript(self) : pass @@ -159,8 +180,6 @@ class Alceste(AnalyseText) : parametres['max_actives'] = self.dial.spin_max_actives.GetValue() parametres['corpus'] = '' parametres['pathout'] = self.pathout.dirout - for val in parametres : - print val, parametres[val] DoConf(self.parent.ConfigPath['alceste']).makeoptions(['ALCESTE'], [parametres]) self.dial.Destroy() return parametres diff --git a/aui/__init__.py b/aui/__init__.py new file mode 100644 index 0000000..ba3b51d --- /dev/null +++ b/aui/__init__.py @@ -0,0 +1,290 @@ +""" +AUI is an Advanced User Interface library that aims to implement "cutting-edge" +interface usability and design features so developers can quickly and easily create +beautiful and usable application interfaces. + + +Vision and Design Principles +============================ + +AUI attempts to encapsulate the following aspects of the user interface: + +* **Frame Management**: Frame management provides the means to open, move and hide common + controls that are needed to interact with the document, and allow these configurations + to be saved into different perspectives and loaded at a later time. + +* **Toolbars**: Toolbars are a specialized subset of the frame management system and should + behave similarly to other docked components. However, they also require additional + functionality, such as "spring-loaded" rebar support, "chevron" buttons and end-user + customizability. + +* **Modeless Controls**: Modeless controls expose a tool palette or set of options that + float above the application content while allowing it to be accessed. Usually accessed + by the toolbar, these controls disappear when an option is selected, but may also be + "torn off" the toolbar into a floating frame of their own. + +* **Look and Feel**: Look and feel encompasses the way controls are drawn, both when shown + statically as well as when they are being moved. This aspect of user interface design + incorporates "special effects" such as transparent window dragging as well as frame animation. + +AUI adheres to the following principles: + +- Use native floating frames to obtain a native look and feel for all platforms; +- Use existing wxPython code where possible, such as sizer implementation for frame management; +- Use standard wxPython coding conventions. + + +Usage +===== + +The following example shows a simple implementation that uses L{AuiManager} to manage +three text controls in a frame window:: + + class MyFrame(wx.Frame): + + def __init__(self, parent, id=-1, title="AUI Test", pos=wx.DefaultPosition, + size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): + + wx.Frame.__init__(self, parent, id, title, pos, size, style) + + self._mgr = aui.AuiManager() + + # notify AUI which frame to use + self._mgr.SetManagedWindow(self) + + # create several text controls + text1 = wx.TextCtrl(self, -1, "Pane 1 - sample text", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + text2 = wx.TextCtrl(self, -1, "Pane 2 - sample text", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + text3 = wx.TextCtrl(self, -1, "Main content window", + wx.DefaultPosition, wx.Size(200,150), + wx.NO_BORDER | wx.TE_MULTILINE) + + # add the panes to the manager + self._mgr.AddPane(text1, AuiPaneInfo().Left().Caption("Pane Number One")) + self._mgr.AddPane(text2, AuiPaneInfo().Bottom().Caption("Pane Number Two")) + self._mgr.AddPane(text3, AuiPaneInfo().CenterPane()) + + # tell the manager to "commit" all the changes just made + self._mgr.Update() + + self.Bind(wx.EVT_CLOSE, self.OnClose) + + + def OnClose(self, event): + + # deinitialize the frame manager + self._mgr.UnInit() + + self.Destroy() + event.Skip() + + + # our normal wxApp-derived class, as usual + + app = wx.PySimpleApp() + + frame = MyFrame(None) + app.SetTopWindow(frame) + frame.Show() + + app.MainLoop() + + +What's New +========== + +Current wxAUI Version Tracked: wxWidgets 2.9.0 (SVN HEAD) + +The wxPython AUI version fixes the following bugs or implement the following +missing features (the list is not exhaustive): + +- Visual Studio 2005 style docking: http://www.kirix.com/forums/viewtopic.php?f=16&t=596 +- Dock and Pane Resizing: http://www.kirix.com/forums/viewtopic.php?f=16&t=582 +- Patch concerning dock resizing: http://www.kirix.com/forums/viewtopic.php?f=16&t=610 +- Patch to effect wxAuiToolBar orientation switch: http://www.kirix.com/forums/viewtopic.php?f=16&t=641 +- AUI: Core dump when loading a perspective in wxGTK (MSW OK): http://www.kirix.com/forums/viewtopic.php?f=15&t=627 +- wxAuiNotebook reordered AdvanceSelection(): http://www.kirix.com/forums/viewtopic.php?f=16&t=617 +- Vertical Toolbar Docking Issue: http://www.kirix.com/forums/viewtopic.php?f=16&t=181 +- Patch to show the resize hint on mouse-down in aui: http://trac.wxwidgets.org/ticket/9612 +- The Left/Right and Top/Bottom Docks over draw each other: http://trac.wxwidgets.org/ticket/3516 +- MinSize() not honoured: http://trac.wxwidgets.org/ticket/3562 +- Layout problem with wxAUI: http://trac.wxwidgets.org/ticket/3597 +- Resizing children ignores current window size: http://trac.wxwidgets.org/ticket/3908 +- Resizing panes under Vista does not repaint background: http://trac.wxwidgets.org/ticket/4325 +- Resize sash resizes in response to click: http://trac.wxwidgets.org/ticket/4547 +- "Illegal" resizing of the AuiPane? (wxPython): http://trac.wxwidgets.org/ticket/4599 +- Floating wxAUIPane Resize Event doesn't update its position: http://trac.wxwidgets.org/ticket/9773 +- Don't hide floating panels when we maximize some other panel: http://trac.wxwidgets.org/ticket/4066 +- wxAUINotebook incorrect ALLOW_ACTIVE_PANE handling: http://trac.wxwidgets.org/ticket/4361 +- Page changing veto doesn't work, (patch supplied): http://trac.wxwidgets.org/ticket/4518 +- Show and DoShow are mixed around in wxAuiMDIChildFrame: http://trac.wxwidgets.org/ticket/4567 +- wxAuiManager & wxToolBar - ToolBar Of Size Zero: http://trac.wxwidgets.org/ticket/9724 +- wxAuiNotebook doesn't behave properly like a container as far as...: http://trac.wxwidgets.org/ticket/9911 +- Serious layout bugs in wxAUI: http://trac.wxwidgets.org/ticket/10620 +- wAuiDefaultTabArt::Clone() should just use copy contructor: http://trac.wxwidgets.org/ticket/11388 +- Drop down button for check tool on wxAuiToolbar: http://trac.wxwidgets.org/ticket/11139 + +Plus the following features: + +- AuiManager: + + (a) Implementation of a simple minimize pane system: Clicking on this minimize button causes a new + AuiToolBar to be created and added to the frame manager, (currently the implementation is such + that panes at West will have a toolbar at the right, panes at South will have toolbars at the + bottom etc...) and the pane is hidden in the manager. + Clicking on the restore button on the newly created toolbar will result in the toolbar being + removed and the original pane being restored; + (b) Panes can be docked on top of each other to form `AuiNotebooks`; `AuiNotebooks` tabs can be torn + off to create floating panes; + (c) On Windows XP, use the nice sash drawing provided by XP while dragging the sash; + (d) Possibility to set an icon on docked panes; + (e) Possibility to draw a sash visual grip, for enhanced visualization of sashes; + (f) Implementation of a native docking art (`ModernDockArt`). Windows XP only, **requires** Mark Hammond's + pywin32 package (winxptheme); + (g) Possibility to set a transparency for floating panes (a la Paint .NET); + (h) Snapping the main frame to the screen in any positin specified by horizontal and vertical + alignments; + (i) Snapping floating panes on left/right/top/bottom or any combination of directions, a la Winamp; + (j) "Fly-out" floating panes, i.e. panes which show themselves only when the mouse hover them; + (k) Ability to set custom bitmaps for pane buttons (close, maximize, etc...); + (l) Implementation of the style ``AUI_MGR_ANIMATE_FRAMES``, which fade-out floating panes when + they are closed (all platforms which support frames transparency) and show a moving rectangle + when they are docked and minimized (Windows < Vista and GTK only); + (m) A pane switcher dialog is available to cycle through existing AUI panes; + (n) Some flags which allow to choose the orientation and the position of the minimized panes; + (o) The functions [Get]MinimizeMode() in `AuiPaneInfo` which allow to set/get the flags described above; + (p) Events like ``EVT_AUI_PANE_DOCKING``, ``EVT_AUI_PANE_DOCKED``, ``EVT_AUI_PANE_FLOATING`` and ``EVT_AUI_PANE_FLOATED`` are + available for all panes *except* toolbar panes; + (q) Implementation of the RequestUserAttention method for panes; + (r) Ability to show the caption bar of docked panes on the left instead of on the top (with caption + text rotated by 90 degrees then). This is similar to what `wxDockIt` did. To enable this feature on any + given pane, simply call `CaptionVisible(True, left=True)`; + (s) New Aero-style docking guides: you can enable them by using the `AuiManager` style ``AUI_MGR_AERO_DOCKING_GUIDES``; + (t) A slide-in/slide-out preview of minimized panes can be seen by enabling the `AuiManager` style + ``AUI_MGR_PREVIEW_MINIMIZED_PANES`` and by hovering with the mouse on the minimized pane toolbar tool; + (u) New Whidbey-style docking guides: you can enable them by using the `AuiManager` style ``AUI_MGR_WHIDBEY_DOCKING_GUIDES``; + (v) Native of custom-drawn mini frames can be used as floating panes, depending on the ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style; + (w) A "smooth docking effect" can be obtained by using the ``AUI_MGR_SMOOTH_DOCKING`` style (similar to PyQT docking style). + +| + +- AuiNotebook: + + (a) Implementation of the style ``AUI_NB_HIDE_ON_SINGLE_TAB``, a la `wx.lib.agw.flatnotebook`; + (b) Implementation of the style ``AUI_NB_SMART_TABS``, a la `wx.lib.agw.flatnotebook`; + (c) Implementation of the style ``AUI_NB_USE_IMAGES_DROPDOWN``, which allows to show tab images + on the tab dropdown menu instead of bare check menu items (a la `wx.lib.agw.flatnotebook`); + (d) 6 different tab arts are available, namely: + + (1) Default "glossy" theme (as in `wx.aui.AuiNotebook`) + (2) Simple theme (as in `wx.aui.AuiNotebook`) + (3) Firefox 2 theme + (4) Visual Studio 2003 theme (VC71) + (5) Visual Studio 2005 theme (VC81) + (6) Google Chrome theme + + (e) Enabling/disabling tabs; + (f) Setting the colour of the tab's text; + (g) Implementation of the style ``AUI_NB_CLOSE_ON_TAB_LEFT``, which draws the tab close button on + the left instead of on the right (a la Camino browser); + (h) Ability to save and load perspectives in `AuiNotebook` (experimental); + (i) Possibility to add custom buttons in the `AuiNotebook` tab area; + (j) Implementation of the style ``AUI_NB_TAB_FLOAT``, which allows the floating of single tabs. + Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far + enough outside of the notebook to become floating pages; + (k) Implementation of the style ``AUI_NB_DRAW_DND_TAB`` (on by default), which draws an image + representation of a tab while dragging; + (l) Implementation of the `AuiNotebook` unsplit functionality, which unsplit a splitted AuiNotebook + when double-clicking on a sash; + (m) Possibility to hide all the tabs by calling `HideAllTAbs`; + (n) wxPython controls can now be added inside page tabs by calling `AddControlToPage`, and they can be + removed by calling `RemoveControlFromPage`; + (o) Possibility to preview all the pages in a `AuiNotebook` (as thumbnails) by using the `NotebookPreview` + method of `AuiNotebook`; + (p) Tab labels can be edited by calling the `SetRenamable` method on a `AuiNotebook` page; + (q) Support for multi-lines tab labels in `AuiNotebook`; + (r) Support for setting minimum and maximum tab widths for fixed width tabs; + (s) Implementation of the style ``AUI_NB_ORDER_BY_ACCESS``, which orders the tabs by last access time + inside the Tab Navigator dialog; + (t) Implementation of the style ``AUI_NB_NO_TAB_FOCUS``, allowing the developer not to draw the tab + focus rectangle on tne `AuiNotebook` tabs. + +| + +- AuiToolBar: + + (a) ``AUI_TB_PLAIN_BACKGROUND`` style that allows to easy setup a plain background to the AUI toolbar, + without the need to override drawing methods. This style contrasts with the default behaviour + of the `wx.aui.AuiToolBar` that draws a background gradient and this break the window design when + putting it within a control that has margin between the borders and the toolbar (example: put + `wx.aui.AuiToolBar` within a `wx.StaticBoxSizer` that has a plain background); + (b) `AuiToolBar` allow item alignment: http://trac.wxwidgets.org/ticket/10174; + (c) `AUIToolBar` `DrawButton()` improvement: http://trac.wxwidgets.org/ticket/10303; + (d) `AuiToolBar` automatically assign new id for tools: http://trac.wxwidgets.org/ticket/10173; + (e) `AuiToolBar` Allow right-click on any kind of button: http://trac.wxwidgets.org/ticket/10079; + (f) `AuiToolBar` idle update only when visible: http://trac.wxwidgets.org/ticket/10075; + (g) Ability of creating `AuiToolBar` tools with [counter]clockwise rotation. This allows to propose a + variant of the minimizing functionality with a rotated button which keeps the caption of the pane + as label; + (h) Allow setting the alignment of all tools in a toolbar that is expanded. + + +TODOs +===== + +- Documentation, documentation and documentation; +- Fix `tabmdi.AuiMDIParentFrame` and friends, they do not work correctly at present; +- Allow specification of `CaptionLeft()` to `AuiPaneInfo` to show the caption bar of docked panes + on the left instead of on the top (with caption text rotated by 90 degrees then). This is + similar to what `wxDockIt` did - DONE; +- Make developer-created `AuiNotebooks` and automatic (framemanager-created) `AuiNotebooks` behave + the same way (undocking of tabs) - DONE, to some extent; +- Find a way to dock panes in already floating panes (`AuiFloatingFrames`), as they already have + their own `AuiManager`; +- Add more gripper styles (see, i.e., PlusDock 4.0); +- Add an "AutoHide" feature to docked panes, similar to fly-out floating panes (see, i.e., PlusDock 4.0); +- Add events for panes when they are about to float or to be docked (something like + ``EVT_AUI_PANE_FLOATING/ED`` and ``EVT_AUI_PANE_DOCKING/ED``) - DONE, to some extent; +- Implement the 4-ways splitter behaviour for horizontal and vertical sashes if they intersect; +- Extend `tabart.py` with more aui tab arts; +- Implement ``AUI_NB_LEFT`` and ``AUI_NB_RIGHT`` tab locations in `AuiNotebook`; +- Move `AuiDefaultToolBarArt` into a separate module (as with `tabart.py` and `dockart.py`) and + provide more arts for toolbars (maybe from `wx.lib.agw.flatmenu`?) +- Support multiple-rows/multiple columns toolbars; +- Integrate as much as possible with `wx.lib.agw.flatmenu`, from dropdown menus in `AuiNotebook` to + toolbars and menu positioning; +- Possibly handle minimization of panes in a different way (or provide an option to switch to + another way of minimizing panes); +- Clean up/speed up the code, especially time-consuming for-loops; +- Possibly integrate `wxPyRibbon` (still on development), at least on Windows. + + +License And Version +=================== + +AUI library is distributed under the wxPython license. + +Latest revision: Andrea Gavana @ 21 Jun 2011, 22.00 GMT + +Version 1.3. + +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +from aui_constants import * +from aui_utilities import * +from auibar import * +from auibook import * +from tabart import * +from dockart import * +from framemanager import * +from tabmdi import * diff --git a/aui/aui_constants.py b/aui/aui_constants.py new file mode 100644 index 0000000..38f183d --- /dev/null +++ b/aui/aui_constants.py @@ -0,0 +1,2588 @@ +""" +This module contains all the constants used by wxPython-AUI. + +Especially important and meaningful are constants for AuiManager, AuiDockArt and +AuiNotebook. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +from wx.lib.embeddedimage import PyEmbeddedImage + +# ------------------------- # +# - AuiNotebook Constants - # +# ------------------------- # + +# For tabart +# -------------- + +vertical_border_padding = 4 +""" Border padding used in drawing tabs. """ + +if wx.Platform == "__WXMAC__": + nb_close_bits = "\xFF\xFF\xFF\xFF\x0F\xFE\x03\xF8\x01\xF0\x19\xF3" \ + "\xB8\xE3\xF0\xE1\xE0\xE0\xF0\xE1\xB8\xE3\x19\xF3" \ + "\x01\xF0\x03\xF8\x0F\xFE\xFF\xFF" + """ AuiNotebook close button image on wxMAC. """ + +elif wx.Platform == "__WXGTK__": + nb_close_bits = "\xff\xff\xff\xff\x07\xf0\xfb\xef\xdb\xed\x8b\xe8" \ + "\x1b\xec\x3b\xee\x1b\xec\x8b\xe8\xdb\xed\xfb\xef" \ + "\x07\xf0\xff\xff\xff\xff\xff\xff" + """ AuiNotebook close button image on wxGTK. """ + +else: + nb_close_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xe7\xf3\xcf\xf9" \ + "\x9f\xfc\x3f\xfe\x3f\xfe\x9f\xfc\xcf\xf9\xe7\xf3" \ + "\xff\xff\xff\xff\xff\xff\xff\xff" + """ AuiNotebook close button image on wxMSW. """ + +nb_left_bits = "\xff\xff\xff\xff\xff\xff\xff\xfe\x7f\xfe\x3f\xfe\x1f" \ + "\xfe\x0f\xfe\x1f\xfe\x3f\xfe\x7f\xfe\xff\xfe\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook left button image. """ + +nb_right_bits = "\xff\xff\xff\xff\xff\xff\xdf\xff\x9f\xff\x1f\xff\x1f" \ + "\xfe\x1f\xfc\x1f\xfe\x1f\xff\x9f\xff\xdf\xff\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook right button image. """ + +nb_list_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x0f" \ + "\xf8\xff\xff\x0f\xf8\x1f\xfc\x3f\xfe\x7f\xff\xff\xff" \ + "\xff\xff\xff\xff\xff\xff" +""" AuiNotebook windows list button image. """ + + +#---------------------------------------------------------------------- +tab_active_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAbCAYAAAC9WOV0AAAABHNCSVQICAgIfAhkiAAAADNJ" + "REFUCJltzMEJwDAUw9DHX6OLdP/Bop4KDc3F2EIYrsFtrZow8GnH6OD1zvRTajvY2QMHIhNx" + "jUhuAgAAAABJRU5ErkJggg==") +""" Center active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_active_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAglJ" + "REFUOI2Nkk9rE0EYh5/J7mpW06xE2iSmeFHxEoqIAc/FQ5CKgn4DP4KlIQG/QVsQbBEKgop+" + "Anvy4rV4bLT2JCGJPVXqwaZJd+f1kN26WTfJDrzszDLPPL/5o0jeFGAC54A0YKmEYAo4DzjA" + "LHAZmElqtIGrhmEsvtzcfPNtb6/V6524SWALKBiGsfhxe/uzFhGth5XEmgVubWxsvA1Az68k" + "1nngYbPZ7ASg69c06wxwe3V9/b3reVqHwGmwCZRs2370fX//wIuA0+CLwEKj0XilZTSu602G" + "FcP7vLe7+7XlRaCgPw62gGv5fP6p63raiwFdLWKOgdNArl6vV1UqpQgcYdcYbwooAPfb7c7h" + "mTWmUjGwCWTL5fL1K6VSLiqQyMTYyLVa/UEwe9IC0chFYKnb/XnkeiIDV+Q0UsG/qNkCnEql" + "crNQLDpaxpskJnYayD1bXl4S/xrDoPLHKjQOmsHwlCuHv44+ZJ2sLTrGGqzg7zEc+VK1Wl1w" + "HMcG0DFxw6sFsRVwAZhdWak9FoRJ+w2HCKzzwN3jXv+daVmGDkdWoMKb9fumHz0DFFfX1p5Y" + "lmXo6N0G48jzVEDOt97pdA9ezOXzGU+PzBmN6VuDqyoDN3Z2vjyfKxQynhYkJuJ/L02Ara3X" + "n3602r8HrpaTUy3HAy1/+hNq8O+r+q4WETirmFMNBwm3v+gdmytKNIUpAAAAAElFTkSuQmCC") +""" Left active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_active_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAkpJ" + "REFUOI2NlM1rU0EUxX9zZ5KaWq3GKKnGutC0FEWCWAWLRUOxBetK/wdp6Re6F6TFXXGhuFdw" + "b7dCQUUpiFt1XbB2q7Uf1iTvunjzkpe0afNgmLnDnHvOPe/OWCALtAFC+Cktfha4CRwBDnhg" + "BQhaSrK19bf89dv35WfPX7y01haBbiAFmH3BlUA1Gm8WFt75BFkg0TK4VAl0Y3NL5+efvgIK" + "wOH92EVjxRljGBi4VgTOeLDbk7kcqEZju1TWX7/Xgtm5J6+BS8ChvdilLhAhkUya4eFbxVQq" + "1e3ZbUtgg8GKJd/Tk70/NjYCHCPsgX1kV8K5VA70z8amfvy0tAwMAcebSRfijikY8ez5/OlM" + "JrOncbIjp4K1lmRb0sw8eDgCpAm7rwlz46YIzjpGb48WveyDNPhDfCOuHmNwzpHL5dK9fX3n" + "mkmvaxJiayOCWMvM1PSdZtJrhiloLJMYIeESDFwf7Acyu0mXGLYmX0PpYi3ZbFdnoVDoBTpp" + "uCxCjFob1tYKzlnGJyZHd5Mu6uVGkqvMCmCwzjE4eOMqcALoINauUic37hjhLXPWcTSdThWL" + "QxcJX5yqdGk4H/cP9a4755iYnLpL+M/b8e0qjafrekb9TUskuNx/5TzQ5Y1zO9yOZEd1R7OI" + "JdXebh/Pzt3zCToAMZv/AjU1orDWWKAGVJVSqcTqysp6X+/ZaeAL8KNac9wsVQ8yNeOsdZw8" + "let4/2HpEdAPXDAb20HLj7xqeHT158ra4uLbz2bdg03krmetxrH9KDAmHP8Bn0j1t/01UV0A" + "AAAASUVORK5CYII=") +""" Right active tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAI9J" + "REFUKJG90MEKAWEUxfEfM4rxAFIommzZzNb7v4BsLJTsiGQlYjHfME3flrO75/xvnXv5p/qY" + "R/wcWTUktWCKFbrYB6/AAhecmwunAI/RwQAjbLGpoFakwjLATxzqMLQjC68A3/FohkljLkKN" + "Ha4YKg8+VkBag3Pll9a1GikmuPk+4qMMs0jFMXoR/0d6A9JRFV/jxY+iAAAAAElFTkSuQmCC") +""" Normal close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close_h = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAOlJ" + "REFUKJGVkiFuw0AQRd849hUS7iPUwGEllhyjYJ+gaK9Q4CsY9QTFIY4shQQucI8Q7l6h3Z0S" + "r7UgjdrPZvVm52k0wpJLWe4y51qgVpECQFQnYPzabN4ra2cAAbgWxZMmyavAkTtROIn33fM0" + "fcilLHep92+/wXHTd5K8JJlzbYD3w8C2aVZo2zTsh4FF5Zg516ZAHYBb35MbszbkxnDr+3hQ" + "napIIUv1eT6vYPggvAGoSJE88r6XVFQnRA7BOdYIk8IUUZ1SYAQOsXOskRsT1+P/11pZO4v3" + "ncLpESzed5W1c1jQn0/jBzPfck1qdmfjAAAAAElFTkSuQmCC") +""" Hover close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_close_p = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAASxJ" + "REFUKJF9kbFLQlEYxX/nvbs55OAkiJAE7k7Nibo9xf+hrTlyr3Boipb+BCGq0bApJEQcG0Ms" + "aQ0Lmq5+Dc+nDtbZ7uHce37fd8VSlWwh50PfRKqClWJXI8y6bu5uHj5e3wEEcJDP75txLBSx" + "RYbdS7QfJ5PnsJIt5BbB4hQjkrQtjxlFILOXyvQDH/qmUCSJznDAYetkFTxsndAZDggkhCIf" + "+qaLmWP1bu8oN+qrC+VGnd7t3bpKqrp4wBjl+ux8FUweSLwlXCnYCv2PHGgE1BLmTYykad2i" + "kcOsi1TbZN7EKDfq67NZV5VsIeedvzQjCv5YK8R/4bw7Cl+/P7920+kJkBEq/hWWaPem45cQ" + "YDybTfdSmf5CizckwHaAH9ATZldu7i560/ELwC+6RXdU6KzezAAAAABJRU5ErkJggg==") +""" Pressed close button image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAbCAYAAAC9WOV0AAAABHNCSVQICAgIfAhkiAAAAElJ" + "REFUCJlVyiEOgDAUBNHp3qmX5iYkyMpqBAaFILRdDGn4qybZB98yy3ZZrRu1PpABAQiDSLN+" + "h4NLEU8CBAfoPHZUywr3M/wCTz8c3/qQrUcAAAAASUVORK5CYII=") +""" Center inactive tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAf5J" + "REFUOI2llE1rE1EUhp8bZwyhaZomk5DaD40hSWPQVkTd6KIIEUWlLqTEhTaLulBQ6sfKjeBC" + "ECULXQku/Alx7d6/U1EQae45LjJpJ5NOnOKBgYG5z33Px3sG/iPMIc87QAmYBZKHgdOu69a2" + "3/W2yrVGK5vPLTlxFV3Xrb3+8v1Ntd5oiSpWBmnEidKT972tar3R6ovSt4qoxoIdoFipNlpW" + "B6AVRYFEHNWn3a8dz/PK1rIHEgN2UpnMseVTK7fUGBME48CFe88+3sh5+SXr1xmMSbABvJXz" + "l9siYAVGWJ0Mu/OVZr5Q8CpWfFWzD2Imj2qu/fhtG4wRVUIZg0bDBsgtn15dt6qIKKBDQZ81" + "kWmnzly6OZ+ZzhSt7jfK6CBjFMwEk5TWOy82AVQGhzVUb5RJEkC2fLK6JgIiPhioeZJJUhev" + "3j2RTqdzooqge2ojCxwxqrnrG4/uq4Ida3HgAjMOJ4CZSq1+RVBUzCgQinDDstfa282jyeTU" + "rhUGF4CJgMPKhbXbmw9VFfG7fBA4LCao7AAzi8cXz1kF0dENMqH38KgWnnd7nSMJxxE5wI4+" + "MHyCaeeAYvPshQ0RJby3wVSDHxxgAVh99elb9/evndmfP3boW2FsqGNhMMCdBy8/fJ5KZ6at" + "qL+3Q1dEzFkNGMX82ZWh18e0/vVT/wuFmdYVv/ruKgAAAABJRU5ErkJggg==") +""" Left inactive tab image for the Chrome tab art. """ + +#---------------------------------------------------------------------- +tab_inactive_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAbCAYAAACjkdXHAAAABHNCSVQICAgIfAhkiAAAAhBJ" + "REFUOI2llM9rE1EQxz8zb1dSTKNuYtW01kQDRoKFWi9FEEq1IooUUWoPokWCtVqkR69KsSBU" + "8OJRPOhBxZNe/At6FBER/HFUPEq1IGn3ecgm2ZjdJODCHPY9vvP9fufNDPzHZ4DDQBrYBKwB" + "ftfoJys/Kw9ef/1y8/6rh67rHgKS3WLl6cqqtcCGD58+vn+zdPXorUql8g5Y7wTWdd+y4Vus" + "teQK+yfKi8/KwM5umBXAAgioCIP54gTQBzgdwTbsQZR0JpOfXXw+0w27hn9EBGMcyRcPnulJ" + "pbKd2JvACKgKnpcePH99+TSwvT3YEphusKsqB4ZHp4FMNWUn5loSEVSFbZ63b8eeUhpwu5Md" + "JBFRjHHk7LXb08CuNuAaZTgEEaFQHJoEvDjpakOYmnURUFWSvam+0ujJfqAnmlnABhG2jlTZ" + "j19YuEzMm7dUu34hihrDQG7vGLCViPq0VruuvdquyWSvN3xsKhclvbXaoUQiihFlfLJ8iYiq" + "O/EtUC2xGGF3vjAObAnI6stCsZbYCLwnEonNY+dulALvHWSH2YN2PXLq4hz/9HpjnmOs18DZ" + "bP9IIL0+afV5juqzRgLFcV1n9u6LGWAgWnaMBFHBOIbi0MgU1S3jAcjyyw9xqpvzWou1Pj++" + "f/t8b/7EAvBW5u48agU37abWs99rv1YfL81fkT8V34YxbZ696d4CfwEszZSZx6Z26wAAAABJ" + "RU5ErkJggg==") +""" Right inactive tab image for the Chrome tab art. """ + +# For auibook +# ----------- + +AuiBaseTabCtrlId = 5380 +""" Base window identifier for AuiTabCtrl. """ + +AUI_NB_TOP = 1 << 0 +""" With this style, tabs are drawn along the top of the notebook. """ +AUI_NB_LEFT = 1 << 1 # not implemented yet +""" With this style, tabs are drawn along the left of the notebook. +Not implemented yet. """ +AUI_NB_RIGHT = 1 << 2 # not implemented yet +""" With this style, tabs are drawn along the right of the notebook. +Not implemented yet. """ +AUI_NB_BOTTOM = 1 << 3 +""" With this style, tabs are drawn along the bottom of the notebook. """ +AUI_NB_TAB_SPLIT = 1 << 4 +""" Allows the tab control to be split by dragging a tab. """ +AUI_NB_TAB_MOVE = 1 << 5 +""" Allows a tab to be moved horizontally by dragging. """ +AUI_NB_TAB_EXTERNAL_MOVE = 1 << 6 +""" Allows a tab to be moved to another tab control. """ +AUI_NB_TAB_FIXED_WIDTH = 1 << 7 +""" With this style, all tabs have the same width. """ +AUI_NB_SCROLL_BUTTONS = 1 << 8 +""" With this style, left and right scroll buttons are displayed. """ +AUI_NB_WINDOWLIST_BUTTON = 1 << 9 +""" With this style, a drop-down list of windows is available. """ +AUI_NB_CLOSE_BUTTON = 1 << 10 +""" With this style, a close button is available on the tab bar. """ +AUI_NB_CLOSE_ON_ACTIVE_TAB = 1 << 11 +""" With this style, a close button is available on the active tab. """ +AUI_NB_CLOSE_ON_ALL_TABS = 1 << 12 +""" With this style, a close button is available on all tabs. """ +AUI_NB_MIDDLE_CLICK_CLOSE = 1 << 13 +""" Allows to close `AuiNotebook` tabs by mouse middle button click. """ +AUI_NB_SUB_NOTEBOOK = 1 << 14 +""" This style is used by `AuiManager` to create automatic `AuiNotebooks`. """ +AUI_NB_HIDE_ON_SINGLE_TAB = 1 << 15 +""" Hides the tab window if only one tab is present. """ +AUI_NB_SMART_TABS = 1 << 16 +""" Use `Smart Tabbing`, like ``Alt`` + ``Tab`` on Windows. """ +AUI_NB_USE_IMAGES_DROPDOWN = 1 << 17 +""" Uses images on dropdown window list menu instead of check items. """ +AUI_NB_CLOSE_ON_TAB_LEFT = 1 << 18 +""" Draws the tab close button on the left instead of on the right +(a la Camino browser). """ +AUI_NB_TAB_FLOAT = 1 << 19 +""" Allows the floating of single tabs. +Known limitation: when the notebook is more or less full screen, tabs +cannot be dragged far enough outside of the notebook to become +floating pages. """ +AUI_NB_DRAW_DND_TAB = 1 << 20 +""" Draws an image representation of a tab while dragging. """ +AUI_NB_ORDER_BY_ACCESS = 1 << 21 +""" Tab navigation order by last access time. """ +AUI_NB_NO_TAB_FOCUS = 1 << 22 +""" Don't draw tab focus rectangle. """ + +AUI_NB_DEFAULT_STYLE = AUI_NB_TOP | AUI_NB_TAB_SPLIT | AUI_NB_TAB_MOVE | \ + AUI_NB_SCROLL_BUTTONS | AUI_NB_CLOSE_ON_ACTIVE_TAB | \ + AUI_NB_MIDDLE_CLICK_CLOSE | AUI_NB_DRAW_DND_TAB +""" Default `AuiNotebook` style. """ + +#---------------------------------------------------------------------- +Mondrian = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAHFJ" + "REFUWIXt1jsKgDAQRdF7xY25cpcWC60kioI6Fm/ahHBCMh+BRmGMnAgEWnvPpzK8dvrFCCCA" + "coD8og4c5Lr6WB3Q3l1TBwLYPuF3YS1gn1HphgEEEABcKERrGy0E3B0HFJg7C1N/f/kTBBBA" + "+Vi+AMkgFEvBPD17AAAAAElFTkSuQmCC") +""" Default icon for the Smart Tabbing dialog. """ + +# -------------------------- # +# - FrameManager Constants - # +# -------------------------- # + +# Docking Styles +AUI_DOCK_NONE = 0 +""" No docking direction. """ +AUI_DOCK_TOP = 1 +""" Top docking direction. """ +AUI_DOCK_RIGHT = 2 +""" Right docking direction. """ +AUI_DOCK_BOTTOM = 3 +""" Bottom docking direction. """ +AUI_DOCK_LEFT = 4 +""" Left docking direction. """ +AUI_DOCK_CENTER = 5 +""" Center docking direction. """ +AUI_DOCK_CENTRE = AUI_DOCK_CENTER +""" Centre docking direction. """ +AUI_DOCK_NOTEBOOK_PAGE = 6 +""" Automatic AuiNotebooks docking style. """ + +# Floating/Dragging Styles +AUI_MGR_ALLOW_FLOATING = 1 << 0 +""" Allow floating of panes. """ +AUI_MGR_ALLOW_ACTIVE_PANE = 1 << 1 +""" If a pane becomes active, "highlight" it in the interface. """ +AUI_MGR_TRANSPARENT_DRAG = 1 << 2 +""" If the platform supports it, set transparency on a floating pane +while it is dragged by the user. """ +AUI_MGR_TRANSPARENT_HINT = 1 << 3 +""" If the platform supports it, show a transparent hint window when +the user is about to dock a floating pane. """ +AUI_MGR_VENETIAN_BLINDS_HINT = 1 << 4 +""" Show a "venetian blind" effect when the user is about to dock a +floating pane. """ +AUI_MGR_RECTANGLE_HINT = 1 << 5 +""" Show a rectangle hint effect when the user is about to dock a +floating pane. """ +AUI_MGR_HINT_FADE = 1 << 6 +""" If the platform supports it, the hint window will fade in and out. """ +AUI_MGR_NO_VENETIAN_BLINDS_FADE = 1 << 7 +""" Disables the "venetian blind" fade in and out. """ +AUI_MGR_LIVE_RESIZE = 1 << 8 +""" Live resize when the user drag a sash. """ +AUI_MGR_ANIMATE_FRAMES = 1 << 9 +""" Fade-out floating panes when they are closed (all platforms which support +frames transparency) and show a moving rectangle when they are docked +(Windows < Vista and GTK only). """ +AUI_MGR_AERO_DOCKING_GUIDES = 1 << 10 +""" Use the new Aero-style bitmaps as docking guides. """ +AUI_MGR_PREVIEW_MINIMIZED_PANES = 1 << 11 +""" Slide in and out minimized panes to preview them. """ +AUI_MGR_WHIDBEY_DOCKING_GUIDES = 1 << 12 +""" Use the new Whidbey-style bitmaps as docking guides. """ +AUI_MGR_SMOOTH_DOCKING = 1 << 13 +""" Performs a "smooth" docking of panes (a la PyQT). """ +AUI_MGR_USE_NATIVE_MINIFRAMES = 1 << 14 +""" Use miniframes with native caption bar as floating panes instead or custom +drawn caption bars (forced on wxMac). """ +AUI_MGR_AUTONB_NO_CAPTION = 1 << 15 +""" Panes that merge into an automatic notebook will not have the pane +caption visible. """ + + +AUI_MGR_DEFAULT = AUI_MGR_ALLOW_FLOATING | AUI_MGR_TRANSPARENT_HINT | \ + AUI_MGR_HINT_FADE | AUI_MGR_NO_VENETIAN_BLINDS_FADE +""" Default `AuiManager` style. """ + +# Panes Customization +AUI_DOCKART_SASH_SIZE = 0 +""" Customizes the sash size. """ +AUI_DOCKART_CAPTION_SIZE = 1 +""" Customizes the caption size. """ +AUI_DOCKART_GRIPPER_SIZE = 2 +""" Customizes the gripper size. """ +AUI_DOCKART_PANE_BORDER_SIZE = 3 +""" Customizes the pane border size. """ +AUI_DOCKART_PANE_BUTTON_SIZE = 4 +""" Customizes the pane button size. """ +AUI_DOCKART_BACKGROUND_COLOUR = 5 +""" Customizes the background colour. """ +AUI_DOCKART_BACKGROUND_GRADIENT_COLOUR = 6 +""" Customizes the background gradient colour. """ +AUI_DOCKART_SASH_COLOUR = 7 +""" Customizes the sash colour. """ +AUI_DOCKART_ACTIVE_CAPTION_COLOUR = 8 +""" Customizes the active caption colour. """ +AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR = 9 +""" Customizes the active caption gradient colour. """ +AUI_DOCKART_INACTIVE_CAPTION_COLOUR = 10 +""" Customizes the inactive caption colour. """ +AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR = 11 +""" Customizes the inactive gradient caption colour. """ +AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR = 12 +""" Customizes the active caption text colour. """ +AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR = 13 +""" Customizes the inactive caption text colour. """ +AUI_DOCKART_BORDER_COLOUR = 14 +""" Customizes the border colour. """ +AUI_DOCKART_GRIPPER_COLOUR = 15 +""" Customizes the gripper colour. """ +AUI_DOCKART_CAPTION_FONT = 16 +""" Customizes the caption font. """ +AUI_DOCKART_GRADIENT_TYPE = 17 +""" Customizes the gradient type (no gradient, vertical or horizontal). """ +AUI_DOCKART_DRAW_SASH_GRIP = 18 +""" Draw a sash grip on the sash. """ + +# Caption Gradient Type +AUI_GRADIENT_NONE = 0 +""" No gradient on the captions. """ +AUI_GRADIENT_VERTICAL = 1 +""" Vertical gradient on the captions. """ +AUI_GRADIENT_HORIZONTAL = 2 +""" Horizontal gradient on the captions. """ + +# Pane Button State +AUI_BUTTON_STATE_NORMAL = 0 +""" Normal button state. """ +AUI_BUTTON_STATE_HOVER = 1 << 1 +""" Hovered button state. """ +AUI_BUTTON_STATE_PRESSED = 1 << 2 +""" Pressed button state. """ +AUI_BUTTON_STATE_DISABLED = 1 << 3 +""" Disabled button state. """ +AUI_BUTTON_STATE_HIDDEN = 1 << 4 +""" Hidden button state. """ +AUI_BUTTON_STATE_CHECKED = 1 << 5 +""" Checked button state. """ + +# Pane minimize mode +AUI_MINIMIZE_POS_SMART = 0x01 +""" Minimizes the pane on the closest tool bar. """ +AUI_MINIMIZE_POS_TOP = 0x02 +""" Minimizes the pane on the top tool bar. """ +AUI_MINIMIZE_POS_LEFT = 0x03 +""" Minimizes the pane on its left tool bar. """ +AUI_MINIMIZE_POS_RIGHT = 0x04 +""" Minimizes the pane on its right tool bar. """ +AUI_MINIMIZE_POS_BOTTOM = 0x05 +""" Minimizes the pane on its bottom tool bar. """ +AUI_MINIMIZE_POS_MASK = 0x07 +""" Mask to filter the position flags. """ +AUI_MINIMIZE_CAPT_HIDE = 0 +""" Hides the caption of the minimized pane. """ +AUI_MINIMIZE_CAPT_SMART = 0x08 +""" Displays the caption in the best rotation (horz or clockwise). """ +AUI_MINIMIZE_CAPT_HORZ = 0x10 +""" Displays the caption horizontally. """ +AUI_MINIMIZE_CAPT_MASK = 0x18 +""" Mask to filter the caption flags. """ + +# Button kind +AUI_BUTTON_CLOSE = 101 +""" Shows a close button on the pane. """ +AUI_BUTTON_MAXIMIZE_RESTORE = 102 +""" Shows a maximize/restore button on the pane. """ +AUI_BUTTON_MINIMIZE = 103 +""" Shows a minimize button on the pane. """ +AUI_BUTTON_PIN = 104 +""" Shows a pin button on the pane. """ +AUI_BUTTON_OPTIONS = 105 +""" Shows an option button on the pane (not implemented). """ +AUI_BUTTON_WINDOWLIST = 106 +""" Shows a window list button on the pane (for AuiNotebook). """ +AUI_BUTTON_LEFT = 107 +""" Shows a left button on the pane (for AuiNotebook). """ +AUI_BUTTON_RIGHT = 108 +""" Shows a right button on the pane (for AuiNotebook). """ +AUI_BUTTON_UP = 109 +""" Shows an up button on the pane (not implemented). """ +AUI_BUTTON_DOWN = 110 +""" Shows a down button on the pane (not implemented). """ +AUI_BUTTON_CUSTOM1 = 201 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM2 = 202 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM3 = 203 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM4 = 204 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM5 = 205 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM6 = 206 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM7 = 207 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM8 = 208 +""" Shows a custom button on the pane. """ +AUI_BUTTON_CUSTOM9 = 209 +""" Shows a custom button on the pane. """ + +# Pane Insert Level +AUI_INSERT_PANE = 0 +""" Level for inserting a pane. """ +AUI_INSERT_ROW = 1 +""" Level for inserting a row. """ +AUI_INSERT_DOCK = 2 +""" Level for inserting a dock. """ + +# Action constants +actionNone = 0 +""" No current action. """ +actionResize = 1 +""" Resize action. """ +actionClickButton = 2 +""" Click on a pane button action. """ +actionClickCaption = 3 +""" Click on a pane caption action. """ +actionDragToolbarPane = 4 +""" Drag a floating toolbar action. """ +actionDragFloatingPane = 5 +""" Drag a floating pane action. """ + +# Drop/Float constants +auiInsertRowPixels = 10 +""" Number of pixels between rows. """ +auiNewRowPixels = 40 +""" Number of pixels for a new inserted row. """ +auiLayerInsertPixels = 40 +""" Number of pixels between layers. """ +auiLayerInsertOffset = 5 +""" Number of offset pixels between layers. """ +auiToolBarLayer = 10 +""" AUI layer for a toolbar. """ + +# some built in bitmaps + +if wx.Platform == "__WXMAC__": + + close_bits = "\xFF\xFF\xFF\xFF\x0F\xFE\x03\xF8\x01\xF0\x19\xF3\xB8\xE3\xF0" \ + "\xE1\xE0\xE0\xF0\xE1\xB8\xE3\x19\xF3\x01\xF0\x03\xF8\x0F\xFE\xFF\xFF" + """ Close button bitmap for a pane on wxMAC. """ + +elif wx.Platform == "__WXGTK__": + + close_bits = "\xff\xff\xff\xff\x07\xf0\xfb\xef\xdb\xed\x8b\xe8\x1b\xec\x3b\xee" \ + "\x1b\xec\x8b\xe8\xdb\xed\xfb\xef\x07\xf0\xff\xff\xff\xff\xff\xff" + """ Close button bitmap for a pane on wxGTK. """ + +else: + + close_bits = "\xff\xff\xff\xff\xff\xff\xff\xff\xcf\xf3\x9f\xf9\x3f\xfc\x7f\xfe" \ + "\x3f\xfc\x9f\xf9\xcf\xf3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + """ Close button bitmap for a pane on wxMSW. """ + +pin_bits = '\xff\xff\xff\xff\xff\xff\x1f\xfc\xdf\xfc\xdf\xfc\xdf\xfc\xdf\xfc' \ + '\xdf\xfc\x0f\xf8\x7f\xff\x7f\xff\x7f\xff\xff\xff\xff\xff\xff\xff' +""" Pin button bitmap for a pane. """ + +max_bits = '\xff\xff\xff\xff\xff\xff\x07\xf0\xf7\xf7\x07\xf0\xf7\xf7\xf7\xf7' \ + '\xf7\xf7\xf7\xf7\xf7\xf7\x07\xf0\xff\xff\xff\xff\xff\xff\xff\xff' +""" Maximize button bitmap for a pane. """ + +restore_bits = '\xff\xff\xff\xff\xff\xff\x1f\xf0\x1f\xf0\xdf\xf7\x07\xf4\x07\xf4' \ + '\xf7\xf5\xf7\xf1\xf7\xfd\xf7\xfd\x07\xfc\xff\xff\xff\xff\xff\xff' +""" Restore/maximize button bitmap for a pane. """ + +minimize_bits = '\xff\xff\xff\xff\xff\xff\x07\xf0\xf7\xf7\x07\xf0\xff\xff\xff\xff' \ + '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' +""" Minimize button bitmap for a pane. """ + +restore_xpm = ["16 15 3 1", + " c None", + ". c #000000", + "+ c #FFFFFF", + " ", + " .......... ", + " .++++++++. ", + " .......... ", + " .++++++++. ", + " ..........+++. ", + " .++++++++.+++. ", + " ..........+++. ", + " .++++++++..... ", + " .++++++++. ", + " .++++++++. ", + " .++++++++. ", + " .++++++++. ", + " .......... ", + " "] +""" Restore/minimize button bitmap for a pane. """ + +#---------------------------------------------------------------------- + +down_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACaUlE" + "QVRIib2WvWsUQRjGn5mdnWxyTaqz9q+QlLnGToSgWAYDNjbpNCAGDGIvaRPbNJGQyiAEbK+w" + "sAo2qexyEhbxsvt+jMXc3u3liPfhmWeXnWVm9vc+vO/M7prVzTb+gxyA7Ye/nXPWWmvtXKBb" + "B9YBcM5lWZam6by4QNcBsNamaeq9d87NmWutdc59+NgGoKIizCwsxMTMFI8oZmZilzomZiFm" + "FWERaXbv7eyueO+TJEHM79LSkvfeWnv2qftgex2ASGDmkrUkKUspiIuCy5IL4qKQgnghdQVx" + "ScKsxCKiaH8lIu99NOwAEFGsG4Dv5xeiQYOKBBYVUWJlFhIVVmIlEZGQJKVIYBbWoKqqwQN5" + "nqdpuri42OMys6rGOG/X78yW0bXWNyLqcyyAEEIIYcYK3aB5Lazb4o5fsPc3ToFaloxBwMle" + "6+9Pjfd7stda6HR85+dCPC86Y6ETcQEcHz32eZ7meZrnx0ePJnlk0vwenm70r/PkTgWdjjuV" + "rnPPfvxaa+3NcL3GMaub7XdPtNFoZFn24tmX1/trAOLuM6aaFQwQYExAMPWNaUw1FW+eHj5/" + "dbfZbDYajY33F7e1L4gUA5uo3fd8AWbQH70bjGqEyxLq3LoMYhKCgakCIWZoLLdkMRE43Iy0" + "tWi9QOP8xoIFAyBUjF7dgOizb9iMhLmByxIAHbAGKYigUPX3hqog47hSvfCHfYRaDcNg3IzO" + "7GmydRaGi37zMujrut/9l58nijROQ9yd3ZXLy8urq6vZWFmW9f+Yhrje++XlZR2keDpZa4f+" + "H/pKkiR+/f9dDsDWgQW6QHcuxKg/ZbVtCjjzINkAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +down_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACY0lE" + "QVRIib2WwWrUUBSG/3tzc5s2m0JhXPsU0u1s3Lkpui4W3PgAuhAFi2/QbesTVEphwCIU3Hbh" + "wk2LG1fujJQgtMk55x4Xd2aS6VAzM479JyQhufnOz3/uzcQMBgP8BzkAeZ4756y11tqlQIui" + "cACcc1mWpWm6ZK61Nk1T771zbilcxBxiAs659x/OAAQJIswsLMTEzBR/UczMxC51TMxCzEGE" + "RaR39WB3b9N7nyTJkLu2tua9t9ZefLx69GYbgIgyc82hJqlrqYiriuuaK+Kqkop4JXUVcU3C" + "HIhFJODsCxF57xu/RBT7BuDb958SNGgQUZYgEogDs5AE4UAcSEREk6QWUWbhoCGEENQDZVmm" + "abq6ujrkMnMIIdZ5t31vsUC3+l+JaMyxAFRVVRds0C1azsS6O273hH24cwq0UjIGipP9/t+f" + "6vZ7st9fKQpf/FqJ28+iEzoTF8Dx0RNflmlZpmV5fPR4lkdmzffwdGe8XyZ3Luh83Ll0k3vx" + "4/dWf3+B/Q2OGQwGGxsbeZ5nWfbi2efXB1sA4uozZjRKDaAwRqGmvTCNGQ3F26eHz1/d7/V6" + "eZ6fn5/f1bogCmhsonU+9AWY5nr0bjCtKS6LtrltGcQQ1MCMCiEm1MmtWUwETh6mjq1qw0Jd" + "fmPD1ADQEWPYNyD6HBs2U2Vu4bIoEBpWE0EE6ej68NaoSBdXRi/8SR/a6qE29830yKFmm2c6" + "2fTbp8FYN/0evPw0U6UuTXB39zYvLy+vr68XY2VZNv5imuB679fX10MT8Xyy1k58P4yVJEn8" + "9/93OQBFURRFsRTcWH8An5lwqISXsWUAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +left_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWvW8TQRDF3+7Ors8ShSsaSpo0dEgoFcINVChSBFRUkajpIKKgiPgP0pqGJiAhITqE" + "FIk2BQUVHT2VK+y7ndmhWN/5Ixcbh8tYWvtO8vvdm5mdPXPv+RmuMgjA670/RGSttdZ2q354" + "YgkAERVF4b3vHABMCIC11nsfQiCiqwJYa4noxbNvOw/6AJIk62ySJMLMwhI5MnPMnxzMzJHJ" + "E0dmicxJhEXk+uTO0fFuCME5h1yDxbh5+zEz93q+LGOv50WUmStOVZSqkjJyWXJVcRm5LKWM" + "3PNURq6iMKfIIpJw9n08Hg8Gg36/3wL4+eu3iHpykcWTS5pElCWJpMiJWaIk4RQ5RRERda4S" + "UWbhpCmllDQA0+k0pZQFVwF3bzEAZ5N3jgje+0COnPVknbUAdm5cW5/1/eGPxcuL2saoAczC" + "DQWAV0/fr1c/HxcBFNC8QGEMMu3NuyddAfIjG9QLjKJTB3NIHV050EZuoQI6+93q4P7B6TYA" + "A2gW1xlC61K0OXi492HZ6EbAnGFqEmBmhlYc7A9HutRq/wgA5plSwDT9tORgfzgCNsmv2QfQ" + "OvEwps7BooOPpwebxFsB83wazdWdl321BjOGWWejrciZ0+wBMwef76LPnx6trXFrivIfVOsl" + "P2V7FwH4MhpuCTBLX7mjckU628naTImlrdDdLDJ59OT+XDDU8SwyTX+Y2bC7hIPVA+fty6/b" + "SmwBODreHY/H0+n0P0WLomjegJYAIYTBYNAcp5cOa20IoQXgnMuvAh0GATg8scAEmHQrneMv" + "3LAo6X/e0vAAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +left_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACTklE" + "QVRIibWWPWsUURSG3/u5u8RiKxtLmzR2gqQSttFKhKBWqQL+BQXL4D9IGxsrBUGEFCIEbC0s" + "rOzshYHt3Jl7PizuzsduJhs3Ts7C3Z2BfZ95zzn33DGnp6e4zvAAdnZ2vPfWWmvtsOpFUXgA" + "3vvxeBxCuC6AtTaEEGP03g8LQE5RTo73/sXzr7sPJwCExTorLMxExMSJEhGl/MlBRJTIB0+J" + "iBORMBMz3/xz7+h4L8bonFsCunH77lMiGo1CWabRKDArEVUkVeKq4jJRWVJVUZmoLLlMNAq+" + "TFQlJpJEzCz49n0+n0+n08lk0gP4+es3swbvEnHwTlSYlViYJZEQcWJhkkSSmJnVuYpZiZhE" + "RUREI7BYLESkTVE37t8hAM5KcM57hBCid97Z4K2zFsDurRubk74/+9G9vKhtjBrAdG4oALw6" + "eLdZ/XxcBFBA8wKFMci012+fDQXIj2xQLzCKQR20kDqGcqCNXKcCuvzd6+DB4dk2AANoFtcl" + "QutS9Dl49Pj9qtFLAS3D1CTALA2tOdifnehKq/0jAGgzpYBp+mnFwf7sBLhMfsM+gNaJhzF1" + "DroOPpwdXibeC2jzaTRXty37eg2WDLPJRl+RM6fZA6YFn++iTx+fbKxxb4ryH1TrJT9lfxcB" + "+Hwy2xJgVr5yR+WKDLaTtZkSK1thuFlk8ujJ/dkxNPAsMk1/mOWwu4KD9QPnzcsv20psATg6" + "3pvP54vF4j9Fx+Nx8wa0AogxTqfT5ji9clhrY4w9AOdcfhUYMDyAoiiKohhWt4m/9Qss43IB" + "CBMAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +right_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACWElE" + "QVRIibWWv2/TQBTHv3e+uyRbJwZWFv4AJNSRLjChSkhlYqrEzFZVDAwVC3PXsrAUISTExlKJ" + "tQMSWzcmFqaqQqT2+8VwtuMkbiBp+mzF0pPz/dzX7z373IMXp7jJCABebf8JIXjvvffrVd8/" + "9gFACGE4HMYY1w4AxgGA9z7GmFIKIdwUwHsfQth7/vXuoxEAFfWFV1ERZhYWYmJmykcOZmbi" + "EAMTsxCzirCI3BrfPzjcTCkVRYFcg27cubfDzINBLEsaDKKIMXPFWpFUlZTEZclVxSVxWUpJ" + "PIihJK5ImJVYRBSn387Pzzc2NkajUQ/g7McvEYuhIJYYCjUVMRYVUWJlFhIVVmIlERErikrE" + "mIXVVFXVEnB5eamqWXAW8Gb39uKHevbzNwARZVFirUSIlFkqEVUD8Pb71P1Lt83LZ+8BAA7O" + "AYABMAPcFfcvDXj97ikA5wxmHVVrf64LyA7Mau1so770uVjRQa1lzaKtSc2ZWAR4uHsyn2xq" + "YBnjbFp4zsRCBw6Ptz/M5GoHgLla15AfUV8F/gEwA/Bk66jPgXNwMNhkyf199F816DIaB5bx" + "yB2aO2qFLsp/+Xiy22YmczA1Cq4hLQlwsK56xwHgumLWln0pgPv8aWcmNdVF7TKujkWAL0db" + "88nagXWb0xYgVn4XWf0CymdzWQNgapJzWC7HCnPQF5M5aBhXzthqgMkcoF57Zxx6YvaDMzO3" + "148pwMHhJhFdXFwQ0XVEh8NhuwOaAqSUUkoxxvaLulp471NKPYC80ci7gXVFALB/7IExMF6j" + "bht/AXIQRaTUgkiHAAAAAElFTkSuQmCC") +""" VS2005 focused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +right_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWv2sUQRTHvzM7M3dHmlQWtjb+AYKkTaOVCEKsrAL+CxaWwcY6bWysRAQRUtgEbC0E" + "u3RWNsJCCILZfb8sZvdu925zej/yveMWHnvvM9837+2OOz09xU0qANjZ2QkheO+999vNXpZl" + "ABBCGI/HMcabAnjvY4wppRDCdgHIJcrFCSG8eP7l7sMJABX1hVdREWYWFmJiZsqfLGZm4hAD" + "E7MQs4qwiNz6c//oeC+lVBRFA+jqzr0DZh6NYlXRaBRFjJlr1pqkrqUiriqua66Iq0oq4lEM" + "FXFNwqzEIqL4+u3i4mJ3d3cymQwAzn/8ErEYCmKJoVBTEWNRESVWZiFRYSVWEhGxoqhFjFlY" + "TVVVLQFXV1eqOitRV68Pby+v6fnP3wBElEWJtRYhUmapRVQNwJvvvftXbpuXz94BABycAwAD" + "YAa4a+5fGfDq7VMAzhnMOllt+rMpIDswa3JnG81lyMWaDppc1i7a2tCCiWWAB4dni8F2Dyxj" + "nPUTL5hY6sDh0eP3c7HGAWCuyWvIJRragX8AzAA82T8ZcuAcHAw2W/JwH/3XHnQZrQPLeOQO" + "zR21Rhflv3w4O5xGZnPQGwXXklYEOFg3e8cB4LrJbLrtKwHcp48Hc6FeF02Xcb2WAT6f7C8G" + "GwfWbU5bglj7WWTNAyh/28sWAL1JzrK8HWvMwZBmc9Ayrp2x9QCzOUCz9s44DGj+hTM3t5ur" + "Bzg63iOiy8tLItok6Xg8np6AeoCUUkopxjh9o64n731KaQCQDxr5NLAtBQBlWZZlucWkXf0F" + "imtJnvbT2psAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +tab_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC10lE" + "QVRIidWWT0gUcRTH387+ZveXZgzsbmvSsmqEfzBJSYz+gUsHCYJgISPytCQKFhJdpIN0qIUO" + "ezIUaU/roQ5eEzp46ZT/DhG4haCXdSUPK+Wuzvze+02HgdFmFqMtD76Bx8yb3/v8vr/3Zn4z" + "np6ReTgCYwAwdqfEGFMURVGU/wIdfaswAGCMcc5VVf1fXIBdBgCKoqiq+nxkobn3BABIkgBA" + "hIiEJFAgorAOyxARBTKVoUAkgSiJkIhO73a/nLjGOd/nWkrPXbqLiH6/CgBEJiIaKA1BhkG6" + "QF1Hw0BdoK6TLtCvMl2gIQhRCiQiCfPLm5ubtbW1YNXXtuzadyJTZV4AkKYkMpEkkRQoEUmQ" + "JJQCpSAiMr1eg8hEJJSmlFJK0wdQLBYR0cl9laj7l6LGY5/tc2ejsrmdgeGJbG5nYHgym9uJ" + "x9KHeGuMNd7B8fSMzCfvyerq6rHHn2bmEgPDE09G+/9WaSqZmRofisfSiadnotHoozclp94K" + "oGWznNxn/e8q4LqznNwXmb4KuO6s4643lZyugOvOcj8PDyrgurOOe30r05tKZv7ALavXmszt" + "rXZZL7EjhTmuU8lpRxNSyemZuUEAmJlLOPzU+CAAuKFluO7OWpF4LO1OPsTcejOOTcRepqXR" + "tngs7Y6U4bbcqNrIF6bGh6yt0prAgm7kC6E2fSNfWF9b2d7e1jStvqGlbMSmeRsuP7zZZvp8" + "PvCoW1s/a2qq7vddD57y3b7VZfmNfGFxadUQBgqztbWps7Pdy04uLq0WSyVJnoMRgUY45NM0" + "bXZZ7OvtaA8vLOdeT85mP+4eXN35K/6W5nBjxFz5tv7+w8LWF3+oTW+IBpsavStf1+xIfTTY" + "cNbknDPGfqsD5/xCa6AuDFe791xtEJyHIhHedTGw17tnj49EeFdH8GAkEAhwzgF+7HMZY5qm" + "cc6tD6rDGGOMMUS075aN2Ho9R/R/9gsXZ7dKHM+ODQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +tab_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC1klE" + "QVRIidWVTWgTQRTHJ5vZZEyMLKSx1RbSImJbioeiCKIechLBU8CCtadAaaGIFC/iQTxowENO" + "hUohp/Tiocdce/Gk/TiIpTm0JCnmA+OaFJs2u/PerIcp27IbKsZ66Ft47P5n3m/evLc768lm" + "s+Q/GCWEBINBSqmiKIqinApU13VKCKGUMsZUVT1lrqIoqqq+frYyeP8cIUSgIIQgAgACcuAA" + "wOUlDQCAA1UpcADkAAIREPHiwa2383cYY0TWwa7AlRuPAMDvVwkhiBYAmCBMjqaJBgfDANME" + "g4NhoMHBr1KDg8kRQHBAREE+r1er1Z6enkOubbn8d0RLpV5CiLAEogUoEAUHAYAcBYLgIDgi" + "ouX1mogWAIKwhBBCWD5Cms0mADi57xKX/6Ws8dgX+97ZqFxpb3JmPlfam5x5nyvtxWPpE7yc" + "I+c7OJ5sNhsOh4PB4Kunn5aWE5Mz87MvJv4201QyszA3HY+lE88vRaPRYrHozLcDaNsoJ/fl" + "xIcOuO4oJ/dNZqwDrjvqrOebSi52wHVHud+HJx1w3VFnvb6d5ZtKZv7AbZuvXMztZbvkR+wI" + "oY7nVHLR0YRUcnFpeYoQsrSccPiFuSlCiBvahuvurFTisbQ7+ARz55txHCL2NmWOtsVjabfS" + "hjt0L1Cu1BfmpuVRKReQ0HKlHhkxypV6Ib/ZaDQ0TesfGGqr2DTv+Ph4IBDw+XzEo9Zqv0Kh" + "wOOxu10XfA8f3JS+XKmvrm2Z3ARuDQ9fGx297qXnV9e2mvv7Aj3HFQ5md8Snadru7u7Rua6q" + "6sp6aTNXzX08OL67q7f9Q4PdTP1ZKCn5Qq321R8ZMQaiXf19VuGbJ1/8IZX+aNdAnxWJRHp7" + "e7e3t4+4oVCo0Wjout5qtdx9YIwxxlqtlj3aVgmHw5qmbWxsHNWXUqppGmNM/lCd/aWUUgoA" + "9mhbhTFGKT3sm67ruq7v7Oy4cR3bb5uW079be13FAAAAAElFTkSuQmCC") +""" VS2005 unfocused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +up_focus_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTUlE" + "QVRIic2WP28TMRjGH/85c1miqEF8CnbUgSFdukVRmZGQ+gW6Vgwd+hW60Yq1gMQMQzpHGZAg" + "C6JS+QIMmUju/L5+Ge6ulzoRTcMh5TmdZfv8/vT4Pcu26h2N8R9kAZwMfltrtdZa60agx5fa" + "ArDWpmmaJElTXGBmAWitkyRxzllrG+Zqra21bz+OAQQOzETExJ48EfniKURE5MkmljwRe6LA" + "TMz8ZPbs9GzXOWeMQZHfW33/NOufvALALESUU8g95zlnnrKM8pwyT1nGmadHic085Z6Jgidm" + "Dhh/mU6nnU6n1WrFXAA/fv7iIEECsxAH5uApELHnwBQ8Bc/MLMbkzELEFCSEEII4YD6fhxAK" + "Tsx9/tQDEIgqOzRggAQQQEEBguIFgKoNqDdfvy1yYq41emG4QKkSpDQAiNQfFQClpBoZcaK2" + "s0awEHzXVVyri1gxN7FaFuILu6qwtAyokqWWwEvcxNTTKsIK95Cqs4JJzV02vMJvHS/1cFFQ" + "UGV+K3tSzWlZq/5bOWGllIio0mzpX+pZSJXdVRmOuabcItRC+ZfKcn+pFRvN65fvNihj9Y7G" + "o9FoMplcX18f9M5lUx30zofD4WQyubm56R2Nm9oYY20B98XeRfPcAro+ei1uf/DBt9u+3c7b" + "7f7gfTPc/cOr7HE36+5k3Z28u5N1u/uHV/dG3X+gfb7YW8dgpC1YD1vBjfP7oEW6Lvf0bHc6" + "nc7n881YaZre3pjucJ1znU7n9qx+qLTWzrkVXGNMcav4d1kAx5camAGzRoiF/gCKPmudbgYP" + "HQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +up_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTklE" + "QVRIic2WP4vUQBjGn/mTMUtgWS6yvb29XGGzzXXHwdWCcGBzH8BCweK+wnWyWKtot6DNge0V" + "gjYnNn4BA9vdJvO+81ok2ewmi7d3RtgnZEgm8/545skwiZrNZvgPsgCSJLHWaq211r1Asyyz" + "AKy1cRxHUdQzV2sdRZFzzlrbCxdlDmUC1to3Hy8BBA7MRMTEnjwR+fIoRUTkyUaWPBF7osBM" + "zDy+fnR2vu+cM8ZU3KV+fLo+fPUUALMQUUGh8FwUnHvKcyoKyj3lOeee7kU291R4JgqemDng" + "8ut8Ph+NRoPBoM0F8PPXbw4SJDALcWAOngIRew5MwVPwzMxiTMEsRExBQgghiAMWi0UIoclh" + "VY8fegACUVWHBgwQAQIoKEBQngBQ3wPq9bfv7XzX7o1eGS5QqgIpDQAizUMFQCmpR3bf26qc" + "NYKV4nVX7aumaavNjayWlfrSriotdQF1WKoD7nAj00yrLCvdQ+rOGiYNt2t4g9+mXprhoqCg" + "qnxre1LPqatN762asFJKRFRltvIvzSykTndTwm2uqbYItdL+5aLbX2nDRvPiyds7tC2p2WyW" + "pmmSJHEcP3/25cPFSXfQNjqeTE9fPhiPx0mSXF1d9bMxdrUD3OPJtH9uCd0evRX38Oi9Hw79" + "cFgMh4dH7/rhHpxc5PfTPN3L070i3cvT9ODk4saqmz9on6eTbQy2tAPrYSe47XxvtUi35Z6d" + "78/n88VicTdWHMfLP6Y1rnNuNBotv9W3ldbaObeBa4wp/yr+XRZAlmVZlvWCW+oP2FUt8NYb" + "g5wAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACFUlE" + "QVRIidWVPWvbUBSG3+tv8KKpnYtH/4DuJtCti2nntBm7depQWih0zT9w/AtSQsDQ0JIfkKFD" + "wRC61Iu3uBgRiKXz1eFalizb8QfxkFdCurofz3l1zhVyg8EAe1BhH9BHyC3NWkTU/XYFQEVF" + "mFlYiImZyR9ezMzEpXKJiVmIWUVYRJ7cPT/uHizhFgqF6+93Lz8fAhAxZo5ZY5I4log4ijiO" + "OSKOIomIq+VSRByTMCuxiCiufo3H4yAI8txisQjgz98bUVNTEWNRESVWZiFRYSVWEhGxYjEW" + "MWZhNVVVtQoQhuESrtfXw6e7JbTd+k1E6dv3+/3dQPeo3+8/3n22Si+OLgFLn52D4aLTun/V" + "er8XnVZ1NKqM/lX9eTNaC92IC+D87HUlDMthWA7D87NXmyzZNL+nl0ez60Nyt4Jux91Kee71" + "8Lbd6uxwzXFcr9drNpv+4f2bn59O2gDMAMC5ZJY5wOCcwZxlV7tkKr68PX338Vmj0cBev7f8" + "d0GkSG0i0576Alza7707LGqBy2JZblYOPgnm4JJA8Blay41ZnAfO3xbumWjTQOv8+oKZA2AJ" + "Y1o3wPucGXYLYVZwWQzQlJWmwIMs6Z8OJUHWcUU1aWZ9WKaGlo67xZlTbbbPbL7oq7fBTHm/" + "Jx9+bBRpnea4x92D4XA4mUx2Y9VqteVcAEEQ1Ov13bhZ5fP7IFB4v/v41f8HFQ1ap0nfm7YA" + "AAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACaUlE" + "QVRIib2WvWsUQRjGn5mdnWxyTaqz9q+QlLnGToSgWAYDNjbpNCAGDGIvaRPbNJGQyiAEbK+w" + "sAo2qexyEhbxsvt+jMXc3u3liPfhmWeXnWVm9vc+vO/M7prVzTb+gxyA7Ye/nXPWWmvtXKBb" + "B9YBcM5lWZam6by4QNcBsNamaeq9d87NmWutdc59+NgGoKIizCwsxMTMFI8oZmZilzomZiFm" + "FWERaXbv7eyueO+TJEHM79LSkvfeWnv2qftgex2ASGDmkrUkKUspiIuCy5IL4qKQgnghdQVx" + "ScKsxCKiaH8lIu99NOwAEFGsG4Dv5xeiQYOKBBYVUWJlFhIVVmIlEZGQJKVIYBbWoKqqwQN5" + "nqdpuri42OMys6rGOG/X78yW0bXWNyLqcyyAEEIIYcYK3aB5Lazb4o5fsPc3ToFaloxBwMle" + "6+9Pjfd7stda6HR85+dCPC86Y6ETcQEcHz32eZ7meZrnx0ePJnlk0vwenm70r/PkTgWdjjuV" + "rnPPfvxaa+3NcL3GMaub7XdPtNFoZFn24tmX1/trAOLuM6aaFQwQYExAMPWNaUw1FW+eHj5/" + "dbfZbDYajY33F7e1L4gUA5uo3fd8AWbQH70bjGqEyxLq3LoMYhKCgakCIWZoLLdkMRE43Iy0" + "tWi9QOP8xoIFAyBUjF7dgOizb9iMhLmByxIAHbAGKYigUPX3hqog47hSvfCHfYRaDcNg3IzO" + "7GmydRaGi37zMujrut/9l58nijROQ9yd3ZXLy8urq6vZWFmW9f+Yhrje++XlZR2keDpZa4f+" + "H/pKkiR+/f9dDsDWgQW6QHcuxKg/ZbVtCjjzINkAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window down bitmap. """ + +#---------------------------------------------------------------------- +left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACMElE" + "QVRIib2WPYsTURSG3zv3ThKJRSqblDYLwU6wFMKCViIEtbKQ/QdWgrXt/oO4hZWCIEIKUfYH" + "WFgIATuraewHM3PPh8XNZCaTSWI2oydwMx/kfeY959wzMbPZDC3FaDTavOi23Wgron8n/Z8A" + "rnry/NmXk/vXAAhLZCNhYSYiJvbkiciHTwgiIk8uduSJ2BMJMzHzjd93zi9OmwEAbt5+TETd" + "bpxlvtuNmZWIcpLcc55z5inLKM8p85RlnHnqxi7zlHsmEk/MLPj6LUmS4XDYDPjx8xezxs56" + "4thZUWFWYmEWT0LEnoVJPIlnZlZrc2YlYhIVERHtAIvFYquDu7cIgI0kttY5xHHccdbZKHaR" + "jSIAJ8Pru5M+GX+vnm4rslEDmMoFBYCXT9/uVt+MbQAFNCxQGINAe/XmSVuA8MgGxQKjaNVB" + "CSmiLQe6kqtUQJfHjQ7unV0eAjCABnFdIrQoRZODBw/frRvdCygZpiABZmmo5mAynupaq/0l" + "ACgzpYBZ9dOag8l4CuyT37EPoEXiYUyRg6qD95dn+8QbAWU+jYbqlmWv12DJMLtsNBU5cFZ7" + "wJTgzS76+OHRzho3pij8QLVYwlM2dxGAT9PxgQCz9hU6KlSktZ2sqymxthXam0UmjJ7QnxVD" + "Lc8is+oPsxx2V3BQf+G8fvH5UIkDAOcXp0mSVF94V4ter5emab/frwMADAaDcOOYSNO00+mE" + "4zrgePWaiAMwn8+PF932//MPv0Uk8OspzrYAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACVElE" + "QVRIibWWvW8TQRDF3+7Ors8ShSsaSpo0dEgoFcINVChSBFRUkajpIKKgiPgP0pqGJiAhITqE" + "FIk2BQUVHT2VK+y7ndmhWN/5Ixcbh8tYWvtO8vvdm5mdPXPv+RmuMgjA670/RGSttdZ2q354" + "YgkAERVF4b3vHABMCIC11nsfQiCiqwJYa4noxbNvOw/6AJIk62ySJMLMwhI5MnPMnxzMzJHJ" + "E0dmicxJhEXk+uTO0fFuCME5h1yDxbh5+zEz93q+LGOv50WUmStOVZSqkjJyWXJVcRm5LKWM" + "3PNURq6iMKfIIpJw9n08Hg8Gg36/3wL4+eu3iHpykcWTS5pElCWJpMiJWaIk4RQ5RRERda4S" + "UWbhpCmllDQA0+k0pZQFVwF3bzEAZ5N3jgje+0COnPVknbUAdm5cW5/1/eGPxcuL2saoAczC" + "DQWAV0/fr1c/HxcBFNC8QGEMMu3NuyddAfIjG9QLjKJTB3NIHV050EZuoQI6+93q4P7B6TYA" + "A2gW1xlC61K0OXi492HZ6EbAnGFqEmBmhlYc7A9HutRq/wgA5plSwDT9tORgfzgCNsmv2QfQ" + "OvEwps7BooOPpwebxFsB83wazdWdl321BjOGWWejrciZ0+wBMwef76LPnx6trXFrivIfVOsl" + "P2V7FwH4MhpuCTBLX7mjckU628naTImlrdDdLDJ59OT+XDDU8SwyTX+Y2bC7hIPVA+fty6/b" + "SmwBODreHY/H0+n0P0WLomjegJYAIYTBYNAcp5cOa20IoQXgnMuvAh0GATg8scAEmHQrneMv" + "3LAo6X/e0vAAAAAASUVORK5CYII=") +""" VS2005 focused docking guide window left bitmap. """ + +#---------------------------------------------------------------------- +right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACMklE" + "QVRIibWWvY7TUBCFz83vojSuKNiShgdAol8hQYWQkJaKAuUNtqWmoeANFgoqhJAQHRLaB6BA" + "orC0HWnSUEURK2LPmRmKayeO4wTysydWbI+c+e7xzDgOo9EIK0rTdDW4m0Ij4FBK07R1fdmj" + "rh3QqZ6cPf965+ENAKbWardMTZWkUoVCUuIniiSFnW6HQqqQpkpVvfnn3uu395sBAG7fPSXZ" + "73ezTPr9rqqTzGm5aJ5rJswy5jkzYZZpJux3O5kwFyVNqKqGb9/H4/Hx8XEz4PLnL1XvdtpC" + "7Xba5qbqVFM1oZEqakoTmqiqerudqzqpNDczM+8Bs9lsrYNXw1ub7+nl+DcAVaOa0HJVESM1" + "VzVzAG9+LF2/dZFfPHsPAAgIAQAcgDsQ1ly/NeDlu6cAQnC4V7L6/GtfQHTgXuSONopdk4sd" + "HRS5vFy0l6EVE5sAD4YXq8GyBh4xwZcTr5jY6CDg0eMPtVjhAPBQ5HXEW9RUgX8A3AE8OTlv" + "chACAhy+WHJzH/1XDaqM0oFHPGKHxo7aoYviTz5eDOeRxRwsjUIoSVsCAryaveIACNVkPi/7" + "VoDw+dNpLbTURfNlrNcmwJfzk9Vg4cCrzekbEDs/i7x4AMWt3B0AsDTJUR7LscMcNGkxByVj" + "7YztBljMAYq1V8ahQfU/nNrc7q/6e9FkMplOpyKyT9Kjo6MkSQaDQZqmdQdJkiRJsk92AFdX" + "V71eLx7XAQfRYDCYH68FHOr19C8Ad0k9S0aHzwAAAABJRU5ErkJggg==") +""" VS2005 unfocused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAIAAABE/PnQAAAAA3NCSVQICAjb4U/gAAACWElE" + "QVRIibWWv2/TQBTHv3e+uyRbJwZWFv4AJNSRLjChSkhlYqrEzFZVDAwVC3PXsrAUISTExlKJ" + "tQMSWzcmFqaqQqT2+8VwtuMkbiBp+mzF0pPz/dzX7z373IMXp7jJCABebf8JIXjvvffrVd8/" + "9gFACGE4HMYY1w4AxgGA9z7GmFIKIdwUwHsfQth7/vXuoxEAFfWFV1ERZhYWYmJmykcOZmbi" + "EAMTsxCzirCI3BrfPzjcTCkVRYFcg27cubfDzINBLEsaDKKIMXPFWpFUlZTEZclVxSVxWUpJ" + "PIihJK5ImJVYRBSn387Pzzc2NkajUQ/g7McvEYuhIJYYCjUVMRYVUWJlFhIVVmIlERErikrE" + "mIXVVFXVEnB5eamqWXAW8Gb39uKHevbzNwARZVFirUSIlFkqEVUD8Pb71P1Lt83LZ+8BAA7O" + "AYABMAPcFfcvDXj97ikA5wxmHVVrf64LyA7Mau1so770uVjRQa1lzaKtSc2ZWAR4uHsyn2xq" + "YBnjbFp4zsRCBw6Ptz/M5GoHgLla15AfUV8F/gEwA/Bk66jPgXNwMNhkyf199F816DIaB5bx" + "yB2aO2qFLsp/+Xiy22YmczA1Cq4hLQlwsK56xwHgumLWln0pgPv8aWcmNdVF7TKujkWAL0db" + "88nagXWb0xYgVn4XWf0CymdzWQNgapJzWC7HCnPQF5M5aBhXzthqgMkcoF57Zxx6YvaDMzO3" + "148pwMHhJhFdXFwQ0XVEh8NhuwOaAqSUUkoxxvaLulp471NKPYC80ci7gXVFALB/7IExMF6j" + "bht/AXIQRaTUgkiHAAAAAElFTkSuQmCC") +""" VS2005 focused docking guide window right bitmap. """ + +#---------------------------------------------------------------------- +tab = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAACq0lE" + "QVRIidWWTWgTQRTHJ+3ETEugERrtF6QhFhKsIlgQRITm5LkBvdiDhBZKc5DiRXryoAEPPRUi" + "pTnVi4cee5NePAitFtFoVxSyheYDa02KCd3deW/Gw2Cy7MZaIz307TK7/Gfeb9587Nvx6LpO" + "TsA6TgJ6glyqHgcHB4/ub0ZvdRFCBApCCCIAICAHDgBcXcoAADhQLwUOgBxAIAIinju89iRz" + "gzHW5Pb09BBCImO3AcDn8xJCECUAWCAsjpaFJgfTBMsCk4NposnB56UmB4sjgOCAiIJsbJXL" + "5b6+PsYYtQev5b8hSi/tJIQIKRAloEAUHAQAchQIgoPgiIiys9NClAAIQgohhJBnCKnX6wDQ" + "jFfZ0+TA/8xpIv6+8e5cN61Qm05ltEJtOvVMK9QS8ewRpWqj2js4nsb+nbv3cnU9OZ3KzD2c" + "/NdIF9IrS4sziXg2+aA/FAr5/X5nvG1AW3o5ufOTL9rgur2c3Mcrd9rgur1Oe7wL6edtcN1e" + "7v1wtw2u2+u0z2978S6kV/7CbRmv6sxdquVSH7HTR/9tE+PLUsqp2cz27k/7PTWbkcezifHl" + "tbW1XC6n6zp1dONeWaUk4tnjTwtx5F81KEcSaQxzdT1p1xPxrFtpwY3d7C6WKkuLMypVqg4U" + "tFiqBEfNYqmi57er1WogEBgOx1oqDVoz/77e2Onu6hq7emGg/6w9imKp8ubt149a/mI0rGqV" + "8u7DlyuXRuzKp8/5yzG/yr9NrmEYm1uFba2svTq0c0eu+2LR88z7Qy905PW9vZwvOGqGQ73D" + "Q1Lf9eR3vitlONQbHpLBYHBwcJAx5rGfd6rV6v7+vmEY7nVgjDHGDMNo1LZUIpGIc34ppYFA" + "gDGmfqgOo5RSSgGgUdtSUSUANLmqWp0q/mTK82hFcX4Bm24GMv+uL+EAAAAASUVORK5CYII=") +""" VS2005 unfocused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +tab_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAA3NCSVQICAjb4U/gAAAC10lE" + "QVRIidWWT0gUcRTH387+ZveXZgzsbmvSsmqEfzBJSYz+gUsHCYJgISPytCQKFhJdpIN0qIUO" + "ezIUaU/roQ5eEzp46ZT/DhG4haCXdSUPK+Wuzvze+02HgdFmFqMtD76Bx8yb3/v8vr/3Zn4z" + "np6ReTgCYwAwdqfEGFMURVGU/wIdfaswAGCMcc5VVf1fXIBdBgCKoqiq+nxkobn3BABIkgBA" + "hIiEJFAgorAOyxARBTKVoUAkgSiJkIhO73a/nLjGOd/nWkrPXbqLiH6/CgBEJiIaKA1BhkG6" + "QF1Hw0BdoK6TLtCvMl2gIQhRCiQiCfPLm5ubtbW1YNXXtuzadyJTZV4AkKYkMpEkkRQoEUmQ" + "JJQCpSAiMr1eg8hEJJSmlFJK0wdQLBYR0cl9laj7l6LGY5/tc2ejsrmdgeGJbG5nYHgym9uJ" + "x9KHeGuMNd7B8fSMzCfvyerq6rHHn2bmEgPDE09G+/9WaSqZmRofisfSiadnotHoozclp94K" + "oGWznNxn/e8q4LqznNwXmb4KuO6s4643lZyugOvOcj8PDyrgurOOe30r05tKZv7ALavXmszt" + "rXZZL7EjhTmuU8lpRxNSyemZuUEAmJlLOPzU+CAAuKFluO7OWpF4LO1OPsTcejOOTcRepqXR" + "tngs7Y6U4bbcqNrIF6bGh6yt0prAgm7kC6E2fSNfWF9b2d7e1jStvqGlbMSmeRsuP7zZZvp8" + "PvCoW1s/a2qq7vddD57y3b7VZfmNfGFxadUQBgqztbWps7Pdy04uLq0WSyVJnoMRgUY45NM0" + "bXZZ7OvtaA8vLOdeT85mP+4eXN35K/6W5nBjxFz5tv7+w8LWF3+oTW+IBpsavStf1+xIfTTY" + "cNbknDPGfqsD5/xCa6AuDFe791xtEJyHIhHedTGw17tnj49EeFdH8GAkEAhwzgF+7HMZY5qm" + "cc6tD6rDGGOMMUS075aN2Ho9R/R/9gsXZ7dKHM+ODQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window center bitmap. """ + +#---------------------------------------------------------------------- +up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACHUlE" + "QVRIidWWP4vUQBjGn8mfcyGQYiM2W8mWsRcLm22uWw6uFpQt7WwVLPwKVsp+gFMsAwqynY2F" + "oLAgNu4HsEiz3E7mfee1yN9Lcueu7oo+IcPMZN4fz7yZzEQlSYIDyAMQx/F+ocvl0tkvsdKh" + "uF6z8eLsAwDLlpmImNiQISKTX7mIiAx5vkeGiA2RZSZmvnF++9nzO0EQ9HC/vj2fPr0PgFmI" + "KCObGc4y1oa0piwjbUhr1oau+Z42lBkmsoaY2eLjpzRN+7kAvn3/wVasWGYhtszWkCViw5bJ" + "GrKGmVlcN2MWIiYr1lpr5QjYbDb9eQBw95YBIBBVdDiAC/iAAAoKEOQ3AJRtQL38/OXS/ALw" + "XKcxXKBUAVIOAIjUDxUApaQcecV7A3DkuYJG8EVX7VpdtNXm+p4jjfjcrsotdQFlslQH3OH6" + "bj2tPCx3Dyk7S5jU3K7hHr91vNTDRUFBFfkt7Uk5p6763lsxYaWUiKjCbOFf6llImd2+DLe5" + "ruM0UlA5uazS7S/Usz88vnf2G2VLKkmSap989OD9m8WsO2gbnU7mD5/cHI/H+C/3yR24p5P5" + "/rk5dHv0VtzpyWsThiYMszCcnrzaD/d4ttDXIx0NdTTMoqGOouPZ4pdR7e+iq3fzyTYGWzrY" + "etj7zwOAOI7/yjmPHRfpFVKr1apqrNfrNE2bx+pOGgwGo9Eor1/wGwRB9QPwh/oH9oed9BPW" + "YyQlBOJt4AAAAABJRU5ErkJggg==") +""" VS2005 unfocused docking guide window up bitmap. """ +#---------------------------------------------------------------------- +up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAIAAABhFeQrAAAAA3NCSVQICAjb4U/gAAACTUlE" + "QVRIic2WP28TMRjGH/85c1miqEF8CnbUgSFdukVRmZGQ+gW6Vgwd+hW60Yq1gMQMQzpHGZAg" + "C6JS+QIMmUju/L5+Ge6ulzoRTcMh5TmdZfv8/vT4Pcu26h2N8R9kAZwMfltrtdZa60agx5fa" + "ArDWpmmaJElTXGBmAWitkyRxzllrG+Zqra21bz+OAQQOzETExJ48EfniKURE5MkmljwRe6LA" + "TMz8ZPbs9GzXOWeMQZHfW33/NOufvALALESUU8g95zlnnrKM8pwyT1nGmadHic085Z6Jgidm" + "Dhh/mU6nnU6n1WrFXAA/fv7iIEECsxAH5uApELHnwBQ8Bc/MLMbkzELEFCSEEII4YD6fhxAK" + "Tsx9/tQDEIgqOzRggAQQQEEBguIFgKoNqDdfvy1yYq41emG4QKkSpDQAiNQfFQClpBoZcaK2" + "s0awEHzXVVyri1gxN7FaFuILu6qwtAyokqWWwEvcxNTTKsIK95Cqs4JJzV02vMJvHS/1cFFQ" + "UGV+K3tSzWlZq/5bOWGllIio0mzpX+pZSJXdVRmOuabcItRC+ZfKcn+pFRvN65fvNihj9Y7G" + "o9FoMplcX18f9M5lUx30zofD4WQyubm56R2Nm9oYY20B98XeRfPcAro+ei1uf/DBt9u+3c7b" + "7f7gfTPc/cOr7HE36+5k3Z28u5N1u/uHV/dG3X+gfb7YW8dgpC1YD1vBjfP7oEW6Lvf0bHc6" + "nc7n881YaZre3pjucJ1znU7n9qx+qLTWzrkVXGNMcav4d1kAx5camAGzRoiF/gCKPmudbgYP" + "HQAAAABJRU5ErkJggg==") +""" VS2005 focused docking guide window up bitmap. """ + +#---------------------------------------------------------------------- +aero_dock_pane = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAGcAAABlCAMAAABnVw3AAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAb3WKdHqPdnyRd3+deYGge4OifISifoallZaWgIy1g463hZC5hZG6iJO8o6Sk" + "pKSkpKWlpaampqenqKmpqaqqqqurq6ysrKysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1" + "tba2tre3t7i4uLm5uru7vLy8vL29vr6+iZfLjJrNjpzPjpzQkZ7PkJ/SlKHSkaHek6Pgk6Th" + "labjmKjlnqzhnqzjoa/npbPov8DAwMDAwMHBwsLCwsPDw8TExMXFxsbGycnJycrKysvLzMzM" + "zM3Nzc7Ozs/Pz9DQ0NDQ0NHR0dLS0tPT09TU1NTU1tbW1tfX0tTY19jY1tjd2NjY2dnZ2dra" + "2tvb29zc29zf3Nzc3N3d3d7e3t/fxs7szNPt0NXo0dfu1djk09js2tzk3d/k3d/m2Nzv3N/r" + "1Nr02N713OH13OL23+X43+X54ODg4eHh4eLi4+Pj4uPm4uPn4+Tk5OTk5OXl5ubm5+fn4eLo" + "4uTs5eXp5efv5+jo6Ojo6enp6urq6+vr6Onv7Ozs7O3t7e7u7u/v4Ob55Oj16Ov37e/26+/9" + "7/D28PDw8fHx8vLy8/Pz8/P09PT09fX19vb29vf38vT89ff9+Pj4+Pj5+vr6+vr7+/v8/Pz8" + "/f39/v7+////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAsPpcmgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAf+SURBVGhD7Zr7d9tEGoa9UKBZCiapneBQN3bUQlgo13DbhXBrnRQnS0LJNhDbUO7s" + "ErCtZWWZm2Rj2YHdbWQsCSQL2/yjZkaypBlpdKH14XA4vL8kkuebx3PR6HtnHBtHkfxyZoGs" + "zMtR4sfjWJRi0mJRHZGlFhej1BCNs1XyocDbpa0ooEjtSfcDOHp6apyFAMzop4XfF2f0K7Vn" + "qpyhr0bT5AwHvhpOkzPQ/TVNjt73lT6c4jzQNV/1p8npq77SBlNsj6b4Sr1Ozmtr99xpvQpm" + "6LKvquyMVezOe9Ze81uDfNbRHLUtiPaqNi8KvrqqztvlRGGbypFJRM7mMoOtnPNHDV8JisOB" + "QczyJolE4qy/MMAX6Pl23VdNGeeMBi+sE0AEzsYl92tgXqj5ipNcnNHo0oYX5OVsnfe8beab" + "VVQcelHveTij895XrJdD6SGc4b+HCIglcHTK0yAPJ4dPAYOJtUd/78b3dAdE4owYz6zzcM4Q" + "3tEop/v18ePHv+7aICJndMbdIDdn93Iwp/vh7VAf2iAy5/KuC+TmPN0J5HQ+eNDUB51Ji8ic" + "zjMhnAwpIbT7TfjXU4+ZeuqfbRNE5miZEE5qGNSe2g//s/RDLYgzSoVwiKlammMmvVTjLE0w" + "NH+UJmV37peFe3yInM3NZp0liz/azk+NM0ptt3tkdXZSxGQ1vD3EDOpiep6s9EVSeW+y5e03" + "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" + "XFekjMjBOHUAAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_dock_pane_denied = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAGcAAABlCAYAAABQif3yAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "ABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2qefiJQAAELhJREFUeF7tnfmPHNURx81v" + "hEsCIQQCxCnu60d+AH5BCCGBAAkEP/AHABKKkEhCCErCHcA4WGAIYJwYbOMrGHzgE9vYxvje" + "9drYa+99etd7zHov766PSn3avFFvu3v6dU9Pz2Q9K5Vmtt9VVd9X76zqOWtSCf4NDw9LfX39" + "pMbGxklHR0YKxuHvzj570tVXXz3ptttuO6tgjUykioeGhuTbRYukrr5eRkZG5OTJkwUj6qcd" + "2ptIOiyYLFV79khDQ0PBAPEDm/Zot2BCTZSKFy9eLGNjY6mCc+zYMaHdiaLDgsmxYOHCVIHB" + "kvij3YIJNVEqLoNTwkgWAxysp2w5Fp2iDI6FkoqVxYBz4sQJSYPM6q1sORaIoyRAOX78eCpE" + "W+VhzQIYsgAOwLC8TYvK4EQAB1DY66RBtIX1lIc1C4BQEgobHR1NhegAZXAsgDHDGgrj3CsN" + "ohMwjJYtxwIglITCjh49mgrRAc5YcGpqa2Xjxo2yZs0aWaiKR/m5aNonn0hLS4vodUEq1NTc" + "LO3t7UK7YbzBP3IgD3JZ9LXSzLJj505Zvny57Nu3T7p7eqS/v9/qzGzBggVO3h4tkwb19fU5" + "wyft2lxPGN6QC/mQszQR8OGqqqpKlv3wg7S2tloJ61UISjpy5IgcPnw4FaIDMITaguPlFzmR" + "F7lLGqRdu3bJ1q1bnTHcphf65UFJvb29cujQoVSoq6tLuHmNCw4yIC9yI39JAlRRUSH7q6tj" + "g5I9RlFw6M1tbW2pUEdHh3D7mg84hnfkRw8lBRC3iNu3b88bGGenruDQm5t1oo5KKDpqGSx0" + "cHAwEXDgHz2U1K0qEyMbx7hDmbtcXHDYSDa3tDsbyigAsVJLEhz0gD5KwnpYrcSd/IPmnKiW" + "MzZ2TOoaWuWPr051PvnfFqCkwUEm9FESq7jlK1YkYjHuOScKOAMDA9LRmZG/vj07S/zPcxuA" + "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" + "VvwtWIicAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_dock_pane_bottom = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_dock_pane_center = PyEmbeddedImage( + "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" + "AAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_dock_pane_left = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_dock_pane_right = PyEmbeddedImage( + "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" + "SBhbDwAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_dock_pane_top = PyEmbeddedImage( + "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") + +#---------------------------------------------------------------------- +aero_down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MTo0MyArMDEwMMndnrAAAAAHdElNRQfZAxkQNALaVrQp" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAhJJREFUeNrtl01v2kAQ" + "ht81FgECjUQUyeKjBrfi0EOJktxzg0uOSaWe0lNP/Wk99lBy7A+oSiO1ORE+BChSTjGfBuPs" + "rATCNSkmyHtpX2ll2czOs+OdWTMMXIPBwKnVaqjX6xiORghK0UgEuVwOhmEgFosx1u/3ncrV" + "FYrFItKpFMLhcGBwy7LQ7nRQrVZRLpXAfl5fO4l4HLquBwb9U41GA2avB6V+e4t0Oi0NTMpk" + "MiCuOuavQlXVtRNGFs+NobPWLhphiO783YZ4gut3tc3OEN9/9fDQt5+0ebEbwttCHG9eR335" + "9A0v5LhDx0LlW+tJm6OTLAr59WDHcTaDiwXk96AqNj5/+eH57fzsEIa+t4k7KMur8TMMPYkP" + "744xMO8Wg+7puZ/5HrhfMI3ZbIb9ZByX708xHt6LK93Tc78+XHu+alXrlNdT+PTxgpepBtu2" + "fc8nu1AotB18Op1C0w7EdRMxxrwJtyn8uXOW9ezIt5ErctlwT+SUrTKkKIo7cvrUTSYTKXDK" + "dEo6V+RULrLkipxeuSy4p9QILmvP5yfhAi4z4VbCZZXaMkfZws/W+g//R+Hz4046fIe3R3S6" + "0YEftChIOtuJR1zRLvHiQ4r3afRxCbLeCRzhzWK73Rb/gNRXvGP8WqmILkLTtEAbRQqu0+3i" + "980NyuUyxGabpila5GarhVGALTJF/TKbFS1yIpFgj6VqglrJraorAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1NDo1OSArMDEwMEcuCiQAAAAHdElNRQfZAxkQNxVyqGIt" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABENJREFUeNrtV02IHEUU" + "/qq6529nFnezLtGNEEk0O5NZI4GoQaLxoCJ4NgjBgAe9K/5c9SToQbx6UaJgQLyJXgIRDUkk" + "t+iS1YDgwSWHMGOyO7Mz0z/l97qqZ3vGnd0ZYb1oDY9XXT39vve+eu9VtwJHo9H4+ufrK8+f" + "XQ6xuh5jt8ZCReNM3Ue9uvjl3NzcKUXgH06fvX7C1B7H08cNDpQUSvxjXlGofWqPWo8JYCiR" + "CCchdZe6Q71OudE2uPy9gv7tEj57qXpeXbh4yXzQfAzvnTQoGwWPKL4WMM4JrERgZdwhDgh/" + "sRExCMURLhjOu1rjrW8M3tz3I/QnPwW4/4RCPrIPjRpaWad2Er2Nl+JELoox+xwguP6tDYOD" + "RYW4sz1wnjzeaW3nnh2VKQXDvYpH/FUYWShpLBPXH4fGPCNaudHC+ctNNG6HI/+35y4fTx6b" + "wZGlaYTRznb91BszQmR0aKhWrUDo+fSrX0YaO/XsImqHp9HK+GcyOq2jOM6CG0kGByhabmo7" + "ly1kHqJNg/X6PF7xAnz48Xd/A37t1adwqDqP9cAM2jIZ+0NbkVSQgAUuI6VE0lLpZ2xs9Z2e" + "waHFBbz7xjNYa/7eF7mWdbkfG/QlGpqL/SDeZEDbyPlQbKx2YuhRMocTt367F+PevTN45+0X" + "0F5bTbRcy3r/WQzZyNplJIO0x7YMPIp2ks2XpM4zJXQr9FCvHcBH77+OBw7ux83OYJmmVKcM" + "JlE7SbekD57SnaUs21iGu5s8vLoBzNy3P9HDI7vnm83GYqg0w7MJF2QiV8YhO52oLep2Y0Q5" + "DQA6VgO332IvNNnIaSSIbB/3XaR9qmN3PUF/jV3kUZrA8WbCqeFSs3tiqEXY043tUHG6O2rr" + "yEcNM1QtiV1hwLW9NNtt5KH1Ktl7bR9Mki6lHtv37K0iz+5zWr6h29KByM81DPaS9z0Fhemc" + "QpnHWdGzbTWn7enmTQCenmI9onepW1xYC4Amy1Hx9wVtnUzBO4x8nTcKSicLXpKuPCA8m7GT" + "gKeUC3iX4NKaNwjeZka3erbWgyDajLwb2PY5xasC1xl8QnNKtSRiPCG4ZHaPtrpOpDIEQ4KJ" + "ogztkuotulrmYpEbnReqo836NhOCJ22UKD2JPpLorbRDY50bAA8t9V1Kj1T3tHt9cm8xySEz" + "IXjax/sMOIxkDIDDlQV2Pl7HAd9KUtvZMe574a6M/8H/q+D63/Mii+Pfzc+jMmugwka+2w4U" + "WdjlnMY054Lrv/xQDheuedj3RIQSi7/C7lLJASV2mIJnDxb/H/T2gC2xyN5e4PM5HpF5vqnM" + "c71M0LlvFc4c9eEfXTq8Ur96pfr58nHUHjV4cEpjhqAVgvHDgocNHYDteOOCy6nGMwT8KEGL" + "rP5JadCjX/mu98dFoBpfwbGHl2z3bDab5uq1ZZxbiXGztXufyPeUNV6sajxypI7Z2Vn1F7X+" + "m7ZM/KBNAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_down_focus_single = aero_down_focus + +#---------------------------------------------------------------------- +aero_down_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MTo0MyArMDEwMMndnrAAAAAHdElNRQfZAxkQNALaVrQp" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAhJJREFUeNrtl01v2kAQ" + "ht81FgECjUQUyeKjBrfi0EOJktxzg0uOSaWe0lNP/Wk99lBy7A+oSiO1ORE+BChSTjGfBuPs" + "rATCNSkmyHtpX2ll2czOs+OdWTMMXIPBwKnVaqjX6xiORghK0UgEuVwOhmEgFosx1u/3ncrV" + "FYrFItKpFMLhcGBwy7LQ7nRQrVZRLpXAfl5fO4l4HLquBwb9U41GA2avB6V+e4t0Oi0NTMpk" + "MiCuOuavQlXVtRNGFs+NobPWLhphiO783YZ4gut3tc3OEN9/9fDQt5+0ebEbwttCHG9eR335" + "9A0v5LhDx0LlW+tJm6OTLAr59WDHcTaDiwXk96AqNj5/+eH57fzsEIa+t4k7KMur8TMMPYkP" + "744xMO8Wg+7puZ/5HrhfMI3ZbIb9ZByX708xHt6LK93Tc78+XHu+alXrlNdT+PTxgpepBtu2" + "fc8nu1AotB18Op1C0w7EdRMxxrwJtyn8uXOW9ezIt5ErctlwT+SUrTKkKIo7cvrUTSYTKXDK" + "dEo6V+RULrLkipxeuSy4p9QILmvP5yfhAi4z4VbCZZXaMkfZws/W+g//R+Hz4046fIe3R3S6" + "0YEftChIOtuJR1zRLvHiQ4r3afRxCbLeCRzhzWK73Rb/gNRXvGP8WqmILkLTtEAbRQqu0+3i" + "980NyuUyxGabpila5GarhVGALTJF/TKbFS1yIpFgj6VqglrJraorAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NDo0MCArMDEwMN+SkKkAAAAHdElNRQfZAxkQMBKjjWFJ" + "AAAACXBIWXMAAAsRAAALEQF/ZF+RAAAABGdBTUEAALGPC/xhBQAAAkJJREFUeNrtV02P0lAU" + "PS2lUEIlkxlHpCAEZ89Ol27M8A/czMK4duO/caeJiSZu3SgTN25M3DhBFkMmDhQJEK0apnwM" + "0wK1t+Z1YOqyr2w8yWuT95qcc9+97/UeAS6m06nTarWg6zrOZzPwhJJMolQqoVwuI5VKCcJk" + "MnFqh4eoVCrQcjnIssxVgGVZ6PX7qNfrqO7vQ/jSaDhqOo1isciV+Co6nQ5G4zFEvd2GpmmR" + "khPy+TyIW7xwt0SSpMgFECdxi5EzX8HGBDiOs1kBDH7ymaKoIAjCpQAiXy6XXAlFUfR4aBA5" + "EyBGET2R630LM+tvxpmQNQG8BpE3T8/w8cjEb3MREMe1BuLxOL4bY3z4NIAob/nzq1x+DYQt" + "IBaLodM18PpNA0p6Fwn5kpzqjXZmTUCYRUiR//x1hucv3yOl3lhbY4Gyd+g7QJGffG3jxau3" + "SCg7gXUWLH3nC6BfJI2wUC4V8OTxAZ4+qwXWiGexWPgp8J40EeYwTRM72xk8OrgfEMC+CRzD" + "sEUYhoFi4ToePriDuBTzBdD20zqrOWl1Mmz0ej1kMhncu3sTn5u2XwORHEOG4XAIbfcazm0F" + "aUXExdQO3oS8MXZbrz1t6ZL/gG3ba2uRtEKUYirMf2Hj/cB/AZ4A1hxsREDCdUKrVyNvULD0" + "HyBO4vackXsokXNtGR0R3t0RkSddf0iX1Hw+h3TbNYnvajXPKGSzWe7ekILsDwY4bjZRrVbh" + "JX80Gnnu+Fu3ixlnd0zR3yoUPHesqqrwB18A5ik1mQXQAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NjozNCArMDEwMCXtbZ4AAAAHdElNRQfZAxkQLw561jOY" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABAVJREFUeNrtV02IHEUY" + "fVXTO70/M+yOExNd4oIRkgljDIiyQZR4FD0rQlDw6E1RDwkoIqiHXLzlKK4Iwb2peFDJHgxx" + "dS+LcTMhhw0qJkvG7Jjsz8xOd1f5qqu6p9eNMB2Y9WLBm6+nf+p79X2vvqoSYFtdXf3ql8bl" + "52aWQlxbVxhkmyxJvFz3UK8dmq1Wqy8IOv/+xEzjSV17AsePaTw0KjDCF4cFUKQtEB6vJSz6" + "bWYYERFqoEvbpW3TXm1rfDsvIBsX8OlLte/E3PkL+vTqNN4/rlGCQEE6hzGEta5TkYOATsCf" + "SBtCGqGy9wJ2+vo5jbcqP0J+fDHA/qcEisqyHnSL6MTjz+GnBYxv+SdDso/x1rvgPBudvb6A" + "8Z0nrQNpXsJIZaxBNt/a/c8bJf0vME3pDAGlbG4ieikYLUgrPOV6MULU4i5EqK0j7XyYazMz" + "RGYkXsImhbAvpIJ0Q8/jHM55MmLl+out6hFMCUR8OyCksiMX0kYj7gV26CInCZVxbvofKQhs" + "0Jq6IPkkISKTFKQRcB9plWGtt+ujH8QRcBgfEmj+3sYe1hVTY0J3PyUQhYiLhHkQa8Hlalvo" + "7hITRYErl5r44txNrNyMUGQkEpKZFLBUKiu2AjKhduKLLXKKkB9MjQn89kcLs18vo+Dvi++b" + "gQZRTwtecjMk3UiaHAlb97VlGidfO2306dy8N+krNK5cx+kz51GamMKo77TB3Ea6Nw1lEoFA" + "WSKR00SaBpU/Dff4EteuN/HOB5/dUZyh01hKwITEpMDAEAl0EpV/6KIPDDPHlxYX8eprH94x" + "Oqb/bmT7TFMw09SodgJUWJ/HKZoSpWqmjU9BDMWrI1dJt0L20w7Wj+LMRyfxxtuf7Hi2fFvh" + "V+45vuTYp9IUBMAaZ8IGmW0SbaKjLLZULzr9ovFXhMn778V7p07sIGD62wyxvQ6ABDZC+6BD" + "bEUWXYdA5UOH33yzAhw++ADeffMZ+MVCSsD013YzIUMgIgHFkWt+TFBJW6TYpQ2IxOaB6efz" + "qwHWS+N4/tkDGC95lgCfbdJXkNUA1E7RKbcoZStknqacKBeaIer7q5jurGGC+8Em05Msfr0I" + "DLgttRRGDpQxe4Ob3s3tQ/F2g4BJ7Q90fivYuaP4z3dE/xOQye9uMjG+ksrg7RkRGKMwy0W5" + "KyR8yr7M+r5GPRrf3itHhjC3WODhJILPyVv27FowSgy7tWAo51pgmqkBoVtlhyPEffmePX+U" + "6bg8h/iM6D16pH6jvjC/9+zSMTw4rXGIZ8MJeivTMS/hmzOiMCTypSl2Trulbeldp71NIsu8" + "cfEnoHZrHo8dfdjuMVqtll74eQlnLyusbAz2gHbfmMSLNYnHH6mjUqmIvwGdqbciWIcx6wAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_left_focus_single = aero_left_focus + +#---------------------------------------------------------------------- +aero_left_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NDo0MCArMDEwMN+SkKkAAAAHdElNRQfZAxkQMBKjjWFJ" + "AAAACXBIWXMAAAsRAAALEQF/ZF+RAAAABGdBTUEAALGPC/xhBQAAAkJJREFUeNrtV02P0lAU" + "PS2lUEIlkxlHpCAEZ89Ol27M8A/czMK4duO/caeJiSZu3SgTN25M3DhBFkMmDhQJEK0apnwM" + "0wK1t+Z1YOqyr2w8yWuT95qcc9+97/UeAS6m06nTarWg6zrOZzPwhJJMolQqoVwuI5VKCcJk" + "MnFqh4eoVCrQcjnIssxVgGVZ6PX7qNfrqO7vQ/jSaDhqOo1isciV+Co6nQ5G4zFEvd2GpmmR" + "khPy+TyIW7xwt0SSpMgFECdxi5EzX8HGBDiOs1kBDH7ymaKoIAjCpQAiXy6XXAlFUfR4aBA5" + "EyBGET2R630LM+tvxpmQNQG8BpE3T8/w8cjEb3MREMe1BuLxOL4bY3z4NIAob/nzq1x+DYQt" + "IBaLodM18PpNA0p6Fwn5kpzqjXZmTUCYRUiR//x1hucv3yOl3lhbY4Gyd+g7QJGffG3jxau3" + "SCg7gXUWLH3nC6BfJI2wUC4V8OTxAZ4+qwXWiGexWPgp8J40EeYwTRM72xk8OrgfEMC+CRzD" + "sEUYhoFi4ToePriDuBTzBdD20zqrOWl1Mmz0ej1kMhncu3sTn5u2XwORHEOG4XAIbfcazm0F" + "aUXExdQO3oS8MXZbrz1t6ZL/gG3ba2uRtEKUYirMf2Hj/cB/AZ4A1hxsREDCdUKrVyNvULD0" + "HyBO4vackXsokXNtGR0R3t0RkSddf0iX1Hw+h3TbNYnvajXPKGSzWe7ekILsDwY4bjZRrVbh" + "JX80Gnnu+Fu3ixlnd0zR3yoUPHesqqrwB18A5ik1mQXQAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NToxOCArMDEwMEtfu5QAAAAHdElNRQfZAxkQLyM/CW/t" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAkpJREFUeNrtVz2PEkEY" + "fnbZQ+AgGD+R5YSAxVkRLfwF5vgH11xlbeNPsbE2Ftf4C+RiY2OijeGu8GIusEjgiJ4Ej689" + "YHedd8zgsBgt3Fkan+TNZnY3eZ55v2ZeDQzj8dir1+uwLAsT24ZKxGMxFAoFFItFJBIJTRuN" + "Rl714ADlchlmNotoNKpUwHQ6RbvTQa1WQ2VnB9rh0ZGXSiaRz+eVEvvRbDYxGA6hW40GTNMM" + "lZyQy+VA3PoFc4lhGKELIE7i1kNn9mFtAjzPW68AgUXwhaKwoGnaLwFE7rru4gOZWKsiFwJ0" + "/+7tqQ6rw7JTVxcd4hOchvyCVPXOHbz9cA57soHtUhqO4yjxgMBSDogPo4mDN+/OcCVt4Ob1" + "JGazmTJvLEIgu4Uwmzt48fI9mq2vPBzyP/9qlF9LZeh/KeP5/mucffuOSCTC/wnCBOcfPSDj" + "6bN9fDppBOYJucJ4DtARSfbzKI6vCHjyeA+X05sYstMrCJA3RZVxAZTpwvx4tPcQ166m0ev1" + "Ak2+lTIkctk1G0YEu7sPeBV0u91AyQmCyxALEiBUbcYjuH/vFnsC7XY7cHK5yS01Iqr3ZELH" + "3VIc5g0X/X4/cHKx4d+ehiTgYvwFd0w3sIT7G1auQiRCZefzY+33gf8CuAD5eAxdwCXWfqkH" + "qLyAyKDNUismTuLmkxErSmTZWEbZr/puSOQxNh9Sg5vP5zBKbEh8Va3yQSGTySifDWmTndNT" + "fDw+RqVSAQ/+YDDg0/HnVgu24umYdn97a4tPx6lUSvsBjEDOU65zEi4AAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0ODo0NiArMDEwMKZ+RR0AAAAHdElNRQfZAxkQMQU5RdXP" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAA/ZJREFUeNrtV09oVEcY" + "/83sy27UfZhtNLWxKNaDK6sNlJZIabHgpdB7KYiCR28W7EEPIkLbgz1481hIaSt4a6UUWkyh" + "Imm9hKbpSg7pIUVjotnGJG5235uZft+bmc1bk8A+NTn1W743b98bvt9vvn9vRoBkbm7uxp/V" + "ux8Mjce4t6ixkdJflDhZCVApH7je29v7oSDwX48PVd8x5bdx9IjB/q0CW2hitwDyNOZIA7qX" + "sNqp8DIUaWyAJo1NGus0/l03+GlEQFZv46sT5Z/F8K3b5vLcID49alCEQE46wESFHZ1RkYGA" + "8UoXZZiQQazts4iMfnzT4JPSb5BfjkV49V2BvLasN1oUgQR0OfieAGPLh+SSl8nfZhPA097p" + "KwgwdpawbogEnpFOjazpeBv3P6uXzDrKok2KgNY2NopQcpwL0iaedlY4EY14hiQ0Fsg4DL7n" + "yhCplQSeTUuFndBKSLf0LOBw4H7F2tlLRr1CsEVA0eyIVNLLAi19K9VhnWvHOOc7UllI6BQ4" + "21euHLkvSHrjiUgfAp7I9d9H/n44VUdPl1hhbdrzoxM1ZrV6W7FfmyegYiRNIp8TmH6k8N3N" + "R5j4axalvGwj8TyqUupJrhDgdqktCZb5xRjXf5jEvw9q2EutudXNMgKyPT96jVgV2kNgX1Jc" + "Ur2w0VS4+MWPqE5Mob+giAS9z6j84/jrVC7ERrcW0+aByJXi03Lhs69x7/4sXqLszOIBg7Xd" + "7xPRpD3ALklCsE6nOX3mc1RHR9FNOaLWMLiWxs5e7O4jF+amsuoXm5Th0KxB73JEEwMMyNXF" + "dvXKOQQ7dmFiXnVchp5IRC5oENgy/VmiZJ+n7zKtA9/T2ve0+kAELNDLZY1VH/1L54+j/5Wd" + "+GVGZWrF3tVRAm71CWEsKQuq0x4AEWB2DdV6gkI+h7Nn38ee3SXcmIptvDJ0Ik8gcbu2thMi" + "ZIr3HJFqI6CIgKaJtvNtLwY4duw1LBS349vJJgJC9puULASiBNwkMW/QAw4Dd1jpynGFgKvR" + "Wfo+9+yUGBwIgd0hRmbipDXL9LehQ1mvCTEOL0alq8DLP080vpmOkd8XYqzWecI9j6zakEzX" + "NW49iFGPs6TcCySw2fI/Aemvm8mEsXLuPtixRWAblURI3/7NIFGgwg+7JBYoxxk7OHW4C8Oj" + "OTqcKBSoaEMq0iIpb8u6iSbNRRedkHLP0Ih8K+6mimZbhcCeP0ICDoeRnBGDNw5XZip3Rvqu" + "jR/BvkGDA7QB6SG0kPeGBFjgM6JgEtnClIDT2OAzIZFYpPExEZmkB2O/A+X5Ebw5cMjuM2u1" + "mrnzxziu3dWYXtrYA9qubRIflSXeer2CUqkk/gNN/sDRnOMoBAAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_right_focus_single = aero_right_focus + +#---------------------------------------------------------------------- +aero_right_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAYAAACGVs+MAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo0NToxOCArMDEwMEtfu5QAAAAHdElNRQfZAxkQLyM/CW/t" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAkpJREFUeNrtVz2PEkEY" + "fnbZQ+AgGD+R5YSAxVkRLfwF5vgH11xlbeNPsbE2Ftf4C+RiY2OijeGu8GIusEjgiJ4Ej689" + "YHedd8zgsBgt3Fkan+TNZnY3eZ55v2ZeDQzj8dir1+uwLAsT24ZKxGMxFAoFFItFJBIJTRuN" + "Rl714ADlchlmNotoNKpUwHQ6RbvTQa1WQ2VnB9rh0ZGXSiaRz+eVEvvRbDYxGA6hW40GTNMM" + "lZyQy+VA3PoFc4lhGKELIE7i1kNn9mFtAjzPW68AgUXwhaKwoGnaLwFE7rru4gOZWKsiFwJ0" + "/+7tqQ6rw7JTVxcd4hOchvyCVPXOHbz9cA57soHtUhqO4yjxgMBSDogPo4mDN+/OcCVt4Ob1" + "JGazmTJvLEIgu4Uwmzt48fI9mq2vPBzyP/9qlF9LZeh/KeP5/mucffuOSCTC/wnCBOcfPSDj" + "6bN9fDppBOYJucJ4DtARSfbzKI6vCHjyeA+X05sYstMrCJA3RZVxAZTpwvx4tPcQ166m0ev1" + "Ak2+lTIkctk1G0YEu7sPeBV0u91AyQmCyxALEiBUbcYjuH/vFnsC7XY7cHK5yS01Iqr3ZELH" + "3VIc5g0X/X4/cHKx4d+ehiTgYvwFd0w3sIT7G1auQiRCZefzY+33gf8CuAD5eAxdwCXWfqkH" + "qLyAyKDNUismTuLmkxErSmTZWEbZr/puSOQxNh9Sg5vP5zBKbEh8Va3yQSGTySifDWmTndNT" + "fDw+RqVSAQ/+YDDg0/HnVgu24umYdn97a4tPx6lUSvsBjEDOU65zEi4AAAAASUVORK5CYII=") + +#---------------------------------------------------------------------- +aero_tab = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAACxiD++AAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFq6ysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1tba2tre3t7i4uLm5uru7vL29" + "v8DAwMDAwsPDxMXFxsbGycrKzc3Nzc7Ozs/P09TU19fX2dnZ2tra29vb3Nzc3N3d3t7e4ODg" + "4uLi4+Pj4+Tk5OTk5OXl5ubm5+fn6Ojo6enp6+vr7Ozs7e3t7e7u7+/v8fHx8vLy8/Pz9PT0" + "9fX19vb29vf3+Pj4+fn5+vr6+/v7/Pz8/f39/v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA0PbvAwAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2" + "qefiJQAAAP9JREFUOE+t0tlSwjAUgOFjZQuxuOBSFURSSOyq0qYNEd//tUzCaIeLk9743Zyb" + "f5LOSUH3AC3vyEWIuIw06KvHRmGqKIJ3+u1RzaD0BpL+S3DwfIO5oqAHDxPk1LOpr9oGe0/h" + "ArXHmSCjLbpIpSobyBa3o5DSWqJaF+xqlPykEE/LHFWmU2AkS1GZCdYkFajkjZhAxCghCLwS" + "zlCcE1j1BpOeE176gqU/mMBy3F0Rb/mpZENh0QWxyJNTfD6HRXfFthgNndFgcJzDa2aCv0WZ" + "tUj7blLdP9nZ6PCZATtPft+qjELt/vAm/HCzCNYmuA2O5xqzmzOwAuIG0AfGfgDFvqY+8bKe" + "lgAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_tab_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAIAAAAJNFjbAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "ABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2qefiJQAABPtJREFUSEu9ldtPI2UYxovR" + "jZd65Z3xxv/AO2/0wnihiZpodgV21cSsiyYbs8ZkiQQSDUaynBaIB0ACLOsegJaWlkNpObSW" + "XQ5dlgUqPQ3DtPRIW9pO5/DNtPGZFjeVte2F4uTtxcz3e96n7/t+802VJEkqlapx2LQXE589" + "80xVVRVu//319FO5F587c/Xc6yoYXOrW983vzrqOrExqPcBuRTLOQ94T5/cSApMU/EnxIFUh" + "wIAEDxW0jyKZRSr+Vv3d724YFYPaDn2Iyy5H5I2Y/MeR7EllaTbrz2QDXC7E5yJ8LlopwIAE" + "DxW0yOBOylOO8CedOlUikfio0wAD239tYPEkatu0qng8/j8ZWCPyg5i8cyS78y3y/dWicL5L" + "5QNMoUVQQYsMzqR8XEEsHr/QaQhksosheS0qbyVkZzJLpbP7yhgUTYjLQV8+wOQHkIMKWmTA" + "LJcKLSoYYDimgHw/Ij9U5qxMiU5nGTZ3AI+M4lE+wIAEDxW0yLCdkBeODWIxGDCsPOUn1jBZ" + "P5TyRcjelEynZaawnSoFGJDgoYIWGTZjktkTUyo4PDw832nA2iRDFoNkNSptxpXNin1GpeX9" + "dNaX9ygfYECChwpaZNiISXPuWM1jAyolj9NkLkCWIxJGjQLxRzwpeS9d6FWFAAMSPFTQIsPa" + "oTRbbADnmx5iYMhSUFqJKpPYgQdeOgw8P4/yAQYkeKigRYZ7EWnala8gGo2iRVgbdAvafdGc" + "L8KOSaBRCdmFOtCrVIUAAxI8VNAigzVE9LtFBljrdQpjtDjrJ5aQhEngj6BYNLTgUT6U7EcK" + "DxW0yLAQJLqCQTgcru00bMXk6zvCTY+oY4g5INnC0nq+Udv5OlBf+QADEjxU0CKD0U/UO48N" + "Ogy2kPTGdPKilW20cx3b/K9O4bZXnKDFKYYANR+Q+dKBVTAgwUMFLTK0bPENJqamVavy+Xy1" + "7YbFgPSKNnl2gb2ywjVv8j0OYdAl3qZEDU30DJn2kZnSgVUwIMFDBS0yNG3wXxvzBgzD1LQb" + "5vzSy2PJt43sJRvXYOdbt4RfdsVht3iXUpS6fTJZOrAKBiR4qKBFhqtr/JUZprp1QkXTdE27" + "Hv/ihd8SrxlSF5bYr1YzzQ+5rh2+3ymMeIQ7XnF8T1SXDqyCAQkeKmiR4fI9rq7YAG/A8yOJ" + "VydT1Qvsl/cz325wndt8764w7BJuecRRShwrHVgFAxI8VNAiw+c27jNDvgKKolDBKRp4vd7T" + "NfCcuoHHc0oV1GEG1yZUbrf7w7YKM8A+0dGifp/8Y2AHT9BklCI33GLvrti+JTTa+QY7V6dz" + "fdyhrWyALYhNvIrzi8mUiqX9zDzNGilW706P7aZGHMn+9eC7Tepv+iZVLperfIvUlLgWlc61" + "6j5o0TwZ7/+gQTz5vLZVU//zxO82m2JQ3aaf9ZGX7iTenEl9amXr1zMtj7gfHfwgXgKvoHwk" + "9pK1rdp0Op0qunDL87zJZFpZWSl+zrKsKIqDQ0MWiwXHhGLw3vdqq5//YjnTZOe6d/ght/Lq" + "4vAyHSjfHxyQP5kcl7vVhJDc3680yw4NDeGje+J5MBjs6u52OByKAX5N/bp3mtVnr2lLxcUu" + "3a1R9fWurvaOjuLo7unp6+8/8RC3AwMDJrMZh9CxwfLyslqj0ZW+jHNzFqt1XK0eHRsrDo1G" + "ozcYTjzE7fT0tP3BA2TH9Sf2aVnapn4zWAAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_tab_focus_single = aero_tab_focus + +#---------------------------------------------------------------------- +aero_tab_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAACxiD++AAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFq6ysra6urq+vr7CwsLGxsbKysrOzs7S0tLS0tLW1tba2tre3t7i4uLm5uru7vL29" + "v8DAwMDAwsPDxMXFxsbGycrKzc3Nzc7Ozs/P09TU19fX2dnZ2tra29vb3Nzc3N3d3t7e4ODg" + "4uLi4+Pj4+Tk5OTk5OXl5ubm5+fn6Ojo6enp6+vr7Ozs7e3t7e7u7+/v8fHx8vLy8/Pz9PT0" + "9fX19vb29vf3+Pj4+fn5+vr6+/v7/Pz8/f39/v7+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA0PbvAwAAABh0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjM2" + "qefiJQAAAP9JREFUOE+t0tlSwjAUgOFjZQuxuOBSFURSSOyq0qYNEd//tUzCaIeLk9743Zyb" + "f5LOSUH3AC3vyEWIuIw06KvHRmGqKIJ3+u1RzaD0BpL+S3DwfIO5oqAHDxPk1LOpr9oGe0/h" + "ArXHmSCjLbpIpSobyBa3o5DSWqJaF+xqlPykEE/LHFWmU2AkS1GZCdYkFajkjZhAxCghCLwS" + "zlCcE1j1BpOeE176gqU/mMBy3F0Rb/mpZENh0QWxyJNTfD6HRXfFthgNndFgcJzDa2aCv0WZ" + "tUj7blLdP9nZ6PCZATtPft+qjELt/vAm/HCzCNYmuA2O5xqzmzOwAuIG0AfGfgDFvqY+8bKe" + "lgAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MToxNCArMDEwMESarloAAAAHdElNRQfZAxkQMyBAd2MK" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAh9JREFUeNrFl0tv2kAU" + "hY+NBXbAElS0QTwColKX4Q+gbvmDXXXVriq160olq7b7Rk2zICsejWyUqkmQgEIMxvWxZAti" + "0eCoHo40ssZj+5vrO3fmXgmuut2u4zb0+33MZjPEJU3TUKvVUK/X2SRlOp067ZMTNBoNNJtN" + "JJPJ2OCWZcEwTXw/OwO50o/zc0fPZFCtVmOD3tdgMMB4MoHc7/VQKpWEgalyuQxy5Tv3VyiK" + "IhROHrmyUOo97QXuOM7+4L4CZ/uziVuSJG3CCRYFX5+AcDg5iURiP3Ba7bOE+3xde/H5huWi" + "4SHLV6uVEKAsy5uW86hbLBZC4FzpoVCzbVsI3OcFcP5yUfBQqBEuyufkhFb7Y+BMubheHguX" + "fXiURjH7sSw7yIKivh/Ao4g+KxQK6A1MvHr9wbuyv35a7arI8Gw2i+ubCd6++4KUlveu7PN+" + "rPBcLoer33d48/4bDvTDoLHP+xyPop0zR13XYfxaov3VhJZ5Fhr/+NlE62UFh090jMfj/wdP" + "p9O4uk3itDNB6iC/9bnTjoXjFxmUnu42AQ/+0GKZz+c4KuaQz6kPflBTJYxuRrtZnnJjlbsb" + "N/xtsc7x2+thkIH8S6M/9tbdkkbyGxwn1yuX3OBDsVj0Dpc4j1aCVVWFYRhYLpdQnrsV46d2" + "26siGK9xFoo0zhwO0bm4QKvVgudsd3F4JfLPy0vPv3GJVh9VKl6J7EaP9Be4+2JJRD7+lAAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1NTo1MyArMDEwMAycPlQAAAAHdElNRQfZAxkQOBMcU9vX" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAABFhJREFUeNq1V82LHEUU" + "/1VNz863yWyUyCSaEPzYuGtkwY8QYwLiQfTgwYsgBPMXeDJXvYmo+Qc8KEFIULyJHhQWNcSE" + "3KLrLgqLoMYVkplIdmZ2PrrL36uqnulOsu50YB68ed3V3fV771fvvZpSoDSbzS9/Xll9+czy" + "EFc3IkxLGlWNE/MBjj17RBljPlQE/uH1MytHzcEjeOGwwYGSQokvzigqbUCbo9UTAhhqKMqL" + "IW2PdpN2g/pbx+DH7xX02gWcPblwWi2dv2Debz2Dd48bVIxCjiiBFjBeE1iJwumkIg4If5ER" + "NRiKIxxgtOhpjVNfGby15xL0xz8NsP+owkzoPpqmiBP5MEL9RUBw9bWuwe6iwvRWOi2C0yhp" + "CO6kSzkVCWJvzBYqopBtScwdruM8sHj+wkYuiWGMBxTrHxrvQdZcMCat0S33qcgFbMBBTau0" + "y3CL6K0yfmxCibzDoYkz3pdelGbA0U53Ir4RqfHLmq9FcHVmSy1D+GbEphnNF5cdLFYuAR65" + "MsgJqNcwMZkFzxB5THXcbGzUXk1iGS240BGmPEw3lqwlkcyfcbNxGHbOKEW7W/M4cpVYb7Em" + "I+0pQM/qIHIq8w1NMnLyMwhdHw98pCOqI39/NwnnGY2tMKwwLjUL7tbE0Iqyp1Ot5/HqqOwJ" + "FyYot/MKA5EZMTOOfOi8smuv3Yc26RLdRWeMPLnOoU++oV/SVOTnmuzv5H22oFDLK1S4nRVZ" + "DTN0JK/d7pbLAB7vYn2i92jbHLg5AFp9KV6Fs5zreAy+ycg3+KDADiMDOZuuCibnMjYLeEy5" + "gPcIvsmQuwTvMKPbfWNrfTAIx5H36FWHDpR5V+A4g7c0x1RLIkYZwSWz+5yr57UbOgwJJgwT" + "tEuqt+lqhYNFLvSMUB2O69tkBB/adm1IuwBL9E47ZMA6lwIfOup71D6p7mv/98m3VvHC3AIu" + "tzuYI//2zG27WJy8cW1bBjyGFQ8+al5RQs02Ksvxyr4Ae9t/WqvV9t8k548l858JYeOlhsLy" + "yhrePHXaWrnPUg13DX5oNsDf/9zAO+99jnKtYa3cy/hUwRd3Bbj5xzre/uAb1Or7Rir3Mi7P" + "s8jEbz+yI4fm79fxyRdrqO588LbnH322hjdezePRvbP4pRVONOdE4PtrGr2rHXx7qWup3krk" + "+bGwjYceqNCB4bbzOnD9//yv86Tx/IEKGrvL205YLSssXds68iROcC+PRxXmf5WNfCsHpEF8" + "xwlLE/DUve7a6Z2kyO8reY0arwU3OPl4HktXctjzHCenw1V2l2oeKLF2Cjm3scQNZzjByUKO" + "WvfwoDdgSyyytxf4fZ5b5Az/qdxHnyoE3fW1wonFAMHiwmOr85cvzn26fBgHnzZ4uKyxk6BV" + "gvFgwc2GDsB1vEnEdjj+cA8BDyVo0+Eb1CbZ+5Unxr/OA3PRRTz5xILrnq1Wy1y+soxzqxHW" + "29M7ON1f0XhtTuOpQ/Oo1+vqPxxtdiUOpmR7AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +aero_up_focus_single = aero_up_focus + +#---------------------------------------------------------------------- +aero_up_single = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAYAAADqgqNBAAAALHRFWHRDcmVhdGlvbiBUaW1l" + "AG1lciAyNSBtYXIgMjAwOSAxNzo1MToxNCArMDEwMESarloAAAAHdElNRQfZAxkQMyBAd2MK" + "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAAh9JREFUeNrFl0tv2kAU" + "hY+NBXbAElS0QTwColKX4Q+gbvmDXXXVriq160olq7b7Rk2zICsejWyUqkmQgEIMxvWxZAti" + "0eCoHo40ssZj+5vrO3fmXgmuut2u4zb0+33MZjPEJU3TUKvVUK/X2SRlOp067ZMTNBoNNJtN" + "JJPJ2OCWZcEwTXw/OwO50o/zc0fPZFCtVmOD3tdgMMB4MoHc7/VQKpWEgalyuQxy5Tv3VyiK" + "IhROHrmyUOo97QXuOM7+4L4CZ/uziVuSJG3CCRYFX5+AcDg5iURiP3Ba7bOE+3xde/H5huWi" + "4SHLV6uVEKAsy5uW86hbLBZC4FzpoVCzbVsI3OcFcP5yUfBQqBEuyufkhFb7Y+BMubheHguX" + "fXiURjH7sSw7yIKivh/Ao4g+KxQK6A1MvHr9wbuyv35a7arI8Gw2i+ubCd6++4KUlveu7PN+" + "rPBcLoer33d48/4bDvTDoLHP+xyPop0zR13XYfxaov3VhJZ5Fhr/+NlE62UFh090jMfj/wdP" + "p9O4uk3itDNB6iC/9bnTjoXjFxmUnu42AQ/+0GKZz+c4KuaQz6kPflBTJYxuRrtZnnJjlbsb" + "N/xtsc7x2+thkIH8S6M/9tbdkkbyGxwn1yuX3OBDsVj0Dpc4j1aCVVWFYRhYLpdQnrsV46d2" + "26siGK9xFoo0zhwO0bm4QKvVgudsd3F4JfLPy0vPv3GJVh9VKl6J7EaP9Be4+2JJRD7+lAAA" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +aero_denied = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAADxklEQVQ4jbWUzWuUVxTGn3Pe" + "+955k8nMJE5m0vqRZCaUYomG0uJGjDR0UXBRUj+6Kqg7kSy6SBf5A7ropiB07aK4qIsWIYsu" + "ioXBQoRKqWhJ1eq0sWqSmWQyk8y8H/e+p4t8oJ2YuPHA2dzD+fHwnHMuiQheR/BroQJQOxWv" + "Z7N9cRB8nAA+dIEBAIiAvwPgJ04kro1Vq/Mv66XtrCgVi9osLEx1K/V57+HD6a5Dh8B9fSDH" + "ga1WsXr7Niq3btVrUfS1yue/HH34MNwV/HM+n1HN5vd7+/vHsmfPQu3bt60i8+wZli5fxr/l" + "8nXT2fnJBwsLK8/XX/B4ZnhYqWbzyv5icaz34kXwnj2IrYUoBXge4HkQpRBbC85kkL1wAfuH" + "hsZUs3llZnhYvRQclMvnsqnUidTp04DWoEwGnE6DEwmQ44AcB5xIgNNpUCYDaI3UqVPIplIn" + "gnL53LbgmULB9YCp9LFjcHM5KM8D+z6o0QD+l9RogH0fyvPg5nLIHD8OD5iaKRTcTd6W/KhS" + "OZJOJgc7R0ag4ngd8gqhAHSOjMArlQbrlcoRAL+8oJhFjiaLRWhmrN29ixYzfNdF6LqIXBdG" + "a1itEWsNaA24LoQZ5s4duAC6hobAIkfbFDOQ78jlQPU6zNIS3jh/Hqz1jmolDFE7eRIqk4GX" + "zYKBfDtYJGDfB9VqkNXVV7IBAOT+fYjrrs9DJGgDE/Ns+OABkExCBQHmL10CM4OMAVkLAkAb" + "3hEAIgJZC7YW9OgRwnodzDy7xds8kF97et70iMrFfF6T1gAzEEWAMburBvBwcTH0RQbfX15+" + "Cjw3vIOl0tMgiq7VVlbA9Tq4VgOvrYGDYNes1esIoujawVLpaZtiAPitu/sta8yt/o6OVI/a" + "8X/aimVj8E+r1XCUeu/dWu3+tmAA+D2dHvetvdqtlDrgOEgQbQsMRDBnLWrGGM9xzozU6z88" + "X28D29VV/LF370d+HH/bFOntIkKGCJuLFwJYEcGqCDqJKh7zZ+88efKj09WFHcFbtuTzOW61" + "vghEPo1EDtiNdweASzSnib6Tjo6vum/eXCwUCm39dO/ePUxMTDizs7Oq0WjoMAy1tVbHceyK" + "iJsFOs4wv32AqF8AzMXx3FWRP5cAn4hCZo6YOdRah8lkMhwYGDCTk5NGzc/PY21tDcYYiuOY" + "RIQ30gGgKiL4xpi/AJSxvsIWAIjIAaBEJBYRx1rLxhhqtVqoVqu0rRXT09N048YNPH78mKrV" + "KjWbTfi+TwDgeZ4kEgn09vZKX1+fjI6OYnx8vA3yHxWIwp50Lj49AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +auinotebook_preview = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0" + "RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJXSURBVHjahFJRSBNxGP/d/+52" + "W66ildPKJB9EMSwjFSNoUkS+GYg9pi9R9tJeopcgC3yKYvRUMHA+VBJUmpQPQSySKINqEGSB" + "Np3aNjs3N8+7285d/7sxa9yw3/G777v/932/+76PP6PrOgw8D876dYImWJAEZPpW8l89nYea" + "i8KGgMHRYPitvgkCw0G9qaNXD4x80Qs1BknRnzaBEfX1e+G758PQaEgvnP8VULA5lCS8/T7T" + "NUQK4ApO4j/1l3s6N/zA8MiGTxiGgUGowGwkhqkfc5Bl1SKwlM6i90y75dzsoKX9fNPLF2Mt" + "sZVGuMt3YPDpK7jsDnj7uiEIfH6ClAjF5rAKHD1xcW99a927C5e6hP3luwCGwONpRjwm4tqA" + "H7du9pmJDG9HrsSeSE3dvmdnz512tFZVQaGjaCyBjedRubscxzva8PrNJzNR0xmsKsQq4KzY" + "fqDW7UY8o4KjxRzLmpYlBLUNNZj48BVRMQlWA7I5yTqCvcyuq+s0qmkQiC1/QeiTo1ajNpWS" + "oMoMVJVyXbZ2sDj9S5sMh2l7CjJUJGswq0HNZBCNLmEhPA0xsQpJUSHJJUZILCbuxuZF7fv8" + "ApbTaaysrSEpSVgURdy//RDJxAp2RvzgMgkQzbpEziVEbgQfBeuXPY1dqcMS4W08cnIWY0Pj" + "iIXjuNI2A0dsDmXyT3yUTtESZ5EAU3CuDvhD7ydDB1mWg6zQWelSj2ydwbE9v1Fd4TQZmHLj" + "yTfBzP88PsgUCZTCne4qFycID7Zt4TuqK50TFaTmZMP1x5l/c/4IMABbKBvEcRELXgAAAABJ" + "RU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_down = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVZ3egZ3e1cYCncYa0dpC4cYXGcpDBc5fWd6Dld6Pwi5OzgJbUkJjQkqHC" + "hafggqfwh7Dwk6Xgl7fwoKjWp7fXsbbWoLfnoLjwpMLwscHnsMfxtdD0wMHg5eTo5/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAverH2wAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAF4SURBVDhPbZTpYoIwEITRGqAQ1ChqOFrx/R8y3SObq8yvsHwZhglaOdH7HZbJIp1W" + "YW7t7w68LOcwDaxd5+lhC3q18zwOMhTWIjtZ+3wGfFmWFYZjgJl9vWC/1x30AI2zzExzIcqz" + "a2RxC2Ak2n4zJmEx1TgaY8R7AtHsdgMS1IivJRZHyN5lA6PMEgwZ2JVYs23bxwuWG3PGdF3X" + "ImvtBO/KqAl38ytgO+WqMz1SDDz7KXaCbatU5YYARt+SbUHg61zT9/iMHsQZKHBI9A1S6njk" + "fpuCRTjmJVQBymfxlfuCc5IX0cMhnhvCMYO8KCdCV0L9GbMz8Fhqous1ugbWw/32k2oDtFUn" + "do2sz1ywqWvCElz6pq4pS3CRVxooMvjqsI2+v5LwXCVrljfrGclhqH2v5e+Nr6VnYltgBct7" + "iDDExm+grvm0ouL/QwrjN1CHXvczYAz0xF5Pp9w168zfAhjeH9gSle8hnWutldL6H7rHOq2P" + "agd1f/M7VhKuYPh3AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_down_single = whidbey_down + +#---------------------------------------------------------------------- +whidbey_down_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGeSURBVDhPfdOLUsIwEAVQCCixFSggVfBFQWpVjKJo+f8fi3ezSZq2jDsDhHB62Wyh" + "o319Vctg9V2tO34p5H0bHw5D4Xe9FULK5bKhb6UcDs/cprNdMYiprhee30khaStx2NpudwCb" + "JHG8WAhbEkU0cZgtUge0iYrjCEUuilheOWysS2U9GpluGJJ1mKyg1LHZn6NYNNYUCWuozSjr" + "FVxnLGQUjZPJlLJKCk7piar0Nk2zS90RAhINVDZN5yUeNZuhbjq6BzmZomYoICr/Yr5mtcry" + "fL2mfnvjSd2anvkSsqBZUazN2XS/YVNLrc2Qut3yHAj/10OWFwVRtrpPvZo5cL+28A7fnxfv" + "G6Ps74GwsfXCsXKkMnWWko39DKukAbjUygKfskFqYLnnKvcHdQT9sA0EPdgD1ts9Vr2G/fpp" + "pDMeAg3gWe2fgn9V9d+0yW5edCylQurnwJejZ/7VPJpZ7V+C1PBsFrPFXAul6rSRi2S2uFmq" + "kdrK1frcWOq1kXrC6osHulnq963Wa3Nm9kOySrVpq1/yr5vNbtdK1foPGIxy6qmqIg0AAAAA" + "SUVORK5CYII=") + +#---------------------------------------------------------------------- +whidbey_down_focus_single = whidbey_down_focus + +#---------------------------------------------------------------------- +whidbey_dock_pane = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHiYZ3egZ3e1cHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pw" + "i5Ozl6e3q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dwk6Xgk7Tkl7fw" + "oKjAoKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo" + "4Of35/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA1jVbdgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAdFSURBVGhDtZpxe6I4EMbtbQVXrHJuaY8KWq/XW3Sr196u8v0/GTfzThICRIh9nssf" + "u4rw42UyM5kJHVVXjDQah/EV51ejK05+SKMwjK/BX0Gfzu9ms2kY3PgL8qd/nc/vCB+GN/54" + "b/p0sVjMaRA+9Mb70hku9GvUe9IBV3TC+9rejy5wTfdX70VneJ7LHRaLu7uIPNPL9j50hp+a" + "w9NzPOhQfvrHHqeZn/phOmzeofvZfpA+zTFa2rNsFk2GPWeIruB5y+5En00mg1E7QNfw/CSP" + "oEaZ51kWDavvpxu4phOWB+hJMqi+l17DFb0sBa+eJB3KOX30xLIFeAQXvKYTvtf2PfTVbrtd" + "6xsonrKMoafhJAwu5/vL9NWO6Rp/iZ5OJsFlfI/2onh7oxvQYKOsaeALjTV944fK2DFn489o" + "ryqmA69NDrtjWPSwZyHs9RlWr+h5Qzt9UdqzPvhATTBMn/Uu4QOxut9r24vFdzSUvUh+lk37" + "6wMXfVNf8sF0mdrt9nGF8YbB3ppNl/WpK8eNHPTNxuCXHx/a9q+vZz3exZvW6+m5NPhi58B3" + "6Yfj2zYRHcuSgOI5Bi0fDgeiT/nT8oxTi2K3U1dZz9ChH5gu+GXJ9HNRvP/dgoOfyEGox/Tn" + "nVlo04kNm34DHPRfv7psxuujywqm4rBo41t0Vs4uvl4/PXHUNLj7/f6VRvte5RJTjxBo4Zv0" + "A3sDxw2d+EeTcnzd4qnIdR43zZ/EbSUjNfEN+oHppFsiEZOmxn5/ZJOBvt1uNivrNyvk2nib" + "fmA6W0W0Z6FGkL0sNvLYt0L/aKeLtnqLTvCD2FzRNR53VelSZ8l1IfhfdqqDbRaWcWo6nWZs" + "zrk1SZLVvww40oBeZTG9oDwbOm6pDy8svKHzA9Y2Z3oUgU5WOSIXdOjAv/Nv1hLJ5aZRr+lQ" + "XhNYevSEZye6ZJo2PZNpd9ANXtFFuYtOM0p50UnP4Dm2VeQZavVCZ6cqudziCOL/szSKYkh7" + "hReu62UJv/MyS8+HM2AZuUwNgwcdTlVSkasrrppOgSL0Rg2ME9l6uD8/cqmWd643T0Y90xEO" + "azku48RzCu0vau1z0lNEhJu++Epkoks4dOhjPPaL+Hl+gf4upmlplzaIPGdUIbewXls7OTvo" + "xktbdK6auNyAWz266fOHaoQ4ZKNpOl940vRXHQN99BU9XMkzqylo3+7u0nTEdkHkKzoKRUNH" + "tccxeoEOwxs6XQqK0GdRNFoVKkqEDo+8lk4Syq2UsDWd2ubxKHtm5dzVid1Fe5bOjCx4kUt7" + "mohf8QPCMg3ttGERjp4z1Yk27O5HV15r6MbuVF1S2xbHo+pRNblNf88SaN9sVIpyaSfDYhnU" + "dEQK7A54ELO/f6VJqC2joknF4bmfPgadw4UtY+hmo4Vj9ZanWNtd07PsRfmDuYqTgc43nAei" + "e8TEu4OuWh7kGcb70kViKfR7phdvVA808gw34thHkBzJ+EZHSquH5NfzN5p1ZU+S/kM3fZxE" + "owBnCF2lWFAivcWi8jvUYydDBujAZ0JX7XD5Q0yTMT0EnVf6emHh9FC3anptgu3nc5ueppC2" + "0nfT//OdqRcOA5HeoVsbFGZdFbxFJ3/7C3ShyTPoz/C4PwHnhau+rLmxVdcEwNunRdEYNcFK" + "8Wv6jO4cBL8DfuC1xbqssbVi1TOMN6chHoSu1Ws6ByLZBfDzObEX8yRqNvd2LaZsLzt3k8lY" + "1g8aDw+Ukc3gfatAzMJjapTnOdEb2yqNOtLgSdwkMHBWGEIxxmQS/iYTqvAyGSmN9rZEswZW" + "eJkaC0Af4/ieBrPHY6kWWniCh+0NoVb9DjzbPLhZNhH07f4+CIK4heYbT0V7d0ui3XvcSvLk" + "QEbXROPnz859zFGOLGptZhRDru2UTt9EeB3ISj31TQ784fBIRwVeVTQtrq2gbs93G0Vfvtg9" + "3/Ft92KqdXWfI1cpVCspOOMnjg3QLr0aRwpOxuGOj+i7orD6JarnqVugEE2sfnXs2khx0CsD" + "r6rv33U3w/3MZvNCQ/dP3EI+1c2pa5fGRbfa2e/SApohXbw6RmFk0a2rzMd+ujRclAI0XnYh" + "pBRH1ujfhuil6yZX6LTEmlEnl158H91uFVVtr+t7ne0o9iKXSdSxHjpvLJieqLmXar5xaPfs" + "Ll2mP8IMWqRzTy/PSTplzIvqe7Qndit3gc4581Paq4p23lHpYP+dH6K5lyoZSce1U3+vz0xb" + "dMZLgcD/AB5aode9Qb+/S5VmtEsDUNMH334MxKqpcxw+4/HWbIAuNSbXgc3RLIo+4zNyjeBR" + "ZdbjRKuFKec+F03qqrpCtuieb/uGLGNXyDbdR7mugXsezlUhn06D3iJED+2qvkcDpCpUvzdl" + "nnTVnSg6vavxfT/spb1Wz9qpnPN7Q+mrXTsmF1I+78j0PHpq13jOLR7v966mV6jS0BX0vAJq" + "OZ+3dlLP8UnDH+7nkUpRHMf/399CVFUcf7nu7zj+A8yummsi9EdGAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_dock_pane_bottom = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAejSURBVGhDpZkLe9o2FIYpTVeWrG3Srmy9stk0LTPJMKMhmYM9KOuSERqydAX8//+H" + "dy6SLF+QRXeepwtg+9XnT+fotlqyRQRey+1vcX9S2+Lm08Bz3f42+C3ond5xt9txnRf2guzp" + "b3u9Y8C77gt7vDW94/t+DwLwrjXelo5wpm+j3pJOcEEHvK33dnSGS7q9eis6wqOIW/D942MP" + "MtPKexs6wlfZsMwcCzopX/2jx6prp76aTp4X6HbeV9I7EUVOexh2vXZ15lTRBTzK+Q70brtd" + "WbUVdAmPVvwKIuIoCkOvWr2ZruCSDlgMog+HleqN9BQu6HHMePEmQdWYY6IPNS+IB3DGSzrg" + "jd4b6KOr6XQsGxA84YyiB27bdTaP95vpoyukS/wmetBuO5vxBu2z2fU1NACBpowh6AvEGL7h" + "S4WYmN3W12hPEqQTXlpOvlNodNcwERpzBtULepTRDl+E9tAEr1gTVNO7xim8olbnc+k9O34F" + "IfwC+WHYMa8PyuiT9JHPSOeunU7PRhTXFJitYWeQ3joqaaiEPpko/ODzZ+n9xcVaxi1n03jc" + "WccKP7sqwRfpi5vr6ZB1DGIAcuYoNH9YLIDewU+DNd06m11diae0dyjQF0hn/CBG+no2u73M" + "wYk/5B9JPXV/VOiFPB3Y5Ol7ghN9uSyyES9/HSRkFZZFHp+jo3JM8fH4/ByrJsOdz+cXEPm2" + "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") + +#---------------------------------------------------------------------- +whidbey_dock_pane_center = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAGDeAJUqQIEOkIkewJFKlJlayMlSiMle1K2G0LmnSNnDgR2OkQ2O1UGagUHCo" + "VXSzaHWVZ3egZ3e1cHilQ2TAQHDAQnLSVnfFQXfgcYCncYa0dpC4VYTTRIDjVYbgZobHYIfT" + "ZpPXcYXGcpDBc5fWYIjgYZHgcZjgd6Dld6Pwi5Ozq62xgIjQgJbUkJjAkJjQgJjggKDXkqHC" + "k6TWlrDXhafggqfwh7Dwk6Xgk7Tkl7fwoKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjpMLwscHn" + "sMfxtdD0wMHg6tTN4Njk5eTo4Of35/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAXehG6QAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAdqSURBVGhDtZoNW9pIEMc5emrB651VUDRUpEkqb5d6SBOUXq9Q8dprke//aXLzsrvZ" + "hGWz+NzN87RCzP74M5mZzGyspDvYOGh4wx3OTys7nPx+HHjecBf8DnS/3wtD37t47S7InX7V" + "7/cA73mv3fHOdH8wGPTBAO85413pCGf6Luod6QQXdMC7+t6NznBJd1fvREd4kvAnDAa9XgCR" + "6eR7FzrCV3lzjBwHOilf/a3bKnRTX04nn2/Q3XxfSvcTsoL2OA6DdnnklNEFPCn4Hehhu12a" + "tSV0CU9W/BWErZMkjoNy9Xa6gks6YNGIHkWl6q30DC7o6zXjxTcZl9UcGz3SfEE8gDNe0gFv" + "9b2FPlnMZlP5AYInPKPoY6/tXWyv99vpkwXSJX4bfdxuX2zHW7TP5w8P8AFg6JQpGL0Bm8I7" + "/FIxBmbYeI72NEU64aXLye9kGt2z3AitMYPqBT3JaYc3Qntsg5f0BOX00HoLL8nVL1+k79nj" + "CzDhL5Afx769PzDR77Il35DOl3Y2u5mQPZBhtMb+KDt1YvggA/3uTuFH375J33/69CTtK0fT" + "dOo/rRV+vjDgN+nLx4dZxDpGawBy5Cg0v1guge7jq9ETnTqfLxZilfYdNuhLpDN+tEb603z+" + "9a8CnPgRHyT1dPmTjatQpAObfPo7wYn+48cmG/Hy6CglV2FaFPEFOirHEJ9Ob28xazRuFIYt" + "sjjOf9p6RJeeUqCAz9OXGA2YN3DiHzrjw4du66zT6VyCNZvNdzk+hy1XpDw+R18iHXRzJtJF" + "Y4uvrrpdgBP98vJds9vNfqmlXBGv05dIR6+w9tiThLet62tkn50h+xzs8GVL/lIvF0X1Gh3g" + "S/a5oEt8+PYtws/AFP2wxfgfeqkj3ww052R0OE35HGtrFEWTfxBwDUZ6NauDnSq6LNNM1/CK" + "jl8w8znSg4DoEcDZG0U64b9indBukdhuKvWSTsqVV+iOH9zSBe0C3Eiv/3yFJxjoCi/orNxE" + "73YBbqbXiS5vAJn+TD3TMajW2G5hBuHPeBwEQ1wbtjrklfuCoePrtVDgsV3QujWFJzoF1Rqa" + "XNlxafSOoOO1zeye6TWkf8KvvBa3d+w3V8r3SKd0mPJxthVeU9Q+adno9Rco3kwfXAEZ6JwO" + "G/QG6nKhg+cL2nkMgsippFRbUK+uHYKd6G/edDoUiPcmz9TrB3jSjZnef59WqLag0yQd262V" + "pLfedC5L6RMImzXEPK1EjTS+9XrjcQX9Qpkv6NQoKnqzc8lZZNOu6LBUo4dBUJnMRV1hOkXk" + "rnRIqPWMW9iMDmNzoxLfo3Kc6tjvrD0eh1Qhm5BI27XX9qt06YnOSzW673mV+1hMojm/P4+u" + "/A7dJYxtw2ElvRFDbj7e44i0d09F9TL5vba/f6Rrp0yhq0rwiyHG+xVc4swzIpugijnQq0TH" + "dEHPKLraaMFcPcEAkn6X9Dj+CAs/NM/PMedVzMhyg3Vg/6A6xhpsoIuRh+oM4rfSD/P0Q3qP" + "dQbolEzzB+gHcnUGB3HaR+AaifjcRAp3j3iCS3svqV5J7Z37jL63R9IFXZRYogRyi0XUd1JP" + "OxlsRCc8weuQTmwd/gGH9gUd7/SyjcCF+vaQvDeR7/t9nT4mZZNXzC8asKsBnrBB1zYo1H2V" + "8RodEvnPrfRaDei/ERwb8GxZfmMr6wkIr58WBA3qCU5Pi7qJXf2V4Eu8X2rLclsrWj+DeHUa" + "5QPTnwr0GhjQCQ4tQ+bzJImC/HCv92LC97xz1243qMKjvXpVq0n9NchQpRx/6SvlRM9tq+T6" + "SIUPcfdFwZ8mk9oLVMxksJ+OKEnZfAywOB6DFbcl8j2wwPOlydbDq/DgYG8P/6Funa3wAPeK" + "G0KF/p3w6POL16McHfQfHR1Uq9W9XwIKRM2GpB7gG1sSxdnjhIsnJjJNTWDfvxdo2lG8Y8Bo" + "E0IOmbZTNuYmwMtEFuphbjLgl8sbOMrwNPXMG1mbM99JEBwf6zPf48Pi47zAf8QuJUK6GCm9" + "sG3YAN2kp41AwME5OPEBfTGfa/Mq9PmPcBAmQ21ebZg2Ugz0VMHT9PNn3odAgyn57iPYjBta" + "bKz922w4Ne3SmOjaOPuZR0BlPMWLY5BGGl1bpV7a6TxwQXmQeN6F4FacqoZ9G8JKl0Mu0++0" + "75AVFyveRtdHRdHby/5eVjvIvcDkEnHMQseNBTUT5fdS1TtMbcvu0nb6TW6SM+7pJQlIh4K+" + "Vb1Fe6SPclvoAdiztKcp7LxTp0P77+jp/F4qVySZ10b91pjxC3TEczeF/xHc01Jv8wPs8c5d" + "mtLOA0BGL336UZKrqs8xxIzDU7MSOveY2AfmLd8UPSdmeA3jqcvMbAV3C9XOPS+bxKqsQ9bo" + "jk/7yjyjd8g63UW57IEtX87UIa9WpdHCRAftor+nAYhbl//0OZ+cTgQdntW4Ph920p6pR+XQ" + "zrk9oXT0jApMbKRcnpHJ6+ioXcY91haH53s701Pq0mgOtTwCKgSfs3ZQj/kJ5g53i0ihaDgc" + "/n9/C5Gmw+Hxbn/H8S+cD8xcYY4GnAAAAABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_dock_pane_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjAoKjW" + "qLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of35/D4" + "8Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAA+wCLtAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAeDSURBVGhDtZr/f9JGGMdprZPJnHSKm9OKI4DG0K5kWAptGIw5FGqpdQL5//+Q7Ply" + "d7kkR3L4ms8PWmjyzifP3T33PM+1FO1gQ6/u+DtcH5V2uPh86DmOvwt+B7p7etLpuE7jmb0g" + "e/qr09MTwDvOM3u8Nd3tdrunYIB3rPG2dIQzfRf1lnSCCzrgbX1vR2e4pNurt6IjfDTiJ3S7" + "JycezEwr39vQEb5OmuXMsaCT8vUn3dYdO/XFdPJ5hm7n+0K6OyJLaQ+CjtcsnjlFdAEfpfwO" + "9E6zWbhqC+gSPlrzKwgLR6Mg8IrV59MVXNIBi0b0fr9QfS49hgt6GDJevMmwKObk0fuaL4gH" + "cMZLOuBzfZ9Dv1xMp2P5AMETnlH0odN0Gtvj/Xb65QLpEr+NPmw2G9vxOdrn8+treAAYOmUM" + "Rh/AxvAJXyrAidmpf432KEI64aXLye9kGt3J2Qhz5wyqF/RRQjt8ENqDPHhBTlBM7+Ru4QVr" + "9epK+p49vgAT/gL5QeDm5wcm+n58y2ek89BOp4NLsmsynK2B24svvTQ8yEC/s6/wvc+fpe9n" + "s420W55N47G7CRV+vjDgs/Q7D+/fE/heCECeOQrNPyyXQHfxp96GNM/ni0U/oz5DP3iIdML3" + "QqRv5vPb9yk48fv8Jamn4R9lRiFNPzg8rCJ+j+BEX62ybMTLb3sRuQqXRRqfogP88BDo9/b2" + "LnDVJLhXV1czsPSzwh4NPS2BFD5JP3j0E1j1YeUeqE9SbmZTmikwdQaT5K942nJESuIT9INH" + "TK9WAL+n46+ubm54HqJNJpcaX1tyabxOBzjR0fWofl8ilkudTXHs7Vz+Ug8XafUaHeFEl+ol" + "frlcgmy5pjhKjueMX+mhjnzT1ZwT0w8eVcHwCfg/eqdc/hsBN2BMVNGeMH8pOj1Q7jNdDa/o" + "oDxDr/+LMw/gpDtDJ/wt/k7bIjHdVOolHd2SpFcrv9K7A50jTZoe0FrdGOgKL+jkcyMdRhTi" + "opEe0MzRvcLvEKtnOsJXutXQ7yRtRrNwHG9LIaVltO9xLCDPhPw1m8ITnZSv4M1XT9BqtRXS" + "f8ZbYaEwPZEDfxJ0ws/QaaHY3jHfXCv1SP+OmCl65elTvPWd2PuM9KGznd59BWSgf0/wtPYK" + "wYHO+/YW+i27JqWdyyCYOaXo5csamK79SW1V/fE+rVRc/DRXUnTMmjDduMCLBmb66XlUOm+3" + "k3T0vaLPbOiX8HIhjizmgOh3Kt9OTobDknt8nKCvVjiq1Qprp2wvRzs5XtEBr9E7npem07Tc" + "lQ4SwimnsDEdyuZ66WLUPW63flN+B+3o90qZtctlbvL7sO/58gXJMwnt0LBwgD46Pm614lHd" + "ge6l6MrvkF1C2eb7pWhwDeqBv/qorSapfTIRIcqkHRxL2yAODmqnzJ5GleANH+f7ACLDcbtt" + "pG/y6XWi44JL0FWjBddqf4r4BL1WuV8+Eo5XmjAYyHiDUcY7q+Mltwa6KHkozvTRNxl62Uhn" + "B4RMP8NL5teQDyTiDBbi1EfgGAnqf1+vtSBZk47fvA107R9l0RcMPc9rULBgOsVIESk92WIR" + "8R0KMJ45HCNrSC8/x3sDpotyOPzIrgmQ7hAd9lxtY8HwEJdqcm96MwXntFtM5m21Un5PvkE2" + "1DD0FPyfDdgsPUPXGhRqX33TpXmp6LhrM13ydDrNONrTl7hxyX01qVz6HX3v/onqdfqDB/8Q" + "XvBjegdmeqPxB8GXuLdo9ERrRctnoFWi06uVB/uYEyj1ko4LEfxC8M2mr2/mfS9Z3Ou5mBvg" + "qmo9Qbc/flyt3uX7N5vz875m2LdqsFvQXKV8NAJ6oq2SyCNJfatFdA2OCh1STNZsOs95QAWe" + "B2YIlm5LJHNgUk/0JBwwvn8Ghux6nSJjGg9wJ90QSuXvLvqG4b0kAj6dnTUaDT+Fxge7rD3b" + "kkjXHp0O8GHW38Xag/lfvmSeo77FlQWlTQfWkKmdkqmbmE5RQqiHusmAXy4H8C3DowiGxdQK" + "ytZ8r1+320dcvXHNd3O9eKeydfGcGywJIFcScMQ3DQ1QQ736uv2DLA1B/Rzpi/lcq5cgn4dq" + "AZZoX6tX66ZGioEe/RLXnR8+yGoG65nJ5B2YrJ+whLyILzV1aUz0+I7oA1RLXCuxcRUvvoNl" + "pNG1u9SP+XQuuCAESDp3ITiZp+CS34bIpcsil+mwxSqLg0suPo+ul4q08bCJ9J2kQ0bnmVwi" + "vsuhY2NBJkupXqrqrGLCmNNd2k4fkBtk5Db29FA7Rsyt6nO0w1Ye2xY6xsyv0g67FZ90cP+d" + "8oxEL5XTuSOxro36c+eMm6IjnhME/IfgTh68oKf3IqmdC4CYXnj6UbBWER97Rg0Day/qYOs5" + "gXngST08wXB2UKi8mB4xvps+96C0oPDMqcAz8ELs+/SJ0P90ZsP4DN1GuYVnhPpvdlYm1FMB" + "LTJUu5MyO+3S94IOZzW258PFo0ozVc571A7pnN0Jpa12oR5Phq3OyOTasdQu8RhbLM73dqZH" + "L1A3VQU5R0CpBW+tHdTj+gSzh+/0txC+73+7v4WIIt8/2u3vOP4D32mBB1S/lsMAAAAASUVO" + "RK5CYII=") + +#---------------------------------------------------------------------- +whidbey_dock_pane_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAe+SURBVGhDtZr/W9pWFMYtW1en67RzunWdpUvQlgWdMCoyJFkotbaRCrMryP//f2Tn" + "yz25N19ILn2enV8Qkn7y5s2555570414jRh4dbezxvnxxhonXww81+2sg1+D3jw7bbWarvPM" + "XpA9/eXZ2SngXfeZPd6a3my322cQgHet8bZ0hDN9HfWWdIIrOuBtvbejM1zo9uqt6AgPAr5C" + "u3166kFmWnlvQ0f4Ih2WmWNBJ+WLf8xYtOzUV9PJ8xzdzvtKejOgyGj3/ZbXqM6cKrqCBxnf" + "gd5qNCpHbQVd4MGCb0HFMgh836tWX05P4EIHLAbRe71K9aV0DVf05ZLx6k4GVTWnjN4zvCAe" + "wBkvdMCXel9CH96Mx6FcQPGUMwl94DZcZ3W9X00f3iBd8Kvog0bDWY0v0R5Ft7dwAQg0JYSg" + "LxAhfMOb8jExW/Uv0R7HSCe8WE6+Uxh0t2QiLM0ZVK/oQUo7fFHa/TJ4RU9QTW+VTuEVY3Uy" + "Ee/Z8RsI5RfI9/1meX9QRB/pf/IJ6fxox+P+kOKWArPVb3b1qT8XXKiAPhol+O6nT+L99fW9" + "xB1nUxg275cJ/rvjV3l8nj6d3Y57fGJ3CUDOnATNf0ynQG/iX917OjU6Pn6Vx+foU6QzvrtE" + "+n0U3b3PwInf4x9JfbR/cHzSzj3iLB3Y5OlrghN9Ps+zES+/duO7h/tMz+IzdFSOKR6Gl5c4" + "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" + "AABJRU5ErkJggg==") + +#---------------------------------------------------------------------- +whidbey_dock_pane_top = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAF0AAABdCAMAAADwr5rxAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagaHWVcHiYZ3egZ3e1cHilUHjRQXLw" + "cYCncYa0dpC4R4DwU4PwZobHYIfTcYXGcIjVcpDBc5fWZpHxcZjgcJj3d6Dld6Pwi5Ozl6e3" + "q62xgIjQgpXEgJbUkJjAkJjQgJjggKDXkqHCk6TWhafggqfwh7Dgh7Dwk6Xgk7Tkl7fwoKjA" + "oKjWqLDIp7fXsLDIsbbWoLfnoLjwsLjjl8D4pMLwscHnsMfxtdD0wMHg6tTN4Njk5eTo4Of3" + "5/D48Ofh9/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAaHenigAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAe1SURBVGhDpZkNexJHEMcxrTVNao3G0PoSaXmJ4l1soBhCCUdBmkaPCDFWCN//e1z/" + "M7O7t/fC3aHzPMqFu/3t/2ZnZmeTUrCBnZ83m2cbPB+UNni4NxweHx/9tsGIDei92bBF9B+L" + "44vTu6T8AFb+oTC+ML07gfIjppe/L4ovSofy1nGjIfT9oviC9O6kBZ83flf0ovhi9NccLY2y" + "sv39gvhC9NeT4R/HR5q+Xwa9GL4IvQmvLBcRK4gvQG/+DZcvP8JG2hYF1efTSflxY2HIdLHY" + "gxVwTi696VGCptLz8Xl0Ud4oR90u2vOXNodOyjlYFjoa+VPT89Rn010ol+xXdP1Bq/oI9jA7" + "azPprguX23T4R7QbejY+i/6q1YJXpLYwlZ0foz/4LqOkZdBfvaJgaZQzPbP3IAu/nv7rEYxi" + "g+xgIfVrgX+44FV99HAP7J2dra216jO0/3RULkfpPAP9p+lg37u3Hp65r/oHByE9GvDy/e4u" + "2Bnw7F3bD+kQbGbS1w/y4Dk9wd29vcePtc5dtp9/1rOQ8jt3MjfBnFwlPLtgd2dnG7YD291V" + "88Et2fBU7eNQz2fgiU3kLWVPnhBflHfCRwcpb5GifTw2+M5n4Mkf21vbh7fanvzCPoLy25XB" + "+9MUfJI+v55NuqKjswLw7v3729vPDVounj6ld6Grzi0/6vvTqRplvUOCPie64Dsrot9u3X//" + "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" + "gg==") + +#---------------------------------------------------------------------- +whidbey_left = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVZ3egZ3e1cHilcYCncYa0dpC4ZobHcYXGcpDBc5fWd6Dld6Pwi5OzgIjQ" + "gJbUkJjQgJjggKDXkqHCk6TWhafggqfwh7Dwk6Xgk7Tkl7fwoKjWqLDIoLfnpMLwscHnsMfx" + "tdD04Njk5/D49/f3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQ3JLMwAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAF4SURBVDhPfdTbdoIwEAVQL6htAi0tElBaoVb+/xPTM5M7wc6LLvc6GTIJbnRel/DT" + "ZkUvwXOefsahdqGMJ2LvSwaO4P7d5BdMWejQ9+dzzhOiY4/quu4z44kYSebudbH4REwLm7Q6" + "Jr2hk+lrGR4e7Rb17ZRSdV23v55vxL4vsRCBOesXRrquBbZm0yb7jIHD/EDNKPpUjRBvbmOU" + "Heb7/f6g/aISJhz6R8J4MJvmbM6FGSrPmRaM09i0YZ4zDdHxTN8909I8RMtzyu3NztgwbyxK" + "qytlq6pK0qqROC/0vipGzx0v7ll/MAY2Y1G1SWv9UpXlCvNlpiPZlyjX2w4VJ/rlZk7+D3Oe" + "DsoXwqoNV5HzKHtidJlUC3eXifuXZcxNE91U4xFLIb6jm8oeWIKLcFPN/jxLKY/HhM3+TUl5" + "OhU48eQN9Y6VTwe6D+kLbJ0W3m5X3m926ntgXb7+eg/z2ZzJhcuusN4Lsds9/WfSuhBes94U" + "C6r/AM3yZVcU56/qAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_left_single = whidbey_left + +#---------------------------------------------------------------------- +whidbey_left_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGNSURBVDhPfdRrW8IgFAdwLyuXZLIUy67D6dK0pd3w+38xOuewAYOMN3ue/fbnwHZG" + "R8ej5251Yu33nMfcHw8H1iNOxsiNh5xkGUfvmqIBg2YZ8KBrvM3J5BoGH7NBnW9xMjHMGTjl" + "fQYlxvKYh/V5jErc5MEdJxMOAx/BKxZI0w/LkI340aZx5jZzdmdrU92TjKr8IbB2szHKqv1+" + "r2Y4hFDINzWf082A2Xxu+II0TDNU4KcnAcNPz4TiV0P6pp1XKduM9R0XZdlipXBpnNXpgGl3" + "Hu+qdSnzZ1sb0libpab2rqrKMs/d0tqstz+QhwfUt/dabFrrbYXzy1OsNwf0Fgs2TB+ad77B" + "6SNOba9B/uV49D6ZqIvX3bJp1m++mEBO710rLg8wv8wNmVZj6ZdrxeWatmcZW9FjXbxj3ufR" + "6NPr8wLyHnM28vtc6+INX08+w9LTKednwS9I+TwnJg3+UMoTGw3/7wKndxqyXq3gAdg9ZaO0" + "1oab8yo+mRYLKe1p9se5tpCX/7G+dUfhL8vucupsrDz0AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_left_focus_single = whidbey_left_focus + +#---------------------------------------------------------------------- +whidbey_right = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pwi5OzgJbUkJjA" + "kJjQgKDXk6TWhafggqfwh7Dwl7fwp7fXoLfnoLjwsLjjpMLwscHntdD0wMHg4Of35/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAWZqHkAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAFySURBVDhPddPbeoIwDABglDqoFDcVGBVw8P4PyXJoQ0sxN+L3m0NDzVaJx/YoT5k8" + "9fbAhfve2luS77kfhq5rir077pkTZ34Ng7Vt2yRO/ELuUPeOTIWxdOrA3Fc46p/9AVobsgnm" + "Z0b1xRsTeLa+EV1f+jCBQ+8DlnzgsDBX2fLxYFR8WeYtxJF/u65tF95KM0/TNEv+ZzZfkElL" + "TbKhuDEVnJ/4Z1+cufpmfsBwC47newNV1fV6v8cMTqMx67Jkhs0s3YIRsNbqHDCePczWhVIx" + "S28NoVRdRyxrMaR5zZPjdcDJha+opxOf+33ACthtrR/glkY7LzmXs5npjbn3VqqcFHmE2i0E" + "934+fd9PjKXdvylbR7yn/q7FuVB8HOF9uMJUOsjF3retb9PcysuFZ+aA0QrJJXYzC6/Fk+IO" + "Eee628IOquJcx5wP6nYV9cYvGpYBKucNRqNHpfW+r9+580uS63vjD855vvXcF4fvB7r+A9+i" + "Xf4K/oDaAAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_right_single = whidbey_right + +#---------------------------------------------------------------------- +whidbey_right_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAAB4AAAArCAMAAABYWciOAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGXSURBVDhPdZTZVoNADEDpaC2CFSqC1hVaxWoVqdv0/39szDIraF7gcHuTTCankbJx" + "6V7tW2TfTprVmDvcNKsxt7ismnbzOPQ1npaMh5zxNMdo4Afr0CfMNK8Bv4UcMdBzwshDHzBS" + "wlWN6QM/UmKecu68hBj40ed8nmrOuN28u/qR+op9XNfANw+mf8asow31ge8Mh9au4zhlRIF+" + "1z2zjwcTiKWL/f6p2zFHHMdJWkpty77/lpCffcQ3IwzHY5+GitkDG8fTddv/MB2v+9n6dlVJ" + "aBxq9/Dk/l+95IDgu8b3eD0GJ1ibTmYwzqFt12wTLn07xKc51XW16XqaF20D1jPVtRHf3XHn" + "Sxyqm1ovC5r+MZ97OcJEj/TULuA+B3ZRFIdm5njd/o1JaSgmvzK7Bh8LXAt8kku1/8KaAr61" + "u+ZsQ1X0Aauks1tsKdhCzGb4gzMKr66uTTzLFwuNnctjmUycb3t2m6om7JPtu3qZyE+yBURI" + "+UogvwAM5QfUYOw/ybIhtVghPuBUXrg/LiHG1NmwcSNXqV+4tHLqnJPo+QAAAABJRU5ErkJg" + "gg==") + +#---------------------------------------------------------------------- +whidbey_right_focus_single = whidbey_right_focus + +#---------------------------------------------------------------------- +whidbey_up = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAaHWVcHilcYCncYa0dpC4ZobHcYXGcpDBc5fWcZjgd6Dld6Pwi5OzgJbUkJjA" + "kJjQgKDXk6TWhafggqfwh7Dwl7fwp7fXoLfnoLjwsLjjpMLwscHntdD0wMHg4Of35/D49/f3" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAWZqHkAAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAFmSURBVDhPhdTtdoMgDAZgW3GKWjsL2i/X2vu/SJaEBBXcWf6Jz3l5i9bMpdNXR3Xa" + "Wc/StXNfKXXawaktm1rrUuWHJCWxX01TA1bqkODYlm3bNjCAVYwji9TbneStJcoWcNR5Yz0V" + "mySvLVJrvW/buq7g7NadVxbpvJ3taSyWUuef9cx6kxwsdU3sprPY0tJEucboqginwZapjfqC" + "1UUhT9BboXb28Twfa42pQjLZQMUCwiHbdZKMdqFsPx+PeZee3w2w3WpXugvUY7GAsXPmLvdx" + "HITzXe4QbK8Klbvsckcr+C/bF0WeZ+52ez6Bw+D2AwxdwAxwhRsaPDp9hA4OLWGpSn1pVlZh" + "X8Cg2dpNLlxwrgFKFpP/sRqZf26Ph3T2Te8w3AyijSlJ8fuA1v/Acfy+0Dxp8DyZig2dr9fw" + "WXj5ExoGnxpy5TSi78c0gRUacvE0XjvfsGnqwuryH3q/d6hz07L6CxOEXf5LAPv7AAAAAElF" + "TkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_up_single = whidbey_up + +#---------------------------------------------------------------------- +whidbey_up_focus = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAACsAAAAeCAMAAACCNBfsAAAAAXNSR0IArs4c6QAAAARnQU1B" + "AACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA" + "AwBQTFRFAAAAECBXFC9vECiQBS2oIDKMIDigC0CVJUqQIkewJFKlJlayMEiwMlSiK2G0JFTR" + "LmnSKGHmKHDoInDwMGjgNnDgNXLwSFeKQFizR2OkUGagcHiYZ3egcHilUHjRQXLwR4DwU4Pw" + "YIfTcYXGcIjVc5fWZpHxcJj3d6PwgJbUkJjAkJjQkqHChafggqfwh7Dgh7Dwl7fwoKjAqLDI" + "sLDIl8D4pMLw4Of35/D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAQS37pgAAAQB0Uk5T////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////////////////////////////////////////////////////////" + "////////////////////AFP3ByUAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4zNqnn" + "4iUAAAGTSURBVDhPfdQNV4IwFAZgpEyCVBRZaR8OUcI0yrLw//8xuvfuy22e7jkKZz68uxtg" + "0Pm135fl24XxwB/bNU1VFS/+D77d/TY12lsPe3aLqTkUu3Gxa7cHSC3IsmsHOxZS64pzYTMH" + "23Z7qKFXvpTWwZZd0w5wJivLbHxu14fmtSqUzRhYC5/ZEuY/tVbZ2NjyA1o9/UB9qmrtZG0x" + "teKtdnjSplCmDWXLd7xZF63G0opUzux2Ra5eoLCYShvQqv2io7IymewGUsV9lVYdcG1TqAnd" + "QbSbDbR6bqETkastYbCruob5xTNAhpp27PgK7WqFG8DZvz2kY8DBQwGF68XKW/HUtPCBE1rb" + "dJKCjOMwDLq7gjHbkscvZUEOBiGtLc+NtTdYjCcJyFDsQ2cshOnr1PlYUmH7aTqbqYyEajRS" + "12Bqr6f2V2CaLInjCCqGShJ5NTRAVOQSRokulDWfozap2gLGmaMwetIv7/yeulGpxnb94TCK" + "Hp23fLHAedSgeS/C4fHo/y89R5qqfhF9+xJGvszoH5Xccuo6pVT3AAAAAElFTkSuQmCC") + +#---------------------------------------------------------------------- +whidbey_up_focus_single = whidbey_up_focus + +#---------------------------------------------------------------------- + +whidbey_denied = aero_denied + +#---------------------------------------------------------------------- + +# ------------------------ # +# - AuiToolBar Constants - # +# ------------------------ # + +ITEM_CONTROL = wx.ITEM_MAX +""" The item in the AuiToolBar is a control. """ +ITEM_LABEL = ITEM_CONTROL + 1 +""" The item in the AuiToolBar is a text label. """ +ITEM_SPACER = ITEM_CONTROL + 2 +""" The item in the AuiToolBar is a spacer. """ +ITEM_SEPARATOR = wx.ITEM_SEPARATOR +""" The item in the AuiToolBar is a separator. """ +ITEM_CHECK = wx.ITEM_CHECK +""" The item in the AuiToolBar is a toolbar check item. """ +ITEM_NORMAL = wx.ITEM_NORMAL +""" The item in the AuiToolBar is a standard toolbar item. """ +ITEM_RADIO = wx.ITEM_RADIO +""" The item in the AuiToolBar is a toolbar radio item. """ +ID_RESTORE_FRAME = wx.ID_HIGHEST + 10000 +""" Identifier for restoring a minimized pane. """ + +BUTTON_DROPDOWN_WIDTH = 10 +""" Width of the drop-down button in AuiToolBar. """ + +DISABLED_TEXT_GREY_HUE = 153.0 +""" Hue text colour for the disabled text in AuiToolBar. """ +DISABLED_TEXT_COLOUR = wx.Colour(DISABLED_TEXT_GREY_HUE, + DISABLED_TEXT_GREY_HUE, + DISABLED_TEXT_GREY_HUE) +""" Text colour for the disabled text in AuiToolBar. """ + +AUI_TB_TEXT = 1 << 0 +""" Shows the text in the toolbar buttons; by default only icons are shown. """ +AUI_TB_NO_TOOLTIPS = 1 << 1 +""" Don't show tooltips on `AuiToolBar` items. """ +AUI_TB_NO_AUTORESIZE = 1 << 2 +""" Do not auto-resize the `AuiToolBar`. """ +AUI_TB_GRIPPER = 1 << 3 +""" Shows a gripper on the `AuiToolBar`. """ +AUI_TB_OVERFLOW = 1 << 4 +""" The `AuiToolBar` can contain overflow items. """ +AUI_TB_VERTICAL = 1 << 5 +""" The `AuiToolBar` is vertical. """ +AUI_TB_HORZ_LAYOUT = 1 << 6 +""" Shows the text and the icons alongside, not vertically stacked. +This style must be used with ``AUI_TB_TEXT``. """ +AUI_TB_PLAIN_BACKGROUND = 1 << 7 +""" Don't draw a gradient background on the toolbar. """ +AUI_TB_CLOCKWISE = 1 << 8 +AUI_TB_COUNTERCLOCKWISE = 1 << 9 + +AUI_TB_HORZ_TEXT = AUI_TB_HORZ_LAYOUT | AUI_TB_TEXT +""" Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT``. """ +AUI_TB_VERT_TEXT = AUI_TB_VERTICAL | AUI_TB_CLOCKWISE | AUI_TB_TEXT + +AUI_TB_DEFAULT_STYLE = 0 +""" `AuiToolBar` default style. """ + +# AuiToolBar settings +AUI_TBART_SEPARATOR_SIZE = 0 +""" Separator size in AuiToolBar. """ +AUI_TBART_GRIPPER_SIZE = 1 +""" Gripper size in AuiToolBar. """ +AUI_TBART_OVERFLOW_SIZE = 2 +""" Overflow button size in AuiToolBar. """ + +# AuiToolBar text orientation +AUI_TBTOOL_TEXT_LEFT = 0 # unused/unimplemented +""" Text in AuiToolBar items is aligned left. """ +AUI_TBTOOL_TEXT_RIGHT = 1 +""" Text in AuiToolBar items is aligned right. """ +AUI_TBTOOL_TEXT_TOP = 2 # unused/unimplemented +""" Text in AuiToolBar items is aligned top. """ +AUI_TBTOOL_TEXT_BOTTOM = 3 +""" Text in AuiToolBar items is aligned bottom. """ + +# AuiToolBar tool orientation +AUI_TBTOOL_HORIZONTAL = 0 # standard +AUI_TBTOOL_VERT_CLOCKWISE = 1 # rotation of 90 on the right +AUI_TBTOOL_VERT_COUNTERCLOCKWISE = 2 # rotation of 90 on the left + + +# --------------------- # +# - AuiMDI* Constants - # +# --------------------- # + +wxWINDOWCLOSE = 4001 +""" Identifier for the AuiMDI "close window" menu. """ +wxWINDOWCLOSEALL = 4002 +""" Identifier for the AuiMDI "close all windows" menu. """ +wxWINDOWNEXT = 4003 +""" Identifier for the AuiMDI "next window" menu. """ +wxWINDOWPREV = 4004 +""" Identifier for the AuiMDI "previous window" menu. """ + +# ----------------------------- # +# - AuiDockingGuide Constants - # +# ----------------------------- # + +colourTargetBorder = wx.Colour(180, 180, 180) +colourTargetShade = wx.Colour(206, 206, 206) +colourTargetBackground = wx.Colour(224, 224, 224) +colourIconBorder = wx.Colour(82, 65, 156) +colourIconBackground = wx.Colour(255, 255, 255) +colourIconDockingPart1 = wx.Colour(215, 228, 243) +colourIconDockingPart2 = wx.Colour(180, 201, 225) +colourIconShadow = wx.Colour(198, 198, 198) +colourIconArrow = wx.Colour(77, 79, 170) +colourHintBackground = wx.Colour(0, 64, 255) +guideSizeX, guideSizeY = 29, 32 +aeroguideSizeX, aeroguideSizeY = 31, 32 +whidbeySizeX, whidbeySizeY = 43, 30 + +# ------------------------------- # +# - AuiSwitcherDialog Constants - # +# ------------------------------- # + +SWITCHER_TEXT_MARGIN_X = 4 +SWITCHER_TEXT_MARGIN_Y = 1 diff --git a/aui/aui_switcherdialog.py b/aui/aui_switcherdialog.py new file mode 100644 index 0000000..552cb84 --- /dev/null +++ b/aui/aui_switcherdialog.py @@ -0,0 +1,1216 @@ +""" +Description +=========== + +The idea of `SwitcherDialog` is to make it easier to implement keyboard +navigation in AUI and other applications that have multiple panes and +tabs. + +A key combination with a modifier (such as ``Ctrl`` + ``Tab``) shows the +dialog, and the user holds down the modifier whilst navigating with +``Tab`` and arrow keys before releasing the modifier to dismiss the dialog +and activate the selected pane. + +The switcher dialog is a multi-column menu with no scrolling, implemented +by the `MultiColumnListCtrl` class. You can have headings for your items +for logical grouping, and you can force a column break if you need to. + +The modifier used for invoking and dismissing the dialog can be customised, +as can the colours, number of rows, and the key used for cycling through +the items. So you can use different keys on different platforms if +required (especially since ``Ctrl`` + ``Tab`` is reserved on some platforms). + +Items are shown as names and optional 16x16 images. + + +Base Functionalities +==================== + +To use the dialog, you set up the items in a `SwitcherItems` object, +before passing this to the `SwitcherDialog` instance. + +Call L{SwitcherItems.AddItem} and optionally L{SwitcherItems.AddGroup} to add items and headings. These +functions take a label (to be displayed to the user), an identifying name, +an integer id, and a bitmap. The name and id are purely for application-defined +identification. You may also set a description to be displayed when each +item is selected; and you can set a window pointer for convenience when +activating the desired window after the dialog returns. + +Have created the dialog, you call `ShowModal()`, and if the return value is +``wx.ID_OK``, retrieve the selection from the dialog and activate the pane. + +The sample code below shows a generic method of finding panes and notebook +tabs within the current L{AuiManager}, and using the pane name or notebook +tab position to display the pane. + +The only other code to add is a menu item with the desired accelerator, +whose modifier matches the one you pass to L{SwitcherDialog.SetModifierKey} +(the default being ``wx.WXK_CONTROL``). + + +Usage +===== + +Menu item:: + + if wx.Platform == "__WXMAC__": + switcherAccel = "Alt+Tab" + elif wx.Platform == "__WXGTK__": + switcherAccel = "Ctrl+/" + else: + switcherAccel = "Ctrl+Tab" + + view_menu.Append(ID_SwitchPane, _("S&witch Window...") + "\t" + switcherAccel) + + +Event handler:: + + def OnSwitchPane(self, event): + + items = SwitcherItems() + items.SetRowCount(12) + + # Add the main windows and toolbars, in two separate columns + # We'll use the item 'id' to store the notebook selection, or -1 if not a page + + for k in xrange(2): + if k == 0: + items.AddGroup(_("Main Windows"), "mainwindows") + else: + items.AddGroup(_("Toolbars"), "toolbars").BreakColumn() + + for pane in self._mgr.GetAllPanes(): + name = pane.name + caption = pane.caption + + toolbar = isinstance(info.window, wx.ToolBar) or isinstance(info.window, aui.AuiToolBar) + if caption and (toolBar and k == 1) or (not toolBar and k == 0): + items.AddItem(caption, name, -1).SetWindow(pane.window) + + # Now add the wxAuiNotebook pages + + items.AddGroup(_("Notebook Pages"), "pages").BreakColumn() + + for pane in self._mgr.GetAllPanes(): + nb = pane.window + if isinstance(nb, aui.AuiNotebook): + for j in xrange(nb.GetPageCount()): + + name = nb.GetPageText(j) + win = nb.GetPage(j) + + items.AddItem(name, name, j, nb.GetPageBitmap(j)).SetWindow(win) + + # Select the focused window + + idx = items.GetIndexForFocus() + if idx != wx.NOT_FOUND: + items.SetSelection(idx) + + if wx.Platform == "__WXMAC__": + items.SetBackgroundColour(wx.WHITE) + + # Show the switcher dialog + + dlg = SwitcherDialog(items, wx.GetApp().GetTopWindow()) + + # In GTK+ we can't use Ctrl+Tab; we use Ctrl+/ instead and tell the switcher + # to treat / in the same was as tab (i.e. cycle through the names) + + if wx.Platform == "__WXGTK__": + dlg.SetExtraNavigationKey(wxT('/')) + + if wx.Platform == "__WXMAC__": + dlg.SetBackgroundColour(wx.WHITE) + dlg.SetModifierKey(wx.WXK_ALT) + + ans = dlg.ShowModal() + + if ans == wx.ID_OK and dlg.GetSelection() != -1: + item = items.GetItem(dlg.GetSelection()) + + if item.GetId() == -1: + info = self._mgr.GetPane(item.GetName()) + info.Show() + self._mgr.Update() + info.window.SetFocus() + + else: + nb = item.GetWindow().GetParent() + win = item.GetWindow(); + if isinstance(nb, aui.AuiNotebook): + nb.SetSelection(item.GetId()) + win.SetFocus() + + +""" + +import wx + +import auibook +from aui_utilities import FindFocusDescendant +from aui_constants import SWITCHER_TEXT_MARGIN_X, SWITCHER_TEXT_MARGIN_Y + + +# Define a translation function +_ = wx.GetTranslation + + +class SwitcherItem(object): + """ An object containing information about one item. """ + + def __init__(self, item=None): + """ Default class constructor. """ + + self._id = 0 + self._isGroup = False + self._breakColumn = False + self._rowPos = 0 + self._colPos = 0 + self._window = None + self._description = "" + + self._textColour = wx.NullColour + self._bitmap = wx.NullBitmap + self._font = wx.NullFont + + if item: + self.Copy(item) + + + def Copy(self, item): + """ + Copy operator between 2 L{SwitcherItem} instances. + + :param `item`: another instance of L{SwitcherItem}. + """ + + self._id = item._id + self._name = item._name + self._title = item._title + self._isGroup = item._isGroup + self._breakColumn = item._breakColumn + self._rect = item._rect + self._font = item._font + self._textColour = item._textColour + self._bitmap = item._bitmap + self._description = item._description + self._rowPos = item._rowPos + self._colPos = item._colPos + self._window = item._window + + + def SetTitle(self, title): + + self._title = title + return self + + + def GetTitle(self): + + return self._title + + + def SetName(self, name): + + self._name = name + return self + + + def GetName(self): + + return self._name + + + def SetDescription(self, descr): + + self._description = descr + return self + + + def GetDescription(self): + + return self._description + + + def SetId(self, id): + + self._id = id + return self + + + def GetId(self): + + return self._id + + + def SetIsGroup(self, isGroup): + + self._isGroup = isGroup + return self + + + def GetIsGroup(self): + + return self._isGroup + + + def BreakColumn(self, breakCol=True): + + self._breakColumn = breakCol + return self + + + def GetBreakColumn(self): + + return self._breakColumn + + + def SetRect(self, rect): + + self._rect = rect + return self + + + def GetRect(self): + + return self._rect + + + def SetTextColour(self, colour): + + self._textColour = colour + return self + + + def GetTextColour(self): + + return self._textColour + + + def SetFont(self, font): + + self._font = font + return self + + + def GetFont(self): + + return self._font + + + def SetBitmap(self, bitmap): + + self._bitmap = bitmap + return self + + + def GetBitmap(self): + + return self._bitmap + + + def SetRowPos(self, pos): + + self._rowPos = pos + return self + + + def GetRowPos(self): + + return self._rowPos + + + def SetColPos(self, pos): + + self._colPos = pos + return self + + + def GetColPos(self): + + return self._colPos + + + def SetWindow(self, win): + + self._window = win + return self + + + def GetWindow(self): + + return self._window + + +class SwitcherItems(object): + """ An object containing switcher items. """ + + def __init__(self, items=None): + """ Default class constructor. """ + + self._selection = -1 + self._rowCount = 10 + self._columnCount = 0 + + self._backgroundColour = wx.NullColour + self._textColour = wx.NullColour + self._selectionColour = wx.NullColour + self._selectionOutlineColour = wx.NullColour + self._itemFont = wx.NullFont + + self._items = [] + + if wx.Platform == "__WXMSW__": + # If on Windows XP/Vista, use more appropriate colours + self.SetSelectionOutlineColour(wx.Colour(49, 106, 197)) + self.SetSelectionColour(wx.Colour(193, 210, 238)) + + if items: + self.Copy(items) + + + def Copy(self, items): + """ + Copy operator between 2 L{SwitcherItems}. + + :param `items`: another instance of L{SwitcherItems}. + """ + + self.Clear() + + for item in items._items: + self._items.append(item) + + self._selection = items._selection + self._rowCount = items._rowCount + self._columnCount = items._columnCount + + self._backgroundColour = items._backgroundColour + self._textColour = items._textColour + self._selectionColour = items._selectionColour + self._selectionOutlineColour = items._selectionOutlineColour + self._itemFont = items._itemFont + + + def AddItem(self, titleOrItem, name=None, id=0, bitmap=wx.NullBitmap): + + if isinstance(titleOrItem, SwitcherItem): + self._items.append(titleOrItem) + return self._items[-1] + + item = SwitcherItem() + item.SetTitle(titleOrItem) + item.SetName(name) + item.SetId(id) + item.SetBitmap(bitmap) + + self._items.append(item) + return self._items[-1] + + + def AddGroup(self, title, name, id=0, bitmap=wx.NullBitmap): + + item = self.AddItem(title, name, id, bitmap) + item.SetIsGroup(True) + + return item + + + def Clear(self): + + self._items = [] + + + def FindItemByName(self, name): + + for i in xrange(len(self._items)): + if self._items[i].GetName() == name: + return i + + return wx.NOT_FOUND + + + def FindItemById(self, id): + + for i in xrange(len(self._items)): + if self._items[i].GetId() == id: + return i + + return wx.NOT_FOUND + + + def SetSelection(self, sel): + + self._selection = sel + + + def SetSelectionByName(self, name): + + idx = self.FindItemByName(name) + if idx != wx.NOT_FOUND: + self.SetSelection(idx) + + + def GetSelection(self): + + return self._selection + + + def GetItem(self, i): + + return self._items[i] + + + def GetItemCount(self): + + return len(self._items) + + + def SetRowCount(self, rows): + + self._rowCount = rows + + + def GetRowCount(self): + + return self._rowCount + + + def SetColumnCount(self, cols): + + self._columnCount = cols + + + def GetColumnCount(self): + + return self._columnCount + + + def SetBackgroundColour(self, colour): + + self._backgroundColour = colour + + + def GetBackgroundColour(self): + + return self._backgroundColour + + + def SetTextColour(self, colour): + + self._textColour = colour + + + def GetTextColour(self): + + return self._textColour + + + def SetSelectionColour(self, colour): + + self._selectionColour = colour + + + def GetSelectionColour(self): + + return self._selectionColour + + + def SetSelectionOutlineColour(self, colour): + + self._selectionOutlineColour = colour + + + def GetSelectionOutlineColour(self): + + return self._selectionOutlineColour + + + def SetItemFont(self, font): + + self._itemFont = font + + + def GetItemFont(self): + + return self._itemFont + + + def PaintItems(self, dc, win): + + backgroundColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + standardTextColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + selectionColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + selectionOutlineColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont.SetWeight(wx.BOLD) + + if self.GetBackgroundColour().IsOk(): + backgroundColour = self.GetBackgroundColour() + + if self.GetTextColour().IsOk(): + standardTextColour = self.GetTextColour() + + if self.GetSelectionColour().IsOk(): + selectionColour = self.GetSelectionColour() + + if self.GetSelectionOutlineColour().IsOk(): + selectionOutlineColour = self.GetSelectionOutlineColour() + + if self.GetItemFont().IsOk(): + + standardFont = self.GetItemFont() + groupFont = wx.Font(standardFont.GetPointSize(), standardFont.GetFamily(), standardFont.GetStyle(), + wx.BOLD, standardFont.GetUnderlined(), standardFont.GetFaceName()) + + textMarginX = SWITCHER_TEXT_MARGIN_X + + dc.SetLogicalFunction(wx.COPY) + dc.SetBrush(wx.Brush(backgroundColour)) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangleRect(win.GetClientRect()) + dc.SetBackgroundMode(wx.TRANSPARENT) + + for i in xrange(len(self._items)): + item = self._items[i] + if i == self._selection: + dc.SetPen(wx.Pen(selectionOutlineColour)) + dc.SetBrush(wx.Brush(selectionColour)) + dc.DrawRectangleRect(item.GetRect()) + + clippingRect = wx.Rect(*item.GetRect()) + clippingRect.Deflate(1, 1) + + dc.SetClippingRect(clippingRect) + + if item.GetTextColour().IsOk(): + dc.SetTextForeground(item.GetTextColour()) + else: + dc.SetTextForeground(standardTextColour) + + if item.GetFont().IsOk(): + dc.SetFont(item.GetFont()) + else: + if item.GetIsGroup(): + dc.SetFont(groupFont) + else: + dc.SetFont(standardFont) + + w, h = dc.GetTextExtent(item.GetTitle()) + x = item.GetRect().x + + x += textMarginX + + if not item.GetIsGroup(): + if item.GetBitmap().IsOk() and item.GetBitmap().GetWidth() <= 16 \ + and item.GetBitmap().GetHeight() <= 16: + x -= textMarginX + dc.DrawBitmap(item.GetBitmap(), x, item.GetRect().y + \ + (item.GetRect().height - item.GetBitmap().GetHeight())/2, + True) + x += 16 + textMarginX + #x += textMarginX + + y = item.GetRect().y + (item.GetRect().height - h)/2 + dc.DrawText(item.GetTitle(), x, y) + dc.DestroyClippingRegion() + + + def CalculateItemSize(self, dc): + + # Start off allowing for an icon + sz = wx.Size(150, 16) + standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + groupFont.SetWeight(wx.BOLD) + + textMarginX = SWITCHER_TEXT_MARGIN_X + textMarginY = SWITCHER_TEXT_MARGIN_Y + maxWidth = 300 + maxHeight = 40 + + if self.GetItemFont().IsOk(): + standardFont = self.GetItemFont() + + for item in self._items: + if item.GetFont().IsOk(): + dc.SetFont(item.GetFont()) + else: + if item.GetIsGroup(): + dc.SetFont(groupFont) + else: + dc.SetFont(standardFont) + + w, h = dc.GetTextExtent(item.GetTitle()) + w += 16 + 2*textMarginX + + if w > sz.x: + sz.x = min(w, maxWidth) + if h > sz.y: + sz.y = min(h, maxHeight) + + if sz == wx.Size(16, 16): + sz = wx.Size(100, 25) + else: + sz.x += textMarginX*2 + sz.y += textMarginY*2 + + return sz + + + def GetIndexForFocus(self): + + for i, item in enumerate(self._items): + if item.GetWindow(): + + if FindFocusDescendant(item.GetWindow()): + return i + + return wx.NOT_FOUND + + +class MultiColumnListCtrl(wx.PyControl): + """ A control for displaying several columns (not scrollable). """ + + def __init__(self, parent, aui_manager, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=0, validator=wx.DefaultValidator, name="MultiColumnListCtrl"): + + wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name) + + self._overallSize = wx.Size(200, 100) + self._modifierKey = wx.WXK_CONTROL + self._extraNavigationKey = 0 + self._aui_manager = aui_manager + + self.SetInitialSize(size) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent) + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_KEY_DOWN, self.OnKey) + self.Bind(wx.EVT_KEY_UP, self.OnKey) + + + def __del__(self): + + self._aui_manager.HideHint() + + + def DoGetBestSize(self): + + return self._overallSize + + + def OnEraseBackground(self, event): + + pass + + + def OnPaint(self, event): + + dc = wx.AutoBufferedPaintDC(self) + rect = self.GetClientRect() + + if self._items.GetColumnCount() == 0: + self.CalculateLayout(dc) + + if self._items.GetColumnCount() == 0: + return + + self._items.PaintItems(dc, self) + + + def OnMouseEvent(self, event): + + if event.LeftDown(): + self.SetFocus() + + + def OnChar(self, event): + + event.Skip() + + + def OnKey(self, event): + + if event.GetEventType() == wx.wxEVT_KEY_UP: + if event.GetKeyCode() == self.GetModifierKey(): + topLevel = wx.GetTopLevelParent(self) + closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId()) + closeEvent.SetEventObject(topLevel) + closeEvent.SetCanVeto(False) + + topLevel.GetEventHandler().ProcessEvent(closeEvent) + return + + event.Skip() + return + + keyCode = event.GetKeyCode() + + if keyCode in [wx.WXK_ESCAPE, wx.WXK_RETURN]: + if keyCode == wx.WXK_ESCAPE: + self._items.SetSelection(-1) + + topLevel = wx.GetTopLevelParent(self) + closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId()) + closeEvent.SetEventObject(topLevel) + closeEvent.SetCanVeto(False) + + topLevel.GetEventHandler().ProcessEvent(closeEvent) + return + + elif keyCode in [wx.WXK_TAB, self.GetExtraNavigationKey()]: + if event.ShiftDown(): + + self._items.SetSelection(self._items.GetSelection() - 1) + if self._items.GetSelection() < 0: + self._items.SetSelection(self._items.GetItemCount() - 1) + + self.AdvanceToNextSelectableItem(-1) + + else: + + self._items.SetSelection(self._items.GetSelection() + 1) + if self._items.GetSelection() >= self._items.GetItemCount(): + self._items.SetSelection(0) + + self.AdvanceToNextSelectableItem(1) + + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN]: + self._items.SetSelection(self._items.GetSelection() + 1) + if self._items.GetSelection() >= self._items.GetItemCount(): + self._items.SetSelection(0) + + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_UP, wx.WXK_NUMPAD_UP]: + self._items.SetSelection(self._items.GetSelection() - 1) + if self._items.GetSelection() < 0: + self._items.SetSelection(self._items.GetItemCount() - 1) + + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_HOME, wx.WXK_NUMPAD_HOME]: + self._items.SetSelection(0) + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_END, wx.WXK_NUMPAD_END]: + self._items.SetSelection(self._items.GetItemCount() - 1) + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT]: + item = self._items.GetItem(self._items.GetSelection()) + + row = item.GetRowPos() + newCol = item.GetColPos() - 1 + if newCol < 0: + newCol = self._items.GetColumnCount() - 1 + + # Find the first item from the end whose row matches and whose column is equal or lower + for i in xrange(self._items.GetItemCount()-1, -1, -1): + item2 = self._items.GetItem(i) + if item2.GetColPos() == newCol and item2.GetRowPos() <= row: + self._items.SetSelection(i) + break + + self.AdvanceToNextSelectableItem(-1) + self.GenerateSelectionEvent() + self.Refresh() + + elif keyCode in [wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT]: + item = self._items.GetItem(self._items.GetSelection()) + + row = item.GetRowPos() + newCol = item.GetColPos() + 1 + if newCol >= self._items.GetColumnCount(): + newCol = 0 + + # Find the first item from the end whose row matches and whose column is equal or lower + for i in xrange(self._items.GetItemCount()-1, -1, -1): + item2 = self._items.GetItem(i) + if item2.GetColPos() == newCol and item2.GetRowPos() <= row: + self._items.SetSelection(i) + break + + self.AdvanceToNextSelectableItem(1) + self.GenerateSelectionEvent() + self.Refresh() + + else: + event.Skip() + + + def AdvanceToNextSelectableItem(self, direction): + + if self._items.GetItemCount() < 2: + return + + if self._items.GetSelection() == -1: + self._items.SetSelection(0) + + oldSel = self._items.GetSelection() + + while 1: + + if self._items.GetItem(self._items.GetSelection()).GetIsGroup(): + + self._items.SetSelection(self._items.GetSelection() + direction) + if self._items.GetSelection() == -1: + self._items.SetSelection(self._items.GetItemCount()-1) + elif self._items.GetSelection() == self._items.GetItemCount(): + self._items.SetSelection(0) + if self._items.GetSelection() == oldSel: + break + + else: + break + + self.SetTransparency() + selection = self._items.GetItem(self._items.GetSelection()).GetWindow() + pane = self._aui_manager.GetPane(selection) + + if not pane.IsOk(): + if isinstance(selection.GetParent(), auibook.AuiNotebook): + self.SetTransparency(selection) + self._aui_manager.ShowHint(selection.GetScreenRect()) + wx.CallAfter(self.SetFocus) + self.SetFocus() + return + else: + self._aui_manager.HideHint() + return + if not pane.IsShown(): + self._aui_manager.HideHint() + return + + self.SetTransparency(selection) + self._aui_manager.ShowHint(selection.GetScreenRect()) + # NOTE: this is odd but it is the only way for the focus to + # work correctly on wxMac... + wx.CallAfter(self.SetFocus) + self.SetFocus() + + + def SetTransparency(self, selection=None): + + if not self.GetParent().CanSetTransparent(): + return + + if selection is not None: + intersects = False + if selection.GetScreenRect().Intersects(self.GetParent().GetScreenRect()): + intersects = True + self.GetParent().SetTransparent(200) + return + + self.GetParent().SetTransparent(255) + + + def GenerateSelectionEvent(self): + + event = wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_SELECTED, self.GetId()) + event.SetEventObject(self) + event.SetInt(self._items.GetSelection()) + self.GetEventHandler().ProcessEvent(event) + + + def CalculateLayout(self, dc=None): + + if dc is None: + dc = wx.ClientDC(self) + + if self._items.GetSelection() == -1: + self._items.SetSelection(0) + + columnCount = 1 + + # Spacing between edge of window or between columns + xMargin = 4 + yMargin = 4 + + # Inter-row spacing + rowSpacing = 2 + + itemSize = self._items.CalculateItemSize(dc) + self._overallSize = wx.Size(350, 200) + + currentRow = 0 + x = xMargin + y = yMargin + + breaking = False + i = 0 + + while 1: + + oldOverallSize = self._overallSize + item = self._items.GetItem(i) + + item.SetRect(wx.Rect(x, y, itemSize.x, itemSize.y)) + item.SetColPos(columnCount-1) + item.SetRowPos(currentRow) + + if item.GetRect().GetBottom() > self._overallSize.y: + self._overallSize.y = item.GetRect().GetBottom() + yMargin + + if item.GetRect().GetRight() > self._overallSize.x: + self._overallSize.x = item.GetRect().GetRight() + xMargin + + currentRow += 1 + + y += rowSpacing + itemSize.y + stopBreaking = breaking + + if currentRow > self._items.GetRowCount() or (item.GetBreakColumn() and not breaking and currentRow != 1): + currentRow = 0 + columnCount += 1 + x += xMargin + itemSize.x + y = yMargin + + # Make sure we don't orphan a group + if item.GetIsGroup() or (item.GetBreakColumn() and not breaking): + self._overallSize = oldOverallSize + + if item.GetBreakColumn(): + breaking = True + + # Repeat the last item, in the next column + i -= 1 + + if stopBreaking: + breaking = False + + i += 1 + + if i >= self._items.GetItemCount(): + break + + self._items.SetColumnCount(columnCount) + self.InvalidateBestSize() + + + def SetItems(self, items): + + self._items = items + + + def GetItems(self): + + return self._items + + + def SetExtraNavigationKey(self, keyCode): + """ + Set an extra key that can be used to cycle through items, + in case not using the ``Ctrl`` + ``Tab`` combination. + """ + + self._extraNavigationKey = keyCode + + + def GetExtraNavigationKey(self): + + return self._extraNavigationKey + + + def SetModifierKey(self, modifierKey): + """ + Set the modifier used to invoke the dialog, and therefore to test for + release. + """ + + self._modifierKey = modifierKey + + + def GetModifierKey(self): + + return self._modifierKey + + + +class SwitcherDialog(wx.Dialog): + """ + SwitcherDialog shows a L{MultiColumnListCtrl} with a list of panes + and tabs for the user to choose. ``Ctrl`` + ``Tab`` cycles through them. + """ + + def __init__(self, items, parent, aui_manager, id=wx.ID_ANY, title=_("Pane Switcher"), pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.STAY_ON_TOP|wx.DIALOG_NO_PARENT|wx.BORDER_SIMPLE): + """ Default class constructor. """ + + self._switcherBorderStyle = (style & wx.BORDER_MASK) + if self._switcherBorderStyle == wx.BORDER_NONE: + self._switcherBorderStyle = wx.BORDER_SIMPLE + + style &= wx.BORDER_MASK + style |= wx.BORDER_NONE + + wx.Dialog.__init__(self, parent, id, title, pos, size, style) + + self._listCtrl = MultiColumnListCtrl(self, aui_manager, + style=wx.WANTS_CHARS|wx.NO_BORDER) + self._listCtrl.SetItems(items) + self._listCtrl.CalculateLayout() + + self._descriptionCtrl = wx.html.HtmlWindow(self, size=(-1, 100), style=wx.BORDER_NONE) + self._descriptionCtrl.SetBackgroundColour(self.GetBackgroundColour()) + + if wx.Platform == "__WXGTK__": + fontSize = 11 + self._descriptionCtrl.SetStandardFonts(fontSize) + + sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(sizer) + sizer.Add(self._listCtrl, 1, wx.ALL|wx.EXPAND, 10) + sizer.Add(self._descriptionCtrl, 0, wx.ALL|wx.EXPAND, 10) + sizer.SetSizeHints(self) + + self._listCtrl.SetFocus() + + self.Centre(wx.BOTH) + + if self._listCtrl.GetItems().GetSelection() == -1: + self._listCtrl.GetItems().SetSelection(0) + + self._listCtrl.AdvanceToNextSelectableItem(1) + + self.ShowDescription(self._listCtrl.GetItems().GetSelection()) + + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.Bind(wx.EVT_ACTIVATE, self.OnActivate) + self.Bind(wx.EVT_LISTBOX, self.OnSelectItem) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + # Attributes + self._closing = False + if wx.Platform == "__WXMSW__": + self._borderColour = wx.Colour(49, 106, 197) + else: + self._borderColour = wx.BLACK + + self._aui_manager = aui_manager + + + def OnCloseWindow(self, event): + + if self._closing: + return + + if self.IsModal(): + self._closing = True + + if self.GetSelection() == -1: + self.EndModal(wx.ID_CANCEL) + else: + self.EndModal(wx.ID_OK) + + self._aui_manager.HideHint() + + + def GetSelection(self): + + return self._listCtrl.GetItems().GetSelection() + + + def OnActivate(self, event): + + if not event.GetActive(): + if not self._closing: + self._closing = True + self.EndModal(wx.ID_CANCEL) + + + def OnPaint(self, event): + + dc = wx.PaintDC(self) + + if self._switcherBorderStyle == wx.BORDER_SIMPLE: + + dc.SetPen(wx.Pen(self._borderColour)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + rect = self.GetClientRect() + dc.DrawRectangleRect(rect) + + # Draw border around the HTML control + rect = wx.Rect(*self._descriptionCtrl.GetRect()) + rect.Inflate(1, 1) + dc.DrawRectangleRect(rect) + + + def OnSelectItem(self, event): + + self.ShowDescription(event.GetSelection()) + + +# Convert a colour to a 6-digit hex string + def ColourToHexString(self, col): + + hx = '%02x%02x%02x' % tuple([int(c) for c in col]) + return hx + + + def ShowDescription(self, i): + + item = self._listCtrl.GetItems().GetItem(i) + colour = self._listCtrl.GetItems().GetBackgroundColour() + + if not colour.IsOk(): + colour = self.GetBackgroundColour() + + backgroundColourHex = self.ColourToHexString(colour) + html = _("") + item.GetTitle() + _("") + + if item.GetDescription(): + html += _("

") + html += item.GetDescription() + + html += _("") + self._descriptionCtrl.SetPage(html) + + + def SetExtraNavigationKey(self, keyCode): + + self._extraNavigationKey = keyCode + if self._listCtrl: + self._listCtrl.SetExtraNavigationKey(keyCode) + + + def GetExtraNavigationKey(self): + + return self._extraNavigationKey + + + def SetModifierKey(self, modifierKey): + + self._modifierKey = modifierKey + if self._listCtrl: + self._listCtrl.SetModifierKey(modifierKey) + + + def GetModifierKey(self): + + return self._modifierKey + + + def SetBorderColour(self, colour): + + self._borderColour = colour + + \ No newline at end of file diff --git a/aui/aui_utilities.py b/aui/aui_utilities.py new file mode 100644 index 0000000..d6c701a --- /dev/null +++ b/aui/aui_utilities.py @@ -0,0 +1,678 @@ +""" +This module contains some common functions used by wxPython-AUI to +manipulate colours, bitmaps, text, gradient shadings and custom +dragging images for AuiNotebook tabs. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx + +from aui_constants import * + + +if wx.Platform == "__WXMAC__": + import Carbon.Appearance + + +def BlendColour(fg, bg, alpha): + """ + Blends the two colour component `fg` and `bg` into one colour component, adding + an optional alpha channel. + + :param `fg`: the first colour component; + :param `bg`: the second colour component; + :param `alpha`: an optional transparency value. + """ + + result = bg + (alpha*(fg - bg)) + + if result < 0.0: + result = 0.0 + if result > 255: + result = 255 + + return result + + +def StepColour(c, ialpha): + """ + Darken/lighten the input colour `c`. + + :param `c`: a colour to darken/lighten; + :param `ialpha`: a transparency value. + """ + + if ialpha == 100: + return c + + r, g, b = c.Red(), c.Green(), c.Blue() + + # ialpha is 0..200 where 0 is completely black + # and 200 is completely white and 100 is the same + # convert that to normal alpha 0.0 - 1.0 + ialpha = min(ialpha, 200) + ialpha = max(ialpha, 0) + alpha = (ialpha - 100.0)/100.0 + + if ialpha > 100: + + # blend with white + bg = 255 + alpha = 1.0 - alpha # 0 = transparent fg 1 = opaque fg + + else: + + # blend with black + bg = 0 + alpha = 1.0 + alpha # 0 = transparent fg 1 = opaque fg + + r = BlendColour(r, bg, alpha) + g = BlendColour(g, bg, alpha) + b = BlendColour(b, bg, alpha) + + return wx.Colour(r, g, b) + + +def LightContrastColour(c): + """ + Creates a new, lighter colour based on the input colour `c`. + + :param `c`: the input colour to analyze. + """ + + amount = 120 + + # if the colour is especially dark, then + # make the contrast even lighter + if c.Red() < 128 and c.Green() < 128 and c.Blue() < 128: + amount = 160 + + return StepColour(c, amount) + + +def ChopText(dc, text, max_size): + """ + Chops the input `text` if its size does not fit in `max_size`, by cutting the + text and adding ellipsis at the end. + + :param `dc`: a `wx.DC` device context; + :param `text`: the text to chop; + :param `max_size`: the maximum size in which the text should fit. + """ + + # first check if the text fits with no problems + x, y, dummy = dc.GetMultiLineTextExtent(text) + + if x <= max_size: + return text + + textLen = len(text) + last_good_length = 0 + + for i in xrange(textLen, -1, -1): + s = text[0:i] + s += "..." + + x, y = dc.GetTextExtent(s) + last_good_length = i + + if x < max_size: + break + + ret = text[0:last_good_length] + "..." + return ret + + +def BitmapFromBits(bits, w, h, colour): + """ + BitmapFromBits() is a utility function that creates a + masked bitmap from raw bits (XBM format). + + :param `bits`: a string containing the raw bits of the bitmap; + :param `w`: the bitmap width; + :param `h`: the bitmap height; + :param `colour`: the colour which will replace all white pixels in the + raw bitmap. + """ + + img = wx.BitmapFromBits(bits, w, h).ConvertToImage() + img.Replace(0, 0, 0, 123, 123, 123) + img.Replace(255, 255, 255, colour.Red(), colour.Green(), colour.Blue()) + img.SetMaskColour(123, 123, 123) + return wx.BitmapFromImage(img) + + +def IndentPressedBitmap(rect, button_state): + """ + Indents the input rectangle `rect` based on the value of `button_state`. + + :param `rect`: an instance of wx.Rect; + :param `button_state`: an L{AuiNotebook} button state. + """ + + if button_state == AUI_BUTTON_STATE_PRESSED: + rect.x += 1 + rect.y += 1 + + return rect + + +def GetBaseColour(): + """ + Returns the face shading colour on push buttons/backgrounds, mimicking as closely + as possible the platform UI colours. + """ + + if wx.Platform == "__WXMAC__": + + if hasattr(wx, 'MacThemeColour'): + base_colour = wx.MacThemeColour(Carbon.Appearance.kThemeBrushToolbarBackground) + else: + brush = wx.Brush(wx.BLACK) + brush.MacSetTheme(Carbon.Appearance.kThemeBrushToolbarBackground) + base_colour = brush.GetColour() + + else: + + base_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + + # the base_colour is too pale to use as our base colour, + # so darken it a bit + if ((255-base_colour.Red()) + + (255-base_colour.Green()) + + (255-base_colour.Blue()) < 60): + + base_colour = StepColour(base_colour, 92) + + return base_colour + + +def MakeDisabledBitmap(bitmap): + """ + Convert the given image (in place) to a grayed-out version, + appropriate for a 'disabled' appearance. + + :param `bitmap`: the bitmap to gray-out. + """ + + anImage = bitmap.ConvertToImage() + factor = 0.7 # 0 < f < 1. Higher Is Grayer + + if anImage.HasMask(): + maskColour = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) + else: + maskColour = None + + data = map(ord, list(anImage.GetData())) + + for i in range(0, len(data), 3): + + pixel = (data[i], data[i+1], data[i+2]) + pixel = MakeGray(pixel, factor, maskColour) + + for x in range(3): + data[i+x] = pixel[x] + + anImage.SetData(''.join(map(chr, data))) + + return anImage.ConvertToBitmap() + + +def MakeGray(rgbTuple, factor, maskColour): + """ + Make a pixel grayed-out. If the pixel matches the `maskColour`, it won't be + changed. + + :param `rgbTuple`: a tuple representing a pixel colour; + :param `factor`: a graying-out factor; + :param `maskColour`: a colour mask. + """ + + if rgbTuple != maskColour: + r, g, b = rgbTuple + return map(lambda x: int((230 - x) * factor) + x, (r, g, b)) + else: + return rgbTuple + + +def Clip(a, b, c): + """ + Clips the value in `a` based on the extremes `b` and `c`. + + :param `a`: the value to analyze; + :param `b`: a minimum value; + :param `c`: a maximum value. + """ + + return ((a < b and [b]) or [(a > c and [c] or [a])[0]])[0] + + +def LightColour(colour, percent): + """ + Brighten input `colour` by `percent`. + + :param `colour`: the colour to be brightened; + :param `percent`: brightening percentage. + """ + + end_colour = wx.WHITE + + rd = end_colour.Red() - colour.Red() + gd = end_colour.Green() - colour.Green() + bd = end_colour.Blue() - colour.Blue() + + high = 100 + + # We take the percent way of the colour from colour -. white + i = percent + r = colour.Red() + ((i*rd*100)/high)/100 + g = colour.Green() + ((i*gd*100)/high)/100 + b = colour.Blue() + ((i*bd*100)/high)/100 + return wx.Colour(r, g, b) + + +def PaneCreateStippleBitmap(): + """ + Creates a stipple bitmap to be used in a `wx.Brush`. + This is used to draw sash resize hints. + """ + + data = [0, 0, 0, 192, 192, 192, 192, 192, 192, 0, 0, 0] + img = wx.EmptyImage(2, 2) + counter = 0 + + for ii in xrange(2): + for jj in xrange(2): + img.SetRGB(ii, jj, data[counter], data[counter+1], data[counter+2]) + counter = counter + 3 + + return img.ConvertToBitmap() + + +def DrawMACCloseButton(colour, backColour=None): + """ + Draws the wxMAC tab close button using `wx.GraphicsContext`. + + :param `colour`: the colour to use to draw the circle; + :param `backColour`: the optional background colour for the circle. + """ + + bmp = wx.EmptyBitmapRGBA(16, 16) + dc = wx.MemoryDC() + dc.SelectObject(bmp) + + gc = wx.GraphicsContext.Create(dc) + gc.SetBrush(wx.Brush(colour)) + path = gc.CreatePath() + path.AddCircle(6.5, 7, 6.5) + path.CloseSubpath() + gc.FillPath(path) + + path = gc.CreatePath() + if backColour is not None: + pen = wx.Pen(backColour, 2) + else: + pen = wx.Pen("white", 2) + + pen.SetCap(wx.CAP_BUTT) + pen.SetJoin(wx.JOIN_BEVEL) + gc.SetPen(pen) + path.MoveToPoint(3.5, 4) + path.AddLineToPoint(9.5, 10) + path.MoveToPoint(3.5, 10) + path.AddLineToPoint(9.5, 4) + path.CloseSubpath() + gc.DrawPath(path) + + dc.SelectObject(wx.NullBitmap) + return bmp + + +def DarkenBitmap(bmp, caption_colour, new_colour): + """ + Darkens the input bitmap on wxMAC using the input colour. + + :param `bmp`: the bitmap to be manipulated; + :param `caption_colour`: the colour of the pane caption; + :param `new_colour`: the colour used to darken the bitmap. + """ + + image = bmp.ConvertToImage() + red = caption_colour.Red()/float(new_colour.Red()) + green = caption_colour.Green()/float(new_colour.Green()) + blue = caption_colour.Blue()/float(new_colour.Blue()) + image = image.AdjustChannels(red, green, blue) + return image.ConvertToBitmap() + + +def DrawGradientRectangle(dc, rect, start_colour, end_colour, direction, offset=0, length=0): + """ + Draws a gradient-shaded rectangle. + + :param `dc`: a `wx.DC` device context; + :param `rect`: the rectangle in which to draw the gradient; + :param `start_colour`: the first colour of the gradient; + :param `end_colour`: the second colour of the gradient; + :param `direction`: the gradient direction (horizontal or vertical). + """ + + if direction == AUI_GRADIENT_VERTICAL: + dc.GradientFillLinear(rect, start_colour, end_colour, wx.SOUTH) + else: + dc.GradientFillLinear(rect, start_colour, end_colour, wx.EAST) + + +def FindFocusDescendant(ancestor): + """ + Find a window with the focus, that is also a descendant of the given window. + This is used to determine the window to initially send commands to. + + :param `ancestor`: the window to check for ancestry. + """ + + # Process events starting with the window with the focus, if any. + focusWin = wx.Window.FindFocus() + win = focusWin + + # Check if this is a descendant of this frame. + # If not, win will be set to NULL. + while win: + if win == ancestor: + break + else: + win = win.GetParent() + + if win is None: + focusWin = None + + return focusWin + + +def GetLabelSize(dc, label, vertical): + """ + Returns the L{AuiToolBar} item label size. + + :param `label`: the toolbar tool label; + :param `vertical`: whether the toolbar tool orientation is vertical or not. + """ + + text_width = text_height = 0 + + # get the text height + dummy, text_height = dc.GetTextExtent("ABCDHgj") + # get the text width + if label.strip(): + text_width, dummy = dc.GetTextExtent(label) + + if vertical: + tmp = text_height + text_height = text_width + text_width = tmp + + return wx.Size(text_width, text_height) + + +#--------------------------------------------------------------------------- +# TabDragImage implementation +# This class handles the creation of a custom image when dragging +# AuiNotebook tabs +#--------------------------------------------------------------------------- + +class TabDragImage(wx.DragImage): + """ + This class handles the creation of a custom image in case of drag and + drop of a notebook tab. + """ + + def __init__(self, notebook, page, button_state, tabArt): + """ + Default class constructor. + + For internal use: do not call it in your code! + + :param `notebook`: an instance of L{AuiNotebook}; + :param `page`: the dragged L{AuiNotebook} page; + :param `button_state`: the state of the close button on the tab; + :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations. + """ + + self._backgroundColour = wx.NamedColour("pink") + self._bitmap = self.CreateBitmap(notebook, page, button_state, tabArt) + wx.DragImage.__init__(self, self._bitmap) + + + def CreateBitmap(self, notebook, page, button_state, tabArt): + """ + Actually creates the drag and drop bitmap. + + :param `notebook`: an instance of L{AuiNotebook}; + :param `page`: the dragged L{AuiNotebook} page; + :param `button_state`: the state of the close button on the tab; + :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations. + """ + + control = page.control + memory = wx.MemoryDC(wx.EmptyBitmap(1, 1)) + + tab_size, x_extent = tabArt.GetTabSize(memory, notebook, page.caption, page.bitmap, page.active, + button_state, control) + + tab_width, tab_height = tab_size + rect = wx.Rect(0, 0, tab_width, tab_height) + + bitmap = wx.EmptyBitmap(tab_width+1, tab_height+1) + memory.SelectObject(bitmap) + + if wx.Platform == "__WXMAC__": + memory.SetBackground(wx.TRANSPARENT_BRUSH) + else: + memory.SetBackground(wx.Brush(self._backgroundColour)) + + memory.SetBackgroundMode(wx.TRANSPARENT) + memory.Clear() + + paint_control = wx.Platform != "__WXMAC__" + tabArt.DrawTab(memory, notebook, page, rect, button_state, paint_control=paint_control) + + memory.SetBrush(wx.TRANSPARENT_BRUSH) + memory.SetPen(wx.BLACK_PEN) + memory.DrawRoundedRectangle(0, 0, tab_width+1, tab_height+1, 2) + + memory.SelectObject(wx.NullBitmap) + + # Gtk and Windows unfortunatly don't do so well with transparent + # drawing so this hack corrects the image to have a transparent + # background. + if wx.Platform != '__WXMAC__': + timg = bitmap.ConvertToImage() + if not timg.HasAlpha(): + timg.InitAlpha() + for y in xrange(timg.GetHeight()): + for x in xrange(timg.GetWidth()): + pix = wx.Colour(timg.GetRed(x, y), + timg.GetGreen(x, y), + timg.GetBlue(x, y)) + if pix == self._backgroundColour: + timg.SetAlpha(x, y, 0) + bitmap = timg.ConvertToBitmap() + return bitmap + + +def GetDockingImage(direction, useAero, center): + """ + Returns the correct name of the docking bitmap depending on the input parameters. + + :param `useAero`: whether L{AuiManager} is using Aero-style or Whidbey-style docking + images or not; + :param `center`: whether we are looking for the center diamond-shaped bitmap or not. + """ + + suffix = (center and [""] or ["_single"])[0] + prefix = "" + if useAero == 2: + # Whidbey docking guides + prefix = "whidbey_" + elif useAero == 1: + # Aero docking style + prefix = "aero_" + + if direction == wx.TOP: + bmp_unfocus = eval("%sup%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sup_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.BOTTOM: + bmp_unfocus = eval("%sdown%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sdown_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.LEFT: + bmp_unfocus = eval("%sleft%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sleft_focus%s"%(prefix, suffix)).GetBitmap() + elif direction == wx.RIGHT: + bmp_unfocus = eval("%sright%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%sright_focus%s"%(prefix, suffix)).GetBitmap() + else: + bmp_unfocus = eval("%stab%s"%(prefix, suffix)).GetBitmap() + bmp_focus = eval("%stab_focus%s"%(prefix, suffix)).GetBitmap() + + return bmp_unfocus, bmp_focus + + +def TakeScreenShot(rect): + """ + Takes a screenshot of the screen at given position and size (rect). + + :param `rect`: the screen rectangle for which we want to take a screenshot. + """ + + # Create a DC for the whole screen area + dcScreen = wx.ScreenDC() + + # Create a Bitmap that will later on hold the screenshot image + # Note that the Bitmap must have a size big enough to hold the screenshot + # -1 means using the current default colour depth + bmp = wx.EmptyBitmap(rect.width, rect.height) + + # Create a memory DC that will be used for actually taking the screenshot + memDC = wx.MemoryDC() + + # Tell the memory DC to use our Bitmap + # all drawing action on the memory DC will go to the Bitmap now + memDC.SelectObject(bmp) + + # Blit (in this case copy) the actual screen on the memory DC + # and thus the Bitmap + memDC.Blit( 0, # Copy to this X coordinate + 0, # Copy to this Y coordinate + rect.width, # Copy this width + rect.height, # Copy this height + dcScreen, # From where do we copy? + rect.x, # What's the X offset in the original DC? + rect.y # What's the Y offset in the original DC? + ) + + # Select the Bitmap out of the memory DC by selecting a new + # uninitialized Bitmap + memDC.SelectObject(wx.NullBitmap) + + return bmp + + +def RescaleScreenShot(bmp, thumbnail_size=200): + """ + Rescales a bitmap to be 300 pixels wide (or tall) at maximum. + + :param `bmp`: the bitmap to rescale; + :param `thumbnail_size`: the maximum size of every page thumbnail. + """ + + bmpW, bmpH = bmp.GetWidth(), bmp.GetHeight() + img = bmp.ConvertToImage() + + newW, newH = bmpW, bmpH + + if bmpW > bmpH: + if bmpW > thumbnail_size: + ratio = bmpW/float(thumbnail_size) + newW, newH = int(bmpW/ratio), int(bmpH/ratio) + img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH) + else: + if bmpH > thumbnail_size: + ratio = bmpH/float(thumbnail_size) + newW, newH = int(bmpW/ratio), int(bmpH/ratio) + img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH) + + newBmp = img.ConvertToBitmap() + otherBmp = wx.EmptyBitmap(newW+5, newH+5) + + memDC = wx.MemoryDC() + memDC.SelectObject(otherBmp) + memDC.SetBackground(wx.WHITE_BRUSH) + memDC.Clear() + + memDC.SetPen(wx.TRANSPARENT_PEN) + + pos = 0 + for i in xrange(5, 0, -1): + brush = wx.Brush(wx.Colour(50*i, 50*i, 50*i)) + memDC.SetBrush(brush) + memDC.DrawRoundedRectangle(0, 0, newW+5-pos, newH+5-pos, 2) + pos += 1 + + memDC.DrawBitmap(newBmp, 0, 0, True) + + # Select the Bitmap out of the memory DC by selecting a new + # uninitialized Bitmap + memDC.SelectObject(wx.NullBitmap) + + return otherBmp + + +def GetSlidingPoints(rect, size, direction): + """ + Returns the point at which the sliding in and out of a minimized pane begins. + + :param `rect`: the L{AuiToolBar} tool screen rectangle; + :param `size`: the pane window size; + :param `direction`: the pane docking direction. + """ + + if direction == AUI_DOCK_LEFT: + startX, startY = rect.x + rect.width + 2, rect.y + elif direction == AUI_DOCK_TOP: + startX, startY = rect.x, rect.y + rect.height + 2 + elif direction == AUI_DOCK_RIGHT: + startX, startY = rect.x - size.x - 2, rect.y + elif direction == AUI_DOCK_BOTTOM: + startX, startY = rect.x, rect.y - size.y - 2 + else: + raise Exception("How did we get here?") + + caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y) + frame_border_x = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_X) + frame_border_y = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_Y) + + stopX = size.x + caption_height + frame_border_x + stopY = size.x + frame_border_y + + return startX, startY, stopX, stopY + + +def CopyAttributes(newArt, oldArt): + """ + Copies pens, brushes, colours and fonts from the old tab art to the new one. + + :param `newArt`: the new instance of L{AuiDefaultTabArt}; + :param `oldArt`: the old instance of L{AuiDefaultTabArt}. + """ + + attrs = dir(oldArt) + + for attr in attrs: + if attr.startswith("_") and (attr.endswith("_colour") or attr.endswith("_font") or \ + attr.endswith("_font") or attr.endswith("_brush") or \ + attr.endswith("Pen") or attr.endswith("_pen")): + setattr(newArt, attr, getattr(oldArt, attr)) + + return newArt + diff --git a/aui/auibar.py b/aui/auibar.py new file mode 100644 index 0000000..88bb509 --- /dev/null +++ b/aui/auibar.py @@ -0,0 +1,3926 @@ +""" +auibar contains an implementation of L{AuiToolBar}, which is a completely owner-drawn +toolbar perfectly integrated with the AUI layout system. This allows drag and drop of +toolbars, docking/floating behaviour and the possibility to define "overflow" items +in the toolbar itself. + +The default theme that is used is L{AuiDefaultToolBarArt}, which provides a modern, +glossy look and feel. The theme can be changed by calling L{AuiToolBar.SetArtProvider}. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +import types + +from aui_utilities import BitmapFromBits, StepColour, GetLabelSize +from aui_utilities import GetBaseColour, MakeDisabledBitmap + +import framemanager +from aui_constants import * + +# wxPython version string +_VERSION_STRING = wx.VERSION_STRING + +# AuiToolBar events +wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK = wx.NewEventType() +wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG = wx.NewEventType() + +EVT_AUITOOLBAR_TOOL_DROPDOWN = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, 1) +""" A dropdown `AuiToolBarItem` is being shown. """ +EVT_AUITOOLBAR_OVERFLOW_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK, 1) +""" The user left-clicked on the overflow button in `AuiToolBar`. """ +EVT_AUITOOLBAR_RIGHT_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, 1) +""" Fires an event when the user right-clicks on a `AuiToolBarItem`. """ +EVT_AUITOOLBAR_MIDDLE_CLICK = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK, 1) +""" Fires an event when the user middle-clicks on a `AuiToolBarItem`. """ +EVT_AUITOOLBAR_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG, 1) +""" A drag operation involving a toolbar item has started. """ + +# ---------------------------------------------------------------------- + +class CommandToolBarEvent(wx.PyCommandEvent): + """ A specialized command event class for events sent by L{AuiToolBar}. """ + + def __init__(self, command_type, win_id): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + if type(command_type) == types.IntType: + wx.PyCommandEvent.__init__(self, command_type, win_id) + else: + wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId()) + + self.is_dropdown_clicked = False + self.click_pt = wx.Point(-1, -1) + self.rect = wx.Rect(-1, -1, 0, 0) + self.tool_id = -1 + + + def IsDropDownClicked(self): + """ Returns whether the drop down menu has been clicked. """ + + return self.is_dropdown_clicked + + + def SetDropDownClicked(self, c): + """ + Sets whether the drop down menu has been clicked. + + :param `c`: ``True`` to set the drop down as clicked, ``False`` otherwise. + """ + + self.is_dropdown_clicked = c + + + def GetClickPoint(self): + """ Returns the point where the user clicked with the mouse. """ + + return self.click_pt + + + def SetClickPoint(self, p): + """ + Sets the clicking point. + + :param `p`: a `wx.Point` object. + """ + + self.click_pt = p + + + def GetItemRect(self): + """ Returns the L{AuiToolBarItem} rectangle. """ + + return self.rect + + + def SetItemRect(self, r): + """ + Sets the L{AuiToolBarItem} rectangle. + + :param `r`: an instance of `wx.Rect`. + """ + + self.rect = r + + + def GetToolId(self): + """ Returns the L{AuiToolBarItem} identifier. """ + + return self.tool_id + + + def SetToolId(self, id): + """ + Sets the L{AuiToolBarItem} identifier. + + :param `id`: the toolbar item identifier. + """ + + self.tool_id = id + + +# ---------------------------------------------------------------------- + +class AuiToolBarEvent(CommandToolBarEvent): + """ A specialized command event class for events sent by L{AuiToolBar}. """ + + def __init__(self, command_type=None, win_id=0): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + CommandToolBarEvent.__init__(self, command_type, win_id) + + if type(command_type) == types.IntType: + self.notify = wx.NotifyEvent(command_type, win_id) + else: + self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId()) + + + def GetNotifyEvent(self): + """ Returns the actual `wx.NotifyEvent`. """ + + return self.notify + + + def IsAllowed(self): + """ Returns whether the event is allowed or not. """ + + return self.notify.IsAllowed() + + + def Veto(self): + """ + Prevents the change announced by this event from happening. + + It is in general a good idea to notify the user about the reasons for + vetoing the change because otherwise the applications behaviour (which + just refuses to do what the user wants) might be quite surprising. + """ + + self.notify.Veto() + + + def Allow(self): + """ + This is the opposite of L{Veto}: it explicitly allows the event to be + processed. For most events it is not necessary to call this method as the + events are allowed anyhow but some are forbidden by default (this will + be mentioned in the corresponding event description). + """ + + self.notify.Allow() + + +# ---------------------------------------------------------------------- + +class ToolbarCommandCapture(wx.PyEvtHandler): + """ A class to handle the dropdown window menu. """ + + def __init__(self): + """ Default class constructor. """ + + wx.PyEvtHandler.__init__(self) + self._last_id = 0 + + + def GetCommandId(self): + """ Returns the event command identifier. """ + + return self._last_id + + + def ProcessEvent(self, event): + """ + Processes an event, searching event tables and calling zero or more suitable + event handler function(s). + + :param `event`: the event to process. + + :note: Normally, your application would not call this function: it is called + in the wxPython implementation to dispatch incoming user interface events + to the framework (and application). + However, you might need to call it if implementing new functionality (such as + a new control) where you define new event types, as opposed to allowing the + user to override functions. + + An instance where you might actually override the L{ProcessEvent} function is where + you want to direct event processing to event handlers not normally noticed by + wxPython. For example, in the document/view architecture, documents and views + are potential event handlers. When an event reaches a frame, L{ProcessEvent} will + need to be called on the associated document and view in case event handler + functions are associated with these objects. + + The normal order of event table searching is as follows: + + 1. If the object is disabled (via a call to `SetEvtHandlerEnabled`) the function + skips to step (6). + 2. If the object is a `wx.Window`, L{ProcessEvent} is recursively called on the window's + `wx.Validator`. If this returns ``True``, the function exits. + 3. wxWidgets `SearchEventTable` is called for this event handler. If this fails, the + base class table is tried, and so on until no more tables exist or an appropriate + function was found, in which case the function exits. + 4. The search is applied down the entire chain of event handlers (usually the chain + has a length of one). If this succeeds, the function exits. + 5. If the object is a `wx.Window` and the event is a `wx.CommandEvent`, L{ProcessEvent} is + recursively applied to the parent window's event handler. If this returns ``True``, + the function exits. + 6. Finally, L{ProcessEvent} is called on the `wx.App` object. + """ + + if event.GetEventType() == wx.wxEVT_COMMAND_MENU_SELECTED: + self._last_id = event.GetId() + return True + + if self.GetNextHandler(): + return self.GetNextHandler().ProcessEvent(event) + + return False + + +# ---------------------------------------------------------------------- + +class AuiToolBarItem(object): + """ + AuiToolBarItem is a toolbar element. + + It has a unique id (except for the separators which always have id = -1), the + style (telling whether it is a normal button, separator or a control), the + state (toggled or not, enabled or not) and short and long help strings. The + default implementations use the short help string for the tooltip text which + is popped up when the mouse pointer enters the tool and the long help string + for the applications status bar. + """ + + def __init__(self, item=None): + """ + Default class constructor. + + :param `item`: another instance of L{AuiToolBarItem}. + """ + + if item: + self.Assign(item) + return + + self.window = None + self.clockwisebmp = wx.NullBitmap + self.counterclockwisebmp = wx.NullBitmap + self.clockwisedisbmp = wx.NullBitmap + self.counterclockwisedisbmp = wx.NullBitmap + self.sizer_item = None + self.spacer_pixels = 0 + self.id = 0 + self.kind = ITEM_NORMAL + self.state = 0 # normal, enabled + self.proportion = 0 + self.active = True + self.dropdown = True + self.sticky = True + self.user_data = 0 + + self.label = "" + self.bitmap = wx.NullBitmap + self.disabled_bitmap = wx.NullBitmap + self.hover_bitmap = wx.NullBitmap + self.short_help = "" + self.long_help = "" + self.min_size = wx.Size(-1, -1) + self.alignment = wx.ALIGN_CENTER + self.orientation = AUI_TBTOOL_HORIZONTAL + + + def Assign(self, c): + """ + Assigns the properties of the L{AuiToolBarItem} `c` to `self`. + + :param `c`: another instance of L{AuiToolBarItem}. + """ + + self.window = c.window + self.label = c.label + self.bitmap = c.bitmap + self.disabled_bitmap = c.disabled_bitmap + self.hover_bitmap = c.hover_bitmap + self.short_help = c.short_help + self.long_help = c.long_help + self.sizer_item = c.sizer_item + self.min_size = c.min_size + self.spacer_pixels = c.spacer_pixels + self.id = c.id + self.kind = c.kind + self.state = c.state + self.proportion = c.proportion + self.active = c.active + self.dropdown = c.dropdown + self.sticky = c.sticky + self.user_data = c.user_data + self.alignment = c.alignment + self.orientation = c.orientation + + + def SetWindow(self, w): + """ + Assigns a window to the toolbar item. + + :param `w`: an instance of `wx.Window`. + """ + + self.window = w + + + def GetWindow(self): + """ Returns window associated to the toolbar item. """ + + return self.window + + + def SetId(self, new_id): + """ + Sets the toolbar item identifier. + + :param `new_id`: the new tool id. + """ + + self.id = new_id + + + def GetId(self): + """ Returns the toolbar item identifier. """ + + return self.id + + + def SetKind(self, new_kind): + """ + Sets the L{AuiToolBarItem} kind. + + :param `new_kind`: can be one of the following items: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + """ + + self.kind = new_kind + + + def GetKind(self): + """ Returns the toolbar item kind. See L{SetKind} for more details. """ + + return self.kind + + + def SetState(self, new_state): + """ + Sets the toolbar item state. + + :param `new_state`: can be one of the following states: + + ============================================ ====================================== + Button State Constant Description + ============================================ ====================================== + ``AUI_BUTTON_STATE_NORMAL`` Normal button state + ``AUI_BUTTON_STATE_HOVER`` Hovered button state + ``AUI_BUTTON_STATE_PRESSED`` Pressed button state + ``AUI_BUTTON_STATE_DISABLED`` Disabled button state + ``AUI_BUTTON_STATE_HIDDEN`` Hidden button state + ``AUI_BUTTON_STATE_CHECKED`` Checked button state + ============================================ ====================================== + + """ + + self.state = new_state + + + def GetState(self): + """ + Returns the toolbar item state. See L{SetState} for more details. + + :see: L{SetState} + """ + + return self.state + + + def SetSizerItem(self, s): + """ + Associates a sizer item to this toolbar item. + + :param `s`: an instance of `wx.SizerItem`. + """ + + self.sizer_item = s + + + def GetSizerItem(self): + """ Returns the associated sizer item. """ + + return self.sizer_item + + + def SetLabel(self, s): + """ + Sets the toolbar item label. + + :param `s`: a string specifying the toolbar item label. + """ + + self.label = s + + + def GetLabel(self): + """ Returns the toolbar item label. """ + + return self.label + + + def SetBitmap(self, bmp): + """ + Sets the toolbar item bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.bitmap = bmp + + + def GetBitmap(self): + """ Returns the toolbar item bitmap. """ + + return self.GetRotatedBitmap(False) + + + def SetDisabledBitmap(self, bmp): + """ + Sets the toolbar item disabled bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.disabled_bitmap = bmp + + + def GetDisabledBitmap(self): + """ Returns the toolbar item disabled bitmap. """ + + return self.GetRotatedBitmap(True) + + + def SetHoverBitmap(self, bmp): + """ + Sets the toolbar item hover bitmap. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + self.hover_bitmap = bmp + + + def SetOrientation(self, a): + """ + Sets the toolbar tool orientation. + + :param `a`: one of ``AUI_TBTOOL_HORIZONTAL``, ``AUI_TBTOOL_VERT_CLOCKWISE`` or + ``AUI_TBTOOL_VERT_COUNTERCLOCKWISE``. + """ + + self.orientation = a + + + def GetOrientation(self): + """ Returns the toolbar tool orientation. """ + + return self.orientation + + + def GetHoverBitmap(self): + """ Returns the toolbar item hover bitmap. """ + + return self.hover_bitmap + + + def GetRotatedBitmap(self, disabled): + """ + Returns the correct bitmap depending on the tool orientation. + + :param `disabled`: whether to return the disabled bitmap or not. + """ + + bitmap_to_rotate = (disabled and [self.disabled_bitmap] or [self.bitmap])[0] + if not bitmap_to_rotate.IsOk() or self.orientation == AUI_TBTOOL_HORIZONTAL: + return bitmap_to_rotate + + rotated_bitmap = wx.NullBitmap + clockwise = True + if self.orientation == AUI_TBTOOL_VERT_CLOCKWISE: + rotated_bitmap = (disabled and [self.clockwisedisbmp] or [self.clockwisebmp])[0] + + elif self.orientation == AUI_TBTOOL_VERT_COUNTERCLOCKWISE: + rotated_bitmap = (disabled and [self.counterclockwisedisbmp] or [self.counterclockwisebmp])[0] + clockwise = False + + if not rotated_bitmap.IsOk(): + rotated_bitmap = wx.BitmapFromImage(bitmap_to_rotate.ConvertToImage().Rotate90(clockwise)) + + return rotated_bitmap + + + def SetShortHelp(self, s): + """ + Sets the short help string for the L{AuiToolBarItem}, to be displayed in a + `wx.ToolTip` when the mouse hover over the toolbar item. + + :param `s`: the tool short help string. + """ + + self.short_help = s + + + def GetShortHelp(self): + """ Returns the short help string for the L{AuiToolBarItem}. """ + + return self.short_help + + + def SetLongHelp(self, s): + """ + Sets the long help string for the toolbar item. This string is shown in the + statusbar (if any) of the parent frame when the mouse pointer is inside the + tool. + + :param `s`: the tool long help string. + """ + + self.long_help = s + + + def GetLongHelp(self): + """ Returns the long help string for the L{AuiToolBarItem}. """ + + return self.long_help + + + def SetMinSize(self, s): + """ + Sets the toolbar item minimum size. + + :param `s`: an instance of `wx.Size`. + """ + + self.min_size = wx.Size(*s) + + + def GetMinSize(self): + """ Returns the toolbar item minimum size. """ + + return self.min_size + + + def SetSpacerPixels(self, s): + """ + Sets the number of pixels for a toolbar item with kind = ``ITEM_SEPARATOR``. + + :param `s`: number of pixels. + """ + + self.spacer_pixels = s + + + def GetSpacerPixels(self): + """ Returns the number of pixels for a toolbar item with kind = ``ITEM_SEPARATOR``. """ + + return self.spacer_pixels + + + def SetProportion(self, p): + """ + Sets the L{AuiToolBarItem} proportion in the toolbar. + + :param `p`: the item proportion. + """ + + self.proportion = p + + + def GetProportion(self): + """ Returns the L{AuiToolBarItem} proportion in the toolbar. """ + + return self.proportion + + + def SetActive(self, b): + """ + Activates/deactivates the toolbar item. + + :param `b`: ``True`` to activate the item, ``False`` to deactivate it. + """ + + self.active = b + + + def IsActive(self): + """ Returns whether the toolbar item is active or not. """ + + return self.active + + + def SetHasDropDown(self, b): + """ + Sets whether the toolbar item has an associated dropdown menu. + + :param `b`: ``True`` to set a dropdown menu, ``False`` otherwise. + """ + + self.dropdown = b + + + def HasDropDown(self): + """ Returns whether the toolbar item has an associated dropdown menu or not. """ + + return self.dropdown + + + def SetSticky(self, b): + """ + Sets whether the toolbar item is sticky (permanent highlight after mouse enter) + or not. + + :param `b`: ``True`` to set the item as sticky, ``False`` otherwise. + """ + + self.sticky = b + + + def IsSticky(self): + """ Returns whether the toolbar item has a sticky behaviour or not. """ + + return self.sticky + + + def SetUserData(self, l): + """ + Associates some kind of user data to the toolbar item. + + :param `l`: a Python object. + + :note: The user data can be any Python object. + """ + + self.user_data = l + + + def GetUserData(self): + """ Returns the associated user data. """ + + return self.user_data + + + def SetAlignment(self, l): + """ + Sets the toolbar item alignment. + + :param `l`: the item alignment, which can be one of the available `wx.Sizer` + alignments. + """ + + self.alignment = l + + + def GetAlignment(self): + """ Returns the toolbar item alignment. """ + + return self.alignment + + +# ---------------------------------------------------------------------- + +class AuiDefaultToolBarArt(object): + """ + Toolbar art provider code - a tab provider provides all drawing functionality to + the L{AuiToolBar}. This allows the L{AuiToolBar} to have a plugable look-and-feel. + + By default, a L{AuiToolBar} uses an instance of this class called L{AuiDefaultToolBarArt} + which provides bitmap art and a colour scheme that is adapted to the major platforms' + look. You can either derive from that class to alter its behaviour or write a + completely new tab art class. Call L{AuiToolBar.SetArtProvider} to make use this + new tab art. + """ + + def __init__(self): + """ Default class constructor. """ + + self._base_colour = GetBaseColour() + + self._agwFlags = 0 + self._text_orientation = AUI_TBTOOL_TEXT_BOTTOM + self._highlight_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + + self._separator_size = 7 + self._orientation = AUI_TBTOOL_HORIZONTAL + self._gripper_size = 7 + self._overflow_size = 16 + + darker1_colour = StepColour(self._base_colour, 85) + darker2_colour = StepColour(self._base_colour, 75) + darker3_colour = StepColour(self._base_colour, 60) + darker4_colour = StepColour(self._base_colour, 50) + darker5_colour = StepColour(self._base_colour, 40) + + self._gripper_pen1 = wx.Pen(darker5_colour) + self._gripper_pen2 = wx.Pen(darker3_colour) + self._gripper_pen3 = wx.WHITE_PEN + + button_dropdown_bits = "\xe0\xf1\xfb" + overflow_bits = "\x80\xff\x80\xc1\xe3\xf7" + + self._button_dropdown_bmp = BitmapFromBits(button_dropdown_bits, 5, 3, wx.BLACK) + self._disabled_button_dropdown_bmp = BitmapFromBits(button_dropdown_bits, 5, 3, + wx.Colour(128, 128, 128)) + self._overflow_bmp = BitmapFromBits(overflow_bits, 7, 6, wx.BLACK) + self._disabled_overflow_bmp = BitmapFromBits(overflow_bits, 7, 6, wx.Colour(128, 128, 128)) + + self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + + + def Clone(self): + """ Clones the L{AuiToolBar} art. """ + + return AuiDefaultToolBarArt() + + + def SetAGWFlags(self, agwFlags): + """ + Sets the toolbar art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + """ + + self._agwFlags = agwFlags + + + def GetAGWFlags(self): + """ + Returns the L{AuiDefaultToolBarArt} flags. See L{SetAGWFlags} for more + details. + + :see: L{SetAGWFlags} + """ + + return self._agwFlags + + + def SetFont(self, font): + """ + Sets the L{AuiDefaultToolBarArt} font. + + :param `font`: a `wx.Font` object. + """ + + self._font = font + + + def SetTextOrientation(self, orientation): + """ + Sets the text orientation. + + :param `orientation`: can be one of the following constants: + + ==================================== ================================== + Orientation Switches Description + ==================================== ================================== + ``AUI_TBTOOL_TEXT_LEFT`` Text in `AuiToolBar` items is aligned left + ``AUI_TBTOOL_TEXT_RIGHT`` Text in `AuiToolBar` items is aligned right + ``AUI_TBTOOL_TEXT_TOP`` Text in `AuiToolBar` items is aligned top + ``AUI_TBTOOL_TEXT_BOTTOM`` Text in `AuiToolBar` items is aligned bottom + ==================================== ================================== + + """ + + self._text_orientation = orientation + + + def GetFont(self): + """ Returns the L{AuiDefaultToolBarArt} font. """ + + return self._font + + + def GetTextOrientation(self): + """ + Returns the L{AuiDefaultToolBarArt} text orientation. See + L{SetTextOrientation} for more details. + + :see: L{SetTextOrientation} + """ + + return self._text_orientation + + + def SetOrientation(self, orientation): + """ + Sets the toolbar tool orientation. + + :param `orientation`: one of ``AUI_TBTOOL_HORIZONTAL``, ``AUI_TBTOOL_VERT_CLOCKWISE`` or + ``AUI_TBTOOL_VERT_COUNTERCLOCKWISE``. + """ + + self._orientation = orientation + + + def GetOrientation(self): + """ Returns the toolbar orientation. """ + + return self._orientation + + + def DrawBackground(self, dc, wnd, _rect, horizontal=True): + """ + Draws a toolbar background with a gradient shading. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBar} rectangle; + :param `horizontal`: ``True`` if the toolbar is horizontal, ``False`` if it is vertical. + """ + + rect = wx.Rect(*_rect) + + start_colour = StepColour(self._base_colour, 180) + end_colour = StepColour(self._base_colour, 85) + reflex_colour = StepColour(self._base_colour, 95) + + dc.GradientFillLinear(rect, start_colour, end_colour, + (horizontal and [wx.SOUTH] or [wx.EAST])[0]) + + left = rect.GetLeft() + right = rect.GetRight() + top = rect.GetTop() + bottom = rect.GetBottom() + + dc.SetPen(wx.Pen(reflex_colour)) + if horizontal: + dc.DrawLine(left, bottom, right+1, bottom) + else: + dc.DrawLine(right, top, right, bottom+1) + + + def DrawPlainBackground(self, dc, wnd, _rect): + """ + Draws a toolbar background with a plain colour. + + This method contrasts with the default behaviour of the L{AuiToolBar} that + draws a background gradient and this break the window design when putting + it within a control that has margin between the borders and the toolbar + (example: put L{AuiToolBar} within a `wx.StaticBoxSizer` that has a plain background). + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBar} rectangle. + """ + + rect = wx.Rect(*_rect) + rect.height += 1 + + dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))) + dc.DrawRectangle(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 1) + + + def DrawLabel(self, dc, wnd, item, rect): + """ + Draws a toolbar item label. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + dc.SetFont(self._font) + dc.SetTextForeground(wx.BLACK) + orient = item.GetOrientation() + + horizontal = orient == AUI_TBTOOL_HORIZONTAL + # we only care about the text height here since the text + # will get cropped based on the width of the item + label_size = GetLabelSize(dc, item.GetLabel(), not horizontal) + text_width = label_size.GetWidth() + text_height = label_size.GetHeight() + + if orient == AUI_TBTOOL_HORIZONTAL: + text_x = rect.x + text_y = rect.y + (rect.height-text_height)/2 + dc.DrawText(item.GetLabel(), text_x, text_y) + + elif orient == AUI_TBTOOL_VERT_CLOCKWISE: + text_x = rect.x + (rect.width+text_width)/2 + text_y = rect.y + dc.DrawRotatedText(item.GetLabel(), text_x, text_y, 270) + + elif AUI_TBTOOL_VERT_COUNTERCLOCKWISE: + text_x = rect.x + (rect.width-text_width)/2 + text_y = rect.y + text_height + dc.DrawRotatedText(item.GetLabel(), text_x, text_y, 90) + + + def DrawButton(self, dc, wnd, item, rect): + """ + Draws a toolbar item button. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + bmp_rect, text_rect = self.GetToolsPosition(dc, item, rect) + + if not item.GetState() & AUI_BUTTON_STATE_DISABLED: + + if item.GetState() & AUI_BUTTON_STATE_PRESSED: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 150))) + dc.DrawRectangleRect(rect) + + elif item.GetState() & AUI_BUTTON_STATE_HOVER or item.IsSticky(): + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + + # draw an even lighter background for checked item hovers (since + # the hover background is the same colour as the check background) + if item.GetState() & AUI_BUTTON_STATE_CHECKED: + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 180))) + + dc.DrawRectangleRect(rect) + + elif item.GetState() & AUI_BUTTON_STATE_CHECKED: + + # it's important to put this code in an else statment after the + # hover, otherwise hovers won't draw properly for checked items + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangleRect(rect) + + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + bmp = item.GetDisabledBitmap() + else: + bmp = item.GetBitmap() + + if bmp.IsOk(): + dc.DrawBitmap(bmp, bmp_rect.x, bmp_rect.y, True) + + # set the item's text colour based on if it is disabled + dc.SetTextForeground(wx.BLACK) + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + dc.SetTextForeground(DISABLED_TEXT_COLOUR) + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + self.DrawLabel(dc, wnd, item, text_rect) + + + def DrawDropDownButton(self, dc, wnd, item, rect): + """ + Draws a toolbar dropdown button. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + dropbmp_x = dropbmp_y = 0 + + button_rect = wx.Rect(rect.x, rect.y, rect.width-BUTTON_DROPDOWN_WIDTH, rect.height) + dropdown_rect = wx.Rect(rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1, rect.y, BUTTON_DROPDOWN_WIDTH+1, rect.height) + + horizontal = item.GetOrientation() == AUI_TBTOOL_HORIZONTAL + + if horizontal: + button_rect = wx.Rect(rect.x, rect.y, rect.width-BUTTON_DROPDOWN_WIDTH, rect.height) + dropdown_rect = wx.Rect(rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1, rect.y, BUTTON_DROPDOWN_WIDTH+1, rect.height) + else: + button_rect = wx.Rect(rect.x, rect.y, rect.width, rect.height-BUTTON_DROPDOWN_WIDTH) + dropdown_rect = wx.Rect(rect.x, rect.y+rect.height-BUTTON_DROPDOWN_WIDTH-1, rect.width, BUTTON_DROPDOWN_WIDTH+1) + + dropbmp_width = self._button_dropdown_bmp.GetWidth() + dropbmp_height = self._button_dropdown_bmp.GetHeight() + if not horizontal: + tmp = dropbmp_width + dropbmp_width = dropbmp_height + dropbmp_height = tmp + + dropbmp_x = dropdown_rect.x + (dropdown_rect.width/2) - dropbmp_width/2 + dropbmp_y = dropdown_rect.y + (dropdown_rect.height/2) - dropbmp_height/2 + + bmp_rect, text_rect = self.GetToolsPosition(dc, item, button_rect) + + if item.GetState() & AUI_BUTTON_STATE_PRESSED: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 140))) + dc.DrawRectangleRect(button_rect) + dc.DrawRectangleRect(dropdown_rect) + + elif item.GetState() & AUI_BUTTON_STATE_HOVER or item.IsSticky(): + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangleRect(button_rect) + dc.DrawRectangleRect(dropdown_rect) + + elif item.GetState() & AUI_BUTTON_STATE_CHECKED: + # it's important to put this code in an else statment after the + # hover, otherwise hovers won't draw properly for checked items + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.SetBrush(wx.Brush(StepColour(self._highlight_colour, 170))) + dc.DrawRectangle(button_rect) + dc.DrawRectangle(dropdown_rect) + + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + + bmp = item.GetDisabledBitmap() + dropbmp = self._disabled_button_dropdown_bmp + + else: + + bmp = item.GetBitmap() + dropbmp = self._button_dropdown_bmp + + if not bmp.IsOk(): + return + + dc.DrawBitmap(bmp, bmp_rect.x, bmp_rect.y, True) + if horizontal: + dc.DrawBitmap(dropbmp, dropbmp_x, dropbmp_y, True) + else: + dc.DrawBitmap(wx.BitmapFromImage(dropbmp.ConvertToImage().Rotate90(item.GetOrientation() == AUI_TBTOOL_VERT_CLOCKWISE)), + dropbmp_x, dropbmp_y, True) + + # set the item's text colour based on if it is disabled + dc.SetTextForeground(wx.BLACK) + if item.GetState() & AUI_BUTTON_STATE_DISABLED: + dc.SetTextForeground(DISABLED_TEXT_COLOUR) + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + self.DrawLabel(dc, wnd, item, text_rect) + + + def DrawControlLabel(self, dc, wnd, item, rect): + """ + Draws a label for a toolbar control. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the L{AuiToolBarItem} rectangle. + """ + + label_size = GetLabelSize(dc, item.GetLabel(), item.GetOrientation() != AUI_TBTOOL_HORIZONTAL) + text_height = label_size.GetHeight() + text_width = label_size.GetWidth() + + dc.SetFont(self._font) + + if self._agwFlags & AUI_TB_TEXT: + + tx, text_height = dc.GetTextExtent("ABCDHgj") + + text_width, ty = dc.GetTextExtent(item.GetLabel()) + + # don't draw the label if it is wider than the item width + if text_width > rect.width: + return + + # set the label's text colour + dc.SetTextForeground(wx.BLACK) + + text_x = rect.x + (rect.width/2) - (text_width/2) + 1 + text_y = rect.y + rect.height - text_height - 1 + + if self._agwFlags & AUI_TB_TEXT and item.GetLabel() != "": + dc.DrawText(item.GetLabel(), text_x, text_y) + + + def GetLabelSize(self, dc, wnd, item): + """ + Returns the label size for a toolbar item. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}. + """ + + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), self._orientation != AUI_TBTOOL_HORIZONTAL) + + return wx.Size(item.GetMinSize().GetWidth(), label_size.GetHeight()) + + + def GetToolSize(self, dc, wnd, item): + """ + Returns the toolbar item size. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `item`: an instance of L{AuiToolBarItem}. + """ + + if not item.GetBitmap().IsOk() and not self._agwFlags & AUI_TB_TEXT: + return wx.Size(16, 16) + + width = item.GetBitmap().GetWidth() + height = item.GetBitmap().GetHeight() + + if self._agwFlags & AUI_TB_TEXT: + + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), self.GetOrientation() != AUI_TBTOOL_HORIZONTAL) + padding = 6 + + if self._text_orientation == AUI_TBTOOL_TEXT_BOTTOM: + + if self.GetOrientation() != AUI_TBTOOL_HORIZONTAL: + height += 3 # space between top border and bitmap + height += 3 # space between bitmap and text + padding = 0 + + height += label_size.GetHeight() + + if item.GetLabel() != "": + width = max(width, label_size.GetWidth()+padding) + + elif self._text_orientation == AUI_TBTOOL_TEXT_RIGHT and item.GetLabel() != "": + + if self.GetOrientation() == AUI_TBTOOL_HORIZONTAL: + + width += 3 # space between left border and bitmap + width += 3 # space between bitmap and text + padding = 0 + + width += label_size.GetWidth() + height = max(height, label_size.GetHeight()+padding) + + # if the tool has a dropdown button, add it to the width + if item.HasDropDown(): + if item.GetOrientation() == AUI_TBTOOL_HORIZONTAL: + width += BUTTON_DROPDOWN_WIDTH+4 + else: + height += BUTTON_DROPDOWN_WIDTH+4 + + return wx.Size(width, height) + + + def DrawSeparator(self, dc, wnd, _rect): + """ + Draws a toolbar separator. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `_rect`: the L{AuiToolBarItem} rectangle. + """ + + horizontal = True + if self._agwFlags & AUI_TB_VERTICAL: + horizontal = False + + rect = wx.Rect(*_rect) + + if horizontal: + + rect.x += (rect.width/2) + rect.width = 1 + new_height = (rect.height*3)/4 + rect.y += (rect.height/2) - (new_height/2) + rect.height = new_height + + else: + + rect.y += (rect.height/2) + rect.height = 1 + new_width = (rect.width*3)/4 + rect.x += (rect.width/2) - (new_width/2) + rect.width = new_width + + start_colour = StepColour(self._base_colour, 80) + end_colour = StepColour(self._base_colour, 80) + dc.GradientFillLinear(rect, start_colour, end_colour, (horizontal and [wx.SOUTH] or [wx.EAST])[0]) + + + def DrawGripper(self, dc, wnd, rect): + """ + Draws the toolbar gripper. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `rect`: the L{AuiToolBar} rectangle. + """ + + i = 0 + while 1: + + if self._agwFlags & AUI_TB_VERTICAL: + + x = rect.x + (i*4) + 4 + y = rect.y + 3 + if x > rect.GetWidth() - 4: + break + + else: + + x = rect.x + 3 + y = rect.y + (i*4) + 4 + if y > rect.GetHeight() - 4: + break + + dc.SetPen(self._gripper_pen1) + dc.DrawPoint(x, y) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(x, y+1) + dc.DrawPoint(x+1, y) + dc.SetPen(self._gripper_pen3) + dc.DrawPoint(x+2, y+1) + dc.DrawPoint(x+2, y+2) + dc.DrawPoint(x+1, y+2) + + i += 1 + + + def DrawOverflowButton(self, dc, wnd, rect, state): + """ + Draws the overflow button for the L{AuiToolBar}. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` derived window; + :param `rect`: the L{AuiToolBar} rectangle; + :param `state`: the overflow button state. + """ + + if state & AUI_BUTTON_STATE_HOVER or state & AUI_BUTTON_STATE_PRESSED: + + cli_rect = wnd.GetClientRect() + light_gray_bg = StepColour(self._highlight_colour, 170) + + if self._agwFlags & AUI_TB_VERTICAL: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.DrawLine(rect.x, rect.y, rect.x+rect.width, rect.y) + dc.SetPen(wx.Pen(light_gray_bg)) + dc.SetBrush(wx.Brush(light_gray_bg)) + dc.DrawRectangle(rect.x, rect.y+1, rect.width, rect.height) + + else: + + dc.SetPen(wx.Pen(self._highlight_colour)) + dc.DrawLine(rect.x, rect.y, rect.x, rect.y+rect.height) + dc.SetPen(wx.Pen(light_gray_bg)) + dc.SetBrush(wx.Brush(light_gray_bg)) + dc.DrawRectangle(rect.x+1, rect.y, rect.width, rect.height) + + x = rect.x + 1 + (rect.width-self._overflow_bmp.GetWidth())/2 + y = rect.y + 1 + (rect.height-self._overflow_bmp.GetHeight())/2 + dc.DrawBitmap(self._overflow_bmp, x, y, True) + + + def GetElementSize(self, element_id): + """ + Returns the size of a UI element in the L{AuiToolBar}. + + :param `element_id`: can be one of the following: + + ==================================== ================================== + Element Identifier Description + ==================================== ================================== + ``AUI_TBART_SEPARATOR_SIZE`` Separator size in `AuiToolBar` + ``AUI_TBART_GRIPPER_SIZE`` Gripper size in `AuiToolBar` + ``AUI_TBART_OVERFLOW_SIZE`` Overflow button size in `AuiToolBar` + ==================================== ================================== + """ + + if element_id == AUI_TBART_SEPARATOR_SIZE: + return self._separator_size + elif element_id == AUI_TBART_GRIPPER_SIZE: + return self._gripper_size + elif element_id == AUI_TBART_OVERFLOW_SIZE: + return self._overflow_size + + return 0 + + + def SetElementSize(self, element_id, size): + """ + Sets the size of a UI element in the L{AuiToolBar}. + + :param `element_id`: can be one of the following: + + ==================================== ================================== + Element Identifier Description + ==================================== ================================== + ``AUI_TBART_SEPARATOR_SIZE`` Separator size in `AuiToolBar` + ``AUI_TBART_GRIPPER_SIZE`` Gripper size in `AuiToolBar` + ``AUI_TBART_OVERFLOW_SIZE`` Overflow button size in `AuiToolBar` + ==================================== ================================== + + :param `size`: the new size of the UI element. + """ + + if element_id == AUI_TBART_SEPARATOR_SIZE: + self._separator_size = size + elif element_id == AUI_TBART_GRIPPER_SIZE: + self._gripper_size = size + elif element_id == AUI_TBART_OVERFLOW_SIZE: + self._overflow_size = size + + + def ShowDropDown(self, wnd, items): + """ + Shows the drop down window menu for overflow items. + + :param `wnd`: an instance of `wx.Window`; + :param `items`: the overflow toolbar items (a Python list). + """ + + menuPopup = wx.Menu() + items_added = 0 + + for item in items: + + if item.GetKind() not in [ITEM_SEPARATOR, ITEM_SPACER, ITEM_CONTROL]: + + text = item.GetShortHelp() + if text == "": + text = item.GetLabel() + if text == "": + text = " " + + kind = item.GetKind() + m = wx.MenuItem(menuPopup, item.GetId(), text, item.GetShortHelp(), kind) + orientation = item.GetOrientation() + item.SetOrientation(AUI_TBTOOL_HORIZONTAL) + + if kind not in [ITEM_CHECK, ITEM_RADIO]: + m.SetBitmap(item.GetBitmap()) + + item.SetOrientation(orientation) + + menuPopup.AppendItem(m) + if kind in [ITEM_CHECK, ITEM_RADIO]: + state = (item.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + m.Check(state) + + items_added += 1 + + else: + + if items_added > 0 and item.GetKind() == ITEM_SEPARATOR: + menuPopup.AppendSeparator() + + # find out where to put the popup menu of window items + pt = wx.GetMousePosition() + pt = wnd.ScreenToClient(pt) + + # find out the screen coordinate at the bottom of the tab ctrl + cli_rect = wnd.GetClientRect() + pt.y = cli_rect.y + cli_rect.height + + cc = ToolbarCommandCapture() + wnd.PushEventHandler(cc) + + # Adjustments to get slightly better menu placement + if wx.Platform == "__WXMAC__": + pt.y += 5 + pt.x -= 5 + + wnd.PopupMenu(menuPopup, pt) + command = cc.GetCommandId() + wnd.PopEventHandler(True) + + return command + + + def GetToolsPosition(self, dc, item, rect): + """ + Returns the bitmap and text rectangles for a toolbar item. + + :param `dc`: a `wx.DC` device context; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the tool rect. + """ + + text_width = text_height = 0 + horizontal = self._orientation == AUI_TBTOOL_HORIZONTAL + text_bottom = self._text_orientation == AUI_TBTOOL_TEXT_BOTTOM + text_right = self._text_orientation == AUI_TBTOOL_TEXT_RIGHT + bmp_width = item.GetBitmap().GetWidth() + bmp_height = item.GetBitmap().GetHeight() + + if self._agwFlags & AUI_TB_TEXT: + dc.SetFont(self._font) + label_size = GetLabelSize(dc, item.GetLabel(), not horizontal) + text_height = label_size.GetHeight() + text_width = label_size.GetWidth() + + bmp_x = bmp_y = text_x = text_y = 0 + + if horizontal and text_bottom: + bmp_x = rect.x + (rect.width/2) - (bmp_width/2) + bmp_y = rect.y + 3 + text_x = rect.x + (rect.width/2) - (text_width/2) + text_y = rect.y + ((bmp_y - rect.y) * 2) + bmp_height + + elif horizontal and text_right: + bmp_x = rect.x + 3 + bmp_y = rect.y + (rect.height/2) - (bmp_height / 2) + text_x = rect.x + ((bmp_x - rect.x) * 2) + bmp_width + text_y = rect.y + (rect.height/2) - (text_height/2) + + elif not horizontal and text_bottom: + bmp_x = rect.x + (rect.width / 2) - (bmp_width / 2) + bmp_y = rect.y + 3 + text_x = rect.x + (rect.width / 2) - (text_width / 2) + text_y = rect.y + ((bmp_y - rect.y) * 2) + bmp_height + + bmp_rect = wx.Rect(bmp_x, bmp_y, bmp_width, bmp_height) + text_rect = wx.Rect(text_x, text_y, text_width, text_height) + + return bmp_rect, text_rect + + +class AuiToolBar(wx.PyControl): + """ + AuiToolBar is a completely owner-drawn toolbar perfectly integrated with the + AUI layout system. This allows drag and drop of toolbars, docking/floating + behaviour and the possibility to define "overflow" items in the toolbar itself. + + The default theme that is used is L{AuiDefaultToolBarArt}, which provides a modern, + glossy look and feel. The theme can be changed by calling L{AuiToolBar.SetArtProvider}. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0, agwStyle=AUI_TB_DEFAULT_STYLE): + """ + Default class constructor. + + :param `parent`: the L{AuiToolBar} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the control window style; + :param `agwStyle`: the AGW-specific window style. This can be a combination of the + following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + The default value for `agwStyle` is: ``AUI_TB_DEFAULT_STYLE`` = 0 + + """ + + wx.PyControl.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE) + + self._sizer = wx.BoxSizer(wx.HORIZONTAL) + self.SetSizer(self._sizer) + self._button_width = -1 + self._button_height = -1 + self._sizer_element_count = 0 + self._action_pos = wx.Point(-1, -1) + self._action_item = None + self._tip_item = None + self._art = AuiDefaultToolBarArt() + self._tool_packing = 2 + self._tool_border_padding = 3 + self._tool_text_orientation = AUI_TBTOOL_TEXT_BOTTOM + self._tool_orientation = AUI_TBTOOL_HORIZONTAL + self._tool_alignment = wx.EXPAND + self._gripper_sizer_item = None + self._overflow_sizer_item = None + self._dragging = False + + self._agwStyle = self._originalStyle = agwStyle + + self._gripper_visible = (self._agwStyle & AUI_TB_GRIPPER and [True] or [False])[0] + self._overflow_visible = (self._agwStyle & AUI_TB_OVERFLOW and [True] or [False])[0] + self._overflow_state = 0 + self._custom_overflow_prepend = [] + self._custom_overflow_append = [] + + self._items = [] + + self.SetMargins(5, 5, 2, 2) + self.SetFont(wx.NORMAL_FONT) + self._art.SetAGWFlags(self._agwStyle) + self.SetExtraStyle(wx.WS_EX_PROCESS_IDLE) + + if agwStyle & AUI_TB_HORZ_LAYOUT: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_RIGHT) + elif agwStyle & AUI_TB_VERTICAL: + if agwStyle & AUI_TB_CLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_CLOCKWISE) + elif agwStyle & AUI_TB_COUNTERCLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_COUNTERCLOCKWISE) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_IDLE, self.OnIdle) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_DCLICK, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_DCLICK, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(wx.EVT_SET_CURSOR, self.OnSetCursor) + + + def SetWindowStyleFlag(self, style): + """ + Sets the style of the window. + + :param `style`: the new window style. + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + + :note: Overridden from `wx.PyControl`. + """ + + wx.PyControl.SetWindowStyleFlag(self, style|wx.BORDER_NONE) + + + def SetAGWWindowStyleFlag(self, agwStyle): + """ + Sets the AGW-specific style of the window. + + :param `agwStyle`: the new window style. This can be a combination of the + following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_TB_TEXT`` Shows the text in the toolbar buttons; by default only icons are shown + ``AUI_TB_NO_TOOLTIPS`` Don't show tooltips on `AuiToolBar` items + ``AUI_TB_NO_AUTORESIZE`` Do not auto-resize the `AuiToolBar` + ``AUI_TB_GRIPPER`` Shows a gripper on the `AuiToolBar` + ``AUI_TB_OVERFLOW`` The `AuiToolBar` can contain overflow items + ``AUI_TB_VERTICAL`` The `AuiToolBar` is vertical + ``AUI_TB_HORZ_LAYOUT`` Shows the text and the icons alongside, not vertically stacked. This style must be used with ``AUI_TB_TEXT`` + ``AUI_TB_PLAIN_BACKGROUND`` Don't draw a gradient background on the toolbar + ``AUI_TB_HORZ_TEXT`` Combination of ``AUI_TB_HORZ_LAYOUT`` and ``AUI_TB_TEXT`` + ==================================== ================================== + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + """ + + self._agwStyle = self._originalStyle = agwStyle + + if self._art: + self._art.SetAGWFlags(self._agwStyle) + + if agwStyle & AUI_TB_GRIPPER: + self._gripper_visible = True + else: + self._gripper_visible = False + + if agwStyle & AUI_TB_OVERFLOW: + self._overflow_visible = True + else: + self._overflow_visible = False + + if agwStyle & AUI_TB_HORZ_LAYOUT: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_RIGHT) + else: + self.SetToolTextOrientation(AUI_TBTOOL_TEXT_BOTTOM) + + if agwStyle & AUI_TB_VERTICAL: + if agwStyle & AUI_TB_CLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_CLOCKWISE) + elif agwStyle & AUI_TB_COUNTERCLOCKWISE: + self.SetToolOrientation(AUI_TBTOOL_VERT_COUNTERCLOCKWISE) + + + def GetAGWWindowStyleFlag(self): + """ + Returns the AGW-specific window style flag. + + :see: L{SetAGWWindowStyleFlag} for an explanation of various AGW-specific style. + """ + + return self._agwStyle + + + def SetArtProvider(self, art): + """ + Instructs L{AuiToolBar} to use art provider specified by parameter `art` + for all drawing calls. This allows plugable look-and-feel features. + + :param `art`: an art provider. + + :note: The previous art provider object, if any, will be deleted by L{AuiToolBar}. + """ + + del self._art + self._art = art + + if self._art: + self._art.SetAGWFlags(self._agwStyle) + self._art.SetTextOrientation(self._tool_text_orientation) + self._art.SetOrientation(self._tool_orientation) + + + def GetArtProvider(self): + """ Returns the current art provider being used. """ + + return self._art + + + def AddSimpleTool(self, tool_id, label, bitmap, short_help_string="", kind=ITEM_NORMAL): + """ + Adds a tool to the toolbar. This is the simplest method you can use to + ass an item to the L{AuiToolBar}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `bitmap`: the primary tool bitmap; + :param `short_help_string`: this string is used for the tools tooltip; + :param `kind`: the item kind. Can be one of the following: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + """ + + return self.AddTool(tool_id, label, bitmap, wx.NullBitmap, kind, short_help_string, "", None) + + + def AddToggleTool(self, tool_id, bitmap, disabled_bitmap, toggle=False, client_data=None, short_help_string="", long_help_string=""): + """ + Adds a toggle tool to the toolbar. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `bitmap`: the primary tool bitmap; + :param `disabled_bitmap`: the bitmap to use when the tool is disabled. If it is equal to + `wx.NullBitmap`, the disabled bitmap is automatically generated by greing the normal one; + :param `client_data`: whatever Python object to associate with the toolbar item; + :param `short_help_string`: this string is used for the tools tooltip; + :param `long_help_string`: this string is shown in the statusbar (if any) of the parent + frame when the mouse pointer is inside the tool. + """ + + kind = (toggle and [ITEM_CHECK] or [ITEM_NORMAL])[0] + return self.AddTool(tool_id, "", bitmap, disabled_bitmap, kind, short_help_string, long_help_string, client_data) + + + def AddTool(self, tool_id, label, bitmap, disabled_bitmap, kind, short_help_string, long_help_string, client_data): + """ + Adds a tool to the toolbar. This is the full feature version of L{AddTool}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `bitmap`: the primary tool bitmap; + :param `disabled_bitmap`: the bitmap to use when the tool is disabled. If it is equal to + `wx.NullBitmap`, the disabled bitmap is automatically generated by greing the normal one; + :param `kind`: the item kind. Can be one of the following: + + ======================== ============================= + Item Kind Description + ======================== ============================= + ``ITEM_CONTROL`` The item in the `AuiToolBar` is a control + ``ITEM_LABEL`` The item in the `AuiToolBar` is a text label + ``ITEM_SPACER`` The item in the `AuiToolBar` is a spacer + ``ITEM_SEPARATOR`` The item in the `AuiToolBar` is a separator + ``ITEM_CHECK`` The item in the `AuiToolBar` is a toolbar check item + ``ITEM_NORMAL`` The item in the `AuiToolBar` is a standard toolbar item + ``ITEM_RADIO`` The item in the `AuiToolBar` is a toolbar radio item + ======================== ============================= + + :param `short_help_string`: this string is used for the tools tooltip; + :param `long_help_string`: this string is shown in the statusbar (if any) of the parent + frame when the mouse pointer is inside the tool. + :param `client_data`: whatever Python object to associate with the toolbar item. + """ + + item = AuiToolBarItem() + item.window = None + item.label = label + item.bitmap = bitmap + item.disabled_bitmap = disabled_bitmap + item.short_help = short_help_string + item.long_help = long_help_string + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + + if tool_id == wx.ID_ANY: + tool_id = wx.NewId() + + item.id = tool_id + item.state = 0 + item.proportion = 0 + item.kind = kind + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + if not item.disabled_bitmap.IsOk(): + # no disabled bitmap specified, we need to make one + if item.bitmap.IsOk(): + item.disabled_bitmap = MakeDisabledBitmap(item.bitmap) + + self._items.append(item) + return self._items[-1] + + + def AddCheckTool(self, tool_id, label, bitmap, disabled_bitmap, short_help_string="", long_help_string="", client_data=None): + """ + Adds a new check (or toggle) tool to the L{AuiToolBar}. + + :see: L{AddTool}. + """ + + return self.AddTool(tool_id, label, bitmap, disabled_bitmap, ITEM_CHECK, short_help_string, long_help_string, client_data) + + + def AddRadioTool(self, tool_id, label, bitmap, disabled_bitmap, short_help_string="", long_help_string="", client_data=None): + """ + Adds a new radio tool to the toolbar. + + Consecutive radio tools form a radio group such that exactly one button + in the group is pressed at any moment, in other words whenever a button + in the group is pressed the previously pressed button is automatically + released. You should avoid having the radio groups of only one element + as it would be impossible for the user to use such button. + + :note: By default, the first button in the radio group is initially pressed, + the others are not. + + :see: L{AddTool}. + """ + + return self.AddTool(tool_id, label, bitmap, disabled_bitmap, ITEM_RADIO, short_help_string, long_help_string, client_data) + + + def AddControl(self, control, label=""): + """ + Adds any control to the toolbar, typically e.g. a combobox. + + :param `control`: the control to be added; + :param `label`: the label which appears if the control goes into the + overflow items in the toolbar. + """ + + item = AuiToolBarItem() + item.window = control + item.label = label + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + item.id = control.GetId() + item.state = 0 + item.proportion = 0 + item.kind = ITEM_CONTROL + item.sizer_item = None + item.min_size = control.GetEffectiveMinSize() + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddLabel(self, tool_id, label="", width=0): + """ + Adds a label tool to the L{AuiToolBar}. + + :param `tool_id`: an integer by which the tool may be identified in subsequent operations; + :param `label`: the toolbar tool label; + :param `width`: the tool width. + """ + + min_size = wx.Size(-1, -1) + + if width != -1: + min_size.x = width + + item = AuiToolBarItem() + item.window = None + item.label = label + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + + if tool_id == wx.ID_ANY: + tool_id = wx.NewId() + + item.id = tool_id + item.state = 0 + item.proportion = 0 + item.kind = ITEM_LABEL + item.sizer_item = None + item.min_size = min_size + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddSeparator(self): + """ Adds a separator for spacing groups of tools. """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.id = -1 + item.state = 0 + item.proportion = 0 + item.kind = ITEM_SEPARATOR + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddSpacer(self, pixels): + """ + Adds a spacer for spacing groups of tools. + + :param `pixels`: the width of the spacer. + """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = pixels + item.id = -1 + item.state = 0 + item.proportion = 0 + item.kind = ITEM_SPACER + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def AddStretchSpacer(self, proportion=1): + """ + Adds a stretchable spacer for spacing groups of tools. + + :param `proportion`: the stretchable spacer proportion. + """ + + item = AuiToolBarItem() + item.window = None + item.label = "" + item.bitmap = wx.NullBitmap + item.disabled_bitmap = wx.NullBitmap + item.active = True + item.dropdown = False + item.spacer_pixels = 0 + item.id = -1 + item.state = 0 + item.proportion = proportion + item.kind = ITEM_SPACER + item.sizer_item = None + item.min_size = wx.Size(-1, -1) + item.user_data = 0 + item.sticky = False + item.orientation = self._tool_orientation + + self._items.append(item) + return self._items[-1] + + + def Clear(self): + """ Deletes all the tools in the L{AuiToolBar}. """ + + self._items = [] + self._sizer_element_count = 0 + + + def ClearTools(self): + """ Deletes all the tools in the L{AuiToolBar}. """ + + self.Clear() + + + def DeleteTool(self, tool_id): + """ + Removes the specified tool from the toolbar and deletes it. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + + :returns: ``True`` if the tool was deleted, ``False`` otherwise. + + :note: Note that it is unnecessary to call L{Realize} for the change to + take place, it will happen immediately. + """ + + idx = self.GetToolIndex(tool_id) + + if idx >= 0 and idx < len(self._items): + self._items.pop(idx) + self.Realize() + return True + + return False + + + def DeleteToolByPos(self, pos): + """ + This function behaves like L{DeleteTool} but it deletes the tool at the + specified position and not the one with the given id. + + :param `pos`: the tool position. + + :see: L{DeleteTool} + """ + + if pos >= 0 and pos < len(self._items): + + self._items.pop(pos) + self.Realize() + return True + + return False + + + def FindControl(self, id): + """ + Returns a pointer to the control identified by `id` or ``None`` if no corresponding + control is found. + + :param `id`: the control identifier. + """ + + wnd = self.FindWindow(id) + return wnd + + + def FindTool(self, tool_id): + """ + Finds a tool for the given tool id. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + for item in self._items: + if item.id == tool_id: + return item + + return None + + + def FindToolForPosition(self, x, y): + """ + Finds a tool for the given mouse position. + + :param `x`: mouse `x` position; + :param `y`: mouse `y` position. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + for i, item in enumerate(self._items): + if not item.sizer_item: + continue + + rect = item.sizer_item.GetRect() + if rect.Contains((x,y)): + + # if the item doesn't fit on the toolbar, return None + if not self.GetToolFitsByIndex(i): + return None + + return item + + return None + + + def FindToolForPositionWithPacking(self, x, y): + """ + Finds a tool for the given mouse position, taking into account also the + tool packing. + + :param `x`: mouse `x` position; + :param `y`: mouse `y` position. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + count = len(self._items) + + for i, item in enumerate(self._items): + if not item.sizer_item: + continue + + rect = item.sizer_item.GetRect() + + # apply tool packing + if i+1 < count: + rect.width += self._tool_packing + + if rect.Contains((x,y)): + + # if the item doesn't fit on the toolbar, return None + if not self.GetToolFitsByIndex(i): + return None + + return item + + return None + + + def FindToolByIndex(self, pos): + """ + Finds a tool for the given tool position in the L{AuiToolBar}. + + :param `pos`: the tool position in the toolbar. + + :returns: a pointer to a L{AuiToolBarItem} if a tool is found, or ``None`` otherwise. + """ + + if pos < 0 or pos >= len(self._items): + return None + + return self._items[pos] + + + def SetToolBitmapSize(self, size): + """ + Sets the default size of each tool bitmap. The default bitmap size is + 16 by 15 pixels. + + :param `size`: the size of the bitmaps in the toolbar. + + :note: This should be called to tell the toolbar what the tool bitmap + size is. Call it before you add tools. + + :note: Note that this is the size of the bitmap you pass to L{AddTool}, + and not the eventual size of the tool button. + + :todo: Add `wx.ToolBar` compatibility, actually implementing this method. + """ + + # TODO: wx.ToolBar compatibility + pass + + + def GetToolBitmapSize(self): + """ + Returns the size of bitmap that the toolbar expects to have. The default + bitmap size is 16 by 15 pixels. + + :note: Note that this is the size of the bitmap you pass to L{AddTool}, + and not the eventual size of the tool button. + + :todo: Add `wx.ToolBar` compatibility, actually implementing this method. + """ + + # TODO: wx.ToolBar compatibility + return wx.Size(16, 15) + + + def SetToolProportion(self, tool_id, proportion): + """ + Sets the tool proportion in the toolbar. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `proportion`: the tool proportion in the toolbar. + """ + + item = self.FindTool(tool_id) + if not item: + return + + item.proportion = proportion + + + def GetToolProportion(self, tool_id): + """ + Returns the tool proportion in the toolbar. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.proportion + + + def SetToolSeparation(self, separation): + """ + Sets the separator size for the toolbar. + + :param `separation`: the separator size in pixels. + """ + + if self._art: + self._art.SetElementSize(AUI_TBART_SEPARATOR_SIZE, separation) + + + def GetToolSeparation(self): + """ Returns the separator size for the toolbar, in pixels. """ + + if self._art: + return self._art.GetElementSize(AUI_TBART_SEPARATOR_SIZE) + + return 5 + + + def SetToolDropDown(self, tool_id, dropdown): + """ + Assigns a drop down window menu to the toolbar item. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `dropdown`: whether to assign a drop down menu or not. + """ + + item = self.FindTool(tool_id) + if not item: + return + + item.dropdown = dropdown + + + def GetToolDropDown(self, tool_id): + """ + Returns whether the toolbar item identified by `tool_id` has an associated + drop down window menu or not. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.dropdown + + + def SetToolSticky(self, tool_id, sticky): + """ + Sets the toolbar item as sticky or non-sticky. + + :param `tool_id`: the L{AuiToolBarItem} identifier; + :param `sticky`: whether the tool should be sticky or not. + """ + + # ignore separators + if tool_id == -1: + return + + item = self.FindTool(tool_id) + if not item: + return + + if item.sticky == sticky: + return + + item.sticky = sticky + + self.Refresh(False) + self.Update() + + + def GetToolSticky(self, tool_id): + """ + Returns whether the toolbar item identified by `tool_id` has a sticky + behaviour or not. + + :param `tool_id`: the L{AuiToolBarItem} identifier. + """ + + item = self.FindTool(tool_id) + if not item: + return + + return item.sticky + + + def SetToolBorderPadding(self, padding): + """ + Sets the padding between the tool border and the label. + + :param `padding`: the padding in pixels. + """ + + self._tool_border_padding = padding + + + def GetToolBorderPadding(self): + """ Returns the padding between the tool border and the label, in pixels. """ + + return self._tool_border_padding + + + def SetToolTextOrientation(self, orientation): + """ + Sets the label orientation for the toolbar items. + + :param `orientation`: the L{AuiToolBarItem} label orientation. + """ + + self._tool_text_orientation = orientation + + if self._art: + self._art.SetTextOrientation(orientation) + + + def GetToolTextOrientation(self): + """ Returns the label orientation for the toolbar items. """ + + return self._tool_text_orientation + + + def SetToolOrientation(self, orientation): + """ + Sets the tool orientation for the toolbar items. + + :param `orientation`: the L{AuiToolBarItem} orientation. + """ + + self._tool_orientation = orientation + if self._art: + self._art.SetOrientation(orientation) + + + def GetToolOrientation(self): + """ Returns the orientation for the toolbar items. """ + + return self._tool_orientation + + + def SetToolPacking(self, packing): + """ + Sets the value used for spacing tools. The default value is 1 pixel. + + :param `packing`: the value for packing. + """ + + self._tool_packing = packing + + + def GetToolPacking(self): + """ Returns the value used for spacing tools. The default value is 1 pixel. """ + + return self._tool_packing + + + def SetOrientation(self, orientation): + """ + Sets the toolbar orientation. + + :param `orientation`: either ``wx.VERTICAL`` or ``wx.HORIZONTAL``. + + :note: This can be temporarily overridden by L{AuiManager} when floating and + docking a L{AuiToolBar}. + """ + + pass + + + def SetMargins(self, left=-1, right=-1, top=-1, bottom=-1): + """ + Set the values to be used as margins for the toolbar. + + :param `left`: the left toolbar margin; + :param `right`: the right toolbar margin; + :param `top`: the top toolbar margin; + :param `bottom`: the bottom toolbar margin. + """ + + if left != -1: + self._left_padding = left + if right != -1: + self._right_padding = right + if top != -1: + self._top_padding = top + if bottom != -1: + self._bottom_padding = bottom + + + def SetMarginsSize(self, size): + """ + Set the values to be used as margins for the toolbar. + + :param `size`: the margin size (an instance of `wx.Size`). + """ + + self.SetMargins(size.x, size.x, size.y, size.y) + + + def SetMarginsXY(self, x, y): + """ + Set the values to be used as margins for the toolbar. + + :param `x`: left margin, right margin and inter-tool separation value; + :param `y`: top margin, bottom margin and inter-tool separation value. + """ + + self.SetMargins(x, x, y, y) + + + def GetGripperVisible(self): + """ Returns whether the toolbar gripper is visible or not. """ + + return self._gripper_visible + + + def SetGripperVisible(self, visible): + """ + Sets whether the toolbar gripper is visible or not. + + :param `visible`: ``True`` for a visible gripper, ``False`` otherwise. + """ + + self._gripper_visible = visible + if visible: + self._agwStyle |= AUI_TB_GRIPPER + else: + self._agwStyle &= ~AUI_TB_GRIPPER + + self.Realize() + self.Refresh(False) + + + def GetOverflowVisible(self): + """ Returns whether the overflow button is visible or not. """ + + return self._overflow_visible + + + def SetOverflowVisible(self, visible): + """ + Sets whether the overflow button is visible or not. + + :param `visible`: ``True`` for a visible overflow button, ``False`` otherwise. + """ + + self._overflow_visible = visible + if visible: + self._agwStyle |= AUI_TB_OVERFLOW + else: + self._agwStyle &= ~AUI_TB_OVERFLOW + + self.Refresh(False) + + + def SetFont(self, font): + """ + Sets the L{AuiToolBar} font. + + :param `font`: a `wx.Font` object. + + :note: Overridden from `wx.PyControl`. + """ + + res = wx.PyControl.SetFont(self, font) + + if self._art: + self._art.SetFont(font) + + return res + + + def SetHoverItem(self, pitem): + """ + Sets a toolbar item to be currently hovered by the mouse. + + :param `pitem`: an instance of L{AuiToolBarItem}. + """ + + former_hover = None + + for item in self._items: + + if item.state & AUI_BUTTON_STATE_HOVER: + former_hover = item + + item.state &= ~AUI_BUTTON_STATE_HOVER + + if pitem: + pitem.state |= AUI_BUTTON_STATE_HOVER + + if former_hover != pitem: + self.Refresh(False) + self.Update() + + + def SetPressedItem(self, pitem): + """ + Sets a toolbar item to be currently in a "pressed" state. + + :param `pitem`: an instance of L{AuiToolBarItem}. + """ + + former_item = None + + for item in self._items: + + if item.state & AUI_BUTTON_STATE_PRESSED: + former_item = item + + item.state &= ~AUI_BUTTON_STATE_PRESSED + + if pitem: + pitem.state &= ~AUI_BUTTON_STATE_HOVER + pitem.state |= AUI_BUTTON_STATE_PRESSED + + if former_item != pitem: + self.Refresh(False) + self.Update() + + + def RefreshOverflowState(self): + """ Refreshes the overflow button. """ + + if not self._overflow_sizer_item: + self._overflow_state = 0 + return + + overflow_state = 0 + overflow_rect = self.GetOverflowRect() + + # find out the mouse's current position + pt = wx.GetMousePosition() + pt = self.ScreenToClient(pt) + + # find out if the mouse cursor is inside the dropdown rectangle + if overflow_rect.Contains((pt.x, pt.y)): + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if leftDown: + overflow_state = AUI_BUTTON_STATE_PRESSED + else: + overflow_state = AUI_BUTTON_STATE_HOVER + + if overflow_state != self._overflow_state: + self._overflow_state = overflow_state + self.Refresh(False) + self.Update() + + self._overflow_state = overflow_state + + + def ToggleTool(self, tool_id, state): + """ + Toggles a tool on or off. This does not cause any event to get emitted. + + :param `tool_id`: tool in question. + :param `state`: if ``True``, toggles the tool on, otherwise toggles it off. + + :note: This only applies to a tool that has been specified as a toggle tool. + """ + + tool = self.FindTool(tool_id) + + if tool: + if tool.kind not in [ITEM_CHECK, ITEM_RADIO]: + return + + if tool.kind == ITEM_RADIO: + idx = self.GetToolIndex(tool_id) + if idx >= 0 and idx < len(self._items): + for i in xrange(idx, len(self._items)): + tool = self.FindToolByIndex(i) + if tool.kind != ITEM_RADIO: + break + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + for i in xrange(idx, -1, -1): + tool = self.FindToolByIndex(i) + if tool.kind != ITEM_RADIO: + break + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + tool = self.FindTool(tool_id) + tool.state |= AUI_BUTTON_STATE_CHECKED + else: + if state == True: + tool.state |= AUI_BUTTON_STATE_CHECKED + else: + tool.state &= ~AUI_BUTTON_STATE_CHECKED + + + def GetToolToggled(self, tool_id): + """ + Returns whether a tool is toggled or not. + + :param `tool_id`: the toolbar item identifier. + + :note: This only applies to a tool that has been specified as a toggle tool. + """ + + tool = self.FindTool(tool_id) + + if tool: + if tool.kind not in [ITEM_CHECK, ITEM_RADIO]: + return False + + return (tool.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + + return False + + + def EnableTool(self, tool_id, state): + """ + Enables or disables the tool. + + :param `tool_id`: identifier for the tool to enable or disable. + :param `state`: if ``True``, enables the tool, otherwise disables it. + """ + + tool = self.FindTool(tool_id) + + if tool: + + if state == True: + tool.state &= ~AUI_BUTTON_STATE_DISABLED + else: + tool.state |= AUI_BUTTON_STATE_DISABLED + + + def GetToolEnabled(self, tool_id): + """ + Returns whether the tool identified by `tool_id` is enabled or not. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + + if tool: + return (tool.state & AUI_BUTTON_STATE_DISABLED and [False] or [True])[0] + + return False + + + def GetToolLabel(self, tool_id): + """ + Returns the tool label for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.label + + + def SetToolLabel(self, tool_id, label): + """ + Sets the tool label for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `label`: the new toolbar item label. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.label = label + + + def GetToolBitmap(self, tool_id): + """ + Returns the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return wx.NullBitmap + + return tool.bitmap + + + def SetToolBitmap(self, tool_id, bitmap): + """ + Sets the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + tool = self.FindTool(tool_id) + if tool: + tool.bitmap = bitmap + + + def SetToolNormalBitmap(self, tool_id, bitmap): + """ + Sets the tool bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + self.SetToolBitmap(tool_id, bitmap) + + + def SetToolDisabledBitmap(self, tool_id, bitmap): + """ + Sets the tool disabled bitmap for the tool identified by `tool_id`. + + :param `tool_id`: the tool identifier; + :param `bitmap`: the new disabled bitmap for the toolbar item (an instance of `wx.Bitmap`). + """ + + tool = self.FindTool(tool_id) + if tool: + tool.disabled_bitmap = bitmap + + + def GetToolShortHelp(self, tool_id): + """ + Returns the short help for the given tool. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.short_help + + + def SetToolShortHelp(self, tool_id, help_string): + """ + Sets the short help for the given tool. + + :param `tool_id`: the tool identifier; + :param `help_string`: the string for the short help. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.short_help = help_string + + + def GetToolLongHelp(self, tool_id): + """ + Returns the long help for the given tool. + + :param `tool_id`: the tool identifier. + """ + + tool = self.FindTool(tool_id) + if not tool: + return "" + + return tool.long_help + + + def SetToolAlignment(self, alignment=wx.EXPAND): + """ + This sets the alignment for all of the tools within the + toolbar (only has an effect when the toolbar is expanded). + + :param `alignment`: `wx.Sizer` alignment value + (``wx.ALIGN_CENTER_HORIZONTAL`` or ``wx.ALIGN_CENTER_VERTICAL``). + """ + + self._tool_alignment = alignment + + + + def SetToolLongHelp(self, tool_id, help_string): + """ + Sets the long help for the given tool. + + :param `tool_id`: the tool identifier; + :param `help_string`: the string for the long help. + """ + + tool = self.FindTool(tool_id) + if tool: + tool.long_help = help_string + + + def SetCustomOverflowItems(self, prepend, append): + """ + Sets the two lists `prepend` and `append` as custom overflow items. + + :param `prepend`: a list of L{AuiToolBarItem} to be prepended; + :param `append`: a list of L{AuiToolBarItem} to be appended. + """ + + self._custom_overflow_prepend = prepend + self._custom_overflow_append = append + + + def GetToolCount(self): + """ Returns the number of tools in the L{AuiToolBar}. """ + + return len(self._items) + + + def GetToolIndex(self, tool_id): + """ + Returns the position of the tool in the toolbar given its identifier. + + :param `tool_id`: the toolbar item identifier. + """ + + # this will prevent us from returning the index of the + # first separator in the toolbar since its id is equal to -1 + if tool_id == -1: + return wx.NOT_FOUND + + for i, item in enumerate(self._items): + if item.id == tool_id: + return i + + return wx.NOT_FOUND + + + def GetToolPos(self, tool_id): + """ + Returns the position of the tool in the toolbar given its identifier. + + :param `tool_id`: the toolbar item identifier. + """ + + return self.GetToolIndex(tool_id) + + + def GetToolFitsByIndex(self, tool_id): + """ + Returns whether the tool identified by `tool_id` fits into the toolbar or not. + + :param `tool_id`: the toolbar item identifier. + """ + + if tool_id < 0 or tool_id >= len(self._items): + return False + + if not self._items[tool_id].sizer_item: + return False + + cli_w, cli_h = self.GetClientSize() + rect = self._items[tool_id].sizer_item.GetRect() + + if self._agwStyle & AUI_TB_VERTICAL: + # take the dropdown size into account + if self._overflow_visible: + cli_h -= self._overflow_sizer_item.GetSize().y + + if rect.y+rect.height < cli_h: + return True + + else: + + # take the dropdown size into account + if self._overflow_visible: + cli_w -= self._overflow_sizer_item.GetSize().x + + if rect.x+rect.width < cli_w: + return True + + return False + + + def GetToolFits(self, tool_id): + """ + Returns whether the tool identified by `tool_id` fits into the toolbar or not. + + :param `tool_id`: the toolbar item identifier. + """ + + return self.GetToolFitsByIndex(self.GetToolIndex(tool_id)) + + + def GetToolRect(self, tool_id): + """ + Returns the toolbar item rectangle + + :param `tool_id`: the toolbar item identifier. + """ + + tool = self.FindTool(tool_id) + if tool and tool.sizer_item: + return tool.sizer_item.GetRect() + + return wx.Rect() + + + def GetToolBarFits(self): + """ Returns whether the L{AuiToolBar} size fits in a specified size. """ + + if len(self._items) == 0: + # empty toolbar always 'fits' + return True + + # entire toolbar content fits if the last tool fits + return self.GetToolFitsByIndex(len(self._items) - 1) + + + def Realize(self): + """ Realizes the toolbar. This function should be called after you have added tools. """ + + dc = wx.ClientDC(self) + + if not dc.IsOk(): + return False + + horizontal = True + if self._agwStyle & AUI_TB_VERTICAL: + horizontal = False + + # create the new sizer to add toolbar elements to + sizer = wx.BoxSizer((horizontal and [wx.HORIZONTAL] or [wx.VERTICAL])[0]) + + # add gripper area + separator_size = self._art.GetElementSize(AUI_TBART_SEPARATOR_SIZE) + gripper_size = self._art.GetElementSize(AUI_TBART_GRIPPER_SIZE) + + if gripper_size > 0 and self._gripper_visible: + if horizontal: + self._gripper_sizer_item = sizer.Add((gripper_size, 1), 0, wx.EXPAND) + else: + self._gripper_sizer_item = sizer.Add((1, gripper_size), 0, wx.EXPAND) + else: + self._gripper_sizer_item = None + + # add "left" padding + if self._left_padding > 0: + if horizontal: + sizer.Add((self._left_padding, 1)) + else: + sizer.Add((1, self._left_padding)) + + count = len(self._items) + for i, item in enumerate(self._items): + + sizer_item = None + kind = item.kind + + if kind == ITEM_LABEL: + + size = self._art.GetLabelSize(dc, self, item) + sizer_item = sizer.Add((size.x + (self._tool_border_padding*2), + size.y + (self._tool_border_padding*2)), + item.proportion, + item.alignment) + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + + elif kind in [ITEM_CHECK, ITEM_NORMAL, ITEM_RADIO]: + + size = self._art.GetToolSize(dc, self, item) + sizer_item = sizer.Add((size.x + (self._tool_border_padding*2), + size.y + (self._tool_border_padding*2)), + 0, + item.alignment) + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + elif kind == ITEM_SEPARATOR: + + if horizontal: + sizer_item = sizer.Add((separator_size, 1), 0, wx.EXPAND) + else: + sizer_item = sizer.Add((1, separator_size), 0, wx.EXPAND) + + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + elif kind == ITEM_SPACER: + + if item.proportion > 0: + sizer_item = sizer.AddStretchSpacer(item.proportion) + else: + sizer_item = sizer.Add((item.spacer_pixels, 1)) + + elif kind == ITEM_CONTROL: + + vert_sizer = wx.BoxSizer(wx.VERTICAL) + vert_sizer.AddStretchSpacer(1) + ctrl_sizer_item = vert_sizer.Add(item.window, 0, wx.EXPAND) + vert_sizer.AddStretchSpacer(1) + + if self._agwStyle & AUI_TB_TEXT and \ + self._tool_text_orientation == AUI_TBTOOL_TEXT_BOTTOM and \ + item.GetLabel() != "": + + s = self.GetLabelSize(item.GetLabel()) + vert_sizer.Add((1, s.y)) + + sizer_item = sizer.Add(vert_sizer, item.proportion, wx.EXPAND) + min_size = item.min_size + + # proportional items will disappear from the toolbar if + # their min width is not set to something really small + if item.proportion != 0: + min_size.x = 1 + + if min_size.IsFullySpecified(): + sizer.SetItemMinSize(vert_sizer, min_size) + vert_sizer.SetItemMinSize(item.window, min_size) + + # add tool packing + if i+1 < count: + sizer.AddSpacer(self._tool_packing) + + item.sizer_item = sizer_item + + + # add "right" padding + if self._right_padding > 0: + if horizontal: + sizer.Add((self._right_padding, 1)) + else: + sizer.Add((1, self._right_padding)) + + # add drop down area + self._overflow_sizer_item = None + + if self._agwStyle & AUI_TB_OVERFLOW: + + overflow_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if overflow_size > 0 and self._overflow_visible: + + if horizontal: + self._overflow_sizer_item = sizer.Add((overflow_size, 1), 0, wx.EXPAND) + else: + self._overflow_sizer_item = sizer.Add((1, overflow_size), 0, wx.EXPAND) + + else: + + self._overflow_sizer_item = None + + # the outside sizer helps us apply the "top" and "bottom" padding + outside_sizer = wx.BoxSizer((horizontal and [wx.VERTICAL] or [wx.HORIZONTAL])[0]) + + # add "top" padding + if self._top_padding > 0: + + if horizontal: + outside_sizer.Add((1, self._top_padding)) + else: + outside_sizer.Add((self._top_padding, 1)) + + # add the sizer that contains all of the toolbar elements + outside_sizer.Add(sizer, 1, self._tool_alignment) + + # add "bottom" padding + if self._bottom_padding > 0: + + if horizontal: + outside_sizer.Add((1, self._bottom_padding)) + else: + outside_sizer.Add((self._bottom_padding, 1)) + + del self._sizer # remove old sizer + self._sizer = outside_sizer + self.SetSizer(outside_sizer) + + # calculate the rock-bottom minimum size + for item in self._items: + + if item.sizer_item and item.proportion > 0 and item.min_size.IsFullySpecified(): + item.sizer_item.SetMinSize((0, 0)) + + self._absolute_min_size = self._sizer.GetMinSize() + + # reset the min sizes to what they were + for item in self._items: + + if item.sizer_item and item.proportion > 0 and item.min_size.IsFullySpecified(): + item.sizer_item.SetMinSize(item.min_size) + + # set control size + size = self._sizer.GetMinSize() + self.SetMinSize(size) + self._minWidth = size.x + self._minHeight = size.y + + if self._agwStyle & AUI_TB_NO_AUTORESIZE == 0: + + cur_size = self.GetClientSize() + new_size = self.GetMinSize() + + if new_size != cur_size: + + self.SetClientSize(new_size) + + else: + + self._sizer.SetDimension(0, 0, cur_size.x, cur_size.y) + + else: + + cur_size = self.GetClientSize() + self._sizer.SetDimension(0, 0, cur_size.x, cur_size.y) + + self.Refresh(False) + return True + + + def GetOverflowState(self): + """ Returns the state of the overflow button. """ + + return self._overflow_state + + + def GetOverflowRect(self): + """ Returns the rectangle of the overflow button. """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + overflow_rect = wx.Rect(*self._overflow_sizer_item.GetRect()) + overflow_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + + if self._agwStyle & AUI_TB_VERTICAL: + + overflow_rect.y = cli_rect.height - overflow_size + overflow_rect.x = 0 + overflow_rect.width = cli_rect.width + overflow_rect.height = overflow_size + + else: + + overflow_rect.x = cli_rect.width - overflow_size + overflow_rect.y = 0 + overflow_rect.width = overflow_size + overflow_rect.height = cli_rect.height + + return overflow_rect + + + def GetLabelSize(self, label): + """ + Returns the standard size of a toolbar item. + + :param `label`: a test label. + """ + + dc = wx.ClientDC(self) + dc.SetFont(self._font) + + return GetLabelSize(dc, label, self._tool_orientation != AUI_TBTOOL_HORIZONTAL) + + + def GetAuiManager(self): + """ Returns the L{AuiManager} which manages the toolbar. """ + + try: + return self._auiManager + except AttributeError: + return False + + + def SetAuiManager(self, auiManager): + """ Sets the L{AuiManager} which manages the toolbar. """ + + self._auiManager = auiManager + + + def DoIdleUpdate(self): + """ Updates the toolbar during idle times. """ + + handler = self.GetEventHandler() + if not handler: + return + + need_refresh = False + + for item in self._items: + + if item.id == -1: + continue + + evt = wx.UpdateUIEvent(item.id) + evt.SetEventObject(self) + + if handler.ProcessEvent(evt): + + if evt.GetSetEnabled(): + + if item.window: + is_enabled = item.window.IsEnabled() + else: + is_enabled = (item.state & AUI_BUTTON_STATE_DISABLED and [False] or [True])[0] + + new_enabled = evt.GetEnabled() + if new_enabled != is_enabled: + + if item.window: + item.window.Enable(new_enabled) + else: + if new_enabled: + item.state &= ~AUI_BUTTON_STATE_DISABLED + else: + item.state |= AUI_BUTTON_STATE_DISABLED + + need_refresh = True + + if evt.GetSetChecked(): + + # make sure we aren't checking an item that can't be + if item.kind != ITEM_CHECK and item.kind != ITEM_RADIO: + continue + + is_checked = (item.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + new_checked = evt.GetChecked() + + if new_checked != is_checked: + + if new_checked: + item.state |= AUI_BUTTON_STATE_CHECKED + else: + item.state &= ~AUI_BUTTON_STATE_CHECKED + + need_refresh = True + + if need_refresh: + self.Refresh(False) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiToolBar}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + x, y = self.GetClientSize() + realize = False + + if x > y: + self.SetOrientation(wx.HORIZONTAL) + else: + self.SetOrientation(wx.VERTICAL) + + if (x >= y and self._absolute_min_size.x > x) or (y > x and self._absolute_min_size.y > y): + + # hide all flexible items + for item in self._items: + if item.sizer_item and item.proportion > 0 and item.sizer_item.IsShown(): + item.sizer_item.Show(False) + item.sizer_item.SetProportion(0) + + if self._originalStyle & AUI_TB_OVERFLOW: + if not self.GetOverflowVisible(): + self.SetOverflowVisible(True) + realize = True + + else: + + if self._originalStyle & AUI_TB_OVERFLOW and not self._custom_overflow_append and \ + not self._custom_overflow_prepend: + if self.GetOverflowVisible(): + self.SetOverflowVisible(False) + realize = True + + # show all flexible items + for item in self._items: + if item.sizer_item and item.proportion > 0 and not item.sizer_item.IsShown(): + item.sizer_item.Show(True) + item.sizer_item.SetProportion(item.proportion) + + self._sizer.SetDimension(0, 0, x, y) + + if realize: + self.Realize() + else: + self.Refresh(False) + + self.Update() + + + def DoSetSize(self, x, y, width, height, sizeFlags=wx.SIZE_AUTO): + """ + Sets the position and size of the window in pixels. The `sizeFlags` + parameter indicates the interpretation of the other params if they are + equal to -1. + + :param `x`: the window `x` position; + :param `y`: the window `y` position; + :param `width`: the window width; + :param `height`: the window height; + :param `sizeFlags`: may have one of this bit set: + + =================================== ====================================== + Size Flags Description + =================================== ====================================== + ``wx.SIZE_AUTO`` A -1 indicates that a class-specific default should be used. + ``wx.SIZE_AUTO_WIDTH`` A -1 indicates that a class-specific default should be used for the width. + ``wx.SIZE_AUTO_HEIGHT`` A -1 indicates that a class-specific default should be used for the height. + ``wx.SIZE_USE_EXISTING`` Existing dimensions should be used if -1 values are supplied. + ``wx.SIZE_ALLOW_MINUS_ONE`` Allow dimensions of -1 and less to be interpreted as real dimensions, not default values. + ``wx.SIZE_FORCE`` Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently) + =================================== ====================================== + + :note: Overridden from `wx.PyControl`. + """ + + parent_size = self.GetParent().GetClientSize() + if x + width > parent_size.x: + width = max(0, parent_size.x - x) + if y + height > parent_size.y: + height = max(0, parent_size.y - y) + + wx.PyControl.DoSetSize(self, x, y, width, height, sizeFlags) + + + def OnIdle(self, event): + """ + Handles the ``wx.EVT_IDLE`` event for L{AuiToolBar}. + + :param `event`: a `wx.IdleEvent` event to be processed. + """ + + self.DoIdleUpdate() + event.Skip() + + + def DoGetBestSize(self): + """ + Gets the size which best suits the window: for a control, it would be the + minimal size which doesn't truncate the control, for a panel - the same + size as it would have after a call to `Fit()`. + + :note: Overridden from `wx.PyControl`. + """ + + return self._absolute_min_size + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiToolBar}. + + :param `event`: a `wx.PaintEvent` event to be processed. + """ + + dc = wx.AutoBufferedPaintDC(self) + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + horizontal = True + if self._agwStyle & AUI_TB_VERTICAL: + horizontal = False + + if self._agwStyle & AUI_TB_PLAIN_BACKGROUND: + self._art.DrawPlainBackground(dc, self, cli_rect) + else: + self._art.DrawBackground(dc, self, cli_rect, horizontal) + + gripper_size = self._art.GetElementSize(AUI_TBART_GRIPPER_SIZE) + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + + # paint the gripper + if gripper_size > 0 and self._gripper_sizer_item: + gripper_rect = wx.Rect(*self._gripper_sizer_item.GetRect()) + if horizontal: + gripper_rect.width = gripper_size + else: + gripper_rect.height = gripper_size + + self._art.DrawGripper(dc, self, gripper_rect) + + # calculated how far we can draw items + if horizontal: + last_extent = cli_rect.width + else: + last_extent = cli_rect.height + + if self._overflow_visible: + last_extent -= dropdown_size + + # paint each individual tool + for item in self._items: + + if not item.sizer_item: + continue + + item_rect = wx.Rect(*item.sizer_item.GetRect()) + + if (horizontal and item_rect.x + item_rect.width >= last_extent) or \ + (not horizontal and item_rect.y + item_rect.height >= last_extent): + + break + + if item.kind == ITEM_SEPARATOR: + # draw a separator + self._art.DrawSeparator(dc, self, item_rect) + + elif item.kind == ITEM_LABEL: + # draw a text label only + self._art.DrawLabel(dc, self, item, item_rect) + + elif item.kind == ITEM_NORMAL: + # draw a regular button or dropdown button + if not item.dropdown: + self._art.DrawButton(dc, self, item, item_rect) + else: + self._art.DrawDropDownButton(dc, self, item, item_rect) + + elif item.kind == ITEM_CHECK: + # draw a regular toggle button or a dropdown one + if not item.dropdown: + self._art.DrawButton(dc, self, item, item_rect) + else: + self._art.DrawDropDownButton(dc, self, item, item_rect) + + elif item.kind == ITEM_RADIO: + # draw a toggle button + self._art.DrawButton(dc, self, item, item_rect) + + elif item.kind == ITEM_CONTROL: + # draw the control's label + self._art.DrawControlLabel(dc, self, item, item_rect) + + # fire a signal to see if the item wants to be custom-rendered + self.OnCustomRender(dc, item, item_rect) + + # paint the overflow button + if dropdown_size > 0 and self._overflow_sizer_item: + dropdown_rect = self.GetOverflowRect() + self._art.DrawOverflowButton(dc, self, dropdown_rect, self._overflow_state) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiToolBar}. + + :param `event`: a `wx.EraseEvent` event to be processed. + + :note: This is intentionally empty, to reduce flicker. + """ + + pass + + + def OnLeftDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + self.StopPreviewTimer() + + if self._gripper_sizer_item: + + gripper_rect = wx.Rect(*self._gripper_sizer_item.GetRect()) + if gripper_rect.Contains(event.GetPosition()): + + # find aui manager + manager = self.GetAuiManager() + if not manager: + return + + x_drag_offset = event.GetX() - gripper_rect.GetX() + y_drag_offset = event.GetY() - gripper_rect.GetY() + + clientPt = wx.Point(*event.GetPosition()) + screenPt = self.ClientToScreen(clientPt) + managedWindow = manager.GetManagedWindow() + managerClientPt = managedWindow.ScreenToClient(screenPt) + + # gripper was clicked + manager.OnGripperClicked(self, managerClientPt, wx.Point(x_drag_offset, y_drag_offset)) + return + + if self._overflow_sizer_item: + overflow_rect = self.GetOverflowRect() + + if self._art and self._overflow_visible and overflow_rect.Contains(event.GetPosition()): + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_OVERFLOW_CLICK, -1) + e.SetEventObject(self) + e.SetToolId(-1) + e.SetClickPoint(event.GetPosition()) + processed = self.ProcessEvent(e) + + if processed: + self.DoIdleUpdate() + else: + overflow_items = [] + + # add custom overflow prepend items, if any + count = len(self._custom_overflow_prepend) + for i in xrange(count): + overflow_items.append(self._custom_overflow_prepend[i]) + + # only show items that don't fit in the dropdown + count = len(self._items) + for i in xrange(count): + + if not self.GetToolFitsByIndex(i): + overflow_items.append(self._items[i]) + + # add custom overflow append items, if any + count = len(self._custom_overflow_append) + for i in xrange(count): + overflow_items.append(self._custom_overflow_append[i]) + + res = self._art.ShowDropDown(self, overflow_items) + self._overflow_state = 0 + self.Refresh(False) + if res != -1: + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, res) + e.SetEventObject(self) + if not self.GetParent().ProcessEvent(e): + tool = self.FindTool(res) + if tool: + state = (tool.state & AUI_BUTTON_STATE_CHECKED and [True] or [False])[0] + self.ToggleTool(res, not state) + + return + + self._dragging = False + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + self.SetPressedItem(self._action_item) + + # fire the tool dropdown event + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetDropDownClicked(False) + + mouse_x, mouse_y = event.GetX(), event.GetY() + rect = wx.Rect(*self._action_item.sizer_item.GetRect()) + + if self._action_item.dropdown: + if (self._action_item.orientation == AUI_TBTOOL_HORIZONTAL and \ + mouse_x >= (rect.x+rect.width-BUTTON_DROPDOWN_WIDTH-1) and \ + mouse_x < (rect.x+rect.width)) or \ + (self._action_item.orientation != AUI_TBTOOL_HORIZONTAL and \ + mouse_y >= (rect.y+rect.height-BUTTON_DROPDOWN_WIDTH-1) and \ + mouse_y < (rect.y+rect.height)): + + e.SetDropDownClicked(True) + + e.SetClickPoint(event.GetPosition()) + e.SetItemRect(rect) + self.ProcessEvent(e) + self.DoIdleUpdate() + + + def OnLeftUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.SetPressedItem(None) + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if hit_item and not hit_item.state & AUI_BUTTON_STATE_DISABLED: + self.SetHoverItem(hit_item) + + if self._dragging: + # reset drag and drop member variables + self._dragging = False + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + else: + + if self._action_item and hit_item == self._action_item: + self.SetToolTipString("") + + if hit_item.kind in [ITEM_CHECK, ITEM_RADIO]: + toggle = not (self._action_item.state & AUI_BUTTON_STATE_CHECKED) + self.ToggleTool(self._action_item.id, toggle) + + # repaint immediately + self.Refresh(False) + self.Update() + + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, self._action_item.id) + e.SetEventObject(self) + e.SetInt(toggle) + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + self.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + if self._action_item.id == ID_RESTORE_FRAME: + # find aui manager + manager = self.GetAuiManager() + + if not manager: + return + + pane = manager.GetPane(self) + e = framemanager.AuiManagerEvent(framemanager.wxEVT_AUI_PANE_MIN_RESTORE) + + e.SetManager(manager) + e.SetPane(pane) + + manager.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + e = wx.CommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, self._action_item.id) + e.SetEventObject(self) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset drag and drop member variables + self._dragging = False + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnRightDown(self, event): + """ + Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + if self._gripper_sizer_item: + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains(event.GetPosition()): + return + + if self._overflow_sizer_item: + + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if dropdown_size > 0 and event.GetX() > cli_rect.width - dropdown_size and \ + event.GetY() >= 0 and event.GetY() < cli_rect.height and self._art: + return + + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + + def OnRightUp(self, event): + """ + Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item and hit_item == self._action_item: + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + else: + + # right-clicked on the invalid area of the toolbar + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_RIGHT_CLICK, -1) + e.SetEventObject(self) + e.SetToolId(-1) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset member variables + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnMiddleDown(self, event): + """ + Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + cli_rect = wx.RectPS(wx.Point(0, 0), self.GetClientSize()) + + if self._gripper_sizer_item: + + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains(event.GetPosition()): + return + + if self._overflow_sizer_item: + + dropdown_size = self._art.GetElementSize(AUI_TBART_OVERFLOW_SIZE) + if dropdown_size > 0 and event.GetX() > cli_rect.width - dropdown_size and \ + event.GetY() >= 0 and event.GetY() < cli_rect.height and self._art: + return + + self._action_pos = wx.Point(*event.GetPosition()) + self._action_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item: + if self._action_item.state & AUI_BUTTON_STATE_DISABLED: + + self._action_pos = wx.Point(-1, -1) + self._action_item = None + return + + + def OnMiddleUp(self, event): + """ + Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if self._action_item and hit_item == self._action_item: + if hit_item.kind == ITEM_NORMAL: + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_MIDDLE_CLICK, self._action_item.id) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + e.SetClickPoint(self._action_pos) + self.ProcessEvent(e) + self.DoIdleUpdate() + + # reset member variables + self._action_pos = wx.Point(-1, -1) + self._action_item = None + + + def OnMotion(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + # start a drag event + if not self._dragging and self._action_item != None and self._action_pos != wx.Point(-1, -1) and \ + abs(event.GetX() - self._action_pos.x) + abs(event.GetY() - self._action_pos.y) > 5: + + self.SetToolTipString("") + self._dragging = True + + e = AuiToolBarEvent(wxEVT_COMMAND_AUITOOLBAR_BEGIN_DRAG, self.GetId()) + e.SetEventObject(self) + e.SetToolId(self._action_item.id) + self.ProcessEvent(e) + self.DoIdleUpdate() + return + + hit_item = self.FindToolForPosition(*event.GetPosition()) + + if hit_item: + if not hit_item.state & AUI_BUTTON_STATE_DISABLED: + self.SetHoverItem(hit_item) + else: + self.SetHoverItem(None) + + else: + # no hit item, remove any hit item + self.SetHoverItem(hit_item) + + # figure out tooltips + packing_hit_item = self.FindToolForPositionWithPacking(*event.GetPosition()) + + if packing_hit_item: + + if packing_hit_item != self._tip_item: + self._tip_item = packing_hit_item + + if packing_hit_item.short_help != "": + self.StartPreviewTimer() + self.SetToolTipString(packing_hit_item.short_help) + else: + self.SetToolTipString("") + self.StopPreviewTimer() + + else: + + self.SetToolTipString("") + self._tip_item = None + self.StopPreviewTimer() + + # if we've pressed down an item and we're hovering + # over it, make sure it's state is set to pressed + if self._action_item: + + if self._action_item == hit_item: + self.SetPressedItem(self._action_item) + else: + self.SetPressedItem(None) + + # figure out the dropdown button state (are we hovering or pressing it?) + self.RefreshOverflowState() + + + def OnLeaveWindow(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiToolBar}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.RefreshOverflowState() + self.SetHoverItem(None) + self.SetPressedItem(None) + + self._tip_item = None + self.StopPreviewTimer() + + + def OnSetCursor(self, event): + """ + Handles the ``wx.EVT_SET_CURSOR`` event for L{AuiToolBar}. + + :param `event`: a `wx.SetCursorEvent` event to be processed. + """ + + cursor = wx.NullCursor + + if self._gripper_sizer_item: + + gripper_rect = self._gripper_sizer_item.GetRect() + if gripper_rect.Contains((event.GetX(), event.GetY())): + cursor = wx.StockCursor(wx.CURSOR_SIZING) + + event.SetCursor(cursor) + + + def OnCustomRender(self, dc, item, rect): + """ + Handles custom render for single L{AuiToolBar} items. + + :param `dc`: a `wx.DC` device context; + :param `item`: an instance of L{AuiToolBarItem}; + :param `rect`: the toolbar item rect. + + :note: This method must be overridden to provide custom rendering of items. + """ + + pass + + + def IsPaneMinimized(self): + """ Returns whether this L{AuiToolBar} contains a minimized pane tool. """ + + manager = self.GetAuiManager() + if not manager: + return False + + if manager.GetAGWFlags() & AUI_MGR_PREVIEW_MINIMIZED_PANES == 0: + # No previews here + return False + + self_name = manager.GetPane(self).name + + if not self_name.endswith("_min"): + # Wrong tool name + return False + + return self_name[0:-4] + + + def StartPreviewTimer(self): + """ Starts a timer in L{AuiManager} to slide-in/slide-out the minimized pane. """ + + self_name = self.IsPaneMinimized() + if not self_name: + return + + manager = self.GetAuiManager() + manager.StartPreviewTimer(self) + + + def StopPreviewTimer(self): + """ Stops a timer in L{AuiManager} to slide-in/slide-out the minimized pane. """ + + self_name = self.IsPaneMinimized() + if not self_name: + return + + manager = self.GetAuiManager() + manager.StopPreviewTimer() + diff --git a/aui/auibook.py b/aui/auibook.py new file mode 100644 index 0000000..62ae00c --- /dev/null +++ b/aui/auibook.py @@ -0,0 +1,5737 @@ +""" +auibook contains a notebook control which implements many features common in +applications with dockable panes. Specifically, L{AuiNotebook} implements functionality +which allows the user to rearrange tab order via drag-and-drop, split the tab window +into many different splitter configurations, and toggle through different themes to +customize the control's look and feel. + +An effort has been made to try to maintain an API as similar to that of `wx.Notebook`. + +The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy +look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +import types +import datetime + +from wx.lib.expando import ExpandoTextCtrl + +import framemanager +import tabart as TA + +from aui_utilities import LightColour, MakeDisabledBitmap, TabDragImage +from aui_utilities import TakeScreenShot, RescaleScreenShot + +from aui_constants import * + +# AuiNotebook events +wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BUTTON = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_END_DRAG = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK = wx.NewEventType() + +# Define a new event for a drag cancelled +wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG = wx.NewEventType() + +# Define events for editing a tab label +wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.NewEventType() +wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT = wx.NewEventType() + +# Create event binders +EVT_AUINOTEBOOK_PAGE_CLOSE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, 1) +""" A tab in `AuiNotebook` is being closed. Can be vetoed by calling `Veto()`. """ +EVT_AUINOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, 1) +""" A tab in `AuiNotebook` has been closed. """ +EVT_AUINOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, 1) +""" The page selection was changed. """ +EVT_AUINOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, 1) +""" The page selection is being changed. """ +EVT_AUINOTEBOOK_BUTTON = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, 1) +""" The user clicked on a button in the `AuiNotebook` tab area. """ +EVT_AUINOTEBOOK_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, 1) +""" A drag-and-drop operation on a notebook tab has started. """ +EVT_AUINOTEBOOK_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, 1) +""" A drag-and-drop operation on a notebook tab has finished. """ +EVT_AUINOTEBOOK_DRAG_MOTION = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, 1) +""" A drag-and-drop operation on a notebook tab is ongoing. """ +EVT_AUINOTEBOOK_ALLOW_DND = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, 1) +""" Fires an event asking if it is OK to drag and drop a tab. """ +EVT_AUINOTEBOOK_DRAG_DONE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, 1) +""" A drag-and-drop operation on a notebook tab has finished. """ +EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, 1) +""" The user clicked with the middle mouse button on a tab. """ +EVT_AUINOTEBOOK_TAB_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, 1) +""" The user clicked with the middle mouse button on a tab. """ +EVT_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, 1) +""" The user clicked with the right mouse button on a tab. """ +EVT_AUINOTEBOOK_TAB_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, 1) +""" The user clicked with the right mouse button on a tab. """ +EVT_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, 1) +""" The user middle-clicked in the tab area but not over a tab or a button. """ +EVT_AUINOTEBOOK_BG_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, 1) +""" The user middle-clicked in the tab area but not over a tab or a button. """ +EVT_AUINOTEBOOK_BG_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, 1) +""" The user right-clicked in the tab area but not over a tab or a button. """ +EVT_AUINOTEBOOK_BG_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, 1) +""" The user right-clicked in the tab area but not over a tab or a button. """ +EVT_AUINOTEBOOK_BG_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, 1) +""" The user left-clicked on the tab area not occupied by `AuiNotebook` tabs. """ +EVT_AUINOTEBOOK_CANCEL_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, 1) +""" A drag and drop operation has been cancelled. """ +EVT_AUINOTEBOOK_TAB_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, 1) +""" The user double-clicked with the left mouse button on a tab. """ +EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, 1) +""" The user double-clicked with the left mouse button on a tab which text is editable. """ +EVT_AUINOTEBOOK_END_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, 1) +""" The user finished editing a tab label. """ + + +# ----------------------------------------------------------------------------- +# Auxiliary class: TabTextCtrl +# This is the temporary ExpandoTextCtrl created when you edit the text of a tab +# ----------------------------------------------------------------------------- + +class TabTextCtrl(ExpandoTextCtrl): + """ Control used for in-place edit. """ + + def __init__(self, owner, tab, page_index): + """ + Default class constructor. + For internal use: do not call it in your code! + + :param `owner`: the L{AuiTabCtrl} owning the tab; + :param `tab`: the actual L{AuiNotebookPage} tab; + :param `page_index`: the L{AuiNotebook} page index for the tab. + """ + + self._owner = owner + self._tabEdited = tab + self._pageIndex = page_index + self._startValue = tab.caption + self._finished = False + self._aboutToFinish = False + self._currentValue = self._startValue + + x, y, w, h = self._tabEdited.rect + + wnd = self._tabEdited.control + if wnd: + x += wnd.GetSize()[0] + 2 + h = 0 + + image_h = 0 + image_w = 0 + + image = tab.bitmap + + if image.IsOk(): + image_w, image_h = image.GetWidth(), image.GetHeight() + image_w += 6 + + dc = wx.ClientDC(self._owner) + h = max(image_h, dc.GetMultiLineTextExtent(tab.caption)[1]) + h = h + 2 + + # FIXME: what are all these hardcoded 4, 8 and 11s really? + x += image_w + w -= image_w + 4 + + y = (self._tabEdited.rect.height - h)/2 + 1 + + expandoStyle = wx.WANTS_CHARS + if wx.Platform in ["__WXGTK__", "__WXMAC__"]: + expandoStyle |= wx.SIMPLE_BORDER + xSize, ySize = w + 2, h + else: + expandoStyle |= wx.SUNKEN_BORDER + xSize, ySize = w + 2, h+2 + + ExpandoTextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue, + wx.Point(x, y), wx.Size(xSize, ySize), + expandoStyle) + + if wx.Platform == "__WXMAC__": + self.SetFont(owner.GetFont()) + bs = self.GetBestSize() + self.SetSize((-1, bs.height)) + + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_KEY_UP, self.OnKeyUp) + self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) + + + def AcceptChanges(self): + """ Accepts/refuses the changes made by the user. """ + + value = self.GetValue() + notebook = self._owner.GetParent() + + if value == self._startValue: + # nothing changed, always accept + # when an item remains unchanged, the owner + # needs to be notified that the user decided + # not to change the tree item label, and that + # the edit has been cancelled + notebook.OnRenameCancelled(self._pageIndex) + return True + + if not notebook.OnRenameAccept(self._pageIndex, value): + # vetoed by the user + return False + + # accepted, do rename the item + notebook.SetPageText(self._pageIndex, value) + + return True + + + def Finish(self): + """ Finish editing. """ + + if not self._finished: + + notebook = self._owner.GetParent() + + self._finished = True + self._owner.SetFocus() + notebook.ResetTextControl() + + + def OnChar(self, event): + """ + Handles the ``wx.EVT_CHAR`` event for L{TabTextCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + """ + + keycode = event.GetKeyCode() + shiftDown = event.ShiftDown() + + if keycode == wx.WXK_RETURN: + if shiftDown and self._tabEdited.IsMultiline(): + event.Skip() + else: + self._aboutToFinish = True + self.SetValue(self._currentValue) + # Notify the owner about the changes + self.AcceptChanges() + # Even if vetoed, close the control (consistent with MSW) + wx.CallAfter(self.Finish) + + elif keycode == wx.WXK_ESCAPE: + self.StopEditing() + + else: + event.Skip() + + + def OnKeyUp(self, event): + """ + Handles the ``wx.EVT_KEY_UP`` event for L{TabTextCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + """ + + if not self._finished: + + # auto-grow the textctrl: + mySize = self.GetSize() + + dc = wx.ClientDC(self) + sx, sy, dummy = dc.GetMultiLineTextExtent(self.GetValue() + "M") + + self.SetSize((sx, -1)) + self._currentValue = self.GetValue() + + event.Skip() + + + def OnKillFocus(self, event): + """ + Handles the ``wx.EVT_KILL_FOCUS`` event for L{TabTextCtrl}. + + :param `event`: a `wx.FocusEvent` event to be processed. + """ + + if not self._finished and not self._aboutToFinish: + + # We must finish regardless of success, otherwise we'll get + # focus problems: + if not self.AcceptChanges(): + self._owner.GetParent().OnRenameCancelled(self._pageIndex) + + # We must let the native text control handle focus, too, otherwise + # it could have problems with the cursor (e.g., in wxGTK). + event.Skip() + wx.CallAfter(self._owner.GetParent().ResetTextControl) + + + def StopEditing(self): + """ Suddenly stops the editing. """ + + self._owner.GetParent().OnRenameCancelled(self._pageIndex) + self.Finish() + + + def item(self): + """ Returns the item currently edited. """ + + return self._tabEdited + + +# ---------------------------------------------------------------------- + +class AuiNotebookPage(object): + """ + A simple class which holds information about tab captions, bitmaps and + colours. + """ + + def __init__(self): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + self.window = None # page's associated window + self.caption = "" # caption displayed on the tab + self.bitmap = wx.NullBitmap # tab's bitmap + self.dis_bitmap = wx.NullBitmap # tab's disabled bitmap + self.rect = wx.Rect() # tab's hit rectangle + self.active = False # True if the page is currently active + self.enabled = True # True if the page is currently enabled + self.hasCloseButton = True # True if the page has a close button using the style + # AUI_NB_CLOSE_ON_ALL_TABS + self.control = None # A control can now be inside a tab + self.renamable = False # If True, a tab can be renamed by a left double-click + + self.text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT) + + self.access_time = datetime.datetime.now() # Last time this page was selected + + + def IsMultiline(self): + """ Returns whether the tab contains multiline text. """ + + return "\n" in self.caption + + +# ---------------------------------------------------------------------- + +class AuiTabContainerButton(object): + """ + A simple class which holds information about tab buttons and their state. + """ + + def __init__(self): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + self.id = -1 # button's id + self.cur_state = AUI_BUTTON_STATE_NORMAL # current state (normal, hover, pressed, etc.) + self.location = wx.LEFT # buttons location (wxLEFT, wxRIGHT, or wxCENTER) + self.bitmap = wx.NullBitmap # button's hover bitmap + self.dis_bitmap = wx.NullBitmap # button's disabled bitmap + self.rect = wx.Rect() # button's hit rectangle + + +# ---------------------------------------------------------------------- + +class CommandNotebookEvent(wx.PyCommandEvent): + """ A specialized command event class for events sent by L{AuiNotebook} . """ + + def __init__(self, command_type=None, win_id=0): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + if type(command_type) == types.IntType: + wx.PyCommandEvent.__init__(self, command_type, win_id) + else: + wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId()) + + self.old_selection = -1 + self.selection = -1 + self.drag_source = None + self.dispatched = 0 + self.label = "" + self.editCancelled = False + + + def SetSelection(self, s): + """ + Sets the selection member variable. + + :param `s`: the new selection. + """ + + self.selection = s + self._commandInt = s + + + def GetSelection(self): + """ Returns the currently selected page, or -1 if none was selected. """ + + return self.selection + + + def SetOldSelection(self, s): + """ + Sets the id of the page selected before the change. + + :param `s`: the old selection. + """ + + self.old_selection = s + + + def GetOldSelection(self): + """ + Returns the page that was selected before the change, or -1 if none was + selected. + """ + + return self.old_selection + + + def SetDragSource(self, s): + """ + Sets the drag and drop source. + + :param `s`: the drag source. + """ + + self.drag_source = s + + + def GetDragSource(self): + """ Returns the drag and drop source. """ + + return self.drag_source + + + def SetDispatched(self, b): + """ + Sets the event as dispatched (used for automatic L{AuiNotebook} ). + + :param `b`: whether the event was dispatched or not. + """ + + self.dispatched = b + + + def GetDispatched(self): + """ Returns whether the event was dispatched (used for automatic L{AuiNotebook} ). """ + + return self.dispatched + + + def IsEditCancelled(self): + """ Returns the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only).""" + + return self.editCancelled + + + def SetEditCanceled(self, editCancelled): + """ + Sets the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only). + + :param `editCancelled`: whether the editing action has been cancelled or not. + """ + + self.editCancelled = editCancelled + + + def GetLabel(self): + """Returns the label-itemtext (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only).""" + + return self.label + + + def SetLabel(self, label): + """ + Sets the label. Useful only for ``EVT_AUINOTEBOOK_END_LABEL_EDIT``. + + :param `label`: the new label. + """ + + self.label = label + + +# ---------------------------------------------------------------------- + +class AuiNotebookEvent(CommandNotebookEvent): + """ A specialized command event class for events sent by L{AuiNotebook}. """ + + def __init__(self, command_type=None, win_id=0): + """ + Default class constructor. + + :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`. + :param `win_id`: the window identification number. + """ + + CommandNotebookEvent.__init__(self, command_type, win_id) + + if type(command_type) == types.IntType: + self.notify = wx.NotifyEvent(command_type, win_id) + else: + self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId()) + + + def GetNotifyEvent(self): + """ Returns the actual `wx.NotifyEvent`. """ + + return self.notify + + + def IsAllowed(self): + """ Returns whether the event is allowed or not. """ + + return self.notify.IsAllowed() + + + def Veto(self): + """ + Prevents the change announced by this event from happening. + + It is in general a good idea to notify the user about the reasons for + vetoing the change because otherwise the applications behaviour (which + just refuses to do what the user wants) might be quite surprising. + """ + + self.notify.Veto() + + + def Allow(self): + """ + This is the opposite of L{Veto}: it explicitly allows the event to be + processed. For most events it is not necessary to call this method as the + events are allowed anyhow but some are forbidden by default (this will + be mentioned in the corresponding event description). + """ + + self.notify.Allow() + + +# ---------------------------------------------------------------------------- # +# Class TabNavigatorWindow +# ---------------------------------------------------------------------------- # + +class TabNavigatorWindow(wx.Dialog): + """ + This class is used to create a modal dialog that enables "Smart Tabbing", + similar to what you would get by hitting ``Alt`` + ``Tab`` on Windows. + """ + + def __init__(self, parent=None, icon=None): + """ + Default class constructor. Used internally. + + :param `parent`: the L{TabNavigatorWindow} parent; + :param `icon`: the L{TabNavigatorWindow} icon. + """ + + wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0) + + self._selectedItem = -1 + self._indexMap = [] + + if icon is None: + self._bmp = Mondrian.GetBitmap() + else: + self._bmp = icon + + if self._bmp.GetSize() != (16, 16): + img = self._bmp.ConvertToImage() + img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH) + self._bmp = wx.BitmapFromImage(img) + + sz = wx.BoxSizer(wx.VERTICAL) + + self._listBox = wx.ListBox(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, 150), [], wx.LB_SINGLE | wx.NO_BORDER) + + mem_dc = wx.MemoryDC() + mem_dc.SelectObject(wx.EmptyBitmap(1,1)) + font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.SetWeight(wx.BOLD) + mem_dc.SetFont(font) + + panelHeight = mem_dc.GetCharHeight() + panelHeight += 4 # Place a spacer of 2 pixels + + # Out signpost bitmap is 24 pixels + if panelHeight < 24: + panelHeight = 24 + + self._panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, panelHeight)) + + sz.Add(self._panel) + sz.Add(self._listBox, 1, wx.EXPAND) + + self.SetSizer(sz) + + # Connect events to the list box + self._listBox.Bind(wx.EVT_KEY_UP, self.OnKeyUp) + self._listBox.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey) + self._listBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnItemSelected) + + # Connect paint event to the panel + self._panel.Bind(wx.EVT_PAINT, self.OnPanelPaint) + self._panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnPanelEraseBg) + + self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) + self._listBox.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) + self.PopulateListControl(parent) + + self.GetSizer().Fit(self) + self.GetSizer().SetSizeHints(self) + self.GetSizer().Layout() + self.Centre() + + # Set focus on the list box to avoid having to click on it to change + # the tab selection under GTK. + self._listBox.SetFocus() + + + def OnKeyUp(self, event): + """ + Handles the ``wx.EVT_KEY_UP`` for the L{TabNavigatorWindow}. + + :param `event`: a `wx.KeyEvent` event to be processed. + """ + + if event.GetKeyCode() == wx.WXK_CONTROL: + self.CloseDialog() + + + def OnNavigationKey(self, event): + """ + Handles the ``wx.EVT_NAVIGATION_KEY`` for the L{TabNavigatorWindow}. + + :param `event`: a `wx.NavigationKeyEvent` event to be processed. + """ + + selected = self._listBox.GetSelection() + bk = self.GetParent() + maxItems = bk.GetPageCount() + + if event.GetDirection(): + + # Select next page + if selected == maxItems - 1: + itemToSelect = 0 + else: + itemToSelect = selected + 1 + + else: + + # Previous page + if selected == 0: + itemToSelect = maxItems - 1 + else: + itemToSelect = selected - 1 + + self._listBox.SetSelection(itemToSelect) + + + def PopulateListControl(self, book): + """ + Populates the L{TabNavigatorWindow} listbox with a list of tabs. + + :param `book`: the actual L{AuiNotebook}. + """ + # Index of currently selected page + selection = book.GetSelection() + # Total number of pages + count = book.GetPageCount() + # List of (index, AuiNotebookPage) + pages = list(enumerate(book.GetTabContainer().GetPages())) + if book.GetAGWWindowStyleFlag() & AUI_NB_ORDER_BY_ACCESS: + # Sort pages using last access time. Most recently used is the + # first in line + pages.sort( + key = lambda element: element[1].access_time, + reverse = True + ) + else: + # Manually add the current selection as first item + # Remaining ones are added in the next loop + del pages[selection] + self._listBox.Append(book.GetPageText(selection)) + self._indexMap.append(selection) + + for (index, page) in pages: + self._listBox.Append(book.GetPageText(index)) + self._indexMap.append(index) + + # Select the next entry after the current selection + self._listBox.SetSelection(0) + dummy = wx.NavigationKeyEvent() + dummy.SetDirection(True) + self.OnNavigationKey(dummy) + + + def OnItemSelected(self, event): + """ + Handles the ``wx.EVT_LISTBOX_DCLICK`` event for the wx.ListBox inside L{TabNavigatorWindow}. + + :param `event`: a `wx.ListEvent` event to be processed. + """ + + self.CloseDialog() + + + def CloseDialog(self): + """ Closes the L{TabNavigatorWindow} dialog, setting selection in L{AuiNotebook}. """ + + bk = self.GetParent() + self._selectedItem = self._listBox.GetSelection() + self.EndModal(wx.ID_OK) + + + def GetSelectedPage(self): + """ Gets the page index that was selected when the dialog was closed. """ + + return self._indexMap[self._selectedItem] + + + def OnPanelPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{TabNavigatorWindow} top panel. + + :param `event`: a `wx.PaintEvent` event to be processed. + """ + + dc = wx.PaintDC(self._panel) + rect = self._panel.GetClientRect() + + bmp = wx.EmptyBitmap(rect.width, rect.height) + + mem_dc = wx.MemoryDC() + mem_dc.SelectObject(bmp) + + endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) + startColour = LightColour(endColour, 50) + mem_dc.GradientFillLinear(rect, startColour, endColour, wx.SOUTH) + + # Draw the caption title and place the bitmap + # get the bitmap optimal position, and draw it + bmpPt, txtPt = wx.Point(), wx.Point() + bmpPt.y = (rect.height - self._bmp.GetHeight())/2 + bmpPt.x = 3 + mem_dc.DrawBitmap(self._bmp, bmpPt.x, bmpPt.y, True) + + # get the text position, and draw it + font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.SetWeight(wx.BOLD) + mem_dc.SetFont(font) + fontHeight = mem_dc.GetCharHeight() + + txtPt.x = bmpPt.x + self._bmp.GetWidth() + 4 + txtPt.y = (rect.height - fontHeight)/2 + mem_dc.SetTextForeground(wx.WHITE) + mem_dc.DrawText("Opened tabs:", txtPt.x, txtPt.y) + mem_dc.SelectObject(wx.NullBitmap) + + dc.DrawBitmap(bmp, 0, 0) + + + def OnPanelEraseBg(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{TabNavigatorWindow} top panel. + + :param `event`: a `wx.EraseEvent` event to be processed. + + :note: This is intentionally empty, to reduce flicker. + """ + + pass + + +# ---------------------------------------------------------------------- +# -- AuiTabContainer class implementation -- + +class AuiTabContainer(object): + """ + AuiTabContainer is a class which contains information about each + tab. It also can render an entire tab control to a specified DC. + It's not a window class itself, because this code will be used by + the L{AuiManager}, where it is disadvantageous to have separate + windows for each tab control in the case of "docked tabs". + + A derived class, L{AuiTabCtrl}, is an actual `wx.Window`-derived window + which can be used as a tab control in the normal sense. + """ + + def __init__(self, auiNotebook): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `auiNotebook`: the parent L{AuiNotebook} window. + """ + + self._tab_offset = 0 + self._agwFlags = 0 + self._art = TA.AuiDefaultTabArt() + + self._buttons = [] + self._pages = [] + self._tab_close_buttons = [] + + self._rect = wx.Rect() + self._auiNotebook = auiNotebook + + self.AddButton(AUI_BUTTON_LEFT, wx.LEFT) + self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT) + self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT) + self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT) + + + def SetArtProvider(self, art): + """ + Instructs L{AuiTabContainer} to use art provider specified by parameter `art` + for all drawing calls. This allows plugable look-and-feel features. + + :param `art`: an art provider. + + :note: The previous art provider object, if any, will be deleted by L{AuiTabContainer}. + """ + + del self._art + self._art = art + + if self._art: + self._art.SetAGWFlags(self._agwFlags) + + + def GetArtProvider(self): + """ Returns the current art provider being used. """ + + return self._art + + + def SetAGWFlags(self, agwFlags): + """ + Sets the tab art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``. + + """ + + self._agwFlags = agwFlags + + # check for new close button settings + self.RemoveButton(AUI_BUTTON_LEFT) + self.RemoveButton(AUI_BUTTON_RIGHT) + self.RemoveButton(AUI_BUTTON_WINDOWLIST) + self.RemoveButton(AUI_BUTTON_CLOSE) + + if agwFlags & AUI_NB_SCROLL_BUTTONS: + self.AddButton(AUI_BUTTON_LEFT, wx.LEFT) + self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT) + + if agwFlags & AUI_NB_WINDOWLIST_BUTTON: + self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT) + + if agwFlags & AUI_NB_CLOSE_BUTTON: + self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT) + + if self._art: + self._art.SetAGWFlags(self._agwFlags) + + + def GetAGWFlags(self): + """ + Returns the tab art flags. + + See L{SetAGWFlags} for a list of possible return values. + + :see: L{SetAGWFlags} + """ + + return self._agwFlags + + + def SetNormalFont(self, font): + """ + Sets the normal font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._art.SetNormalFont(font) + + + def SetSelectedFont(self, font): + """ + Sets the selected tab font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._art.SetSelectedFont(font) + + + def SetMeasuringFont(self, font): + """ + Sets the font for calculating text measurements. + + :param `font`: a `wx.Font` object. + """ + + self._art.SetMeasuringFont(font) + + + def SetTabRect(self, rect): + """ + Sets the tab area rectangle. + + :param `rect`: an instance of `wx.Rect`, specifying the available area for L{AuiTabContainer}. + """ + + self._rect = rect + + if self._art: + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + self._art.SetSizingInfo(rect.GetSize(), len(self._pages), minMaxTabWidth) + + + def AddPage(self, page, info): + """ + Adds a page to the tab control. + + :param `page`: the window associated with this tab; + :param `info`: an instance of L{AuiNotebookPage}. + """ + + page_info = info + page_info.window = page + + self._pages.append(page_info) + + # let the art provider know how many pages we have + if self._art: + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth) + + return True + + + def InsertPage(self, page, info, idx): + """ + Inserts a page in the tab control in the position specified by `idx`. + + :param `page`: the window associated with this tab; + :param `info`: an instance of L{AuiNotebookPage}; + :param `idx`: the page insertion index. + """ + + page_info = info + page_info.window = page + + if idx >= len(self._pages): + self._pages.append(page_info) + else: + self._pages.insert(idx, page_info) + + # let the art provider know how many pages we have + if self._art: + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth) + + return True + + + def MovePage(self, page, new_idx): + """ + Moves a page in a new position specified by `new_idx`. + + :param `page`: the window associated with this tab; + :param `new_idx`: the new page position. + """ + + idx = self.GetIdxFromWindow(page) + if idx == -1: + return False + + # get page entry, make a copy of it + p = self.GetPage(idx) + + # remove old page entry + self.RemovePage(page) + + # insert page where it should be + self.InsertPage(page, p, new_idx) + + return True + + + def RemovePage(self, wnd): + """ + Removes a page from the tab control. + + :param `wnd`: an instance of `wx.Window`, a window associated with this tab. + """ + + minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth() + + for page in self._pages: + if page.window == wnd: + self._pages.remove(page) + self._tab_offset = min(self._tab_offset, len(self._pages) - 1) + + # let the art provider know how many pages we have + if self._art: + self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth) + + return True + + return False + + + def SetActivePage(self, wndOrInt): + """ + Sets the L{AuiTabContainer} active page. + + :param `wndOrInt`: an instance of `wx.Window` or an integer specifying a tab index. + """ + + if type(wndOrInt) == types.IntType: + + if wndOrInt >= len(self._pages): + return False + + wnd = self._pages[wndOrInt].window + + else: + wnd = wndOrInt + + found = False + + for indx, page in enumerate(self._pages): + if page.window == wnd: + page.active = True + found = True + else: + page.active = False + + return found + + + def SetNoneActive(self): + """ Sets all the tabs as inactive (non-selected). """ + + for page in self._pages: + page.active = False + + + def GetActivePage(self): + """ Returns the current selected tab or ``wx.NOT_FOUND`` if none is selected. """ + + for indx, page in enumerate(self._pages): + if page.active: + return indx + + return wx.NOT_FOUND + + + def GetWindowFromIdx(self, idx): + """ + Returns the window associated with the tab with index `idx`. + + :param `idx`: the tab index. + """ + + if idx >= len(self._pages): + return None + + return self._pages[idx].window + + + def GetIdxFromWindow(self, wnd): + """ + Returns the tab index based on the window `wnd` associated with it. + + :param `wnd`: an instance of `wx.Window`. + """ + + for indx, page in enumerate(self._pages): + if page.window == wnd: + return indx + + return wx.NOT_FOUND + + + def GetPage(self, idx): + """ + Returns the page specified by the given index. + + :param `idx`: the tab index. + """ + + if idx < 0 or idx >= len(self._pages): + raise Exception("Invalid Page index") + + return self._pages[idx] + + + def GetPages(self): + """ Returns a list of all the pages in this L{AuiTabContainer}. """ + + return self._pages + + + def GetPageCount(self): + """ Returns the number of pages in the L{AuiTabContainer}. """ + + return len(self._pages) + + + def GetEnabled(self, idx): + """ + Returns whether a tab is enabled or not. + + :param `idx`: the tab index. + """ + + if idx < 0 or idx >= len(self._pages): + return False + + return self._pages[idx].enabled + + + def EnableTab(self, idx, enable=True): + """ + Enables/disables a tab in the L{AuiTabContainer}. + + :param `idx`: the tab index; + :param `enable`: ``True`` to enable a tab, ``False`` to disable it. + """ + + if idx < 0 or idx >= len(self._pages): + raise Exception("Invalid Page index") + + self._pages[idx].enabled = enable + wnd = self.GetWindowFromIdx(idx) + wnd.Enable(enable) + + + def AddButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap): + """ + Adds a button in the tab area. + + :param `id`: the button identifier. This can be one of the following: + + ============================== ================================= + Button Identifier Description + ============================== ================================= + ``AUI_BUTTON_CLOSE`` Shows a close button on the tab area + ``AUI_BUTTON_WINDOWLIST`` Shows a window list button on the tab area + ``AUI_BUTTON_LEFT`` Shows a left button on the tab area + ``AUI_BUTTON_RIGHT`` Shows a right button on the tab area + ============================== ================================= + + :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``; + :param `normal_bitmap`: the bitmap for an enabled tab; + :param `disabled_bitmap`: the bitmap for a disabled tab. + """ + + button = AuiTabContainerButton() + button.id = id + button.bitmap = normal_bitmap + button.dis_bitmap = disabled_bitmap + button.location = location + button.cur_state = AUI_BUTTON_STATE_NORMAL + + self._buttons.append(button) + + + def RemoveButton(self, id): + """ + Removes a button from the tab area. + + :param `id`: the button identifier. See L{AddButton} for a list of button identifiers. + + :see: L{AddButton} + """ + + for button in self._buttons: + if button.id == id: + self._buttons.remove(button) + return + + + def GetTabOffset(self): + """ Returns the tab offset. """ + + return self._tab_offset + + + def SetTabOffset(self, offset): + """ + Sets the tab offset. + + :param `offset`: the tab offset. + """ + + self._tab_offset = offset + + + def MinimizeTabOffset(self, dc, wnd, max_width): + """ + Minimize `self._tab_offset` to fit as many tabs as possible in the available space. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window`; + :param `max_width`: the maximum available width for the tabs. + """ + + total_width = 0 + + for i, page in reversed(list(enumerate(self._pages))): + + tab_button = self._tab_close_buttons[i] + (tab_width, tab_height), x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control) + total_width += tab_width + + if total_width > max_width: + + tab_offset = i + 1 + + if tab_offset < self._tab_offset and tab_offset < len(self._pages): + self._tab_offset = tab_offset + + break + + if i == 0: + self._tab_offset = 0 + + + def Render(self, raw_dc, wnd): + """ + Renders the tab catalog to the specified `wx.DC`. + + It is a virtual function and can be overridden to provide custom drawing + capabilities. + + :param `raw_dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window`. + """ + + if not raw_dc or not raw_dc.IsOk(): + return + + dc = wx.MemoryDC() + + # use the same layout direction as the window DC uses to ensure that the + # text is rendered correctly + dc.SetLayoutDirection(raw_dc.GetLayoutDirection()) + + page_count = len(self._pages) + button_count = len(self._buttons) + + # create off-screen bitmap + bmp = wx.EmptyBitmap(self._rect.GetWidth(), self._rect.GetHeight()) + dc.SelectObject(bmp) + + if not dc.IsOk(): + return + + # find out if size of tabs is larger than can be + # afforded on screen + total_width = visible_width = 0 + + for i in xrange(page_count): + page = self._pages[i] + + # determine if a close button is on this tab + close_button = False + if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \ + (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton): + + close_button = True + + control = page.control + if control: + try: + control.GetSize() + except wx.PyDeadObjectError: + page.control = None + + size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, + (close_button and [AUI_BUTTON_STATE_NORMAL] or \ + [AUI_BUTTON_STATE_HIDDEN])[0], page.control) + + if i+1 < page_count: + total_width += x_extent + else: + total_width += size[0] + + if i >= self._tab_offset: + if i+1 < page_count: + visible_width += x_extent + else: + visible_width += size[0] + + if total_width > self._rect.GetWidth() or self._tab_offset != 0: + + # show left/right buttons + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + button.cur_state &= ~AUI_BUTTON_STATE_HIDDEN + + else: + + # hide left/right buttons + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + button.cur_state |= AUI_BUTTON_STATE_HIDDEN + + # determine whether left button should be enabled + for button in self._buttons: + if button.id == AUI_BUTTON_LEFT: + if self._tab_offset == 0: + button.cur_state |= AUI_BUTTON_STATE_DISABLED + else: + button.cur_state &= ~AUI_BUTTON_STATE_DISABLED + + if button.id == AUI_BUTTON_RIGHT: + if visible_width < self._rect.GetWidth() - 16*button_count: + button.cur_state |= AUI_BUTTON_STATE_DISABLED + else: + button.cur_state &= ~AUI_BUTTON_STATE_DISABLED + + # draw background + self._art.DrawBackground(dc, wnd, self._rect) + + # draw buttons + left_buttons_width = 0 + right_buttons_width = 0 + + # draw the buttons on the right side + offset = self._rect.x + self._rect.width + + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.RIGHT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + button_rect = wx.Rect(*self._rect) + button_rect.SetY(1) + button_rect.SetWidth(offset) + + button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.RIGHT) + + offset -= button.rect.GetWidth() + right_buttons_width += button.rect.GetWidth() + + offset = 0 + + # draw the buttons on the left side + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.LEFT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + button_rect = wx.Rect(offset, 1, 1000, self._rect.height) + + button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.LEFT) + + offset += button.rect.GetWidth() + left_buttons_width += button.rect.GetWidth() + + offset = left_buttons_width + + if offset == 0: + offset += self._art.GetIndentSize() + + # prepare the tab-close-button array + # make sure tab button entries which aren't used are marked as hidden + for i in xrange(page_count, len(self._tab_close_buttons)): + self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN + + # make sure there are enough tab button entries to accommodate all tabs + while len(self._tab_close_buttons) < page_count: + tempbtn = AuiTabContainerButton() + tempbtn.id = AUI_BUTTON_CLOSE + tempbtn.location = wx.CENTER + tempbtn.cur_state = AUI_BUTTON_STATE_HIDDEN + self._tab_close_buttons.append(tempbtn) + + # buttons before the tab offset must be set to hidden + for i in xrange(self._tab_offset): + self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN + if self._pages[i].control: + if self._pages[i].control.IsShown(): + self._pages[i].control.Hide() + + self.MinimizeTabOffset(dc, wnd, self._rect.GetWidth() - right_buttons_width - offset - 2) + + # draw the tabs + active = 999 + active_offset = 0 + + rect = wx.Rect(*self._rect) + rect.y = 0 + rect.height = self._rect.height + + for i in xrange(self._tab_offset, page_count): + + page = self._pages[i] + tab_button = self._tab_close_buttons[i] + + # determine if a close button is on this tab + if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \ + (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton): + + if tab_button.cur_state == AUI_BUTTON_STATE_HIDDEN: + + tab_button.id = AUI_BUTTON_CLOSE + tab_button.cur_state = AUI_BUTTON_STATE_NORMAL + tab_button.location = wx.CENTER + + else: + + tab_button.cur_state = AUI_BUTTON_STATE_HIDDEN + + rect.x = offset + rect.width = self._rect.width - right_buttons_width - offset - 2 + + if rect.width <= 0: + break + + page.rect, tab_button.rect, x_extent = self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state) + + if page.active: + active = i + active_offset = offset + active_rect = wx.Rect(*rect) + + offset += x_extent + + lenPages = len(self._pages) + # make sure to deactivate buttons which are off the screen to the right + for j in xrange(i+1, len(self._tab_close_buttons)): + self._tab_close_buttons[j].cur_state = AUI_BUTTON_STATE_HIDDEN + if j > 0 and j <= lenPages: + if self._pages[j-1].control: + if self._pages[j-1].control.IsShown(): + self._pages[j-1].control.Hide() + + # draw the active tab again so it stands in the foreground + if active >= self._tab_offset and active < len(self._pages): + + page = self._pages[active] + tab_button = self._tab_close_buttons[active] + + rect.x = active_offset + dummy = self._art.DrawTab(dc, wnd, page, active_rect, tab_button.cur_state) + + raw_dc.Blit(self._rect.x, self._rect.y, self._rect.GetWidth(), self._rect.GetHeight(), dc, 0, 0) + + + def IsTabVisible(self, tabPage, tabOffset, dc, wnd): + """ + Returns whether a tab is visible or not. + + :param `tabPage`: the tab index; + :param `tabOffset`: the tab offset; + :param `dc`: a `wx.DC` device context; + :param `wnd`: an instance of `wx.Window` derived window. + """ + + if not dc or not dc.IsOk(): + return False + + page_count = len(self._pages) + button_count = len(self._buttons) + self.Render(dc, wnd) + + # Hasn't been rendered yet assume it's visible + if len(self._tab_close_buttons) < page_count: + return True + + if self._agwFlags & AUI_NB_SCROLL_BUTTONS: + # First check if both buttons are disabled - if so, there's no need to + # check further for visibility. + arrowButtonVisibleCount = 0 + for i in xrange(button_count): + + button = self._buttons[i] + if button.id == AUI_BUTTON_LEFT or \ + button.id == AUI_BUTTON_RIGHT: + + if button.cur_state & AUI_BUTTON_STATE_HIDDEN == 0: + arrowButtonVisibleCount += 1 + + # Tab must be visible + if arrowButtonVisibleCount == 0: + return True + + # If tab is less than the given offset, it must be invisible by definition + if tabPage < tabOffset: + return False + + # draw buttons + left_buttons_width = 0 + right_buttons_width = 0 + + offset = 0 + + # calculate size of the buttons on the right side + offset = self._rect.x + self._rect.width + + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.RIGHT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + offset -= button.rect.GetWidth() + right_buttons_width += button.rect.GetWidth() + + offset = 0 + + # calculate size of the buttons on the left side + for i in xrange(button_count): + button = self._buttons[button_count - i - 1] + + if button.location != wx.LEFT: + continue + if button.cur_state & AUI_BUTTON_STATE_HIDDEN: + continue + + offset += button.rect.GetWidth() + left_buttons_width += button.rect.GetWidth() + + offset = left_buttons_width + + if offset == 0: + offset += self._art.GetIndentSize() + + rect = wx.Rect(*self._rect) + rect.y = 0 + rect.height = self._rect.height + + # See if the given page is visible at the given tab offset (effectively scroll position) + for i in xrange(tabOffset, page_count): + + page = self._pages[i] + tab_button = self._tab_close_buttons[i] + + rect.x = offset + rect.width = self._rect.width - right_buttons_width - offset - 2 + + if rect.width <= 0: + return False # haven't found the tab, and we've run out of space, so return False + + size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control) + offset += x_extent + + if i == tabPage: + + # If not all of the tab is visible, and supposing there's space to display it all, + # we could do better so we return False. + if (self._rect.width - right_buttons_width - offset - 2) <= 0 and (self._rect.width - right_buttons_width - left_buttons_width) > x_extent: + return False + else: + return True + + # Shouldn't really get here, but if it does, assume the tab is visible to prevent + # further looping in calling code. + return True + + + def MakeTabVisible(self, tabPage, win): + """ + Make the tab visible if it wasn't already. + + :param `tabPage`: the tab index; + :param `win`: an instance of `wx.Window` derived window. + """ + + dc = wx.ClientDC(win) + + if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win): + for i in xrange(len(self._pages)): + if self.IsTabVisible(tabPage, i, dc, win): + self.SetTabOffset(i) + win.Refresh() + return + + + def TabHitTest(self, x, y): + """ + TabHitTest() tests if a tab was hit, passing the window pointer + back if that condition was fulfilled. + + :param `x`: the mouse `x` position; + :param `y`: the mouse `y` position. + """ + + if not self._rect.Contains((x,y)): + return None + + btn = self.ButtonHitTest(x, y) + if btn: + if btn in self._buttons: + return None + + for i in xrange(self._tab_offset, len(self._pages)): + page = self._pages[i] + if page.rect.Contains((x,y)): + return page.window + + return None + + + def ButtonHitTest(self, x, y): + """ + Tests if a button was hit. + + :param `x`: the mouse `x` position; + :param `y`: the mouse `y` position. + + :returns: and instance of L{AuiTabContainerButton} if a button was hit, ``None`` otherwise. + """ + + if not self._rect.Contains((x,y)): + return None + + for button in self._buttons: + if button.rect.Contains((x,y)) and \ + (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0: + return button + + for button in self._tab_close_buttons: + if button.rect.Contains((x,y)) and \ + (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0: + return button + + return None + + + def DoShowHide(self): + """ + This function shows the active window, then hides all of the other windows + (in that order). + """ + + pages = self.GetPages() + + # show new active page first + for page in pages: + if page.active: + page.window.Show(True) + break + + # hide all other pages + for page in pages: + if not page.active: + page.window.Show(False) + + +# ---------------------------------------------------------------------- +# -- AuiTabCtrl class implementation -- + +class AuiTabCtrl(wx.PyControl, AuiTabContainer): + """ + This is an actual `wx.Window`-derived window which can be used as a tab + control in the normal sense. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=wx.NO_BORDER|wx.WANTS_CHARS|wx.TAB_TRAVERSAL): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `parent`: the L{AuiTabCtrl} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the window style. + """ + + wx.PyControl.__init__(self, parent, id, pos, size, style, name="AuiTabCtrl") + AuiTabContainer.__init__(self, parent) + + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._hover_button = None + self._pressed_button = None + self._drag_image = None + self._drag_img_offset = (0, 0) + self._on_button = False + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) + self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) + self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) + self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) + self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnButton) + + + def IsDragging(self): + """ Returns whether the user is dragging a tab with the mouse or not. """ + + return self._is_dragging + + + def GetDefaultBorder(self): + """ Returns the default border style for L{AuiTabCtrl}. """ + + return wx.BORDER_NONE + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.PaintEvent` event to be processed. + """ + + dc = wx.PaintDC(self) + dc.SetFont(self.GetFont()) + + if self.GetPageCount() > 0: + self.Render(dc, self) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.EraseEvent` event to be processed. + + :note: This is intentionally empty, to reduce flicker. + """ + + pass + + + def DoGetBestSize(self): + """ + Gets the size which best suits the window: for a control, it would be the + minimal size which doesn't truncate the control, for a panel - the same + size as it would have after a call to `Fit()`. + + :note: Overridden from `wx.PyControl`. + """ + + return wx.Size(self._rect.width, self._rect.height) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + s = event.GetSize() + self.SetTabRect(wx.Rect(0, 0, s.GetWidth(), s.GetHeight())) + + + def OnLeftDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self.CaptureMouse() + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._click_tab = None + self._pressed_button = None + + wnd = self.TabHitTest(event.GetX(), event.GetY()) + + if wnd is not None: + new_selection = self.GetIdxFromWindow(wnd) + + # AuiNotebooks always want to receive this event + # even if the tab is already active, because they may + # have multiple tab controls + if new_selection != self.GetActivePage() or isinstance(self.GetParent(), AuiNotebook): + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(new_selection) + e.SetOldSelection(self.GetActivePage()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + self._click_pt.x = event.GetX() + self._click_pt.y = event.GetY() + self._click_tab = wnd + else: + page_index = self.GetActivePage() + if page_index != wx.NOT_FOUND: + self.GetWindowFromIdx(page_index).SetFocus() + + if self._hover_button: + self._pressed_button = self._hover_button + self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED + self._on_button = True + self.Refresh() + self.Update() + + + def OnCaptureLost(self, event): + """ + Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseCaptureLostEvent` event to be processed. + """ + + if self._is_dragging: + self._is_dragging = False + self._on_button = False + + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + + event = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, self.GetId()) + event.SetSelection(self.GetIdxFromWindow(self._click_tab)) + event.SetOldSelection(event.GetSelection()) + event.SetEventObject(self) + self.GetEventHandler().ProcessEvent(event) + + + def OnLeftUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + self._on_button = False + + if self._is_dragging: + + if self.HasCapture(): + self.ReleaseMouse() + + self._is_dragging = False + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + self.GetParent().Refresh() + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, self.GetId()) + evt.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt.SetOldSelection(evt.GetSelection()) + evt.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt) + + return + + self.GetParent()._mgr.HideHint() + + if self.HasCapture(): + self.ReleaseMouse() + + if self._hover_button: + self._pressed_button = self._hover_button + + if self._pressed_button: + + # make sure we're still clicking the button + button = self.ButtonHitTest(event.GetX(), event.GetY()) + + if button is None: + return + + if button != self._pressed_button: + self._pressed_button = None + return + + self.Refresh() + self.Update() + + if self._pressed_button.cur_state & AUI_BUTTON_STATE_DISABLED == 0: + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, self.GetId()) + evt.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt.SetInt(self._pressed_button.id) + evt.SetEventObject(self) + eventHandler = self.GetEventHandler() + + if eventHandler is not None: + eventHandler.ProcessEvent(evt) + + self._pressed_button = None + + self._click_pt = wx.Point(-1, -1) + self._is_dragging = False + self._click_tab = None + + + def OnMiddleUp(self, event): + """ + Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + eventHandler = self.GetEventHandler() + if not isinstance(eventHandler, AuiTabCtrl): + event.Skip() + return + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnMiddleDown(self, event): + """ + Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + eventHandler = self.GetEventHandler() + if not isinstance(eventHandler, AuiTabCtrl): + event.Skip() + return + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnRightUp(self, event): + """ + Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnRightDown(self, event): + """ + Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnLeftDClick(self, event): + """ + Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + x, y = event.GetX(), event.GetY() + wnd = self.TabHitTest(x, y) + + if wnd: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId()) + e.SetEventObject(self) + e.SetSelection(self.GetIdxFromWindow(wnd)) + self.GetEventHandler().ProcessEvent(e) + elif not self.ButtonHitTest(x, y): + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnMotion(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + pos = event.GetPosition() + + # check if the mouse is hovering above a button + + button = self.ButtonHitTest(pos.x, pos.y) + wnd = self.TabHitTest(pos.x, pos.y) + + if wnd is not None: + mouse_tab = self.GetIdxFromWindow(wnd) + if not self._pages[mouse_tab].enabled: + self._hover_button = None + return + + if self._on_button: + return + + if button: + + if self._hover_button and button != self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + if button.cur_state != AUI_BUTTON_STATE_HOVER: + button.cur_state = AUI_BUTTON_STATE_HOVER + self.Refresh() + self.Update() + self._hover_button = button + return + + else: + + if self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + if not event.LeftIsDown() or self._click_pt == wx.Point(-1, -1): + return + + if not self.HasCapture(): + return + + wnd = self.TabHitTest(pos.x, pos.y) + + if not self._is_dragging: + + drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X) + drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y) + + if abs(pos.x - self._click_pt.x) > drag_x_threshold or \ + abs(pos.y - self._click_pt.y) > drag_y_threshold: + + self._is_dragging = True + + if self._drag_image: + self._drag_image.EndDrag() + del self._drag_image + self._drag_image = None + + if self._agwFlags & AUI_NB_DRAW_DND_TAB: + # Create the custom draw image from the icons and the text of the item + mouse_tab = self.GetIdxFromWindow(wnd) + page = self._pages[mouse_tab] + tab_button = self._tab_close_buttons[mouse_tab] + self._drag_image = TabDragImage(self, page, tab_button.cur_state, self._art) + + if self._agwFlags & AUI_NB_TAB_FLOAT: + self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True) + else: + self._drag_image.BeginDragBounded(wx.Point(0,0), self, self.GetParent()) + + # Capture the mouse cursor position offset relative to + # The tab image location + self._drag_img_offset = (pos[0] - page.rect.x, + pos[1] - page.rect.y) + + self._drag_image.Show() + + if not wnd: + evt2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, self.GetId()) + evt2.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt2.SetOldSelection(evt2.GetSelection()) + evt2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt2) + if evt2.GetDispatched(): + return + + evt3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, self.GetId()) + evt3.SetSelection(self.GetIdxFromWindow(self._click_tab)) + evt3.SetOldSelection(evt3.GetSelection()) + evt3.SetEventObject(self) + self.GetEventHandler().ProcessEvent(evt3) + + if self._drag_image: + # Apply the drag images offset + pos -= self._drag_img_offset + self._drag_image.Move(pos) + + + def OnLeaveWindow(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.MouseEvent` event to be processed. + """ + + if self._hover_button: + self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL + self._hover_button = None + self.Refresh() + self.Update() + + + def OnButton(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiTabCtrl}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + button = event.GetInt() + + if button == AUI_BUTTON_LEFT or button == AUI_BUTTON_RIGHT: + if button == AUI_BUTTON_LEFT: + if self.GetTabOffset() > 0: + + self.SetTabOffset(self.GetTabOffset()-1) + self.Refresh() + self.Update() + else: + self.SetTabOffset(self.GetTabOffset()+1) + self.Refresh() + self.Update() + + elif button == AUI_BUTTON_WINDOWLIST: + idx = self.GetArtProvider().ShowDropDown(self, self._pages, self.GetActivePage()) + + if idx != -1: + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(idx) + e.SetOldSelection(self.GetActivePage()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + else: + event.Skip() + + + def OnSetFocus(self, event): + """ + Handles the ``wx.EVT_SET_FOCUS`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.FocusEvent` event to be processed. + """ + + self.Refresh() + + + def OnKillFocus(self, event): + """ + Handles the ``wx.EVT_KILL_FOCUS`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.FocusEvent` event to be processed. + """ + + self.Refresh() + + + def OnKeyDown(self, event): + """ + Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + """ + + key = event.GetKeyCode() + nb = self.GetParent() + + if key == wx.WXK_LEFT: + nb.AdvanceSelection(False) + self.SetFocus() + + elif key == wx.WXK_RIGHT: + nb.AdvanceSelection(True) + self.SetFocus() + + elif key == wx.WXK_HOME: + newPage = 0 + nb.SetSelection(newPage) + self.SetFocus() + + elif key == wx.WXK_END: + newPage = nb.GetPageCount() - 1 + nb.SetSelection(newPage) + self.SetFocus() + + elif key == wx.WXK_TAB: + if not event.ControlDown(): + flags = 0 + if not event.ShiftDown(): flags |= wx.NavigationKeyEvent.IsForward + if event.CmdDown(): flags |= wx.NavigationKeyEvent.WinChange + self.Navigate(flags) + else: + + if not nb or not isinstance(nb, AuiNotebook): + event.Skip() + return + + bForward = bWindowChange = 0 + if not event.ShiftDown(): bForward |= wx.NavigationKeyEvent.IsForward + if event.CmdDown(): bWindowChange |= wx.NavigationKeyEvent.WinChange + + keyEvent = wx.NavigationKeyEvent() + keyEvent.SetDirection(bForward) + keyEvent.SetWindowChange(bWindowChange) + keyEvent.SetFromTab(True) + keyEvent.SetEventObject(nb) + + if not nb.GetEventHandler().ProcessEvent(keyEvent): + + # Not processed? Do an explicit tab into the page. + win = self.GetWindowFromIdx(self.GetActivePage()) + if win: + win.SetFocus() + + self.SetFocus() + + return + + else: + event.Skip() + + + def OnKeyDown2(self, event): + """ + Deprecated. + + Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}. + + :param `event`: a `wx.KeyEvent` event to be processed. + + :warning: This method implementation is now deprecated. Refer to L{OnKeyDown} + for the correct one. + """ + + if self.GetActivePage() == -1: + event.Skip() + return + + # We can't leave tab processing to the system on Windows, tabs and keys + # get eaten by the system and not processed properly if we specify both + # wxTAB_TRAVERSAL and wxWANTS_CHARS. And if we specify just wxTAB_TRAVERSAL, + # we don't key arrow key events. + + key = event.GetKeyCode() + + if key == wx.WXK_NUMPAD_PAGEUP: + key = wx.WXK_PAGEUP + if key == wx.WXK_NUMPAD_PAGEDOWN: + key = wx.WXK_PAGEDOWN + if key == wx.WXK_NUMPAD_HOME: + key = wx.WXK_HOME + if key == wx.WXK_NUMPAD_END: + key = wx.WXK_END + if key == wx.WXK_NUMPAD_LEFT: + key = wx.WXK_LEFT + if key == wx.WXK_NUMPAD_RIGHT: + key = wx.WXK_RIGHT + + if key == wx.WXK_TAB or key == wx.WXK_PAGEUP or key == wx.WXK_PAGEDOWN: + + bCtrlDown = event.ControlDown() + bShiftDown = event.ShiftDown() + + bForward = (key == wx.WXK_TAB and not bShiftDown) or (key == wx.WXK_PAGEDOWN) + bWindowChange = (key == wx.WXK_PAGEUP) or (key == wx.WXK_PAGEDOWN) or bCtrlDown + bFromTab = (key == wx.WXK_TAB) + + nb = self.GetParent() + if not nb or not isinstance(nb, AuiNotebook): + event.Skip() + return + + keyEvent = wx.NavigationKeyEvent() + keyEvent.SetDirection(bForward) + keyEvent.SetWindowChange(bWindowChange) + keyEvent.SetFromTab(bFromTab) + keyEvent.SetEventObject(nb) + + if not nb.GetEventHandler().ProcessEvent(keyEvent): + + # Not processed? Do an explicit tab into the page. + win = self.GetWindowFromIdx(self.GetActivePage()) + if win: + win.SetFocus() + + return + + if len(self._pages) < 2: + event.Skip() + return + + newPage = -1 + + if self.GetLayoutDirection() == wx.Layout_RightToLeft: + forwardKey = wx.WXK_LEFT + backwardKey = wx.WXK_RIGHT + else: + forwardKey = wx.WXK_RIGHT + backwardKey = wx.WXK_LEFT + + if key == forwardKey: + if self.GetActivePage() == -1: + newPage = 0 + elif self.GetActivePage() < len(self._pages) - 1: + newPage = self.GetActivePage() + 1 + + elif key == backwardKey: + if self.GetActivePage() == -1: + newPage = len(self._pages) - 1 + elif self.GetActivePage() > 0: + newPage = self.GetActivePage() - 1 + + elif key == wx.WXK_HOME: + newPage = 0 + + elif key == wx.WXK_END: + newPage = len(self._pages) - 1 + + else: + event.Skip() + + if newPage != -1: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + e.SetSelection(newPage) + e.SetOldSelection(newPage) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + else: + event.Skip() + + +# ---------------------------------------------------------------------- + +class TabFrame(wx.PyWindow): + """ + TabFrame is an interesting case. It's important that all child pages + of the multi-notebook control are all actually children of that control + (and not grandchildren). TabFrame facilitates this. There is one + instance of TabFrame for each tab control inside the multi-notebook. + + It's important to know that TabFrame is not a real window, but it merely + used to capture the dimensions/positioning of the internal tab control and + it's managed page windows. + """ + + def __init__(self, parent): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + pre = wx.PrePyWindow() + + self._tabs = None + self._rect = wx.Rect(0, 0, 200, 200) + self._tab_ctrl_height = 20 + self._tab_rect = wx.Rect() + self._parent = parent + + self.PostCreate(pre) + + + def SetTabCtrlHeight(self, h): + """ + Sets the tab control height. + + :param `h`: the tab area height. + """ + + self._tab_ctrl_height = h + + + def DoSetSize(self, x, y, width, height, flags=wx.SIZE_AUTO): + """ + Sets the position and size of the window in pixels. The `flags` + parameter indicates the interpretation of the other params if they are + equal to -1. + + :param `x`: the window `x` position; + :param `y`: the window `y` position; + :param `width`: the window width; + :param `height`: the window height; + :param `flags`: may have one of this bit set: + + =================================== ====================================== + Size Flags Description + =================================== ====================================== + ``wx.SIZE_AUTO`` A -1 indicates that a class-specific default should be used. + ``wx.SIZE_AUTO_WIDTH`` A -1 indicates that a class-specific default should be used for the width. + ``wx.SIZE_AUTO_HEIGHT`` A -1 indicates that a class-specific default should be used for the height. + ``wx.SIZE_USE_EXISTING`` Existing dimensions should be used if -1 values are supplied. + ``wx.SIZE_ALLOW_MINUS_ONE`` Allow dimensions of -1 and less to be interpreted as real dimensions, not default values. + ``wx.SIZE_FORCE`` Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently) + =================================== ====================================== + + :note: Overridden from `wx.PyControl`. + """ + + self._rect = wx.Rect(x, y, max(1, width), max(1, height)) + self.DoSizing() + + + def DoGetSize(self): + """ + Returns the window size. + + :note: Overridden from `wx.PyControl`. + """ + + return self._rect.width, self._rect.height + + + def DoGetClientSize(self): + """ + Returns the window client size. + + :note: Overridden from `wx.PyControl`. + """ + + return self._rect.width, self._rect.height + + + def Show(self, show=True): + """ + Shows/hides the window. + + :param `show`: ``True`` to show the window, ``False`` otherwise. + + :note: Overridden from `wx.PyControl`, this method always returns ``False`` as + L{TabFrame} should never be phisically shown on screen. + """ + + return False + + + def DoSizing(self): + """ Does the actual sizing of the tab control. """ + + if not self._tabs: + return + + hideOnSingle = ((self._tabs.GetAGWFlags() & AUI_NB_HIDE_ON_SINGLE_TAB) and \ + self._tabs.GetPageCount() <= 1) + + if not hideOnSingle and not self._parent._hide_tabs: + tab_height = self._tab_ctrl_height + + self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, self._tab_ctrl_height) + + if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM: + self._tab_rect = wx.Rect(self._rect.x, self._rect.y + self._rect.height - tab_height, + self._rect.width, tab_height) + self._tabs.SetDimensions(self._rect.x, self._rect.y + self._rect.height - tab_height, + self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + else: + + self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + # TODO: elif (GetAGWFlags() & AUI_NB_LEFT) + # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT) + + self._tabs.Refresh() + self._tabs.Update() + + else: + + tab_height = 0 + self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height) + self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height)) + + pages = self._tabs.GetPages() + + for page in pages: + + height = self._rect.height - tab_height + + if height < 0: + # avoid passing negative height to wx.Window.SetSize(), this + # results in assert failures/GTK+ warnings + height = 0 + + if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM: + page.window.SetDimensions(self._rect.x, self._rect.y, self._rect.width, height) + + else: + page.window.SetDimensions(self._rect.x, self._rect.y + tab_height, + self._rect.width, height) + + # TODO: elif (GetAGWFlags() & AUI_NB_LEFT) + # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT) + + if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0: + page.window.ApplyMDIChildFrameRect() + + + def Update(self): + """ + Calling this method immediately repaints the invalidated area of the window + and all of its children recursively while this would usually only happen when + the flow of control returns to the event loop. + + :note: Notice that this function doesn't invalidate any area of the window so + nothing happens if nothing has been invalidated (i.e. marked as requiring a redraw). + Use `Refresh` first if you want to immediately redraw the window unconditionally. + + :note: Overridden from `wx.PyControl`. + """ + + # does nothing + pass + + +# ---------------------------------------------------------------------- +# -- AuiNotebook class implementation -- + +class AuiNotebook(wx.PyPanel): + """ + AuiNotebook is a notebook control which implements many features common in + applications with dockable panes. Specifically, AuiNotebook implements functionality + which allows the user to rearrange tab order via drag-and-drop, split the tab window + into many different splitter configurations, and toggle through different themes to + customize the control's look and feel. + + An effort has been made to try to maintain an API as similar to that of `wx.Notebook`. + + The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy + look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}. + """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=0, agwStyle=AUI_NB_DEFAULT_STYLE): + """ + Default class constructor. + + :param `parent`: the L{AuiNotebook} parent; + :param `id`: an identifier for the control: a value of -1 is taken to mean a default; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `style`: the underlying `wx.PyPanel` window style; + :param `agwStyle`: the AGW-specific window style. This can be a combination of the following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + Default value for `agwStyle` is: + ``AUI_NB_DEFAULT_STYLE`` = ``AUI_NB_TOP`` | ``AUI_NB_TAB_SPLIT`` | ``AUI_NB_TAB_MOVE`` | ``AUI_NB_SCROLL_BUTTONS`` | ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` | ``AUI_NB_MIDDLE_CLICK_CLOSE`` | ``AUI_NB_DRAW_DND_TAB`` + + """ + + self._curpage = -1 + self._tab_id_counter = AuiBaseTabCtrlId + self._dummy_wnd = None + self._hide_tabs = False + self._sash_dclick_unsplit = False + self._tab_ctrl_height = 20 + self._requested_bmp_size = wx.Size(-1, -1) + self._requested_tabctrl_height = -1 + self._textCtrl = None + self._tabBounds = (-1, -1) + self._click_tab = None + + wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL) + self._mgr = framemanager.AuiManager() + self._tabs = AuiTabContainer(self) + + self.InitNotebook(agwStyle) + + + def GetTabContainer(self): + """ Returns the instance of L{AuiTabContainer}. """ + + return self._tabs + + + def InitNotebook(self, agwStyle): + """ + Contains common initialization code called by all constructors. + + :param `agwStyle`: the notebook style. + + :see: L{__init__} + """ + + self.SetName("AuiNotebook") + self._agwFlags = agwStyle + + self._popupWin = None + self._naviIcon = None + self._imageList = None + self._last_drag_x = 0 + + self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font.SetWeight(wx.BOLD) + + self.SetArtProvider(TA.AuiDefaultTabArt()) + + self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0)) + self._dummy_wnd.SetSize((200, 200)) + self._dummy_wnd.Show(False) + + self._mgr.SetManagedWindow(self) + self._mgr.SetAGWFlags(AUI_MGR_DEFAULT) + self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint + + self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False)) + self._mgr.Update() + + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook) + self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick, + id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500) + + self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook) + + + def SetArtProvider(self, art): + """ + Sets the art provider to be used by the notebook. + + :param `art`: an art provider. + """ + + self._tabs.SetArtProvider(art) + self.UpdateTabCtrlHeight(force=True) + + + def SavePerspective(self): + """ + Saves the entire user interface layout into an encoded string, which can then + be stored by the application (probably using `wx.Config`). When a perspective + is restored using L{LoadPerspective}, the entire user interface will return + to the state it was when the perspective was saved. + """ + + # Build list of panes/tabs + tabs = "" + all_panes = self._mgr.GetAllPanes() + + for pane in all_panes: + + if pane.name == "dummy": + continue + + tabframe = pane.window + + if tabs: + tabs += "|" + + tabs += pane.name + "=" + + # add tab id's + page_count = tabframe._tabs.GetPageCount() + + for p in xrange(page_count): + + page = tabframe._tabs.GetPage(p) + page_idx = self._tabs.GetIdxFromWindow(page.window) + + if p: + tabs += "," + + if p == tabframe._tabs.GetActivePage(): + tabs += "+" + elif page_idx == self._curpage: + tabs += "*" + + tabs += "%u"%page_idx + + tabs += "@" + + # Add frame perspective + tabs += self._mgr.SavePerspective() + + return tabs + + + def LoadPerspective(self, layout): + """ + Loads a layout which was saved with L{SavePerspective}. + + :param `layout`: a string which contains a saved L{AuiNotebook} layout. + """ + + # Remove all tab ctrls (but still keep them in main index) + tab_count = self._tabs.GetPageCount() + for i in xrange(tab_count): + wnd = self._tabs.GetWindowFromIdx(i) + + # find out which onscreen tab ctrl owns this tab + ctrl, ctrl_idx = self.FindTab(wnd) + if not ctrl: + return False + + # remove the tab from ctrl + if not ctrl.RemovePage(wnd): + return False + + self.RemoveEmptyTabFrames() + + sel_page = 0 + tabs = layout[0:layout.index("@")] + to_break1 = False + + while 1: + + if "|" not in tabs: + to_break1 = True + tab_part = tabs + else: + tab_part = tabs[0:tabs.index('|')] + + if "=" not in tab_part: + # No pages in this perspective... + return False + + # Get pane name + pane_name = tab_part[0:tab_part.index("=")] + + # create a new tab frame + new_tabs = TabFrame(self) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + dest_tabs = new_tabs._tabs + + # create a pane info structure with the information + # about where the pane should be added + pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False) + self._mgr.AddPane(new_tabs, pane_info) + + # Get list of tab id's and move them to pane + tab_list = tab_part[tab_part.index("=")+1:] + to_break2, active_found = False, False + + while 1: + if "," not in tab_list: + to_break2 = True + tab = tab_list + else: + tab = tab_list[0:tab_list.index(",")] + tab_list = tab_list[tab_list.index(",")+1:] + + # Check if this page has an 'active' marker + c = tab[0] + if c in ['+', '*']: + tab = tab[1:] + + tab_idx = int(tab) + if tab_idx >= self.GetPageCount(): + to_break1 = True + break + + # Move tab to pane + page = self._tabs.GetPage(tab_idx) + newpage_idx = dest_tabs.GetPageCount() + dest_tabs.InsertPage(page.window, page, newpage_idx) + + if c == '+': + dest_tabs.SetActivePage(newpage_idx) + active_found = True + elif c == '*': + sel_page = tab_idx + + if to_break2: + break + + if not active_found: + dest_tabs.SetActivePage(0) + + new_tabs.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + if to_break1: + break + + tabs = tabs[tabs.index('|')+1:] + + # Load the frame perspective + frames = layout[layout.index('@')+1:] + self._mgr.LoadPerspective(frames) + + # Force refresh of selection + self._curpage = -1 + self.SetSelection(sel_page) + + return True + + + def SetTabCtrlHeight(self, height): + """ + Sets the tab height. + + By default, the tab control height is calculated by measuring the text + height and bitmap sizes on the tab captions. + + Calling this method will override that calculation and set the tab control + to the specified height parameter. A call to this method will override + any call to L{SetUniformBitmapSize}. Specifying -1 as the height will + return the control to its default auto-sizing behaviour. + + :param `height`: the tab control area height. + """ + + self._requested_tabctrl_height = height + + # if window is already initialized, recalculate the tab height + if self._dummy_wnd: + self.UpdateTabCtrlHeight() + + + def SetUniformBitmapSize(self, size): + """ + Ensures that all tabs will have the same height, even if some tabs + don't have bitmaps. Passing ``wx.DefaultSize`` to this + function will instruct the control to use dynamic tab height, which is + the default behaviour. Under the default behaviour, when a tab with a + large bitmap is added, the tab control's height will automatically + increase to accommodate the larger bitmap. + + :param `size`: an instance of `wx.Size` specifying the tab bitmap size. + """ + + self._requested_bmp_size = wx.Size(*size) + + # if window is already initialized, recalculate the tab height + if self._dummy_wnd: + self.UpdateTabCtrlHeight() + + + def UpdateTabCtrlHeight(self, force=False): + """ + UpdateTabCtrlHeight() does the actual tab resizing. It's meant + to be used interally. + + :param `force`: ``True`` to force the tab art to repaint. + """ + + # get the tab ctrl height we will use + height = self.CalculateTabCtrlHeight() + + # if the tab control height needs to change, update + # all of our tab controls with the new height + if self._tab_ctrl_height != height or force: + art = self._tabs.GetArtProvider() + + self._tab_ctrl_height = height + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + + if pane.name == "dummy": + continue + + tab_frame = pane.window + tabctrl = tab_frame._tabs + tab_frame.SetTabCtrlHeight(self._tab_ctrl_height) + tabctrl.SetArtProvider(art.Clone()) + tab_frame.DoSizing() + + + def UpdateHintWindowSize(self): + """ Updates the L{AuiManager} hint window size. """ + + size = self.CalculateNewSplitSize() + + # the placeholder hint window should be set to this size + info = self._mgr.GetPane("dummy") + + if info.IsOk(): + info.MinSize(size) + info.BestSize(size) + self._dummy_wnd.SetSize(size) + + + def CalculateNewSplitSize(self): + """ Calculates the size of the new split. """ + + # count number of tab controls + tab_ctrl_count = 0 + all_panes = self._mgr.GetAllPanes() + + for pane in all_panes: + if pane.name == "dummy": + continue + + tab_ctrl_count += 1 + + # if there is only one tab control, the first split + # should happen around the middle + if tab_ctrl_count < 2: + new_split_size = self.GetClientSize() + new_split_size.x /= 2 + new_split_size.y /= 2 + + else: + + # this is in place of a more complicated calculation + # that needs to be implemented + new_split_size = wx.Size(180, 180) + + return new_split_size + + + def CalculateTabCtrlHeight(self): + """ Calculates the tab control area height. """ + + # if a fixed tab ctrl height is specified, + # just return that instead of calculating a + # tab height + if self._requested_tabctrl_height != -1: + return self._requested_tabctrl_height + + # find out new best tab height + art = self._tabs.GetArtProvider() + + return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size) + + + def GetArtProvider(self): + """ Returns the associated art provider. """ + + return self._tabs.GetArtProvider() + + + def SetAGWWindowStyleFlag(self, agwStyle): + """ + Sets the AGW-specific style of the window. + + :param `agwStyle`: the new window style. This can be a combination of the following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + :note: Please note that some styles cannot be changed after the window + creation and that `Refresh` might need to be be called after changing the + others for the change to take place immediately. + + :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``. + """ + + self._agwFlags = agwStyle + + # if the control is already initialized + if self._mgr.GetManagedWindow() == self: + + # let all of the tab children know about the new style + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + tabctrl = tabframe._tabs + tabctrl.SetAGWFlags(self._agwFlags) + tabframe.DoSizing() + tabctrl.Refresh() + tabctrl.Update() + + + def GetAGWWindowStyleFlag(self): + """ + Returns the AGW-specific style of the window. + + :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles. + """ + + return self._agwFlags + + + def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None): + """ + Adds a page. If the `select` parameter is ``True``, calling this will generate a + page change event. + + :param `page`: the page to be added; + :param `caption`: specifies the text for the new page; + :param `select`: specifies whether the page should be selected; + :param `bitmap`: the `wx.Bitmap` to display in the enabled tab; + :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control) + + + def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, + control=None): + """ + This is similar to L{AddPage}, but allows the ability to specify the insert location. + + :param `page_idx`: specifies the position for the new page; + :param `page`: the page to be added; + :param `caption`: specifies the text for the new page; + :param `select`: specifies whether the page should be selected; + :param `bitmap`: the `wx.Bitmap` to display in the enabled tab; + :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + if not page: + return False + + page.Reparent(self) + info = AuiNotebookPage() + info.window = page + info.caption = caption + info.bitmap = bitmap + info.active = False + info.control = control + + originalPaneMgr = framemanager.GetManager(page) + if originalPaneMgr: + originalPane = originalPaneMgr.GetPane(page) + + if originalPane: + info.hasCloseButton = originalPane.HasCloseButton() + + if bitmap.IsOk() and not disabled_bitmap.IsOk(): + disabled_bitmap = MakeDisabledBitmap(bitmap) + info.dis_bitmap = disabled_bitmap + + # if there are currently no tabs, the first added + # tab must be active + if self._tabs.GetPageCount() == 0: + info.active = True + + self._tabs.InsertPage(page, info, page_idx) + + # if that was the first page added, even if + # select is False, it must become the "current page" + # (though no select events will be fired) + if not select and self._tabs.GetPageCount() == 1: + select = True + + active_tabctrl = self.GetActiveTabCtrl() + if page_idx >= active_tabctrl.GetPageCount(): + active_tabctrl.AddPage(page, info) + else: + active_tabctrl.InsertPage(page, info, page_idx) + + force = False + if control: + force = True + control.Reparent(active_tabctrl) + control.Show() + + self.UpdateTabCtrlHeight(force=force) + self.DoSizing() + active_tabctrl.DoShowHide() + + # adjust selected index + if self._curpage >= page_idx: + self._curpage += 1 + + if select: + self.SetSelectionToWindow(page) + + return True + + + def DeletePage(self, page_idx): + """ + Deletes a page at the given index. Calling this method will generate a page + change event. + + :param `page_idx`: the page index to be deleted. + + :note: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well. + + :see: L{RemovePage} + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + wnd = self._tabs.GetWindowFromIdx(page_idx) + # hide the window in advance, as this will + # prevent flicker + wnd.Show(False) + + self.RemoveControlFromPage(page_idx) + + if not self.RemovePage(page_idx): + return False + + wnd.Destroy() + + return True + + + def RemovePage(self, page_idx): + """ + Removes a page, without deleting the window pointer. + + :param `page_idx`: the page index to be removed. + + :note: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window. + + :see: L{DeletePage} + """ + + # save active window pointer + active_wnd = None + if self._curpage >= 0: + active_wnd = self._tabs.GetWindowFromIdx(self._curpage) + + # save pointer of window being deleted + wnd = self._tabs.GetWindowFromIdx(page_idx) + new_active = None + + # make sure we found the page + if not wnd: + return False + + # find out which onscreen tab ctrl owns this tab + ctrl, ctrl_idx = self.FindTab(wnd) + if not ctrl: + return False + + currentPage = ctrl.GetPage(ctrl_idx) + is_curpage = (self._curpage == page_idx) + is_active_in_split = currentPage.active + + # remove the tab from main catalog + if not self._tabs.RemovePage(wnd): + return False + + # remove the tab from the onscreen tab ctrl + ctrl.RemovePage(wnd) + + if is_active_in_split: + + ctrl_new_page_count = ctrl.GetPageCount() + + if ctrl_idx >= ctrl_new_page_count: + ctrl_idx = ctrl_new_page_count - 1 + + if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount(): + + ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl) + + # set new page as active in the tab split + ctrl.SetActivePage(ctrl_idx) + + # if the page deleted was the current page for the + # entire tab control, then record the window + # pointer of the new active page for activation + if is_curpage: + new_active = ctrl.GetWindowFromIdx(ctrl_idx) + + else: + + # we are not deleting the active page, so keep it the same + new_active = active_wnd + + if not new_active: + + # we haven't yet found a new page to active, + # so select the next page from the main tab + # catalogue + + if 0 <= page_idx < self._tabs.GetPageCount(): + new_active = self._tabs.GetPage(page_idx).window + if not new_active and self._tabs.GetPageCount() > 0: + new_active = self._tabs.GetPage(0).window + + self.RemoveEmptyTabFrames() + + # set new active pane + if new_active: + if not self.IsBeingDeleted(): + self._curpage = -1 + self.SetSelectionToWindow(new_active) + else: + self._curpage = -1 + self._tabs.SetNoneActive() + + return True + + + def FindNextActiveTab(self, ctrl_idx, ctrl): + """ + Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled + tabs in it). + + :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status; + :param `ctrl`: an instance of L{AuiTabCtrl}. + """ + + if self.GetEnabled(ctrl_idx): + return ctrl_idx + + for indx in xrange(ctrl_idx, ctrl.GetPageCount()): + if self.GetEnabled(indx): + return indx + + for indx in xrange(ctrl_idx, -1, -1): + if self.GetEnabled(indx): + return indx + + return 0 + + + def HideAllTabs(self, hidden=True): + """ + Hides all tabs on the L{AuiNotebook} control. + + :param `hidden`: if ``True`` hides all tabs. + """ + + self._hide_tabs = hidden + + + def SetSashDClickUnsplit(self, unsplit=True): + """ + Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash. + + :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise. + """ + + self._sash_dclick_unsplit = unsplit + + + def GetSashDClickUnsplit(self): + """ + Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking + on the splitter sash. + """ + + return self._sash_dclick_unsplit + + + def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth): + """ + Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the + ``AUI_NB_TAB_FIXED_WIDTH`` style is defined. + + Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab + width behaviour for L{AuiNotebook}. + + :param `minTabWidth`: the minimum allowed tab width, in pixels; + :param `maxTabWidth`: the maximum allowed tab width, in pixels. + + :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH`` + style is present. + """ + + if minTabWidth > maxTabWidth: + raise Exception("Minimum tab width must be less or equal than maximum tab width") + + self._tabBounds = (minTabWidth, maxTabWidth) + self.SetAGWWindowStyleFlag(self._agwFlags) + + + def GetMinMaxTabWidth(self): + """ + Returns the minimum and the maximum tab widths for L{AuiNotebook} when the + ``AUI_NB_TAB_FIXED_WIDTH`` style is defined. + + :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH`` + style is present. + + :see: L{SetMinMaxTabWidth} for more information. + """ + + return self._tabBounds + + + def GetPageIndex(self, page_wnd): + """ + Returns the page index for the specified window. If the window is not + found in the notebook, ``wx.NOT_FOUND`` is returned. + """ + + return self._tabs.GetIdxFromWindow(page_wnd) + + + def SetPageText(self, page_idx, text): + """ + Sets the tab label for the page. + + :param `page_idx`: the page index; + :param `text`: the new tab label. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.caption != text + page_info.caption = text + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.caption != text + info.caption = text + + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + self.UpdateTabCtrlHeight(force=True) + + return True + + + def GetPageText(self, page_idx): + """ + Returns the tab label for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return "" + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.caption + + + def SetPageBitmap(self, page_idx, bitmap): + """ + Sets the tab bitmap for the page. + + :param `page_idx`: the page index; + :param `bitmap`: an instance of `wx.Bitmap`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.bitmap is not bitmap + page_info.bitmap = bitmap + if bitmap.IsOk() and not page_info.dis_bitmap.IsOk(): + page_info.dis_bitmap = MakeDisabledBitmap(bitmap) + + # tab height might have changed + self.UpdateTabCtrlHeight() + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.bitmap is not bitmap + info.bitmap = bitmap + info.dis_bitmap = page_info.dis_bitmap + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + return True + + + def GetPageBitmap(self, page_idx): + """ + Returns the tab bitmap for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return wx.NullBitmap + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.bitmap + + + def SetImageList(self, imageList): + """ + Sets the image list for the L{AuiNotebook} control. + + :param `imageList`: an instance of `wx.ImageList`. + """ + + self._imageList = imageList + + + def AssignImageList(self, imageList): + """ + Sets the image list for the L{AuiNotebook} control. + + :param `imageList`: an instance of `wx.ImageList`. + """ + + self.SetImageList(imageList) + + + def GetImageList(self): + """ Returns the associated image list (if any). """ + + return self._imageList + + + def SetPageImage(self, page, image): + """ + Sets the image index for the given page. + + :param `page`: the page index; + :param `image`: an index into the image list which was set with L{SetImageList}. + """ + + if page >= self._tabs.GetPageCount(): + return False + + if not isinstance(image, types.IntType): + raise Exception("The image parameter must be an integer, you passed " \ + "%s"%repr(image)) + + if not self._imageList: + raise Exception("To use SetPageImage you need to associate an image list " \ + "Using SetImageList or AssignImageList") + + if image >= self._imageList.GetImageCount(): + raise Exception("Invalid image index (%d), the image list contains only" \ + " (%d) bitmaps"%(image, self._imageList.GetImageCount())) + + if image == -1: + self.SetPageBitmap(page, wx.NullBitmap) + return + + bitmap = self._imageList.GetBitmap(image) + self.SetPageBitmap(page, bitmap) + + + def GetPageImage(self, page): + """ + Returns the image index for the given page. + + :param `page`: the given page for which to retrieve the image index. + """ + + if page >= self._tabs.GetPageCount(): + return False + + bitmap = self.GetPageBitmap(page) + for indx in xrange(self._imageList.GetImageCount()): + imgListBmp = self._imageList.GetBitmap(indx) + if imgListBmp == bitmap: + return indx + + return wx.NOT_FOUND + + + def SetPageTextColour(self, page_idx, colour): + """ + Sets the tab text colour for the page. + + :param `page_idx`: the page index; + :param `colour`: an instance of `wx.Colour`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + should_refresh = page_info.text_colour != colour + page_info.text_colour = colour + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + should_refresh = should_refresh or info.text_colour != colour + info.text_colour = page_info.text_colour + + if should_refresh: + ctrl.Refresh() + ctrl.Update() + + return True + + + def GetPageTextColour(self, page_idx): + """ + Returns the tab text colour for the page. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return wx.NullColour + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + return page_info.text_colour + + + def AddControlToPage(self, page_idx, control): + """ + Adds a control inside a tab (not in the tab area). + + :param `page_idx`: the page index; + :param `control`: an instance of `wx.Window`. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.control = control + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + control.Reparent(ctrl) + + info = ctrl.GetPage(ctrl_idx) + info.control = control + ctrl.Refresh() + ctrl.Update() + + return True + + + def RemoveControlFromPage(self, page_idx): + """ + Removes a control from a tab (not from the tab area). + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + if page_info.control is None: + return False + + page_info.control.Destroy() + page_info.control = None + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.control = None + ctrl.Refresh() + ctrl.Update() + + return True + + + def SetCloseButton(self, page_idx, hasCloseButton): + """ + Sets whether a tab should display a close button or not. + + :param `page_idx`: the page index; + :param `hasCloseButton`: ``True`` if the page displays a close button. + + :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + if self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS == 0: + raise Exception("SetCloseButton can only be used with AUI_NB_CLOSE_ON_ALL_TABS style.") + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.hasCloseButton = hasCloseButton + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.hasCloseButton = page_info.hasCloseButton + ctrl.Refresh() + ctrl.Update() + + return True + + + def HasCloseButton(self, page_idx): + """ + Returns whether a tab displays a close button or not. + + :param `page_idx`: the page index. + + :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + return page_info.hasCloseButton + + + def GetSelection(self): + """ Returns the index of the currently active page, or -1 if none was selected. """ + + return self._curpage + + + def GetCurrentPage(self): + """ Returns the currently active page (not the index), or ``None`` if none was selected. """ + + if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount(): + return self.GetPage(self._curpage) + + return None + + + def EnsureVisible(self, indx): + """ + Ensures the input page index `indx` is visible. + + :param `indx`: the page index. + """ + + self._tabs.MakeTabVisible(indx, self) + + + def SetSelection(self, new_page, force=False): + """ + Sets the page selection. Calling this method will generate a page change event. + + :param `new_page`: the index of the new selection; + :param `force`: whether to force the selection or not. + """ + wnd = self._tabs.GetWindowFromIdx(new_page) + + #Update page access time + self._tabs.GetPages()[new_page].access_time = datetime.datetime.now() + + if not wnd or not self.GetEnabled(new_page): + return self._curpage + + # don't change the page unless necessary + # however, clicking again on a tab should give it the focus. + if new_page == self._curpage and not force: + + ctrl, ctrl_idx = self.FindTab(wnd) + if wx.Window.FindFocus() != ctrl: + ctrl.SetFocus() + + return self._curpage + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId()) + evt.SetSelection(new_page) + evt.SetOldSelection(self._curpage) + evt.SetEventObject(self) + + if not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed(): + + old_curpage = self._curpage + self._curpage = new_page + + # program allows the page change + evt.SetEventType(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED) + self.GetEventHandler().ProcessEvent(evt) + + if not evt.IsAllowed(): # event is no longer allowed after handler + return self._curpage + + ctrl, ctrl_idx = self.FindTab(wnd) + + if ctrl: + self._tabs.SetActivePage(wnd) + ctrl.SetActivePage(ctrl_idx) + self.DoSizing() + ctrl.DoShowHide() + ctrl.MakeTabVisible(ctrl_idx, ctrl) + + # set fonts + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabctrl = pane.window._tabs + if tabctrl != ctrl: + tabctrl.SetSelectedFont(self._normal_font) + else: + tabctrl.SetSelectedFont(self._selected_font) + + tabctrl.Refresh() + tabctrl.Update() + + # Set the focus to the page if we're not currently focused on the tab. + # This is Firefox-like behaviour. + if wnd.IsShownOnScreen() and wx.Window.FindFocus() != ctrl: + wnd.SetFocus() + + return old_curpage + + return self._curpage + + + def SetSelectionToWindow(self, win): + """ + Sets the selection based on the input window `win`. + + :param `win`: a `wx.Window` derived window. + """ + + idx = self._tabs.GetIdxFromWindow(win) + + if idx == wx.NOT_FOUND: + raise Exception("invalid notebook page") + + if not self.GetEnabled(idx): + return + + # since a tab was clicked, let the parent know that we received + # the focus, even if we will assign that focus immediately + # to the child tab in the SetSelection call below + # (the child focus event will also let AuiManager, if any, + # know that the notebook control has been activated) + + parent = self.GetParent() + if parent: + eventFocus = wx.ChildFocusEvent(self) + parent.GetEventHandler().ProcessEvent(eventFocus) + + self.SetSelection(idx) + + + def SetSelectionToPage(self, page): + """ + Sets the selection based on the input page. + + :param `page`: an instance of L{AuiNotebookPage}. + """ + + self.SetSelectionToWindow(page.window) + + + def GetPageCount(self): + """ Returns the number of pages in the notebook. """ + + return self._tabs.GetPageCount() + + + def GetPage(self, page_idx): + """ + Returns the page specified by the given index. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + raise Exception("invalid notebook page") + + return self._tabs.GetWindowFromIdx(page_idx) + + + def GetPageInfo(self, page_idx): + """ + Returns the L{AuiNotebookPage} info structure specified by the given index. + + :param `page_idx`: the page index. + """ + + if page_idx >= self._tabs.GetPageCount(): + raise Exception("invalid notebook page") + + return self._tabs.GetPage(page_idx) + + + def GetEnabled(self, page_idx): + """ + Returns whether the page specified by the index `page_idx` is enabled. + + :param `page_idx`: the page index. + """ + + return self._tabs.GetEnabled(page_idx) + + + def EnableTab(self, page_idx, enable=True): + """ + Enables/disables a page in the notebook. + + :param `page_idx`: the page index; + :param `enable`: ``True`` to enable the page, ``False`` to disable it. + """ + + self._tabs.EnableTab(page_idx, enable) + self.Refresh() + + + def DoSizing(self): + """ Performs all sizing operations in each tab control. """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + tabframe.DoSizing() + + + def GetAuiManager(self): + """ Returns the associated L{AuiManager}. """ + + return self._mgr + + + def GetActiveTabCtrl(self): + """ + Returns the active tab control. It is called to determine which control + gets new windows being added. + """ + + if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount(): + + # find the tab ctrl with the current page + ctrl, idx = self.FindTab(self._tabs.GetPage(self._curpage).window) + if ctrl: + return ctrl + + # no current page, just find the first tab ctrl + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + return tabframe._tabs + + # If there is no tabframe at all, create one + tabframe = TabFrame(self) + tabframe.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter) + + tabframe._tabs.SetAGWFlags(self._agwFlags) + tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False). + PaneBorder((self._agwFlags & AUI_NB_SUB_NOTEBOOK) == 0)) + + self._mgr.Update() + + return tabframe._tabs + + + def FindTab(self, page): + """ + Finds the tab control that currently contains the window as well + as the index of the window in the tab control. It returns ``True`` if the + window was found, otherwise ``False``. + + :param `page`: an instance of L{AuiNotebookPage}. + """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + + page_idx = tabframe._tabs.GetIdxFromWindow(page) + + if page_idx != -1: + + ctrl = tabframe._tabs + idx = page_idx + return ctrl, idx + + return None, wx.NOT_FOUND + + + def Split(self, page, direction): + """ + Performs a split operation programmatically. + + :param `page`: indicates the page that will be split off. This page will also become + the active page after the split. + :param `direction`: specifies where the pane should go, it should be one of the + following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``. + """ + + cli_size = self.GetClientSize() + + # get the page's window pointer + wnd = self.GetPage(page) + if not wnd: + return + + # notebooks with 1 or less pages can't be split + if self.GetPageCount() < 2: + return + + # find out which tab control the page currently belongs to + + src_tabs, src_idx = self.FindTab(wnd) + if not src_tabs: + return + + # choose a split size + if self.GetPageCount() > 2: + split_size = self.CalculateNewSplitSize() + else: + # because there are two panes, always split them + # equally + split_size = self.GetClientSize() + split_size.x /= 2 + split_size.y /= 2 + + # create a new tab frame + new_tabs = TabFrame(self) + new_tabs._rect = wx.RectPS(wx.Point(0, 0), split_size) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + dest_tabs = new_tabs._tabs + + page_info = src_tabs.GetPage(src_idx) + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + # create a pane info structure with the information + # about where the pane should be added + pane_info = framemanager.AuiPaneInfo().Bottom().CaptionVisible(False) + + if direction == wx.LEFT: + + pane_info.Left() + mouse_pt = wx.Point(0, cli_size.y/2) + + elif direction == wx.RIGHT: + + pane_info.Right() + mouse_pt = wx.Point(cli_size.x, cli_size.y/2) + + elif direction == wx.TOP: + + pane_info.Top() + mouse_pt = wx.Point(cli_size.x/2, 0) + + elif direction == wx.BOTTOM: + + pane_info.Bottom() + mouse_pt = wx.Point(cli_size.x/2, cli_size.y) + + self._mgr.AddPane(new_tabs, pane_info, mouse_pt) + self._mgr.Update() + + # remove the page from the source tabs + page_info.active = False + + src_tabs.RemovePage(page_info.window) + + if src_tabs.GetPageCount() > 0: + src_tabs.SetActivePage(0) + src_tabs.DoShowHide() + src_tabs.Refresh() + + # add the page to the destination tabs + dest_tabs.InsertPage(page_info.window, page_info, 0) + + if src_tabs.GetPageCount() == 0: + self.RemoveEmptyTabFrames() + + self.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # force the set selection function reset the selection + self._curpage = -1 + + # set the active page to the one we just split off + self.SetSelectionToPage(page_info) + + self.UpdateHintWindowSize() + + + def UnSplit(self): + """ Restores original view after a tab split. """ + + self.Freeze() + + # remember the tab now selected + nowSelected = self.GetSelection() + # select first tab as destination + self.SetSelection(0) + # iterate all other tabs + for idx in xrange(1, self.GetPageCount()): + # get win reference + win = self.GetPage(idx) + # get tab title + title = self.GetPageText(idx) + # get page bitmap + bmp = self.GetPageBitmap(idx) + # remove from notebook + self.RemovePage(idx) + # re-add in the same position so it will tab + self.InsertPage(idx, win, title, False, bmp) + # restore orignial selected tab + self.SetSelection(nowSelected) + + self.Thaw() + + + def ReparentControl(self, control, dest_tabs): + """ + Reparents a control added inside a tab. + + :param `control`: an instance of `wx.Window`; + :param `dest_tabs`: the destination L{AuiTabCtrl}. + """ + + control.Hide() + control.Reparent(dest_tabs) + + + def UnsplitDClick(self, part, sash_size, pos): + """ + Unsplit the L{AuiNotebook} on sash double-click. + + :param `part`: an UI part representing the sash; + :param `sash_size`: the sash size; + :param `pos`: the double-click mouse position. + + :warning: Due to a bug on MSW, for disabled pages `wx.FindWindowAtPoint` + returns the wrong window. See http://trac.wxwidgets.org/ticket/2942 + """ + + if not self._sash_dclick_unsplit: + # Unsplit not allowed + return + + pos1 = wx.Point(*pos) + pos2 = wx.Point(*pos) + if part.orientation == wx.HORIZONTAL: + pos1.y -= 2*sash_size + pos2.y += 2*sash_size + self.GetTabCtrlHeight() + elif part.orientation == wx.VERTICAL: + pos1.x -= 2*sash_size + pos2.x += 2*sash_size + else: + raise Exception("Invalid UI part orientation") + + pos1, pos2 = self.ClientToScreen(pos1), self.ClientToScreen(pos2) + win1, win2 = wx.FindWindowAtPoint(pos1), wx.FindWindowAtPoint(pos2) + + if isinstance(win1, wx.ScrollBar): + # Hopefully it will work + pos1 = wx.Point(*pos) + shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1) + if part.orientation == wx.HORIZONTAL: + pos1.y -= shift + else: + pos1.x -= shift + + pos1 = self.ClientToScreen(pos1) + win1 = wx.FindWindowAtPoint(pos1) + + if isinstance(win2, wx.ScrollBar): + pos2 = wx.Point(*pos) + shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1) + if part.orientation == wx.HORIZONTAL: + pos2.y += shift + else: + pos2.x += shift + + pos2 = self.ClientToScreen(pos2) + win2 = wx.FindWindowAtPoint(pos2) + + if not win1 or not win2: + # How did we get here? + return + + if isinstance(win1, AuiNotebook) or isinstance(win2, AuiNotebook): + # This is a bug on MSW, for disabled pages wx.FindWindowAtPoint + # returns the wrong window. + # See http://trac.wxwidgets.org/ticket/2942 + return + + tab_frame1, tab_frame2 = self.GetTabFrameFromWindow(win1), self.GetTabFrameFromWindow(win2) + + if not tab_frame1 or not tab_frame2: + return + + tab_ctrl_1, tab_ctrl_2 = tab_frame1._tabs, tab_frame2._tabs + + if tab_ctrl_1.GetPageCount() > tab_ctrl_2.GetPageCount(): + src_tabs = tab_ctrl_2 + dest_tabs = tab_ctrl_1 + else: + src_tabs = tab_ctrl_1 + dest_tabs = tab_ctrl_2 + + selection = -1 + page_count = dest_tabs.GetPageCount() + + for page in xrange(src_tabs.GetPageCount()-1, -1, -1): + # remove the page from the source tabs + page_info = src_tabs.GetPage(page) + if page_info.active: + selection = page_count + page + src_tabs.RemovePage(page_info.window) + + # add the page to the destination tabs + dest_tabs.AddPage(page_info.window, page_info) + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + self.RemoveEmptyTabFrames() + + dest_tabs.DoShowHide() + self.DoSizing() + dest_tabs.Refresh() + self._mgr.Update() + if selection > 0: + wx.CallAfter(dest_tabs.MakeTabVisible, selection, self) + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiNotebook}. + + :param `event`: a `wx.SizeEvent` event to be processed. + """ + + self.UpdateHintWindowSize() + event.Skip() + + + def OnTabClicked(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGING`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + ctrl = event.GetEventObject() + assert ctrl != None + + wnd = ctrl.GetWindowFromIdx(event.GetSelection()) + assert wnd != None + + self.SetSelectionToWindow(wnd) + + + def OnTabBgDClick(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BG_DCLICK`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + # notify owner that the tabbar background has been double-clicked + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabDClick(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_DCLICK`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + # notify owner that the tabbar background has been double-clicked + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + if not self.IsRenamable(event.GetSelection()): + return + + self.EditTab(event.GetSelection()) + + + def OnTabBeginDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._last_drag_x = 0 + + + def OnTabDragMotion(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_DRAG_MOTION`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + if self._textCtrl is not None: + self._textCtrl.StopEditing() + + screen_pt = wx.GetMousePosition() + client_pt = self.ScreenToClient(screen_pt) + zero = wx.Point(0, 0) + + src_tabs = event.GetEventObject() + dest_tabs = self.GetTabCtrlFromPoint(client_pt) + + if dest_tabs == src_tabs: + + # always hide the hint for inner-tabctrl drag + self._mgr.HideHint() + + # if tab moving is not allowed, leave + if not self._agwFlags & AUI_NB_TAB_MOVE: + return + + pt = dest_tabs.ScreenToClient(screen_pt) + + # this is an inner-tab drag/reposition + dest_location_tab = dest_tabs.TabHitTest(pt.x, pt.y) + + if dest_location_tab: + + src_idx = event.GetSelection() + dest_idx = dest_tabs.GetIdxFromWindow(dest_location_tab) + + # prevent jumpy drag + if (src_idx == dest_idx) or dest_idx == -1 or \ + (src_idx > dest_idx and self._last_drag_x <= pt.x) or \ + (src_idx < dest_idx and self._last_drag_x >= pt.x): + + self._last_drag_x = pt.x + return + + src_tab = dest_tabs.GetWindowFromIdx(src_idx) + dest_tabs.MovePage(src_tab, dest_idx) + self._tabs.MovePage(self._tabs.GetPage(src_idx).window, dest_idx) + dest_tabs.SetActivePage(dest_idx) + dest_tabs.DoShowHide() + dest_tabs.Refresh() + self._last_drag_x = pt.x + + return + + # if external drag is allowed, check if the tab is being dragged + # over a different AuiNotebook control + if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE: + + tab_ctrl = wx.FindWindowAtPoint(screen_pt) + + # if we aren't over any window, stop here + if not tab_ctrl: + if self._agwFlags & AUI_NB_TAB_FLOAT: + if self.IsMouseWellOutsideWindow(): + hintRect = wx.RectPS(screen_pt, (400, 300)) + # Use CallAfter so we overwrite the hint that might be + # shown by our superclass: + wx.CallAfter(self._mgr.ShowHint, hintRect) + return + + # make sure we are not over the hint window + if not isinstance(tab_ctrl, wx.Frame): + while tab_ctrl: + if isinstance(tab_ctrl, AuiTabCtrl): + break + + tab_ctrl = tab_ctrl.GetParent() + + if tab_ctrl: + nb = tab_ctrl.GetParent() + + if nb != self: + + hint_rect = tab_ctrl.GetClientRect() + hint_rect.x, hint_rect.y = tab_ctrl.ClientToScreenXY(hint_rect.x, hint_rect.y) + self._mgr.ShowHint(hint_rect) + return + + else: + + if not dest_tabs: + # we are either over a hint window, or not over a tab + # window, and there is no where to drag to, so exit + return + + if self._agwFlags & AUI_NB_TAB_FLOAT: + if self.IsMouseWellOutsideWindow(): + hintRect = wx.RectPS(screen_pt, (400, 300)) + # Use CallAfter so we overwrite the hint that might be + # shown by our superclass: + wx.CallAfter(self._mgr.ShowHint, hintRect) + return + + # if there are less than two panes, split can't happen, so leave + if self._tabs.GetPageCount() < 2: + return + + # if tab moving is not allowed, leave + if not self._agwFlags & AUI_NB_TAB_SPLIT: + return + + if dest_tabs: + + hint_rect = dest_tabs.GetRect() + hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y) + self._mgr.ShowHint(hint_rect) + + else: + rect = self._mgr.CalculateHintRect(self._dummy_wnd, client_pt, zero) + if rect.IsEmpty(): + self._mgr.HideHint() + return + + hit_wnd = wx.FindWindowAtPoint(screen_pt) + if hit_wnd and not isinstance(hit_wnd, AuiNotebook): + tab_frame = self.GetTabFrameFromWindow(hit_wnd) + if tab_frame: + hint_rect = wx.Rect(*tab_frame._rect) + hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y) + rect.Intersect(hint_rect) + self._mgr.ShowHint(rect) + else: + self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero) + else: + self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero) + + + def OnTabEndDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_END_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._mgr.HideHint() + + src_tabs = event.GetEventObject() + if not src_tabs: + raise Exception("no source object?") + + # get the mouse position, which will be used to determine the drop point + mouse_screen_pt = wx.GetMousePosition() + mouse_client_pt = self.ScreenToClient(mouse_screen_pt) + + # check for an external move + if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE: + tab_ctrl = wx.FindWindowAtPoint(mouse_screen_pt) + + while tab_ctrl: + + if isinstance(tab_ctrl, AuiTabCtrl): + break + + tab_ctrl = tab_ctrl.GetParent() + + if tab_ctrl: + + nb = tab_ctrl.GetParent() + + if nb != self: + + # find out from the destination control + # if it's ok to drop this tab here + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, self.GetId()) + e.SetSelection(event.GetSelection()) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + e.SetDragSource(self) + e.Veto() # dropping must be explicitly approved by control owner + + nb.GetEventHandler().ProcessEvent(e) + + if not e.IsAllowed(): + + # no answer or negative answer + self._mgr.HideHint() + return + + # drop was allowed + src_idx = event.GetSelection() + src_page = src_tabs.GetWindowFromIdx(src_idx) + + # Check that it's not an impossible parent relationship + p = nb + while p and not p.IsTopLevel(): + if p == src_page: + return + + p = p.GetParent() + + # get main index of the page + main_idx = self._tabs.GetIdxFromWindow(src_page) + if main_idx == wx.NOT_FOUND: + raise Exception("no source page?") + + # make a copy of the page info + page_info = self._tabs.GetPage(main_idx) + + # remove the page from the source notebook + self.RemovePage(main_idx) + + # reparent the page + src_page.Reparent(nb) + + # Reparent the control in a tab (if any) + if page_info.control: + self.ReparentControl(page_info.control, tab_ctrl) + + # find out the insert idx + dest_tabs = tab_ctrl + pt = dest_tabs.ScreenToClient(mouse_screen_pt) + + target = dest_tabs.TabHitTest(pt.x, pt.y) + insert_idx = -1 + if target: + insert_idx = dest_tabs.GetIdxFromWindow(target) + + # add the page to the new notebook + if insert_idx == -1: + insert_idx = dest_tabs.GetPageCount() + + dest_tabs.InsertPage(page_info.window, page_info, insert_idx) + nb._tabs.AddPage(page_info.window, page_info) + + nb.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # set the selection in the destination tab control + nb.SetSelectionToPage(page_info) + + # notify owner that the tab has been dragged + e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId()) + e2.SetSelection(event.GetSelection()) + e2.SetOldSelection(event.GetSelection()) + e2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e2) + + # notify the target notebook that the tab has been dragged + e3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, nb.GetId()) + e3.SetSelection(insert_idx) + e3.SetOldSelection(insert_idx) + e3.SetEventObject(nb) + nb.GetEventHandler().ProcessEvent(e3) + + return + + if self._agwFlags & AUI_NB_TAB_FLOAT: + self._mgr.HideHint() + if self.IsMouseWellOutsideWindow(): + # Use CallAfter so we our superclass can deal with the event first + wx.CallAfter(self.FloatPage, self.GetSelection()) + event.Skip() + return + + # only perform a tab split if it's allowed + dest_tabs = None + + if self._agwFlags & AUI_NB_TAB_SPLIT and self._tabs.GetPageCount() >= 2: + + # If the pointer is in an existing tab frame, do a tab insert + hit_wnd = wx.FindWindowAtPoint(mouse_screen_pt) + tab_frame = self.GetTabFrameFromTabCtrl(hit_wnd) + insert_idx = -1 + + if tab_frame: + + dest_tabs = tab_frame._tabs + + if dest_tabs == src_tabs: + return + + pt = dest_tabs.ScreenToClient(mouse_screen_pt) + target = dest_tabs.TabHitTest(pt.x, pt.y) + + if target: + insert_idx = dest_tabs.GetIdxFromWindow(target) + + else: + + zero = wx.Point(0, 0) + rect = self._mgr.CalculateHintRect(self._dummy_wnd, mouse_client_pt, zero) + + if rect.IsEmpty(): + # there is no suitable drop location here, exit out + return + + # If there is no tabframe at all, create one + new_tabs = TabFrame(self) + new_tabs._rect = wx.RectPS(wx.Point(0, 0), self.CalculateNewSplitSize()) + new_tabs.SetTabCtrlHeight(self._tab_ctrl_height) + self._tab_id_counter += 1 + new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter) + new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone()) + new_tabs._tabs.SetAGWFlags(self._agwFlags) + + self._mgr.AddPane(new_tabs, framemanager.AuiPaneInfo().Bottom().CaptionVisible(False), mouse_client_pt) + self._mgr.Update() + dest_tabs = new_tabs._tabs + + # remove the page from the source tabs + page_info = src_tabs.GetPage(event.GetSelection()) + + if page_info.control: + self.ReparentControl(page_info.control, dest_tabs) + + page_info.active = False + src_tabs.RemovePage(page_info.window) + + if src_tabs.GetPageCount() > 0: + src_tabs.SetActivePage(0) + src_tabs.DoShowHide() + src_tabs.Refresh() + + # add the page to the destination tabs + if insert_idx == -1: + insert_idx = dest_tabs.GetPageCount() + + dest_tabs.InsertPage(page_info.window, page_info, insert_idx) + + if src_tabs.GetPageCount() == 0: + self.RemoveEmptyTabFrames() + + self.DoSizing() + dest_tabs.DoShowHide() + dest_tabs.Refresh() + + # force the set selection function reset the selection + self._curpage = -1 + + # set the active page to the one we just split off + self.SetSelectionToPage(page_info) + + self.UpdateHintWindowSize() + + # notify owner that the tab has been dragged + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId()) + e.SetSelection(event.GetSelection()) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabCancelDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_CANCEL_DRAG`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + self._mgr.HideHint() + + src_tabs = event.GetEventObject() + if not src_tabs: + raise Exception("no source object?") + + + def IsMouseWellOutsideWindow(self): + """ Returns whether the mouse is well outside the L{AuiNotebook} screen rectangle. """ + + screen_rect = self.GetScreenRect() + screen_rect.Inflate(50, 50) + + return not screen_rect.Contains(wx.GetMousePosition()) + + + def FloatPage(self, page_index): + """ + Float the page in `page_index` by reparenting it to a floating frame. + + :param `page_index`: the index of the page to be floated. + + :warning: When the notebook is more or less full screen, tabs cannot be dragged far + enough outside of the notebook to become floating pages. + """ + + root_manager = framemanager.GetManager(self) + page_title = self.GetPageText(page_index) + page_contents = self.GetPage(page_index) + page_bitmap = self.GetPageBitmap(page_index) + text_colour = self.GetPageTextColour(page_index) + info = self.GetPageInfo(page_index) + + if root_manager and root_manager != self._mgr: + root_manager = framemanager.GetManager(self) + + if hasattr(page_contents, "__floating_size__"): + floating_size = wx.Size(*page_contents.__floating_size__) + else: + floating_size = page_contents.GetBestSize() + if floating_size == wx.DefaultSize: + floating_size = wx.Size(300, 200) + + page_contents.__page_index__ = page_index + page_contents.__aui_notebook__ = self + page_contents.__text_colour__ = text_colour + page_contents.__control__ = info.control + + if info.control: + info.control.Reparent(page_contents) + info.control.Hide() + info.control = None + + self.RemovePage(page_index) + self.RemoveEmptyTabFrames() + + pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \ + FloatingSize(floating_size).BestSize(floating_size).Name("__floating__%s"%page_title). \ + Caption(page_title).Icon(page_bitmap) + root_manager.AddPane(page_contents, pane_info) + root_manager.Bind(framemanager.EVT_AUI_PANE_CLOSE, self.OnCloseFloatingPage) + self.GetActiveTabCtrl().DoShowHide() + self.DoSizing() + root_manager.Update() + + else: + frame = wx.Frame(self, title=page_title, + style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW| + wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR) + + if info.control: + info.control.Reparent(frame) + info.control.Hide() + + frame.bitmap = page_bitmap + frame.page_index = page_index + frame.text_colour = text_colour + frame.control = info.control + page_contents.Reparent(frame) + frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage) + frame.Move(wx.GetMousePosition()) + frame.Show() + self.RemovePage(page_index) + + self.RemoveEmptyTabFrames() + + wx.CallAfter(self.RemoveEmptyTabFrames) + + + def OnCloseFloatingPage(self, event): + """ + Handles the ``wx.EVT_CLOSE`` event for a floating page in L{AuiNotebook}. + + :param `event`: a `wx.CloseEvent` event to be processed. + """ + + root_manager = framemanager.GetManager(self) + if root_manager and root_manager != self._mgr: + pane = event.pane + if pane.name.startswith("__floating__"): + self.ReDockPage(pane) + return + + event.Skip() + else: + event.Skip() + frame = event.GetEventObject() + page_title = frame.GetTitle() + page_contents = list(frame.GetChildren())[-1] + page_contents.Reparent(self) + self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap, control=frame.control) + + if frame.control: + src_tabs, idx = self.FindTab(page_contents) + frame.control.Reparent(src_tabs) + frame.control.Hide() + frame.control = None + + self.SetPageTextColour(frame.page_index, frame.text_colour) + + + def ReDockPage(self, pane): + """ + Re-docks a floating L{AuiNotebook} tab in the original position, when possible. + + :param `pane`: an instance of L{framemanager.AuiPaneInfo}. + """ + + root_manager = framemanager.GetManager(self) + + pane.window.__floating_size__ = wx.Size(*pane.floating_size) + page_index = pane.window.__page_index__ + text_colour = pane.window.__text_colour__ + control = pane.window.__control__ + + root_manager.DetachPane(pane.window) + self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon, control=control) + + self.SetPageTextColour(page_index, text_colour) + self.GetActiveTabCtrl().DoShowHide() + self.DoSizing() + if control: + self.UpdateTabCtrlHeight(force=True) + + self._mgr.Update() + root_manager.Update() + + + def GetTabCtrlFromPoint(self, pt): + """ + Returns the tab control at the specified point. + + :param `pt`: a `wx.Point` object. + """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + if tabframe._tab_rect.Contains(pt): + return tabframe._tabs + + return None + + + def GetTabFrameFromTabCtrl(self, tab_ctrl): + """ + Returns the tab frame associated with a tab control. + + :param `tab_ctrl`: an instance of L{AuiTabCtrl}. + """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + if tabframe._tabs == tab_ctrl: + return tabframe + + return None + + + def GetTabFrameFromWindow(self, wnd): + """ + Returns the tab frame associated with a window. + + :param `wnd`: an instance of `wx.Window`. + """ + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + tabframe = pane.window + for page in tabframe._tabs.GetPages(): + if wnd == page.window: + return tabframe + + return None + + + def RemoveEmptyTabFrames(self): + """ Removes all the empty tab frames. """ + + # if we've just removed the last tab from the source + # tab set, the remove the tab control completely + all_panes = self._mgr.GetAllPanes() + + for indx in xrange(len(all_panes)-1, -1, -1): + pane = all_panes[indx] + if pane.name == "dummy": + continue + + tab_frame = pane.window + if tab_frame._tabs.GetPageCount() == 0: + self._mgr.DetachPane(tab_frame) + tab_frame._tabs.Destroy() + tab_frame._tabs = None + del tab_frame + + # check to see if there is still a center pane + # if there isn't, make a frame the center pane + first_good = None + center_found = False + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + + if pane.dock_direction == AUI_DOCK_CENTRE: + center_found = True + if not first_good: + first_good = pane.window + + if not center_found and first_good: + self._mgr.GetPane(first_good).Centre() + + if not self.IsBeingDeleted(): + self._mgr.Update() + + + def OnChildFocusNotebook(self, event): + """ + Handles the ``wx.EVT_CHILD_FOCUS`` event for L{AuiNotebook}. + + :param `event`: a `wx.ChildFocusEvent` event to be processed. + """ + + # if we're dragging a tab, don't change the current selection. + # This code prevents a bug that used to happen when the hint window + # was hidden. In the bug, the focus would return to the notebook + # child, which would then enter this handler and call + # SetSelection, which is not desired turn tab dragging. + + event.Skip() + + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if pane.name == "dummy": + continue + tabframe = pane.window + if tabframe._tabs.IsDragging(): + return + +## # change the tab selection to the child +## # which was focused +## idx = self._tabs.GetIdxFromWindow(event.GetWindow()) +## if idx != -1 and idx != self._curpage: +## self.SetSelection(idx) + + + def SetNavigatorIcon(self, bmp): + """ + Sets the icon used by the L{TabNavigatorWindow}. + + :param `bmp`: an instance of `wx.Bitmap`. + """ + + if isinstance(bmp, wx.Bitmap) and bmp.IsOk(): + # Make sure image is proper size + if bmp.GetSize() != (16, 16): + img = bmp.ConvertToImage() + img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH) + bmp = wx.BitmapFromImage(img) + self._naviIcon = bmp + else: + raise TypeError, "SetNavigatorIcon requires a valid bitmap" + + + def OnNavigationKeyNotebook(self, event): + """ + Handles the ``wx.EVT_NAVIGATION_KEY`` event for L{AuiNotebook}. + + :param `event`: a `wx.NavigationKeyEvent` event to be processed. + """ + + if event.IsWindowChange(): + if self._agwFlags & AUI_NB_SMART_TABS: + if not self._popupWin: + self._popupWin = TabNavigatorWindow(self, self._naviIcon) + self._popupWin.SetReturnCode(wx.ID_OK) + self._popupWin.ShowModal() + idx = self._popupWin.GetSelectedPage() + self._popupWin.Destroy() + self._popupWin = None + # Need to do CallAfter so that the selection and its + # associated events get processed outside the context of + # this key event. Not doing so causes odd issues with the + # window focus under certain use cases on Windows. + wx.CallAfter(self.SetSelection, idx, True) + else: + # a dialog is already opened + self._popupWin.OnNavigationKey(event) + return + else: + # change pages + # FIXME: the problem with this is that if we have a split notebook, + # we selection may go all over the place. + self.AdvanceSelection(event.GetDirection()) + + else: + # we get this event in 3 cases + # + # a) one of our pages might have generated it because the user TABbed + # out from it in which case we should propagate the event upwards and + # our parent will take care of setting the focus to prev/next sibling + # + # or + # + # b) the parent panel wants to give the focus to us so that we + # forward it to our selected page. We can't deal with this in + # OnSetFocus() because we don't know which direction the focus came + # from in this case and so can't choose between setting the focus to + # first or last panel child + # + # or + # + # c) we ourselves (see MSWTranslateMessage) generated the event + # + parent = self.GetParent() + + # the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE + isFromParent = event.GetEventObject() == parent + isFromSelf = event.GetEventObject() == self + + if isFromParent or isFromSelf: + + # no, it doesn't come from child, case (b) or (c): forward to a + # page but only if direction is backwards (TAB) or from ourselves, + if self.GetSelection() != wx.NOT_FOUND and (not event.GetDirection() or isFromSelf): + + # so that the page knows that the event comes from it's parent + # and is being propagated downwards + event.SetEventObject(self) + + page = self.GetPage(self.GetSelection()) + if not page.GetEventHandler().ProcessEvent(event): + page.SetFocus() + + #else: page manages focus inside it itself + + else: # otherwise set the focus to the notebook itself + + self.SetFocus() + + else: + + # send this event back for the 'wraparound' focus. + winFocus = event.GetCurrentFocus() + + if winFocus: + event.SetEventObject(self) + winFocus.GetEventHandler().ProcessEvent(event) + + + def OnTabButton(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + button_id = event.GetInt() + + if button_id == AUI_BUTTON_CLOSE: + + selection = event.GetSelection() + + if selection == -1: + + # if the close button is to the right, use the active + # page selection to determine which page to close + selection = tabs.GetActivePage() + + if selection == -1 or not tabs.GetEnabled(selection): + return + + if selection != -1: + + close_wnd = tabs.GetWindowFromIdx(selection) + + if close_wnd.GetName() == "__fake__page__": + # This is a notebook preview + previous_active, page_status = close_wnd.__previousStatus + for page, status in zip(tabs.GetPages(), page_status): + page.enabled = status + + main_idx = self._tabs.GetIdxFromWindow(close_wnd) + self.DeletePage(main_idx) + + if previous_active >= 0: + tabs.SetActivePage(previous_active) + page_count = tabs.GetPageCount() + selection = -1 + + for page in xrange(page_count): + # remove the page from the source tabs + page_info = tabs.GetPage(page) + if page_info.active: + selection = page + break + + tabs.DoShowHide() + self.DoSizing() + tabs.Refresh() + + if selection >= 0: + wx.CallAfter(tabs.MakeTabVisible, selection, self) + + # Don't fire the event + return + + # ask owner if it's ok to close the tab + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, self.GetId()) + idx = self._tabs.GetIdxFromWindow(close_wnd) + e.SetSelection(idx) + e.SetOldSelection(event.GetSelection()) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + if not e.IsAllowed(): + return + + if repr(close_wnd.__class__).find("AuiMDIChildFrame") >= 0: + close_wnd.Close() + + else: + main_idx = self._tabs.GetIdxFromWindow(close_wnd) + self.DeletePage(main_idx) + + # notify owner that the tab has been closed + e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, self.GetId()) + e2.SetSelection(idx) + e2.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e2) + + if self.GetPageCount() == 0: + mgr = self.GetAuiManager() + win = mgr.GetManagedWindow() + win.SendSizeEvent() + + + def OnTabMiddleDown(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabMiddleUp(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_UP`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # if the AUI_NB_MIDDLE_CLICK_CLOSE is specified, middle + # click should act like a tab close action. However, first + # give the owner an opportunity to handle the middle up event + # for custom action + + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + if self.GetEventHandler().ProcessEvent(e): + return + if not e.IsAllowed(): + return + + # check if we are supposed to close on middle-up + if self._agwFlags & AUI_NB_MIDDLE_CLICK_CLOSE == 0: + return + + # simulate the user pressing the close button on the tab + event.SetInt(AUI_BUTTON_CLOSE) + self.OnTabButton(event) + + + def OnTabRightDown(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_DOWN`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def OnTabRightUp(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_UP`` event for L{AuiNotebook}. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + tabs = event.GetEventObject() + if not tabs.GetEnabled(event.GetSelection()): + return + + # patch event through to owner + wnd = tabs.GetWindowFromIdx(event.GetSelection()) + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId()) + e.SetSelection(self._tabs.GetIdxFromWindow(wnd)) + e.SetEventObject(self) + self.GetEventHandler().ProcessEvent(e) + + + def SetNormalFont(self, font): + """ + Sets the normal font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._normal_font = font + self.GetArtProvider().SetNormalFont(font) + + + def SetSelectedFont(self, font): + """ + Sets the selected tab font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._selected_font = font + self.GetArtProvider().SetSelectedFont(font) + + + def SetMeasuringFont(self, font): + """ + Sets the font for calculating text measurements. + + :param `font`: a `wx.Font` object. + """ + + self.GetArtProvider().SetMeasuringFont(font) + + + def SetFont(self, font): + """ + Sets the tab font. + + :param `font`: a `wx.Font` object. + + :note: Overridden from `wx.PyPanel`. + """ + + wx.PyPanel.SetFont(self, font) + + selectedFont = wx.Font(font.GetPointSize(), font.GetFamily(), + font.GetStyle(), wx.BOLD, font.GetUnderlined(), + font.GetFaceName(), font.GetEncoding()) + + self.SetNormalFont(font) + self.SetSelectedFont(selectedFont) + self.SetMeasuringFont(selectedFont) + + # Recalculate tab container size based on new font + self.UpdateTabCtrlHeight(force=False) + self.DoSizing() + + return True + + + def GetTabCtrlHeight(self): + """ Returns the tab control height. """ + + return self._tab_ctrl_height + + + def GetHeightForPageHeight(self, pageHeight): + """ + Gets the height of the notebook for a given page height. + + :param `pageHeight`: the given page height. + """ + + self.UpdateTabCtrlHeight() + + tabCtrlHeight = self.GetTabCtrlHeight() + decorHeight = 2 + return tabCtrlHeight + pageHeight + decorHeight + + + def AdvanceSelection(self, forward=True, wrap=True): + """ + Cycles through the tabs. + + :param `forward`: whether to advance forward or backward; + :param `wrap`: ``True`` to return to the first tab if we reach the last tab. + + :note: The call to this function generates the page changing events. + """ + + tabCtrl = self.GetActiveTabCtrl() + newPage = -1 + + focusWin = tabCtrl.FindFocus() + activePage = tabCtrl.GetActivePage() + lenPages = len(tabCtrl.GetPages()) + + if lenPages == 1: + return False + + if forward: + if lenPages > 1: + + if activePage == -1 or activePage == lenPages - 1: + if not wrap: + return False + + newPage = 0 + + elif activePage < lenPages - 1: + newPage = activePage + 1 + + else: + + if lenPages > 1: + if activePage == -1 or activePage == 0: + if not wrap: + return False + + newPage = lenPages - 1 + + elif activePage > 0: + newPage = activePage - 1 + + + if newPage != -1: + if not self.GetEnabled(newPage): + return False + + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId()) + e.SetSelection(newPage) + e.SetOldSelection(activePage) + e.SetEventObject(tabCtrl) + self.GetEventHandler().ProcessEvent(e) + +## if focusWin: +## focusWin.SetFocus() + + return True + + + def ShowWindowMenu(self): + """ + Shows the window menu for the active tab control associated with this + notebook, and returns ``True`` if a selection was made. + """ + + tabCtrl = self.GetActiveTabCtrl() + idx = tabCtrl.GetArtProvider().ShowDropDown(tabCtrl, tabCtrl.GetPages(), tabCtrl.GetActivePage()) + + if not self.GetEnabled(idx): + return False + + if idx != -1: + e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId()) + e.SetSelection(idx) + e.SetOldSelection(tabCtrl.GetActivePage()) + e.SetEventObject(tabCtrl) + self.GetEventHandler().ProcessEvent(e) + + return True + + else: + + return False + + + def AddTabAreaButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap): + """ + Adds a button in the tab area. + + :param `id`: the button identifier. This can be one of the following: + + ============================== ================================= + Button Identifier Description + ============================== ================================= + ``AUI_BUTTON_CLOSE`` Shows a close button on the tab area + ``AUI_BUTTON_WINDOWLIST`` Shows a window list button on the tab area + ``AUI_BUTTON_LEFT`` Shows a left button on the tab area + ``AUI_BUTTON_RIGHT`` Shows a right button on the tab area + ============================== ================================= + + :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``; + :param `normal_bitmap`: the bitmap for an enabled tab; + :param `disabled_bitmap`: the bitmap for a disabled tab. + """ + + active_tabctrl = self.GetActiveTabCtrl() + active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap) + + + def RemoveTabAreaButton(self, id): + """ + Removes a button from the tab area. + + :param `id`: the button identifier. See L{AddTabAreaButton} for a list of button identifiers. + + :see: L{AddTabAreaButton} + """ + + active_tabctrl = self.GetActiveTabCtrl() + active_tabctrl.RemoveButton(id) + + + def HasMultiplePages(self): + """ + This method should be overridden to return ``True`` if this window has multiple pages. All + standard class with multiple pages such as `wx.Notebook`, `wx.Listbook` and `wx.Treebook` + already override it to return ``True`` and user-defined classes with similar behaviour + should do it as well to allow the library to handle such windows appropriately. + + :note: Overridden from `wx.PyPanel`. + """ + + return True + + + def GetDefaultBorder(self): + """ Returns the default border style for L{AuiNotebook}. """ + + return wx.BORDER_NONE + + + def NotebookPreview(self, thumbnail_size=200): + """ + Generates a preview of all the pages in the notebook (MSW and GTK only). + + :param `thumbnail_size`: the maximum size of every page thumbnail. + + :note: this functionality is currently unavailable on wxMac. + """ + + if wx.Platform == "__WXMAC__": + return False + + tabCtrl = self.GetActiveTabCtrl() + activePage = tabCtrl.GetActivePage() + pages = tabCtrl.GetPages() + + pageStatus, pageText = [], [] + + for indx, page in enumerate(pages): + + pageStatus.append(page.enabled) + + if not page.enabled: + continue + + self.SetSelectionToPage(page) + pageText.append(page.caption) + + rect = page.window.GetScreenRect() + bmp = RescaleScreenShot(TakeScreenShot(rect), thumbnail_size) + + page.enabled = False + if indx == 0: + il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True) + + il.Add(bmp) + + # create the list control + listCtrl = wx.ListCtrl(self, style=wx.LC_ICON|wx.LC_AUTOARRANGE|wx.LC_HRULES|wx.LC_VRULES, + name="__fake__page__") + + # assign the image list to it + listCtrl.AssignImageList(il, wx.IMAGE_LIST_NORMAL) + listCtrl.__previousStatus = [activePage, pageStatus] + + # create some items for the list + for indx, text in enumerate(pageText): + listCtrl.InsertImageStringItem(10000, text, indx) + + self.AddPage(listCtrl, "AuiNotebook Preview", True, bitmap=auinotebook_preview.GetBitmap(), disabled_bitmap=wx.NullBitmap) + return True + + + def SetRenamable(self, page_idx, renamable): + """ + Sets whether a tab can be renamed via a left double-click or not. + + :param `page_idx`: the page index; + :param `renamable`: ``True`` if the page can be renamed. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + # update our own tab catalog + page_info = self._tabs.GetPage(page_idx) + page_info.renamable = renamable + + # update what's on screen + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + info = ctrl.GetPage(ctrl_idx) + info.renamable = page_info.renamable + + return True + + + def IsRenamable(self, page_idx): + """ + Returns whether a tab can be renamed or not. + + :param `page_idx`: the page index. + + :returns: ``True`` is a page can be renamed, ``False`` otherwise. + """ + + if page_idx >= self._tabs.GetPageCount(): + return False + + page_info = self._tabs.GetPage(page_idx) + return page_info.renamable + + + def OnRenameCancelled(self, page_index): + """ + Called by L{TabTextCtrl}, to cancel the changes and to send the + `EVT_AUINOTEBOOK_END_LABEL_EDIT` event. + + :param `page_index`: the page index in the notebook. + """ + + # let owner know that the edit was cancelled + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId()) + + evt.SetSelection(page_index) + evt.SetEventObject(self) + evt.SetLabel("") + evt.SetEditCanceled(True) + self.GetEventHandler().ProcessEvent(evt) + + + def OnRenameAccept(self, page_index, value): + """ + Called by L{TabTextCtrl}, to accept the changes and to send the + `EVT_AUINOTEBOOK_END_LABEL_EDIT` event. + + :param `page_index`: the page index in the notebook; + :param `value`: the new label for the tab. + """ + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId()) + evt.SetSelection(page_index) + evt.SetEventObject(self) + evt.SetLabel(value) + evt.SetEditCanceled(False) + + return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed() + + + def ResetTextControl(self): + """ Called by L{TabTextCtrl} when it marks itself for deletion. """ + + if not self._textCtrl: + return + + self._textCtrl.Destroy() + self._textCtrl = None + + # tab height might have changed + self.UpdateTabCtrlHeight(force=True) + + + def EditTab(self, page_index): + """ + Starts the editing of an item label, sending a `EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT` event. + + :param `page_index`: the page index we want to edit. + """ + + if page_index >= self._tabs.GetPageCount(): + return False + + if not self.IsRenamable(page_index): + return False + + page_info = self._tabs.GetPage(page_index) + ctrl, ctrl_idx = self.FindTab(page_info.window) + if not ctrl: + return False + + evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, self.GetId()) + evt.SetSelection(page_index) + evt.SetEventObject(self) + if self.GetEventHandler().ProcessEvent(evt) and not evt.IsAllowed(): + # vetoed by user + return False + + if self._textCtrl is not None and page_info != self._textCtrl.item(): + self._textCtrl.StopEditing() + + self._textCtrl = TabTextCtrl(ctrl, page_info, page_index) + self._textCtrl.SetFocus() + + return True diff --git a/aui/dockart.py b/aui/dockart.py new file mode 100644 index 0000000..17da477 --- /dev/null +++ b/aui/dockart.py @@ -0,0 +1,1162 @@ +""" +Dock art provider code - a dock provider provides all drawing functionality to +the AUI dock manager. This allows the dock manager to have a plugable look-and-feel. + +By default, a L{AuiManager} uses an instance of this class called L{AuiDefaultDockArt} +which provides bitmap art and a colour scheme that is adapted to the major platforms' +look. You can either derive from that class to alter its behaviour or write a +completely new dock art class. Call L{AuiManager.SetArtProvider} to make use this +new dock art. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +import types + +from aui_utilities import BitmapFromBits, StepColour, ChopText, GetBaseColour +from aui_utilities import DrawGradientRectangle, DrawMACCloseButton +from aui_utilities import DarkenBitmap, LightContrastColour +from aui_constants import * + +optionActive = 2**14 + +_ctypes = False + +# Try to import winxptheme for ModernDockArt +if wx.Platform == "__WXMSW__": + try: + import ctypes + import winxptheme + _ctypes = True + except ImportError: + pass + +# -- AuiDefaultDockArt class implementation -- + +class AuiDefaultDockArt(object): + """ + Dock art provider code - a dock provider provides all drawing functionality + to the AUI dock manager. This allows the dock manager to have a plugable + look-and-feel. + + By default, a L{AuiManager} uses an instance of this class called L{AuiDefaultDockArt} + which provides bitmap art and a colour scheme that is adapted to the major + platforms' look. You can either derive from that class to alter its behaviour or + write a completely new dock art class. + + Call L{AuiManager.SetArtProvider} to make use this new dock art. + + + **Metric Ordinals** + + These are the possible pane dock art settings for L{AuiManager}: + + ================================================ ====================================== + Metric Ordinal Constant Description + ================================================ ====================================== + ``AUI_DOCKART_SASH_SIZE`` Customizes the sash size + ``AUI_DOCKART_CAPTION_SIZE`` Customizes the caption size + ``AUI_DOCKART_GRIPPER_SIZE`` Customizes the gripper size + ``AUI_DOCKART_PANE_BORDER_SIZE`` Customizes the pane border size + ``AUI_DOCKART_PANE_BUTTON_SIZE`` Customizes the pane button size + ``AUI_DOCKART_BACKGROUND_COLOUR`` Customizes the background colour + ``AUI_DOCKART_BACKGROUND_GRADIENT_COLOUR`` Customizes the background gradient colour + ``AUI_DOCKART_SASH_COLOUR`` Customizes the sash colour + ``AUI_DOCKART_ACTIVE_CAPTION_COLOUR`` Customizes the active caption colour + ``AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR`` Customizes the active caption gradient colour + ``AUI_DOCKART_INACTIVE_CAPTION_COLOUR`` Customizes the inactive caption colour + ``AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR`` Customizes the inactive gradient caption colour + ``AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR`` Customizes the active caption text colour + ``AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR`` Customizes the inactive caption text colour + ``AUI_DOCKART_BORDER_COLOUR`` Customizes the border colour + ``AUI_DOCKART_GRIPPER_COLOUR`` Customizes the gripper colour + ``AUI_DOCKART_CAPTION_FONT`` Customizes the caption font + ``AUI_DOCKART_GRADIENT_TYPE`` Customizes the gradient type (no gradient, vertical or horizontal) + ``AUI_DOCKART_DRAW_SASH_GRIP`` Draw a sash grip on the sash + ================================================ ====================================== + + + **Gradient Types** + + These are the possible gradient dock art settings for L{AuiManager}: + + ============================================ ====================================== + Gradient Constant Description + ============================================ ====================================== + ``AUI_GRADIENT_NONE`` No gradient on the captions + ``AUI_GRADIENT_VERTICAL`` Vertical gradient on the captions + ``AUI_GRADIENT_HORIZONTAL`` Horizontal gradient on the captions + ============================================ ====================================== + + + **Button States** + + These are the possible pane button / L{AuiNotebook} button / L{AuiToolBar} button states: + + ============================================ ====================================== + Button State Constant Description + ============================================ ====================================== + ``AUI_BUTTON_STATE_NORMAL`` Normal button state + ``AUI_BUTTON_STATE_HOVER`` Hovered button state + ``AUI_BUTTON_STATE_PRESSED`` Pressed button state + ``AUI_BUTTON_STATE_DISABLED`` Disabled button state + ``AUI_BUTTON_STATE_HIDDEN`` Hidden button state + ``AUI_BUTTON_STATE_CHECKED`` Checked button state + ============================================ ====================================== + + + **Button Identifiers** + + These are the possible pane button / L{AuiNotebook} button / L{AuiToolBar} button identifiers: + + ============================================ ====================================== + Button Identifier Description + ============================================ ====================================== + ``AUI_BUTTON_CLOSE`` Shows a close button on the pane + ``AUI_BUTTON_MAXIMIZE_RESTORE`` Shows a maximize/restore button on the pane + ``AUI_BUTTON_MINIMIZE`` Shows a minimize button on the pane + ``AUI_BUTTON_PIN`` Shows a pin button on the pane + ``AUI_BUTTON_OPTIONS`` Shows an option button on the pane (not implemented) + ``AUI_BUTTON_WINDOWLIST`` Shows a window list button on the pane (for L{AuiNotebook}) + ``AUI_BUTTON_LEFT`` Shows a left button on the pane (for L{AuiNotebook}) + ``AUI_BUTTON_RIGHT`` Shows a right button on the pane (for L{AuiNotebook}) + ``AUI_BUTTON_UP`` Shows an up button on the pane (not implemented) + ``AUI_BUTTON_DOWN`` Shows a down button on the pane (not implemented) + ``AUI_BUTTON_CUSTOM1`` Shows a custom button on the pane (not implemented) + ``AUI_BUTTON_CUSTOM2`` Shows a custom button on the pane (not implemented) + ``AUI_BUTTON_CUSTOM3`` Shows a custom button on the pane (not implemented) + ============================================ ====================================== + + """ + + def __init__(self): + """ Default class constructor. """ + + self.Init() + + isMac = wx.Platform == "__WXMAC__" + + if isMac: + self._caption_font = wx.SMALL_FONT + else: + self._caption_font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False) + + self.SetDefaultPaneBitmaps(isMac) + self._restore_bitmap = wx.BitmapFromXPMData(restore_xpm) + + # default metric values + self._sash_size = 4 + + if isMac: + # This really should be implemented in wx.SystemSettings + # There is no way to do this that I am aware outside of using + # the cocoa python bindings. 8 pixels looks correct on my system + # so hard coding it for now. + + # How do I translate this?!? Not sure of the below implementation... + # SInt32 height; + # GetThemeMetric( kThemeMetricSmallPaneSplitterHeight , &height ); + # self._sash_size = height; + + self._sash_size = 8 # Carbon.Appearance.kThemeMetricPaneSplitterHeight + + elif wx.Platform == "__WXGTK__": + self._sash_size = wx.RendererNative.Get().GetSplitterParams(wx.GetTopLevelWindows()[0]).widthSash + + else: + self._sash_size = 4 + + self._caption_size = 19 + self._border_size = 1 + self._button_size = 14 + self._gripper_size = 9 + self._gradient_type = AUI_GRADIENT_VERTICAL + self._draw_sash = False + + + def Init(self): + """ Initializes the dock art. """ + + base_colour = GetBaseColour() + darker1_colour = StepColour(base_colour, 85) + darker2_colour = StepColour(base_colour, 75) + darker3_colour = StepColour(base_colour, 60) + darker4_colour = StepColour(base_colour, 40) + + self._background_colour = base_colour + self._background_gradient_colour = StepColour(base_colour, 180) + + isMac = wx.Platform == "__WXMAC__" + + if isMac: + self._active_caption_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + else: + self._active_caption_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION) + + self._active_caption_gradient_colour = LightContrastColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)) + self._active_caption_text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT) + self._inactive_caption_colour = darker1_colour + self._inactive_caption_gradient_colour = StepColour(base_colour, 97) + self._inactive_caption_text_colour = wx.BLACK + + self._sash_brush = wx.Brush(base_colour) + self._background_brush = wx.Brush(base_colour) + self._border_pen = wx.Pen(darker2_colour) + self._gripper_brush = wx.Brush(base_colour) + self._gripper_pen1 = wx.Pen(darker4_colour) + self._gripper_pen2 = wx.Pen(darker3_colour) + self._gripper_pen3 = wx.WHITE_PEN + + + def GetMetric(self, id): + """ + Gets the value of a certain setting. + + :param `id`: can be one of the size values in `Metric Ordinals`. + """ + + + if id == AUI_DOCKART_SASH_SIZE: + return self._sash_size + elif id == AUI_DOCKART_CAPTION_SIZE: + return self._caption_size + elif id == AUI_DOCKART_GRIPPER_SIZE: + return self._gripper_size + elif id == AUI_DOCKART_PANE_BORDER_SIZE: + return self._border_size + elif id == AUI_DOCKART_PANE_BUTTON_SIZE: + return self._button_size + elif id == AUI_DOCKART_GRADIENT_TYPE: + return self._gradient_type + elif id == AUI_DOCKART_DRAW_SASH_GRIP: + return self._draw_sash + else: + raise Exception("Invalid Metric Ordinal.") + + + def SetMetric(self, id, new_val): + """ + Sets the value of a certain setting using `new_val` + + :param `id`: can be one of the size values in `Metric Ordinals`; + :param `new_val`: the new value of the setting. + """ + + if id == AUI_DOCKART_SASH_SIZE: + self._sash_size = new_val + elif id == AUI_DOCKART_CAPTION_SIZE: + self._caption_size = new_val + elif id == AUI_DOCKART_GRIPPER_SIZE: + self._gripper_size = new_val + elif id == AUI_DOCKART_PANE_BORDER_SIZE: + self._border_size = new_val + elif id == AUI_DOCKART_PANE_BUTTON_SIZE: + self._button_size = new_val + elif id == AUI_DOCKART_GRADIENT_TYPE: + self._gradient_type = new_val + elif id == AUI_DOCKART_DRAW_SASH_GRIP: + self._draw_sash = new_val + else: + raise Exception("Invalid Metric Ordinal.") + + + def GetColor(self, id): + """ + Gets the colour of a certain setting. + + :param `id`: can be one of the colour values in `Metric Ordinals`. + """ + + if id == AUI_DOCKART_BACKGROUND_COLOUR: + return self._background_brush.GetColour() + elif id == AUI_DOCKART_BACKGROUND_GRADIENT_COLOUR: + return self._background_gradient_colour + elif id == AUI_DOCKART_SASH_COLOUR: + return self._sash_brush.GetColour() + elif id == AUI_DOCKART_INACTIVE_CAPTION_COLOUR: + return self._inactive_caption_colour + elif id == AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR: + return self._inactive_caption_gradient_colour + elif id == AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR: + return self._inactive_caption_text_colour + elif id == AUI_DOCKART_ACTIVE_CAPTION_COLOUR: + return self._active_caption_colour + elif id == AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR: + return self._active_caption_gradient_colour + elif id == AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR: + return self._active_caption_text_colour + elif id == AUI_DOCKART_BORDER_COLOUR: + return self._border_pen.GetColour() + elif id == AUI_DOCKART_GRIPPER_COLOUR: + return self._gripper_brush.GetColour() + else: + raise Exception("Invalid Colour Ordinal.") + + + def SetColor(self, id, colour): + """ + Sets the colour of a certain setting. + + :param `id`: can be one of the colour values in `Metric Ordinals`; + :param `colour`: the new value of the setting. + """ + + if isinstance(colour, basestring): + colour = wx.NamedColour(colour) + elif isinstance(colour, types.TupleType): + colour = wx.Colour(*colour) + elif isinstance(colour, types.IntType): + colour = wx.ColourRGB(colour) + + if id == AUI_DOCKART_BACKGROUND_COLOUR: + self._background_brush.SetColour(colour) + elif id == AUI_DOCKART_BACKGROUND_GRADIENT_COLOUR: + self._background_gradient_colour = colour + elif id == AUI_DOCKART_SASH_COLOUR: + self._sash_brush.SetColour(colour) + elif id == AUI_DOCKART_INACTIVE_CAPTION_COLOUR: + self._inactive_caption_colour = colour + if not self._custom_pane_bitmaps and wx.Platform == "__WXMAC__": + # No custom bitmaps for the pane close button + # Change the MAC close bitmap colour + self._inactive_close_bitmap = DrawMACCloseButton(wx.WHITE, colour) + + elif id == AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR: + self._inactive_caption_gradient_colour = colour + elif id == AUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR: + self._inactive_caption_text_colour = colour + elif id == AUI_DOCKART_ACTIVE_CAPTION_COLOUR: + self._active_caption_colour = colour + if not self._custom_pane_bitmaps and wx.Platform == "__WXMAC__": + # No custom bitmaps for the pane close button + # Change the MAC close bitmap colour + self._active_close_bitmap = DrawMACCloseButton(wx.WHITE, colour) + + elif id == AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR: + self._active_caption_gradient_colour = colour + elif id == AUI_DOCKART_ACTIVE_CAPTION_TEXT_COLOUR: + self._active_caption_text_colour = colour + elif id == AUI_DOCKART_BORDER_COLOUR: + self._border_pen.SetColour(colour) + elif id == AUI_DOCKART_GRIPPER_COLOUR: + self._gripper_brush.SetColour(colour) + self._gripper_pen1.SetColour(StepColour(colour, 40)) + self._gripper_pen2.SetColour(StepColour(colour, 60)) + else: + raise Exception("Invalid Colour Ordinal.") + + + GetColour = GetColor + SetColour = SetColor + + def SetFont(self, id, font): + """ + Sets a font setting. + + :param `id`: must be ``AUI_DOCKART_CAPTION_FONT``; + :param `font`: an instance of `wx.Font`. + """ + + if id == AUI_DOCKART_CAPTION_FONT: + self._caption_font = font + + + def GetFont(self, id): + """ + Gets a font setting. + + :param `id`: must be ``AUI_DOCKART_CAPTION_FONT``, otherwise `wx.NullFont` is returned. + """ + + if id == AUI_DOCKART_CAPTION_FONT: + return self._caption_font + + return wx.NullFont + + + def DrawSash(self, dc, window, orient, rect): + """ + Draws a sash between two windows. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `orient`: the sash orientation; + :param `rect`: the sash rectangle. + """ + + # AG: How do we make this work?!? + # RendererNative does not use the sash_brush chosen by the user + # and the rect.GetSize() is ignored as the sash is always drawn + # 3 pixel wide + # wx.RendererNative.Get().DrawSplitterSash(window, dc, rect.GetSize(), pos, orient) + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(self._sash_brush) + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + draw_sash = self.GetMetric(AUI_DOCKART_DRAW_SASH_GRIP) + if draw_sash: + self.DrawSashGripper(dc, orient, rect) + + + def DrawBackground(self, dc, window, orient, rect): + """ + Draws a background. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `orient`: the gradient (if any) orientation; + :param `rect`: the background rectangle. + """ + + dc.SetPen(wx.TRANSPARENT_PEN) + if wx.Platform == "__WXMAC__": + # we have to clear first, otherwise we are drawing a light striped pattern + # over an already darker striped background + dc.SetBrush(wx.WHITE_BRUSH) + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + DrawGradientRectangle(dc, rect, self._background_brush.GetColour(), + self._background_gradient_colour, + AUI_GRADIENT_HORIZONTAL, rect.x, 700) + + + def DrawBorder(self, dc, window, rect, pane): + """ + Draws the pane border. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `rect`: the border rectangle; + :param `pane`: the pane for which the border is drawn. + """ + + drect = wx.Rect(*rect) + + dc.SetPen(self._border_pen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + border_width = self.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + + if pane.IsToolbar(): + + for ii in xrange(0, border_width): + + dc.SetPen(wx.WHITE_PEN) + dc.DrawLine(drect.x, drect.y, drect.x+drect.width, drect.y) + dc.DrawLine(drect.x, drect.y, drect.x, drect.y+drect.height) + dc.SetPen(self._border_pen) + dc.DrawLine(drect.x, drect.y+drect.height-1, + drect.x+drect.width, drect.y+drect.height-1) + dc.DrawLine(drect.x+drect.width-1, drect.y, + drect.x+drect.width-1, drect.y+drect.height) + drect.Deflate(1, 1) + + else: + + for ii in xrange(0, border_width): + + dc.DrawRectangle(drect.x, drect.y, drect.width, drect.height) + drect.Deflate(1, 1) + + + def DrawCaptionBackground(self, dc, rect, pane): + """ + Draws the text caption background in the pane. + + :param `dc`: a `wx.DC` device context; + :param `rect`: the text caption rectangle; + :param `pane`: the pane for which the text background is drawn. + """ + + active = pane.state & optionActive + + if self._gradient_type == AUI_GRADIENT_NONE: + if active: + dc.SetBrush(wx.Brush(self._active_caption_colour)) + else: + dc.SetBrush(wx.Brush(self._inactive_caption_colour)) + + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + else: + + switch_gradient = pane.HasCaptionLeft() + gradient_type = self._gradient_type + if switch_gradient: + gradient_type = (self._gradient_type == AUI_GRADIENT_HORIZONTAL and [AUI_GRADIENT_VERTICAL] or \ + [AUI_GRADIENT_HORIZONTAL])[0] + + if active: + if wx.Platform == "__WXMAC__": + DrawGradientRectangle(dc, rect, self._active_caption_colour, + self._active_caption_gradient_colour, + gradient_type) + else: + DrawGradientRectangle(dc, rect, self._active_caption_gradient_colour, + self._active_caption_colour, + gradient_type) + else: + if wx.Platform == "__WXMAC__": + DrawGradientRectangle(dc, rect, self._inactive_caption_gradient_colour, + self._inactive_caption_colour, + gradient_type) + else: + DrawGradientRectangle(dc, rect, self._inactive_caption_colour, + self._inactive_caption_gradient_colour, + gradient_type) + + + def DrawIcon(self, dc, rect, pane): + """ + Draws the icon in the pane caption area. + + :param `dc`: a `wx.DC` device context; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the icon is drawn. + """ + + # Draw the icon centered vertically + if pane.icon.Ok(): + if pane.HasCaptionLeft(): + bmp = wx.ImageFromBitmap(pane.icon).Rotate90(clockwise=False) + dc.DrawBitmap(bmp.ConvertToBitmap(), rect.x+(rect.width-pane.icon.GetWidth())/2, rect.y+rect.height-2-pane.icon.GetHeight(), True) + else: + dc.DrawBitmap(pane.icon, rect.x+2, rect.y+(rect.height-pane.icon.GetHeight())/2, True) + + + def DrawCaption(self, dc, window, text, rect, pane): + """ + Draws the text in the pane caption. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `text`: the text to be displayed; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the text is drawn. + """ + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetFont(self._caption_font) + + self.DrawCaptionBackground(dc, rect, pane) + + if pane.state & optionActive: + dc.SetTextForeground(self._active_caption_text_colour) + else: + dc.SetTextForeground(self._inactive_caption_text_colour) + + w, h = dc.GetTextExtent("ABCDEFHXfgkj") + + clip_rect = wx.Rect(*rect) + btns = pane.CountButtons() + + captionLeft = pane.HasCaptionLeft() + variable = (captionLeft and [rect.height] or [rect.width])[0] + + variable -= 3 # text offset + variable -= 2 # button padding + + caption_offset = 0 + if pane.icon: + if captionLeft: + caption_offset += pane.icon.GetHeight() + 3 + else: + caption_offset += pane.icon.GetWidth() + 3 + + self.DrawIcon(dc, rect, pane) + + variable -= caption_offset + variable -= btns*(self._button_size + self._border_size) + draw_text = ChopText(dc, text, variable) + + if captionLeft: + dc.DrawRotatedText(draw_text, rect.x+(rect.width/2)-(h/2)-1, rect.y+rect.height-3-caption_offset, 90) + else: + dc.DrawText(draw_text, rect.x+3+caption_offset, rect.y+(rect.height/2)-(h/2)-1) + + + def RequestUserAttention(self, dc, window, text, rect, pane): + """ + Requests the user attention by intermittently highlighting the pane caption. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `text`: the text to be displayed; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the text is drawn. + """ + + state = pane.state + pane.state &= ~optionActive + + for indx in xrange(6): + active = (indx%2 == 0 and [True] or [False])[0] + if active: + pane.state |= optionActive + else: + pane.state &= ~optionActive + + self.DrawCaptionBackground(dc, rect, pane) + self.DrawCaption(dc, window, text, rect, pane) + wx.SafeYield() + wx.MilliSleep(350) + + pane.state = state + + + def DrawGripper(self, dc, window, rect, pane): + """ + Draws a gripper on the pane. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the gripper is drawn. + """ + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(self._gripper_brush) + + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + if not pane.HasGripperTop(): + y = 4 + while 1: + dc.SetPen(self._gripper_pen1) + dc.DrawPoint(rect.x+3, rect.y+y) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(rect.x+3, rect.y+y+1) + dc.DrawPoint(rect.x+4, rect.y+y) + dc.SetPen(self._gripper_pen3) + dc.DrawPoint(rect.x+5, rect.y+y+1) + dc.DrawPoint(rect.x+5, rect.y+y+2) + dc.DrawPoint(rect.x+4, rect.y+y+2) + y = y + 4 + if y > rect.GetHeight() - 4: + break + else: + x = 4 + while 1: + dc.SetPen(self._gripper_pen1) + dc.DrawPoint(rect.x+x, rect.y+3) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(rect.x+x+1, rect.y+3) + dc.DrawPoint(rect.x+x, rect.y+4) + dc.SetPen(self._gripper_pen3) + dc.DrawPoint(rect.x+x+1, rect.y+5) + dc.DrawPoint(rect.x+x+2, rect.y+5) + dc.DrawPoint(rect.x+x+2, rect.y+4) + x = x + 4 + if x > rect.GetWidth() - 4: + break + + + def DrawPaneButton(self, dc, window, button, button_state, _rect, pane): + """ + Draws a pane button in the pane caption area. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `button`: the button to be drawn; + :param `button_state`: the pane button state; + :param `_rect`: the pane caption rectangle; + :param `pane`: the pane for which the button is drawn. + """ + + if not pane: + return + + if button == AUI_BUTTON_CLOSE: + if pane.state & optionActive: + bmp = self._active_close_bitmap + else: + bmp = self._inactive_close_bitmap + + elif button == AUI_BUTTON_PIN: + if pane.state & optionActive: + bmp = self._active_pin_bitmap + else: + bmp = self._inactive_pin_bitmap + + elif button == AUI_BUTTON_MAXIMIZE_RESTORE: + if pane.IsMaximized(): + if pane.state & optionActive: + bmp = self._active_restore_bitmap + else: + bmp = self._inactive_restore_bitmap + else: + if pane.state & optionActive: + bmp = self._active_maximize_bitmap + else: + bmp = self._inactive_maximize_bitmap + + elif button == AUI_BUTTON_MINIMIZE: + if pane.state & optionActive: + bmp = self._active_minimize_bitmap + else: + bmp = self._inactive_minimize_bitmap + + isVertical = pane.HasCaptionLeft() + + rect = wx.Rect(*_rect) + + if isVertical: + old_x = rect.x + rect.x = rect.x + (rect.width/2) - (bmp.GetWidth()/2) + rect.width = old_x + rect.width - rect.x - 1 + else: + old_y = rect.y + rect.y = rect.y + (rect.height/2) - (bmp.GetHeight()/2) + rect.height = old_y + rect.height - rect.y - 1 + + if button_state == AUI_BUTTON_STATE_PRESSED: + rect.x += 1 + rect.y += 1 + + if button_state in [AUI_BUTTON_STATE_HOVER, AUI_BUTTON_STATE_PRESSED]: + + if pane.state & optionActive: + + dc.SetBrush(wx.Brush(StepColour(self._active_caption_colour, 120))) + dc.SetPen(wx.Pen(StepColour(self._active_caption_colour, 70))) + + else: + + dc.SetBrush(wx.Brush(StepColour(self._inactive_caption_colour, 120))) + dc.SetPen(wx.Pen(StepColour(self._inactive_caption_colour, 70))) + + if wx.Platform != "__WXMAC__": + # draw the background behind the button + dc.DrawRectangle(rect.x, rect.y, 15, 15) + else: + # Darker the bitmap a bit + bmp = DarkenBitmap(bmp, self._active_caption_colour, StepColour(self._active_caption_colour, 110)) + + if isVertical: + bmp = wx.ImageFromBitmap(bmp).Rotate90(clockwise=False).ConvertToBitmap() + + # draw the button itself + dc.DrawBitmap(bmp, rect.x, rect.y, True) + + + def DrawSashGripper(self, dc, orient, rect): + """ + Draws a sash gripper on a sash between two windows. + + :param `dc`: a `wx.DC` device context; + :param `orient`: the sash orientation; + :param `rect`: the sash rectangle. + """ + + dc.SetBrush(self._gripper_brush) + + if orient == wx.HORIZONTAL: # horizontal sash + + x = rect.x + int((1.0/4.0)*rect.width) + xend = rect.x + int((3.0/4.0)*rect.width) + y = rect.y + (rect.height/2) - 1 + + while 1: + dc.SetPen(self._gripper_pen3) + dc.DrawRectangle(x, y, 2, 2) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(x+1, y+1) + x = x + 5 + + if x >= xend: + break + + else: + + y = rect.y + int((1.0/4.0)*rect.height) + yend = rect.y + int((3.0/4.0)*rect.height) + x = rect.x + (rect.width/2) - 1 + + while 1: + dc.SetPen(self._gripper_pen3) + dc.DrawRectangle(x, y, 2, 2) + dc.SetPen(self._gripper_pen2) + dc.DrawPoint(x+1, y+1) + y = y + 5 + + if y >= yend: + break + + + def SetDefaultPaneBitmaps(self, isMac): + """ + Assigns the default pane bitmaps. + + :param `isMac`: whether we are on wxMAC or not. + """ + + if isMac: + self._inactive_close_bitmap = DrawMACCloseButton(wx.WHITE, self._inactive_caption_colour) + self._active_close_bitmap = DrawMACCloseButton(wx.WHITE, self._active_caption_colour) + else: + self._inactive_close_bitmap = BitmapFromBits(close_bits, 16, 16, self._inactive_caption_text_colour) + self._active_close_bitmap = BitmapFromBits(close_bits, 16, 16, self._active_caption_text_colour) + + if isMac: + self._inactive_maximize_bitmap = BitmapFromBits(max_bits, 16, 16, wx.WHITE) + self._active_maximize_bitmap = BitmapFromBits(max_bits, 16, 16, wx.WHITE) + else: + self._inactive_maximize_bitmap = BitmapFromBits(max_bits, 16, 16, self._inactive_caption_text_colour) + self._active_maximize_bitmap = BitmapFromBits(max_bits, 16, 16, self._active_caption_text_colour) + + if isMac: + self._inactive_restore_bitmap = BitmapFromBits(restore_bits, 16, 16, wx.WHITE) + self._active_restore_bitmap = BitmapFromBits(restore_bits, 16, 16, wx.WHITE) + else: + self._inactive_restore_bitmap = BitmapFromBits(restore_bits, 16, 16, self._inactive_caption_text_colour) + self._active_restore_bitmap = BitmapFromBits(restore_bits, 16, 16, self._active_caption_text_colour) + + if isMac: + self._inactive_minimize_bitmap = BitmapFromBits(minimize_bits, 16, 16, wx.WHITE) + self._active_minimize_bitmap = BitmapFromBits(minimize_bits, 16, 16, wx.WHITE) + else: + self._inactive_minimize_bitmap = BitmapFromBits(minimize_bits, 16, 16, self._inactive_caption_text_colour) + self._active_minimize_bitmap = BitmapFromBits(minimize_bits, 16, 16, self._active_caption_text_colour) + + self._inactive_pin_bitmap = BitmapFromBits(pin_bits, 16, 16, self._inactive_caption_text_colour) + self._active_pin_bitmap = BitmapFromBits(pin_bits, 16, 16, self._active_caption_text_colour) + + self._custom_pane_bitmaps = False + + + def SetCustomPaneBitmap(self, bmp, button, active, maximize=False): + """ + Sets a custom button bitmap for the pane button. + + :param `bmp`: the actual bitmap to set; + :param `button`: the button identifier; + :param `active`: whether it is the bitmap for the active button or not; + :param `maximize`: used to distinguish between the maximize and restore bitmaps. + """ + + if bmp.GetWidth() > 16 or bmp.GetHeight() > 16: + raise Exception("The input bitmap is too big") + + if button == AUI_BUTTON_CLOSE: + if active: + self._active_close_bitmap = bmp + else: + self._inactive_close_bitmap = bmp + + if wx.Platform == "__WXMAC__": + self._custom_pane_bitmaps = True + + elif button == AUI_BUTTON_PIN: + if active: + self._active_pin_bitmap = bmp + else: + self._inactive_pin_bitmap = bmp + + elif button == AUI_BUTTON_MAXIMIZE_RESTORE: + if maximize: + if active: + self._active_maximize_bitmap = bmp + else: + self._inactive_maximize_bitmap = bmp + else: + if active: + self._active_restore_bitmap = bmp + else: + self._inactive_restore_bitmap = bmp + + elif button == AUI_BUTTON_MINIMIZE: + if active: + self._active_minimize_bitmap = bmp + else: + self._inactive_minimize_bitmap = bmp + + +if _ctypes: + class RECT(ctypes.Structure): + """ Used to handle L{ModernDockArt} on Windows XP/Vista/7. """ + _fields_ = [('left', ctypes.c_ulong),('top', ctypes.c_ulong),('right', ctypes.c_ulong),('bottom', ctypes.c_ulong)] + + def dump(self): + """ Dumps `self` as a `wx.Rect`. """ + return map(int, (self.left, self.top, self.right, self.bottom)) + + + class SIZE(ctypes.Structure): + """ Used to handle L{ModernDockArt} on Windows XP/Vista/7. """ + _fields_ = [('x', ctypes.c_long),('y', ctypes.c_long)] + + +class ModernDockArt(AuiDefaultDockArt): + """ + ModernDockArt is a custom `AuiDockArt` class, that implements a look similar to + Firefox and other recents applications. + + Is uses the `winxptheme` module and XP themes whenever possible, so it should + look good even if the user has a custom theme. + + :note: This dock art is Windows only and will only work if you have installed + Mark Hammond's `pywin32` module (http://sourceforge.net/projects/pywin32/). + """ + + def __init__(self, win): + """ + Default class constructor. + + :param `win`: the window managed by L{AuiManager}. + """ + + AuiDefaultDockArt.__init__(self) + + self.win = win + + # Get the size of a small close button (themed) + hwnd = self.win.GetHandle() + + self.hTheme1 = winxptheme.OpenThemeData(hwnd, "Window") + + self.usingTheme = True + + if not self.hTheme1: + self.usingTheme = False + + self._button_size = 13 + + self._button_border_size = 3 + self._caption_text_indent = 6 + self._caption_size = 22 + + # We only highlight the active pane with the caption text being in bold. + # So we do not want a special colour for active elements. + self._active_close_bitmap = self._inactive_close_bitmap + + self.Init() + + + def Init(self): + """ Initializes the dock art. """ + + AuiDefaultDockArt.Init(self) + + self._active_caption_colour = self._inactive_caption_colour + self._active_caption_text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_CAPTIONTEXT) + self._inactive_caption_text_colour = self._active_caption_text_colour + + + def DrawCaption(self, dc, window, text, rect, pane): + """ + Draws the text in the pane caption. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `text`: the text to be displayed; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the text is drawn. + """ + + dc.SetPen(wx.TRANSPARENT_PEN) + self.DrawCaptionBackground(dc, rect, pane) + + active = ((pane.state & optionActive) and [True] or [False])[0] + + self._caption_font.SetWeight(wx.FONTWEIGHT_BOLD) + dc.SetFont(self._caption_font) + + if active: + dc.SetTextForeground(self._active_caption_text_colour) + else: + dc.SetTextForeground(self._inactive_caption_text_colour) + + w, h = dc.GetTextExtent("ABCDEFHXfgkj") + + clip_rect = wx.Rect(*rect) + btns = pane.CountButtons() + + captionLeft = pane.HasCaptionLeft() + variable = (captionLeft and [rect.height] or [rect.width])[0] + + variable -= 3 # text offset + variable -= 2 # button padding + + caption_offset = 0 + if pane.icon: + if captionLeft: + caption_offset += pane.icon.GetHeight() + 3 + else: + caption_offset += pane.icon.GetWidth() + 3 + + self.DrawIcon(dc, rect, pane) + + diff = -2 + if self.usingTheme: + diff = -1 + + variable -= caption_offset + variable -= btns*(self._button_size + self._button_border_size) + draw_text = ChopText(dc, text, variable) + + if captionLeft: + dc.DrawRotatedText(draw_text, rect.x+(rect.width/2)-(h/2)-diff, rect.y+rect.height-3-caption_offset, 90) + else: + dc.DrawText(draw_text, rect.x+3+caption_offset, rect.y+(rect.height/2)-(h/2)-diff) + + + def DrawCaptionBackground(self, dc, rect, pane): + """ + Draws the text caption background in the pane. + + :param `dc`: a `wx.DC` device context; + :param `rect`: the text caption rectangle; + :param `pane`: the pane for which we are drawing the caption background. + """ + + dc.SetBrush(self._background_brush) + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + active = ((pane.state & optionActive) and [True] or [False])[0] + + if self.usingTheme: + + rectangle = wx.Rect() + + rc = RECT(rectangle.x, rectangle.y, rectangle.width, rectangle.height) + + # If rect x/y values are negative rc.right/bottom values will overflow and winxptheme.DrawThemeBackground + # will raise a TypeError. Ensure they are never negative. + rect.x = max(0, rect.x) + rect.y = max(0, rect.y) + + rc.top = rect.x + rc.left = rect.y + rc.right = rect.x + rect.width + rc.bottom = rect.y + rect.height + + if active: + winxptheme.DrawThemeBackground(self.hTheme1, dc.GetHDC(), 5, 1, (rc.top, rc.left, rc.right, rc.bottom), None) + else: + winxptheme.DrawThemeBackground(self.hTheme1, dc.GetHDC(), 5, 2, (rc.top, rc.left, rc.right, rc.bottom), None) + + else: + + AuiDefaultDockArt.DrawCaptionBackground(self, dc, rect, pane) + + + def RequestUserAttention(self, dc, window, text, rect, pane): + """ + Requests the user attention by intermittently highlighting the pane caption. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `text`: the text to be displayed; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the text is drawn. + """ + + state = pane.state + pane.state &= ~optionActive + + for indx in xrange(6): + active = (indx%2 == 0 and [True] or [False])[0] + if active: + pane.state |= optionActive + else: + pane.state &= ~optionActive + + self.DrawCaptionBackground(dc, rect, pane) + self.DrawCaption(dc, window, text, rect, pane) + wx.SafeYield() + wx.MilliSleep(350) + + pane.state = state + + + def DrawPaneButton(self, dc, window, button, button_state, rect, pane): + """ + Draws a pane button in the pane caption area. + + :param `dc`: a `wx.DC` device context; + :param `window`: an instance of `wx.Window`; + :param `button`: the button to be drawn; + :param `button_state`: the pane button state; + :param `rect`: the pane caption rectangle; + :param `pane`: the pane for which the button is drawn. + """ + + if self.usingTheme: + + hTheme = self.hTheme1 + + # Get the real button position (compensating for borders) + drect = wx.Rect(rect.x, rect.y, self._button_size, self._button_size) + + # Draw the themed close button + rc = RECT(0, 0, 0, 0) + if pane.HasCaptionLeft(): + rc.top = rect.x + self._button_border_size + rc.left = int(rect.y + 1.5*self._button_border_size) + rc.right = rect.x + self._button_size + self._button_border_size + rc.bottom = int(rect.y + self._button_size + 1.5*self._button_border_size) + else: + rc.top = rect.x - self._button_border_size + rc.left = int(rect.y + 1.5*self._button_border_size) + rc.right = rect.x + self._button_size- self._button_border_size + rc.bottom = int(rect.y + self._button_size + 1.5*self._button_border_size) + + if button == AUI_BUTTON_CLOSE: + btntype = 19 + + elif button == AUI_BUTTON_PIN: + btntype = 23 + + elif button == AUI_BUTTON_MAXIMIZE_RESTORE: + if not pane.IsMaximized(): + btntype = 17 + else: + btntype = 21 + else: + btntype = 15 + + state = 4 # CBS_DISABLED + + if pane.state & optionActive: + + if button_state == AUI_BUTTON_STATE_NORMAL: + state = 1 # CBS_NORMAL + + elif button_state == AUI_BUTTON_STATE_HOVER: + state = 2 # CBS_HOT + + elif button_state == AUI_BUTTON_STATE_PRESSED: + state = 3 # CBS_PUSHED + + else: + raise Exception("ERROR: Unknown State.") + + else: # inactive pane + + if button_state == AUI_BUTTON_STATE_NORMAL: + state = 5 # CBS_NORMAL + + elif button_state == AUI_BUTTON_STATE_HOVER: + state = 6 # CBS_HOT + + elif button_state == AUI_BUTTON_STATE_PRESSED: + state = 7 # CBS_PUSHED + + else: + raise Exception("ERROR: Unknown State.") + + try: + winxptheme.DrawThemeBackground(hTheme, dc.GetHDC(), btntype, state, (rc.top, rc.left, rc.right, rc.bottom), None) + except TypeError: + return + + else: + + # Fallback to default closebutton if themes are not enabled + rect2 = wx.Rect(rect.x-4, rect.y+2, rect.width, rect.height) + AuiDefaultDockArt.DrawPaneButton(self, dc, window, button, button_state, rect2, pane) + diff --git a/aui/framemanager.py b/aui/framemanager.py new file mode 100644 index 0000000..9b277bf --- /dev/null +++ b/aui/framemanager.py @@ -0,0 +1,10385 @@ +# --------------------------------------------------------------------------- # +# AUI Library wxPython IMPLEMENTATION +# +# Original C++ Code From Kirix (wxAUI). You Can Find It At: +# +# License: wxWidgets license +# +# http:#www.kirix.com/en/community/opensource/wxaui/about_wxaui.html +# +# Current wxAUI Version Tracked: wxWidgets 2.9.0 SVN HEAD +# +# +# Python Code By: +# +# Andrea Gavana, @ 23 Dec 2005 +# Latest Revision: 10 Mar 2011, 15.00 GMT +# +# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please +# Write To Me At: +# +# andrea.gavana@gmail.com +# gavana@kpo.kz +# +# Or, Obviously, To The wxPython Mailing List!!! +# +# End Of Comments +# --------------------------------------------------------------------------- # + +""" +Description +=========== + +framemanager is the central module of the AUI class framework. + +L{AuiManager} manages the panes associated with it for a particular `wx.Frame`, using +a pane's L{AuiPaneInfo} information to determine each pane's docking and floating +behavior. AuiManager uses wxPython' sizer mechanism to plan the layout of each frame. +It uses a replaceable dock art class to do all drawing, so all drawing is localized +in one area, and may be customized depending on an application's specific needs. + +AuiManager works as follows: the programmer adds panes to the class, or makes +changes to existing pane properties (dock position, floating state, show state, etc...). +To apply these changes, AuiManager's L{AuiManager.Update} function is called. This batch +processing can be used to avoid flicker, by modifying more than one pane at a time, +and then "committing" all of the changes at once by calling `Update()`. + +Panes can be added quite easily:: + + text1 = wx.TextCtrl(self, -1) + text2 = wx.TextCtrl(self, -1) + self._mgr.AddPane(text1, AuiPaneInfo().Left().Caption("Pane Number One")) + self._mgr.AddPane(text2, AuiPaneInfo().Bottom().Caption("Pane Number Two")) + + self._mgr.Update() + + +Later on, the positions can be modified easily. The following will float an +existing pane in a tool window:: + + self._mgr.GetPane(text1).Float() + + +Layers, Rows and Directions, Positions +====================================== + +Inside AUI, the docking layout is figured out by checking several pane parameters. +Four of these are important for determining where a pane will end up. + +**Direction** - Each docked pane has a direction, `Top`, `Bottom`, `Left`, `Right`, or `Center`. +This is fairly self-explanatory. The pane will be placed in the location specified +by this variable. + +**Position** - More than one pane can be placed inside of a "dock". Imagine two panes +being docked on the left side of a window. One pane can be placed over another. +In proportionally managed docks, the pane position indicates it's sequential position, +starting with zero. So, in our scenario with two panes docked on the left side, the +top pane in the dock would have position 0, and the second one would occupy position 1. + +**Row** - A row can allow for two docks to be placed next to each other. One of the most +common places for this to happen is in the toolbar. Multiple toolbar rows are allowed, +the first row being in row 0, and the second in row 1. Rows can also be used on +vertically docked panes. + +**Layer** - A layer is akin to an onion. Layer 0 is the very center of the managed pane. +Thus, if a pane is in layer 0, it will be closest to the center window (also sometimes +known as the "content window"). Increasing layers "swallow up" all layers of a lower +value. This can look very similar to multiple rows, but is different because all panes +in a lower level yield to panes in higher levels. The best way to understand layers +is by running the AUI sample (`AUI.py`). +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx +import time +import types +import warnings + +import auibar +import auibook +import tabmdi +import dockart +import tabart + +from aui_utilities import Clip, PaneCreateStippleBitmap, GetDockingImage, GetSlidingPoints + +from aui_constants import * + +# Define this as a translation function +_ = wx.GetTranslation + +_winxptheme = False +if wx.Platform == "__WXMSW__": + try: + import winxptheme + _winxptheme = True + except ImportError: + pass + +# wxPython version string +_VERSION_STRING = wx.VERSION_STRING + +# AUI Events +wxEVT_AUI_PANE_BUTTON = wx.NewEventType() +wxEVT_AUI_PANE_CLOSE = wx.NewEventType() +wxEVT_AUI_PANE_MAXIMIZE = wx.NewEventType() +wxEVT_AUI_PANE_RESTORE = wx.NewEventType() +wxEVT_AUI_RENDER = wx.NewEventType() +wxEVT_AUI_FIND_MANAGER = wx.NewEventType() +wxEVT_AUI_PANE_MINIMIZE = wx.NewEventType() +wxEVT_AUI_PANE_MIN_RESTORE = wx.NewEventType() +wxEVT_AUI_PANE_FLOATING = wx.NewEventType() +wxEVT_AUI_PANE_FLOATED = wx.NewEventType() +wxEVT_AUI_PANE_DOCKING = wx.NewEventType() +wxEVT_AUI_PANE_DOCKED = wx.NewEventType() +wxEVT_AUI_PANE_ACTIVATED = wx.NewEventType() +wxEVT_AUI_PERSPECTIVE_CHANGED = wx.NewEventType() + +EVT_AUI_PANE_BUTTON = wx.PyEventBinder(wxEVT_AUI_PANE_BUTTON, 0) +""" Fires an event when the user left-clicks on a pane button. """ +EVT_AUI_PANE_CLOSE = wx.PyEventBinder(wxEVT_AUI_PANE_CLOSE, 0) +""" A pane in `AuiManager` has been closed. """ +EVT_AUI_PANE_MAXIMIZE = wx.PyEventBinder(wxEVT_AUI_PANE_MAXIMIZE, 0) +""" A pane in `AuiManager` has been maximized. """ +EVT_AUI_PANE_RESTORE = wx.PyEventBinder(wxEVT_AUI_PANE_RESTORE, 0) +""" A pane in `AuiManager` has been restored from a maximized state. """ +EVT_AUI_RENDER = wx.PyEventBinder(wxEVT_AUI_RENDER, 0) +""" Fires an event every time the AUI frame is being repainted. """ +EVT_AUI_FIND_MANAGER = wx.PyEventBinder(wxEVT_AUI_FIND_MANAGER, 0) +""" Used to find which AUI manager is controlling a certain pane. """ +EVT_AUI_PANE_MINIMIZE = wx.PyEventBinder(wxEVT_AUI_PANE_MINIMIZE, 0) +""" A pane in `AuiManager` has been minimized. """ +EVT_AUI_PANE_MIN_RESTORE = wx.PyEventBinder(wxEVT_AUI_PANE_MIN_RESTORE, 0) +""" A pane in `AuiManager` has been restored from a minimized state. """ +EVT_AUI_PANE_FLOATING = wx.PyEventBinder(wxEVT_AUI_PANE_FLOATING, 0) +""" A pane in `AuiManager` is about to be floated. """ +EVT_AUI_PANE_FLOATED = wx.PyEventBinder(wxEVT_AUI_PANE_FLOATED, 0) +""" A pane in `AuiManager` has been floated. """ +EVT_AUI_PANE_DOCKING = wx.PyEventBinder(wxEVT_AUI_PANE_DOCKING, 0) +""" A pane in `AuiManager` is about to be docked. """ +EVT_AUI_PANE_DOCKED = wx.PyEventBinder(wxEVT_AUI_PANE_DOCKED, 0) +""" A pane in `AuiManager` has been docked. """ +EVT_AUI_PANE_ACTIVATED = wx.PyEventBinder(wxEVT_AUI_PANE_ACTIVATED, 0) +""" A pane in `AuiManager` has been activated. """ +EVT_AUI_PERSPECTIVE_CHANGED = wx.PyEventBinder(wxEVT_AUI_PERSPECTIVE_CHANGED, 0) +""" The layout in `AuiManager` has been changed. """ + +# ---------------------------------------------------------------------------- # + +class AuiDockInfo(object): + """ A class to store all properties of a dock. """ + + def __init__(self): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + object.__init__(self) + + self.dock_direction = 0 + self.dock_layer = 0 + self.dock_row = 0 + self.size = 0 + self.min_size = 0 + self.resizable = True + self.fixed = False + self.toolbar = False + self.rect = wx.Rect() + self.panes = [] + + + def IsOk(self): + """ + Returns whether a dock is valid or not. + + In order to be valid, a dock needs to have a non-zero `dock_direction`. + """ + + return self.dock_direction != 0 + + + def IsHorizontal(self): + """ Returns whether the dock is horizontal or not. """ + + return self.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_BOTTOM] + + + def IsVertical(self): + """ Returns whether the dock is vertical or not. """ + + return self.dock_direction in [AUI_DOCK_LEFT, AUI_DOCK_RIGHT, AUI_DOCK_CENTER] + + +# ---------------------------------------------------------------------------- # + +class AuiDockingGuideInfo(object): + """ A class which holds information about VS2005 docking guide windows. """ + + def __init__(self, other=None): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `other`: another instance of L{AuiDockingGuideInfo}. + """ + + if other: + self.Assign(other) + else: + # window representing the docking target + self.host = None + # dock direction (top, bottom, left, right, center) + self.dock_direction = AUI_DOCK_NONE + + + def Assign(self, other): + """ + Assigns the properties of the `other` L{AuiDockingGuideInfo} to `self`. + + :param `other`: another instance of L{AuiDockingGuideInfo}. + """ + + self.host = other.host + self.dock_direction = other.dock_direction + + + def Host(self, h): + """ + Hosts a docking guide window. + + :param `h`: an instance of L{AuiSingleDockingGuide} or L{AuiCenterDockingGuide}. + """ + + self.host = h + return self + + + def Left(self): + """ Sets the guide window to left docking. """ + + self.dock_direction = AUI_DOCK_LEFT + return self + + + def Right(self): + """ Sets the guide window to right docking. """ + + self.dock_direction = AUI_DOCK_RIGHT + return self + + + def Top(self): + """ Sets the guide window to top docking. """ + + self.dock_direction = AUI_DOCK_TOP + return self + + + def Bottom(self): + """ Sets the guide window to bottom docking. """ + + self.dock_direction = AUI_DOCK_BOTTOM + return self + + + def Center(self): + """ Sets the guide window to center docking. """ + + self.dock_direction = AUI_DOCK_CENTER + return self + + + def Centre(self): + """ Sets the guide window to centre docking. """ + + self.dock_direction = AUI_DOCK_CENTRE + return self + + +# ---------------------------------------------------------------------------- # + +class AuiDockUIPart(object): + """ A class which holds attributes for a UI part in the interface. """ + + typeCaption = 0 + typeGripper = 1 + typeDock = 2 + typeDockSizer = 3 + typePane = 4 + typePaneSizer = 5 + typeBackground = 6 + typePaneBorder = 7 + typePaneButton = 8 + + def __init__(self): + """ + Default class constructor. + Used internally, do not call it in your code! + """ + + self.orientation = wx.VERTICAL + self.type = 0 + self.rect = wx.Rect() + + +# ---------------------------------------------------------------------------- # + +class AuiPaneButton(object): + """ A simple class which describes the caption pane button attributes. """ + + def __init__(self, button_id): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `button_id`: the pane button identifier. + """ + + self.button_id = button_id + + +# ---------------------------------------------------------------------------- # + +# event declarations/classes + +class AuiManagerEvent(wx.PyCommandEvent): + """ A specialized command event class for events sent by L{AuiManager}. """ + + def __init__(self, eventType, id=1): + """ + Default class constructor. + + :param `eventType`: the event kind; + :param `id`: the event identification number. + """ + + wx.PyCommandEvent.__init__(self, eventType, id) + + self.manager = None + self.pane = None + self.button = 0 + self.veto_flag = False + self.canveto_flag = True + self.dc = None + + + def SetManager(self, mgr): + """ + Associates a L{AuiManager} to the current event. + + :param `mgr`: an instance of L{AuiManager}. + """ + + self.manager = mgr + + + def SetDC(self, pdc): + """ + Associates a `wx.DC` device context to this event. + + :param `pdc`: a `wx.DC` device context object. + """ + + self.dc = pdc + + + def SetPane(self, p): + """ + Associates a L{AuiPaneInfo} instance to this event. + + :param `p`: a L{AuiPaneInfo} instance. + """ + + self.pane = p + + + def SetButton(self, b): + """ + Associates a L{AuiPaneButton} instance to this event. + + :param `b`: a L{AuiPaneButton} instance. + """ + + self.button = b + + + def GetManager(self): + """ Returns the associated L{AuiManager} (if any). """ + + return self.manager + + + def GetDC(self): + """ Returns the associated `wx.DC` device context (if any). """ + + return self.dc + + + def GetPane(self): + """ Returns the associated L{AuiPaneInfo} structure (if any). """ + + return self.pane + + + def GetButton(self): + """ Returns the associated L{AuiPaneButton} instance (if any). """ + + return self.button + + + def Veto(self, veto=True): + """ + Prevents the change announced by this event from happening. + + It is in general a good idea to notify the user about the reasons for + vetoing the change because otherwise the applications behaviour (which + just refuses to do what the user wants) might be quite surprising. + + :param `veto`: ``True`` to veto the event, ``False`` otherwise. + """ + + self.veto_flag = veto + + + def GetVeto(self): + """ Returns whether the event has been vetoed or not. """ + + return self.veto_flag + + + def SetCanVeto(self, can_veto): + """ + Sets whether the event can be vetoed or not. + + :param `can_veto`: a bool flag. ``True`` if the event can be vetoed, ``False`` otherwise. + """ + + self.canveto_flag = can_veto + + + def CanVeto(self): + """ Returns whether the event can be vetoed and has been vetoed. """ + + return self.canveto_flag and self.veto_flag + + +# ---------------------------------------------------------------------------- # + +class AuiPaneInfo(object): + """ + AuiPaneInfo specifies all the parameters for a pane. These parameters specify where + the pane is on the screen, whether it is docked or floating, or hidden. In addition, + these parameters specify the pane's docked position, floating position, preferred + size, minimum size, caption text among many other parameters. + """ + + optionFloating = 2**0 + optionHidden = 2**1 + optionLeftDockable = 2**2 + optionRightDockable = 2**3 + optionTopDockable = 2**4 + optionBottomDockable = 2**5 + optionFloatable = 2**6 + optionMovable = 2**7 + optionResizable = 2**8 + optionPaneBorder = 2**9 + optionCaption = 2**10 + optionGripper = 2**11 + optionDestroyOnClose = 2**12 + optionToolbar = 2**13 + optionActive = 2**14 + optionGripperTop = 2**15 + optionMaximized = 2**16 + optionDockFixed = 2**17 + optionNotebookDockable = 2**18 + optionMinimized = 2**19 + optionLeftSnapped = 2**20 + optionRightSnapped = 2**21 + optionTopSnapped = 2**22 + optionBottomSnapped = 2**23 + optionFlyOut = 2**24 + optionCaptionLeft = 2**25 + + buttonClose = 2**26 + buttonMaximize = 2**27 + buttonMinimize = 2**28 + buttonPin = 2**29 + + buttonCustom1 = 2**30 + buttonCustom2 = 2**31 + buttonCustom3 = 2**32 + + savedHiddenState = 2**33 # used internally + actionPane = 2**34 # used internally + wasMaximized = 2**35 # used internally + needsRestore = 2**36 # used internally + + + def __init__(self): + """ Default class constructor. """ + + self.window = None + self.frame = None + self.state = 0 + self.dock_direction = AUI_DOCK_LEFT + self.dock_layer = 0 + self.dock_row = 0 + self.dock_pos = 0 + self.minimize_mode = AUI_MINIMIZE_POS_SMART + self.floating_pos = wx.Point(-1, -1) + self.floating_size = wx.Size(-1, -1) + self.best_size = wx.Size(-1, -1) + self.min_size = wx.Size(-1, -1) + self.max_size = wx.Size(-1, -1) + self.dock_proportion = 0 + self.caption = "" + self.buttons = [] + self.name = "" + self.icon = wx.NullIcon + self.rect = wx.Rect() + self.notebook_id = -1 + self.transparent = 255 + self.needsTransparency = False + self.previousDockPos = None + self.previousDockSize = 0 + self.snapped = 0 + + self.DefaultPane() + + + def dock_direction_get(self): + """ + Getter for the `dock_direction`. + + :see: L{dock_direction_set} for a set of valid docking directions. + """ + + if self.IsMaximized(): + return AUI_DOCK_CENTER + else: + return self._dock_direction + + + def dock_direction_set(self, value): + """ + Setter for the `dock_direction`. + + :param `value`: the docking direction. This cab ne one of the following bits: + + ============================ ======= ============================================= + Dock Flag Value Description + ============================ ======= ============================================= + ``AUI_DOCK_NONE`` 0 No docking direction. + ``AUI_DOCK_TOP`` 1 Top docking direction. + ``AUI_DOCK_RIGHT`` 2 Right docking direction. + ``AUI_DOCK_BOTTOM`` 3 Bottom docking direction. + ``AUI_DOCK_LEFT`` 4 Left docking direction. + ``AUI_DOCK_CENTER`` 5 Center docking direction. + ``AUI_DOCK_CENTRE`` 5 Centre docking direction. + ``AUI_DOCK_NOTEBOOK_PAGE`` 6 Automatic AuiNotebooks docking style. + ============================ ======= ============================================= + + """ + + self._dock_direction = value + + dock_direction = property(dock_direction_get, dock_direction_set) + + def IsOk(self): + """ + Returns ``True`` if the L{AuiPaneInfo} structure is valid. + + :note: A pane structure is valid if it has an associated window. + """ + + return self.window != None + + + def IsMaximized(self): + """ Returns ``True`` if the pane is maximized. """ + + return self.HasFlag(self.optionMaximized) + + + def IsMinimized(self): + """ Returns ``True`` if the pane is minimized. """ + + return self.HasFlag(self.optionMinimized) + + + def IsFixed(self): + """ Returns ``True`` if the pane cannot be resized. """ + + return not self.HasFlag(self.optionResizable) + + + def IsResizeable(self): + """ Returns ``True`` if the pane can be resized. """ + + return self.HasFlag(self.optionResizable) + + + def IsShown(self): + """ Returns ``True`` if the pane is currently shown. """ + + return not self.HasFlag(self.optionHidden) + + + def IsFloating(self): + """ Returns ``True`` if the pane is floating. """ + + return self.HasFlag(self.optionFloating) + + + def IsDocked(self): + """ Returns ``True`` if the pane is docked. """ + + return not self.HasFlag(self.optionFloating) + + + def IsToolbar(self): + """ Returns ``True`` if the pane contains a toolbar. """ + + return self.HasFlag(self.optionToolbar) + + + def IsTopDockable(self): + """ + Returns ``True`` if the pane can be docked at the top + of the managed frame. + """ + + return self.HasFlag(self.optionTopDockable) + + + def IsBottomDockable(self): + """ + Returns ``True`` if the pane can be docked at the bottom + of the managed frame. + """ + + return self.HasFlag(self.optionBottomDockable) + + + def IsLeftDockable(self): + """ + Returns ``True`` if the pane can be docked at the left + of the managed frame. + """ + + return self.HasFlag(self.optionLeftDockable) + + + def IsRightDockable(self): + """ + Returns ``True`` if the pane can be docked at the right + of the managed frame. + """ + + return self.HasFlag(self.optionRightDockable) + + + def IsDockable(self): + """ Returns ``True`` if the pane can be docked. """ + + return self.IsTopDockable() or self.IsBottomDockable() or self.IsLeftDockable() or \ + self.IsRightDockable() or self.IsNotebookDockable() + + + def IsFloatable(self): + """ + Returns ``True`` if the pane can be undocked and displayed as a + floating window. + """ + + return self.HasFlag(self.optionFloatable) + + + def IsMovable(self): + """ + Returns ``True`` if the docked frame can be undocked or moved to + another dock position. + """ + + return self.HasFlag(self.optionMovable) + + + def IsDestroyOnClose(self): + """ + Returns ``True`` if the pane should be destroyed when it is closed. + + Normally a pane is simply hidden when the close button is clicked. Calling L{DestroyOnClose} + with a ``True`` input parameter will cause the window to be destroyed when the user clicks + the pane's close button. + """ + + return self.HasFlag(self.optionDestroyOnClose) + + + def IsNotebookDockable(self): + """ + Returns ``True`` if a pane can be docked on top to another to create a + L{AuiNotebook}. + """ + + return self.HasFlag(self.optionNotebookDockable) + + + def IsTopSnappable(self): + """ Returns ``True`` if the pane can be snapped at the top of the managed frame. """ + + return self.HasFlag(self.optionTopSnapped) + + + def IsBottomSnappable(self): + """ Returns ``True`` if the pane can be snapped at the bottom of the managed frame. """ + + return self.HasFlag(self.optionBottomSnapped) + + + def IsLeftSnappable(self): + """ Returns ``True`` if the pane can be snapped on the left of the managed frame. """ + + return self.HasFlag(self.optionLeftSnapped) + + + def IsRightSnappable(self): + """ Returns ``True`` if the pane can be snapped on the right of the managed frame. """ + + return self.HasFlag(self.optionRightSnapped) + + + def IsSnappable(self): + """ Returns ``True`` if the pane can be snapped. """ + + return self.IsTopSnappable() or self.IsBottomSnappable() or self.IsLeftSnappable() or \ + self.IsRightSnappable() + + + def IsFlyOut(self): + """ Returns ``True`` if the floating pane has a "fly-out" effect. """ + + return self.HasFlag(self.optionFlyOut) + + + def HasCaption(self): + """ Returns ``True`` if the pane displays a caption. """ + + return self.HasFlag(self.optionCaption) + + + def HasCaptionLeft(self): + """ Returns ``True`` if the pane displays a caption on the left (rotated by 90 degrees). """ + + return self.HasFlag(self.optionCaptionLeft) + + + def HasGripper(self): + """ Returns ``True`` if the pane displays a gripper. """ + + return self.HasFlag(self.optionGripper) + + + def HasBorder(self): + """ Returns ``True`` if the pane displays a border. """ + + return self.HasFlag(self.optionPaneBorder) + + + def HasCloseButton(self): + """ Returns ``True`` if the pane displays a button to close the pane. """ + + return self.HasFlag(self.buttonClose) + + + def HasMaximizeButton(self): + """ Returns ``True`` if the pane displays a button to maximize the pane. """ + + return self.HasFlag(self.buttonMaximize) + + + def HasMinimizeButton(self): + """ Returns ``True`` if the pane displays a button to minimize the pane. """ + + return self.HasFlag(self.buttonMinimize) + + + def GetMinimizeMode(self): + """ + Returns the minimization style for this pane. + + Possible return values are: + + ============================== ========= ============================== + Minimize Mode Flag Hex Value Description + ============================== ========= ============================== + ``AUI_MINIMIZE_POS_SMART`` 0x01 Minimizes the pane on the closest tool bar + ``AUI_MINIMIZE_POS_TOP`` 0x02 Minimizes the pane on the top tool bar + ``AUI_MINIMIZE_POS_LEFT`` 0x03 Minimizes the pane on its left tool bar + ``AUI_MINIMIZE_POS_RIGHT`` 0x04 Minimizes the pane on its right tool bar + ``AUI_MINIMIZE_POS_BOTTOM`` 0x05 Minimizes the pane on its bottom tool bar + ``AUI_MINIMIZE_POS_MASK`` 0x07 Mask to filter the position flags + ``AUI_MINIMIZE_CAPT_HIDE`` 0x0 Hides the caption of the minimized pane + ``AUI_MINIMIZE_CAPT_SMART`` 0x08 Displays the caption in the best rotation (horizontal or clockwise) + ``AUI_MINIMIZE_CAPT_HORZ`` 0x10 Displays the caption horizontally + ``AUI_MINIMIZE_CAPT_MASK`` 0x18 Mask to filter the caption flags + ============================== ========= ============================== + + The flags can be filtered with the following masks: + + ============================== ========= ============================== + Minimize Mask Flag Hex Value Description + ============================== ========= ============================== + ``AUI_MINIMIZE_POS_MASK`` 0x07 Filters the position flags + ``AUI_MINIMIZE_CAPT_MASK`` 0x18 Filters the caption flags + ============================== ========= ============================== + + """ + + return self.minimize_mode + + + def HasPinButton(self): + """ Returns ``True`` if the pane displays a button to float the pane. """ + + return self.HasFlag(self.buttonPin) + + + def HasGripperTop(self): + """ Returns ``True`` if the pane displays a gripper at the top. """ + + return self.HasFlag(self.optionGripperTop) + + + def Window(self, w): + """ + Associate a `wx.Window` derived window to this pane. + + This normally does not need to be specified, as the window pointer is + automatically assigned to the L{AuiPaneInfo} structure as soon as it is + added to the manager. + + :param `w`: a `wx.Window` derived window. + """ + + self.window = w + return self + + + def Name(self, name): + """ + Sets the name of the pane so it can be referenced in lookup functions. + + If a name is not specified by the user, a random name is assigned to the pane + when it is added to the manager. + + :param `name`: a string specifying the pane name. + + :warning: If you are using L{AuiManager.SavePerspective} and L{AuiManager.LoadPerspective}, you will have + to specify a name for your pane using L{Name}, as randomly generated names can + not be properly restored. + """ + + self.name = name + return self + + + def Caption(self, caption): + """ + Sets the caption of the pane. + + :param `caption`: a string specifying the pane caption. + """ + + self.caption = caption + return self + + + def Left(self): + """ + Sets the pane dock position to the left side of the frame. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_LEFT`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_LEFT + return self + + + def Right(self): + """ + Sets the pane dock position to the right side of the frame. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_RIGHT`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_RIGHT + return self + + + def Top(self): + """ + Sets the pane dock position to the top of the frame. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_TOP`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_TOP + return self + + + def Bottom(self): + """ + Sets the pane dock position to the bottom of the frame. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_BOTTOM`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_BOTTOM + return self + + + def Center(self): + """ + Sets the pane to the center position of the frame. + + The centre pane is the space in the middle after all border panes (left, top, + right, bottom) are subtracted from the layout. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_CENTER`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_CENTER + return self + + + def Centre(self): + """ + Sets the pane to the center position of the frame. + + The centre pane is the space in the middle after all border panes (left, top, + right, bottom) are subtracted from the layout. + + :note: This is the same thing as calling L{Direction} with ``AUI_DOCK_CENTRE`` as + parameter. + """ + + self.dock_direction = AUI_DOCK_CENTRE + return self + + + def Direction(self, direction): + """ + Determines the direction of the docked pane. It is functionally the + same as calling L{Left}, L{Right}, L{Top} or L{Bottom}, except that docking direction + may be specified programmatically via the parameter `direction`. + + :param `direction`: the direction of the docked pane. + + :see: L{dock_direction_set} for a list of valid docking directions. + """ + + self.dock_direction = direction + return self + + + def Layer(self, layer): + """ + Determines the layer of the docked pane. + + The dock layer is similar to an onion, the inner-most layer being layer 0. Each + shell moving in the outward direction has a higher layer number. This allows for + more complex docking layout formation. + + :param `layer`: the layer of the docked pane. + """ + + self.dock_layer = layer + return self + + + def Row(self, row): + """ + Determines the row of the docked pane. + + :param `row`: the row of the docked pane. + """ + + self.dock_row = row + return self + + + def Position(self, pos): + """ + Determines the position of the docked pane. + + :param `pos`: the position of the docked pane. + """ + + self.dock_pos = pos + return self + + + def MinSize(self, arg1=None, arg2=None): + """ + Sets the minimum size of the pane. + + This method is split in 2 versions depending on the input type. If `arg1` is + a `wx.Size` object, then L{MinSize1} is called. Otherwise, L{MinSize2} is called. + + :param `arg1`: a `wx.Size` object, a (x, y) tuple or or a `x` coordinate. + :param `arg2`: a `y` coordinate (only if `arg1` is a `x` coordinate, otherwise unused). + """ + + if isinstance(arg1, wx.Size): + ret = self.MinSize1(arg1) + elif isinstance(arg1, types.TupleType): + ret = self.MinSize1(wx.Size(*arg1)) + else: + ret = self.MinSize2(arg1, arg2) + + return ret + + + def MinSize1(self, size): + """ + Sets the minimum size of the pane. + + :see: L{MinSize} for an explanation of input parameters. + """ + self.min_size = size + return self + + + def MinSize2(self, x, y): + """ + Sets the minimum size of the pane. + + :see: L{MinSize} for an explanation of input parameters. + """ + + self.min_size = wx.Size(x, y) + return self + + + def MaxSize(self, arg1=None, arg2=None): + """ + Sets the maximum size of the pane. + + This method is split in 2 versions depending on the input type. If `arg1` is + a `wx.Size` object, then L{MaxSize1} is called. Otherwise, L{MaxSize2} is called. + + :param `arg1`: a `wx.Size` object, a (x, y) tuple or a `x` coordinate. + :param `arg2`: a `y` coordinate (only if `arg1` is a `x` coordinate, otherwise unused). + """ + + if isinstance(arg1, wx.Size): + ret = self.MaxSize1(arg1) + elif isinstance(arg1, types.TupleType): + ret = self.MaxSize1(wx.Size(*arg1)) + else: + ret = self.MaxSize2(arg1, arg2) + + return ret + + + def MaxSize1(self, size): + """ + Sets the maximum size of the pane. + + :see: L{MaxSize} for an explanation of input parameters. + """ + + self.max_size = size + return self + + + def MaxSize2(self, x, y): + """ + Sets the maximum size of the pane. + + :see: L{MaxSize} for an explanation of input parameters. + """ + + self.max_size.Set(x,y) + return self + + + def BestSize(self, arg1=None, arg2=None): + """ + Sets the ideal size for the pane. The docking manager will attempt to use + this size as much as possible when docking or floating the pane. + + This method is split in 2 versions depending on the input type. If `arg1` is + a `wx.Size` object, then L{BestSize1} is called. Otherwise, L{BestSize2} is called. + + :param `arg1`: a `wx.Size` object, a (x, y) tuple or a `x` coordinate. + :param `arg2`: a `y` coordinate (only if `arg1` is a `x` coordinate, otherwise unused). + """ + + if isinstance(arg1, wx.Size): + ret = self.BestSize1(arg1) + elif isinstance(arg1, types.TupleType): + ret = self.BestSize1(wx.Size(*arg1)) + else: + ret = self.BestSize2(arg1, arg2) + + return ret + + + def BestSize1(self, size): + """ + Sets the best size of the pane. + + :see: L{BestSize} for an explanation of input parameters. + """ + + self.best_size = size + return self + + + def BestSize2(self, x, y): + """ + Sets the best size of the pane. + + :see: L{BestSize} for an explanation of input parameters. + """ + + self.best_size.Set(x,y) + return self + + + def FloatingPosition(self, pos): + """ + Sets the position of the floating pane. + + :param `pos`: a `wx.Point` or a tuple indicating the pane floating position. + """ + + self.floating_pos = wx.Point(*pos) + return self + + + def FloatingSize(self, size): + """ + Sets the size of the floating pane. + + :param `size`: a `wx.Size` or a tuple indicating the pane floating size. + """ + + self.floating_size = wx.Size(*size) + return self + + + def Maximize(self): + """ Makes the pane take up the full area.""" + + return self.SetFlag(self.optionMaximized, True) + + + def Minimize(self): + """ + Makes the pane minimized in a L{AuiToolBar}. + + Clicking on the minimize button causes a new L{AuiToolBar} to be created + and added to the frame manager, (currently the implementation is such that + panes at West will have a toolbar at the right, panes at South will have + toolbars at the bottom etc...) and the pane is hidden in the manager. + + Clicking on the restore button on the newly created toolbar will result in the + toolbar being removed and the original pane being restored. + """ + + return self.SetFlag(self.optionMinimized, True) + + + def MinimizeMode(self, mode): + """ + Sets the expected minimized mode if the MinimizeButton() is visible. + + The minimized pane can have a specific position in the work space: + + ============================== ========= ============================== + Minimize Mode Flag Hex Value Description + ============================== ========= ============================== + ``AUI_MINIMIZE_POS_SMART`` 0x01 Minimizes the pane on the closest tool bar + ``AUI_MINIMIZE_POS_TOP`` 0x02 Minimizes the pane on the top tool bar + ``AUI_MINIMIZE_POS_LEFT`` 0x03 Minimizes the pane on its left tool bar + ``AUI_MINIMIZE_POS_RIGHT`` 0x04 Minimizes the pane on its right tool bar + ``AUI_MINIMIZE_POS_BOTTOM`` 0x05 Minimizes the pane on its bottom tool bar + ============================== ========= ============================== + + The caption of the minimized pane can be displayed in different modes: + + ============================== ========= ============================== + Caption Mode Flag Hex Value Description + ============================== ========= ============================== + ``AUI_MINIMIZE_CAPT_HIDE`` 0x0 Hides the caption of the minimized pane + ``AUI_MINIMIZE_CAPT_SMART`` 0x08 Displays the caption in the best rotation (horizontal in the top and in the bottom tool bar or clockwise in the right and in the left tool bar) + ``AUI_MINIMIZE_CAPT_HORZ`` 0x10 Displays the caption horizontally + ============================== ========= ============================== + + """ + + self.minimize_mode = mode + return self + + + def Restore(self): + """ Is the reverse of L{Maximize} and L{Minimize}.""" + + return self.SetFlag(self.optionMaximized or self.optionMinimized, False) + + + def Fixed(self): + """ + Forces a pane to be fixed size so that it cannot be resized. + After calling L{Fixed}, L{IsFixed} will return ``True``. + """ + + return self.SetFlag(self.optionResizable, False) + + + def Resizable(self, resizable=True): + """ + Allows a pane to be resizable if `resizable` is ``True``, and forces + it to be a fixed size if `resizeable` is ``False``. + + If `resizable` is ``False``, this is simply an antonym for L{Fixed}. + + :param `resizable`: whether the pane will be resizeable or not. + """ + + return self.SetFlag(self.optionResizable, resizable) + + + def Transparent(self, alpha): + """ + Makes the pane transparent when floating. + + :param `alpha`: an integer value between 0 and 255 for pane transparency. + """ + + if alpha < 0 or alpha > 255: + raise Exception("Invalid transparency value (%s)"%repr(alpha)) + + self.transparent = alpha + self.needsTransparency = True + + + def Dock(self): + """ + Indicates that a pane should be docked. It is the opposite of L{Float}. + """ + + if self.IsNotebookPage(): + self.notebook_id = -1 + self.dock_direction = AUI_DOCK_NONE + + return self.SetFlag(self.optionFloating, False) + + + def Float(self): + """ + Indicates that a pane should be floated. It is the opposite of L{Dock}. + """ + + if self.IsNotebookPage(): + self.notebook_id = -1 + self.dock_direction = AUI_DOCK_NONE + + return self.SetFlag(self.optionFloating, True) + + + def Hide(self): + """ + Indicates that a pane should be hidden. + + Calling L{Show} (``False``) achieve the same effect. + """ + + return self.SetFlag(self.optionHidden, True) + + + def Show(self, show=True): + """ + Indicates that a pane should be shown. + + :param `show`: whether the pane should be shown or not. + """ + + return self.SetFlag(self.optionHidden, not show) + + + # By defaulting to 1000, the tab will get placed at the end + def NotebookPage(self, id, tab_position=1000): + """ + Forces a pane to be a notebook page, so that the pane can be + docked on top to another to create a L{AuiNotebook}. + + :param `id`: the notebook id; + :param `tab_position`: the tab number of the pane once docked in a notebook. + """ + + # Remove any floating frame + self.Dock() + self.notebook_id = id + self.dock_pos = tab_position + self.dock_row = 0 + self.dock_layer = 0 + self.dock_direction = AUI_DOCK_NOTEBOOK_PAGE + + return self + + + def NotebookControl(self, id): + """ + Forces a pane to be a notebook control (L{AuiNotebook}). + + :param `id`: the notebook id. + """ + + self.notebook_id = id + self.window = None + self.buttons = [] + + if self.dock_direction == AUI_DOCK_NOTEBOOK_PAGE: + self.dock_direction = AUI_DOCK_NONE + + return self + + + def HasNotebook(self): + """ Returns whether a pane has a L{AuiNotebook} or not. """ + + return self.notebook_id >= 0 + + + def IsNotebookPage(self): + """ Returns whether the pane is a notebook page in a L{AuiNotebook}. """ + + return self.notebook_id >= 0 and self.dock_direction == AUI_DOCK_NOTEBOOK_PAGE + + + def IsNotebookControl(self): + """ Returns whether the pane is a notebook control (L{AuiNotebook}). """ + + return not self.IsNotebookPage() and self.HasNotebook() + + + def SetNameFromNotebookId(self): + """ Sets the pane name once docked in a L{AuiNotebook} using the notebook id. """ + + if self.notebook_id >= 0: + self.name = "__notebook_%d"%self.notebook_id + + return self + + + def CaptionVisible(self, visible=True, left=False): + """ + Indicates that a pane caption should be visible. If `visible` is ``False``, no pane + caption is drawn. + + :param `visible`: whether the caption should be visible or not; + :param `left`: whether the caption should be drawn on the left (rotated by 90 degrees) or not. + """ + + if left: + self.SetFlag(self.optionCaption, False) + return self.SetFlag(self.optionCaptionLeft, visible) + + self.SetFlag(self.optionCaptionLeft, False) + return self.SetFlag(self.optionCaption, visible) + + + def PaneBorder(self, visible=True): + """ + Indicates that a border should be drawn for the pane. + + :param `visible`: whether the pane border should be visible or not. + """ + + return self.SetFlag(self.optionPaneBorder, visible) + + + def Gripper(self, visible=True): + """ + Indicates that a gripper should be drawn for the pane. + + :param `visible`: whether the gripper should be visible or not. + """ + + return self.SetFlag(self.optionGripper, visible) + + + def GripperTop(self, attop=True): + """ + Indicates that a gripper should be drawn at the top of the pane. + + :param `attop`: whether the gripper should be drawn at the top or not. + """ + + return self.SetFlag(self.optionGripperTop, attop) + + + def CloseButton(self, visible=True): + """ + Indicates that a close button should be drawn for the pane. + + :param `visible`: whether the close button should be visible or not. + """ + + return self.SetFlag(self.buttonClose, visible) + + + def MaximizeButton(self, visible=True): + """ + Indicates that a maximize button should be drawn for the pane. + + :param `visible`: whether the maximize button should be visible or not. + """ + + return self.SetFlag(self.buttonMaximize, visible) + + + def MinimizeButton(self, visible=True): + """ + Indicates that a minimize button should be drawn for the pane. + + :param `visible`: whether the minimize button should be visible or not. + """ + + return self.SetFlag(self.buttonMinimize, visible) + + + def PinButton(self, visible=True): + """ + Indicates that a pin button should be drawn for the pane. + + :param `visible`: whether the pin button should be visible or not. + """ + + return self.SetFlag(self.buttonPin, visible) + + + def DestroyOnClose(self, b=True): + """ + Indicates whether a pane should be destroyed when it is closed. + + Normally a pane is simply hidden when the close button is clicked. Setting + `b` to ``True`` will cause the window to be destroyed when the user clicks + the pane's close button. + + :param `b`: whether the pane should be destroyed when it is closed or not. + """ + + return self.SetFlag(self.optionDestroyOnClose, b) + + + def TopDockable(self, b=True): + """ + Indicates whether a pane can be docked at the top of the frame. + + :param `b`: whether the pane can be docked at the top or not. + """ + + return self.SetFlag(self.optionTopDockable, b) + + + def BottomDockable(self, b=True): + """ + Indicates whether a pane can be docked at the bottom of the frame. + + :param `b`: whether the pane can be docked at the bottom or not. + """ + + return self.SetFlag(self.optionBottomDockable, b) + + + def LeftDockable(self, b=True): + """ + Indicates whether a pane can be docked on the left of the frame. + + :param `b`: whether the pane can be docked at the left or not. + """ + + return self.SetFlag(self.optionLeftDockable, b) + + + def RightDockable(self, b=True): + """ + Indicates whether a pane can be docked on the right of the frame. + + :param `b`: whether the pane can be docked at the right or not. + """ + + return self.SetFlag(self.optionRightDockable, b) + + + def Floatable(self, b=True): + """ + Sets whether the user will be able to undock a pane and turn it + into a floating window. + + :param `b`: whether the pane can be floated or not. + """ + + return self.SetFlag(self.optionFloatable, b) + + + def Movable(self, b=True): + """ + Indicates whether a pane can be moved. + + :param `b`: whether the pane can be moved or not. + """ + + return self.SetFlag(self.optionMovable, b) + + + def NotebookDockable(self, b=True): + """ + Indicates whether a pane can be docked in an automatic L{AuiNotebook}. + + :param `b`: whether the pane can be docked in a notebook or not. + """ + + return self.SetFlag(self.optionNotebookDockable, b) + + + def DockFixed(self, b=True): + """ + Causes the containing dock to have no resize sash. This is useful + for creating panes that span the entire width or height of a dock, but should + not be resizable in the other direction. + + :param `b`: whether the pane will have a resize sash or not. + """ + + return self.SetFlag(self.optionDockFixed, b) + + + def Dockable(self, b=True): + """ + Specifies whether a frame can be docked or not. It is the same as specifying + L{TopDockable} . L{BottomDockable} . L{LeftDockable} . L{RightDockable} . + + :param `b`: whether the frame can be docked or not. + """ + + return self.TopDockable(b).BottomDockable(b).LeftDockable(b).RightDockable(b) + + + def TopSnappable(self, b=True): + """ + Indicates whether a pane can be snapped at the top of the main frame. + + :param `b`: whether the pane can be snapped at the top of the main frame or not. + """ + + return self.SetFlag(self.optionTopSnapped, b) + + + def BottomSnappable(self, b=True): + """ + Indicates whether a pane can be snapped at the bottom of the main frame. + + :param `b`: whether the pane can be snapped at the bottom of the main frame or not. + """ + + return self.SetFlag(self.optionBottomSnapped, b) + + + def LeftSnappable(self, b=True): + """ + Indicates whether a pane can be snapped on the left of the main frame. + + :param `b`: whether the pane can be snapped at the left of the main frame or not. + """ + + return self.SetFlag(self.optionLeftSnapped, b) + + + def RightSnappable(self, b=True): + """ + Indicates whether a pane can be snapped on the right of the main frame. + + :param `b`: whether the pane can be snapped at the right of the main frame or not. + """ + + return self.SetFlag(self.optionRightSnapped, b) + + + def Snappable(self, b=True): + """ + Indicates whether a pane can be snapped on the main frame. This is + equivalent as calling L{TopSnappable} . L{BottomSnappable} . L{LeftSnappable} . L{RightSnappable} . + + :param `b`: whether the pane can be snapped on the main frame or not. + """ + + return self.TopSnappable(b).BottomSnappable(b).LeftSnappable(b).RightSnappable(b) + + + def FlyOut(self, b=True): + """ + Indicates whether a pane, when floating, has a "fly-out" effect + (i.e., floating panes which only show themselves when moused over). + + :param `b`: whether the pane can be snapped on the main frame or not. + """ + + return self.SetFlag(self.optionFlyOut, b) + + + # Copy over the members that pertain to docking position + def SetDockPos(self, source): + """ + Copies the `source` pane members that pertain to docking position to `self`. + + :param `source`: the source pane from where to copy the attributes. + """ + + self.dock_direction = source.dock_direction + self.dock_layer = source.dock_layer + self.dock_row = source.dock_row + self.dock_pos = source.dock_pos + self.dock_proportion = source.dock_proportion + self.floating_pos = wx.Point(*source.floating_pos) + self.floating_size = wx.Size(*source.floating_size) + self.rect = wx.Rect(*source.rect) + + return self + + + def DefaultPane(self): + """ Specifies that the pane should adopt the default pane settings. """ + + state = self.state + state |= self.optionTopDockable | self.optionBottomDockable | \ + self.optionLeftDockable | self.optionRightDockable | \ + self.optionNotebookDockable | \ + self.optionFloatable | self.optionMovable | self.optionResizable | \ + self.optionCaption | self.optionPaneBorder | self.buttonClose + + self.state = state + + return self + + + def CentrePane(self): + """ + Specifies that the pane should adopt the default center pane settings. + + Centre panes usually do not have caption bars. This function provides an easy way of + preparing a pane to be displayed in the center dock position. + """ + + return self.CenterPane() + + + def CenterPane(self): + """ + Specifies that the pane should adopt the default center pane settings. + + Centre panes usually do not have caption bars. This function provides an easy way of + preparing a pane to be displayed in the center dock position. + """ + + self.state = 0 + return self.Center().PaneBorder().Resizable() + + + def ToolbarPane(self): + """ Specifies that the pane should adopt the default toolbar pane settings. """ + + self.DefaultPane() + state = self.state + + state |= (self.optionToolbar | self.optionGripper) + state &= ~(self.optionResizable | self.optionCaption | self.optionCaptionLeft) + + if self.dock_layer == 0: + self.dock_layer = 10 + + self.state = state + + return self + + + def Icon(self, icon): + """ + Specifies whether an icon is drawn on the left of the caption text when + the pane is docked. If `icon` is ``None`` or `wx.NullIcon`, no icon is drawn on + the caption space. + + :param icon: an icon to draw on the caption space, or ``None``. + """ + + if icon is None: + icon = wx.NullIcon + + self.icon = icon + return self + + + def SetFlag(self, flag, option_state): + """ + Turns the property given by `flag` on or off with the `option_state` + parameter. + + :param `flag`: the property to set; + :param `option_state`: either ``True`` or ``False``. + """ + + state = self.state + + if option_state: + state |= flag + else: + state &= ~flag + + self.state = state + + if flag in [self.buttonClose, self.buttonMaximize, self.buttonMinimize, self.buttonPin]: + self.ResetButtons() + + return self + + + def HasFlag(self, flag): + """ + Returns ``True`` if the the property specified by flag is active for the pane. + + :param `flag`: the property to check for activity. + """ + + return (self.state & flag and [True] or [False])[0] + + + def ResetButtons(self): + """ + Resets all the buttons and recreates them from scratch depending on the + L{AuiPaneInfo} flags. + """ + + floating = self.HasFlag(self.optionFloating) + self.buttons = [] + + if not floating and self.HasMinimizeButton(): + button = AuiPaneButton(AUI_BUTTON_MINIMIZE) + self.buttons.append(button) + + if not floating and self.HasMaximizeButton(): + button = AuiPaneButton(AUI_BUTTON_MAXIMIZE_RESTORE) + self.buttons.append(button) + + if not floating and self.HasPinButton(): + button = AuiPaneButton(AUI_BUTTON_PIN) + self.buttons.append(button) + + if self.HasCloseButton(): + button = AuiPaneButton(AUI_BUTTON_CLOSE) + self.buttons.append(button) + + + def CountButtons(self): + """ Returns the number of visible buttons in the docked pane. """ + + n = 0 + + if self.HasCaption() or self.HasCaptionLeft(): + if isinstance(wx.GetTopLevelParent(self.window), AuiFloatingFrame): + return 1 + + if self.HasCloseButton(): + n += 1 + if self.HasMaximizeButton(): + n += 1 + if self.HasMinimizeButton(): + n += 1 + if self.HasPinButton(): + n += 1 + + return n + + + def IsHorizontal(self): + """ Returns ``True`` if the pane `dock_direction` is horizontal. """ + + return self.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_BOTTOM] + + def IsVertical(self): + """ Returns ``True`` if the pane `dock_direction` is vertical. """ + + return self.dock_direction in [AUI_DOCK_LEFT, AUI_DOCK_RIGHT] + + +# Null AuiPaneInfo reference +NonePaneInfo = AuiPaneInfo() + + +# ---------------------------------------------------------------------------- # + +class AuiDockingGuide(wx.Frame): + """ Base class for L{AuiCenterDockingGuide} and L{AuiSingleDockingGuide}.""" + + def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.FRAME_TOOL_WINDOW | wx.STAY_ON_TOP | + wx.FRAME_NO_TASKBAR | wx.NO_BORDER, name="AuiDockingGuide"): + """ + Default class constructor. Used internally, do not call it in your code! + + :param `parent`: the L{AuiDockingGuide} parent; + :param `id`: the window identifier. It may take a value of -1 to indicate a default value. + :param `title`: the caption to be displayed on the frame's title bar. + :param `pos`: the window position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform. + :param `size`: the window size. A value of (-1, -1) indicates a default size, chosen by + either the windowing system or wxPython, depending on platform. + :param `style`: the window style. + :param `name`: the name of the window. This parameter is used to associate a name with the + item, allowing the application user to set Motif resource values for individual windows. + """ + + wx.Frame.__init__(self, parent, id, title, pos, size, style, name=name) + + + def HitTest(self, x, y): + """ + To be overridden by parent classes. + + :param `x`: the `x` mouse position; + :param `y`: the `y` mouse position. + """ + + return 0 + + + def ValidateNotebookDocking(self, valid): + """ + To be overridden by parent classes. + + :param `valid`: whether a pane can be docked on top to another to form an automatic + L{AuiNotebook}. + """ + + return 0 + +# ============================================================================ +# implementation +# ============================================================================ + +# --------------------------------------------------------------------------- +# AuiDockingGuideWindow +# --------------------------------------------------------------------------- + +class AuiDockingGuideWindow(wx.Window): + """ Target class for L{AuiSingleDockingGuide} and L{AuiCenterDockingGuide}. """ + + def __init__(self, parent, rect, direction=0, center=False, useAero=False): + """ + Default class constructor. Used internally, do not call it in your code! + + :param `parent`: the L{AuiDockingGuideWindow} parent; + :param `rect`: the window rect; + :param `direction`: one of ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, ``wx.RIGHT``, + ``wx.CENTER``; + :param `center`: whether the calling class is a L{AuiCenterDockingGuide}; + :param `useAero`: whether to use the new Aero-style bitmaps or Whidbey-style bitmaps + for the docking guide. + """ + + wx.Window.__init__(self, parent, -1, rect.GetPosition(), rect.GetSize(), wx.NO_BORDER) + + self._direction = direction + self._center = center + self._valid = True + self._useAero = useAero + + self._bmp_unfocus, self._bmp_focus = GetDockingImage(direction, useAero, center) + + self._currentImage = self._bmp_unfocus + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def SetValid(self, valid): + """ + Sets the docking direction as valid or invalid. + + :param `valid`: whether the docking direction is allowed or not. + """ + + self._valid = valid + + + def IsValid(self): + """ Returns whether the docking direction is valid. """ + + return self._valid + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiDockingGuideWindow}. + + :param `event`: a `wx.EraseEvent` to be processed. + + :note: This is intentionally empty to reduce flickering while drawing. + """ + + pass + + + def DrawBackground(self, dc): + """ + Draws the docking guide background. + + :param `dc`: a `wx.DC` device context object. + """ + + rect = self.GetClientRect() + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.Brush(colourTargetBackground)) + dc.DrawRectangleRect(rect) + + dc.SetPen(wx.Pen(colourTargetBorder)) + + left = rect.GetLeft() + top = rect.GetTop() + right = rect.GetRight() + bottom = rect.GetBottom() + + if self._direction != wx.CENTER: + + if not self._center or self._direction != wx.BOTTOM: + dc.DrawLine(left, top, right+1, top) + if not self._center or self._direction != wx.RIGHT: + dc.DrawLine(left, top, left, bottom+1) + if not self._center or self._direction != wx.LEFT: + dc.DrawLine(right, top, right, bottom+1) + if not self._center or self._direction != wx.TOP: + dc.DrawLine(left, bottom, right+1, bottom) + + dc.SetPen(wx.Pen(colourTargetShade)) + + if self._direction != wx.RIGHT: + dc.DrawLine(left + 1, top + 1, left + 1, bottom) + if self._direction != wx.BOTTOM: + dc.DrawLine(left + 1, top + 1, right, top + 1) + + + def DrawDottedLine(self, dc, point, length, vertical): + """ + Draws a dotted line (not used if the docking guide images are ok). + + :param `dc`: a `wx.DC` device context object; + :param `point`: a `wx.Point` where to start drawing the dotted line; + :param `length`: the length of the dotted line; + :param `vertical`: whether it is a vertical docking guide window or not. + """ + + for i in xrange(0, length, 2): + dc.DrawPoint(point.x, point.y) + if vertical: + point.y += 2 + else: + point.x += 2 + + + def DrawIcon(self, dc): + """ + Draws the docking guide icon (not used if the docking guide images are ok). + + :param `dc`: a `wx.DC` device context object. + """ + + rect = wx.Rect(*self.GetClientRect()) + point = wx.Point() + length = 0 + + rect.Deflate(4, 4) + dc.SetPen(wx.Pen(colourIconBorder)) + dc.SetBrush(wx.Brush(colourIconBackground)) + dc.DrawRectangleRect(rect) + + right1 = rect.GetRight() + 1 + bottom1 = rect.GetBottom() + 1 + + dc.SetPen(wx.Pen(colourIconShadow)) + dc.DrawLine(rect.x + 1, bottom1, right1 + 1, bottom1) + dc.DrawLine(right1, rect.y + 1, right1, bottom1 + 1) + + rect.Deflate(1, 1) + + if self._direction == wx.TOP: + rect.height -= rect.height / 2 + point = rect.GetBottomLeft() + length = rect.width + + elif self._direction == wx.LEFT: + rect.width -= rect.width / 2 + point = rect.GetTopRight() + length = rect.height + + elif self._direction == wx.RIGHT: + rect.x += rect.width / 2 + rect.width -= rect.width / 2 + point = rect.GetTopLeft() + length = rect.height + + elif self._direction == wx.BOTTOM: + rect.y += rect.height / 2 + rect.height -= rect.height / 2 + point = rect.GetTopLeft() + length = rect.width + + elif self._direction == wx.CENTER: + rect.Deflate(1, 1) + point = rect.GetTopLeft() + length = rect.width + + dc.GradientFillLinear(rect, colourIconDockingPart1, + colourIconDockingPart2, self._direction) + + dc.SetPen(wx.Pen(colourIconBorder)) + + if self._direction == wx.CENTER: + self.DrawDottedLine(dc, rect.GetTopLeft(), rect.width, False) + self.DrawDottedLine(dc, rect.GetTopLeft(), rect.height, True) + self.DrawDottedLine(dc, rect.GetBottomLeft(), rect.width, False) + self.DrawDottedLine(dc, rect.GetTopRight(), rect.height, True) + + elif self._direction in [wx.TOP, wx.BOTTOM]: + self.DrawDottedLine(dc, point, length, False) + + else: + self.DrawDottedLine(dc, point, length, True) + + + def DrawArrow(self, dc): + """ + Draws the docking guide arrow icon (not used if the docking guide images are ok). + + :param `dc`: a `wx.DC` device context object. + """ + + rect = self.GetClientRect() + point = wx.Point() + + point.x = (rect.GetLeft() + rect.GetRight()) / 2 + point.y = (rect.GetTop() + rect.GetBottom()) / 2 + rx, ry = wx.Size(), wx.Size() + + if self._direction == wx.TOP: + rx = wx.Size(1, 0) + ry = wx.Size(0, 1) + + elif self._direction == wx.LEFT: + rx = wx.Size(0, -1) + ry = wx.Size(1, 0) + + elif self._direction == wx.RIGHT: + rx = wx.Size(0, 1) + ry = wx.Size(-1, 0) + + elif self._direction == wx.BOTTOM: + rx = wx.Size(-1, 0) + ry = wx.Size(0, -1) + + point.x += ry.x*3 + point.y += ry.y*3 + + dc.SetPen(wx.Pen(colourIconArrow)) + + for i in xrange(4): + pt1 = wx.Point(point.x - rx.x*i, point.y - rx.y*i) + pt2 = wx.Point(point.x + rx.x*(i+1), point.y + rx.y*(i+1)) + dc.DrawLinePoint(pt1, pt2) + point.x += ry.x + point.y += ry.y + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiDockingGuideWindow}. + + :param `event`: a `wx.PaintEvent` to be processed. + """ + + dc = wx.AutoBufferedPaintDC(self) + if self._currentImage.IsOk() and self._valid: + dc.DrawBitmap(self._currentImage, 0, 0, True) + else: + self.Draw(dc) + + + def Draw(self, dc): + """ + Draws the whole docking guide window (not used if the docking guide images are ok). + + :param `dc`: a `wx.DC` device context object. + """ + + self.DrawBackground(dc) + + if self._valid: + self.DrawIcon(dc) + self.DrawArrow(dc) + + + def UpdateDockGuide(self, pos): + """ + Updates the docking guide images depending on the mouse position, using focused + images if the mouse is inside the docking guide or unfocused images if it is + outside. + + :param `pos`: a `wx.Point` mouse position. + """ + + inside = self.GetScreenRect().Contains(pos) + + if inside: + image = self._bmp_focus + else: + image = self._bmp_unfocus + + if image != self._currentImage: + self._currentImage = image + self.Refresh() + self.Update() + + +# --------------------------------------------------------------------------- +# AuiSingleDockingGuide +# --------------------------------------------------------------------------- + +class AuiSingleDockingGuide(AuiDockingGuide): + """ A docking guide window for single docking hint (not diamond-shaped HUD). """ + + def __init__(self, parent, direction=0): + """ + Default class constructor. Used internally, do not call it in your code! + + :param `parent`: the L{AuiSingleDockingGuide} parent; + :param `direction`: one of ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, ``wx.RIGHT``. + """ + + self._direction = direction + + style = wx.FRAME_TOOL_WINDOW | wx.STAY_ON_TOP | \ + wx.FRAME_NO_TASKBAR | wx.NO_BORDER + + # Use of FRAME_SHAPED on wxMac causes the frame to be visible + # breaking the docking hints. + if wx.Platform != '__WXMAC__': + style |= wx.FRAME_SHAPED + + AuiDockingGuide.__init__(self, parent, style=style, name="auiSingleDockTarget") + + self.Hide() + + useAero = GetManager(self.GetParent()).GetAGWFlags() & AUI_MGR_AERO_DOCKING_GUIDES + useWhidbey = GetManager(self.GetParent()).GetAGWFlags() & AUI_MGR_WHIDBEY_DOCKING_GUIDES + + self._useAero = useAero or useWhidbey + self._valid = True + + if useAero: + sizeX, sizeY = aeroguideSizeX, aeroguideSizeY + elif useWhidbey: + sizeX, sizeY = whidbeySizeX, whidbeySizeY + else: + sizeX, sizeY = guideSizeX, guideSizeY + + if direction not in [wx.TOP, wx.BOTTOM]: + sizeX, sizeY = sizeY, sizeX + + if self._useAero: + self.CreateShapesWithStyle(useWhidbey) + + if wx.Platform == "__WXGTK__": + self.Bind(wx.EVT_WINDOW_CREATE, self.SetGuideShape) + else: + self.SetGuideShape() + + self.SetSize(self.region.GetBox().GetSize()) + else: + self.SetSize((sizeX, sizeY)) + + self.rect = wx.Rect(0, 0, sizeX, sizeY) + + if self._useAero: + useAero = (useWhidbey and [2] or [1])[0] + else: + useAero = 0 + + self.target = AuiDockingGuideWindow(self, self.rect, direction, False, useAero) + + + def CreateShapesWithStyle(self, useWhidbey): + """ + Creates the docking guide window shape based on which docking bitmaps are used. + + :param `useWhidbey`: if ``True``, use Whidbey-style bitmaps; if ``False``, use the + Aero-style bitmaps. + """ + + sizeX, sizeY = aeroguideSizeX, aeroguideSizeY + if useWhidbey: + sizeX, sizeY = whidbeySizeX, whidbeySizeY + + if self._direction not in [wx.TOP, wx.BOTTOM]: + sizeX, sizeY = sizeY, sizeX + + useAero = (useWhidbey and [2] or [1])[0] + bmp, dummy = GetDockingImage(self._direction, useAero, False) + region = wx.RegionFromBitmap(bmp) + + self.region = region + + + def AeroMove(self, pos): + """ + Moves the docking window to the new position. Overridden in children classes. + + :param `pos`: the new docking guide position. + """ + + pass + + + def SetGuideShape(self, event=None): + """ + Sets the correct shape for the docking guide window. + + :param `event`: on wxGTK, a `wx.WindowCreateEvent` event to process. + """ + + self.SetShape(self.region) + + if event is not None: + # Skip the event on wxGTK + event.Skip() + wx.CallAfter(wx.SafeYield, self, True) + + + def SetShape(self, region): + """ + If the platform supports it, sets the shape of the window to that depicted by `region`. + The system will not display or respond to any mouse event for the pixels that lie + outside of the region. To reset the window to the normal rectangular shape simply call + L{SetShape} again with an empty region. + + :param `region`: the shape of the frame. + + :note: Overridden for wxMac. + """ + + if wx.Platform == '__WXMAC__': + # HACK so we don't crash when SetShape is called + return + else: + super(AuiSingleDockingGuide, self).SetShape(region) + + + def SetValid(self, valid): + """ + Sets the docking direction as valid or invalid. + + :param `valid`: whether the docking direction is allowed or not. + """ + + self._valid = valid + + + def IsValid(self): + """ Returns whether the docking direction is valid. """ + + return self._valid + + + def UpdateDockGuide(self, pos): + """ + Updates the docking guide images depending on the mouse position, using focused + images if the mouse is inside the docking guide or unfocused images if it is + outside. + + :param `pos`: a `wx.Point` mouse position. + """ + + self.target.UpdateDockGuide(pos) + + + def HitTest(self, x, y): + """ + Checks if the mouse position is inside the target window rect. + + :param `x`: the `x` mouse position; + :param `y`: the `y` mouse position. + """ + + if self.target.GetScreenRect().Contains((x, y)): + return wx.ALL + + return -1 + + +# --------------------------------------------------------------------------- +# AuiCenterDockingGuide +# --------------------------------------------------------------------------- + +class AuiCenterDockingGuide(AuiDockingGuide): + """ A docking guide window for multiple docking hint (diamond-shaped HUD). """ + + def __init__(self, parent): + """ + Default class constructor. + Used internally, do not call it in your code! + + :param `parent`: the L{AuiCenterDockingGuide} parent. + """ + + AuiDockingGuide.__init__(self, parent, style=wx.FRAME_TOOL_WINDOW | wx.STAY_ON_TOP | + wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.FRAME_SHAPED, + name="auiCenterDockTarget") + + self.Hide() + + self.CreateShapesWithStyle() + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + + if wx.Platform == "__WXGTK__": + self.Bind(wx.EVT_WINDOW_CREATE, self.SetGuideShape) + else: + self.SetGuideShape() + + self.SetSize(self.region.GetBox().GetSize()) + + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def CreateShapesWithStyle(self): + """ Creates the docking guide window shape based on which docking bitmaps are used. """ + + useAero = (GetManager(self.GetParent()).GetAGWFlags() & AUI_MGR_AERO_DOCKING_GUIDES) != 0 + useWhidbey = (GetManager(self.GetParent()).GetAGWFlags() & AUI_MGR_WHIDBEY_DOCKING_GUIDES) != 0 + + self._useAero = 0 + if useAero: + self._useAero = 1 + elif useWhidbey: + self._useAero = 2 + + if useAero: + sizeX, sizeY = aeroguideSizeX, aeroguideSizeY + elif useWhidbey: + sizeX, sizeY = whidbeySizeX, whidbeySizeY + else: + sizeX, sizeY = guideSizeX, guideSizeY + + rectLeft = wx.Rect(0, sizeY, sizeY, sizeX) + rectTop = wx.Rect(sizeY, 0, sizeX, sizeY) + rectRight = wx.Rect(sizeY+sizeX, sizeY, sizeY, sizeX) + rectBottom = wx.Rect(sizeY, sizeX + sizeY, sizeX, sizeY) + rectCenter = wx.Rect(sizeY, sizeY, sizeX, sizeX) + + if not self._useAero: + + self.targetLeft = AuiDockingGuideWindow(self, rectLeft, wx.LEFT, True, useAero) + self.targetTop = AuiDockingGuideWindow(self, rectTop, wx.TOP, True, useAero) + self.targetRight = AuiDockingGuideWindow(self, rectRight, wx.RIGHT, True, useAero) + self.targetBottom = AuiDockingGuideWindow(self, rectBottom, wx.BOTTOM, True, useAero) + self.targetCenter = AuiDockingGuideWindow(self, rectCenter, wx.CENTER, True, useAero) + + + # top-left diamond + tld = [wx.Point(rectTop.x, rectTop.y+rectTop.height-8), + wx.Point(rectLeft.x+rectLeft.width-8, rectLeft.y), + rectTop.GetBottomLeft()] + # bottom-left diamond + bld = [wx.Point(rectLeft.x+rectLeft.width-8, rectLeft.y+rectLeft.height), + wx.Point(rectBottom.x, rectBottom.y+8), + rectBottom.GetTopLeft()] + # top-right diamond + trd = [wx.Point(rectTop.x+rectTop.width, rectTop.y+rectTop.height-8), + wx.Point(rectRight.x+8, rectRight.y), + rectRight.GetTopLeft()] + # bottom-right diamond + brd = [wx.Point(rectRight.x+8, rectRight.y+rectRight.height), + wx.Point(rectBottom.x+rectBottom.width, rectBottom.y+8), + rectBottom.GetTopRight()] + + self._triangles = [tld[0:2], bld[0:2], + [wx.Point(rectTop.x+rectTop.width-1, rectTop.y+rectTop.height-8), + wx.Point(rectRight.x+7, rectRight.y)], + [wx.Point(rectRight.x+7, rectRight.y+rectRight.height), + wx.Point(rectBottom.x+rectBottom.width-1, rectBottom.y+8)]] + + region = wx.Region() + region.UnionRect(rectLeft) + region.UnionRect(rectTop) + region.UnionRect(rectRight) + region.UnionRect(rectBottom) + region.UnionRect(rectCenter) + region.UnionRegion(wx.RegionFromPoints(tld)) + region.UnionRegion(wx.RegionFromPoints(bld)) + region.UnionRegion(wx.RegionFromPoints(trd)) + region.UnionRegion(wx.RegionFromPoints(brd)) + + elif useAero: + + self._aeroBmp = aero_dock_pane.GetBitmap() + region = wx.RegionFromBitmap(self._aeroBmp) + + self._allAeroBmps = [aero_dock_pane_left.GetBitmap(), aero_dock_pane_top.GetBitmap(), + aero_dock_pane_right.GetBitmap(), aero_dock_pane_bottom.GetBitmap(), + aero_dock_pane_center.GetBitmap(), aero_dock_pane.GetBitmap()] + self._deniedBitmap = aero_denied.GetBitmap() + self._aeroRects = [rectLeft, rectTop, rectRight, rectBottom, rectCenter] + self._valid = True + + elif useWhidbey: + + self._aeroBmp = whidbey_dock_pane.GetBitmap() + region = wx.RegionFromBitmap(self._aeroBmp) + + self._allAeroBmps = [whidbey_dock_pane_left.GetBitmap(), whidbey_dock_pane_top.GetBitmap(), + whidbey_dock_pane_right.GetBitmap(), whidbey_dock_pane_bottom.GetBitmap(), + whidbey_dock_pane_center.GetBitmap(), whidbey_dock_pane.GetBitmap()] + self._deniedBitmap = whidbey_denied.GetBitmap() + self._aeroRects = [rectLeft, rectTop, rectRight, rectBottom, rectCenter] + self._valid = True + + + self.region = region + + + def SetGuideShape(self, event=None): + """ + Sets the correct shape for the docking guide window. + + :param `event`: on wxGTK, a `wx.WindowCreateEvent` event to process. + """ + + self.SetShape(self.region) + + if event is not None: + # Skip the event on wxGTK + event.Skip() + wx.CallAfter(wx.SafeYield, self, True) + + + def UpdateDockGuide(self, pos): + """ + Updates the docking guides images depending on the mouse position, using focused + images if the mouse is inside the docking guide or unfocused images if it is + outside. + + :param `pos`: a `wx.Point` mouse position. + """ + + if not self._useAero: + for target in self.GetChildren(): + target.UpdateDockGuide(pos) + else: + lenRects = len(self._aeroRects) + for indx, rect in enumerate(self._aeroRects): + if rect.Contains(pos): + if self._allAeroBmps[indx] != self._aeroBmp: + if indx < lenRects - 1 or (indx == lenRects - 1 and self._valid): + self._aeroBmp = self._allAeroBmps[indx] + self.Refresh() + else: + self._aeroBmp = self._allAeroBmps[-1] + self.Refresh() + + return + + if self._aeroBmp != self._allAeroBmps[-1]: + self._aeroBmp = self._allAeroBmps[-1] + self.Refresh() + + + def HitTest(self, x, y): + """ + Checks if the mouse position is inside the target windows rect. + + :param `x`: the `x` mouse position; + :param `y`: the `y` mouse position. + """ + + if not self._useAero: + if self.targetLeft.GetScreenRect().Contains((x, y)): + return wx.LEFT + if self.targetTop.GetScreenRect().Contains((x, y)): + return wx.UP + if self.targetRight.GetScreenRect().Contains((x, y)): + return wx.RIGHT + if self.targetBottom.GetScreenRect().Contains((x, y)): + return wx.DOWN + if self.targetCenter.IsValid() and self.targetCenter.GetScreenRect().Contains((x, y)): + return wx.CENTER + else: + constants = [wx.LEFT, wx.UP, wx.RIGHT, wx.DOWN, wx.CENTER] + lenRects = len(self._aeroRects) + for indx, rect in enumerate(self._aeroRects): + if rect.Contains((x, y)): + if indx < lenRects or (indx == lenRects-1 and self._valid): + return constants[indx] + + return -1 + + + def ValidateNotebookDocking(self, valid): + """ + Sets whether a pane can be docked on top of another to create an automatic + L{AuiNotebook}. + + :param `valid`: whether a pane can be docked on top to another to form an automatic + L{AuiNotebook}. + """ + + if not self._useAero: + if self.targetCenter.IsValid() != valid: + self.targetCenter.SetValid(valid) + self.targetCenter.Refresh() + else: + if self._valid != valid: + self._valid = valid + self.Refresh() + + + def AeroMove(self, pos): + """ + Moves the docking guide window to the new position. + + :param `pos`: the new docking guide position. + """ + + if not self._useAero: + return + + useWhidbey = (GetManager(self.GetParent()).GetAGWFlags() & AUI_MGR_WHIDBEY_DOCKING_GUIDES) != 0 + + if useWhidbey: + sizeX, sizeY = whidbeySizeX, whidbeySizeY + else: + sizeX, sizeY = aeroguideSizeX, aeroguideSizeY + + size = self.GetSize() + + leftRect, topRect, rightRect, bottomRect, centerRect = self._aeroRects + thePos = pos + wx.Point((size.x-sizeY)/2, (size.y-sizeX)/2) + + centerRect.SetPosition(thePos) + + leftRect.SetPosition(thePos + wx.Point(-sizeY, 0)) + topRect.SetPosition(thePos + wx.Point(0, -sizeY)) + rightRect.SetPosition(thePos + wx.Point(sizeX, 0)) + bottomRect.SetPosition(thePos + wx.Point(0, sizeX)) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiCenterDockingGuide}. + + :param `event`: `wx.EraseEvent` to be processed. + + :note: This is intentionally empty to reduce flickering while drawing. + """ + + pass + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiCenterDockingGuide}. + + :param `event`: a `wx.PaintEvent` to be processed. + """ + + dc = wx.AutoBufferedPaintDC(self) + + if self._useAero: + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(wx.TRANSPARENT_PEN) + else: + dc.SetBrush(wx.Brush(colourTargetBackground)) + dc.SetPen(wx.Pen(colourTargetBorder)) + + rect = self.GetClientRect() + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + if self._useAero: + dc.DrawBitmap(self._aeroBmp, 0, 0, True) + if not self._valid: + diff = (self._useAero == 2 and [1] or [0])[0] + bmpX, bmpY = self._deniedBitmap.GetWidth(), self._deniedBitmap.GetHeight() + xPos, yPos = (rect.x + (rect.width)/2 - bmpX/2), (rect.y + (rect.height)/2 - bmpY/2) + dc.DrawBitmap(self._deniedBitmap, xPos+1, yPos+diff, True) + + return + + dc.SetPen(wx.Pen(colourTargetBorder, 2)) + for pts in self._triangles: + dc.DrawLinePoint(pts[0], pts[1]) + + +# ---------------------------------------------------------------------------- +# AuiDockingHintWindow +# ---------------------------------------------------------------------------- + +class AuiDockingHintWindow(wx.Frame): + """ The original wxAUI docking window hint. """ + + def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, + size=wx.Size(1, 1), style=wx.FRAME_TOOL_WINDOW | wx.FRAME_FLOAT_ON_PARENT | + wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.FRAME_SHAPED, + name="auiHintWindow"): + """ + Default class constructor. Used internally, do not call it in your code! + + :param `parent`: the L{AuiDockingGuide} parent; + :param `id`: the window identifier. It may take a value of -1 to indicate a default value. + :param `title`: the caption to be displayed on the frame's title bar; + :param `pos`: the window position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the window size. A value of (-1, -1) indicates a default size, chosen by + either the windowing system or wxPython, depending on platform; + :param `style`: the window style; + :param `name`: the name of the window. This parameter is used to associate a name with the + item, allowing the application user to set Motif resource values for individual windows. + """ + if wx.Platform == '__WXMAC__' and style & wx.FRAME_SHAPED: + # Having the shaped frame causes the frame to not be visible + # with the transparent style hints. + style -= wx.FRAME_SHAPED + + wx.Frame.__init__(self, parent, id, title, pos, size, style, name=name) + + self._blindMode = False + self.SetBackgroundColour(colourHintBackground) + + # Can't set background colour on a frame on wxMac + # so add a panel to set the colour on. + if wx.Platform == '__WXMAC__': + sizer = wx.BoxSizer(wx.HORIZONTAL) + self.panel = wx.Panel(self) + sizer.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(sizer) + self.panel.SetBackgroundColour(colourHintBackground) + + self.Bind(wx.EVT_SIZE, self.OnSize) + + + def MakeVenetianBlinds(self): + """ + Creates the "venetian blind" effect if L{AuiManager} has the ``AUI_MGR_VENETIAN_BLINDS_HINT`` + flag set. + """ + + amount = 128 + size = self.GetClientSize() + region = wx.Region(0, 0, size.x, 1) + + for y in xrange(size.y): + + # Reverse the order of the bottom 4 bits + j = (y & 8 and [1] or [0])[0] | (y & 4 and [2] or [0])[0] | \ + (y & 2 and [4] or [0])[0] | (y & 1 and [8] or [0])[0] + + if 16*j+8 < amount: + region.Union(0, y, size.x, 1) + + self.SetShape(region) + + + def SetBlindMode(self, agwFlags): + """ + Sets whether venetian blinds or transparent hints will be shown as docking hint. + This depends on the L{AuiManager} flags. + + :param `agwFlags`: the L{AuiManager} flags. + """ + + self._blindMode = (agwFlags & AUI_MGR_VENETIAN_BLINDS_HINT) != 0 + + if self._blindMode or not self.CanSetTransparent(): + self.MakeVenetianBlinds() + self.SetTransparent(255) + + else: + self.SetShape(wx.Region()) + if agwFlags & AUI_MGR_HINT_FADE == 0: + self.SetTransparent(80) + else: + self.SetTransparent(0) + + + def SetShape(self, region): + """ + If the platform supports it, sets the shape of the window to that depicted by `region`. + The system will not display or respond to any mouse event for the pixels that lie + outside of the region. To reset the window to the normal rectangular shape simply call + L{SetShape} again with an empty region. + + :param `region`: the shape of the frame (an instance of `wx.Region`). + + :note: Overridden for wxMac. + """ + + if wx.Platform == '__WXMAC__': + # HACK so we don't crash when SetShape is called + return + else: + super(AuiDockingHintWindow, self).SetShape(region) + + + def Show(self, show=True): + """ + Show the hint window. + + :param `show`: whether to show or hide the hint docking window. + """ + + super(AuiDockingHintWindow, self).Show(show) + if wx.Platform == '__WXMAC__': + # Need to manually do layout since its a borderless frame. + self.Layout() + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiDockingHintWindow}. + + :param `event`: a `wx.SizeEvent` to be processed. + """ + + if self._blindMode or not self.CanSetTransparent(): + self.MakeVenetianBlinds() + + +# ---------------------------------------------------------------------------- # + +# -- AuiFloatingFrame class implementation -- + +class AuiFloatingFrame(wx.MiniFrame): + """ AuiFloatingFrame is the frame class that holds floating panes. """ + + def __init__(self, parent, owner_mgr, pane=None, id=wx.ID_ANY, title="", + style=wx.FRAME_TOOL_WINDOW | wx.FRAME_FLOAT_ON_PARENT | + wx.FRAME_NO_TASKBAR | wx.CLIP_CHILDREN): + """ + Default class constructor. Used internally, do not call it in your code! + + :param `parent`: the L{AuiFloatingFrame} parent; + :param `owner_mgr`: the L{AuiManager} that manages the floating pane; + :param `pane`: the L{AuiPaneInfo} pane that is about to float; + :param `id`: the window identifier. It may take a value of -1 to indicate a default value. + :param `title`: the caption to be displayed on the frame's title bar. + :param `style`: the window style. + """ + + if pane and pane.IsResizeable(): + style += wx.RESIZE_BORDER + if pane: + self._is_toolbar = pane.IsToolbar() + + self._useNativeMiniframes = False + if AuiManager_UseNativeMiniframes(owner_mgr): + # On wxMac we always use native miniframes + self._useNativeMiniframes = True + style += wx.CAPTION + wx.SYSTEM_MENU + if pane.HasCloseButton(): + style += wx.CLOSE_BOX + if pane.HasMaximizeButton(): + style += wx.MAXIMIZE_BOX + if pane.HasMinimizeButton(): + style += wx.MINIMIZE_BOX + + wx.MiniFrame.__init__(self, parent, id, title, pos=pane.floating_pos, + size=pane.floating_size, style=style, name="auiFloatingFrame") + + self._fly_timer = wx.Timer(self, wx.ID_ANY) + self._check_fly_timer = wx.Timer(self, wx.ID_ANY) + + self.Bind(wx.EVT_CLOSE, self.OnClose) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_ACTIVATE, self.OnActivate) + self.Bind(wx.EVT_TIMER, self.OnCheckFlyTimer, self._check_fly_timer) + self.Bind(wx.EVT_TIMER, self.OnFlyTimer, self._fly_timer) + self.Bind(EVT_AUI_FIND_MANAGER, self.OnFindManager) + + if self._useNativeMiniframes: + self.Bind(wx.EVT_MOVE, self.OnMoveEvent) + self.Bind(wx.EVT_MOVING, self.OnMoveEvent) + self.Bind(wx.EVT_IDLE, self.OnIdle) + self._useNativeMiniframes = True + self.SetExtraStyle(wx.WS_EX_PROCESS_IDLE) + else: + self.Bind(wx.EVT_MOVE, self.OnMove) + + self._fly = False + self._send_size = True + self._alpha_amount = 255 + + self._owner_mgr = owner_mgr + self._moving = False + self._lastDirection = None + self._transparent = 255 + + self._last_rect = wx.Rect() + self._last2_rect = wx.Rect() + self._last3_rect = wx.Rect() + + self._mgr = AuiManager() + self._mgr.SetManagedWindow(self) + self._mgr.SetArtProvider(owner_mgr.GetArtProvider()) + self._mgr.SetAGWFlags(owner_mgr.GetAGWFlags()) + + + def CopyAttributes(self, pane): + """ + Copies all the attributes of the input `pane` into another L{AuiPaneInfo}. + + :param `pane`: the source L{AuiPaneInfo} from where to copy attributes. + """ + + contained_pane = AuiPaneInfo() + + contained_pane.name = pane.name + contained_pane.caption = pane.caption + contained_pane.window = pane.window + contained_pane.frame = pane.frame + contained_pane.state = pane.state + contained_pane.dock_direction = pane.dock_direction + contained_pane.dock_layer = pane.dock_layer + contained_pane.dock_row = pane.dock_row + contained_pane.dock_pos = pane.dock_pos + contained_pane.best_size = wx.Size(*pane.best_size) + contained_pane.min_size = wx.Size(*pane.min_size) + contained_pane.max_size = wx.Size(*pane.max_size) + contained_pane.floating_pos = wx.Point(*pane.floating_pos) + contained_pane.floating_size = wx.Size(*pane.floating_size) + contained_pane.dock_proportion = pane.dock_proportion + contained_pane.buttons = pane.buttons + contained_pane.rect = wx.Rect(*pane.rect) + contained_pane.icon = pane.icon + contained_pane.notebook_id = pane.notebook_id + contained_pane.transparent = pane.transparent + contained_pane.snapped = pane.snapped + contained_pane.minimize_mode = pane.minimize_mode + + return contained_pane + + + def SetPaneWindow(self, pane): + """ + Sets all the properties of a pane. + + :param `pane`: the L{AuiPaneInfo} to analyze. + """ + + self._is_toolbar = pane.IsToolbar() + self._pane_window = pane.window + + if isinstance(pane.window, auibar.AuiToolBar): + pane.window.SetAuiManager(self._mgr) + + self._pane_window.Reparent(self) + + contained_pane = self.CopyAttributes(pane) + + contained_pane.Dock().Center().Show(). \ + CaptionVisible(False). \ + PaneBorder(False). \ + Layer(0).Row(0).Position(0) + + if not contained_pane.HasGripper() and not self._useNativeMiniframes: + contained_pane.CaptionVisible(True) + + indx = self._owner_mgr._panes.index(pane) + + # Carry over the minimum size + pane_min_size = pane.window.GetMinSize() + + # if the best size is smaller than the min size + # then set the min size to the best size as well + pane_best_size = contained_pane.best_size + if pane_best_size.IsFullySpecified() and (pane_best_size.x < pane_min_size.x or \ + pane_best_size.y < pane_min_size.y): + + pane_min_size = pane_best_size + self._pane_window.SetMinSize(pane_min_size) + + # if the frame window's max size is greater than the min size + # then set the max size to the min size as well + cur_max_size = self.GetMaxSize() + if cur_max_size.IsFullySpecified() and (cur_max_size.x < pane_min_size.x or \ + cur_max_size.y < pane_min_size.y): + self.SetMaxSize(pane_min_size) + + art_provider = self._mgr.GetArtProvider() + caption_size = art_provider.GetMetric(AUI_DOCKART_CAPTION_SIZE) + button_size = art_provider.GetMetric(AUI_DOCKART_PANE_BUTTON_SIZE) + \ + 4*art_provider.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + + min_size = pane.window.GetMinSize() + + if min_size.y < caption_size or min_size.x < button_size: + new_x, new_y = min_size.x, min_size.y + if min_size.y < caption_size: + new_y = (pane.IsResizeable() and [2*wx.SystemSettings.GetMetric(wx.SYS_EDGE_Y)+caption_size] or [1])[0] + if min_size.x < button_size: + new_x = (pane.IsResizeable() and [2*wx.SystemSettings.GetMetric(wx.SYS_EDGE_X)+button_size] or [1])[0] + + self.SetMinSize((new_x, new_y)) + else: + self.SetMinSize(min_size) + + self._mgr.AddPane(self._pane_window, contained_pane) + self._mgr.Update() + + if pane.min_size.IsFullySpecified(): + # because SetSizeHints() calls Fit() too (which sets the window + # size to its minimum allowed), we keep the size before calling + # SetSizeHints() and reset it afterwards... + tmp = self.GetSize() + self.GetSizer().SetSizeHints(self) + self.SetSize(tmp) + + self.SetTitle(pane.caption) + + if pane.floating_size != wx.Size(-1, -1): + self.SetSize(pane.floating_size) + else: + size = pane.best_size + if size == wx.Size(-1, -1): + size = pane.min_size + if size == wx.Size(-1, -1): + size = self._pane_window.GetSize() + if self._owner_mgr and pane.HasGripper(): + if pane.HasGripperTop(): + size.y += self._owner_mgr._art.GetMetric(AUI_DOCKART_GRIPPER_SIZE) + else: + size.x += self._owner_mgr._art.GetMetric(AUI_DOCKART_GRIPPER_SIZE) + + if not self._useNativeMiniframes: + size.y += self._owner_mgr._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + + pane.floating_size = size + + self.SetClientSize(size) + + self._owner_mgr._panes[indx] = pane + + self._fly_step = abs(pane.floating_size.y - \ + (caption_size + 2*wx.SystemSettings.GetMetric(wx.SYS_EDGE_Y)))/10 + + self._floating_size = wx.Size(*self.GetSize()) + + if pane.IsFlyOut(): + self._check_fly_timer.Start(50) + + + def GetOwnerManager(self): + """ Returns the L{AuiManager} that manages the pane. """ + + return self._owner_mgr + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.SizeEvent` to be processed. + """ + + if self._owner_mgr and self._send_size: + self._owner_mgr.OnFloatingPaneResized(self._pane_window, event.GetSize()) + + + def OnClose(self, event): + """ + Handles the ``wx.EVT_CLOSE`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.CloseEvent` to be processed. + """ + + if self._owner_mgr: + self._owner_mgr.OnFloatingPaneClosed(self._pane_window, event) + + if not event.GetVeto(): + self._mgr.DetachPane(self._pane_window) + + if isinstance(self._pane_window, auibar.AuiToolBar): + self._pane_window.SetAuiManager(self._owner_mgr) + + # if we do not do this, then we can crash... + if self._owner_mgr and self._owner_mgr._action_window == self: + self._owner_mgr._action_window = None + + self.Destroy() + + + def OnActivate(self, event): + """ + Handles the ``wx.EVT_ACTIVATE`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.ActivateEvent` to be processed. + """ + + if self._owner_mgr and event.GetActive(): + self._owner_mgr.OnFloatingPaneActivated(self._pane_window) + + + def OnMove(self, event): + """ + Handles the ``wx.EVT_MOVE`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.MoveEvent` to be processed. + + :note: This event is not processed on wxMAC or if L{AuiManager} is not using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + if self._owner_mgr: + self._owner_mgr.OnFloatingPaneMoved(self._pane_window, event) + + + def OnMoveEvent(self, event): + """ + Handles the ``wx.EVT_MOVE`` and ``wx.EVT_MOVING`` events for L{AuiFloatingFrame}. + + :param `event`: a `wx.MoveEvent` to be processed. + + :note: This event is only processed on wxMAC or if L{AuiManager} is using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + win_rect = self.GetRect() + + if win_rect == self._last_rect: + return + + # skip the first move event + if self._last_rect.IsEmpty(): + self._last_rect = wx.Rect(*win_rect) + return + + # skip if moving too fast to avoid massive redraws and + # jumping hint windows + if abs(win_rect.x - self._last_rect.x) > 3 or abs(win_rect.y - self._last_rect.y) > 3: + self._last3_rect = wx.Rect(*self._last2_rect) + self._last2_rect = wx.Rect(*self._last_rect) + self._last_rect = wx.Rect(*win_rect) + return + + # prevent frame redocking during resize + if self._last_rect.GetSize() != win_rect.GetSize(): + self._last3_rect = wx.Rect(*self._last2_rect) + self._last2_rect = wx.Rect(*self._last_rect) + self._last_rect = wx.Rect(*win_rect) + return + + self._last3_rect = wx.Rect(*self._last2_rect) + self._last2_rect = wx.Rect(*self._last_rect) + self._last_rect = wx.Rect(*win_rect) + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if not leftDown: + return + + if not self._moving: + self.OnMoveStart(event) + self._moving = True + + if self._last3_rect.IsEmpty(): + return + + self.OnMoving(event) + + + def OnIdle(self, event): + """ + Handles the ``wx.EVT_IDLE`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.IdleEvent` event to be processed. + + :note: This event is only processed on wxMAC or if L{AuiManager} is using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + if self._moving: + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if not leftDown: + self._moving = False + self.OnMoveFinished() + else: + event.RequestMore() + + + def OnMoveStart(self, event): + """ + The user has just started moving the floating pane. + + :param `event`: an instance of `wx.MouseEvent`. + + :note: This method is used only on wxMAC or if L{AuiManager} is using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + # notify the owner manager that the pane has started to move + if self._owner_mgr: + if self._owner_mgr._from_move: + return + self._owner_mgr._action_window = self._pane_window + point = wx.GetMousePosition() + action_offset = point - self.GetPosition() + + if self._is_toolbar: + self._owner_mgr._toolbar_action_offset = action_offset + self._owner_mgr.OnMotion_DragToolbarPane(point) + else: + self._owner_mgr._action_offset = action_offset + self._owner_mgr.OnMotion_DragFloatingPane(point) + + + def OnMoving(self, event): + """ + The user is moving the floating pane. + + :param `event`: an instance of `wx.MouseEvent`. + + :note: This method is used only on wxMAC or if L{AuiManager} is using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + # notify the owner manager that the pane is moving + self.OnMoveStart(event) + + + def OnMoveFinished(self): + """ + The user has just finished moving the floating pane. + + :note: This method is used only on wxMAC or if L{AuiManager} is using the + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` style. + """ + + # notify the owner manager that the pane has finished moving + if self._owner_mgr: + self._owner_mgr._action_window = self._pane_window + point = wx.GetMousePosition() + if self._is_toolbar: + self._owner_mgr.OnLeftUp_DragToolbarPane(point) + else: + self._owner_mgr.OnLeftUp_DragFloatingPane(point) + + self._owner_mgr.OnFloatingPaneMoved(self._pane_window, point) + + + def OnCheckFlyTimer(self, event): + """ + Handles the ``wx.EVT_TIMER`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.TimerEvent` to be processed. + + :note: This is used solely for "fly-out" panes. + """ + + if self._owner_mgr: + pane = self._mgr.GetPane(self._pane_window) + if pane.IsFlyOut(): + if self.IsShownOnScreen(): + self.FlyOut() + + + def OnFindManager(self, event): + """ + Handles the ``EVT_AUI_FIND_MANAGER`` event for L{AuiFloatingFrame}. + + :param `event`: a L{AuiManagerEvent} event to be processed. + """ + + event.SetManager(self._owner_mgr) + + + def FlyOut(self): + """ Starts the flying in and out of a floating pane. """ + + if self._fly_timer.IsRunning(): + return + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if leftDown: + return + + rect = wx.Rect(*self.GetScreenRect()) + rect.Inflate(10, 10) + + if rect.Contains(wx.GetMousePosition()): + if not self._fly: + return + self._send_size = False + self._fly_timer.Start(5) + else: + if self._fly: + return + self._send_size = False + self._fly_timer.Start(5) + + + def OnFlyTimer(self, event): + """ + Handles the ``wx.EVT_TIMER`` event for L{AuiFloatingFrame}. + + :param `event`: a `wx.TimerEvent` to be processed. + """ + + current_size = self.GetClientSize() + floating_size = wx.Size(*self._owner_mgr.GetPane(self._pane_window).floating_size) + + if floating_size.y == -1: + floating_size = self._floating_size + + if not self._fly: + min_size = self._mgr.GetArtProvider().GetMetric(AUI_DOCKART_CAPTION_SIZE) + + if wx.Platform != "__WXMSW__": + min_size += 2*wx.SystemSettings.GetMetric(wx.SYS_EDGE_Y) + + if current_size.y - self._fly_step <= min_size: + self.SetClientSize((current_size.x, min_size)) + self._fly = True + self._fly_timer.Stop() + self._send_size = True + else: + self.SetClientSize((current_size.x, current_size.y-self._fly_step)) + + else: + if current_size.y + self._fly_step >= floating_size.y: + self.SetClientSize((current_size.x, floating_size.y)) + self._fly = False + self._fly_timer.Stop() + self._send_size = True + else: + self.SetClientSize((current_size.x, current_size.y+self._fly_step)) + + self.Update() + self.Refresh() + + + def FadeOut(self): + """ Actually starts the fading out of the floating pane. """ + + while 1: + self._alpha_amount -= 10 + if self._alpha_amount <= 0: + self._alpha_amount = 255 + return + + self.SetTransparent(self._alpha_amount) + wx.SafeYield() + wx.MilliSleep(15) + + +# -- static utility functions -- + +def DrawResizeHint(dc, rect): + """ + Draws a resize hint while a sash is dragged. + + :param `rect`: a `wx.Rect` rectangle which specifies the sash dimensions. + """ + + if wx.Platform == "__WXMSW__" and wx.App.GetComCtl32Version() >= 600: + if wx.GetOsVersion()[1] > 5: + # Windows Vista + dc.SetPen(wx.Pen("black", 2, wx.SOLID)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + else: + # Draw the nice XP style splitter + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(wx.BLACK_BRUSH) + dc.SetLogicalFunction(wx.INVERT) + dc.DrawRectangleRect(rect) + dc.SetLogicalFunction(wx.COPY) + else: + stipple = PaneCreateStippleBitmap() + brush = wx.BrushFromBitmap(stipple) + dc.SetBrush(brush) + dc.SetPen(wx.TRANSPARENT_PEN) + + dc.SetLogicalFunction(wx.XOR) + dc.DrawRectangleRect(rect) + + +def CopyDocksAndPanes(src_docks, src_panes): + """ + This utility function creates shallow copies of + the dock and pane info. L{AuiDockInfo} usually contain pointers + to L{AuiPaneInfo} classes, thus this function is necessary to reliably + reconstruct that relationship in the new dock info and pane info arrays. + + :param `src_docks`: a list of L{AuiDockInfo} classes; + :param `src_panes`: a list of L{AuiPaneInfo} classes. + """ + + dest_docks = src_docks + dest_panes = src_panes + + for ii in xrange(len(dest_docks)): + dock = dest_docks[ii] + for jj in xrange(len(dock.panes)): + for kk in xrange(len(src_panes)): + if dock.panes[jj] == src_panes[kk]: + dock.panes[jj] = dest_panes[kk] + + return dest_docks, dest_panes + + +def CopyDocksAndPanes2(src_docks, src_panes): + """ + This utility function creates full copies of + the dock and pane info. L{AuiDockInfo} usually contain pointers + to L{AuiPaneInfo} classes, thus this function is necessary to reliably + reconstruct that relationship in the new dock info and pane info arrays. + + :param `src_docks`: a list of L{AuiDockInfo} classes; + :param `src_panes`: a list of L{AuiPaneInfo} classes. + """ + + dest_docks = [] + + for ii in xrange(len(src_docks)): + dest_docks.append(AuiDockInfo()) + dest_docks[ii].dock_direction = src_docks[ii].dock_direction + dest_docks[ii].dock_layer = src_docks[ii].dock_layer + dest_docks[ii].dock_row = src_docks[ii].dock_row + dest_docks[ii].size = src_docks[ii].size + dest_docks[ii].min_size = src_docks[ii].min_size + dest_docks[ii].resizable = src_docks[ii].resizable + dest_docks[ii].fixed = src_docks[ii].fixed + dest_docks[ii].toolbar = src_docks[ii].toolbar + dest_docks[ii].panes = src_docks[ii].panes + dest_docks[ii].rect = wx.Rect(*src_docks[ii].rect) + + dest_panes = [] + + for ii in xrange(len(src_panes)): + dest_panes.append(AuiPaneInfo()) + dest_panes[ii].name = src_panes[ii].name + dest_panes[ii].caption = src_panes[ii].caption + dest_panes[ii].window = src_panes[ii].window + dest_panes[ii].frame = src_panes[ii].frame + dest_panes[ii].state = src_panes[ii].state + dest_panes[ii].dock_direction = src_panes[ii].dock_direction + dest_panes[ii].dock_layer = src_panes[ii].dock_layer + dest_panes[ii].dock_row = src_panes[ii].dock_row + dest_panes[ii].dock_pos = src_panes[ii].dock_pos + dest_panes[ii].best_size = wx.Size(*src_panes[ii].best_size) + dest_panes[ii].min_size = wx.Size(*src_panes[ii].min_size) + dest_panes[ii].max_size = wx.Size(*src_panes[ii].max_size) + dest_panes[ii].floating_pos = wx.Point(*src_panes[ii].floating_pos) + dest_panes[ii].floating_size = wx.Size(*src_panes[ii].floating_size) + dest_panes[ii].dock_proportion = src_panes[ii].dock_proportion + dest_panes[ii].buttons = src_panes[ii].buttons + dest_panes[ii].rect = wx.Rect(*src_panes[ii].rect) + dest_panes[ii].icon = src_panes[ii].icon + dest_panes[ii].notebook_id = src_panes[ii].notebook_id + dest_panes[ii].transparent = src_panes[ii].transparent + dest_panes[ii].snapped = src_panes[ii].snapped + dest_panes[ii].minimize_mode = src_panes[ii].minimize_mode + + for ii in xrange(len(dest_docks)): + dock = dest_docks[ii] + for jj in xrange(len(dock.panes)): + for kk in xrange(len(src_panes)): + if dock.panes[jj] == src_panes[kk]: + dock.panes[jj] = dest_panes[kk] + + dest_docks[ii] = dock + + return dest_docks, dest_panes + + +def GetMaxLayer(docks, dock_direction): + """ + This is an internal function which returns + the highest layer inside the specified dock. + + :param `docks`: a list of L{AuiDockInfo}; + :param `dock_direction`: the L{AuiDockInfo} docking direction to analyze. + """ + + max_layer = 0 + + for dock in docks: + if dock.dock_direction == dock_direction and dock.dock_layer > max_layer and not dock.fixed: + max_layer = dock.dock_layer + + return max_layer + + +def GetMaxRow(panes, dock_direction, dock_layer): + """ + This is an internal function which returns + the highest layer inside the specified dock. + + :param `panes`: a list of L{AuiPaneInfo}; + :param `dock_direction`: the L{AuiPaneInfo} docking direction to analyze; + :param `dock_layer`: the L{AuiPaneInfo} layer to analyze. + """ + + max_row = 0 + + for pane in panes: + if pane.dock_direction == dock_direction and pane.dock_layer == dock_layer and \ + pane.dock_row > max_row: + max_row = pane.dock_row + + return max_row + + +def DoInsertDockLayer(panes, dock_direction, dock_layer): + """ + This is an internal function that inserts a new dock + layer by incrementing all existing dock layer values by one. + + :param `panes`: a list of L{AuiPaneInfo}; + :param `dock_direction`: the L{AuiPaneInfo} docking direction to analyze; + :param `dock_layer`: the L{AuiPaneInfo} layer to analyze. + """ + + for ii in xrange(len(panes)): + pane = panes[ii] + if not pane.IsFloating() and pane.dock_direction == dock_direction and pane.dock_layer >= dock_layer: + pane.dock_layer = pane.dock_layer + 1 + + panes[ii] = pane + + return panes + + +def DoInsertDockRow(panes, dock_direction, dock_layer, dock_row): + """ + This is an internal function that inserts a new dock + row by incrementing all existing dock row values by one. + + :param `panes`: a list of L{AuiPaneInfo}; + :param `dock_direction`: the L{AuiPaneInfo} docking direction to analyze; + :param `dock_layer`: the L{AuiPaneInfo} layer to analyze; + :param `dock_row`: the L{AuiPaneInfo} row to analyze. + """ + + for pane in panes: + if not pane.IsFloating() and pane.dock_direction == dock_direction and \ + pane.dock_layer == dock_layer and pane.dock_row >= dock_row: + pane.dock_row += 1 + + return panes + + +def DoInsertPane(panes, dock_direction, dock_layer, dock_row, dock_pos): + """ + This is an internal function that inserts a new pane + by incrementing all existing dock position values by one. + + :param `panes`: a list of L{AuiPaneInfo}; + :param `dock_direction`: the L{AuiPaneInfo} docking direction to analyze; + :param `dock_layer`: the L{AuiPaneInfo} layer to analyze; + :param `dock_row`: the L{AuiPaneInfo} row to analyze; + :param `dock_pos`: the L{AuiPaneInfo} row to analyze. + """ + + for ii in xrange(len(panes)): + pane = panes[ii] + if not pane.IsFloating() and pane.dock_direction == dock_direction and \ + pane.dock_layer == dock_layer and pane.dock_row == dock_row and \ + pane.dock_pos >= dock_pos: + pane.dock_pos = pane.dock_pos + 1 + + panes[ii] = pane + + return panes + + +def FindDocks(docks, dock_direction, dock_layer=-1, dock_row=-1, reverse=False): + """ + This is an internal function that returns a list of docks which meet + the specified conditions in the parameters and returns a sorted array + (sorted by layer and then row). + + :param `docks`: a list of L{AuiDockInfo}; + :param `dock_direction`: the L{AuiDockInfo} docking direction to analyze; + :param `dock_layer`: the L{AuiDockInfo} layer to analyze; + :param `dock_row`: the L{AuiDockInfo} row to analyze; + """ + + matchDocks = [(d.dock_layer, d.dock_row, d.dock_direction, d) for d in docks if \ + (dock_direction == -1 or dock_direction == d.dock_direction) and \ + ((dock_layer == -1 or dock_layer == d.dock_layer) and \ + (dock_row == -1 or dock_row == d.dock_row))] + + arr = [x[-1] for x in sorted(matchDocks, reverse=reverse)] + + return arr + + +def FindOppositeDocks(docks, dock_direction): + """ + This is an internal function that returns a list of docks + which is related to the opposite direction. + + :param `docks`: a list of L{AuiDockInfo}; + :param `dock_direction`: the L{AuiDockInfo} docking direction to analyze; + """ + + if dock_direction == AUI_DOCK_LEFT: + arr = FindDocks(docks, AUI_DOCK_RIGHT, -1, -1) + elif dock_direction == AUI_DOCK_TOP: + arr = FindDocks(docks, AUI_DOCK_BOTTOM, -1, -1) + elif dock_direction == AUI_DOCK_RIGHT: + arr = FindDocks(docks, AUI_DOCK_LEFT, -1, -1) + elif dock_direction == AUI_DOCK_BOTTOM: + arr = FindDocks(docks, AUI_DOCK_TOP, -1, -1) + + return arr + + +def FindPaneInDock(dock, window): + """ + This method looks up a specified window pointer inside a dock. + If found, the corresponding L{AuiPaneInfo} pointer is returned, otherwise ``None``. + + :param `dock`: a L{AuiDockInfo} structure; + :param `window`: a `wx.Window` derived window (associated to a pane). + """ + + for p in dock.panes: + if p.window == window: + return p + + return None + + +def GetToolBarDockOffsets(docks): + """ + Returns the toolbar dock offsets (top-left and bottom-right). + + :param `docks`: a list of L{AuiDockInfo} to analyze. + """ + + top_left = wx.Size(0, 0) + bottom_right = wx.Size(0, 0) + + for dock in docks: + if dock.toolbar: + dock_direction = dock.dock_direction + if dock_direction == AUI_DOCK_LEFT: + top_left.x += dock.rect.width + bottom_right.x += dock.rect.width + + elif dock_direction == AUI_DOCK_TOP: + top_left.y += dock.rect.height + bottom_right.y += dock.rect.height + + elif dock_direction == AUI_DOCK_RIGHT: + bottom_right.x += dock.rect.width + + elif dock_direction == AUI_DOCK_BOTTOM: + bottom_right.y += dock.rect.height + + return top_left, bottom_right + + +def GetInternalFrameRect(window, docks): + """ + Returns the window rectangle excluding toolbars. + + :param `window`: a `wx.Window` derived window; + :param `docks`: a list of L{AuiDockInfo} structures. + """ + + frameRect = wx.Rect() + + frameRect.SetTopLeft(window.ClientToScreen(window.GetClientAreaOrigin())) + frameRect.SetSize(window.GetClientSize()) + + top_left, bottom_right = GetToolBarDockOffsets(docks) + + # make adjustments for toolbars + frameRect.x += top_left.x + frameRect.y += top_left.y + frameRect.width -= bottom_right.x + frameRect.height -= bottom_right.y + + return frameRect + + +def CheckOutOfWindow(window, pt): + """ + Checks if a point is outside the window rectangle. + + :param `window`: a `wx.Window` derived window; + :param `pt`: a `wx.Point` object. + """ + + auiWindowMargin = 30 + marginRect = wx.Rect(*window.GetClientRect()) + marginRect.Inflate(auiWindowMargin, auiWindowMargin) + + return not marginRect.Contains(pt) + + +def CheckEdgeDrop(window, docks, pt): + """ + Checks on which edge of a window the drop action has taken place. + + :param `window`: a `wx.Window` derived window; + :param `docks`: a list of L{AuiDockInfo} structures; + :param `pt`: a `wx.Point` object. + """ + + screenPt = window.ClientToScreen(pt) + clientSize = window.GetClientSize() + frameRect = GetInternalFrameRect(window, docks) + + if screenPt.y >= frameRect.GetTop() and screenPt.y < frameRect.GetBottom(): + if pt.x < auiLayerInsertOffset and pt.x > auiLayerInsertOffset - auiLayerInsertPixels: + return wx.LEFT + + if pt.x >= clientSize.x - auiLayerInsertOffset and \ + pt.x < clientSize.x - auiLayerInsertOffset + auiLayerInsertPixels: + return wx.RIGHT + + if screenPt.x >= frameRect.GetLeft() and screenPt.x < frameRect.GetRight(): + if pt.y < auiLayerInsertOffset and pt.y > auiLayerInsertOffset - auiLayerInsertPixels: + return wx.TOP + + if pt.y >= clientSize.y - auiLayerInsertOffset and \ + pt.y < clientSize.y - auiLayerInsertOffset + auiLayerInsertPixels: + return wx.BOTTOM + + return -1 + + +def RemovePaneFromDocks(docks, pane, exc=None): + """ + Removes a pane window from all docks + with a possible exception specified by parameter `exc`. + + :param `docks`: a list of L{AuiDockInfo} structures; + :param `pane`: the L{AuiPaneInfo} pane to be removed; + :param `exc`: the possible pane exception. + """ + + for ii in xrange(len(docks)): + d = docks[ii] + if d == exc: + continue + pi = FindPaneInDock(d, pane.window) + if pi: + d.panes.remove(pi) + + docks[ii] = d + + return docks + + +def RenumberDockRows(docks): + """ + Takes a dock and assigns sequential numbers + to existing rows. Basically it takes out the gaps so if a + dock has rows with numbers 0, 2, 5, they will become 0, 1, 2. + + :param `docks`: a list of L{AuiDockInfo} structures. + """ + + for ii in xrange(len(docks)): + dock = docks[ii] + dock.dock_row = ii + for jj in xrange(len(dock.panes)): + dock.panes[jj].dock_row = ii + + docks[ii] = dock + + return docks + + +def SetActivePane(panes, active_pane): + """ + Sets the active pane, as well as cycles through + every other pane and makes sure that all others' active flags + are turned off. + + :param `panes`: a list of L{AuiPaneInfo} structures; + :param `active_pane`: the pane to be made active (if found). + """ + + for pane in panes: + pane.state &= ~AuiPaneInfo.optionActive + + for pane in panes: + if pane.window == active_pane and not pane.IsNotebookPage(): + pane.state |= AuiPaneInfo.optionActive + return True, panes + + return False, panes + + +def ShowDockingGuides(guides, show): + """ + Shows or hide the docking guide windows. + + :param `guides`: a list of L{AuiDockingGuideInfo} classes; + :param `show`: whether to show or hide the docking guide windows. + """ + + for target in guides: + + if show and not target.host.IsShown(): + target.host.Show() + target.host.Update() + + elif not show and target.host.IsShown(): + target.host.Hide() + + +def RefreshDockingGuides(guides): + """ + Refreshes the docking guide windows. + + :param `guides`: a list of L{AuiDockingGuideInfo} classes; + """ + + for target in guides: + if target.host.IsShown(): + target.host.Refresh() + + +def PaneSortFunc(p1, p2): + """ + This function is used to sort panes by dock position. + + :param `p1`: a L{AuiPaneInfo} instance; + :param `p2`: another L{AuiPaneInfo} instance. + """ + + return (p1.dock_pos < p2.dock_pos and [-1] or [1])[0] + + +def GetNotebookRoot(panes, notebook_id): + """ + Returns the L{AuiPaneInfo} which has the specified `notebook_id`. + + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `notebook_id`: the target notebook id. + """ + + for paneInfo in panes: + if paneInfo.IsNotebookControl() and paneInfo.notebook_id == notebook_id: + return paneInfo + + return None + + +def EscapeDelimiters(s): + """ + Changes ``;`` into ``\`` and ``|`` into ``\|`` in the input string. + + :param `s`: the string to be analyzed. + + :note: This is an internal functions which is used for saving perspectives. + """ + + result = s.replace(";", "\\") + result = result.replace("|", "|\\") + + return result + + +def IsDifferentDockingPosition(pane1, pane2): + """ + Returns whether `pane1` and `pane2` are in a different docking position + based on pane status, docking direction, docking layer and docking row. + + :param `pane1`: a L{AuiPaneInfo} instance; + :param `pane2`: another L{AuiPaneInfo} instance. + """ + + return pane1.IsFloating() != pane2.IsFloating() or \ + pane1.dock_direction != pane2.dock_direction or \ + pane1.dock_layer != pane2.dock_layer or \ + pane1.dock_row != pane2.dock_row + + +# Convenience function +def AuiManager_HasLiveResize(manager): + """ + Static function which returns if the input `manager` should have "live resize" + behaviour. + + :param `manager`: an instance of L{AuiManager}. + + :note: This method always returns ``True`` on wxMac as this platform doesn't have + the ability to use `wx.ScreenDC` to draw sashes. + """ + + # With Core Graphics on Mac, it's not possible to show sash feedback, + # so we'll always use live update instead. + + if wx.Platform == "__WXMAC__": + return True + else: + return (manager.GetAGWFlags() & AUI_MGR_LIVE_RESIZE) == AUI_MGR_LIVE_RESIZE + + +# Convenience function +def AuiManager_UseNativeMiniframes(manager): + """ + Static function which returns if the input `manager` should use native `wx.MiniFrame` as + floating panes. + + :param `manager`: an instance of L{AuiManager}. + + :note: This method always returns ``True`` on wxMac as this platform doesn't have + the ability to use custom drawn miniframes. + """ + + # With Core Graphics on Mac, it's not possible to show sash feedback, + # so we'll always use live update instead. + + if wx.Platform == "__WXMAC__": + return True + else: + return (manager.GetAGWFlags() & AUI_MGR_USE_NATIVE_MINIFRAMES) == AUI_MGR_USE_NATIVE_MINIFRAMES + + +def GetManager(window): + """ + This function will return the aui manager for a given window. + + :param `window`: this parameter should be any child window or grand-child + window (and so on) of the frame/window managed by L{AuiManager}. The window + does not need to be managed by the manager itself, nor does it even need + to be a child or sub-child of a managed window. It must however be inside + the window hierarchy underneath the managed window. + """ + + if not isinstance(wx.GetTopLevelParent(window), AuiFloatingFrame): + if isinstance(window, auibar.AuiToolBar): + return window.GetAuiManager() + + evt = AuiManagerEvent(wxEVT_AUI_FIND_MANAGER) + evt.SetManager(None) + evt.ResumePropagation(wx.EVENT_PROPAGATE_MAX) + + if not window.GetEventHandler().ProcessEvent(evt): + return None + + return evt.GetManager() + + +# ---------------------------------------------------------------------------- # + +class AuiManager(wx.EvtHandler): + """ + AuiManager manages the panes associated with it for a particular `wx.Frame`, + using a pane's L{AuiPaneInfo} information to determine each pane's docking and + floating behavior. L{AuiManager} uses wxPython's sizer mechanism to plan the + layout of each frame. It uses a replaceable dock art class to do all drawing, + so all drawing is localized in one area, and may be customized depending on an + applications' specific needs. + + L{AuiManager} works as follows: the programmer adds panes to the class, or makes + changes to existing pane properties (dock position, floating state, show state, etc...). + To apply these changes, the L{AuiManager.Update} function is called. This batch + processing can be used to avoid flicker, by modifying more than one pane at a time, + and then "committing" all of the changes at once by calling `Update()`. + + Panes can be added quite easily:: + + text1 = wx.TextCtrl(self, -1) + text2 = wx.TextCtrl(self, -1) + self._mgr.AddPane(text1, AuiPaneInfo().Left().Caption("Pane Number One")) + self._mgr.AddPane(text2, AuiPaneInfo().Bottom().Caption("Pane Number Two")) + + self._mgr.Update() + + + Later on, the positions can be modified easily. The following will float an + existing pane in a tool window:: + + self._mgr.GetPane(text1).Float() + + + **Layers, Rows and Directions, Positions:** + + Inside AUI, the docking layout is figured out by checking several pane parameters. + Four of these are important for determining where a pane will end up. + + **Direction** - Each docked pane has a direction, `Top`, `Bottom`, `Left`, `Right`, or `Center`. + This is fairly self-explanatory. The pane will be placed in the location specified + by this variable. + + **Position** - More than one pane can be placed inside of a "dock". Imagine two panes + being docked on the left side of a window. One pane can be placed over another. + In proportionally managed docks, the pane position indicates it's sequential position, + starting with zero. So, in our scenario with two panes docked on the left side, the + top pane in the dock would have position 0, and the second one would occupy position 1. + + **Row** - A row can allow for two docks to be placed next to each other. One of the most + common places for this to happen is in the toolbar. Multiple toolbar rows are allowed, + the first row being in row 0, and the second in row 1. Rows can also be used on + vertically docked panes. + + **Layer** - A layer is akin to an onion. Layer 0 is the very center of the managed pane. + Thus, if a pane is in layer 0, it will be closest to the center window (also sometimes + known as the "content window"). Increasing layers "swallow up" all layers of a lower + value. This can look very similar to multiple rows, but is different because all panes + in a lower level yield to panes in higher levels. The best way to understand layers + is by running the AUI sample (`AUI.py`). + """ + + def __init__(self, managed_window=None, agwFlags=None): + """ + Default class constructor. + + :param `managed_window`: specifies the window which should be managed; + :param `agwFlags`: specifies options which allow the frame management behavior to be + modified. `agwFlags` can be a combination of the following style bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_MGR_ALLOW_FLOATING`` Allow floating of panes + ``AUI_MGR_ALLOW_ACTIVE_PANE`` If a pane becomes active, "highlight" it in the interface + ``AUI_MGR_TRANSPARENT_DRAG`` If the platform supports it, set transparency on a floating pane while it is dragged by the user + ``AUI_MGR_TRANSPARENT_HINT`` If the platform supports it, show a transparent hint window when the user is about to dock a floating pane + ``AUI_MGR_VENETIAN_BLINDS_HINT`` Show a "venetian blind" effect when the user is about to dock a floating pane + ``AUI_MGR_RECTANGLE_HINT`` Show a rectangle hint effect when the user is about to dock a floating pane + ``AUI_MGR_HINT_FADE`` If the platform supports it, the hint window will fade in and out + ``AUI_MGR_NO_VENETIAN_BLINDS_FADE`` Disables the "venetian blind" fade in and out + ``AUI_MGR_LIVE_RESIZE`` Live resize when the user drag a sash + ``AUI_MGR_ANIMATE_FRAMES`` Fade-out floating panes when they are closed (all platforms which support frames transparency) and show a moving rectangle when they are docked (Windows < Vista and GTK only) + ``AUI_MGR_AERO_DOCKING_GUIDES`` Use the new Aero-style bitmaps as docking guides + ``AUI_MGR_PREVIEW_MINIMIZED_PANES`` Slide in and out minimized panes to preview them + ``AUI_MGR_WHIDBEY_DOCKING_GUIDES`` Use the new Whidbey-style bitmaps as docking guides + ``AUI_MGR_SMOOTH_DOCKING`` Performs a "smooth" docking of panes (a la PyQT) + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` Use miniframes with native caption bar as floating panes instead or custom drawn caption bars (forced on wxMac) + ``AUI_MGR_AUTONB_NO_CAPTION`` Panes that merge into an automatic notebook will not have the pane caption visible + ==================================== ================================== + + Default value for `agwFlags` is: + ``AUI_MGR_DEFAULT`` = ``AUI_MGR_ALLOW_FLOATING`` | ``AUI_MGR_TRANSPARENT_HINT`` | ``AUI_MGR_HINT_FADE`` | ``AUI_MGR_NO_VENETIAN_BLINDS_FADE`` + + :note: If using the ``AUI_MGR_USE_NATIVE_MINIFRAMES``, double-clicking on a + floating pane caption will not re-dock the pane, but simply maximize it (if + L{AuiPaneInfo.MaximizeButton} has been set to ``True``) or do nothing. + """ + + wx.EvtHandler.__init__(self) + + self._action = actionNone + self._action_window = None + self._hover_button = None + self._art = dockart.AuiDefaultDockArt() + self._hint_window = None + self._active_pane = None + self._has_maximized = False + self._has_minimized = False + + self._frame = None + self._dock_constraint_x = 0.3 + self._dock_constraint_y = 0.3 + self._reserved = None + + self._panes = [] + self._docks = [] + self._uiparts = [] + + self._guides = [] + self._notebooks = [] + + self._masterManager = None + self._currentDragItem = -1 + self._lastknowndocks = {} + + self._hint_fadetimer = wx.Timer(self, wx.ID_ANY) + self._hint_fademax = 50 + self._last_hint = wx.Rect() + + self._from_move = False + self._last_rect = wx.Rect() + + if agwFlags is None: + agwFlags = AUI_MGR_DEFAULT + + self._agwFlags = agwFlags + self._is_docked = (False, wx.RIGHT, wx.TOP, 0) + self._snap_limits = (15, 15) + + if wx.Platform == "__WXMSW__": + self._animation_step = 30.0 + else: + self._animation_step = 5.0 + + self._hint_rect = wx.Rect() + + self._preview_timer = wx.Timer(self, wx.ID_ANY) + self._sliding_frame = None + + self._autoNBTabArt = tabart.AuiDefaultTabArt() + self._autoNBStyle = AUI_NB_DEFAULT_STYLE | AUI_NB_BOTTOM | \ + AUI_NB_SUB_NOTEBOOK | AUI_NB_TAB_EXTERNAL_MOVE + self._autoNBStyle -= AUI_NB_DRAW_DND_TAB + + if managed_window: + self.SetManagedWindow(managed_window) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_SET_CURSOR, self.OnSetCursor) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocus) + self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost) + self.Bind(wx.EVT_TIMER, self.OnHintFadeTimer, self._hint_fadetimer) + self.Bind(wx.EVT_TIMER, self.SlideIn, self._preview_timer) + + self.Bind(wx.EVT_MOVE, self.OnMove) + self.Bind(wx.EVT_SYS_COLOUR_CHANGED, self.OnSysColourChanged) + + self.Bind(EVT_AUI_PANE_BUTTON, self.OnPaneButton) + self.Bind(EVT_AUI_RENDER, self.OnRender) + self.Bind(EVT_AUI_FIND_MANAGER, self.OnFindManager) + self.Bind(EVT_AUI_PANE_MIN_RESTORE, self.OnRestoreMinimizedPane) + self.Bind(EVT_AUI_PANE_DOCKED, self.OnPaneDocked) + + self.Bind(auibook.EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag) + self.Bind(auibook.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnTabPageClose) + self.Bind(auibook.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnTabSelected) + + + def CreateFloatingFrame(self, parent, pane_info): + """ + Creates a floating frame for the windows. + + :param `parent`: the floating frame parent; + :param `pane_info`: the L{AuiPaneInfo} class with all the pane's information. + """ + + return AuiFloatingFrame(parent, self, pane_info) + + + def CanDockPanel(self, p): + """ + Returns whether a pane can be docked or not. + + :param `p`: the L{AuiPaneInfo} class with all the pane's information. + """ + + # is the pane dockable? + if not p.IsDockable(): + return False + + # if a key modifier is pressed while dragging the frame, + # don't dock the window + return not (wx.GetKeyState(wx.WXK_CONTROL) or wx.GetKeyState(wx.WXK_ALT)) + + + def GetPaneByWidget(self, window): + """ + This version of L{GetPane} looks up a pane based on a + 'pane window'. + + :param `window`: a `wx.Window` derived window. + + :see: L{GetPane} + """ + + for p in self._panes: + if p.window == window: + return p + + return NonePaneInfo + + + def GetPaneByName(self, name): + """ + This version of L{GetPane} looks up a pane based on a + 'pane name'. + + :param `name`: the pane name. + + :see: L{GetPane} + """ + + for p in self._panes: + if p.name == name: + return p + + return NonePaneInfo + + + def GetPane(self, item): + """ + Looks up a L{AuiPaneInfo} structure based + on the supplied window pointer. Upon failure, L{GetPane} + returns an empty L{AuiPaneInfo}, a condition which can be checked + by calling L{AuiPaneInfo.IsOk}. + + The pane info's structure may then be modified. Once a pane's + info is modified, L{Update} must be called to + realize the changes in the UI. + + :param `item`: either a pane name or a `wx.Window`. + """ + + if isinstance(item, basestring): + return self.GetPaneByName(item) + else: + return self.GetPaneByWidget(item) + + + def GetAllPanes(self): + """ Returns a reference to all the pane info structures. """ + + return self._panes + + + def ShowPane(self, window, show): + """ + Shows or hides a pane based on the window passed as input. + + :param `window`: a `wx.Window` derived window; + :param `show`: ``True`` to show the pane, ``False`` otherwise. + """ + + p = self.GetPane(window) + + if p.IsOk(): + if p.IsNotebookPage(): + if show: + + notebook = self._notebooks[p.notebook_id] + id = notebook.GetPageIndex(p.window) + if id >= 0: + notebook.SetSelection(id) + self.ShowPane(notebook, True) + + else: + p.Show(show) + + if p.frame: + p.frame.Raise() + + self.Update() + + + def HitTest(self, x, y): + """ + This is an internal function which determines + which UI item the specified coordinates are over. + + :param `x`: specifies a x position in client coordinates; + :param `y`: specifies a y position in client coordinates. + """ + + result = None + + for item in self._uiparts: + # we are not interested in typeDock, because this space + # isn't used to draw anything, just for measurements + # besides, the entire dock area is covered with other + # rectangles, which we are interested in. + if item.type == AuiDockUIPart.typeDock: + continue + + # if we already have a hit on a more specific item, we are not + # interested in a pane hit. If, however, we don't already have + # a hit, returning a pane hit is necessary for some operations + if item.type in [AuiDockUIPart.typePane, AuiDockUIPart.typePaneBorder] and result: + continue + + # if the point is inside the rectangle, we have a hit + if item.rect.Contains((x, y)): + result = item + + return result + + + def PaneHitTest(self, panes, pt): + """ + Similar to L{HitTest}, but it checks in which L{AuiPaneInfo} rectangle the + input point belongs to. + + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `pt`: a `wx.Point` object. + """ + + for paneInfo in panes: + if paneInfo.IsDocked() and paneInfo.IsShown() and paneInfo.rect.Contains(pt): + return paneInfo + + return NonePaneInfo + + + # SetAGWFlags() and GetAGWFlags() allow the owner to set various + # options which are global to AuiManager + + def SetAGWFlags(self, agwFlags): + """ + This method is used to specify L{AuiManager}'s settings flags. + + :param `agwFlags`: specifies options which allow the frame management behavior + to be modified. `agwFlags` can be one of the following style bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_MGR_ALLOW_FLOATING`` Allow floating of panes + ``AUI_MGR_ALLOW_ACTIVE_PANE`` If a pane becomes active, "highlight" it in the interface + ``AUI_MGR_TRANSPARENT_DRAG`` If the platform supports it, set transparency on a floating pane while it is dragged by the user + ``AUI_MGR_TRANSPARENT_HINT`` If the platform supports it, show a transparent hint window when the user is about to dock a floating pane + ``AUI_MGR_VENETIAN_BLINDS_HINT`` Show a "venetian blind" effect when the user is about to dock a floating pane + ``AUI_MGR_RECTANGLE_HINT`` Show a rectangle hint effect when the user is about to dock a floating pane + ``AUI_MGR_HINT_FADE`` If the platform supports it, the hint window will fade in and out + ``AUI_MGR_NO_VENETIAN_BLINDS_FADE`` Disables the "venetian blind" fade in and out + ``AUI_MGR_LIVE_RESIZE`` Live resize when the user drag a sash + ``AUI_MGR_ANIMATE_FRAMES`` Fade-out floating panes when they are closed (all platforms which support frames transparency) and show a moving rectangle when they are docked (Windows < Vista and GTK only) + ``AUI_MGR_AERO_DOCKING_GUIDES`` Use the new Aero-style bitmaps as docking guides + ``AUI_MGR_PREVIEW_MINIMIZED_PANES`` Slide in and out minimized panes to preview them + ``AUI_MGR_WHIDBEY_DOCKING_GUIDES`` Use the new Whidbey-style bitmaps as docking guides + ``AUI_MGR_SMOOTH_DOCKING`` Performs a "smooth" docking of panes (a la PyQT) + ``AUI_MGR_USE_NATIVE_MINIFRAMES`` Use miniframes with native caption bar as floating panes instead or custom drawn caption bars (forced on wxMac) + ``AUI_MGR_AUTONB_NO_CAPTION`` Panes that merge into an automatic notebook will not have the pane caption visible + ==================================== ================================== + + :note: If using the ``AUI_MGR_USE_NATIVE_MINIFRAMES``, double-clicking on a + floating pane caption will not re-dock the pane, but simply maximize it (if + L{AuiPaneInfo.MaximizeButton} has been set to ``True``) or do nothing. + + """ + + self._agwFlags = agwFlags + + if len(self._guides) > 0: + self.CreateGuideWindows() + + if self._hint_window and agwFlags & AUI_MGR_RECTANGLE_HINT == 0: + self.CreateHintWindow() + + + def GetAGWFlags(self): + """ + Returns the current manager's flags. + + :see: L{SetAGWFlags} for a list of possible L{AuiManager} flags. + """ + + return self._agwFlags + + + def SetManagedWindow(self, managed_window): + """ + Called to specify the frame or window which is to be managed by L{AuiManager}. + Frame management is not restricted to just frames. Child windows or custom + controls are also allowed. + + :param `managed_window`: specifies the window which should be managed by + the AUI manager. + """ + + if not managed_window: + raise Exception("Specified managed window must be non-null. ") + + self._frame = managed_window + self._frame.PushEventHandler(self) + + # if the owner is going to manage an MDI parent frame, + # we need to add the MDI client window as the default + # center pane + + if isinstance(self._frame, wx.MDIParentFrame): + mdi_frame = self._frame + client_window = mdi_frame.GetClientWindow() + + if not client_window: + raise Exception("Client window is None!") + + self.AddPane(client_window, AuiPaneInfo().Name("mdiclient"). + CenterPane().PaneBorder(False)) + + elif isinstance(self._frame, tabmdi.AuiMDIParentFrame): + + mdi_frame = self._frame + client_window = mdi_frame.GetClientWindow() + + if not client_window: + raise Exception("Client window is None!") + + self.AddPane(client_window, AuiPaneInfo().Name("mdiclient"). + CenterPane().PaneBorder(False)) + + + def GetManagedWindow(self): + """ Returns the window being managed by L{AuiManager}. """ + + return self._frame + + + def SetFrame(self, managed_window): + """ + Called to specify the frame or window which is to be managed by L{AuiManager}. + Frame management is not restricted to just frames. Child windows or custom + controls are also allowed. + + :param `managed_window`: specifies the window which should be managed by + the AUI manager. + + :warning: This method is now deprecated, use L{SetManagedWindow} instead. + """ + + DeprecationWarning("This method is deprecated, use SetManagedWindow instead.") + return self.SetManagedWindow(managed_window) + + + def GetFrame(self): + """ + Returns the window being managed by L{AuiManager}. + + :warning: This method is now deprecated, use L{GetManagedWindow} instead. + """ + + DeprecationWarning("This method is deprecated, use GetManagedWindow instead.") + return self._frame + + + def CreateGuideWindows(self): + """ Creates the VS2005 HUD guide windows. """ + + self.DestroyGuideWindows() + + self._guides.append(AuiDockingGuideInfo().Left(). + Host(AuiSingleDockingGuide(self._frame, wx.LEFT))) + self._guides.append(AuiDockingGuideInfo().Top(). + Host(AuiSingleDockingGuide(self._frame, wx.TOP))) + self._guides.append(AuiDockingGuideInfo().Right(). + Host(AuiSingleDockingGuide(self._frame, wx.RIGHT))) + self._guides.append(AuiDockingGuideInfo().Bottom(). + Host(AuiSingleDockingGuide(self._frame, wx.BOTTOM))) + self._guides.append(AuiDockingGuideInfo().Centre(). + Host(AuiCenterDockingGuide(self._frame))) + + + def DestroyGuideWindows(self): + """ Destroys the VS2005 HUD guide windows. """ + + for guide in self._guides: + if guide.host: + guide.host.Destroy() + + self._guides = [] + + + def CreateHintWindow(self): + """ Creates the standard wxAUI hint window. """ + + self.DestroyHintWindow() + + self._hint_window = AuiDockingHintWindow(self._frame) + self._hint_window.SetBlindMode(self._agwFlags) + + + def DestroyHintWindow(self): + """ Destroys the standard wxAUI hint window. """ + + if self._hint_window: + + self._hint_window.Destroy() + self._hint_window = None + + + def UnInit(self): + """ + Uninitializes the framework and should be called before a managed frame or + window is destroyed. L{UnInit} is usually called in the managed `wx.Frame`/`wx.Window` + destructor. + + It is necessary to call this function before the managed frame or window is + destroyed, otherwise the manager cannot remove its custom event handlers + from a window. + """ + + if self._frame: + self._frame.RemoveEventHandler(self) + + + def GetArtProvider(self): + """ Returns the current art provider being used. """ + + return self._art + + + def ProcessMgrEvent(self, event): + """ + Process the AUI events sent to the manager. + + :param `event`: the event to process, an instance of L{AuiManagerEvent}. + """ + + # first, give the owner frame a chance to override + if self._frame: + if self._frame.GetEventHandler().ProcessEvent(event): + return + + self.ProcessEvent(event) + + + def FireEvent(self, evtType, pane, canVeto=False): + """ + Fires one of the ``EVT_AUI_PANE_FLOATED``/``FLOATING``/``DOCKING``/``DOCKED``/``ACTIVATED`` event. + + :param `evtType`: one of the aforementioned events; + :param `pane`: the L{AuiPaneInfo} instance associated to this event; + :param `canVeto`: whether the event can be vetoed or not. + """ + + event = AuiManagerEvent(evtType) + event.SetPane(pane) + event.SetCanVeto(canVeto) + self.ProcessMgrEvent(event) + + return event + + + def CanUseModernDockArt(self): + """ + Returns whether L{ModernDockArt} can be used (Windows XP / Vista / 7 only, + requires Mark Hammonds's `pywin32` package). + """ + + if not _winxptheme: + return False + + # Get the size of a small close button (themed) + hwnd = self._frame.GetHandle() + hTheme = winxptheme.OpenThemeData(hwnd, "Window") + + if not hTheme: + return False + + return True + + + def SetArtProvider(self, art_provider): + """ + Instructs L{AuiManager} to use art provider specified by the parameter + `art_provider` for all drawing calls. This allows plugable look-and-feel + features. + + :param `art_provider`: a AUI dock art provider. + + :note: The previous art provider object, if any, will be deleted by L{AuiManager}. + """ + + # delete the last art provider, if any + del self._art + + # assign the new art provider + self._art = art_provider + + for pane in self.GetAllPanes(): + if pane.IsFloating() and pane.frame: + pane.frame._mgr.SetArtProvider(art_provider) + pane.frame._mgr.Update() + + + def AddPane(self, window, arg1=None, arg2=None, target=None): + """ + Tells the frame manager to start managing a child window. There + are four versions of this function. The first verison allows the full spectrum + of pane parameter possibilities (L{AddPane1}). The second version is used for + simpler user interfaces which do not require as much configuration (L{AddPane2}). + The L{AddPane3} version allows a drop position to be specified, which will determine + where the pane will be added. The L{AddPane4} version allows to turn the target + L{AuiPaneInfo} pane into a notebook and the added pane into a page. + + In wxPython, simply call L{AddPane}. + + :param `window`: the child window to manage; + :param `arg1`: a L{AuiPaneInfo} or an integer value (direction); + :param `arg2`: a L{AuiPaneInfo} or a `wx.Point` (drop position); + :param `target`: a L{AuiPaneInfo} to be turned into a notebook + and new pane added to it as a page. (additionally, target can be any pane in + an existing notebook) + """ + + if target in self._panes: + return self.AddPane4(window, arg1, target) + + if type(arg1) == type(1): + # This Is Addpane2 + if arg1 is None: + arg1 = wx.LEFT + if arg2 is None: + arg2 = "" + return self.AddPane2(window, arg1, arg2) + else: + if isinstance(arg2, wx.Point): + return self.AddPane3(window, arg1, arg2) + else: + return self.AddPane1(window, arg1) + + + def AddPane1(self, window, pane_info): + """ See comments on L{AddPane}. """ + + # check if the pane has a valid window + if not window: + return False + + # check if the pane already exists + if self.GetPane(pane_info.window).IsOk(): + return False + + # check if the pane name already exists, this could reveal a + # bug in the library user's application + already_exists = False + if pane_info.name != "" and self.GetPane(pane_info.name).IsOk(): + warnings.warn("A pane with the name '%s' already exists in the manager!"%pane_info.name) + already_exists = True + + # if the new pane is docked then we should undo maximize + if pane_info.IsDocked(): + self.RestoreMaximizedPane() + + self._panes.append(pane_info) + pinfo = self._panes[-1] + + # set the pane window + pinfo.window = window + + # if the pane's name identifier is blank, create a random string + if pinfo.name == "" or already_exists: + pinfo.name = ("%s%08x%08x%08x")%(pinfo.window.GetName(), time.time(), + time.clock(), len(self._panes)) + + # set initial proportion (if not already set) + if pinfo.dock_proportion == 0: + pinfo.dock_proportion = 100000 + + floating = isinstance(self._frame, AuiFloatingFrame) + + pinfo.buttons = [] + + if not floating and pinfo.HasMinimizeButton(): + button = AuiPaneButton(AUI_BUTTON_MINIMIZE) + pinfo.buttons.append(button) + + if not floating and pinfo.HasMaximizeButton(): + button = AuiPaneButton(AUI_BUTTON_MAXIMIZE_RESTORE) + pinfo.buttons.append(button) + + if not floating and pinfo.HasPinButton(): + button = AuiPaneButton(AUI_BUTTON_PIN) + pinfo.buttons.append(button) + + if pinfo.HasCloseButton(): + button = AuiPaneButton(AUI_BUTTON_CLOSE) + pinfo.buttons.append(button) + + if pinfo.HasGripper(): + if isinstance(pinfo.window, auibar.AuiToolBar): + # prevent duplicate gripper -- both AuiManager and AuiToolBar + # have a gripper control. The toolbar's built-in gripper + # meshes better with the look and feel of the control than ours, + # so turn AuiManager's gripper off, and the toolbar's on. + + tb = pinfo.window + pinfo.SetFlag(AuiPaneInfo.optionGripper, False) + tb.SetGripperVisible(True) + + if pinfo.window: + if pinfo.best_size == wx.Size(-1, -1): + pinfo.best_size = pinfo.window.GetClientSize() + + if isinstance(pinfo.window, wx.ToolBar): + # GetClientSize() doesn't get the best size for + # a toolbar under some newer versions of wxWidgets, + # so use GetBestSize() + pinfo.best_size = pinfo.window.GetBestSize() + + # this is needed for Win2000 to correctly fill toolbar backround + # it should probably be repeated once system colour change happens + if wx.Platform == "__WXMSW__" and pinfo.window.UseBgCol(): + pinfo.window.SetBackgroundColour(self.GetArtProvider().GetColour(AUI_DOCKART_BACKGROUND_COLOUR)) + + if pinfo.min_size != wx.Size(-1, -1): + if pinfo.best_size.x < pinfo.min_size.x: + pinfo.best_size.x = pinfo.min_size.x + if pinfo.best_size.y < pinfo.min_size.y: + pinfo.best_size.y = pinfo.min_size.y + + self._panes[-1] = pinfo + if isinstance(window, auibar.AuiToolBar): + window.SetAuiManager(self) + + return True + + + def AddPane2(self, window, direction, caption): + """ See comments on L{AddPane}. """ + + pinfo = AuiPaneInfo() + pinfo.Caption(caption) + + if direction == wx.TOP: + pinfo.Top() + elif direction == wx.BOTTOM: + pinfo.Bottom() + elif direction == wx.LEFT: + pinfo.Left() + elif direction == wx.RIGHT: + pinfo.Right() + elif direction == wx.CENTER: + pinfo.CenterPane() + + return self.AddPane(window, pinfo) + + + def AddPane3(self, window, pane_info, drop_pos): + """ See comments on L{AddPane}. """ + + if not self.AddPane(window, pane_info): + return False + + pane = self.GetPane(window) + indx = self._panes.index(pane) + + ret, pane = self.DoDrop(self._docks, self._panes, pane, drop_pos, wx.Point(0, 0)) + self._panes[indx] = pane + + return True + + + def AddPane4(self, window, pane_info, target): + """ See comments on L{AddPane}. """ + + if not self.AddPane(window, pane_info): + return False + + paneInfo = self.GetPane(window) + + if not paneInfo.IsNotebookDockable(): + return self.AddPane1(window, pane_info) + if not target.IsNotebookDockable() and not target.IsNotebookControl(): + return self.AddPane1(window, pane_info) + + if not target.HasNotebook(): + self.CreateNotebookBase(self._panes, target) + + # Add new item to notebook + paneInfo.NotebookPage(target.notebook_id) + + # we also want to remove our captions sometimes + self.RemoveAutoNBCaption(paneInfo) + self.UpdateNotebook() + + return True + + + def InsertPane(self, window, pane_info, insert_level=AUI_INSERT_PANE): + """ + This method is used to insert either a previously unmanaged pane window + into the frame manager, or to insert a currently managed pane somewhere else. + L{InsertPane} will push all panes, rows, or docks aside and insert the window + into the position specified by `pane_info`. + + Because `pane_info` can specify either a pane, dock row, or dock layer, the + `insert_level` parameter is used to disambiguate this. The parameter `insert_level` + can take a value of ``AUI_INSERT_PANE``, ``AUI_INSERT_ROW`` or ``AUI_INSERT_DOCK``. + + :param `window`: the window to be inserted and managed; + :param `pane_info`: the insert location for the new window; + :param `insert_level`: the insertion level of the new pane. + """ + + if not window: + raise Exception("Invalid window passed to InsertPane.") + + # shift the panes around, depending on the insert level + if insert_level == AUI_INSERT_PANE: + self._panes = DoInsertPane(self._panes, pane_info.dock_direction, + pane_info.dock_layer, pane_info.dock_row, + pane_info.dock_pos) + + elif insert_level == AUI_INSERT_ROW: + self._panes = DoInsertDockRow(self._panes, pane_info.dock_direction, + pane_info.dock_layer, pane_info.dock_row) + + elif insert_level == AUI_INSERT_DOCK: + self._panes = DoInsertDockLayer(self._panes, pane_info.dock_direction, + pane_info.dock_layer) + + # if the window already exists, we are basically just moving/inserting the + # existing window. If it doesn't exist, we need to add it and insert it + existing_pane = self.GetPane(window) + indx = self._panes.index(existing_pane) + + if not existing_pane.IsOk(): + + return self.AddPane(window, pane_info) + + else: + + if pane_info.IsFloating(): + existing_pane.Float() + if pane_info.floating_pos != wx.Point(-1, -1): + existing_pane.FloatingPosition(pane_info.floating_pos) + if pane_info.floating_size != wx.Size(-1, -1): + existing_pane.FloatingSize(pane_info.floating_size) + else: + # if the new pane is docked then we should undo maximize + self.RestoreMaximizedPane() + + existing_pane.Direction(pane_info.dock_direction) + existing_pane.Layer(pane_info.dock_layer) + existing_pane.Row(pane_info.dock_row) + existing_pane.Position(pane_info.dock_pos) + + self._panes[indx] = existing_pane + + return True + + + def DetachPane(self, window): + """ + Tells the L{AuiManager} to stop managing the pane specified + by `window`. The window, if in a floated frame, is reparented to the frame + managed by L{AuiManager}. + + :param `window`: the window to be un-managed. + """ + + for p in self._panes: + if p.window == window: + if p.frame: + # we have a floating frame which is being detached. We need to + # reparent it to self._frame and destroy the floating frame + + # reduce flicker + p.window.SetSize((1, 1)) + if p.frame.IsShown(): + p.frame.Show(False) + + if self._action_window == p.frame: + self._action_window = None + + # reparent to self._frame and destroy the pane + p.window.Reparent(self._frame) + p.frame.SetSizer(None) + p.frame.Destroy() + p.frame = None + + elif p.IsNotebookPage(): + notebook = self._notebooks[p.notebook_id] + id = notebook.GetPageIndex(p.window) + notebook.RemovePage(id) + + # make sure there are no references to this pane in our uiparts, + # just in case the caller doesn't call Update() immediately after + # the DetachPane() call. This prevets obscure crashes which would + # happen at window repaint if the caller forgets to call Update() + counter = 0 + for pi in xrange(len(self._uiparts)): + part = self._uiparts[counter] + if part.pane == p: + self._uiparts.pop(counter) + counter -= 1 + + counter += 1 + + self._panes.remove(p) + return True + + return False + + + def ClosePane(self, pane_info): + """ + Destroys or hides the pane depending on its flags. + + :param `pane_info`: a L{AuiPaneInfo} instance. + """ + + # if we were maximized, restore + if pane_info.IsMaximized(): + self.RestorePane(pane_info) + + if pane_info.frame: + if self._agwFlags & AUI_MGR_ANIMATE_FRAMES: + pane_info.frame.FadeOut() + + # first, hide the window + if pane_info.window and pane_info.window.IsShown(): + pane_info.window.Show(False) + + # make sure that we are the parent of this window + if pane_info.window and pane_info.window.GetParent() != self._frame: + pane_info.window.Reparent(self._frame) + + # if we have a frame, destroy it + if pane_info.frame: + pane_info.frame.Destroy() + pane_info.frame = None + + elif pane_info.IsNotebookPage(): + # if we are a notebook page, remove ourselves... + # the code would index out of bounds + # if the last page of a sub-notebook was closed + # because the notebook would be deleted, before this + # code is executed. + # This code just prevents an out-of bounds error. + if self._notebooks: + nid = pane_info.notebook_id + if nid >= 0 and nid < len(self._notebooks): + notebook = self._notebooks[nid] + page_idx = notebook.GetPageIndex(pane_info.window) + if page_idx >= 0: + notebook.RemovePage(page_idx) + + # now we need to either destroy or hide the pane + to_destroy = 0 + if pane_info.IsDestroyOnClose(): + to_destroy = pane_info.window + self.DetachPane(to_destroy) + else: + if isinstance(pane_info.window, auibar.AuiToolBar) and pane_info.IsFloating(): + tb = pane_info.window + if pane_info.dock_direction in [AUI_DOCK_LEFT, AUI_DOCK_RIGHT]: + tb.SetAGWWindowStyleFlag(tb.GetAGWWindowStyleFlag() | AUI_TB_VERTICAL) + + pane_info.Dock().Hide() + + if pane_info.IsNotebookControl(): + + notebook = self._notebooks[pane_info.notebook_id] + while notebook.GetPageCount(): + window = notebook.GetPage(0) + notebook.RemovePage(0) + info = self.GetPane(window) + if info.IsOk(): + info.notebook_id = -1 + info.dock_direction = AUI_DOCK_NONE + # Note: this could change our paneInfo reference ... + self.ClosePane(info) + + if to_destroy: + to_destroy.Destroy() + + + def MaximizePane(self, pane_info, savesizes=True): + """ + Maximizes the input pane. + + :param `pane_info`: a L{AuiPaneInfo} instance. + :param `savesizes`: whether to save previous dock sizes. + """ + + if savesizes: + self.SavePreviousDockSizes(pane_info) + + for p in self._panes: + + # save hidden state + p.SetFlag(p.savedHiddenState, p.HasFlag(p.optionHidden)) + + if not p.IsToolbar() and not p.IsFloating(): + p.Restore() + + # hide the pane, because only the newly + # maximized pane should show + p.Hide() + + pane_info.previousDockPos = pane_info.dock_pos + + # mark ourselves maximized + pane_info.Maximize() + pane_info.Show() + self._has_maximized = True + + # last, show the window + if pane_info.window and not pane_info.window.IsShown(): + pane_info.window.Show(True) + + + def SavePreviousDockSizes(self, pane_info): + """ + Stores the previous dock sizes, to be used in a "restore" action later. + + :param `pane_info`: a L{AuiPaneInfo} instance. + """ + + for d in self._docks: + if not d.toolbar: + for p in d.panes: + p.previousDockSize = d.size + if pane_info is not p: + p.SetFlag(p.needsRestore, True) + + + def RestorePane(self, pane_info): + """ + Restores the input pane from a previous maximized or minimized state. + + :param `pane_info`: a L{AuiPaneInfo} instance. + """ + + # restore all the panes + for p in self._panes: + if not p.IsToolbar(): + p.SetFlag(p.optionHidden, p.HasFlag(p.savedHiddenState)) + + pane_info.SetFlag(pane_info.needsRestore, True) + + # mark ourselves non-maximized + pane_info.Restore() + self._has_maximized = False + self._has_minimized = False + + # last, show the window + if pane_info.window and not pane_info.window.IsShown(): + pane_info.window.Show(True) + + + def RestoreMaximizedPane(self): + """ Restores the current maximized pane (if any). """ + + # restore all the panes + for p in self._panes: + if p.IsMaximized(): + self.RestorePane(p) + break + + + def ActivatePane(self, window): + """ + Activates the pane to which `window` is associated. + + :param `window`: a `wx.Window` derived window. + """ + + if self.GetAGWFlags() & AUI_MGR_ALLOW_ACTIVE_PANE: + while window: + ret, self._panes = SetActivePane(self._panes, window) + if ret: + break + + window = window.GetParent() + + self.RefreshCaptions() + self.FireEvent(wxEVT_AUI_PANE_ACTIVATED, window, canVeto=False) + + + def CreateNotebook(self): + """ + Creates an automatic L{AuiNotebook} when a pane is docked on + top of another pane. + """ + + notebook = auibook.AuiNotebook(self._frame, -1, wx.Point(0, 0), wx.Size(0, 0), agwStyle=self._autoNBStyle) + + # This is so we can get the tab-drag event. + notebook.GetAuiManager().SetMasterManager(self) + notebook.SetArtProvider(self._autoNBTabArt.Clone()) + self._notebooks.append(notebook) + + return notebook + + + def SetAutoNotebookTabArt(self, art): + """ + Sets the default tab art provider for automatic notebooks. + + :param `art`: a tab art provider. + """ + + for nb in self._notebooks: + nb.SetArtProvider(art.Clone()) + nb.Refresh() + nb.Update() + + self._autoNBTabArt = art + + + def GetAutoNotebookTabArt(self): + """ Returns the default tab art provider for automatic notebooks. """ + + return self._autoNBTabArt + + + def SetAutoNotebookStyle(self, agwStyle): + """ + Sets the default AGW-specific window style for automatic notebooks. + + :param `agwStyle`: the underlying L{AuiNotebook} window style. + This can be a combination of the following bits: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by {AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ==================================== ================================== + + """ + + for nb in self._notebooks: + nb.SetAGWWindowStyleFlag(agwStyle) + nb.Refresh() + nb.Update() + + self._autoNBStyle = agwStyle + + + def GetAutoNotebookStyle(self): + """ + Returns the default AGW-specific window style for automatic notebooks. + + :see: L{SetAutoNotebookStyle} method for a list of possible styles. + """ + + return self._autoNBStyle + + + def SavePaneInfo(self, pane): + """ + This method is similar to L{SavePerspective}, with the exception + that it only saves information about a single pane. It is used in + combination with L{LoadPaneInfo}. + + :param `pane`: a L{AuiPaneInfo} instance to save. + """ + + result = "name=" + EscapeDelimiters(pane.name) + ";" + result += "caption=" + EscapeDelimiters(pane.caption) + ";" + + result += "state=%u;"%pane.state + result += "dir=%d;"%pane.dock_direction + result += "layer=%d;"%pane.dock_layer + result += "row=%d;"%pane.dock_row + result += "pos=%d;"%pane.dock_pos + result += "prop=%d;"%pane.dock_proportion + result += "bestw=%d;"%pane.best_size.x + result += "besth=%d;"%pane.best_size.y + result += "minw=%d;"%pane.min_size.x + result += "minh=%d;"%pane.min_size.y + result += "maxw=%d;"%pane.max_size.x + result += "maxh=%d;"%pane.max_size.y + result += "floatx=%d;"%pane.floating_pos.x + result += "floaty=%d;"%pane.floating_pos.y + result += "floatw=%d;"%pane.floating_size.x + result += "floath=%d;"%pane.floating_size.y + result += "notebookid=%d;"%pane.notebook_id + result += "transparent=%d"%pane.transparent + + return result + + + def LoadPaneInfo(self, pane_part, pane): + """ + This method is similar to to L{LoadPerspective}, with the exception that + it only loads information about a single pane. It is used in combination + with L{SavePaneInfo}. + + :param `pane_part`: the string to analyze; + :param `pane`: the L{AuiPaneInfo} structure in which to load `pane_part`. + """ + + # replace escaped characters so we can + # split up the string easily + pane_part = pane_part.replace("\\|", "\a") + pane_part = pane_part.replace("\\;", "\b") + + options = pane_part.split(";") + for items in options: + + val_name, value = items.split("=") + val_name = val_name.strip() + + if val_name == "name": + pane.name = value + elif val_name == "caption": + pane.caption = value + elif val_name == "state": + pane.state = int(value) + elif val_name == "dir": + pane.dock_direction = int(value) + elif val_name == "layer": + pane.dock_layer = int(value) + elif val_name == "row": + pane.dock_row = int(value) + elif val_name == "pos": + pane.dock_pos = int(value) + elif val_name == "prop": + pane.dock_proportion = int(value) + elif val_name == "bestw": + pane.best_size.x = int(value) + elif val_name == "besth": + pane.best_size.y = int(value) + pane.best_size = wx.Size(pane.best_size.x, pane.best_size.y) + elif val_name == "minw": + pane.min_size.x = int(value) + elif val_name == "minh": + pane.min_size.y = int(value) + pane.min_size = wx.Size(pane.min_size.x, pane.min_size.y) + elif val_name == "maxw": + pane.max_size.x = int(value) + elif val_name == "maxh": + pane.max_size.y = int(value) + pane.max_size = wx.Size(pane.max_size.x, pane.max_size.y) + elif val_name == "floatx": + pane.floating_pos.x = int(value) + elif val_name == "floaty": + pane.floating_pos.y = int(value) + pane.floating_pos = wx.Point(pane.floating_pos.x, pane.floating_pos.y) + elif val_name == "floatw": + pane.floating_size.x = int(value) + elif val_name == "floath": + pane.floating_size.y = int(value) + pane.floating_size = wx.Size(pane.floating_size.x, pane.floating_size.y) + elif val_name == "notebookid": + pane.notebook_id = int(value) + elif val_name == "transparent": + pane.transparent = int(value) + else: + raise Exception("Bad perspective string") + + # replace escaped characters so we can + # split up the string easily + pane.name = pane.name.replace("\a", "|") + pane.name = pane.name.replace("\b", ";") + pane.caption = pane.caption.replace("\a", "|") + pane.caption = pane.caption.replace("\b", ";") + pane_part = pane_part.replace("\a", "|") + pane_part = pane_part.replace("\b", ";") + + return pane + + + def SavePerspective(self): + """ + Saves the entire user interface layout into an encoded string, which can then + be stored by the application (probably using `wx.Config`). + + When a perspective is restored using L{LoadPerspective}, the entire user + interface will return to the state it was when the perspective was saved. + """ + + result = "layout2|" + + for pane in self._panes: + result += self.SavePaneInfo(pane) + "|" + + for dock in self._docks: + result = result + ("dock_size(%d,%d,%d)=%d|")%(dock.dock_direction, + dock.dock_layer, + dock.dock_row, + dock.size) + return result + + + def LoadPerspective(self, layout, update=True): + """ + Loads a layout which was saved with L{SavePerspective}. + + If the `update` flag parameter is ``True``, L{Update} will be + automatically invoked, thus realizing the saved perspective on screen. + + :param `layout`: a string which contains a saved AUI layout; + :param `update`: whether to update immediately the window or not. + """ + + input = layout + + # check layout string version + # 'layout1' = wxAUI 0.9.0 - wxAUI 0.9.2 + # 'layout2' = wxAUI 0.9.2 (wxWidgets 2.8) + index = input.find("|") + part = input[0:index].strip() + input = input[index+1:] + + if part != "layout2": + return False + + # mark all panes currently managed as docked and hidden + for pane in self._panes: + pane.Dock().Hide() + + # clear out the dock array; this will be reconstructed + self._docks = [] + + # replace escaped characters so we can + # split up the string easily + input = input.replace("\\|", "\a") + input = input.replace("\\;", "\b") + + while 1: + + pane = AuiPaneInfo() + index = input.find("|") + pane_part = input[0:index].strip() + input = input[index+1:] + + # if the string is empty, we're done parsing + if pane_part == "": + break + + if pane_part[0:9] == "dock_size": + index = pane_part.find("=") + val_name = pane_part[0:index] + value = pane_part[index+1:] + + index = val_name.find("(") + piece = val_name[index+1:] + index = piece.find(")") + piece = piece[0:index] + + vals = piece.split(",") + dir = int(vals[0]) + layer = int(vals[1]) + row = int(vals[2]) + size = int(value) + + dock = AuiDockInfo() + dock.dock_direction = dir + dock.dock_layer = layer + dock.dock_row = row + dock.size = size + self._docks.append(dock) + + continue + + # Undo our escaping as LoadPaneInfo needs to take an unescaped + # name so it can be called by external callers + pane_part = pane_part.replace("\a", "|") + pane_part = pane_part.replace("\b", ";") + + pane = self.LoadPaneInfo(pane_part, pane) + + p = self.GetPane(pane.name) + + if not p.IsOk(): + if pane.IsNotebookControl(): + # notebook controls - auto add... + self._panes.append(pane) + indx = self._panes.index(pane) + else: + # the pane window couldn't be found + # in the existing layout -- skip it + continue + + else: + indx = self._panes.index(p) + pane.window = p.window + pane.frame = p.frame + pane.buttons = p.buttons + self._panes[indx] = pane + + if isinstance(pane.window, auibar.AuiToolBar) and (pane.IsFloatable() or pane.IsDockable()): + pane.window.SetGripperVisible(True) + + if update: + self.Update() + + return True + + + def GetPanePositionsAndSizes(self, dock): + """ + Returns all the panes positions and sizes in a dock. + + :param `dock`: a L{AuiDockInfo} instance. + """ + + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + pane_border_size = self._art.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + gripper_size = self._art.GetMetric(AUI_DOCKART_GRIPPER_SIZE) + + positions = [] + sizes = [] + + action_pane = -1 + pane_count = len(dock.panes) + + # find the pane marked as our action pane + for pane_i in xrange(pane_count): + pane = dock.panes[pane_i] + if pane.HasFlag(AuiPaneInfo.actionPane): + if action_pane != -1: + raise Exception("Too many action panes!") + action_pane = pane_i + + # set up each panes default position, and + # determine the size (width or height, depending + # on the dock's orientation) of each pane + for pane in dock.panes: + positions.append(pane.dock_pos) + size = 0 + + if pane.HasBorder(): + size += pane_border_size*2 + + if dock.IsHorizontal(): + if pane.HasGripper() and not pane.HasGripperTop(): + size += gripper_size + + if pane.HasCaptionLeft(): + size += caption_size + + size += pane.best_size.x + + else: + if pane.HasGripper() and pane.HasGripperTop(): + size += gripper_size + + if pane.HasCaption() and not pane.HasCaptionLeft(): + size += caption_size + + size += pane.best_size.y + + sizes.append(size) + + # if there is no action pane, just return the default + # positions (as specified in pane.pane_pos) + if action_pane == -1: + return positions, sizes + + offset = 0 + for pane_i in xrange(action_pane-1, -1, -1): + amount = positions[pane_i+1] - (positions[pane_i] + sizes[pane_i]) + if amount >= 0: + offset += amount + else: + positions[pane_i] -= -amount + + offset += sizes[pane_i] + + # if the dock mode is fixed, make sure none of the panes + # overlap we will bump panes that overlap + offset = 0 + for pane_i in xrange(action_pane, pane_count): + amount = positions[pane_i] - offset + if amount >= 0: + offset += amount + else: + positions[pane_i] += -amount + + offset += sizes[pane_i] + + return positions, sizes + + + def LayoutAddPane(self, cont, dock, pane, uiparts, spacer_only): + """ + Adds a pane into the existing layout (in an existing dock). + + :param `cont`: a `wx.Sizer` object; + :param `dock`: the L{AuiDockInfo} structure in which to add the pane; + :param `pane`: the L{AuiPaneInfo} instance to add to the dock; + :param `uiparts`: a list of UI parts in the interface; + :param `spacer_only`: whether to add a simple spacer or a real window. + """ + + sizer_item = wx.SizerItem() + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + gripper_size = self._art.GetMetric(AUI_DOCKART_GRIPPER_SIZE) + pane_border_size = self._art.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + pane_button_size = self._art.GetMetric(AUI_DOCKART_PANE_BUTTON_SIZE) + + # find out the orientation of the item (orientation for panes + # is the same as the dock's orientation) + + if dock.IsHorizontal(): + orientation = wx.HORIZONTAL + else: + orientation = wx.VERTICAL + + # this variable will store the proportion + # value that the pane will receive + pane_proportion = pane.dock_proportion + + horz_pane_sizer = wx.BoxSizer(wx.HORIZONTAL) + vert_pane_sizer = wx.BoxSizer(wx.VERTICAL) + + if pane.HasGripper(): + + part = AuiDockUIPart() + if pane.HasGripperTop(): + sizer_item = vert_pane_sizer.Add((1, gripper_size), 0, wx.EXPAND) + else: + sizer_item = horz_pane_sizer.Add((gripper_size, 1), 0, wx.EXPAND) + + part.type = AuiDockUIPart.typeGripper + part.dock = dock + part.pane = pane + part.button = None + part.orientation = orientation + part.cont_sizer = horz_pane_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + button_count = len(pane.buttons) + button_width_total = button_count*pane_button_size + if button_count >= 1: + button_width_total += 3 + + caption, captionLeft = pane.HasCaption(), pane.HasCaptionLeft() + button_count = len(pane.buttons) + + if captionLeft: + caption_sizer = wx.BoxSizer(wx.VERTICAL) + + # add pane buttons to the caption + dummy_parts = [] + for btn_id in xrange(len(pane.buttons)-1, -1, -1): + sizer_item = caption_sizer.Add((caption_size, pane_button_size), 0, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typePaneButton + part.dock = dock + part.pane = pane + part.button = pane.buttons[btn_id] + part.orientation = orientation + part.cont_sizer = caption_sizer + part.sizer_item = sizer_item + dummy_parts.append(part) + + sizer_item = caption_sizer.Add((caption_size, 1), 1, wx.EXPAND) + vert_pane_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # create the caption sizer + part = AuiDockUIPart() + + part.type = AuiDockUIPart.typeCaption + part.dock = dock + part.pane = pane + part.button = None + part.orientation = orientation + part.cont_sizer = vert_pane_sizer + part.sizer_item = sizer_item + caption_part_idx = len(uiparts) + uiparts.append(part) + uiparts.extend(dummy_parts) + + elif caption: + + caption_sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer_item = caption_sizer.Add((1, caption_size), 1, wx.EXPAND) + + # create the caption sizer + part = AuiDockUIPart() + + part.type = AuiDockUIPart.typeCaption + part.dock = dock + part.pane = pane + part.button = None + part.orientation = orientation + part.cont_sizer = vert_pane_sizer + part.sizer_item = sizer_item + caption_part_idx = len(uiparts) + uiparts.append(part) + + # add pane buttons to the caption + for button in pane.buttons: + sizer_item = caption_sizer.Add((pane_button_size, caption_size), 0, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typePaneButton + part.dock = dock + part.pane = pane + part.button = button + part.orientation = orientation + part.cont_sizer = caption_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + if caption or captionLeft: + # if we have buttons, add a little space to the right + # of them to ease visual crowding + if button_count >= 1: + if captionLeft: + caption_sizer.Add((caption_size, 3), 0, wx.EXPAND) + else: + caption_sizer.Add((3, caption_size), 0, wx.EXPAND) + + # add the caption sizer + sizer_item = vert_pane_sizer.Add(caption_sizer, 0, wx.EXPAND) + uiparts[caption_part_idx].sizer_item = sizer_item + + # add the pane window itself + if spacer_only or not pane.window: + sizer_item = vert_pane_sizer.Add((1, 1), 1, wx.EXPAND) + else: + sizer_item = vert_pane_sizer.Add(pane.window, 1, wx.EXPAND) + vert_pane_sizer.SetItemMinSize(pane.window, (1, 1)) + + part = AuiDockUIPart() + part.type = AuiDockUIPart.typePane + part.dock = dock + part.pane = pane + part.button = None + part.orientation = orientation + part.cont_sizer = vert_pane_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + # determine if the pane should have a minimum size if the pane is + # non-resizable (fixed) then we must set a minimum size. Alternatively, + # if the pane.min_size is set, we must use that value as well + + min_size = pane.min_size + if pane.IsFixed(): + if min_size == wx.Size(-1, -1): + min_size = pane.best_size + pane_proportion = 0 + + if min_size != wx.Size(-1, -1): + vert_pane_sizer.SetItemMinSize(len(vert_pane_sizer.GetChildren())-1, (min_size.x, min_size.y)) + + # add the vertical/horizontal sizer (caption, pane window) to the + # horizontal sizer (gripper, vertical sizer) + horz_pane_sizer.Add(vert_pane_sizer, 1, wx.EXPAND) + + # finally, add the pane sizer to the dock sizer + if pane.HasBorder(): + # allowing space for the pane's border + sizer_item = cont.Add(horz_pane_sizer, pane_proportion, + wx.EXPAND | wx.ALL, pane_border_size) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typePaneBorder + part.dock = dock + part.pane = pane + part.button = None + part.orientation = orientation + part.cont_sizer = cont + part.sizer_item = sizer_item + uiparts.append(part) + else: + sizer_item = cont.Add(horz_pane_sizer, pane_proportion, wx.EXPAND) + + return uiparts + + + def LayoutAddDock(self, cont, dock, uiparts, spacer_only): + """ + Adds a dock into the existing layout. + + :param `cont`: a `wx.Sizer` object; + :param `dock`: the L{AuiDockInfo} structure to add to the layout; + :param `uiparts`: a list of UI parts in the interface; + :param `spacer_only`: whether to add a simple spacer or a real window. + """ + + sizer_item = wx.SizerItem() + part = AuiDockUIPart() + + sash_size = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + orientation = (dock.IsHorizontal() and [wx.HORIZONTAL] or [wx.VERTICAL])[0] + + # resizable bottom and right docks have a sash before them + if not self._has_maximized and not dock.fixed and \ + dock.dock_direction in [AUI_DOCK_BOTTOM, AUI_DOCK_RIGHT]: + + sizer_item = cont.Add((sash_size, sash_size), 0, wx.EXPAND) + + part.type = AuiDockUIPart.typeDockSizer + part.orientation = orientation + part.dock = dock + part.pane = None + part.button = None + part.cont_sizer = cont + part.sizer_item = sizer_item + uiparts.append(part) + + # create the sizer for the dock + dock_sizer = wx.BoxSizer(orientation) + + # add each pane to the dock + has_maximized_pane = False + pane_count = len(dock.panes) + + if dock.fixed: + + # figure out the real pane positions we will + # use, without modifying the each pane's pane_pos member + pane_positions, pane_sizes = self.GetPanePositionsAndSizes(dock) + + offset = 0 + for pane_i in xrange(pane_count): + + pane = dock.panes[pane_i] + pane_pos = pane_positions[pane_i] + + if pane.IsMaximized(): + has_maximized_pane = True + + amount = pane_pos - offset + if amount > 0: + + if dock.IsVertical(): + sizer_item = dock_sizer.Add((1, amount), 0, wx.EXPAND) + else: + sizer_item = dock_sizer.Add((amount, 1), 0, wx.EXPAND) + + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeBackground + part.dock = dock + part.pane = None + part.button = None + part.orientation = (orientation==wx.HORIZONTAL and \ + [wx.VERTICAL] or [wx.HORIZONTAL])[0] + part.cont_sizer = dock_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + offset = offset + amount + + uiparts = self.LayoutAddPane(dock_sizer, dock, pane, uiparts, spacer_only) + + offset = offset + pane_sizes[pane_i] + + # at the end add a very small stretchable background area + sizer_item = dock_sizer.Add((0, 0), 1, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeBackground + part.dock = dock + part.pane = None + part.button = None + part.orientation = orientation + part.cont_sizer = dock_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + else: + + for pane_i in xrange(pane_count): + + pane = dock.panes[pane_i] + + if pane.IsMaximized(): + has_maximized_pane = True + + # if this is not the first pane being added, + # we need to add a pane sizer + if not self._has_maximized and pane_i > 0: + sizer_item = dock_sizer.Add((sash_size, sash_size), 0, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typePaneSizer + part.dock = dock + part.pane = dock.panes[pane_i-1] + part.button = None + part.orientation = (orientation==wx.HORIZONTAL and \ + [wx.VERTICAL] or [wx.HORIZONTAL])[0] + part.cont_sizer = dock_sizer + part.sizer_item = sizer_item + uiparts.append(part) + + uiparts = self.LayoutAddPane(dock_sizer, dock, pane, uiparts, spacer_only) + + if dock.dock_direction == AUI_DOCK_CENTER or has_maximized_pane: + sizer_item = cont.Add(dock_sizer, 1, wx.EXPAND) + else: + sizer_item = cont.Add(dock_sizer, 0, wx.EXPAND) + + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeDock + part.dock = dock + part.pane = None + part.button = None + part.orientation = orientation + part.cont_sizer = cont + part.sizer_item = sizer_item + uiparts.append(part) + + if dock.IsHorizontal(): + cont.SetItemMinSize(dock_sizer, (0, dock.size)) + else: + cont.SetItemMinSize(dock_sizer, (dock.size, 0)) + + # top and left docks have a sash after them + if not self._has_maximized and not dock.fixed and \ + dock.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_LEFT]: + + sizer_item = cont.Add((sash_size, sash_size), 0, wx.EXPAND) + + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeDockSizer + part.dock = dock + part.pane = None + part.button = None + part.orientation = orientation + part.cont_sizer = cont + part.sizer_item = sizer_item + uiparts.append(part) + + return uiparts + + + def LayoutAll(self, panes, docks, uiparts, spacer_only=False, oncheck=True): + """ + Layouts all the UI structures in the interface. + + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `docks`: a list of L{AuiDockInfo} classes; + :param `uiparts`: a list of UI parts in the interface; + :param `spacer_only`: whether to add a simple spacer or a real window; + :param `oncheck`: whether to store the results in a class member or not. + """ + + container = wx.BoxSizer(wx.VERTICAL) + + pane_border_size = self._art.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + cli_size = self._frame.GetClientSize() + + # empty all docks out + for dock in docks: + dock.panes = [] + if dock.fixed: + # always reset fixed docks' sizes, because + # the contained windows may have been resized + dock.size = 0 + + dock_count = len(docks) + + # iterate through all known panes, filing each + # of them into the appropriate dock. If the + # pane does not exist in the dock, add it + for p in panes: + + # don't layout hidden panes. + if p.IsShown(): + + # find any docks with the same dock direction, dock layer, and + # dock row as the pane we are working on + arr = FindDocks(docks, p.dock_direction, p.dock_layer, p.dock_row) + + if arr: + dock = arr[0] + + else: + # dock was not found, so we need to create a new one + d = AuiDockInfo() + d.dock_direction = p.dock_direction + d.dock_layer = p.dock_layer + d.dock_row = p.dock_row + docks.append(d) + dock = docks[-1] + + if p.HasFlag(p.needsRestore) and not p.HasFlag(p.wasMaximized): + + isHor = dock.IsHorizontal() + sashSize = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + + # get the sizes of any docks that might + # overlap with our restored dock + + # make list of widths or heights from the size in the dock rects + sizes = [d.rect[2:][isHor] for \ + d in docks if d.IsOk() and \ + (d.IsHorizontal() == isHor) and \ + not d.toolbar and \ + d.dock_direction != AUI_DOCK_CENTER] + + frameRect = GetInternalFrameRect(self._frame, self._docks) + + # set max size allowing for sashes and absolute minimum + maxsize = frameRect[2:][isHor] - sum(sizes) - (len(sizes)*10) - (sashSize*len(sizes)) + dock.size = min(p.previousDockSize,maxsize) + + else: + dock.size = 0 + + if p.HasFlag(p.wasMaximized): + self.MaximizePane(p, savesizes=False) + p.SetFlag(p.wasMaximized, False) + + if p.HasFlag(p.needsRestore): + if p.previousDockPos is not None: + DoInsertPane(dock.panes, dock.dock_direction, dock.dock_layer, dock.dock_row, p.previousDockPos) + p.dock_pos = p.previousDockPos + p.previousDockPos = None + p.SetFlag(p.needsRestore, False) + + if p.IsDocked(): + # remove the pane from any existing docks except this one + docks = RemovePaneFromDocks(docks, p, dock) + + # pane needs to be added to the dock, + # if it doesn't already exist + if not FindPaneInDock(dock, p.window): + dock.panes.append(p) + else: + # remove the pane from any existing docks + docks = RemovePaneFromDocks(docks, p) + + # remove any empty docks + docks = [dock for dock in docks if dock.panes] + + dock_count = len(docks) + # configure the docks further + for ii, dock in enumerate(docks): + # sort the dock pane array by the pane's + # dock position (dock_pos), in ascending order + dock.panes.sort(PaneSortFunc) + dock_pane_count = len(dock.panes) + + # for newly created docks, set up their initial size + if dock.size == 0: + size = 0 + for pane in dock.panes: + pane_size = pane.best_size + if pane_size == wx.Size(-1, -1): + pane_size = pane.min_size + if pane_size == wx.Size(-1, -1) and pane.window: + pane_size = pane.window.GetSize() + if dock.IsHorizontal(): + size = max(pane_size.y, size) + else: + size = max(pane_size.x, size) + + # add space for the border (two times), but only + # if at least one pane inside the dock has a pane border + for pane in dock.panes: + if pane.HasBorder(): + size = size + pane_border_size*2 + break + + # if pane is on the top or bottom, add the caption height, + # but only if at least one pane inside the dock has a caption + if dock.IsHorizontal(): + for pane in dock.panes: + if pane.HasCaption() and not pane.HasCaptionLeft(): + size = size + caption_size + break + else: + for pane in dock.panes: + if pane.HasCaptionLeft() and not pane.HasCaption(): + size = size + caption_size + break + + # new dock's size may not be more than the dock constraint + # parameter specifies. See SetDockSizeConstraint() + max_dock_x_size = int(self._dock_constraint_x*float(cli_size.x)) + max_dock_y_size = int(self._dock_constraint_y*float(cli_size.y)) + if cli_size <= wx.Size(20, 20): + max_dock_x_size = 10000 + max_dock_y_size = 10000 + + if dock.IsHorizontal(): + size = min(size, max_dock_y_size) + else: + size = min(size, max_dock_x_size) + + # absolute minimum size for a dock is 10 pixels + if size < 10: + size = 10 + + dock.size = size + + # determine the dock's minimum size + plus_border = False + plus_caption = False + plus_caption_left = False + dock_min_size = 0 + for pane in dock.panes: + if pane.min_size != wx.Size(-1, -1): + if pane.HasBorder(): + plus_border = True + if pane.HasCaption(): + plus_caption = True + if pane.HasCaptionLeft(): + plus_caption_left = True + if dock.IsHorizontal(): + if pane.min_size.y > dock_min_size: + dock_min_size = pane.min_size.y + else: + if pane.min_size.x > dock_min_size: + dock_min_size = pane.min_size.x + + if plus_border: + dock_min_size += pane_border_size*2 + if plus_caption and dock.IsHorizontal(): + dock_min_size += caption_size + if plus_caption_left and dock.IsVertical(): + dock_min_size += caption_size + + dock.min_size = dock_min_size + + # if the pane's current size is less than it's + # minimum, increase the dock's size to it's minimum + if dock.size < dock.min_size: + dock.size = dock.min_size + + # determine the dock's mode (fixed or proportional) + # determine whether the dock has only toolbars + action_pane_marked = False + dock.fixed = True + dock.toolbar = True + for pane in dock.panes: + if not pane.IsFixed(): + dock.fixed = False + if not pane.IsToolbar(): + dock.toolbar = False + if pane.HasFlag(AuiPaneInfo.optionDockFixed): + dock.fixed = True + if pane.HasFlag(AuiPaneInfo.actionPane): + action_pane_marked = True + + # if the dock mode is proportional and not fixed-pixel, + # reassign the dock_pos to the sequential 0, 1, 2, 3 + # e.g. remove gaps like 1, 2, 30, 500 + if not dock.fixed: + for jj in xrange(dock_pane_count): + pane = dock.panes[jj] + pane.dock_pos = jj + + # if the dock mode is fixed, and none of the panes + # are being moved right now, make sure the panes + # do not overlap each other. If they do, we will + # adjust the panes' positions + if dock.fixed and not action_pane_marked: + pane_positions, pane_sizes = self.GetPanePositionsAndSizes(dock) + offset = 0 + for jj in xrange(dock_pane_count): + pane = dock.panes[jj] + pane.dock_pos = pane_positions[jj] + amount = pane.dock_pos - offset + if amount >= 0: + offset += amount + else: + pane.dock_pos += -amount + + offset += pane_sizes[jj] + dock.panes[jj] = pane + + if oncheck: + self._docks[ii] = dock + + # shrink docks if needed +## docks = self.SmartShrink(docks, AUI_DOCK_TOP) +## docks = self.SmartShrink(docks, AUI_DOCK_LEFT) + + if oncheck: + self._docks = docks + + # discover the maximum dock layer + max_layer = 0 + dock_count = len(docks) + + for ii in xrange(dock_count): + max_layer = max(max_layer, docks[ii].dock_layer) + + # clear out uiparts + uiparts = [] + + # create a bunch of box sizers, + # from the innermost level outwards. + cont = None + middle = None + + if oncheck: + docks = self._docks + + for layer in xrange(max_layer+1): + # find any docks in this layer + arr = FindDocks(docks, -1, layer, -1) + # if there aren't any, skip to the next layer + if not arr: + continue + + old_cont = cont + + # create a container which will hold this layer's + # docks (top, bottom, left, right) + cont = wx.BoxSizer(wx.VERTICAL) + + # find any top docks in this layer + arr = FindDocks(docks, AUI_DOCK_TOP, layer, -1) + for row in arr: + uiparts = self.LayoutAddDock(cont, row, uiparts, spacer_only) + + # fill out the middle layer (which consists + # of left docks, content area and right docks) + + middle = wx.BoxSizer(wx.HORIZONTAL) + + # find any left docks in this layer + arr = FindDocks(docks, AUI_DOCK_LEFT, layer, -1) + for row in arr: + uiparts = self.LayoutAddDock(middle, row, uiparts, spacer_only) + + # add content dock (or previous layer's sizer + # to the middle + if not old_cont: + # find any center docks + arr = FindDocks(docks, AUI_DOCK_CENTER, -1, -1) + if arr: + for row in arr: + uiparts = self.LayoutAddDock(middle, row, uiparts, spacer_only) + + elif not self._has_maximized: + # there are no center docks, add a background area + sizer_item = middle.Add((1, 1), 1, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeBackground + part.pane = None + part.dock = None + part.button = None + part.cont_sizer = middle + part.sizer_item = sizer_item + uiparts.append(part) + else: + middle.Add(old_cont, 1, wx.EXPAND) + + # find any right docks in this layer + arr = FindDocks(docks, AUI_DOCK_RIGHT, layer, -1, reverse=True) + for row in arr: + uiparts = self.LayoutAddDock(middle, row, uiparts, spacer_only) + + if len(middle.GetChildren()) > 0: + cont.Add(middle, 1, wx.EXPAND) + + # find any bottom docks in this layer + arr = FindDocks(docks, AUI_DOCK_BOTTOM, layer, -1, reverse=True) + for row in arr: + uiparts = self.LayoutAddDock(cont, row, uiparts, spacer_only) + + if not cont: + # no sizer available, because there are no docks, + # therefore we will create a simple background area + cont = wx.BoxSizer(wx.VERTICAL) + sizer_item = cont.Add((1, 1), 1, wx.EXPAND) + part = AuiDockUIPart() + part.type = AuiDockUIPart.typeBackground + part.pane = None + part.dock = None + part.button = None + part.cont_sizer = middle + part.sizer_item = sizer_item + uiparts.append(part) + + if oncheck: + self._uiparts = uiparts + self._docks = docks + + container.Add(cont, 1, wx.EXPAND) + + if oncheck: + return container + else: + return container, panes, docks, uiparts + + + def SetDockSizeConstraint(self, width_pct, height_pct): + """ + When a user creates a new dock by dragging a window into a docked position, + often times the large size of the window will create a dock that is unwieldly + large. + + L{AuiManager} by default limits the size of any new dock to 1/3 of the window + size. For horizontal docks, this would be 1/3 of the window height. For vertical + docks, 1/3 of the width. Calling this function will adjust this constraint value. + + The numbers must be between 0.0 and 1.0. For instance, calling L{SetDockSizeConstraint} + with (0.5, 0.5) will cause new docks to be limited to half of the size of the entire + managed window. + + :param `width_pct`: a float number representing the x dock size constraint; + :param `width_pct`: a float number representing the y dock size constraint. + """ + + self._dock_constraint_x = max(0.0, min(1.0, width_pct)) + self._dock_constraint_y = max(0.0, min(1.0, height_pct)) + + + def GetDockSizeConstraint(self): + """ + Returns the current dock constraint values. + + :see: L{SetDockSizeConstraint} + """ + + return self._dock_constraint_x, self._dock_constraint_y + + + def Update(self): + """ + This method is called after any number of changes are made to any of the + managed panes. L{Update} must be invoked after L{AddPane} or L{InsertPane} are + called in order to "realize" or "commit" the changes. + + In addition, any number of changes may be made to L{AuiPaneInfo} structures + (retrieved with L{GetPane}), but to realize the changes, L{Update} + must be called. This construction allows pane flicker to be avoided by updating + the whole layout at one time. + """ + + self._hover_button = None + self._action_part = None + + # destroy floating panes which have been + # redocked or are becoming non-floating + for p in self._panes: + if p.IsFloating() or not p.frame: + continue + + # because the pane is no longer in a floating, we need to + # reparent it to self._frame and destroy the floating frame + # reduce flicker + p.window.SetSize((1, 1)) + + # the following block is a workaround for bug #1531361 + # (see wxWidgets sourceforge page). On wxGTK (only), when + # a frame is shown/hidden, a move event unfortunately + # also gets fired. Because we may be dragging around + # a pane, we need to cancel that action here to prevent + # a spurious crash. + if self._action_window == p.frame: + if self._frame.HasCapture(): + self._frame.ReleaseMouse() + self._action = actionNone + self._action_window = None + + # hide the frame + if p.frame.IsShown(): + p.frame.Show(False) + + if self._action_window == p.frame: + self._action_window = None + + # reparent to self._frame and destroy the pane + p.window.Reparent(self._frame) + if isinstance(p.window, auibar.AuiToolBar): + p.window.SetAuiManager(self) + + if p.frame: + p.frame.SetSizer(None) + p.frame.Destroy() + p.frame = None + + # Only the master manager should create/destroy notebooks... + if not self._masterManager: + self.UpdateNotebook() + + # delete old sizer first + self._frame.SetSizer(None) + + # create a layout for all of the panes + sizer = self.LayoutAll(self._panes, self._docks, self._uiparts, False) + + # hide or show panes as necessary, + # and float panes as necessary + + pane_count = len(self._panes) + + for ii in xrange(pane_count): + p = self._panes[ii] + pFrame = p.frame + + if p.IsFloating(): + if pFrame is None: + # we need to create a frame for this + # pane, which has recently been floated + frame = self.CreateFloatingFrame(self._frame, p) + + # on MSW and Mac, if the owner desires transparent dragging, and + # the dragging is happening right now, then the floating + # window should have this style by default + if self._action in [actionDragFloatingPane, actionDragToolbarPane] and \ + self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + frame.SetTransparent(150) + + if p.IsToolbar(): + bar = p.window + if isinstance(bar, auibar.AuiToolBar): + bar.SetGripperVisible(False) + agwStyle = bar.GetAGWWindowStyleFlag() + bar.SetAGWWindowStyleFlag(agwStyle & ~AUI_TB_VERTICAL) + bar.Realize() + + s = p.window.GetMinSize() + p.BestSize(s) + p.FloatingSize(wx.DefaultSize) + + frame.SetPaneWindow(p) + p.needsTransparency = True + p.frame = pFrame = frame + if p.IsShown() and not frame.IsShown(): + frame.Show() + frame.Update() + else: + + # frame already exists, make sure it's position + # and size reflect the information in AuiPaneInfo + if pFrame.GetPosition() != p.floating_pos or pFrame.GetSize() != p.floating_size: + pFrame.SetDimensions(p.floating_pos.x, p.floating_pos.y, + p.floating_size.x, p.floating_size.y, wx.SIZE_USE_EXISTING) + + # update whether the pane is resizable or not + style = p.frame.GetWindowStyleFlag() + if p.IsFixed(): + style &= ~wx.RESIZE_BORDER + else: + style |= wx.RESIZE_BORDER + + p.frame.SetWindowStyleFlag(style) + + if pFrame.IsShown() != p.IsShown(): + p.needsTransparency = True + pFrame.Show(p.IsShown()) + + if pFrame.GetTitle() != p.caption: + pFrame.SetTitle(p.caption) + if p.icon.IsOk(): + pFrame.SetIcon(wx.IconFromBitmap(p.icon)) + + else: + + if p.IsToolbar(): +# self.SwitchToolBarOrientation(p) + p.best_size = p.window.GetBestSize() + + if p.window and not p.IsNotebookPage() and p.window.IsShown() != p.IsShown(): + p.window.Show(p.IsShown()) + + if pFrame and p.needsTransparency: + if pFrame.IsShown() and pFrame._transparent != p.transparent: + pFrame.SetTransparent(p.transparent) + pFrame._transparent = p.transparent + + p.needsTransparency = False + + # if "active panes" are no longer allowed, clear + # any optionActive values from the pane states + if self._agwFlags & AUI_MGR_ALLOW_ACTIVE_PANE == 0: + p.state &= ~AuiPaneInfo.optionActive + + self._panes[ii] = p + + old_pane_rects = [] + pane_count = len(self._panes) + + for p in self._panes: + r = wx.Rect() + if p.window and p.IsShown() and p.IsDocked(): + r = p.rect + + old_pane_rects.append(r) + + # apply the new sizer + self._frame.SetSizer(sizer) + self._frame.SetAutoLayout(False) + self.DoFrameLayout() + + # now that the frame layout is done, we need to check + # the new pane rectangles against the old rectangles that + # we saved a few lines above here. If the rectangles have + # changed, the corresponding panes must also be updated + for ii in xrange(pane_count): + p = self._panes[ii] + if p.window and p.IsShown() and p.IsDocked(): + if p.rect != old_pane_rects[ii]: + p.window.Refresh() + p.window.Update() + + if wx.Platform == "__WXMAC__": + self._frame.Refresh() + else: + self.Repaint() + + if not self._masterManager: + e = self.FireEvent(wxEVT_AUI_PERSPECTIVE_CHANGED, None, canVeto=False) + + + def UpdateNotebook(self): + """ Updates the automatic L{AuiNotebook} in the layout (if any exists). """ + + # Workout how many notebooks we need. + max_notebook = -1 + + # destroy floating panes which have been + # redocked or are becoming non-floating + for paneInfo in self._panes: + if max_notebook < paneInfo.notebook_id: + max_notebook = paneInfo.notebook_id + + # We are the master of our domain + extra_notebook = len(self._notebooks) + max_notebook += 1 + + for i in xrange(extra_notebook, max_notebook): + self.CreateNotebook() + + # Remove pages from notebooks that no-longer belong there ... + for nb, notebook in enumerate(self._notebooks): + pages = notebook.GetPageCount() + pageCounter, allPages = 0, pages + + # Check each tab ... + for page in xrange(pages): + + if page >= allPages: + break + + window = notebook.GetPage(pageCounter) + paneInfo = self.GetPane(window) + if paneInfo.IsOk() and paneInfo.notebook_id != nb: + notebook.RemovePage(pageCounter) + window.Hide() + window.Reparent(self._frame) + pageCounter -= 1 + allPages -= 1 + + pageCounter += 1 + + notebook.DoSizing() + + # Add notebook pages that aren't there already... + for paneInfo in self._panes: + if paneInfo.IsNotebookPage(): + + title = (paneInfo.caption == "" and [paneInfo.name] or [paneInfo.caption])[0] + + notebook = self._notebooks[paneInfo.notebook_id] + page_id = notebook.GetPageIndex(paneInfo.window) + + if page_id < 0: + + paneInfo.window.Reparent(notebook) + notebook.AddPage(paneInfo.window, title, True, paneInfo.icon) + + # Update title and icon ... + else: + + notebook.SetPageText(page_id, title) + notebook.SetPageBitmap(page_id, paneInfo.icon) + + notebook.DoSizing() + + # Wire-up newly created notebooks + elif paneInfo.IsNotebookControl() and not paneInfo.window: + paneInfo.window = self._notebooks[paneInfo.notebook_id] + + # Delete empty notebooks, and convert notebooks with 1 page to + # normal panes... + remap_ids = [-1]*len(self._notebooks) + nb_idx = 0 + + for nb, notebook in enumerate(self._notebooks): + if notebook.GetPageCount() == 1: + + # Convert notebook page to pane... + window = notebook.GetPage(0) + child_pane = self.GetPane(window) + notebook_pane = self.GetPane(notebook) + if child_pane.IsOk() and notebook_pane.IsOk(): + + child_pane.SetDockPos(notebook_pane) + child_pane.window.Hide() + child_pane.window.Reparent(self._frame) + child_pane.frame = None + child_pane.notebook_id = -1 + if notebook_pane.IsFloating(): + child_pane.Float() + + self.DetachPane(notebook) + + notebook.RemovePage(0) + notebook.Destroy() + + else: + + raise Exception("Odd notebook docking") + + elif notebook.GetPageCount() == 0: + + self.DetachPane(notebook) + notebook.Destroy() + + else: + + # Correct page ordering. The original wxPython code + # for this did not work properly, and would misplace + # windows causing errors. + notebook.Freeze() + self._notebooks[nb_idx] = notebook + pages = notebook.GetPageCount() + selected = notebook.GetPage(notebook.GetSelection()) + + # Take each page out of the notebook, group it with + # its current pane, and sort the list by pane.dock_pos + # order + pages_and_panes = [] + for idx in reversed(range(pages)): + page = notebook.GetPage(idx) + pane = self.GetPane(page) + pages_and_panes.append((page, pane)) + notebook.RemovePage(idx) + sorted_pnp = sorted(pages_and_panes, key=lambda tup: tup[1].dock_pos) + + # Grab the attributes from the panes which are ordered + # correctly, and copy those attributes to the original + # panes. (This avoids having to change the ordering + # of self._panes) Then, add the page back into the notebook + sorted_attributes = [self.GetAttributes(tup[1]) + for tup in sorted_pnp] + for attrs, tup in zip(sorted_attributes, pages_and_panes): + pane = tup[1] + self.SetAttributes(pane, attrs) + notebook.AddPage(pane.window, pane.caption) + + notebook.SetSelection(notebook.GetPageIndex(selected), True) + notebook.DoSizing() + notebook.Thaw() + + # It's a keeper. + remap_ids[nb] = nb_idx + nb_idx += 1 + + # Apply remap... + nb_count = len(self._notebooks) + + if nb_count != nb_idx: + + self._notebooks = self._notebooks[0:nb_idx] + for p in self._panes: + if p.notebook_id >= 0: + p.notebook_id = remap_ids[p.notebook_id] + if p.IsNotebookControl(): + p.SetNameFromNotebookId() + + # Make sure buttons are correct ... + for notebook in self._notebooks: + want_max = True + want_min = True + want_close = True + + pages = notebook.GetPageCount() + for page in xrange(pages): + + win = notebook.GetPage(page) + pane = self.GetPane(win) + if pane.IsOk(): + + if not pane.HasCloseButton(): + want_close = False + if not pane.HasMaximizeButton(): + want_max = False + if not pane.HasMinimizeButton(): + want_min = False + + notebook_pane = self.GetPane(notebook) + if notebook_pane.IsOk(): + if notebook_pane.HasMinimizeButton() != want_min: + if want_min: + button = AuiPaneButton(AUI_BUTTON_MINIMIZE) + notebook_pane.state |= AuiPaneInfo.buttonMinimize + notebook_pane.buttons.append(button) + + # todo: remove min/max + + if notebook_pane.HasMaximizeButton() != want_max: + if want_max: + button = AuiPaneButton(AUI_BUTTON_MAXIMIZE_RESTORE) + notebook_pane.state |= AuiPaneInfo.buttonMaximize + notebook_pane.buttons.append(button) + + # todo: remove min/max + + if notebook_pane.HasCloseButton() != want_close: + if want_close: + button = AuiPaneButton(AUI_BUTTON_CLOSE) + notebook_pane.state |= AuiPaneInfo.buttonClose + notebook_pane.buttons.append(button) + + # todo: remove close + + + def SmartShrink(self, docks, direction): + """ + Used to intelligently shrink the docks' size (if needed). + + :param `docks`: a list of L{AuiDockInfo} instances; + :param `direction`: the direction in which to shrink. + """ + + sashSize = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + clientSize = self._frame.GetClientSize() + ourDocks = FindDocks(docks, direction, -1, -1) + oppositeDocks = FindOppositeDocks(docks, direction) + oppositeSize = self.GetOppositeDockTotalSize(docks, direction) + ourSize = 0 + + for dock in ourDocks: + ourSize += dock.size + + if not dock.toolbar: + ourSize += sashSize + + shrinkSize = ourSize + oppositeSize + + if direction == AUI_DOCK_TOP or direction == AUI_DOCK_BOTTOM: + shrinkSize -= clientSize.y + else: + shrinkSize -= clientSize.x + + if shrinkSize <= 0: + return docks + + # Combine arrays + for dock in oppositeDocks: + ourDocks.append(dock) + + oppositeDocks = [] + + for dock in ourDocks: + if dock.toolbar or not dock.resizable: + continue + + dockRange = dock.size - dock.min_size + + if dock.min_size == 0: + dockRange -= sashSize + if direction == AUI_DOCK_TOP or direction == AUI_DOCK_BOTTOM: + dockRange -= caption_size + + if dockRange >= shrinkSize: + + dock.size -= shrinkSize + return docks + + else: + + dock.size -= dockRange + shrinkSize -= dockRange + + return docks + + + def UpdateDockingGuides(self, paneInfo): + """ + Updates the docking guide windows positions and appearance. + + :param `paneInfo`: a L{AuiPaneInfo} instance. + """ + + if len(self._guides) == 0: + self.CreateGuideWindows() + + captionSize = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + frameRect = GetInternalFrameRect(self._frame, self._docks) + mousePos = wx.GetMousePosition() + + for indx, guide in enumerate(self._guides): + + pt = wx.Point() + guide_size = guide.host.GetSize() + if not guide.host: + raise Exception("Invalid docking host") + + direction = guide.dock_direction + + if direction == AUI_DOCK_LEFT: + pt.x = frameRect.x + guide_size.x / 2 + 16 + pt.y = frameRect.y + frameRect.height / 2 + + elif direction == AUI_DOCK_TOP: + pt.x = frameRect.x + frameRect.width / 2 + pt.y = frameRect.y + guide_size.y / 2 + 16 + + elif direction == AUI_DOCK_RIGHT: + pt.x = frameRect.x + frameRect.width - guide_size.x / 2 - 16 + pt.y = frameRect.y + frameRect.height / 2 + + elif direction == AUI_DOCK_BOTTOM: + pt.x = frameRect.x + frameRect.width / 2 + pt.y = frameRect.y + frameRect.height - guide_size.y / 2 - 16 + + elif direction == AUI_DOCK_CENTER: + rc = paneInfo.window.GetScreenRect() + pt.x = rc.x + rc.width / 2 + pt.y = rc.y + rc.height / 2 + if paneInfo.HasCaption(): + pt.y -= captionSize / 2 + elif paneInfo.HasCaptionLeft(): + pt.x -= captionSize / 2 + + # guide will be centered around point 'pt' + targetPosition = wx.Point(pt.x - guide_size.x / 2, pt.y - guide_size.y / 2) + + if guide.host.GetPosition() != targetPosition: + guide.host.Move(targetPosition) + + guide.host.AeroMove(targetPosition) + + if guide.dock_direction == AUI_DOCK_CENTER: + guide.host.ValidateNotebookDocking(paneInfo.IsNotebookDockable()) + + guide.host.UpdateDockGuide(mousePos) + + paneInfo.window.Lower() + + + def DoFrameLayout(self): + """ + This is an internal function which invokes `wx.Sizer.Layout` + on the frame's main sizer, then measures all the various UI items + and updates their internal rectangles. + + :note: This should always be called instead of calling + `self._managed_window.Layout()` directly. + """ + + self._frame.Layout() + + for part in self._uiparts: + # get the rectangle of the UI part + # originally, this code looked like this: + # part.rect = wx.Rect(part.sizer_item.GetPosition(), + # part.sizer_item.GetSize()) + # this worked quite well, with one exception: the mdi + # client window had a "deferred" size variable + # that returned the wrong size. It looks like + # a bug in wx, because the former size of the window + # was being returned. So, we will retrieve the part's + # rectangle via other means + + part.rect = part.sizer_item.GetRect() + flag = part.sizer_item.GetFlag() + border = part.sizer_item.GetBorder() + + if flag & wx.TOP: + part.rect.y -= border + part.rect.height += border + if flag & wx.LEFT: + part.rect.x -= border + part.rect.width += border + if flag & wx.BOTTOM: + part.rect.height += border + if flag & wx.RIGHT: + part.rect.width += border + + if part.type == AuiDockUIPart.typeDock: + part.dock.rect = part.rect + if part.type == AuiDockUIPart.typePane: + part.pane.rect = part.rect + + + def GetPanePart(self, wnd): + """ + Looks up the pane border UI part of the + pane specified. This allows the caller to get the exact rectangle + of the pane in question, including decorations like caption and border. + + :param `wnd`: the window to which the pane border belongs to. + """ + + for part in self._uiparts: + if part.type == AuiDockUIPart.typePaneBorder and \ + part.pane and part.pane.window == wnd: + return part + + for part in self._uiparts: + if part.type == AuiDockUIPart.typePane and \ + part.pane and part.pane.window == wnd: + return part + + return None + + + def GetDockPixelOffset(self, test): + """ + This is an internal function which returns + a dock's offset in pixels from the left side of the window + (for horizontal docks) or from the top of the window (for + vertical docks). + + This value is necessary for calculating fixed-pane/toolbar offsets + when they are dragged. + + :param `test`: a fake L{AuiPaneInfo} for testing purposes. + """ + + # the only way to accurately calculate the dock's + # offset is to actually run a theoretical layout + docks, panes = CopyDocksAndPanes2(self._docks, self._panes) + panes.append(test) + + sizer, panes, docks, uiparts = self.LayoutAll(panes, docks, [], True, False) + client_size = self._frame.GetClientSize() + sizer.SetDimension(0, 0, client_size.x, client_size.y) + sizer.Layout() + + for part in uiparts: + pos = part.sizer_item.GetPosition() + size = part.sizer_item.GetSize() + part.rect = wx.RectPS(pos, size) + if part.type == AuiDockUIPart.typeDock: + part.dock.rect = part.rect + + sizer.Destroy() + + for dock in docks: + if test.dock_direction == dock.dock_direction and \ + test.dock_layer == dock.dock_layer and \ + test.dock_row == dock.dock_row: + + if dock.IsVertical(): + return dock.rect.y + else: + return dock.rect.x + + return 0 + + + def GetPartnerDock(self, dock): + """ + Returns the partner dock for the input dock. + + :param `dock`: a L{AuiDockInfo} instance. + """ + + for layer in xrange(dock.dock_layer, -1, -1): + + bestDock = None + + for tmpDock in self._docks: + + if tmpDock.dock_layer != layer: + continue + + if tmpDock.dock_direction != dock.dock_direction: + continue + + if tmpDock.dock_layer < dock.dock_layer: + + if not bestDock or tmpDock.dock_row < bestDock.dock_row: + bestDock = tmpDock + + elif tmpDock.dock_row > dock.dock_row: + + if not bestDock or tmpDock.dock_row > bestDock.dock_row: + bestDock = tmpDock + + if bestDock: + return bestDock + + return None + + + def GetPartnerPane(self, dock, pane): + """ + Returns the partner pane for the input pane. They both need to live + in the same L{AuiDockInfo}. + + :param `dock`: a L{AuiDockInfo} instance; + :param `pane`: a L{AuiPaneInfo} class. + """ + + panePosition = -1 + + for i, tmpPane in enumerate(dock.panes): + if tmpPane.window == pane.window: + panePosition = i + elif not tmpPane.IsFixed() and panePosition != -1: + return tmpPane + + return None + + + def GetTotalPixSizeAndProportion(self, dock): + """ + Returns the dimensions and proportion of the input dock. + + :param `dock`: the L{AuiDockInfo} structure to analyze. + """ + + totalPixsize = 0 + totalProportion = 0 + + # determine the total proportion of all resizable panes, + # and the total size of the dock minus the size of all + # the fixed panes + for tmpPane in dock.panes: + + if tmpPane.IsFixed(): + continue + + totalProportion += tmpPane.dock_proportion + + if dock.IsHorizontal(): + totalPixsize += tmpPane.rect.width + else: + totalPixsize += tmpPane.rect.height + +## if tmpPane.min_size.IsFullySpecified(): +## +## if dock.IsHorizontal(): +## totalPixsize -= tmpPane.min_size.x +## else: +## totalPixsize -= tmpPane.min_size.y + + return totalPixsize, totalProportion + + + def GetOppositeDockTotalSize(self, docks, direction): + """ + Returns the dimensions of the dock which lives opposite of the input dock. + + :param `docks`: a list of L{AuiDockInfo} structures to analyze; + :param `direction`: the direction in which to look for the opposite dock. + """ + + sash_size = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + pane_border_size = self._art.GetMetric(AUI_DOCKART_PANE_BORDER_SIZE) + minSizeMax = 0 + result = sash_size + vertical = False + + if direction in [AUI_DOCK_TOP, AUI_DOCK_BOTTOM]: + vertical = True + + # Get minimum size of the most inner area + for tmpDock in docks: + + if tmpDock.dock_layer != 0: + continue + + if tmpDock.dock_direction != AUI_DOCK_CENTER and tmpDock.IsVertical() != vertical: + continue + + for tmpPane in tmpDock.panes: + + minSize = pane_border_size*2 - sash_size + + if vertical: + minSize += tmpPane.min_size.y + caption_size + else: + minSize += tmpPane.min_size.x + + if minSize > minSizeMax: + minSizeMax = minSize + + result += minSizeMax + + # Get opposite docks + oppositeDocks = FindOppositeDocks(docks, direction) + + # Sum size of the opposite docks and their sashes + for dock in oppositeDocks: + result += dock.size + # if it's not a toolbar add the sash_size too + if not dock.toolbar: + result += sash_size + + return result + + + def CalculateDockSizerLimits(self, dock): + """ + Calculates the minimum and maximum sizes allowed for the input dock. + + :param `dock`: the L{AuiDockInfo} structure to analyze. + """ + + docks, panes = CopyDocksAndPanes2(self._docks, self._panes) + + sash_size = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + caption_size = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + opposite_size = self.GetOppositeDockTotalSize(docks, dock.dock_direction) + + for tmpDock in docks: + + if tmpDock.dock_direction == dock.dock_direction and \ + tmpDock.dock_layer == dock.dock_layer and \ + tmpDock.dock_row == dock.dock_row: + + tmpDock.size = 1 + break + + sizer, panes, docks, uiparts = self.LayoutAll(panes, docks, [], True, False) + client_size = self._frame.GetClientSize() + sizer.SetDimension(0, 0, client_size.x, client_size.y) + sizer.Layout() + + for part in uiparts: + + part.rect = wx.RectPS(part.sizer_item.GetPosition(), part.sizer_item.GetSize()) + if part.type == AuiDockUIPart.typeDock: + part.dock.rect = part.rect + + sizer.Destroy() + new_dock = None + + for tmpDock in docks: + if tmpDock.dock_direction == dock.dock_direction and \ + tmpDock.dock_layer == dock.dock_layer and \ + tmpDock.dock_row == dock.dock_row: + + new_dock = tmpDock + break + + partnerDock = self.GetPartnerDock(dock) + + if partnerDock: + partnerRange = partnerDock.size - partnerDock.min_size + if partnerDock.min_size == 0: + partnerRange -= sash_size + if dock.IsHorizontal(): + partnerRange -= caption_size + + direction = dock.dock_direction + + if direction == AUI_DOCK_LEFT: + minPix = new_dock.rect.x + new_dock.rect.width + maxPix = dock.rect.x + dock.rect.width + maxPix += partnerRange + + elif direction == AUI_DOCK_TOP: + minPix = new_dock.rect.y + new_dock.rect.height + maxPix = dock.rect.y + dock.rect.height + maxPix += partnerRange + + elif direction == AUI_DOCK_RIGHT: + minPix = dock.rect.x - partnerRange - sash_size + maxPix = new_dock.rect.x - sash_size + + elif direction == AUI_DOCK_BOTTOM: + minPix = dock.rect.y - partnerRange - sash_size + maxPix = new_dock.rect.y - sash_size + + return minPix, maxPix + + direction = new_dock.dock_direction + + if direction == AUI_DOCK_LEFT: + minPix = new_dock.rect.x + new_dock.rect.width + maxPix = client_size.x - opposite_size - sash_size + + elif direction == AUI_DOCK_TOP: + minPix = new_dock.rect.y + new_dock.rect.height + maxPix = client_size.y - opposite_size - sash_size + + elif direction == AUI_DOCK_RIGHT: + minPix = opposite_size + maxPix = new_dock.rect.x - sash_size + + elif direction == AUI_DOCK_BOTTOM: + minPix = opposite_size + maxPix = new_dock.rect.y - sash_size + + return minPix, maxPix + + + def CalculatePaneSizerLimits(self, dock, pane): + """ + Calculates the minimum and maximum sizes allowed for the input pane. + + :param `dock`: the L{AuiDockInfo} structure to which `pane` belongs to; + :param `pane`: a L{AuiPaneInfo} class for which calculation are requested. + """ + + if pane.IsFixed(): + if dock.IsHorizontal(): + minPix = maxPix = pane.rect.x + 1 + pane.rect.width + else: + minPix = maxPix = pane.rect.y + 1 + pane.rect.height + + return minPix, maxPix + + totalPixsize, totalProportion = self.GetTotalPixSizeAndProportion(dock) + partnerPane = self.GetPartnerPane(dock, pane) + + if dock.IsHorizontal(): + + minPix = pane.rect.x + 1 + maxPix = pane.rect.x + 1 + pane.rect.width + + if pane.min_size.IsFullySpecified(): + minPix += pane.min_size.x + else: + minPix += 1 + + if partnerPane: + maxPix += partnerPane.rect.width + + if partnerPane.min_size.IsFullySpecified(): + maxPix -= partnerPane.min_size.x - 1 + + else: + minPix = maxPix + + else: + + minPix = pane.rect.y + 1 + maxPix = pane.rect.y + 1 + pane.rect.height + + if pane.min_size.IsFullySpecified(): + minPix += pane.min_size.y + else: + minPix += 1 + + if partnerPane: + maxPix += partnerPane.rect.height + + if partnerPane.min_size.IsFullySpecified(): + maxPix -= partnerPane.min_size.y - 1 + + else: + minPix = maxPix + + return minPix, maxPix + + + def CheckMovableSizer(self, part): + """ + Checks if a UI part can be actually resized. + + :param `part`: a UI part. + """ + + # a dock may not be resized if it has a single + # pane which is not resizable + if part.type == AuiDockUIPart.typeDockSizer and part.dock and \ + len(part.dock.panes) == 1 and part.dock.panes[0].IsFixed(): + + return False + + if part.pane: + + # panes that may not be resized should be ignored here + minPix, maxPix = self.CalculatePaneSizerLimits(part.dock, part.pane) + + if minPix == maxPix: + return False + + return True + + + def PaneFromTabEvent(self, event): + """ + Returns a L{AuiPaneInfo} from a L{AuiNotebookEvent} event. + + :param `event`: a L{AuiNotebookEvent} event. + """ + + obj = event.GetEventObject() + + if obj and isinstance(obj, auibook.AuiTabCtrl): + + page_idx = obj.GetActivePage() + + if page_idx >= 0: + page = obj.GetPage(page_idx) + window = page.window + if window: + return self.GetPane(window) + + elif obj and isinstance(obj, auibook.AuiNotebook): + + page_idx = event.GetSelection() + + if page_idx >= 0: + window = obj.GetPage(page_idx) + if window: + return self.GetPane(window) + + return NonePaneInfo + + + def OnTabBeginDrag(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._masterManager: + self._masterManager.OnTabBeginDrag(event) + + else: + paneInfo = self.PaneFromTabEvent(event) + + if paneInfo.IsOk(): + + # It's one of ours! + self._action = actionDragFloatingPane + mouse = wx.GetMousePosition() + + # set initial float position - may have to think about this + # offset a bit more later ... + self._action_offset = wx.Point(20, 10) + self._toolbar_action_offset = wx.Point(20, 10) + + paneInfo.floating_pos = mouse - self._action_offset + paneInfo.dock_pos = AUI_DOCK_NONE + paneInfo.notebook_id = -1 + + tab = event.GetEventObject() + + if tab.HasCapture(): + tab.ReleaseMouse() + + # float the window + if paneInfo.IsMaximized(): + self.RestorePane(paneInfo) + paneInfo.Float() + self.Update() + + self._action_window = paneInfo.window + + self._frame.CaptureMouse() + event.SetDispatched(True) + + else: + + # not our window + event.Skip() + + + def OnTabPageClose(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_PAGE_CLOSE`` event. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._masterManager: + self._masterManager.OnTabPageClose(event) + + else: + + p = self.PaneFromTabEvent(event) + if p.IsOk(): + + # veto it because we will call "RemovePage" ourselves + event.Veto() + + # Now ask the app if they really want to close... + # fire pane close event + e = AuiManagerEvent(wxEVT_AUI_PANE_CLOSE) + e.SetPane(p) + e.SetCanVeto(True) + self.ProcessMgrEvent(e) + + if e.GetVeto(): + return + + self.ClosePane(p) + self.Update() + else: + event.Skip() + + + def OnTabSelected(self, event): + """ + Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGED`` event. + + :param `event`: a L{AuiNotebookEvent} event to be processed. + """ + + if self._masterManager: + self._masterManager.OnTabSelected(event) + return + + obj = event.GetEventObject() + + if obj and isinstance(obj, auibook.AuiNotebook): + + notebook = obj + page = notebook.GetPage(event.GetSelection()) + paneInfo = self.GetPane(page) + + if paneInfo.IsOk(): + notebookRoot = GetNotebookRoot(self._panes, paneInfo.notebook_id) + if notebookRoot: + + notebookRoot.Caption(paneInfo.caption) + self.RefreshCaptions() + + event.Skip() + + + def GetNotebooks(self): + """ Returns all the automatic L{AuiNotebook} in the L{AuiManager}. """ + + if self._masterManager: + return self._masterManager.GetNotebooks() + + return self._notebooks + + + def SetMasterManager(self, manager): + """ + Sets the master manager for an automatic L{AuiNotebook}. + + :param `manager`: an instance of L{AuiManager}. + """ + + self._masterManager = manager + + + def ProcessDockResult(self, target, new_pos): + """ + This is a utility function used by L{DoDrop} - it checks + if a dock operation is allowed, the new dock position is copied into + the target info. If the operation was allowed, the function returns ``True``. + + :param `target`: the L{AuiPaneInfo} instance to be docked; + :param `new_pos`: the new docking position if the docking operation is allowed. + """ + + allowed = False + direction = new_pos.dock_direction + + if direction == AUI_DOCK_TOP: + allowed = target.IsTopDockable() + elif direction == AUI_DOCK_BOTTOM: + allowed = target.IsBottomDockable() + elif direction == AUI_DOCK_LEFT: + allowed = target.IsLeftDockable() + elif direction == AUI_DOCK_RIGHT: + allowed = target.IsRightDockable() + + if allowed: + target = new_pos + + if target.IsToolbar(): + self.SwitchToolBarOrientation(target) + + return allowed, target + + + def SwitchToolBarOrientation(self, pane): + """ + Switches the toolbar orientation from vertical to horizontal and vice-versa. + This is especially useful for vertical docked toolbars once they float. + + :param `pane`: an instance of L{AuiPaneInfo}, which may have a L{AuiToolBar} + window associated with it. + """ + + if not isinstance(pane.window, auibar.AuiToolBar): + return pane + + if pane.IsFloating(): + return pane + + toolBar = pane.window + direction = pane.dock_direction + vertical = direction in [AUI_DOCK_LEFT, AUI_DOCK_RIGHT] + + agwStyle = toolBar.GetAGWWindowStyleFlag() + new_agwStyle = agwStyle + + if vertical: + new_agwStyle |= AUI_TB_VERTICAL + else: + new_agwStyle &= ~(AUI_TB_VERTICAL) + + if agwStyle != new_agwStyle: + toolBar.SetAGWWindowStyleFlag(new_agwStyle) + if not toolBar.GetGripperVisible(): + toolBar.SetGripperVisible(True) + + s = pane.window.GetMinSize() + pane.BestSize(s) + + if new_agwStyle != agwStyle: + toolBar.Realize() + + return pane + + + def DoDrop(self, docks, panes, target, pt, offset=wx.Point(0, 0)): + """ + This is an important function. It basically takes a mouse position, + and determines where the panes new position would be. If the pane is to be + dropped, it performs the drop operation using the specified dock and pane + arrays. By specifying copy dock and pane arrays when calling, a "what-if" + scenario can be performed, giving precise coordinates for drop hints. + + :param `docks`: a list of L{AuiDockInfo} classes; + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `pt`: a mouse position to check for a drop operation; + :param `offset`: a possible offset from the input point `pt`. + """ + + if target.IsToolbar(): + return self.DoDropToolbar(docks, panes, target, pt, offset) + elif target.IsFloating(): + return self.DoDropFloatingPane(docks, panes, target, pt) + else: + return self.DoDropNonFloatingPane(docks, panes, target, pt) + + + def CopyTarget(self, target): + """ + Copies all the attributes of the input `target` into another L{AuiPaneInfo}. + + :param `target`: the source L{AuiPaneInfo} from where to copy attributes. + """ + + drop = AuiPaneInfo() + drop.name = target.name + drop.caption = target.caption + drop.window = target.window + drop.frame = target.frame + drop.state = target.state + drop.dock_direction = target.dock_direction + drop.dock_layer = target.dock_layer + drop.dock_row = target.dock_row + drop.dock_pos = target.dock_pos + drop.best_size = wx.Size(*target.best_size) + drop.min_size = wx.Size(*target.min_size) + drop.max_size = wx.Size(*target.max_size) + drop.floating_pos = wx.Point(*target.floating_pos) + drop.floating_size = wx.Size(*target.floating_size) + drop.dock_proportion = target.dock_proportion + drop.buttons = target.buttons + drop.rect = wx.Rect(*target.rect) + drop.icon = target.icon + drop.notebook_id = target.notebook_id + drop.transparent = target.transparent + drop.snapped = target.snapped + drop.minimize_mode = target.minimize_mode + + return drop + + + def DoDropToolbar(self, docks, panes, target, pt, offset): + """ + Handles the situation in which the dropped pane contains a toolbar. + + :param `docks`: a list of L{AuiDockInfo} classes; + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `target`: the target pane containing the toolbar; + :param `pt`: a mouse position to check for a drop operation; + :param `offset`: a possible offset from the input point `pt`. + """ + + drop = self.CopyTarget(target) + + # The result should always be shown + drop.Show() + + # Check to see if the toolbar has been dragged out of the window + if CheckOutOfWindow(self._frame, pt): + if self._agwFlags & AUI_MGR_ALLOW_FLOATING and drop.IsFloatable(): + drop.Float() + + return self.ProcessDockResult(target, drop) + + # Allow directional change when the cursor leaves this rect + safeRect = wx.Rect(*target.rect) + if target.IsHorizontal(): + safeRect.Inflate(100, 50) + else: + safeRect.Inflate(50, 100) + + # Check to see if the toolbar has been dragged to edge of the frame + dropDir = CheckEdgeDrop(self._frame, docks, pt) + + if dropDir != -1: + + if dropDir == wx.LEFT: + drop.Dock().Left().Layer(auiToolBarLayer).Row(0). \ + Position(pt.y - self.GetDockPixelOffset(drop) - offset.y) + + elif dropDir == wx.RIGHT: + drop.Dock().Right().Layer(auiToolBarLayer).Row(0). \ + Position(pt.y - self.GetDockPixelOffset(drop) - offset.y) + + elif dropDir == wx.TOP: + drop.Dock().Top().Layer(auiToolBarLayer).Row(0). \ + Position(pt.x - self.GetDockPixelOffset(drop) - offset.x) + + elif dropDir == wx.BOTTOM: + drop.Dock().Bottom().Layer(auiToolBarLayer).Row(0). \ + Position(pt.x - self.GetDockPixelOffset(drop) - offset.x) + + if not target.IsFloating() and safeRect.Contains(pt) and \ + target.dock_direction != drop.dock_direction: + return False, target + + return self.ProcessDockResult(target, drop) + + # If the windows is floating and out of the client area, do nothing + if drop.IsFloating() and not self._frame.GetClientRect().Contains(pt): + return False, target + + # Ok, can't drop on edge - check internals ... + + clientSize = self._frame.GetClientSize() + x = Clip(pt.x, 0, clientSize.x - 1) + y = Clip(pt.y, 0, clientSize.y - 1) + part = self.HitTest(x, y) + + if not part or not part.dock: + return False, target + + dock = part.dock + + # toolbars may only be moved in and to fixed-pane docks, + # otherwise we will try to float the pane. Also, the pane + # should float if being dragged over center pane windows + if not dock.fixed or dock.dock_direction == AUI_DOCK_CENTER: + + if (self._agwFlags & AUI_MGR_ALLOW_FLOATING and drop.IsFloatable()) or \ + dock.dock_direction not in [AUI_DOCK_CENTER, AUI_DOCK_NONE]: + if drop.IsFloatable(): + drop.Float() + + return self.ProcessDockResult(target, drop) + + # calculate the offset from where the dock begins + # to the point where the user dropped the pane + dockDropOffset = 0 + if dock.IsHorizontal(): + dockDropOffset = pt.x - dock.rect.x - offset.x + else: + dockDropOffset = pt.y - dock.rect.y - offset.y + + drop.Dock().Direction(dock.dock_direction).Layer(dock.dock_layer). \ + Row(dock.dock_row).Position(dockDropOffset) + + if (pt.y <= dock.rect.GetTop() + 2 and dock.IsHorizontal()) or \ + (pt.x <= dock.rect.GetLeft() + 2 and dock.IsVertical()): + + if dock.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_LEFT]: + row = drop.dock_row + panes = DoInsertDockRow(panes, dock.dock_direction, dock.dock_layer, dock.dock_row) + drop.dock_row = row + + else: + panes = DoInsertDockRow(panes, dock.dock_direction, dock.dock_layer, dock.dock_row+1) + drop.dock_row = dock.dock_row + 1 + + if (pt.y >= dock.rect.GetBottom() - 2 and dock.IsHorizontal()) or \ + (pt.x >= dock.rect.GetRight() - 2 and dock.IsVertical()): + + if dock.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_LEFT]: + panes = DoInsertDockRow(panes, dock.dock_direction, dock.dock_layer, dock.dock_row+1) + drop.dock_row = dock.dock_row+1 + + else: + row = drop.dock_row + panes = DoInsertDockRow(panes, dock.dock_direction, dock.dock_layer, dock.dock_row) + drop.dock_row = row + + if not target.IsFloating() and safeRect.Contains(pt) and \ + target.dock_direction != drop.dock_direction: + return False, target + + return self.ProcessDockResult(target, drop) + + + def DoDropFloatingPane(self, docks, panes, target, pt): + """ + Handles the situation in which the dropped pane contains a normal window. + + :param `docks`: a list of L{AuiDockInfo} classes; + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `target`: the target pane containing the window; + :param `pt`: a mouse position to check for a drop operation. + """ + + screenPt = self._frame.ClientToScreen(pt) + paneInfo = self.PaneHitTest(panes, pt) + + if paneInfo.IsMaximized(): + return False, target + + if paneInfo.window is None: + return False, target + + # search the dock guides. + # reverse order to handle the center first. + for i in xrange(len(self._guides)-1, -1, -1): + guide = self._guides[i] + + # do hit testing on the guide + dir = guide.host.HitTest(screenPt.x, screenPt.y) + + if dir == -1: # point was outside of the dock guide + continue + + if dir == wx.ALL: # target is a single dock guide + return self.DoDropLayer(docks, target, guide.dock_direction) + + elif dir == wx.CENTER: + + if not target.IsNotebookDockable(): + continue + if not paneInfo.IsNotebookDockable() and not paneInfo.IsNotebookControl(): + continue + + if not paneInfo.HasNotebook(): + + # Add a new notebook pane with the original as a tab... + self.CreateNotebookBase(panes, paneInfo) + + # Add new item to notebook + target.NotebookPage(paneInfo.notebook_id) + + else: + + drop_pane = False + drop_row = False + + insert_dir = paneInfo.dock_direction + insert_layer = paneInfo.dock_layer + insert_row = paneInfo.dock_row + insert_pos = paneInfo.dock_pos + + if insert_dir == AUI_DOCK_CENTER: + + insert_layer = 0 + if dir == wx.LEFT: + insert_dir = AUI_DOCK_LEFT + elif dir == wx.UP: + insert_dir = AUI_DOCK_TOP + elif dir == wx.RIGHT: + insert_dir = AUI_DOCK_RIGHT + elif dir == wx.DOWN: + insert_dir = AUI_DOCK_BOTTOM + + if insert_dir == AUI_DOCK_LEFT: + + drop_pane = (dir == wx.UP or dir == wx.DOWN) + drop_row = (dir == wx.LEFT or dir == wx.RIGHT) + if dir == wx.RIGHT: + insert_row += 1 + elif dir == wx.DOWN: + insert_pos += 1 + + elif insert_dir == AUI_DOCK_RIGHT: + + drop_pane = (dir == wx.UP or dir == wx.DOWN) + drop_row = (dir == wx.LEFT or dir == wx.RIGHT) + if dir == wx.LEFT: + insert_row += 1 + elif dir == wx.DOWN: + insert_pos += 1 + + elif insert_dir == AUI_DOCK_TOP: + + drop_pane = (dir == wx.LEFT or dir == wx.RIGHT) + drop_row = (dir == wx.UP or dir == wx.DOWN) + if dir == wx.DOWN: + insert_row += 1 + elif dir == wx.RIGHT: + insert_pos += 1 + + elif insert_dir == AUI_DOCK_BOTTOM: + + drop_pane = (dir == wx.LEFT or dir == wx.RIGHT) + drop_row = (dir == wx.UP or dir == wx.DOWN) + if dir == wx.UP: + insert_row += 1 + elif dir == wx.RIGHT: + insert_pos += 1 + + if paneInfo.dock_direction == AUI_DOCK_CENTER: + insert_row = GetMaxRow(panes, insert_dir, insert_layer) + 1 + + if drop_pane: + return self.DoDropPane(panes, target, insert_dir, insert_layer, insert_row, insert_pos) + + if drop_row: + return self.DoDropRow(panes, target, insert_dir, insert_layer, insert_row) + + return True, target + + return False, target + + + def DoDropNonFloatingPane(self, docks, panes, target, pt): + """ + Handles the situation in which the dropped pane is not floating. + + :param `docks`: a list of L{AuiDockInfo} classes; + :param `panes`: a list of L{AuiPaneInfo} instances; + :param `target`: the target pane containing the toolbar; + :param `pt`: a mouse position to check for a drop operation. + """ + + screenPt = self._frame.ClientToScreen(pt) + clientSize = self._frame.GetClientSize() + frameRect = GetInternalFrameRect(self._frame, self._docks) + + drop = self.CopyTarget(target) + + # The result should always be shown + drop.Show() + + part = self.HitTest(pt.x, pt.y) + + if not part: + return False, target + + if part.type == AuiDockUIPart.typeDockSizer: + + if len(part.dock.panes) != 1: + return False, target + + part = self.GetPanePart(part.dock.panes[0].window) + if not part: + return False, target + + if not part.pane: + return False, target + + part = self.GetPanePart(part.pane.window) + if not part: + return False, target + + insert_dock_row = False + insert_row = part.pane.dock_row + insert_dir = part.pane.dock_direction + insert_layer = part.pane.dock_layer + + direction = part.pane.dock_direction + + if direction == AUI_DOCK_TOP: + if pt.y >= part.rect.y and pt.y < part.rect.y+auiInsertRowPixels: + insert_dock_row = True + + elif direction == AUI_DOCK_BOTTOM: + if pt.y > part.rect.y+part.rect.height-auiInsertRowPixels and \ + pt.y <= part.rect.y + part.rect.height: + insert_dock_row = True + + elif direction == AUI_DOCK_LEFT: + if pt.x >= part.rect.x and pt.x < part.rect.x+auiInsertRowPixels: + insert_dock_row = True + + elif direction == AUI_DOCK_RIGHT: + if pt.x > part.rect.x+part.rect.width-auiInsertRowPixels and \ + pt.x <= part.rect.x+part.rect.width: + insert_dock_row = True + + elif direction == AUI_DOCK_CENTER: + + # "new row pixels" will be set to the default, but + # must never exceed 20% of the window size + new_row_pixels_x = auiNewRowPixels + new_row_pixels_y = auiNewRowPixels + + if new_row_pixels_x > (part.rect.width*20)/100: + new_row_pixels_x = (part.rect.width*20)/100 + + if new_row_pixels_y > (part.rect.height*20)/100: + new_row_pixels_y = (part.rect.height*20)/100 + + # determine if the mouse pointer is in a location that + # will cause a new row to be inserted. The hot spot positions + # are along the borders of the center pane + + insert_layer = 0 + insert_dock_row = True + pr = part.rect + + if pt.x >= pr.x and pt.x < pr.x + new_row_pixels_x: + insert_dir = AUI_DOCK_LEFT + elif pt.y >= pr.y and pt.y < pr.y + new_row_pixels_y: + insert_dir = AUI_DOCK_TOP + elif pt.x >= pr.x + pr.width - new_row_pixels_x and pt.x < pr.x + pr.width: + insert_dir = AUI_DOCK_RIGHT + elif pt.y >= pr.y+ pr.height - new_row_pixels_y and pt.y < pr.y + pr.height: + insert_dir = AUI_DOCK_BOTTOM + else: + return False, target + + insert_row = GetMaxRow(panes, insert_dir, insert_layer) + 1 + + if insert_dock_row: + + panes = DoInsertDockRow(panes, insert_dir, insert_layer, insert_row) + drop.Dock().Direction(insert_dir).Layer(insert_layer). \ + Row(insert_row).Position(0) + + return self.ProcessDockResult(target, drop) + + # determine the mouse offset and the pane size, both in the + # direction of the dock itself, and perpendicular to the dock + + if part.orientation == wx.VERTICAL: + + offset = pt.y - part.rect.y + size = part.rect.GetHeight() + + else: + + offset = pt.x - part.rect.x + size = part.rect.GetWidth() + + drop_position = part.pane.dock_pos + + # if we are in the top/left part of the pane, + # insert the pane before the pane being hovered over + if offset <= size/2: + + drop_position = part.pane.dock_pos + panes = DoInsertPane(panes, + part.pane.dock_direction, + part.pane.dock_layer, + part.pane.dock_row, + part.pane.dock_pos) + + # if we are in the bottom/right part of the pane, + # insert the pane before the pane being hovered over + if offset > size/2: + + drop_position = part.pane.dock_pos+1 + panes = DoInsertPane(panes, + part.pane.dock_direction, + part.pane.dock_layer, + part.pane.dock_row, + part.pane.dock_pos+1) + + + drop.Dock(). \ + Direction(part.dock.dock_direction). \ + Layer(part.dock.dock_layer).Row(part.dock.dock_row). \ + Position(drop_position) + + return self.ProcessDockResult(target, drop) + + + def DoDropLayer(self, docks, target, dock_direction): + """ + Handles the situation in which `target` is a single dock guide. + + :param `docks`: a list of L{AuiDockInfo} classes; + :param `target`: the target pane; + :param `dock_direction`: the docking direction. + """ + + drop = self.CopyTarget(target) + + if dock_direction == AUI_DOCK_LEFT: + drop.Dock().Left() + drop_new_layer = max(max(GetMaxLayer(docks, AUI_DOCK_LEFT), + GetMaxLayer(docks, AUI_DOCK_BOTTOM)), + GetMaxLayer(docks, AUI_DOCK_TOP)) + 1 + + elif dock_direction == AUI_DOCK_TOP: + drop.Dock().Top() + drop_new_layer = max(max(GetMaxLayer(docks, AUI_DOCK_TOP), + GetMaxLayer(docks, AUI_DOCK_LEFT)), + GetMaxLayer(docks, AUI_DOCK_RIGHT)) + 1 + + elif dock_direction == AUI_DOCK_RIGHT: + drop.Dock().Right() + drop_new_layer = max(max(GetMaxLayer(docks, AUI_DOCK_RIGHT), + GetMaxLayer(docks, AUI_DOCK_TOP)), + GetMaxLayer(docks, AUI_DOCK_BOTTOM)) + 1 + + elif dock_direction == AUI_DOCK_BOTTOM: + drop.Dock().Bottom() + drop_new_layer = max(max(GetMaxLayer(docks, AUI_DOCK_BOTTOM), + GetMaxLayer(docks, AUI_DOCK_LEFT)), + GetMaxLayer(docks, AUI_DOCK_RIGHT)) + 1 + + else: + return False, target + + + drop.Dock().Layer(drop_new_layer) + return self.ProcessDockResult(target, drop) + + + def DoDropPane(self, panes, target, dock_direction, dock_layer, dock_row, dock_pos): + """ + Drop a pane in the interface. + + :param `panes`: a list of L{AuiPaneInfo} classes; + :param `target`: the target pane; + :param `dock_direction`: the docking direction; + :param `dock_layer`: the docking layer; + :param `dock_row`: the docking row; + :param `dock_pos`: the docking position. + """ + + drop = self.CopyTarget(target) + panes = DoInsertPane(panes, dock_direction, dock_layer, dock_row, dock_pos) + + drop.Dock().Direction(dock_direction).Layer(dock_layer).Row(dock_row).Position(dock_pos) + return self.ProcessDockResult(target, drop) + + + def DoDropRow(self, panes, target, dock_direction, dock_layer, dock_row): + """ + Insert a row in the interface before dropping. + + :param `panes`: a list of L{AuiPaneInfo} classes; + :param `target`: the target pane; + :param `dock_direction`: the docking direction; + :param `dock_layer`: the docking layer; + :param `dock_row`: the docking row. + """ + + drop = self.CopyTarget(target) + panes = DoInsertDockRow(panes, dock_direction, dock_layer, dock_row) + + drop.Dock().Direction(dock_direction).Layer(dock_layer).Row(dock_row).Position(0) + return self.ProcessDockResult(target, drop) + + + def ShowHint(self, rect): + """ + Shows the AUI hint window. + + :param `rect`: the hint rect calculated in advance. + """ + + if rect == self._last_hint: + return + + if self._agwFlags & AUI_MGR_RECTANGLE_HINT and wx.Platform != "__WXMAC__": + + if self._last_hint != rect: + # remove the last hint rectangle + self._last_hint = wx.Rect(*rect) + self._frame.Refresh() + self._frame.Update() + + screendc = wx.ScreenDC() + clip = wx.Region(1, 1, 10000, 10000) + + # clip all floating windows, so we don't draw over them + for pane in self._panes: + if pane.IsFloating() and pane.frame.IsShown(): + + rect2 = wx.Rect(*pane.frame.GetRect()) + if wx.Platform == "__WXGTK__": + # wxGTK returns the client size, not the whole frame size + rect2.width += 15 + rect2.height += 35 + rect2.Inflate(5, 5) + + clip.SubtractRect(rect2) + + # As we can only hide the hint by redrawing the managed window, we + # need to clip the region to the managed window too or we get + # nasty redrawn problems. + clip.IntersectRect(self._frame.GetRect()) + screendc.SetClippingRegionAsRegion(clip) + + stipple = PaneCreateStippleBitmap() + brush = wx.BrushFromBitmap(stipple) + screendc.SetBrush(brush) + screendc.SetPen(wx.TRANSPARENT_PEN) + screendc.DrawRectangle(rect.x, rect.y, 5, rect.height) + screendc.DrawRectangle(rect.x+5, rect.y, rect.width-10, 5) + screendc.DrawRectangle(rect.x+rect.width-5, rect.y, 5, rect.height) + screendc.DrawRectangle(rect.x+5, rect.y+rect.height-5, rect.width-10, 5) + RefreshDockingGuides(self._guides) + + return + + if not self._hint_window: + self.CreateHintWindow() + + if self._hint_window: + self._hint_window.SetRect(rect) + self._hint_window.Show() + + self._hint_fadeamt = self._hint_fademax + + if self._agwFlags & AUI_MGR_HINT_FADE: + self._hint_fadeamt = 0 + self._hint_window.SetTransparent(self._hint_fadeamt) + + if self._action == actionDragFloatingPane and self._action_window: + self._action_window.SetFocus() + + if self._hint_fadeamt != self._hint_fademax: # Only fade if we need to + # start fade in timer + self._hint_fadetimer.Start(5) + + self._last_hint = wx.Rect(*rect) + + + def HideHint(self): + """ Hides a transparent window hint if there is one. """ + + # hides a transparent window hint if there is one + if self._hint_window: + self._hint_window.Hide() + + self._hint_fadetimer.Stop() + self._last_hint = wx.Rect() + + + def IsPaneButtonVisible(self, part): + """ + Returns whether a pane button in the pane caption is visible. + + :param `part`: the UI part to analyze. + """ + + captionRect = wx.Rect() + + for temp_part in self._uiparts: + if temp_part.pane == part.pane and \ + temp_part.type == AuiDockUIPart.typeCaption: + captionRect = temp_part.rect + break + + return captionRect.ContainsRect(part.rect) + + + def DrawPaneButton(self, dc, part, pt): + """ + Draws a pane button in the caption (convenience function). + + :param `dc`: a `wx.DC` device context object; + :param `part`: the UI part to analyze; + :param `pt`: a `wx.Point` object, specifying the mouse location. + """ + + if not self.IsPaneButtonVisible(part): + return + + state = AUI_BUTTON_STATE_NORMAL + + if part.rect.Contains(pt): + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if leftDown: + state = AUI_BUTTON_STATE_PRESSED + else: + state = AUI_BUTTON_STATE_HOVER + + self._art.DrawPaneButton(dc, self._frame, part.button.button_id, + state, part.rect, part.pane) + + + def RefreshButton(self, part): + """ + Refreshes a pane button in the caption. + + :param `part`: the UI part to analyze. + """ + + rect = wx.Rect(*part.rect) + rect.Inflate(2, 2) + self._frame.Refresh(True, rect) + self._frame.Update() + + + def RefreshCaptions(self): + """ Refreshes all pane captions. """ + + for part in self._uiparts: + if part.type == AuiDockUIPart.typeCaption: + self._frame.Refresh(True, part.rect) + self._frame.Update() + + + def CalculateHintRect(self, pane_window, pt, offset): + """ + Calculates the drop hint rectangle. + + The method first calls L{DoDrop} to determine the exact position the pane would + be at were if dropped. If the pane would indeed become docked at the + specified drop point, the the rectangle hint will be returned in + screen coordinates. Otherwise, an empty rectangle is returned. + + :param `pane_window`: it is the window pointer of the pane being dragged; + :param `pt`: is the mouse position, in client coordinates; + :param `offset`: describes the offset that the mouse is from the upper-left + corner of the item being dragged. + """ + + # we need to paint a hint rectangle to find out the exact hint rectangle, + # we will create a new temporary layout and then measure the resulting + # rectangle we will create a copy of the docking structures (self._docks) + # so that we don't modify the real thing on screen + + rect = wx.Rect() + pane = self.GetPane(pane_window) + + attrs = self.GetAttributes(pane) + hint = AuiPaneInfo() + hint = self.SetAttributes(hint, attrs) + + if hint.name != "__HINT__": + self._oldname = hint.name + + hint.name = "__HINT__" + hint.PaneBorder(True) + hint.Show() + + if not hint.IsOk(): + hint.name = self._oldname + return rect + + docks, panes = CopyDocksAndPanes2(self._docks, self._panes) + + # remove any pane already there which bears the same window + # this happens when you are moving a pane around in a dock + for ii in xrange(len(panes)): + if panes[ii].window == pane_window: + docks = RemovePaneFromDocks(docks, panes[ii]) + panes.pop(ii) + break + + # find out where the new pane would be + allow, hint = self.DoDrop(docks, panes, hint, pt, offset) + + if not allow: + return rect + + panes.append(hint) + + sizer, panes, docks, uiparts = self.LayoutAll(panes, docks, [], True, False) + + client_size = self._frame.GetClientSize() + sizer.SetDimension(0, 0, client_size.x, client_size.y) + sizer.Layout() + + sought = "__HINT__" + + # For a notebook page, actually look for the noteboot itself. + if hint.IsNotebookPage(): + id = hint.notebook_id + for pane in panes: + if pane.IsNotebookControl() and pane.notebook_id==id: + sought = pane.name + break + + for part in uiparts: + if part.pane and part.pane.name == sought: + rect.Union(wx.RectPS(part.sizer_item.GetPosition(), + part.sizer_item.GetSize())) + + sizer.Destroy() + + # check for floating frame ... + if rect.IsEmpty(): + for p in panes: + if p.name == sought and p.IsFloating(): + return wx.RectPS(p.floating_pos, p.floating_size) + + if rect.IsEmpty(): + return rect + + # actually show the hint rectangle on the screen + rect.x, rect.y = self._frame.ClientToScreen((rect.x, rect.y)) + if self._frame.GetLayoutDirection() == wx.Layout_RightToLeft: + # Mirror rectangle in RTL mode + rect.x -= rect.GetWidth() + + return rect + + + def DrawHintRect(self, pane_window, pt, offset): + """ + Calculates the hint rectangle by calling + L{CalculateHintRect}. If there is a rectangle, it shows it + by calling L{ShowHint}, otherwise it hides any hint + rectangle currently shown. + + :param `pane_window`: it is the window pointer of the pane being dragged; + :param `pt`: is the mouse position, in client coordinates; + :param `offset`: describes the offset that the mouse is from the upper-left + corner of the item being dragged. + """ + + rect = self.CalculateHintRect(pane_window, pt, offset) + + if rect.IsEmpty(): + self.HideHint() + self._hint_rect = wx.Rect() + else: + self.ShowHint(rect) + self._hint_rect = wx.Rect(*rect) + + + def GetPartSizerRect(self, uiparts): + """ + Returns the rectangle surrounding the specified UI parts. + + :param `uiparts`: UI parts. + """ + + rect = wx.Rect() + + for part in self._uiparts: + if part.pane and part.pane.name == "__HINT__": + rect.Union(wx.RectPS(part.sizer_item.GetPosition(), + part.sizer_item.GetSize())) + + return rect + + + def GetAttributes(self, pane): + """ + Returns all the attributes of a L{AuiPaneInfo}. + + :param `pane`: a L{AuiPaneInfo} instance. + """ + + attrs = [] + attrs.extend([pane.window, pane.frame, pane.state, pane.dock_direction, + pane.dock_layer, pane.dock_pos, pane.dock_row, pane.dock_proportion, + pane.floating_pos, pane.floating_size, pane.best_size, + pane.min_size, pane.max_size, pane.caption, pane.name, + pane.buttons, pane.rect, pane.icon, pane.notebook_id, + pane.transparent, pane.snapped, pane.minimize_mode]) + + return attrs + + + def SetAttributes(self, pane, attrs): + """ + Sets all the attributes contained in `attrs` to a L{AuiPaneInfo}. + + :param `pane`: a L{AuiPaneInfo} instance; + :param `attrs`: a list of attributes. + """ + + pane.window = attrs[0] + pane.frame = attrs[1] + pane.state = attrs[2] + pane.dock_direction = attrs[3] + pane.dock_layer = attrs[4] + pane.dock_pos = attrs[5] + pane.dock_row = attrs[6] + pane.dock_proportion = attrs[7] + pane.floating_pos = attrs[8] + pane.floating_size = attrs[9] + pane.best_size = attrs[10] + pane.min_size = attrs[11] + pane.max_size = attrs[12] + pane.caption = attrs[13] + pane.name = attrs[14] + pane.buttons = attrs[15] + pane.rect = attrs[16] + pane.icon = attrs[17] + pane.notebook_id = attrs[18] + pane.transparent = attrs[19] + pane.snapped = attrs[20] + pane.minimize_mode = attrs[21] + + return pane + + + def OnFloatingPaneResized(self, wnd, size): + """ + Handles the resizing of a floating pane. + + :param `wnd`: a `wx.Window` derived window, managed by the pane; + :param `size`: a `wx.Size` object, specifying the new pane floating size. + """ + + # try to find the pane + pane = self.GetPane(wnd) + if not pane.IsOk(): + raise Exception("Pane window not found") + + if pane.frame: + indx = self._panes.index(pane) + pane.floating_pos = pane.frame.GetPosition() + pane.floating_size = size + self._panes[indx] = pane + if pane.IsSnappable(): + self.SnapPane(pane, pane.floating_pos, pane.floating_size, True) + + + def OnFloatingPaneClosed(self, wnd, event): + """ + Handles the close event of a floating pane. + + :param `wnd`: a `wx.Window` derived window, managed by the pane; + :param `event`: a `wx.CloseEvent` to be processed. + """ + + # try to find the pane + pane = self.GetPane(wnd) + if not pane.IsOk(): + raise Exception("Pane window not found") + + # fire pane close event + e = AuiManagerEvent(wxEVT_AUI_PANE_CLOSE) + e.SetPane(pane) + e.SetCanVeto(event.CanVeto()) + self.ProcessMgrEvent(e) + + if e.GetVeto(): + event.Veto() + return + else: + # close the pane, but check that it + # still exists in our pane array first + # (the event handler above might have removed it) + + check = self.GetPane(wnd) + if check.IsOk(): + self.ClosePane(pane) + + + def OnFloatingPaneActivated(self, wnd): + """ + Handles the activation event of a floating pane. + + :param `wnd`: a `wx.Window` derived window, managed by the pane. + """ + + pane = self.GetPane(wnd) + if not pane.IsOk(): + raise Exception("Pane window not found") + + if self.GetAGWFlags() & AUI_MGR_ALLOW_ACTIVE_PANE: + ret, self._panes = SetActivePane(self._panes, wnd) + self.RefreshCaptions() + self.FireEvent(wxEVT_AUI_PANE_ACTIVATED, wnd, canVeto=False) + + + def OnFloatingPaneMoved(self, wnd, eventOrPt): + """ + Handles the move event of a floating pane. + + :param `wnd`: a `wx.Window` derived window, managed by the pane; + :param `eventOrPt`: a `wx.MoveEvent` to be processed or an instance of `wx.Point`. + """ + + pane = self.GetPane(wnd) + if not pane.IsOk(): + raise Exception("Pane window not found") + + if not pane.IsSnappable(): + return + + if isinstance(eventOrPt, wx.Point): + pane_pos = wx.Point(*eventOrPt) + else: + pane_pos = eventOrPt.GetPosition() + + pane_size = pane.floating_size + + self.SnapPane(pane, pane_pos, pane_size, False) + + + def SnapPane(self, pane, pane_pos, pane_size, toSnap=False): + """ + Snaps a floating pane to one of the main frame sides. + + :param `pane`: a L{AuiPaneInfo} instance; + :param `pane_pos`: the new pane floating position; + :param `pane_size`: the new pane floating size; + :param `toSnap`: a bool variable to check if L{SnapPane} was called from + a move event. + """ + + if self._from_move: + return + + managed_window = self.GetManagedWindow() + wnd_pos = managed_window.GetPosition() + wnd_size = managed_window.GetSize() + snapX, snapY = self._snap_limits + + if not toSnap: + pane.snapped = 0 + if pane.IsLeftSnappable(): + # Check if we can snap to the left + diff = wnd_pos.x - (pane_pos.x + pane_size.x) + if -snapX <= diff <= snapX: + pane.snapped = wx.LEFT + pane.floating_pos = wx.Point(wnd_pos.x-pane_size.x, pane_pos.y) + elif pane.IsTopSnappable(): + # Check if we can snap to the top + diff = wnd_pos.y - (pane_pos.y + pane_size.y) + if -snapY <= diff <= snapY: + pane.snapped = wx.TOP + pane.floating_pos = wx.Point(pane_pos.x, wnd_pos.y-pane_size.y) + elif pane.IsRightSnappable(): + # Check if we can snap to the right + diff = pane_pos.x - (wnd_pos.x + wnd_size.x) + if -snapX <= diff <= snapX: + pane.snapped = wx.RIGHT + pane.floating_pos = wx.Point(wnd_pos.x + wnd_size.x, pane_pos.y) + elif pane.IsBottomSnappable(): + # Check if we can snap to the bottom + diff = pane_pos.y - (wnd_pos.y + wnd_size.y) + if -snapY <= diff <= snapY: + pane.snapped = wx.BOTTOM + pane.floating_pos = wx.Point(pane_pos.x, wnd_pos.y + wnd_size.y) + + self.RepositionPane(pane, wnd_pos, wnd_size) + + + def RepositionPane(self, pane, wnd_pos, wnd_size): + """ + Repositions a pane after the main frame has been moved/resized. + + :param `pane`: a L{AuiPaneInfo} instance; + :param `wnd_pos`: the main frame position; + :param `wnd_size`: the main frame size. + """ + + pane_pos = pane.floating_pos + pane_size = pane.floating_size + + snap = pane.snapped + if snap == wx.LEFT: + floating_pos = wx.Point(wnd_pos.x - pane_size.x, pane_pos.y) + elif snap == wx.TOP: + floating_pos = wx.Point(pane_pos.x, wnd_pos.y - pane_size.y) + elif snap == wx.RIGHT: + floating_pos = wx.Point(wnd_pos.x + wnd_size.x, pane_pos.y) + elif snap == wx.BOTTOM: + floating_pos = wx.Point(pane_pos.x, wnd_pos.y + wnd_size.y) + + if snap: + if pane_pos != floating_pos: + pane.floating_pos = floating_pos + self._from_move = True + pane.frame.SetPosition(pane.floating_pos) + self._from_move = False + + + def OnGripperClicked(self, pane_window, start, offset): + """ + Handles the mouse click on the pane gripper. + + :param `pane_window`: a `wx.Window` derived window, managed by the pane; + :param `start`: a `wx.Point` object, specifying the clicking position; + :param `offset`: an offset point from the `start` position. + """ + + # try to find the pane + paneInfo = self.GetPane(pane_window) + + if not paneInfo.IsOk(): + raise Exception("Pane window not found") + + if self.GetAGWFlags() & AUI_MGR_ALLOW_ACTIVE_PANE: + # set the caption as active + ret, self._panes = SetActivePane(self._panes, pane_window) + self.RefreshCaptions() + self.FireEvent(wxEVT_AUI_PANE_ACTIVATED, pane_window, canVeto=False) + + self._action_part = None + self._action_pane = paneInfo + self._action_window = pane_window + self._action_start = start + self._action_offset = offset + self._toolbar_action_offset = wx.Point(*self._action_offset) + + self._frame.CaptureMouse() + + if paneInfo.IsDocked(): + self._action = actionClickCaption + else: + if paneInfo.IsToolbar(): + self._action = actionDragToolbarPane + else: + self._action = actionDragFloatingPane + + if paneInfo.frame: + + windowPt = paneInfo.frame.GetRect().GetTopLeft() + originPt = paneInfo.frame.ClientToScreen(wx.Point()) + self._action_offset += originPt - windowPt + self._toolbar_action_offset = wx.Point(*self._action_offset) + + if self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + paneInfo.frame.SetTransparent(150) + + if paneInfo.IsToolbar(): + self._frame.SetCursor(wx.StockCursor(wx.CURSOR_SIZING)) + + + def OnRender(self, event): + """ + Draws all of the pane captions, sashes, + backgrounds, captions, grippers, pane borders and buttons. + It renders the entire user interface. It binds the ``EVT_AUI_RENDER`` event. + + :param `event`: an instance of L{AuiManagerEvent}. + """ + + # if the frame is about to be deleted, don't bother + if not self._frame or self._frame.IsBeingDeleted(): + return + + if not self._frame.GetSizer(): + return + + mouse = wx.GetMouseState() + mousePos = wx.Point(mouse.GetX(), mouse.GetY()) + point = self._frame.ScreenToClient(mousePos) + art = self._art + + dc = event.GetDC() + + for part in self._uiparts: + + # don't draw hidden pane items or items that aren't windows + if part.sizer_item and ((not part.sizer_item.IsWindow() and \ + not part.sizer_item.IsSpacer() and \ + not part.sizer_item.IsSizer()) or \ + not part.sizer_item.IsShown()): + + continue + + ptype = part.type + + if ptype in [AuiDockUIPart.typeDockSizer, AuiDockUIPart.typePaneSizer]: + art.DrawSash(dc, self._frame, part.orientation, part.rect) + + elif ptype == AuiDockUIPart.typeBackground: + art.DrawBackground(dc, self._frame, part.orientation, part.rect) + + elif ptype == AuiDockUIPart.typeCaption: + art.DrawCaption(dc, self._frame, part.pane.caption, part.rect, part.pane) + + elif ptype == AuiDockUIPart.typeGripper: + art.DrawGripper(dc, self._frame, part.rect, part.pane) + + elif ptype == AuiDockUIPart.typePaneBorder: + art.DrawBorder(dc, self._frame, part.rect, part.pane) + + elif ptype == AuiDockUIPart.typePaneButton: + self.DrawPaneButton(dc, part, point) + + + def Repaint(self, dc=None): + """ + Repaints the entire frame decorations (sashes, borders, buttons and so on). + It renders the entire user interface. + + :param `dc`: if not ``None``, an instance of `wx.PaintDC`. + """ + + w, h = self._frame.GetClientSize() + + # Figure out which dc to use; if one + # has been specified, use it, otherwise + # make a client dc + if dc is None: + client_dc = wx.ClientDC(self._frame) + dc = client_dc + + # If the frame has a toolbar, the client area + # origin will not be (0, 0). + pt = self._frame.GetClientAreaOrigin() + if pt.x != 0 or pt.y != 0: + dc.SetDeviceOrigin(pt.x, pt.y) + + # Render all the items + self.Render(dc) + + + def Render(self, dc): + """ + Fires a render event, which is normally handled by + L{OnRender}. This allows the render function to + be overridden via the render event. + + This can be useful for painting custom graphics in the main window. + Default behavior can be invoked in the overridden function by calling + L{OnRender}. + + :param `dc`: a `wx.DC` device context object. + """ + + e = AuiManagerEvent(wxEVT_AUI_RENDER) + e.SetManager(self) + e.SetDC(dc) + self.ProcessMgrEvent(e) + + + def OnCaptionDoubleClicked(self, pane_window): + """ + Handles the mouse double click on the pane caption. + + :param `pane_window`: a `wx.Window` derived window, managed by the pane. + """ + + # try to find the pane + paneInfo = self.GetPane(pane_window) + if not paneInfo.IsOk(): + raise Exception("Pane window not found") + + if not paneInfo.IsFloatable() or not paneInfo.IsDockable() or \ + self._agwFlags & AUI_MGR_ALLOW_FLOATING == 0: + return + + indx = self._panes.index(paneInfo) + win_rect = None + + if paneInfo.IsFloating(): + if paneInfo.name.startswith("__floating__"): + # It's a floating tab from a AuiNotebook + notebook = paneInfo.window.__aui_notebook__ + notebook.ReDockPage(paneInfo) + self.Update() + return + else: + + e = self.FireEvent(wxEVT_AUI_PANE_DOCKING, paneInfo, canVeto=True) + if e.GetVeto(): + self.HideHint() + ShowDockingGuides(self._guides, False) + return + + win_rect = paneInfo.frame.GetRect() + paneInfo.Dock() + if paneInfo.IsToolbar(): + paneInfo = self.SwitchToolBarOrientation(paneInfo) + + e = self.FireEvent(wxEVT_AUI_PANE_DOCKED, paneInfo, canVeto=False) + + else: + + e = self.FireEvent(wxEVT_AUI_PANE_FLOATING, paneInfo, canVeto=True) + if e.GetVeto(): + return + + # float the window + if paneInfo.IsMaximized(): + self.RestorePane(paneInfo) + + if paneInfo.floating_pos == wx.Point(-1, -1): + captionSize = self._art.GetMetric(AUI_DOCKART_CAPTION_SIZE) + paneInfo.floating_pos = pane_window.GetScreenPosition() + paneInfo.floating_pos.y -= captionSize + + paneInfo.Float() + e = self.FireEvent(wxEVT_AUI_PANE_FLOATED, paneInfo, canVeto=False) + + self._panes[indx] = paneInfo + self.Update() + + if win_rect and self._agwFlags & AUI_MGR_ANIMATE_FRAMES: + paneInfo = self.GetPane(pane_window) + pane_rect = paneInfo.window.GetScreenRect() + self.AnimateDocking(win_rect, pane_rect) + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for L{AuiManager}. + + :param `event`: an instance of `wx.PaintEvent` to be processed. + """ + + dc = wx.PaintDC(self._frame) + self.Repaint(dc) + + + def OnEraseBackground(self, event): + """ + Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiManager}. + + :param `event`: `wx.EraseEvent` to be processed. + + :note: This is intentionally empty (excluding wxMAC) to reduce + flickering while drawing. + """ + + if wx.Platform == "__WXMAC__": + event.Skip() + + + def OnSize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for L{AuiManager}. + + :param `event`: a `wx.SizeEvent` to be processed. + """ + + skipped = False + if isinstance(self._frame, AuiFloatingFrame) and self._frame.IsShownOnScreen(): + skipped = True + event.Skip() + + if self._frame: + + self.DoFrameLayout() + if wx.Platform == "__WXMAC__": + self._frame.Refresh() + else: + self.Repaint() + + if isinstance(self._frame, wx.MDIParentFrame) or isinstance(self._frame, tabmdi.AuiMDIClientWindow) \ + or isinstance(self._frame, tabmdi.AuiMDIParentFrame): + # for MDI parent frames, this event must not + # be "skipped". In other words, the parent frame + # must not be allowed to resize the client window + # after we are finished processing sizing changes + return + + if not skipped: + event.Skip() + + # For the snap to screen... + self.OnMove(None) + + + def OnFindManager(self, event): + """ + Handles the ``EVT_AUI_FIND_MANAGER`` event for L{AuiManager}. + + :param `event`: a L{AuiManagerEvent} event to be processed. + """ + + # Initialize to None + event.SetManager(None) + + if not self._frame: + return + + # See it this window wants to overwrite + self._frame.ProcessEvent(event) + + # if no, it must be us + if not event.GetManager(): + event.SetManager(self) + + + def OnSetCursor(self, event): + """ + Handles the ``wx.EVT_SET_CURSOR`` event for L{AuiManager}. + + :param `event`: a `wx.SetCursorEvent` to be processed. + """ + + # determine cursor + part = self.HitTest(event.GetX(), event.GetY()) + cursor = wx.NullCursor + + if part: + if part.type in [AuiDockUIPart.typeDockSizer, AuiDockUIPart.typePaneSizer]: + + if not self.CheckMovableSizer(part): + return + + if part.orientation == wx.VERTICAL: + cursor = wx.StockCursor(wx.CURSOR_SIZEWE) + else: + cursor = wx.StockCursor(wx.CURSOR_SIZENS) + + elif part.type == AuiDockUIPart.typeGripper: + cursor = wx.StockCursor(wx.CURSOR_SIZING) + + event.SetCursor(cursor) + + + def UpdateButtonOnScreen(self, button_ui_part, event): + """ + Updates/redraws the UI part containing a pane button. + + :param `button_ui_part`: the UI part the button belongs to; + :param `event`: a `wx.MouseEvent` to be processed. + """ + + hit_test = self.HitTest(*event.GetPosition()) + + if not hit_test or not button_ui_part: + return + + state = AUI_BUTTON_STATE_NORMAL + + if hit_test == button_ui_part: + if event.LeftDown(): + state = AUI_BUTTON_STATE_PRESSED + else: + state = AUI_BUTTON_STATE_HOVER + else: + if event.LeftDown(): + state = AUI_BUTTON_STATE_HOVER + + # now repaint the button with hover state + cdc = wx.ClientDC(self._frame) + + # if the frame has a toolbar, the client area + # origin will not be (0,0). + pt = self._frame.GetClientAreaOrigin() + if pt.x != 0 or pt.y != 0: + cdc.SetDeviceOrigin(pt.x, pt.y) + + if hit_test.pane: + self._art.DrawPaneButton(cdc, self._frame, + button_ui_part.button.button_id, + state, + button_ui_part.rect, hit_test.pane) + + + def OnLeftDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiManager}. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + part = self.HitTest(*event.GetPosition()) + + if not part: + event.Skip() + return + + self._currentDragItem = -1 + + if part.type in [AuiDockUIPart.typeDockSizer, AuiDockUIPart.typePaneSizer]: + + if not self.CheckMovableSizer(part): + return + + self._action = actionResize + self._action_part = part + self._action_pane = None + self._action_rect = wx.Rect() + self._action_start = wx.Point(event.GetX(), event.GetY()) + self._action_offset = wx.Point(event.GetX() - part.rect.x, + event.GetY() - part.rect.y) + + # draw the resize hint + rect = wx.RectPS(self._frame.ClientToScreen(part.rect.GetPosition()), + part.rect.GetSize()) + + self._action_rect = wx.Rect(*rect) + + if not AuiManager_HasLiveResize(self): + if wx.Platform == "__WXMAC__": + dc = wx.ClientDC(self._frame) + else: + dc = wx.ScreenDC() + + DrawResizeHint(dc, rect) + + self._frame.CaptureMouse() + + elif part.type == AuiDockUIPart.typePaneButton: + if self.IsPaneButtonVisible(part): + self._action = actionClickButton + self._action_part = part + self._action_pane = None + self._action_start = wx.Point(*event.GetPosition()) + self._frame.CaptureMouse() + + self.RefreshButton(part) + + elif part.type in [AuiDockUIPart.typeCaption, AuiDockUIPart.typeGripper]: + + # if we are managing a AuiFloatingFrame window, then + # we are an embedded AuiManager inside the AuiFloatingFrame. + # We want to initiate a toolbar drag in our owner manager + if isinstance(part.pane.window.GetParent(), AuiFloatingFrame): + rootManager = GetManager(part.pane.window) + else: + rootManager = self + + offset = wx.Point(event.GetX() - part.rect.x, event.GetY() - part.rect.y) + rootManager.OnGripperClicked(part.pane.window, event.GetPosition(), offset) + + if wx.Platform != "__WXMAC__": + event.Skip() + + + def OnLeftDClick(self, event): + """ + Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiManager}. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + part = self.HitTest(event.GetX(), event.GetY()) + + if part and part.type == AuiDockUIPart.typeCaption: + if isinstance(part.pane.window.GetParent(), AuiFloatingFrame): + rootManager = GetManager(part.pane.window) + else: + rootManager = self + + rootManager.OnCaptionDoubleClicked(part.pane.window) + + elif part and part.type in [AuiDockUIPart.typeDockSizer, AuiDockUIPart.typePaneSizer]: + # Handles double click on AuiNotebook sashes to unsplit + sash_size = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + for child in part.cont_sizer.GetChildren(): + if child.IsSizer(): + win = child.GetSizer().GetContainingWindow() + if isinstance(win, auibook.AuiNotebook): + win.UnsplitDClick(part, sash_size, event.GetPosition()) + break + + event.Skip() + + + def DoEndResizeAction(self, event): + """ + Ends a resize action, or for live update, resizes the sash. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + clientPt = event.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + return self.RestrictResize(clientPt, screenPt, createDC=False) + + + def RestrictResize(self, clientPt, screenPt, createDC): + """ Common method between L{DoEndResizeAction} and L{OnLeftUp_Resize}. """ + + dock = self._action_part.dock + pane = self._action_part.pane + + if createDC: + if wx.Platform == "__WXMAC__": + dc = wx.ClientDC(self._frame) + else: + dc = wx.ScreenDC() + + DrawResizeHint(dc, self._action_rect) + self._action_rect = wx.Rect() + + newPos = clientPt - self._action_offset + + if self._action_part.type == AuiDockUIPart.typeDockSizer: + minPix, maxPix = self.CalculateDockSizerLimits(dock) + else: + if not self._action_part.pane: + return + minPix, maxPix = self.CalculatePaneSizerLimits(dock, pane) + + if self._action_part.orientation == wx.HORIZONTAL: + newPos.y = Clip(newPos.y, minPix, maxPix) + else: + newPos.x = Clip(newPos.x, minPix, maxPix) + + if self._action_part.type == AuiDockUIPart.typeDockSizer: + + partnerDock = self.GetPartnerDock(dock) + sash_size = self._art.GetMetric(AUI_DOCKART_SASH_SIZE) + new_dock_size = 0 + direction = dock.dock_direction + + if direction == AUI_DOCK_LEFT: + new_dock_size = newPos.x - dock.rect.x + + elif direction == AUI_DOCK_TOP: + new_dock_size = newPos.y - dock.rect.y + + elif direction == AUI_DOCK_RIGHT: + new_dock_size = dock.rect.x + dock.rect.width - newPos.x - sash_size + + elif direction == AUI_DOCK_BOTTOM: + new_dock_size = dock.rect.y + dock.rect.height - newPos.y - sash_size + + deltaDockSize = new_dock_size - dock.size + + if partnerDock: + if deltaDockSize > partnerDock.size - sash_size: + deltaDockSize = partnerDock.size - sash_size + + partnerDock.size -= deltaDockSize + + dock.size += deltaDockSize + self.Update() + + else: + + # determine the new pixel size that the user wants + # this will help us recalculate the pane's proportion + if dock.IsHorizontal(): + oldPixsize = pane.rect.width + newPixsize = oldPixsize + newPos.x - self._action_part.rect.x + + else: + oldPixsize = pane.rect.height + newPixsize = oldPixsize + newPos.y - self._action_part.rect.y + + totalPixsize, totalProportion = self.GetTotalPixSizeAndProportion(dock) + partnerPane = self.GetPartnerPane(dock, pane) + + # prevent division by zero + if totalPixsize <= 0 or totalProportion <= 0 or not partnerPane: + return + + # adjust for the surplus + while (oldPixsize > 0 and totalPixsize > 10 and \ + oldPixsize*totalProportion/totalPixsize < pane.dock_proportion): + + totalPixsize -= 1 + + # calculate the new proportion of the pane + + newProportion = newPixsize*totalProportion/totalPixsize + newProportion = Clip(newProportion, 1, totalProportion) + deltaProp = newProportion - pane.dock_proportion + + if partnerPane.dock_proportion - deltaProp < 1: + deltaProp = partnerPane.dock_proportion - 1 + newProportion = pane.dock_proportion + deltaProp + + # borrow the space from our neighbor pane to the + # right or bottom (depending on orientation) + partnerPane.dock_proportion -= deltaProp + pane.dock_proportion = newProportion + + self.Update() + + return True + + + def OnLeftUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` event for L{AuiManager}. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if self._action == actionResize: +## self._frame.Freeze() + self.OnLeftUp_Resize(event) +## self._frame.Thaw() + + elif self._action == actionClickButton: + self.OnLeftUp_ClickButton(event) + + elif self._action == actionDragFloatingPane: + self.OnLeftUp_DragFloatingPane(event) + + elif self._action == actionDragToolbarPane: + self.OnLeftUp_DragToolbarPane(event) + + else: + event.Skip() + + if self._frame.HasCapture(): + self._frame.ReleaseMouse() + + self._action = actionNone + + + def OnMotion(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for L{AuiManager}. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if self._action == actionResize: + self.OnMotion_Resize(event) + + elif self._action == actionClickCaption: + self.OnMotion_ClickCaption(event) + + elif self._action == actionDragFloatingPane: + self.OnMotion_DragFloatingPane(event) + + elif self._action == actionDragToolbarPane: + self.OnMotion_DragToolbarPane(event) + + else: + self.OnMotion_Other(event) + + + def OnLeaveWindow(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiManager}. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if self._hover_button: + self.RefreshButton(self._hover_button) + self._hover_button = None + + + def OnCaptureLost(self, event): + """ + Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiManager}. + + :param `event`: a `wx.MouseCaptureLostEvent` to be processed. + """ + + # cancel the operation in progress, if any + if self._action != actionNone: + self._action = actionNone + self.HideHint() + + + def OnHintFadeTimer(self, event): + """ + Handles the ``wx.EVT_TIMER`` event for L{AuiManager}. + + :param `event`: a `wx.TimerEvent` to be processed. + """ + + if not self._hint_window or self._hint_fadeamt >= self._hint_fademax: + self._hint_fadetimer.Stop() + return + + self._hint_fadeamt += 4 + self._hint_window.SetTransparent(self._hint_fadeamt) + + + def OnMove(self, event): + """ + Handles the ``wx.EVT_MOVE`` event for L{AuiManager}. + + :param `event`: a `wx.MoveEvent` to be processed. + """ + + if event is not None: + event.Skip() + + if isinstance(self._frame, AuiFloatingFrame) and self._frame.IsShownOnScreen(): + return + + docked, hAlign, vAlign, monitor = self._is_docked + if docked: + self.Snap() + + for pane in self._panes: + if pane.IsSnappable(): + if pane.IsFloating() and pane.IsShown(): + self.SnapPane(pane, pane.floating_pos, pane.floating_size, True) + + + def OnSysColourChanged(self, event): + """ + Handles the ``wx.EVT_SYS_COLOUR_CHANGED`` event for L{AuiManager}. + + :param `event`: a `wx.SysColourChangedEvent` to be processed. + """ + + # This event is probably triggered by a theme change + # so we have to re-init the art provider. + if self._art: + self._art.Init() + + if self._frame: + self.Update() + self._frame.Refresh() + + + def OnChildFocus(self, event): + """ + Handles the ``wx.EVT_CHILD_FOCUS`` event for L{AuiManager}. + + :param `event`: a `wx.ChildFocusEvent` to be processed. + """ + + # when a child pane has it's focus set, we should change the + # pane's active state to reflect this. (this is only true if + # active panes are allowed by the owner) + + window = event.GetWindow() + if isinstance(window, wx.Dialog): + # Ignore EVT_CHILD_FOCUS events originating from dialogs not + # managed by AUI + rootManager = None + elif isinstance(window.GetParent(), AuiFloatingFrame): + rootManager = GetManager(window) + else: + rootManager = self + + if rootManager: + rootManager.ActivatePane(window) + + event.Skip() + + + def OnMotion_ClickCaption(self, event): + """ + Sub-handler for the L{OnMotion} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + clientPt = event.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X) + drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y) + + if not self._action_pane: + return + + # we need to check if the mouse is now being dragged + if not (abs(clientPt.x - self._action_start.x) > drag_x_threshold or \ + abs(clientPt.y - self._action_start.y) > drag_y_threshold): + + return + + # dragged -- we need to change the mouse action to 'drag' + if self._action_pane.IsToolbar(): + self._action = actionDragToolbarPane + self._action_window = self._action_pane.window + + elif self._action_pane.IsFloatable() and self._agwFlags & AUI_MGR_ALLOW_FLOATING: + + e = self.FireEvent(wxEVT_AUI_PANE_FLOATING, self._action_pane, canVeto=True) + if e.GetVeto(): + return + + self._action = actionDragFloatingPane + + # set initial float position + self._action_pane.floating_pos = screenPt - self._action_offset + + # float the window + if self._action_pane.IsMaximized(): + self.RestorePane(self._action_pane) + + self._action_pane.Hide() + self._action_pane.Float() + if wx.Platform == "__WXGTK__": + self._action_pane.Show() + + e = self.FireEvent(wxEVT_AUI_PANE_FLOATED, self._action_pane, canVeto=False) + + if not self._action_pane.frame: + self.Update() + + self._action_window = self._action_pane.window + + # adjust action offset for window frame + windowPt = self._action_pane.frame.GetRect().GetTopLeft() + originPt = self._action_pane.frame.ClientToScreen(wx.Point()) + self._toolbar_action_offset = originPt - windowPt + + if self._agwFlags & AUI_MGR_USE_NATIVE_MINIFRAMES: + originPt = windowPt + wx.Point(3, 3) + + self._action_offset += originPt - windowPt + + # action offset is used here to make it feel "natural" to the user + # to drag a docked pane and suddenly have it become a floating frame. + # Sometimes, however, the offset where the user clicked on the docked + # caption is bigger than the width of the floating frame itself, so + # in that case we need to set the action offset to a sensible value + frame_size = self._action_pane.frame.GetSize() + if self._action_offset.x > frame_size.x * 2 / 3: + self._action_offset.x = frame_size.x / 2 + if self._action_offset.y > frame_size.y * 2 / 3: + self._action_offset.y = frame_size.y / 2 + + self.OnMotion_DragFloatingPane(event) + if wx.Platform != "__WXGTK__": + self._action_pane.Show() + + self.Update() + + + def OnMotion_Resize(self, event): + """ + Sub-handler for the L{OnMotion} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if AuiManager_HasLiveResize(self): + if self._currentDragItem != -1: + self._action_part = self._uiparts[self._currentDragItem] + else: + self._currentDragItem = self._uiparts.index(self._action_part) + + if self._frame.HasCapture(): + self._frame.ReleaseMouse() + + self.DoEndResizeAction(event) + self._frame.CaptureMouse() + return + + if not self._action_part or not self._action_part.dock or not self._action_part.orientation: + return + + clientPt = event.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + dock = self._action_part.dock + pos = self._action_part.rect.GetPosition() + + if self._action_part.type == AuiDockUIPart.typeDockSizer: + minPix, maxPix = self.CalculateDockSizerLimits(dock) + else: + if not self._action_part.pane: + return + + pane = self._action_part.pane + minPix, maxPix = self.CalculatePaneSizerLimits(dock, pane) + + if self._action_part.orientation == wx.HORIZONTAL: + pos.y = Clip(clientPt.y - self._action_offset.y, minPix, maxPix) + else: + pos.x = Clip(clientPt.x - self._action_offset.x, minPix, maxPix) + + hintrect = wx.RectPS(self._frame.ClientToScreen(pos), self._action_part.rect.GetSize()) + + if hintrect != self._action_rect: + + if wx.Platform == "__WXMAC__": + dc = wx.ClientDC(self._frame) + else: + dc = wx.ScreenDC() + + DrawResizeHint(dc, self._action_rect) + DrawResizeHint(dc, hintrect) + self._action_rect = wx.Rect(*hintrect) + + + def OnLeftUp_Resize(self, event): + """ + Sub-handler for the L{OnLeftUp} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if self._currentDragItem != -1 and AuiManager_HasLiveResize(self): + self._action_part = self._uiparts[self._currentDragItem] + + if self._frame.HasCapture(): + self._frame.ReleaseMouse() + + self.DoEndResizeAction(event) + self._currentDragItem = -1 + return + + if not self._action_part or not self._action_part.dock: + return + + clientPt = event.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + return self.RestrictResize(clientPt, screenPt, createDC=True) + + + def OnLeftUp_ClickButton(self, event): + """ + Sub-handler for the L{OnLeftUp} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + self._hover_button = None + + if self._action_part: + self.RefreshButton(self._action_part) + + # make sure we're still over the item that was originally clicked + if self._action_part == self.HitTest(*event.GetPosition()): + + # fire button-click event + e = AuiManagerEvent(wxEVT_AUI_PANE_BUTTON) + e.SetManager(self) + e.SetPane(self._action_part.pane) + e.SetButton(self._action_part.button.button_id) + self.ProcessMgrEvent(e) + + + def CheckPaneMove(self, pane): + """ + Checks if a pane has moved by a visible amount. + + :param `pane`: an instance of L{AuiPaneInfo}. + """ + + win_rect = pane.frame.GetRect() + win_rect.x, win_rect.y = pane.floating_pos + + if win_rect == self._last_rect: + return False + + # skip the first move event + if self._last_rect.IsEmpty(): + self._last_rect = wx.Rect(*win_rect) + return False + + # skip if moving too fast to avoid massive redraws and + # jumping hint windows + if abs(win_rect.x - self._last_rect.x) > 10 or \ + abs(win_rect.y - self._last_rect.y) > 10: + self._last_rect = wx.Rect(*win_rect) + return False + + return True + + + def OnMotion_DragFloatingPane(self, eventOrPt): + """ + Sub-handler for the L{OnMotion} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + isPoint = False + if isinstance(eventOrPt, wx.Point): + clientPt = self._frame.ScreenToClient(eventOrPt) + screenPt = wx.Point(*eventOrPt) + isPoint = True + else: + clientPt = eventOrPt.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + framePos = wx.Point() + + # try to find the pane + pane = self.GetPane(self._action_window) + if not pane.IsOk(): + raise Exception("Pane window not found") + + # update floating position + if pane.IsFloating(): + diff = pane.floating_pos - (screenPt - self._action_offset) + pane.floating_pos = screenPt - self._action_offset + + framePos = pane.floating_pos + + # Move the pane window + if pane.frame: + + if diff.x != 0 or diff.y != 0: + if wx.Platform == "__WXMSW__" and (self._agwFlags & AUI_MGR_TRANSPARENT_DRAG) == 0: # and not self.CheckPaneMove(pane): + # return + # HACK: Terrible hack on wxMSW (!) + pane.frame.SetTransparent(254) + + self._from_move = True + pane.frame.Move(pane.floating_pos) + self._from_move = False + + if self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + pane.frame.SetTransparent(150) + + # calculate the offset from the upper left-hand corner + # of the frame to the mouse pointer + action_offset = screenPt - framePos + + # is the pane dockable? + if not self.CanDockPanel(pane): + self.HideHint() + ShowDockingGuides(self._guides, False) + return + + for paneInfo in self._panes: + + if not paneInfo.IsDocked() or not paneInfo.IsShown(): + continue + if paneInfo.IsToolbar() or paneInfo.IsNotebookControl(): + continue + if paneInfo.IsMaximized(): + continue + + if paneInfo.IsNotebookPage(): + + notebookRoot = GetNotebookRoot(self._panes, paneInfo.notebook_id) + + if not notebookRoot or not notebookRoot.IsDocked(): + continue + + rc = paneInfo.window.GetScreenRect() + if rc.Contains(screenPt): + if rc.height < 20 or rc.width < 20: + return + + self.UpdateDockingGuides(paneInfo) + ShowDockingGuides(self._guides, True) + break + + self.DrawHintRect(pane.window, clientPt, action_offset) + + + def OnLeftUp_DragFloatingPane(self, eventOrPt): + """ + Sub-handler for the L{OnLeftUp} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + if isinstance(eventOrPt, wx.Point): + clientPt = self._frame.ScreenToClient(eventOrPt) + screenPt = wx.Point(*eventOrPt) + else: + clientPt = eventOrPt.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + # try to find the pane + paneInfo = self.GetPane(self._action_window) + if not paneInfo.IsOk(): + raise Exception("Pane window not found") + + ret = False + + if paneInfo.frame: + + # calculate the offset from the upper left-hand corner + # of the frame to the mouse pointer + framePos = paneInfo.frame.GetPosition() + action_offset = screenPt - framePos + + # is the pane dockable? + if self.CanDockPanel(paneInfo): + # do the drop calculation + indx = self._panes.index(paneInfo) + ret, paneInfo = self.DoDrop(self._docks, self._panes, paneInfo, clientPt, action_offset) + + if ret: + e = self.FireEvent(wxEVT_AUI_PANE_DOCKING, paneInfo, canVeto=True) + if e.GetVeto(): + self.HideHint() + ShowDockingGuides(self._guides, False) + return + + e = self.FireEvent(wxEVT_AUI_PANE_DOCKED, paneInfo, canVeto=False) + + if self._agwFlags & AUI_MGR_SMOOTH_DOCKING: + self.SmoothDock(paneInfo) + + self._panes[indx] = paneInfo + + # if the pane is still floating, update it's floating + # position (that we store) + if paneInfo.IsFloating(): + paneInfo.floating_pos = paneInfo.frame.GetPosition() + if paneInfo.frame._transparent != paneInfo.transparent or self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + paneInfo.frame.SetTransparent(paneInfo.transparent) + paneInfo.frame._transparent = paneInfo.transparent + + elif self._has_maximized: + self.RestoreMaximizedPane() + + # reorder for dropping to a new notebook + # (caution: this code breaks the reference!) + tempPaneInfo = self.CopyTarget(paneInfo) + self._panes.remove(paneInfo) + self._panes.append(tempPaneInfo) + + if ret: + self.Update() + + self.HideHint() + ShowDockingGuides(self._guides, False) + + + def OnMotion_DragToolbarPane(self, eventOrPt): + """ + Sub-handler for the L{OnMotion} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + isPoint = False + if isinstance(eventOrPt, wx.Point): + clientPt = self._frame.ScreenToClient(eventOrPt) + screenPt = wx.Point(*eventOrPt) + isPoint = True + else: + clientPt = eventOrPt.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + pane = self.GetPane(self._action_window) + if not pane.IsOk(): + raise Exception("Pane window not found") + + pane.state |= AuiPaneInfo.actionPane + indx = self._panes.index(pane) + + ret = False + wasFloating = pane.IsFloating() + # is the pane dockable? + if self.CanDockPanel(pane): + # do the drop calculation + ret, pane = self.DoDrop(self._docks, self._panes, pane, clientPt, self._action_offset) + + # update floating position + if pane.IsFloating(): + pane.floating_pos = screenPt - self._toolbar_action_offset + + # move the pane window + if pane.frame: + if wx.Platform == "__WXMSW__" and (self._agwFlags & AUI_MGR_TRANSPARENT_DRAG) == 0: # and not self.CheckPaneMove(pane): + # return + # HACK: Terrible hack on wxMSW (!) + pane.frame.SetTransparent(254) + + self._from_move = True + pane.frame.Move(pane.floating_pos) + self._from_move = False + + if self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + pane.frame.SetTransparent(150) + + self._panes[indx] = pane + if ret and wasFloating != pane.IsFloating() or (ret and not wasFloating): + wx.CallAfter(self.Update) + + # when release the button out of the window. + # TODO: a better fix is needed. + + if _VERSION_STRING < "2.9": + leftDown = wx.GetMouseState().LeftDown() + else: + leftDown = wx.GetMouseState().LeftIsDown() + + if not leftDown: + self._action = actionNone + self.OnLeftUp_DragToolbarPane(eventOrPt) + + + def OnMotion_Other(self, event): + """ + Sub-handler for the L{OnMotion} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + part = self.HitTest(*event.GetPosition()) + + if part and part.type == AuiDockUIPart.typePaneButton \ + and self.IsPaneButtonVisible(part): + if part != self._hover_button: + + if self._hover_button: + self.RefreshButton(self._hover_button) + + self._hover_button = part + self.RefreshButton(part) + + else: + + if self._hover_button: + self.RefreshButton(self._hover_button) + else: + event.Skip() + + self._hover_button = None + + + def OnLeftUp_DragToolbarPane(self, eventOrPt): + """ + Sub-handler for the L{OnLeftUp} event. + + :param `event`: a `wx.MouseEvent` to be processed. + """ + + isPoint = False + if isinstance(eventOrPt, wx.Point): + clientPt = self._frame.ScreenToClient(eventOrPt) + screenPt = wx.Point(*eventOrPt) + isPoint = True + else: + clientPt = eventOrPt.GetPosition() + screenPt = self._frame.ClientToScreen(clientPt) + + # try to find the pane + pane = self.GetPane(self._action_window) + if not pane.IsOk(): + raise Exception("Pane window not found") + + if pane.IsFloating(): + pane.floating_pos = pane.frame.GetPosition() + if pane.frame._transparent != pane.transparent or self._agwFlags & AUI_MGR_TRANSPARENT_DRAG: + pane.frame.SetTransparent(pane.transparent) + pane.frame._transparent = pane.transparent + + # save the new positions + docks = FindDocks(self._docks, pane.dock_direction, pane.dock_layer, pane.dock_row) + if len(docks) == 1: + dock = docks[0] + pane_positions, pane_sizes = self.GetPanePositionsAndSizes(dock) + + for i in xrange(len(dock.panes)): + dock.panes[i].dock_pos = pane_positions[i] + + pane.state &= ~AuiPaneInfo.actionPane + self.Update() + + + def OnPaneButton(self, event): + """ + Handles the ``EVT_AUI_PANE_BUTTON`` event for L{AuiManager}. + + :param `event`: a L{AuiManagerEvent} event to be processed. + """ + + if not event.pane: + raise Exception("Pane Info passed to AuiManager.OnPaneButton must be non-null") + + pane = event.pane + + if event.button == AUI_BUTTON_CLOSE: + + if isinstance(pane.window.GetParent(), AuiFloatingFrame): + rootManager = GetManager(pane.window) + else: + rootManager = self + + if rootManager != self: + self._frame.Close() + return + + # fire pane close event + e = AuiManagerEvent(wxEVT_AUI_PANE_CLOSE) + e.SetManager(self) + e.SetPane(event.pane) + self.ProcessMgrEvent(e) + + if not e.GetVeto(): + + # close the pane, but check that it + # still exists in our pane array first + # (the event handler above might have removed it) + + check = self.GetPane(pane.window) + if check.IsOk(): + self.ClosePane(pane) + + self.Update() + + # mn this performs the minimizing of a pane + elif event.button == AUI_BUTTON_MINIMIZE: + e = AuiManagerEvent(wxEVT_AUI_PANE_MINIMIZE) + e.SetManager(self) + e.SetPane(event.pane) + self.ProcessMgrEvent(e) + + if not e.GetVeto(): + self.MinimizePane(pane) + + elif event.button == AUI_BUTTON_MAXIMIZE_RESTORE and not pane.IsMaximized(): + + # fire pane close event + e = AuiManagerEvent(wxEVT_AUI_PANE_MAXIMIZE) + e.SetManager(self) + e.SetPane(event.pane) + self.ProcessMgrEvent(e) + + if not e.GetVeto(): + + self.MaximizePane(pane) + self.Update() + + elif event.button == AUI_BUTTON_MAXIMIZE_RESTORE and pane.IsMaximized(): + + # fire pane close event + e = AuiManagerEvent(wxEVT_AUI_PANE_RESTORE) + e.SetManager(self) + e.SetPane(event.pane) + self.ProcessMgrEvent(e) + + if not e.GetVeto(): + + self.RestorePane(pane) + self.Update() + + elif event.button == AUI_BUTTON_PIN: + + if self._agwFlags & AUI_MGR_ALLOW_FLOATING and pane.IsFloatable(): + e = self.FireEvent(wxEVT_AUI_PANE_FLOATING, pane, canVeto=True) + if e.GetVeto(): + return + + pane.Float() + e = self.FireEvent(wxEVT_AUI_PANE_FLOATED, pane, canVeto=False) + + self.Update() + + + def MinimizePane(self, paneInfo): + """ + Minimizes a pane in a newly and automatically created L{AuiToolBar}. + + Clicking on the minimize button causes a new L{AuiToolBar} to be created + and added to the frame manager (currently the implementation is such that + panes at West will have a toolbar at the right, panes at South will have + toolbars at the bottom etc...) and the pane is hidden in the manager. + + Clicking on the restore button on the newly created toolbar will result in the + toolbar being removed and the original pane being restored. + + :param `paneInfo`: a L{AuiPaneInfo} instance for the pane to be minimized. + """ + + if not paneInfo.IsToolbar(): + + if paneInfo.IsMinimized(): + # We are already minimized + return + + # Basically the idea is this. + # + # 1) create a toolbar, with a restore button + # + # 2) place the new toolbar in the toolbar area representative of the location of the pane + # (NORTH/SOUTH/EAST/WEST, central area always to the right) + # + # 3) Hide the minimizing pane + + + # personalize the toolbar style + tbStyle = AUI_TB_DEFAULT_STYLE + posMask = paneInfo.minimize_mode & AUI_MINIMIZE_POS_MASK + captMask = paneInfo.minimize_mode & AUI_MINIMIZE_CAPT_MASK + dockDirection = paneInfo.dock_direction + if captMask != 0: + tbStyle |= AUI_TB_TEXT + if posMask == AUI_MINIMIZE_POS_SMART: + if paneInfo.dock_direction in [AUI_DOCK_TOP, AUI_DOCK_BOTTOM]: + tbStyle |= AUI_TB_HORZ_LAYOUT + + elif paneInfo.dock_direction in [AUI_DOCK_LEFT, AUI_DOCK_RIGHT, AUI_DOCK_CENTER]: + tbStyle |= AUI_TB_VERTICAL + if captMask == AUI_MINIMIZE_CAPT_SMART: + tbStyle |= AUI_TB_CLOCKWISE + + elif posMask in [AUI_MINIMIZE_POS_TOP, AUI_MINIMIZE_POS_BOTTOM]: + tbStyle |= AUI_TB_HORZ_LAYOUT + if posMask == AUI_MINIMIZE_POS_TOP: + dockDirection = AUI_DOCK_TOP + else: + dockDirection = AUI_DOCK_BOTTOM + + else: + tbStyle |= AUI_TB_VERTICAL + if captMask == AUI_MINIMIZE_CAPT_SMART: + tbStyle |= AUI_TB_CLOCKWISE + if posMask == AUI_MINIMIZE_POS_LEFT: + dockDirection = AUI_DOCK_LEFT + elif posMask == AUI_MINIMIZE_POS_RIGHT: + dockDirection = AUI_DOCK_RIGHT + elif posMask == AUI_MINIMIZE_POS_BOTTOM: + dockDirection = AUI_DOCK_BOTTOM + + # Create a new toolbar + # give it the same name as the minimized pane with _min appended + + win_rect = paneInfo.window.GetScreenRect() + + minimize_toolbar = auibar.AuiToolBar(self.GetManagedWindow(), agwStyle=tbStyle) + minimize_toolbar.Hide() + minimize_toolbar.SetToolBitmapSize(wx.Size(16, 16)) + + if paneInfo.icon and paneInfo.icon.IsOk(): + restore_bitmap = paneInfo.icon + else: + restore_bitmap = self._art._restore_bitmap + + minimize_toolbar.AddSimpleTool(ID_RESTORE_FRAME, paneInfo.caption, restore_bitmap, "Restore " + paneInfo.caption) + minimize_toolbar.SetAuiManager(self) + minimize_toolbar.Realize() + toolpanelname = paneInfo.name + "_min" + + if paneInfo.IsMaximized(): + paneInfo.SetFlag(paneInfo.wasMaximized, True) + + if dockDirection == AUI_DOCK_TOP: + self.AddPane(minimize_toolbar, AuiPaneInfo(). \ + Name(toolpanelname).Caption(paneInfo.caption). \ + ToolbarPane().Top().BottomDockable(False). \ + LeftDockable(False).RightDockable(False).DestroyOnClose()) + + elif dockDirection == AUI_DOCK_BOTTOM: + self.AddPane(minimize_toolbar, AuiPaneInfo(). \ + Name(toolpanelname).Caption(paneInfo.caption). \ + ToolbarPane().Bottom().TopDockable(False). \ + LeftDockable(False).RightDockable(False).DestroyOnClose()) + + elif dockDirection == AUI_DOCK_LEFT: + self.AddPane(minimize_toolbar, AuiPaneInfo(). \ + Name(toolpanelname).Caption(paneInfo.caption). \ + ToolbarPane().Left().TopDockable(False). \ + BottomDockable(False).RightDockable(False).DestroyOnClose()) + + elif dockDirection in [AUI_DOCK_RIGHT, AUI_DOCK_CENTER]: + self.AddPane(minimize_toolbar, AuiPaneInfo(). \ + Name(toolpanelname).Caption(paneInfo.caption). \ + ToolbarPane().Right().TopDockable(False). \ + LeftDockable(False).BottomDockable(False).DestroyOnClose()) + + arr = FindDocks(self._docks, paneInfo.dock_direction, paneInfo.dock_layer, paneInfo.dock_row) + + if arr: + dock = arr[0] + paneInfo.previousDockSize = dock.size + + paneInfo.previousDockPos = paneInfo.dock_pos + + # mark ourselves minimized + paneInfo.Minimize() + paneInfo.Show(False) + self._has_minimized = True + # last, hide the window + if paneInfo.window and paneInfo.window.IsShown(): + paneInfo.window.Show(False) + + minimize_toolbar.Show() + self.Update() + if self._agwFlags & AUI_MGR_ANIMATE_FRAMES: + self.AnimateDocking(win_rect, minimize_toolbar.GetScreenRect()) + + + def OnRestoreMinimizedPane(self, event): + """ + Handles the ``EVT_AUI_PANE_MIN_RESTORE`` event for L{AuiManager}. + + :param `event`: an instance of L{AuiManagerEvent} to be processed. + """ + + self.RestoreMinimizedPane(event.pane) + + + def OnPaneDocked(self, event): + """ + Handles the ``EVT_AUI_PANE_DOCKED`` event for L{AuiManager}. + + :param `event`: an instance of L{AuiManagerEvent} to be processed. + """ + + event.Skip() + self.RemoveAutoNBCaption(event.GetPane()) + + + def CreateNotebookBase(self, panes, paneInfo): + """ + Creates an auto-notebook base from a pane, and then add that pane as a page. + + :param `panes`: Set of panes to append new notebook base pane to + :param `paneInfo`: L{AuiPaneInfo} instance to convert to new notebook. + """ + + # Create base notebook pane ... + nbid = len(self._notebooks) + + baseInfo = AuiPaneInfo() + baseInfo.SetDockPos(paneInfo).NotebookControl(nbid). \ + CloseButton(False).SetNameFromNotebookId(). \ + NotebookDockable(False).Floatable(paneInfo.IsFloatable()) + baseInfo.best_size = paneInfo.best_size + panes.append(baseInfo) + + # add original pane as tab ... + paneInfo.NotebookPage(nbid) + + def RemoveAutoNBCaption(self, pane): + """ + Removes the caption on newly created automatic notebooks. + + :param `pane`: an instance of L{AuiPaneInfo} (the target notebook). + """ + + if self._agwFlags & AUI_MGR_AUTONB_NO_CAPTION == 0: + return False + + def RemoveCaption(): + """ Sub-function used to remove the pane caption on automatic notebooks. """ + + if pane.HasNotebook(): + notebook = self._notebooks[pane.notebook_id] + self.GetPane(notebook).CaptionVisible(False).PaneBorder(False) + self.Update() + + # it seems the notebook isnt created by this stage, so remove + # the caption a moment later + wx.CallAfter(RemoveCaption) + return True + + + def RestoreMinimizedPane(self, paneInfo): + """ + Restores a previously minimized pane. + + :param `paneInfo`: a L{AuiPaneInfo} instance for the pane to be restored. + """ + + panename = paneInfo.name + panename = panename[0:-4] + pane = self.GetPane(panename) + + pane.SetFlag(pane.needsRestore, True) + + if not pane.IsOk(): + panename = paneInfo.name + pane = self.GetPane(panename) + paneInfo = self.GetPane(panename + "_min") + if not paneInfo.IsOk(): + # Already minimized + return + + if pane.IsOk(): + if not pane.IsMinimized(): + return + + + if pane.HasFlag(pane.wasMaximized): + + self.SavePreviousDockSizes(pane) + + + self.ShowPane(pane.window, True) + pane.Show(True) + self._has_minimized = False + pane.SetFlag(pane.optionMinimized, False) + paneInfo.window.Show(False) + self.DetachPane(paneInfo.window) + paneInfo.Show(False) + paneInfo.Hide() + + self.Update() + + + def AnimateDocking(self, win_rect, pane_rect): + """ + Animates the minimization/docking of a pane a la Eclipse, using a `wx.ScreenDC` + to draw a "moving docking rectangle" on the screen. + + :param `win_rect`: the original pane screen rectangle; + :param `pane_rect`: the newly created toolbar/pane screen rectangle. + + :note: This functionality is not available on wxMAC as this platform doesn't have + the ability to use `wx.ScreenDC` to draw on-screen and on Windows > Vista. + """ + + if wx.Platform == "__WXMAC__": + # No wx.ScreenDC on the Mac... + return + if wx.Platform == "__WXMSW__" and wx.GetOsVersion()[1] > 5: + # No easy way to handle this on Vista... + return + + xstart, ystart = win_rect.x, win_rect.y + xend, yend = pane_rect.x, pane_rect.y + + step = self.GetAnimationStep() + + wstep = int(abs(win_rect.width - pane_rect.width)/step) + hstep = int(abs(win_rect.height - pane_rect.height)/step) + xstep = int(win_rect.x - pane_rect.x)/step + ystep = int(win_rect.y - pane_rect.y)/step + + dc = wx.ScreenDC() + dc.SetLogicalFunction(wx.INVERT) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(wx.LIGHT_GREY_PEN) + + for i in xrange(int(step)): + width, height = win_rect.width - i*wstep, win_rect.height - i*hstep + x, y = xstart - i*xstep, ystart - i*ystep + new_rect = wx.Rect(x, y, width, height) + dc.DrawRoundedRectangleRect(new_rect, 3) + wx.SafeYield() + wx.MilliSleep(10) + dc.DrawRoundedRectangleRect(new_rect, 3) + + + def SmoothDock(self, paneInfo): + """ + This method implements a smooth docking effect for floating panes, similar to + what the PyQT library does with its floating windows. + + :param `paneInfo`: an instance of L{AuiPaneInfo}. + + :note: The smooth docking effect can only be used if you set the ``AUI_MGR_SMOOTH_DOCKING`` + style to L{AuiManager}. + """ + + if paneInfo.IsToolbar(): + return + + if not paneInfo.frame or self._hint_rect.IsEmpty(): + return + + hint_rect = self._hint_rect + win_rect = paneInfo.frame.GetScreenRect() + + xstart, ystart = win_rect.x, win_rect.y + xend, yend = hint_rect.x, hint_rect.y + + step = self.GetAnimationStep()/3 + + wstep = int((win_rect.width - hint_rect.width)/step) + hstep = int((win_rect.height - hint_rect.height)/step) + xstep = int((win_rect.x - hint_rect.x))/step + ystep = int((win_rect.y - hint_rect.y))/step + + for i in xrange(int(step)): + width, height = win_rect.width - i*wstep, win_rect.height - i*hstep + x, y = xstart - i*xstep, ystart - i*ystep + new_rect = wx.Rect(x, y, width, height) + paneInfo.frame.SetRect(new_rect) + wx.MilliSleep(10) + + + def SetSnapLimits(self, x, y): + """ + Modifies the snap limits used when snapping the `managed_window` to the screen + (using L{SnapToScreen}) or when snapping the floating panes to one side of the + `managed_window` (using L{SnapPane}). + + To change the limit after which the `managed_window` or the floating panes are + automatically stickled to the screen border (or to the `managed_window` side), + set these two variables. Default values are 15 pixels. + + :param `x`: the minimum horizontal distance below which the snap occurs; + :param `y`: the minimum vertical distance below which the snap occurs. + """ + + self._snap_limits = (x, y) + self.Snap() + + + def Snap(self): + """ + Snaps the main frame to specified position on the screen. + + :see: L{SnapToScreen} + """ + + snap, hAlign, vAlign, monitor = self._is_docked + if not snap: + return + + managed_window = self.GetManagedWindow() + snap_pos = self.GetSnapPosition() + wnd_pos = managed_window.GetPosition() + snapX, snapY = self._snap_limits + + if abs(snap_pos.x - wnd_pos.x) < snapX and abs(snap_pos.y - wnd_pos.y) < snapY: + managed_window.SetPosition(snap_pos) + + + def SnapToScreen(self, snap=True, monitor=0, hAlign=wx.RIGHT, vAlign=wx.TOP): + """ + Snaps the main frame to specified position on the screen. + + :param `snap`: whether to snap the main frame or not; + :param `monitor`: the monitor display in which snapping the window; + :param `hAlign`: the horizontal alignment of the snapping position; + :param `vAlign`: the vertical alignment of the snapping position. + """ + + if not snap: + self._is_docked = (False, wx.RIGHT, wx.TOP, 0) + return + + displayCount = wx.Display.GetCount() + if monitor > displayCount: + raise Exception("Invalid monitor selected: you only have %d monitors"%displayCount) + + self._is_docked = (True, hAlign, vAlign, monitor) + self.GetManagedWindow().SetPosition(self.GetSnapPosition()) + + + def GetSnapPosition(self): + """ Returns the main frame snapping position. """ + + snap, hAlign, vAlign, monitor = self._is_docked + + display = wx.Display(monitor) + area = display.GetClientArea() + size = self.GetManagedWindow().GetSize() + + pos = wx.Point() + if hAlign == wx.LEFT: + pos.x = area.x + elif hAlign == wx.CENTER: + pos.x = area.x + (area.width - size.x)/2 + else: + pos.x = area.x + area.width - size.x + + if vAlign == wx.TOP: + pos.y = area.y + elif vAlign == wx.CENTER: + pos.y = area.y + (area.height - size.y)/2 + else: + pos.y = area.y + area.height - size.y + + return pos + + + def GetAnimationStep(self): + """ Returns the animation step speed (a float) to use in L{AnimateDocking}. """ + + return self._animation_step + + + def SetAnimationStep(self, step): + """ + Sets the animation step speed (a float) to use in L{AnimateDocking}. + + :param `step`: a floating point value for the animation speed. + """ + + self._animation_step = float(step) + + + def RequestUserAttention(self, pane_window): + """ + Requests the user attention by intermittently highlighting the pane caption. + + :param `pane_window`: a `wx.Window` derived window, managed by the pane. + """ + + # try to find the pane + paneInfo = self.GetPane(pane_window) + if not paneInfo.IsOk(): + raise Exception("Pane window not found") + + dc = wx.ClientDC(self._frame) + + # if the frame is about to be deleted, don't bother + if not self._frame or self._frame.IsBeingDeleted(): + return + + if not self._frame.GetSizer(): + return + + for part in self._uiparts: + if part.pane == paneInfo: + self._art.RequestUserAttention(dc, self._frame, part.pane.caption, part.rect, part.pane) + self._frame.RefreshRect(part.rect, True) + break + + + def StartPreviewTimer(self, toolbar): + """ + Starts a timer for sliding in and out a minimized pane. + + :param `toolbar`: the L{AuiToolBar} containing the minimized pane tool. + """ + + toolbar_pane = self.GetPane(toolbar) + toolbar_name = toolbar_pane.name + + pane_name = toolbar_name[0:-4] + + self._sliding_pane = self.GetPane(pane_name) + self._sliding_rect = toolbar.GetScreenRect() + self._sliding_direction = toolbar_pane.dock_direction + self._sliding_frame = None + + self._preview_timer.Start(1000, wx.TIMER_ONE_SHOT) + + + def StopPreviewTimer(self): + """ Stops a timer for sliding in and out a minimized pane. """ + + if self._preview_timer.IsRunning(): + self._preview_timer.Stop() + + self.SlideOut() + self._sliding_pane = None + + + def SlideIn(self, event): + """ + Handles the ``wx.EVT_TIMER`` event for L{AuiManager}. + + :param `event`: a `wx.TimerEvent` to be processed. + + :note: This is used solely for sliding in and out minimized panes. + """ + + window = self._sliding_pane.window + self._sliding_frame = wx.MiniFrame(None, -1, title=_("Pane Preview"), + style=wx.FRAME_TOOL_WINDOW | wx.STAY_ON_TOP | + wx.FRAME_NO_TASKBAR | wx.CAPTION) + window.Reparent(self._sliding_frame) + self._sliding_frame.SetSize((0, 0)) + window.Show() + self._sliding_frame.Show() + + size = window.GetBestSize() + + startX, startY, stopX, stopY = GetSlidingPoints(self._sliding_rect, size, self._sliding_direction) + + step = stopX/10 + window_size = 0 + + for i in xrange(0, stopX, step): + window_size = i + self._sliding_frame.SetDimensions(startX, startY, window_size, stopY) + self._sliding_frame.Refresh() + self._sliding_frame.Update() + wx.MilliSleep(10) + + self._sliding_frame.SetDimensions(startX, startY, stopX, stopY) + self._sliding_frame.Refresh() + self._sliding_frame.Update() + + + def SlideOut(self): + """ + Slides out a preview of a minimized pane. + + :note: This is used solely for sliding in and out minimized panes. + """ + + if not self._sliding_frame: + return + + window = self._sliding_frame.GetChildren()[0] + size = window.GetBestSize() + + startX, startY, stopX, stopY = GetSlidingPoints(self._sliding_rect, size, self._sliding_direction) + + step = stopX/10 + window_size = 0 + + for i in xrange(stopX, 0, -step): + window_size = i + self._sliding_frame.SetDimensions(startX, startY, window_size, stopY) + self._sliding_frame.Refresh() + self._sliding_frame.Update() + self._frame.RefreshRect(wx.Rect(startX+window_size, startY, step, stopY)) + self._frame.Update() + wx.MilliSleep(10) + + self._sliding_frame.SetDimensions(startX, startY, 0, stopY) + + window.Hide() + window.Reparent(self._frame) + + self._sliding_frame.Hide() + self._sliding_frame.Destroy() + self._sliding_frame = None + self._sliding_pane = None + + +class AuiManager_DCP(AuiManager): + """ + A class similar to L{AuiManager} but with a Dummy Center Pane (**DCP**). + The code for this class is still flickery due to the call to `wx.CallAfter` + and the double-update call. + """ + + def __init__(self, *args, **keys): + + AuiManager.__init__(self, *args, **keys) + self.hasDummyPane = False + + + def _createDummyPane(self): + """ Creates a Dummy Center Pane (**DCP**). """ + + if self.hasDummyPane: + return + + self.hasDummyPane = True + dummy = wx.Panel(self.GetManagedWindow()) + info = AuiPaneInfo().CenterPane().NotebookDockable(True).Name('dummyCenterPane').DestroyOnClose(True) + self.AddPane(dummy, info) + + + def _destroyDummyPane(self): + """ Destroys the Dummy Center Pane (**DCP**). """ + + if not self.hasDummyPane: + return + + self.hasDummyPane = False + self.ClosePane(self.GetPane('dummyCenterPane')) + + + def Update(self): + """ + This method is called after any number of changes are made to any of the + managed panes. L{Update} must be invoked after L{AuiManager.AddPane} or L{AuiManager.InsertPane} are + called in order to "realize" or "commit" the changes. + + In addition, any number of changes may be made to L{AuiPaneInfo} structures + (retrieved with L{AuiManager.GetPane}), but to realize the changes, L{Update} + must be called. This construction allows pane flicker to be avoided by updating + the whole layout at one time. + """ + + AuiManager.Update(self) + + # check if there's already a center pane (except our dummy pane) + dummyCenterPane = self.GetPane('dummyCenterPane') + haveCenterPane = any((pane != dummyCenterPane) and (pane.dock_direction == AUI_DOCK_CENTER) and + not pane.IsFloating() and pane.IsShown() for pane in self.GetAllPanes()) + if haveCenterPane: + if self.hasDummyPane: + # there's our dummy pane and also another center pane, therefor let's remove our dummy + def do(): + self._destroyDummyPane() + self.Update() + wx.CallAfter(do) + else: + # if we get here, there's no center pane, create our dummy + if not self.hasDummyPane: + self._createDummyPane() + + diff --git a/aui/tabart.py b/aui/tabart.py new file mode 100644 index 0000000..60b8e01 --- /dev/null +++ b/aui/tabart.py @@ -0,0 +1,2777 @@ +""" +Tab art provider code - a tab provider provides all drawing functionality to +the L{AuiNotebook}. This allows the L{AuiNotebook} to have a plugable look-and-feel. + +By default, a L{AuiNotebook} uses an instance of this class called L{AuiDefaultTabArt} +which provides bitmap art and a colour scheme that is adapted to the major platforms' +look. You can either derive from that class to alter its behaviour or write a +completely new tab art class. Call L{AuiNotebook.SetArtProvider} to make use this +new tab art. +""" + +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx + +if wx.Platform == '__WXMAC__': + import Carbon.Appearance + +from aui_utilities import BitmapFromBits, StepColour, IndentPressedBitmap, ChopText +from aui_utilities import GetBaseColour, DrawMACCloseButton, LightColour, TakeScreenShot +from aui_utilities import CopyAttributes + +from aui_constants import * + + +# -- GUI helper classes and functions -- +class AuiCommandCapture(wx.PyEvtHandler): + """ A class to handle the dropdown window menu. """ + + def __init__(self): + """ Default class constructor. """ + + wx.PyEvtHandler.__init__(self) + self._last_id = 0 + + + def GetCommandId(self): + """ Returns the event command identifier. """ + + return self._last_id + + + def ProcessEvent(self, event): + """ + Processes an event, searching event tables and calling zero or more suitable + event handler function(s). + + :param `event`: the event to process. + + :note: Normally, your application would not call this function: it is called + in the wxPython implementation to dispatch incoming user interface events + to the framework (and application). + However, you might need to call it if implementing new functionality (such as + a new control) where you define new event types, as opposed to allowing the + user to override functions. + + An instance where you might actually override the L{ProcessEvent} function is where + you want to direct event processing to event handlers not normally noticed by + wxPython. For example, in the document/view architecture, documents and views + are potential event handlers. When an event reaches a frame, L{ProcessEvent} will + need to be called on the associated document and view in case event handler + functions are associated with these objects. + + The normal order of event table searching is as follows: + + 1. If the object is disabled (via a call to `SetEvtHandlerEnabled`) the function + skips to step (6). + 2. If the object is a `wx.Window`, L{ProcessEvent} is recursively called on the window's + `wx.Validator`. If this returns ``True``, the function exits. + 3. wxWidgets `SearchEventTable` is called for this event handler. If this fails, the + base class table is tried, and so on until no more tables exist or an appropriate + function was found, in which case the function exits. + 4. The search is applied down the entire chain of event handlers (usually the chain + has a length of one). If this succeeds, the function exits. + 5. If the object is a `wx.Window` and the event is a `wx.CommandEvent`, L{ProcessEvent} is + recursively applied to the parent window's event handler. If this returns ``True``, + the function exits. + 6. Finally, L{ProcessEvent} is called on the `wx.App` object. + """ + + if event.GetEventType() == wx.wxEVT_COMMAND_MENU_SELECTED: + self._last_id = event.GetId() + return True + + if self.GetNextHandler(): + return self.GetNextHandler().ProcessEvent(event) + + return False + + +class AuiDefaultTabArt(object): + """ + Tab art provider code - a tab provider provides all drawing functionality to + the L{AuiNotebook}. This allows the L{AuiNotebook} to have a plugable look-and-feel. + + By default, a L{AuiNotebook} uses an instance of this class called L{AuiDefaultTabArt} + which provides bitmap art and a colour scheme that is adapted to the major platforms' + look. You can either derive from that class to alter its behaviour or write a + completely new tab art class. Call L{AuiNotebook.SetArtProvider} to make use this + new tab art. + """ + + def __init__(self): + """ Default class constructor. """ + + self._normal_font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font.SetWeight(wx.BOLD) + self._measuring_font = self._selected_font + + self._fixed_tab_width = 100 + self._tab_ctrl_height = 0 + self._buttonRect = wx.Rect() + + self.SetDefaultColours() + + if wx.Platform == "__WXMAC__": + bmp_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW) + self._active_close_bmp = DrawMACCloseButton(bmp_colour) + self._disabled_close_bmp = DrawMACCloseButton(wx.Colour(128, 128, 128)) + else: + self._active_close_bmp = BitmapFromBits(nb_close_bits, 16, 16, wx.BLACK) + self._disabled_close_bmp = BitmapFromBits(nb_close_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._hover_close_bmp = self._active_close_bmp + self._pressed_close_bmp = self._active_close_bmp + + self._active_left_bmp = BitmapFromBits(nb_left_bits, 16, 16, wx.BLACK) + self._disabled_left_bmp = BitmapFromBits(nb_left_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._active_right_bmp = BitmapFromBits(nb_right_bits, 16, 16, wx.BLACK) + self._disabled_right_bmp = BitmapFromBits(nb_right_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._active_windowlist_bmp = BitmapFromBits(nb_list_bits, 16, 16, wx.BLACK) + self._disabled_windowlist_bmp = BitmapFromBits(nb_list_bits, 16, 16, wx.Colour(128, 128, 128)) + + if wx.Platform == "__WXMAC__": + # Get proper highlight colour for focus rectangle from the + # current Mac theme. kThemeBrushFocusHighlight is + # available on Mac OS 8.5 and higher + if hasattr(wx, 'MacThemeColour'): + c = wx.MacThemeColour(Carbon.Appearance.kThemeBrushFocusHighlight) + else: + brush = wx.Brush(wx.BLACK) + brush.MacSetTheme(Carbon.Appearance.kThemeBrushFocusHighlight) + c = brush.GetColour() + self._focusPen = wx.Pen(c, 2, wx.SOLID) + else: + self._focusPen = wx.Pen(wx.BLACK, 1, wx.USER_DASH) + self._focusPen.SetDashes([1, 1]) + self._focusPen.SetCap(wx.CAP_BUTT) + + + def SetBaseColour(self, base_colour): + """ + Sets a new base colour. + + :param `base_colour`: an instance of `wx.Colour`. + """ + + self._base_colour = base_colour + self._base_colour_pen = wx.Pen(self._base_colour) + self._base_colour_brush = wx.Brush(self._base_colour) + + + def SetDefaultColours(self, base_colour=None): + """ + Sets the default colours, which are calculated from the given base colour. + + :param `base_colour`: an instance of `wx.Colour`. If defaulted to ``None``, a colour + is generated accordingly to the platform and theme. + """ + + if base_colour is None: + base_colour = GetBaseColour() + + self.SetBaseColour( base_colour ) + self._border_colour = StepColour(base_colour, 75) + self._border_pen = wx.Pen(self._border_colour) + + self._background_top_colour = StepColour(self._base_colour, 90) + self._background_bottom_colour = StepColour(self._base_colour, 170) + + self._tab_top_colour = self._base_colour + self._tab_bottom_colour = wx.WHITE + self._tab_gradient_highlight_colour = wx.WHITE + + self._tab_inactive_top_colour = self._base_colour + self._tab_inactive_bottom_colour = StepColour(self._tab_inactive_top_colour, 160) + + self._tab_text_colour = lambda page: page.text_colour + self._tab_disabled_text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT) + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def SetAGWFlags(self, agwFlags): + """ + Sets the tab art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + """ + + self._agwFlags = agwFlags + + + def GetAGWFlags(self): + """ + Returns the tab art flags. + + :see: L{SetAGWFlags} for a list of possible return values. + """ + + return self._agwFlags + + + def SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth): + """ + Sets the tab sizing information. + + :param `tab_ctrl_size`: the size of the tab control area; + :param `tab_count`: the number of tabs; + :param `minMaxTabWidth`: a tuple containing the minimum and maximum tab widths + to be used when the ``AUI_NB_TAB_FIXED_WIDTH`` style is active. + """ + + self._fixed_tab_width = 100 + minTabWidth, maxTabWidth = minMaxTabWidth + + tot_width = tab_ctrl_size.x - self.GetIndentSize() - 4 + agwFlags = self.GetAGWFlags() + + if agwFlags & AUI_NB_CLOSE_BUTTON: + tot_width -= self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_WINDOWLIST_BUTTON: + tot_width -= self._active_windowlist_bmp.GetWidth() + + if tab_count > 0: + self._fixed_tab_width = tot_width/tab_count + + if self._fixed_tab_width < 100: + self._fixed_tab_width = 100 + + if self._fixed_tab_width > tot_width/2: + self._fixed_tab_width = tot_width/2 + + if self._fixed_tab_width > 220: + self._fixed_tab_width = 220 + + if minTabWidth > -1: + self._fixed_tab_width = max(self._fixed_tab_width, minTabWidth) + if maxTabWidth > -1: + self._fixed_tab_width = min(self._fixed_tab_width, maxTabWidth) + + self._tab_ctrl_height = tab_ctrl_size.y + + + def DrawBackground(self, dc, wnd, rect): + """ + Draws the tab area background. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `rect`: the tab control rectangle. + """ + + self._buttonRect = wx.Rect() + + # draw background + agwFlags = self.GetAGWFlags() + if agwFlags & AUI_NB_BOTTOM: + r = wx.Rect(rect.x, rect.y, rect.width+2, rect.height) + + # TODO: else if (agwFlags & AUI_NB_LEFT) + # TODO: else if (agwFlags & AUI_NB_RIGHT) + else: #for AUI_NB_TOP + r = wx.Rect(rect.x, rect.y, rect.width+2, rect.height-3) + + dc.GradientFillLinear(r, self._background_top_colour, self._background_bottom_colour, wx.SOUTH) + + # draw base lines + + dc.SetPen(self._border_pen) + y = rect.GetHeight() + w = rect.GetWidth() + + if agwFlags & AUI_NB_BOTTOM: + dc.SetBrush(wx.Brush(self._background_bottom_colour)) + dc.DrawRectangle(-1, 0, w+2, 4) + + # TODO: else if (agwFlags & AUI_NB_LEFT) + # TODO: else if (agwFlags & AUI_NB_RIGHT) + + else: # for AUI_NB_TOP + dc.SetBrush(self._base_colour_brush) + dc.DrawRectangle(-1, y-4, w+2, 4) + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # if the caption is empty, measure some temporary text + caption = page.caption + if not caption: + caption = "Xj" + + dc.SetFont(self._selected_font) + selected_textx, selected_texty, dummy = dc.GetMultiLineTextExtent(caption) + + dc.SetFont(self._normal_font) + normal_textx, normal_texty, dummy = dc.GetMultiLineTextExtent(caption) + + control = page.control + + # figure out the size of the tab + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, + page.active, close_button_state, control) + + tab_height = self._tab_ctrl_height - 3 + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + + caption = page.caption + + # select pen, brush and font for the tab to be drawn + + if page.active: + + dc.SetFont(self._selected_font) + textx, texty = selected_textx, selected_texty + + else: + + dc.SetFont(self._normal_font) + textx, texty = normal_textx, normal_texty + + if not page.enabled: + dc.SetTextForeground(self._tab_disabled_text_colour) + pagebitmap = page.dis_bitmap + else: + dc.SetTextForeground(self._tab_text_colour(page)) + pagebitmap = page.bitmap + + # create points that will make the tab outline + + clip_width = tab_width + if tab_x + clip_width > in_rect.x + in_rect.width: + clip_width = in_rect.x + in_rect.width - tab_x + + # since the above code above doesn't play well with WXDFB or WXCOCOA, + # we'll just use a rectangle for the clipping region for now -- + dc.SetClippingRegion(tab_x, tab_y, clip_width+1, tab_height-3) + + border_points = [wx.Point() for i in xrange(6)] + agwFlags = self.GetAGWFlags() + + if agwFlags & AUI_NB_BOTTOM: + + border_points[0] = wx.Point(tab_x, tab_y) + border_points[1] = wx.Point(tab_x, tab_y+tab_height-6) + border_points[2] = wx.Point(tab_x+2, tab_y+tab_height-4) + border_points[3] = wx.Point(tab_x+tab_width-2, tab_y+tab_height-4) + border_points[4] = wx.Point(tab_x+tab_width, tab_y+tab_height-6) + border_points[5] = wx.Point(tab_x+tab_width, tab_y) + + else: #if (agwFlags & AUI_NB_TOP) + + border_points[0] = wx.Point(tab_x, tab_y+tab_height-4) + border_points[1] = wx.Point(tab_x, tab_y+2) + border_points[2] = wx.Point(tab_x+2, tab_y) + border_points[3] = wx.Point(tab_x+tab_width-2, tab_y) + border_points[4] = wx.Point(tab_x+tab_width, tab_y+2) + border_points[5] = wx.Point(tab_x+tab_width, tab_y+tab_height-4) + + # TODO: else if (agwFlags & AUI_NB_LEFT) + # TODO: else if (agwFlags & AUI_NB_RIGHT) + + drawn_tab_yoff = border_points[1].y + drawn_tab_height = border_points[0].y - border_points[1].y + + if page.active: + + # draw active tab + + # draw base background colour + r = wx.Rect(tab_x, tab_y, tab_width, tab_height) + dc.SetPen(self._base_colour_pen) + dc.SetBrush(self._base_colour_brush) + dc.DrawRectangle(r.x+1, r.y+1, r.width-1, r.height-4) + + # this white helps fill out the gradient at the top of the tab + dc.SetPen( wx.Pen(self._tab_gradient_highlight_colour) ) + dc.SetBrush( wx.Brush(self._tab_gradient_highlight_colour) ) + dc.DrawRectangle(r.x+2, r.y+1, r.width-3, r.height-4) + + # these two points help the rounded corners appear more antialiased + dc.SetPen(self._base_colour_pen) + dc.DrawPoint(r.x+2, r.y+1) + dc.DrawPoint(r.x+r.width-2, r.y+1) + + # set rectangle down a bit for gradient drawing + r.SetHeight(r.GetHeight()/2) + r.x += 2 + r.width -= 2 + r.y += r.height + r.y -= 2 + + # draw gradient background + top_colour = self._tab_bottom_colour + bottom_colour = self._tab_top_colour + dc.GradientFillLinear(r, bottom_colour, top_colour, wx.NORTH) + + else: + + # draw inactive tab + + r = wx.Rect(tab_x, tab_y+1, tab_width, tab_height-3) + + # start the gradent up a bit and leave the inside border inset + # by a pixel for a 3D look. Only the top half of the inactive + # tab will have a slight gradient + r.x += 3 + r.y += 1 + r.width -= 4 + r.height /= 2 + r.height -= 1 + + # -- draw top gradient fill for glossy look + top_colour = self._tab_inactive_top_colour + bottom_colour = self._tab_inactive_bottom_colour + dc.GradientFillLinear(r, bottom_colour, top_colour, wx.NORTH) + + r.y += r.height + r.y -= 1 + + # -- draw bottom fill for glossy look + top_colour = self._tab_inactive_bottom_colour + bottom_colour = self._tab_inactive_bottom_colour + dc.GradientFillLinear(r, top_colour, bottom_colour, wx.SOUTH) + + # draw tab outline + dc.SetPen(self._border_pen) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.DrawPolygon(border_points) + + # there are two horizontal grey lines at the bottom of the tab control, + # this gets rid of the top one of those lines in the tab control + if page.active: + + if agwFlags & AUI_NB_BOTTOM: + dc.SetPen(wx.Pen(self._background_bottom_colour)) + + # TODO: else if (agwFlags & AUI_NB_LEFT) + # TODO: else if (agwFlags & AUI_NB_RIGHT) + else: # for AUI_NB_TOP + dc.SetPen(self._base_colour_pen) + + dc.DrawLine(border_points[0].x+1, + border_points[0].y, + border_points[5].x, + border_points[5].y) + + text_offset = tab_x + 8 + close_button_width = 0 + + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset += close_button_width - 5 + + bitmap_offset = 0 + + if pagebitmap.IsOk(): + + bitmap_offset = tab_x + 8 + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width: + bitmap_offset += close_button_width - 5 + + # draw bitmap + dc.DrawBitmap(pagebitmap, + bitmap_offset, + drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2), + True) + + text_offset = bitmap_offset + pagebitmap.GetWidth() + text_offset += 3 # bitmap padding + + else: + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width: + text_offset = tab_x + 8 + + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width) + + ypos = drawn_tab_yoff + (drawn_tab_height)/2 - (texty/2) - 1 + + offset_focus = text_offset + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + textx += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + # draw focus rectangle + if (agwFlags & AUI_NB_NO_TAB_FOCUS) == 0: + self.DrawFocusRectangle(dc, page, wnd, draw_text, offset_focus, bitmap_offset, drawn_tab_yoff, drawn_tab_height, rectx, recty) + + out_button_rect = wx.Rect() + + # draw close button if necessary + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + bmp = self._disabled_close_bmp + + if close_button_state == AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif close_button_state == AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + + shift = (agwFlags & AUI_NB_BOTTOM and [1] or [0])[0] + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + 4, tab_y + (tab_height - bmp.GetHeight())/2 - shift, + close_button_width, tab_height) + else: + rect = wx.Rect(tab_x + tab_width - close_button_width - 1, + tab_y + (tab_height - bmp.GetHeight())/2 - shift, + close_button_width, tab_height) + + rect = IndentPressedBitmap(rect, close_button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + + out_button_rect = rect + + out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height) + + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + + def SetCustomButton(self, bitmap_id, button_state, bmp): + """ + Sets a custom bitmap for the close, left, right and window list + buttons. + + :param `bitmap_id`: the button identifier; + :param `button_state`: the button state; + :param `bmp`: the custom bitmap to use for the button. + """ + + if bitmap_id == AUI_BUTTON_CLOSE: + if button_state == AUI_BUTTON_STATE_NORMAL: + self._active_close_bmp = bmp + self._hover_close_bmp = self._active_close_bmp + self._pressed_close_bmp = self._active_close_bmp + self._disabled_close_bmp = self._active_close_bmp + + elif button_state == AUI_BUTTON_STATE_HOVER: + self._hover_close_bmp = bmp + elif button_state == AUI_BUTTON_STATE_PRESSED: + self._pressed_close_bmp = bmp + else: + self._disabled_close_bmp = bmp + + elif bitmap_id == AUI_BUTTON_LEFT: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_left_bmp = bmp + else: + self._active_left_bmp = bmp + + elif bitmap_id == AUI_BUTTON_RIGHT: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_right_bmp = bmp + else: + self._active_right_bmp = bmp + + elif bitmap_id == AUI_BUTTON_WINDOWLIST: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_windowlist_bmp = bmp + else: + self._active_windowlist_bmp = bmp + + + def GetIndentSize(self): + """ Returns the tabs indent size. """ + + return 5 + + + def GetTabSize(self, dc, wnd, caption, bitmap, active, close_button_state, control=None): + """ + Returns the tab size for the given caption, bitmap and button state. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `caption`: the tab text caption; + :param `bitmap`: the bitmap displayed on the tab; + :param `active`: whether the tab is selected or not; + :param `close_button_state`: the state of the close button on the tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + dc.SetFont(self._measuring_font) + measured_textx, measured_texty, dummy = dc.GetMultiLineTextExtent(caption) + + # add padding around the text + tab_width = measured_textx + tab_height = measured_texty + + # if the close button is showing, add space for it + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + tab_width += self._active_close_bmp.GetWidth() + 3 + + # if there's a bitmap, add space for it + if bitmap.IsOk(): + tab_width += bitmap.GetWidth() + tab_width += 3 # right side bitmap padding + tab_height = max(tab_height, bitmap.GetHeight()) + + # add padding + tab_width += 16 + tab_height += 10 + + agwFlags = self.GetAGWFlags() + if agwFlags & AUI_NB_TAB_FIXED_WIDTH: + tab_width = self._fixed_tab_width + + if control is not None: + tab_width += control.GetSize().GetWidth() + 4 + + x_extent = tab_width + + return (tab_width, tab_height), x_extent + + + def DrawButton(self, dc, wnd, in_rect, button, orientation): + """ + Draws a button on the tab or on the tab area, depending on the button identifier. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `in_rect`: rectangle the tab should be confined to; + :param `button`: an instance of the button class; + :param `orientation`: the tab orientation. + """ + + bitmap_id, button_state = button.id, button.cur_state + + if bitmap_id == AUI_BUTTON_CLOSE: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_close_bmp + elif button_state & AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif button_state & AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + else: + bmp = self._active_close_bmp + + elif bitmap_id == AUI_BUTTON_LEFT: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_left_bmp + else: + bmp = self._active_left_bmp + + elif bitmap_id == AUI_BUTTON_RIGHT: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_right_bmp + else: + bmp = self._active_right_bmp + + elif bitmap_id == AUI_BUTTON_WINDOWLIST: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_windowlist_bmp + else: + bmp = self._active_windowlist_bmp + + else: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = button.dis_bitmap + else: + bmp = button.bitmap + + if not bmp.IsOk(): + return + + rect = wx.Rect(*in_rect) + + if orientation == wx.LEFT: + + rect.SetX(in_rect.x) + rect.SetY(((in_rect.y + in_rect.height)/2) - (bmp.GetHeight()/2)) + rect.SetWidth(bmp.GetWidth()) + rect.SetHeight(bmp.GetHeight()) + + else: + + rect = wx.Rect(in_rect.x + in_rect.width - bmp.GetWidth(), + ((in_rect.y + in_rect.height)/2) - (bmp.GetHeight()/2), + bmp.GetWidth(), bmp.GetHeight()) + + rect = IndentPressedBitmap(rect, button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + + out_rect = rect + + if bitmap_id == AUI_BUTTON_RIGHT: + self._buttonRect = wx.Rect(rect.x, rect.y, 30, rect.height) + + return out_rect + + + def DrawFocusRectangle(self, dc, page, wnd, draw_text, text_offset, bitmap_offset, drawn_tab_yoff, drawn_tab_height, textx, texty): + """ + Draws the focus rectangle on a tab. + + :param `dc`: a `wx.DC` device context; + :param `page`: the page associated with the tab; + :param `wnd`: a `wx.Window` instance object; + :param `draw_text`: the text that has been drawn on the tab; + :param `text_offset`: the text offset on the tab; + :param `bitmap_offset`: the bitmap offset on the tab; + :param `drawn_tab_yoff`: the y offset of the tab text; + :param `drawn_tab_height`: the height of the tab; + :param `textx`: the x text extent; + :param `texty`: the y text extent. + """ + + if self.GetAGWFlags() & AUI_NB_NO_TAB_FOCUS: + return + + if page.active and wx.Window.FindFocus() == wnd: + + focusRectText = wx.Rect(text_offset, (drawn_tab_yoff + (drawn_tab_height)/2 - (texty/2)), + textx, texty) + + if page.bitmap.IsOk(): + focusRectBitmap = wx.Rect(bitmap_offset, drawn_tab_yoff + (drawn_tab_height/2) - (page.bitmap.GetHeight()/2), + page.bitmap.GetWidth(), page.bitmap.GetHeight()) + + if page.bitmap.IsOk() and draw_text == "": + focusRect = wx.Rect(*focusRectBitmap) + elif not page.bitmap.IsOk() and draw_text != "": + focusRect = wx.Rect(*focusRectText) + elif page.bitmap.IsOk() and draw_text != "": + focusRect = focusRectText.Union(focusRectBitmap) + + focusRect.Inflate(2, 2) + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(self._focusPen) + dc.DrawRoundedRectangleRect(focusRect, 2) + + + def GetBestTabCtrlSize(self, wnd, pages, required_bmp_size): + """ + Returns the best tab control size. + + :param `wnd`: a `wx.Window` instance object; + :param `pages`: the pages associated with the tabs; + :param `required_bmp_size`: the size of the bitmap on the tabs. + """ + + dc = wx.ClientDC(wnd) + dc.SetFont(self._measuring_font) + + # sometimes a standard bitmap size needs to be enforced, especially + # if some tabs have bitmaps and others don't. This is important because + # it prevents the tab control from resizing when tabs are added. + + measure_bmp = wx.NullBitmap + + if required_bmp_size.IsFullySpecified(): + measure_bmp = wx.EmptyBitmap(required_bmp_size.x, + required_bmp_size.y) + + max_y = 0 + + for page in pages: + + if measure_bmp.IsOk(): + bmp = measure_bmp + else: + bmp = page.bitmap + + # we don't use the caption text because we don't + # want tab heights to be different in the case + # of a very short piece of text on one tab and a very + # tall piece of text on another tab + s, x_ext = self.GetTabSize(dc, wnd, page.caption, bmp, True, AUI_BUTTON_STATE_HIDDEN, None) + max_y = max(max_y, s[1]) + + if page.control: + controlW, controlH = page.control.GetSize() + max_y = max(max_y, controlH+4) + + return max_y + 2 + + + def SetNormalFont(self, font): + """ + Sets the normal font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._normal_font = font + + + def SetSelectedFont(self, font): + """ + Sets the selected tab font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._selected_font = font + + + def SetMeasuringFont(self, font): + """ + Sets the font for calculating text measurements. + + :param `font`: a `wx.Font` object. + """ + + self._measuring_font = font + + + def GetNormalFont(self): + """ Returns the normal font for drawing tab labels. """ + + return self._normal_font + + + def GetSelectedFont(self): + """ Returns the selected tab font for drawing tab labels. """ + + return self._selected_font + + + def GetMeasuringFont(self): + """ Returns the font for calculating text measurements. """ + + return self._measuring_font + + + def ShowDropDown(self, wnd, pages, active_idx): + """ + Shows the drop-down window menu on the tab area. + + :param `wnd`: a `wx.Window` derived window instance; + :param `pages`: the pages associated with the tabs; + :param `active_idx`: the active tab index. + """ + + useImages = self.GetAGWFlags() & AUI_NB_USE_IMAGES_DROPDOWN + menuPopup = wx.Menu() + + longest = 0 + for i, page in enumerate(pages): + + caption = page.caption + + # if there is no caption, make it a space. This will prevent + # an assert in the menu code. + if caption == "": + caption = " " + + # Save longest caption width for calculating menu width with + width = wnd.GetTextExtent(caption)[0] + if width > longest: + longest = width + + if useImages: + menuItem = wx.MenuItem(menuPopup, 1000+i, caption) + if page.bitmap: + menuItem.SetBitmap(page.bitmap) + + menuPopup.AppendItem(menuItem) + + else: + + menuPopup.AppendCheckItem(1000+i, caption) + + menuPopup.Enable(1000+i, page.enabled) + + if active_idx != -1 and not useImages: + + menuPopup.Check(1000+active_idx, True) + + # find out the screen coordinate at the bottom of the tab ctrl + cli_rect = wnd.GetClientRect() + + # Calculate the approximate size of the popupmenu for setting the + # position of the menu when its shown. + # Account for extra padding on left/right of text on mac menus + if wx.Platform in ['__WXMAC__', '__WXMSW__']: + longest += 32 + + # Bitmap/Checkmark width + padding + longest += 20 + + if self.GetAGWFlags() & AUI_NB_CLOSE_BUTTON: + longest += 16 + + pt = wx.Point(cli_rect.x + cli_rect.GetWidth() - longest, + cli_rect.y + cli_rect.height) + + cc = AuiCommandCapture() + wnd.PushEventHandler(cc) + wnd.PopupMenu(menuPopup, pt) + command = cc.GetCommandId() + wnd.PopEventHandler(True) + + if command >= 1000: + return command - 1000 + + return -1 + + +class AuiSimpleTabArt(object): + """ A simple-looking implementation of a tab art. """ + + def __init__(self): + """ Default class constructor. """ + + self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) + self._selected_font.SetWeight(wx.BOLD) + self._measuring_font = self._selected_font + + self._agwFlags = 0 + self._fixed_tab_width = 100 + + base_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + + background_colour = base_colour + normaltab_colour = base_colour + selectedtab_colour = wx.WHITE + + self._bkbrush = wx.Brush(background_colour) + self._normal_bkbrush = wx.Brush(normaltab_colour) + self._normal_bkpen = wx.Pen(normaltab_colour) + self._selected_bkbrush = wx.Brush(selectedtab_colour) + self._selected_bkpen = wx.Pen(selectedtab_colour) + + self._active_close_bmp = BitmapFromBits(nb_close_bits, 16, 16, wx.BLACK) + self._disabled_close_bmp = BitmapFromBits(nb_close_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._active_left_bmp = BitmapFromBits(nb_left_bits, 16, 16, wx.BLACK) + self._disabled_left_bmp = BitmapFromBits(nb_left_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._active_right_bmp = BitmapFromBits(nb_right_bits, 16, 16, wx.BLACK) + self._disabled_right_bmp = BitmapFromBits(nb_right_bits, 16, 16, wx.Colour(128, 128, 128)) + + self._active_windowlist_bmp = BitmapFromBits(nb_list_bits, 16, 16, wx.BLACK) + self._disabled_windowlist_bmp = BitmapFromBits(nb_list_bits, 16, 16, wx.Colour(128, 128, 128)) + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def SetAGWFlags(self, agwFlags): + """ + Sets the tab art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + """ + + self._agwFlags = agwFlags + + + def GetAGWFlags(self): + """ + Returns the tab art flags. + + :see: L{SetAGWFlags} for a list of possible return values. + """ + + return self._agwFlags + + + def SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth): + """ + Sets the tab sizing information. + + :param `tab_ctrl_size`: the size of the tab control area; + :param `tab_count`: the number of tabs; + :param `minMaxTabWidth`: a tuple containing the minimum and maximum tab widths + to be used when the ``AUI_NB_TAB_FIXED_WIDTH`` style is active. + """ + + self._fixed_tab_width = 100 + minTabWidth, maxTabWidth = minMaxTabWidth + + tot_width = tab_ctrl_size.x - self.GetIndentSize() - 4 + + if self._agwFlags & AUI_NB_CLOSE_BUTTON: + tot_width -= self._active_close_bmp.GetWidth() + if self._agwFlags & AUI_NB_WINDOWLIST_BUTTON: + tot_width -= self._active_windowlist_bmp.GetWidth() + + if tab_count > 0: + self._fixed_tab_width = tot_width/tab_count + + if self._fixed_tab_width < 100: + self._fixed_tab_width = 100 + + if self._fixed_tab_width > tot_width/2: + self._fixed_tab_width = tot_width/2 + + if self._fixed_tab_width > 220: + self._fixed_tab_width = 220 + + if minTabWidth > -1: + self._fixed_tab_width = max(self._fixed_tab_width, minTabWidth) + if maxTabWidth > -1: + self._fixed_tab_width = min(self._fixed_tab_width, maxTabWidth) + + self._tab_ctrl_height = tab_ctrl_size.y + + + def DrawBackground(self, dc, wnd, rect): + """ + Draws the tab area background. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `rect`: the tab control rectangle. + """ + + # draw background + dc.SetBrush(self._bkbrush) + dc.SetPen(wx.TRANSPARENT_PEN) + dc.DrawRectangle(-1, -1, rect.GetWidth()+2, rect.GetHeight()+2) + + # draw base line + dc.SetPen(wx.GREY_PEN) + dc.DrawLine(0, rect.GetHeight()-1, rect.GetWidth(), rect.GetHeight()-1) + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # if the caption is empty, measure some temporary text + caption = page.caption + if caption == "": + caption = "Xj" + + agwFlags = self.GetAGWFlags() + + dc.SetFont(self._selected_font) + selected_textx, selected_texty, dummy = dc.GetMultiLineTextExtent(caption) + + dc.SetFont(self._normal_font) + normal_textx, normal_texty, dummy = dc.GetMultiLineTextExtent(caption) + + control = page.control + + # figure out the size of the tab + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, + page.active, close_button_state, control) + + tab_height = tab_size[1] + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + + caption = page.caption + # select pen, brush and font for the tab to be drawn + + if page.active: + + dc.SetPen(self._selected_bkpen) + dc.SetBrush(self._selected_bkbrush) + dc.SetFont(self._selected_font) + textx = selected_textx + texty = selected_texty + + else: + + dc.SetPen(self._normal_bkpen) + dc.SetBrush(self._normal_bkbrush) + dc.SetFont(self._normal_font) + textx = normal_textx + texty = normal_texty + + if not page.enabled: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + else: + dc.SetTextForeground(page.text_colour) + + # -- draw line -- + + points = [wx.Point() for i in xrange(7)] + points[0].x = tab_x + points[0].y = tab_y + tab_height - 1 + points[1].x = tab_x + tab_height - 3 + points[1].y = tab_y + 2 + points[2].x = tab_x + tab_height + 3 + points[2].y = tab_y + points[3].x = tab_x + tab_width - 2 + points[3].y = tab_y + points[4].x = tab_x + tab_width + points[4].y = tab_y + 2 + points[5].x = tab_x + tab_width + points[5].y = tab_y + tab_height - 1 + points[6] = points[0] + + dc.SetClippingRect(in_rect) + dc.DrawPolygon(points) + + dc.SetPen(wx.GREY_PEN) + dc.DrawLines(points) + + close_button_width = 0 + + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + close_button_width = self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + if control: + text_offset = tab_x + (tab_height/2) + close_button_width - (textx/2) - 2 + else: + text_offset = tab_x + (tab_height/2) + ((tab_width+close_button_width)/2) - (textx/2) - 2 + else: + if control: + text_offset = tab_x + (tab_height/2) + close_button_width - (textx/2) + else: + text_offset = tab_x + (tab_height/2) + ((tab_width-close_button_width)/2) - (textx/2) + + else: + + text_offset = tab_x + (tab_height/3) + (tab_width/2) - (textx/2) + if control: + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset = tab_x + (tab_height/3) - (textx/2) + close_button_width + 2 + else: + text_offset = tab_x + (tab_height/3) - (textx/2) + + # set minimum text offset + if text_offset < tab_x + tab_height: + text_offset = tab_x + tab_height + + # chop text if necessary + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x)) + else: + draw_text = ChopText(dc, caption, + tab_width - (text_offset-tab_x) - close_button_width) + + ypos = (tab_y + tab_height)/2 - (texty/2) + 1 + + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + # draw focus rectangle + if page.active and wx.Window.FindFocus() == wnd and (agwFlags & AUI_NB_NO_TAB_FOCUS) == 0: + + focusRect = wx.Rect(text_offset, ((tab_y + tab_height)/2 - (texty/2) + 1), + selected_textx, selected_texty) + + focusRect.Inflate(2, 2) + # TODO: + # This should be uncommented when DrawFocusRect will become + # available in wxPython + # wx.RendererNative.Get().DrawFocusRect(wnd, dc, focusRect, 0) + + out_button_rect = wx.Rect() + # draw close button if necessary + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + if page.active: + bmp = self._active_close_bmp + else: + bmp = self._disabled_close_bmp + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + tab_height - 2, + tab_y + (tab_height/2) - (bmp.GetHeight()/2) + 1, + close_button_width, tab_height - 1) + else: + rect = wx.Rect(tab_x + tab_width - close_button_width - 1, + tab_y + (tab_height/2) - (bmp.GetHeight()/2) + 1, + close_button_width, tab_height - 1) + + self.DrawButtons(dc, rect, bmp, wx.WHITE, close_button_state) + out_button_rect = wx.Rect(*rect) + + out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height) + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + + def DrawButtons(self, dc, _rect, bmp, bkcolour, button_state): + """ + Convenience method to draw tab buttons. + + :param `dc`: a `wx.DC` device context; + :param `_rect`: the tab rectangle; + :param `bmp`: the tab bitmap; + :param `bkcolour`: the tab background colour; + :param `button_state`: the state of the tab button. + """ + + rect = wx.Rect(*_rect) + + if button_state == AUI_BUTTON_STATE_PRESSED: + rect.x += 1 + rect.y += 1 + + if button_state in [AUI_BUTTON_STATE_HOVER, AUI_BUTTON_STATE_PRESSED]: + dc.SetBrush(wx.Brush(StepColour(bkcolour, 120))) + dc.SetPen(wx.Pen(StepColour(bkcolour, 75))) + + # draw the background behind the button + dc.DrawRectangle(rect.x, rect.y, 15, 15) + + # draw the button itself + dc.DrawBitmap(bmp, rect.x, rect.y, True) + + + def GetIndentSize(self): + """ Returns the tabs indent size. """ + + return 0 + + + def GetTabSize(self, dc, wnd, caption, bitmap, active, close_button_state, control=None): + """ + Returns the tab size for the given caption, bitmap and button state. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `caption`: the tab text caption; + :param `bitmap`: the bitmap displayed on the tab; + :param `active`: whether the tab is selected or not; + :param `close_button_state`: the state of the close button on the tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + dc.SetFont(self._measuring_font) + measured_textx, measured_texty, dummy = dc.GetMultiLineTextExtent(caption) + + tab_height = measured_texty + 4 + tab_width = measured_textx + tab_height + 5 + + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + tab_width += self._active_close_bmp.GetWidth() + + if self._agwFlags & AUI_NB_TAB_FIXED_WIDTH: + tab_width = self._fixed_tab_width + + if control is not None: + controlW, controlH = control.GetSize() + tab_width += controlW + 4 + + x_extent = tab_width - (tab_height/2) - 1 + + return (tab_width, tab_height), x_extent + + + def DrawButton(self, dc, wnd, in_rect, button, orientation): + """ + Draws a button on the tab or on the tab area, depending on the button identifier. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `in_rect`: rectangle the tab should be confined to; + :param `button`: an instance of the button class; + :param `orientation`: the tab orientation. + """ + + bitmap_id, button_state = button.id, button.cur_state + + if bitmap_id == AUI_BUTTON_CLOSE: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_close_bmp + else: + bmp = self._active_close_bmp + + elif bitmap_id == AUI_BUTTON_LEFT: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_left_bmp + else: + bmp = self._active_left_bmp + + elif bitmap_id == AUI_BUTTON_RIGHT: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_right_bmp + else: + bmp = self._active_right_bmp + + elif bitmap_id == AUI_BUTTON_WINDOWLIST: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = self._disabled_windowlist_bmp + else: + bmp = self._active_windowlist_bmp + + else: + if button_state & AUI_BUTTON_STATE_DISABLED: + bmp = button.dis_bitmap + else: + bmp = button.bitmap + + if not bmp.IsOk(): + return + + rect = wx.Rect(*in_rect) + + if orientation == wx.LEFT: + + rect.SetX(in_rect.x) + rect.SetY(((in_rect.y + in_rect.height)/2) - (bmp.GetHeight()/2)) + rect.SetWidth(bmp.GetWidth()) + rect.SetHeight(bmp.GetHeight()) + + else: + + rect = wx.Rect(in_rect.x + in_rect.width - bmp.GetWidth(), + ((in_rect.y + in_rect.height)/2) - (bmp.GetHeight()/2), + bmp.GetWidth(), bmp.GetHeight()) + + self.DrawButtons(dc, rect, bmp, wx.WHITE, button_state) + + out_rect = wx.Rect(*rect) + return out_rect + + + def ShowDropDown(self, wnd, pages, active_idx): + """ + Shows the drop-down window menu on the tab area. + + :param `wnd`: a `wx.Window` derived window instance; + :param `pages`: the pages associated with the tabs; + :param `active_idx`: the active tab index. + """ + + menuPopup = wx.Menu() + useImages = self.GetAGWFlags() & AUI_NB_USE_IMAGES_DROPDOWN + + for i, page in enumerate(pages): + + if useImages: + menuItem = wx.MenuItem(menuPopup, 1000+i, page.caption) + if page.bitmap: + menuItem.SetBitmap(page.bitmap) + + menuPopup.AppendItem(menuItem) + + else: + + menuPopup.AppendCheckItem(1000+i, page.caption) + + menuPopup.Enable(1000+i, page.enabled) + + if active_idx != -1 and not useImages: + menuPopup.Check(1000+active_idx, True) + + # find out where to put the popup menu of window + # items. Subtract 100 for now to center the menu + # a bit, until a better mechanism can be implemented + pt = wx.GetMousePosition() + pt = wnd.ScreenToClient(pt) + + if pt.x < 100: + pt.x = 0 + else: + pt.x -= 100 + + # find out the screen coordinate at the bottom of the tab ctrl + cli_rect = wnd.GetClientRect() + pt.y = cli_rect.y + cli_rect.height + + cc = AuiCommandCapture() + wnd.PushEventHandler(cc) + wnd.PopupMenu(menuPopup, pt) + command = cc.GetCommandId() + wnd.PopEventHandler(True) + + if command >= 1000: + return command-1000 + + return -1 + + + def GetBestTabCtrlSize(self, wnd, pages, required_bmp_size): + """ + Returns the best tab control size. + + :param `wnd`: a `wx.Window` instance object; + :param `pages`: the pages associated with the tabs; + :param `required_bmp_size`: the size of the bitmap on the tabs. + """ + + dc = wx.ClientDC(wnd) + dc.SetFont(self._measuring_font) + s, x_extent = self.GetTabSize(dc, wnd, "ABCDEFGHIj", wx.NullBitmap, True, + AUI_BUTTON_STATE_HIDDEN, None) + + max_y = s[1] + + for page in pages: + if page.control: + controlW, controlH = page.control.GetSize() + max_y = max(max_y, controlH+4) + + textx, texty, dummy = dc.GetMultiLineTextExtent(page.caption) + max_y = max(max_y, texty) + + return max_y + 3 + + + def SetNormalFont(self, font): + """ + Sets the normal font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._normal_font = font + + + def SetSelectedFont(self, font): + """ + Sets the selected tab font for drawing tab labels. + + :param `font`: a `wx.Font` object. + """ + + self._selected_font = font + + + def SetMeasuringFont(self, font): + """ + Sets the font for calculating text measurements. + + :param `font`: a `wx.Font` object. + """ + + self._measuring_font = font + + + def GetNormalFont(self): + """ Returns the normal font for drawing tab labels. """ + + return self._normal_font + + + def GetSelectedFont(self): + """ Returns the selected tab font for drawing tab labels. """ + + return self._selected_font + + + def GetMeasuringFont(self): + """ Returns the font for calculating text measurements. """ + + return self._measuring_font + + + def SetCustomButton(self, bitmap_id, button_state, bmp): + """ + Sets a custom bitmap for the close, left, right and window list + buttons. + + :param `bitmap_id`: the button identifier; + :param `button_state`: the button state; + :param `bmp`: the custom bitmap to use for the button. + """ + + if bitmap_id == AUI_BUTTON_CLOSE: + if button_state == AUI_BUTTON_STATE_NORMAL: + self._active_close_bmp = bmp + self._hover_close_bmp = self._active_close_bmp + self._pressed_close_bmp = self._active_close_bmp + self._disabled_close_bmp = self._active_close_bmp + + elif button_state == AUI_BUTTON_STATE_HOVER: + self._hover_close_bmp = bmp + elif button_state == AUI_BUTTON_STATE_PRESSED: + self._pressed_close_bmp = bmp + else: + self._disabled_close_bmp = bmp + + elif bitmap_id == AUI_BUTTON_LEFT: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_left_bmp = bmp + else: + self._active_left_bmp = bmp + + elif bitmap_id == AUI_BUTTON_RIGHT: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_right_bmp = bmp + else: + self._active_right_bmp = bmp + + elif bitmap_id == AUI_BUTTON_WINDOWLIST: + if button_state & AUI_BUTTON_STATE_DISABLED: + self._disabled_windowlist_bmp = bmp + else: + self._active_windowlist_bmp = bmp + + +class VC71TabArt(AuiDefaultTabArt): + """ A class to draw tabs using the Visual Studio 2003 (VC71) style. """ + + def __init__(self): + """ Default class constructor. """ + + AuiDefaultTabArt.__init__(self) + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # Visual studio 7.1 style + # This code is based on the renderer included in FlatNotebook + + # figure out the size of the tab + + control = page.control + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, + close_button_state, control) + + tab_height = self._tab_ctrl_height - 3 + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + clip_width = tab_width + + if tab_x + clip_width > in_rect.x + in_rect.width - 4: + clip_width = (in_rect.x + in_rect.width) - tab_x - 4 + + dc.SetClippingRegion(tab_x, tab_y, clip_width + 1, tab_height - 3) + agwFlags = self.GetAGWFlags() + + if agwFlags & AUI_NB_BOTTOM: + tab_y -= 1 + + dc.SetPen((page.active and [wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DHIGHLIGHT))] or \ + [wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW))])[0]) + dc.SetBrush((page.active and [wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))] or \ + [wx.TRANSPARENT_BRUSH])[0]) + + if page.active: + + tabH = tab_height - 2 + dc.DrawRectangle(tab_x, tab_y, tab_width, tabH) + + rightLineY1 = (agwFlags & AUI_NB_BOTTOM and [vertical_border_padding - 2] or \ + [vertical_border_padding - 1])[0] + rightLineY2 = tabH + 3 + dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DSHADOW))) + dc.DrawLine(tab_x + tab_width - 1, rightLineY1 + 1, tab_x + tab_width - 1, rightLineY2) + + if agwFlags & AUI_NB_BOTTOM: + dc.DrawLine(tab_x + 1, rightLineY2 - 3 , tab_x + tab_width - 1, rightLineY2 - 3) + + dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))) + dc.DrawLine(tab_x + tab_width, rightLineY1, tab_x + tab_width, rightLineY2) + + if agwFlags & AUI_NB_BOTTOM: + dc.DrawLine(tab_x, rightLineY2 - 2, tab_x + tab_width, rightLineY2 - 2) + + else: + + # We dont draw a rectangle for non selected tabs, but only + # vertical line on the right + blackLineY1 = (agwFlags & AUI_NB_BOTTOM and [vertical_border_padding + 2] or \ + [vertical_border_padding + 1])[0] + blackLineY2 = tab_height - 5 + dc.DrawLine(tab_x + tab_width, blackLineY1, tab_x + tab_width, blackLineY2) + + border_points = [0, 0] + + if agwFlags & AUI_NB_BOTTOM: + + border_points[0] = wx.Point(tab_x, tab_y) + border_points[1] = wx.Point(tab_x, tab_y + tab_height - 6) + + else: # if (agwFlags & AUI_NB_TOP) + + border_points[0] = wx.Point(tab_x, tab_y + tab_height - 4) + border_points[1] = wx.Point(tab_x, tab_y + 2) + + drawn_tab_yoff = border_points[1].y + drawn_tab_height = border_points[0].y - border_points[1].y + + text_offset = tab_x + 8 + close_button_width = 0 + + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset += close_button_width - 5 + + if not page.enabled: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + pagebitmap = page.dis_bitmap + else: + dc.SetTextForeground(page.text_colour) + pagebitmap = page.bitmap + + shift = 0 + if agwFlags & AUI_NB_BOTTOM: + shift = (page.active and [1] or [2])[0] + + bitmap_offset = 0 + if pagebitmap.IsOk(): + bitmap_offset = tab_x + 8 + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width: + bitmap_offset += close_button_width - 5 + + # draw bitmap + dc.DrawBitmap(pagebitmap, bitmap_offset, + drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2) + shift, + True) + + text_offset = bitmap_offset + pagebitmap.GetWidth() + text_offset += 3 # bitmap padding + + else: + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width: + text_offset = tab_x + 8 + + # if the caption is empty, measure some temporary text + caption = page.caption + + if caption == "": + caption = "Xj" + + if page.active: + dc.SetFont(self._selected_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + else: + dc.SetFont(self._normal_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width) + + ypos = drawn_tab_yoff + (drawn_tab_height)/2 - (texty/2) - 1 + shift + + offset_focus = text_offset + + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + textx += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + out_button_rect = wx.Rect() + + # draw focus rectangle + if (agwFlags & AUI_NB_NO_TAB_FOCUS) == 0: + self.DrawFocusRectangle(dc, page, wnd, draw_text, offset_focus, bitmap_offset, drawn_tab_yoff+shift, + drawn_tab_height+shift, rectx, recty) + + # draw 'x' on tab (if enabled) + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + + bmp = self._disabled_close_bmp + + if close_button_state == AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif close_button_state == AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + 4, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + else: + rect = wx.Rect(tab_x + tab_width - close_button_width - 3, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + + # Indent the button if it is pressed down: + rect = IndentPressedBitmap(rect, close_button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + + out_button_rect = rect + + out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height) + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + +class FF2TabArt(AuiDefaultTabArt): + """ A class to draw tabs using the Firefox 2 (FF2) style. """ + + def __init__(self): + """ Default class constructor. """ + + AuiDefaultTabArt.__init__(self) + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def GetTabSize(self, dc, wnd, caption, bitmap, active, close_button_state, control): + """ + Returns the tab size for the given caption, bitmap and button state. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `caption`: the tab text caption; + :param `bitmap`: the bitmap displayed on the tab; + :param `active`: whether the tab is selected or not; + :param `close_button_state`: the state of the close button on the tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + tab_size, x_extent = AuiDefaultTabArt.GetTabSize(self, dc, wnd, caption, bitmap, + active, close_button_state, control) + + tab_width, tab_height = tab_size + + # add some vertical padding + tab_height += 2 + + return (tab_width, tab_height), x_extent + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # Firefox 2 style + + control = page.control + + # figure out the size of the tab + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, + page.active, close_button_state, control) + + tab_height = self._tab_ctrl_height - 2 + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + + clip_width = tab_width + if tab_x + clip_width > in_rect.x + in_rect.width - 4: + clip_width = (in_rect.x + in_rect.width) - tab_x - 4 + + dc.SetClippingRegion(tab_x, tab_y, clip_width + 1, tab_height - 3) + + tabPoints = [wx.Point() for i in xrange(7)] + + adjust = 0 + if not page.active: + adjust = 1 + + agwFlags = self.GetAGWFlags() + + tabPoints[0].x = tab_x + 3 + tabPoints[0].y = (agwFlags & AUI_NB_BOTTOM and [3] or [tab_height - 2])[0] + + tabPoints[1].x = tabPoints[0].x + tabPoints[1].y = (agwFlags & AUI_NB_BOTTOM and [tab_height - (vertical_border_padding + 2) - adjust] or \ + [(vertical_border_padding + 2) + adjust])[0] + + tabPoints[2].x = tabPoints[1].x+2 + tabPoints[2].y = (agwFlags & AUI_NB_BOTTOM and [tab_height - vertical_border_padding - adjust] or \ + [vertical_border_padding + adjust])[0] + + tabPoints[3].x = tab_x + tab_width - 2 + tabPoints[3].y = tabPoints[2].y + + tabPoints[4].x = tabPoints[3].x + 2 + tabPoints[4].y = tabPoints[1].y + + tabPoints[5].x = tabPoints[4].x + tabPoints[5].y = tabPoints[0].y + + tabPoints[6].x = tabPoints[0].x + tabPoints[6].y = tabPoints[0].y + + rr = wx.RectPP(tabPoints[2], tabPoints[5]) + self.DrawTabBackground(dc, rr, page.active, (agwFlags & AUI_NB_BOTTOM) == 0) + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))) + + # Draw the tab as rounded rectangle + dc.DrawPolygon(tabPoints) + + if page.active: + dc.DrawLine(tabPoints[0].x + 1, tabPoints[0].y, tabPoints[5].x , tabPoints[0].y) + + drawn_tab_yoff = tabPoints[1].y + drawn_tab_height = tabPoints[0].y - tabPoints[2].y + + text_offset = tab_x + 8 + close_button_width = 0 + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset += close_button_width - 4 + + if not page.enabled: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + pagebitmap = page.dis_bitmap + else: + dc.SetTextForeground(page.text_colour) + pagebitmap = page.bitmap + + shift = -1 + if agwFlags & AUI_NB_BOTTOM: + shift = 2 + + bitmap_offset = 0 + if pagebitmap.IsOk(): + bitmap_offset = tab_x + 8 + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width: + bitmap_offset += close_button_width - 4 + + # draw bitmap + dc.DrawBitmap(pagebitmap, bitmap_offset, + drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2) + shift, + True) + + text_offset = bitmap_offset + pagebitmap.GetWidth() + text_offset += 3 # bitmap padding + + else: + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width: + text_offset = tab_x + 8 + + # if the caption is empty, measure some temporary text + caption = page.caption + if caption == "": + caption = "Xj" + + if page.active: + dc.SetFont(self._selected_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + else: + dc.SetFont(self._normal_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width + 1) + else: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width) + + ypos = drawn_tab_yoff + drawn_tab_height/2 - texty/2 - 1 + shift + + offset_focus = text_offset + + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + textx += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + # draw focus rectangle + if (agwFlags & AUI_NB_NO_TAB_FOCUS) == 0: + self.DrawFocusRectangle(dc, page, wnd, draw_text, offset_focus, bitmap_offset, drawn_tab_yoff+shift, + drawn_tab_height, rectx, recty) + + out_button_rect = wx.Rect() + # draw 'x' on tab (if enabled) + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + close_button_width = self._active_close_bmp.GetWidth() + bmp = self._disabled_close_bmp + + if close_button_state == AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif close_button_state == AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + 5, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + else: + rect = wx.Rect(tab_x + tab_width - close_button_width - 3, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + + # Indent the button if it is pressed down: + rect = IndentPressedBitmap(rect, close_button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + out_button_rect = rect + + out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height) + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + + def DrawTabBackground(self, dc, rect, focus, upperTabs): + """ + Draws the tab background for the Firefox 2 style. + This is more consistent with L{FlatNotebook} than before. + + :param `dc`: a `wx.DC` device context; + :param `rect`: rectangle the tab should be confined to; + :param `focus`: whether the tab has focus or not; + :param `upperTabs`: whether the style is ``AUI_NB_TOP`` or ``AUI_NB_BOTTOM``. + """ + + # Define the rounded rectangle base on the given rect + # we need an array of 9 points for it + regPts = [wx.Point() for indx in xrange(9)] + + if focus: + if upperTabs: + leftPt = wx.Point(rect.x, rect.y + (rect.height / 10)*8) + rightPt = wx.Point(rect.x + rect.width - 2, rect.y + (rect.height / 10)*8) + else: + leftPt = wx.Point(rect.x, rect.y + (rect.height / 10)*5) + rightPt = wx.Point(rect.x + rect.width - 2, rect.y + (rect.height / 10)*5) + else: + leftPt = wx.Point(rect.x, rect.y + (rect.height / 2)) + rightPt = wx.Point(rect.x + rect.width - 2, rect.y + (rect.height / 2)) + + # Define the top region + top = wx.RectPP(rect.GetTopLeft(), rightPt) + bottom = wx.RectPP(leftPt, rect.GetBottomRight()) + + topStartColour = wx.WHITE + + if not focus: + topStartColour = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 50) + + topEndColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) + bottomStartColour = topEndColour + bottomEndColour = topEndColour + + # Incase we use bottom tabs, switch the colours + if upperTabs: + if focus: + dc.GradientFillLinear(top, topStartColour, topEndColour, wx.SOUTH) + dc.GradientFillLinear(bottom, bottomStartColour, bottomEndColour, wx.SOUTH) + else: + dc.GradientFillLinear(top, topEndColour , topStartColour, wx.SOUTH) + dc.GradientFillLinear(bottom, bottomStartColour, bottomEndColour, wx.SOUTH) + + else: + if focus: + dc.GradientFillLinear(bottom, topEndColour, bottomEndColour, wx.SOUTH) + dc.GradientFillLinear(top, topStartColour, topStartColour, wx.SOUTH) + else: + dc.GradientFillLinear(bottom, bottomStartColour, bottomEndColour, wx.SOUTH) + dc.GradientFillLinear(top, topEndColour, topStartColour, wx.SOUTH) + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + +class VC8TabArt(AuiDefaultTabArt): + """ A class to draw tabs using the Visual Studio 2005 (VC8) style. """ + + def __init__(self): + """ Default class constructor. """ + + AuiDefaultTabArt.__init__(self) + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth): + """ + Sets the tab sizing information. + + :param `tab_ctrl_size`: the size of the tab control area; + :param `tab_count`: the number of tabs; + :param `minMaxTabWidth`: a tuple containing the minimum and maximum tab widths + to be used when the ``AUI_NB_TAB_FIXED_WIDTH`` style is active. + """ + + AuiDefaultTabArt.SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth) + + minTabWidth, maxTabWidth = minMaxTabWidth + if minTabWidth > -1: + self._fixed_tab_width = max(self._fixed_tab_width, minTabWidth) + if maxTabWidth > -1: + self._fixed_tab_width = min(self._fixed_tab_width, maxTabWidth) + + self._fixed_tab_width -= 5 + + + def GetTabSize(self, dc, wnd, caption, bitmap, active, close_button_state, control=None): + """ + Returns the tab size for the given caption, bitmap and button state. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `caption`: the tab text caption; + :param `bitmap`: the bitmap displayed on the tab; + :param `active`: whether the tab is selected or not; + :param `close_button_state`: the state of the close button on the tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + tab_size, x_extent = AuiDefaultTabArt.GetTabSize(self, dc, wnd, caption, bitmap, + active, close_button_state, control) + + tab_width, tab_height = tab_size + + # add some padding + tab_width += 10 + tab_height += 2 + + return (tab_width, tab_height), x_extent + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # Visual Studio 8 style + + control = page.control + + # figure out the size of the tab + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, + page.active, close_button_state, control) + + tab_height = self._tab_ctrl_height - 1 + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + + clip_width = tab_width + 3 + if tab_x + clip_width > in_rect.x + in_rect.width - 4: + clip_width = (in_rect.x + in_rect.width) - tab_x - 4 + + tabPoints = [wx.Point() for i in xrange(8)] + + # If we draw the first tab or the active tab, + # we draw a full tab, else we draw a truncated tab + # + # X(2) X(3) + # X(1) X(4) + # + # X(5) + # + # X(0),(7) X(6) + # + # + + adjust = 0 + if not page.active: + adjust = 1 + + agwFlags = self.GetAGWFlags() + tabPoints[0].x = (agwFlags & AUI_NB_BOTTOM and [tab_x] or [tab_x + adjust])[0] + tabPoints[0].y = (agwFlags & AUI_NB_BOTTOM and [2] or [tab_height - 3])[0] + + tabPoints[1].x = tabPoints[0].x + tab_height - vertical_border_padding - 3 - adjust + tabPoints[1].y = (agwFlags & AUI_NB_BOTTOM and [tab_height - (vertical_border_padding+2)] or \ + [(vertical_border_padding+2)])[0] + + tabPoints[2].x = tabPoints[1].x + 4 + tabPoints[2].y = (agwFlags & AUI_NB_BOTTOM and [tab_height - vertical_border_padding] or \ + [vertical_border_padding])[0] + + tabPoints[3].x = tabPoints[2].x + tab_width - tab_height + vertical_border_padding + tabPoints[3].y = (agwFlags & AUI_NB_BOTTOM and [tab_height - vertical_border_padding] or \ + [vertical_border_padding])[0] + + tabPoints[4].x = tabPoints[3].x + 1 + tabPoints[4].y = (agwFlags & AUI_NB_BOTTOM and [tabPoints[3].y - 1] or [tabPoints[3].y + 1])[0] + + tabPoints[5].x = tabPoints[4].x + 1 + tabPoints[5].y = (agwFlags & AUI_NB_BOTTOM and [(tabPoints[4].y - 1)] or [tabPoints[4].y + 1])[0] + + tabPoints[6].x = tabPoints[2].x + tab_width - tab_height + 2 + vertical_border_padding + tabPoints[6].y = tabPoints[0].y + + tabPoints[7].x = tabPoints[0].x + tabPoints[7].y = tabPoints[0].y + + self.FillVC8GradientColour(dc, tabPoints, page.active) + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))) + dc.DrawPolygon(tabPoints) + + if page.active: + # Delete the bottom line (or the upper one, incase we use wxBOTTOM) + dc.SetPen(wx.WHITE_PEN) + dc.DrawLine(tabPoints[0].x, tabPoints[0].y, tabPoints[6].x, tabPoints[6].y) + + dc.SetClippingRegion(tab_x, tab_y, clip_width + 2, tab_height - 3) + + drawn_tab_yoff = tabPoints[1].y + drawn_tab_height = tabPoints[0].y - tabPoints[2].y + + text_offset = tab_x + 20 + close_button_width = 0 + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset += close_button_width + + if not page.enabled: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + pagebitmap = page.dis_bitmap + else: + dc.SetTextForeground(page.text_colour) + pagebitmap = page.bitmap + + shift = 0 + if agwFlags & AUI_NB_BOTTOM: + shift = (page.active and [1] or [2])[0] + + bitmap_offset = 0 + if pagebitmap.IsOk(): + bitmap_offset = tab_x + 20 + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width: + bitmap_offset += close_button_width + + # draw bitmap + dc.DrawBitmap(pagebitmap, bitmap_offset, + drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2) + shift, + True) + + text_offset = bitmap_offset + pagebitmap.GetWidth() + text_offset += 3 # bitmap padding + + else: + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width: + text_offset = tab_x + tab_height + + # if the caption is empty, measure some temporary text + caption = page.caption + if caption == "": + caption = "Xj" + + if page.active: + dc.SetFont(self._selected_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + else: + dc.SetFont(self._normal_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x)) + else: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width) + + ypos = drawn_tab_yoff + drawn_tab_height/2 - texty/2 - 1 + shift + + offset_focus = text_offset + + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + textx += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + # draw focus rectangle + if (agwFlags & AUI_NB_NO_TAB_FOCUS) == 0: + self.DrawFocusRectangle(dc, page, wnd, draw_text, offset_focus, bitmap_offset, drawn_tab_yoff+shift, + drawn_tab_height+shift, rectx, recty) + + out_button_rect = wx.Rect() + # draw 'x' on tab (if enabled) + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + close_button_width = self._active_close_bmp.GetWidth() + bmp = self._disabled_close_bmp + + if close_button_state == AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif close_button_state == AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + + if page.active: + xpos = tab_x + tab_width - close_button_width + 3 + else: + xpos = tab_x + tab_width - close_button_width - 5 + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + 20, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + else: + rect = wx.Rect(xpos, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + shift, + close_button_width, tab_height) + + # Indent the button if it is pressed down: + rect = IndentPressedBitmap(rect, close_button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + out_button_rect = rect + + out_tab_rect = wx.Rect(tab_x, tab_y, x_extent, tab_height) + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + + def FillVC8GradientColour(self, dc, tabPoints, active): + """ + Fills the tab with the Visual Studio 2005 gradient background. + + :param `dc`: a `wx.DC` device context; + :param `tabPoints`: a list of `wx.Point` objects describing the tab shape; + :param `active`: whether the tab is selected or not. + """ + + xList = [pt.x for pt in tabPoints] + yList = [pt.y for pt in tabPoints] + + minx, maxx = min(xList), max(xList) + miny, maxy = min(yList), max(yList) + + rect = wx.Rect(minx, maxy, maxx-minx, miny-maxy+1) + region = wx.RegionFromPoints(tabPoints) + + if self._buttonRect.width > 0: + buttonRegion = wx.Region(*self._buttonRect) + region.XorRegion(buttonRegion) + + dc.SetClippingRegionAsRegion(region) + + if active: + bottom_colour = top_colour = wx.WHITE + else: + bottom_colour = StepColour(self._base_colour, 90) + top_colour = StepColour(self._base_colour, 170) + + dc.GradientFillLinear(rect, top_colour, bottom_colour, wx.SOUTH) + dc.DestroyClippingRegion() + + +class ChromeTabArt(AuiDefaultTabArt): + """ + A class to draw tabs using the Google Chrome browser style. + It uses custom bitmap to render the tabs, so that the look and feel is as close + as possible to the Chrome style. + """ + + def __init__(self): + """ Default class constructor. """ + + AuiDefaultTabArt.__init__(self) + + self.SetBitmaps(mirror=False) + + closeBmp = tab_close.GetBitmap() + closeHBmp = tab_close_h.GetBitmap() + closePBmp = tab_close_p.GetBitmap() + + self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_NORMAL, closeBmp) + self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_HOVER, closeHBmp) + self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_PRESSED, closePBmp) + + + def SetAGWFlags(self, agwFlags): + """ + Sets the tab art flags. + + :param `agwFlags`: a combination of the following values: + + ==================================== ================================== + Flag name Description + ==================================== ================================== + ``AUI_NB_TOP`` With this style, tabs are drawn along the top of the notebook + ``AUI_NB_LEFT`` With this style, tabs are drawn along the left of the notebook. Not implemented yet. + ``AUI_NB_RIGHT`` With this style, tabs are drawn along the right of the notebook. Not implemented yet. + ``AUI_NB_BOTTOM`` With this style, tabs are drawn along the bottom of the notebook + ``AUI_NB_TAB_SPLIT`` Allows the tab control to be split by dragging a tab + ``AUI_NB_TAB_MOVE`` Allows a tab to be moved horizontally by dragging + ``AUI_NB_TAB_EXTERNAL_MOVE`` Allows a tab to be moved to another tab control + ``AUI_NB_TAB_FIXED_WIDTH`` With this style, all tabs have the same width + ``AUI_NB_SCROLL_BUTTONS`` With this style, left and right scroll buttons are displayed + ``AUI_NB_WINDOWLIST_BUTTON`` With this style, a drop-down list of windows is available + ``AUI_NB_CLOSE_BUTTON`` With this style, a close button is available on the tab bar + ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` With this style, a close button is available on the active tab + ``AUI_NB_CLOSE_ON_ALL_TABS`` With this style, a close button is available on all tabs + ``AUI_NB_MIDDLE_CLICK_CLOSE`` Allows to close L{AuiNotebook} tabs by mouse middle button click + ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{AuiManager} to create automatic AuiNotebooks + ``AUI_NB_HIDE_ON_SINGLE_TAB`` Hides the tab window if only one tab is present + ``AUI_NB_SMART_TABS`` Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows + ``AUI_NB_USE_IMAGES_DROPDOWN`` Uses images on dropdown window list menu instead of check items + ``AUI_NB_CLOSE_ON_TAB_LEFT`` Draws the tab close button on the left instead of on the right (a la Camino browser) + ``AUI_NB_TAB_FLOAT`` Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages + ``AUI_NB_DRAW_DND_TAB`` Draws an image representation of a tab while dragging (on by default) + ``AUI_NB_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs + ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle + ==================================== ================================== + + :note: Overridden from L{AuiDefaultTabArt}. + """ + + if agwFlags & AUI_NB_TOP: + self.SetBitmaps(mirror=False) + elif agwFlags & AUI_NB_BOTTOM: + self.SetBitmaps(mirror=True) + + AuiDefaultTabArt.SetAGWFlags(self, agwFlags) + + + def SetBitmaps(self, mirror): + """ + Assigns the tab custom bitmaps + + :param `mirror`: whether to vertically mirror the bitmap or not. + """ + + bmps = [tab_active_left.GetBitmap(), tab_active_center.GetBitmap(), + tab_active_right.GetBitmap(), tab_inactive_left.GetBitmap(), + tab_inactive_center.GetBitmap(), tab_inactive_right.GetBitmap()] + + if mirror: + for indx, bmp in enumerate(bmps): + img = bmp.ConvertToImage() + img = img.Mirror(horizontally=False) + bmps[indx] = img.ConvertToBitmap() + + self._leftActiveBmp = bmps[0] + self._centerActiveBmp = bmps[1] + self._rightActiveBmp = bmps[2] + self._leftInactiveBmp = bmps[3] + self._centerInactiveBmp = bmps[4] + self._rightInactiveBmp = bmps[5] + + + def Clone(self): + """ Clones the art object. """ + + art = type(self)() + art.SetNormalFont(self.GetNormalFont()) + art.SetSelectedFont(self.GetSelectedFont()) + art.SetMeasuringFont(self.GetMeasuringFont()) + + art = CopyAttributes(art, self) + return art + + + def SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth): + """ + Sets the tab sizing information. + + :param `tab_ctrl_size`: the size of the tab control area; + :param `tab_count`: the number of tabs; + :param `minMaxTabWidth`: a tuple containing the minimum and maximum tab widths + to be used when the ``AUI_NB_TAB_FIXED_WIDTH`` style is active. + """ + + AuiDefaultTabArt.SetSizingInfo(self, tab_ctrl_size, tab_count, minMaxTabWidth) + + minTabWidth, maxTabWidth = minMaxTabWidth + if minTabWidth > -1: + self._fixed_tab_width = max(self._fixed_tab_width, minTabWidth) + if maxTabWidth > -1: + self._fixed_tab_width = min(self._fixed_tab_width, maxTabWidth) + + self._fixed_tab_width -= 5 + + + def GetTabSize(self, dc, wnd, caption, bitmap, active, close_button_state, control=None): + """ + Returns the tab size for the given caption, bitmap and button state. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `caption`: the tab text caption; + :param `bitmap`: the bitmap displayed on the tab; + :param `active`: whether the tab is selected or not; + :param `close_button_state`: the state of the close button on the tab; + :param `control`: a `wx.Window` instance inside a tab (or ``None``). + """ + + tab_size, x_extent = AuiDefaultTabArt.GetTabSize(self, dc, wnd, caption, bitmap, + active, close_button_state, control) + + tab_width, tab_height = tab_size + + # add some padding + tab_width += self._leftActiveBmp.GetWidth() + tab_height += 2 + + tab_height = max(tab_height, self._centerActiveBmp.GetHeight()) + + return (tab_width, tab_height), x_extent + + + def DrawTab(self, dc, wnd, page, in_rect, close_button_state, paint_control=False): + """ + Draws a single tab. + + :param `dc`: a `wx.DC` device context; + :param `wnd`: a `wx.Window` instance object; + :param `page`: the tab control page associated with the tab; + :param `in_rect`: rectangle the tab should be confined to; + :param `close_button_state`: the state of the close button on the tab; + :param `paint_control`: whether to draw the control inside a tab (if any) on a `wx.MemoryDC`. + """ + + # Chrome tab style + + control = page.control + # figure out the size of the tab + tab_size, x_extent = self.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, + close_button_state, control) + + agwFlags = self.GetAGWFlags() + + tab_height = self._tab_ctrl_height - 1 + tab_width = tab_size[0] + tab_x = in_rect.x + tab_y = in_rect.y + in_rect.height - tab_height + clip_width = tab_width + + if tab_x + clip_width > in_rect.x + in_rect.width - 4: + clip_width = (in_rect.x + in_rect.width) - tab_x - 4 + + dc.SetClippingRegion(tab_x, tab_y, clip_width + 1, tab_height - 3) + drawn_tab_yoff = 1 + + if page.active: + left = self._leftActiveBmp + center = self._centerActiveBmp + right = self._rightActiveBmp + else: + left = self._leftInactiveBmp + center = self._centerInactiveBmp + right = self._rightInactiveBmp + + dc.DrawBitmap(left, tab_x, tab_y) + leftw = left.GetWidth() + centerw = center.GetWidth() + rightw = right.GetWidth() + + available = tab_x + tab_width - rightw + posx = tab_x + leftw + + while 1: + if posx >= available: + break + dc.DrawBitmap(center, posx, tab_y) + posx += centerw + + dc.DrawBitmap(right, posx, tab_y) + + drawn_tab_height = center.GetHeight() + text_offset = tab_x + leftw + + close_button_width = 0 + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + close_button_width = self._active_close_bmp.GetWidth() + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + text_offset += close_button_width + + if not page.enabled: + dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + pagebitmap = page.dis_bitmap + else: + dc.SetTextForeground(page.text_colour) + pagebitmap = page.bitmap + + bitmap_offset = 0 + if pagebitmap.IsOk(): + bitmap_offset = tab_x + leftw + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT and close_button_width: + bitmap_offset += close_button_width + + # draw bitmap + dc.DrawBitmap(pagebitmap, bitmap_offset, + drawn_tab_yoff + (drawn_tab_height/2) - (pagebitmap.GetHeight()/2), + True) + + text_offset = bitmap_offset + pagebitmap.GetWidth() + text_offset += 3 # bitmap padding + + else: + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT == 0 or not close_button_width: + text_offset = tab_x + leftw + + # if the caption is empty, measure some temporary text + caption = page.caption + if caption == "": + caption = "Xj" + + if page.active: + dc.SetFont(self._selected_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + else: + dc.SetFont(self._normal_font) + textx, texty, dummy = dc.GetMultiLineTextExtent(caption) + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - leftw) + else: + draw_text = ChopText(dc, caption, tab_width - (text_offset-tab_x) - close_button_width - leftw) + + ypos = drawn_tab_yoff + drawn_tab_height/2 - texty/2 - 1 + + if control is not None: + if control.GetPosition() != wx.Point(text_offset+1, ypos): + control.SetPosition(wx.Point(text_offset+1, ypos)) + + if not control.IsShown(): + control.Show() + + if paint_control: + bmp = TakeScreenShot(control.GetScreenRect()) + dc.DrawBitmap(bmp, text_offset+1, ypos, True) + + controlW, controlH = control.GetSize() + text_offset += controlW + 4 + + # draw tab text + rectx, recty, dummy = dc.GetMultiLineTextExtent(draw_text) + dc.DrawLabel(draw_text, wx.Rect(text_offset, ypos, rectx, recty)) + + out_button_rect = wx.Rect() + # draw 'x' on tab (if enabled) + if close_button_state != AUI_BUTTON_STATE_HIDDEN: + + close_button_width = self._active_close_bmp.GetWidth() + bmp = self._disabled_close_bmp + + if close_button_state == AUI_BUTTON_STATE_HOVER: + bmp = self._hover_close_bmp + elif close_button_state == AUI_BUTTON_STATE_PRESSED: + bmp = self._pressed_close_bmp + + if agwFlags & AUI_NB_CLOSE_ON_TAB_LEFT: + rect = wx.Rect(tab_x + leftw - 2, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + 1, + close_button_width, tab_height) + else: + rect = wx.Rect(tab_x + tab_width - close_button_width - rightw + 2, + drawn_tab_yoff + (drawn_tab_height / 2) - (bmp.GetHeight() / 2) + 1, + close_button_width, tab_height) + + if agwFlags & AUI_NB_BOTTOM: + rect.y -= 1 + + # Indent the button if it is pressed down: + rect = IndentPressedBitmap(rect, close_button_state) + dc.DrawBitmap(bmp, rect.x, rect.y, True) + out_button_rect = rect + + out_tab_rect = wx.Rect(tab_x, tab_y, tab_width, tab_height) + dc.DestroyClippingRegion() + + return out_tab_rect, out_button_rect, x_extent + + diff --git a/aui/tabmdi.py b/aui/tabmdi.py new file mode 100644 index 0000000..ef09e9f --- /dev/null +++ b/aui/tabmdi.py @@ -0,0 +1,666 @@ +__author__ = "Andrea Gavana " +__date__ = "31 March 2009" + + +import wx + +import auibook +from aui_constants import * + +_ = wx.GetTranslation + +#----------------------------------------------------------------------------- +# AuiMDIParentFrame +#----------------------------------------------------------------------------- + +class AuiMDIParentFrame(wx.Frame): + + def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.VSCROLL|wx.HSCROLL, + name="AuiMDIParentFrame"): + + wx.Frame.__init__(self, parent, id, title, pos, size, style, name=name) + self.Init() + + self.Bind(wx.EVT_MENU, self.DoHandleMenu, id=wx.ID_ANY) + + # this style can be used to prevent a window from having the standard MDI + # "Window" menu + if not style & wx.FRAME_NO_WINDOW_MENU: + + self._pWindowMenu = wx.Menu() + self._pWindowMenu.Append(wxWINDOWCLOSE, _("Cl&ose")) + self._pWindowMenu.Append(wxWINDOWCLOSEALL, _("Close All")) + self._pWindowMenu.AppendSeparator() + self._pWindowMenu.Append(wxWINDOWNEXT, _("&Next")) + self._pWindowMenu.Append(wxWINDOWPREV, _("&Previous")) + + self._pClientWindow = self.OnCreateClient() + + + def SetArtProvider(self, provider): + + if self._pClientWindow: + self._pClientWindow.SetArtProvider(provider) + + + def GetArtProvider(self): + + if not self._pClientWindow: + return None + + return self._pClientWindow.GetArtProvider() + + + def GetNotebook(self): + + return self._pClientWindow + + + def SetWindowMenu(self, pMenu): + + # Replace the window menu from the currently loaded menu bar. + pMenuBar = self.GetMenuBar() + + if self._pWindowMenu: + self.RemoveWindowMenu(pMenuBar) + del self._pWindowMenu + self._pWindowMenu = None + + if pMenu: + self._pWindowMenu = pMenu + self.AddWindowMenu(pMenuBar) + + + def GetWindowMenu(self): + + return self._pWindowMenu + + + def SetMenuBar(self, pMenuBar): + + # Remove the Window menu from the old menu bar + self.RemoveWindowMenu(self.GetMenuBar()) + + # Add the Window menu to the new menu bar. + self.AddWindowMenu(pMenuBar) + + wx.Frame.SetMenuBar(self, pMenuBar) + + + def SetChildMenuBar(self, pChild): + + if not pChild: + + # No Child, set Our menu bar back. + if self._pMyMenuBar: + self.SetMenuBar(self._pMyMenuBar) + else: + self.SetMenuBar(self.GetMenuBar()) + + # Make sure we know our menu bar is in use + self._pMyMenuBar = None + + else: + + if pChild.GetMenuBar() == None: + return + + # Do we need to save the current bar? + if self._pMyMenuBar == None: + self._pMyMenuBar = self.GetMenuBar() + + self.SetMenuBar(pChild.GetMenuBar()) + + + def ProcessEvent(self, event): + + # stops the same event being processed repeatedly + if self._pLastEvt == event: + return False + + self._pLastEvt = event + + # let the active child (if any) process the event first. + res = False + if self._pActiveChild and event.IsCommandEvent() and \ + event.GetEventObject() != self._pClientWindow and \ + event.GetEventType() not in [wx.wxEVT_ACTIVATE, wx.wxEVT_SET_FOCUS, + wx.wxEVT_KILL_FOCUS, wx.wxEVT_CHILD_FOCUS, + wx.wxEVT_COMMAND_SET_FOCUS, wx.wxEVT_COMMAND_KILL_FOCUS]: + + res = self._pActiveChild.GetEventHandler().ProcessEvent(event) + + if not res: + + # if the event was not handled this frame will handle it, + # which is why we need the protection code at the beginning + # of this method + res = self.GetEventHandler().ProcessEvent(event) + + self._pLastEvt = None + + return res + + + def GetActiveChild(self): + + return self._pActiveChild + + + def SetActiveChild(self, pChildFrame): + + self._pActiveChild = pChildFrame + + + def GetClientWindow(self): + + return self._pClientWindow + + + def OnCreateClient(self): + + return AuiMDIClientWindow(self) + + + def ActivateNext(self): + + if self._pClientWindow and self._pClientWindow.GetSelection() != wx.NOT_FOUND: + + active = self._pClientWindow.GetSelection() + 1 + if active >= self._pClientWindow.GetPageCount(): + active = 0 + + self._pClientWindow.SetSelection(active) + + + def ActivatePrevious(self): + + if self._pClientWindow and self._pClientWindow.GetSelection() != wx.NOT_FOUND: + + active = self._pClientWindow.GetSelection() - 1 + if active < 0: + active = self._pClientWindow.GetPageCount() - 1 + + self._pClientWindow.SetSelection(active) + + + def Init(self): + + self._pLastEvt = None + + self._pClientWindow = None + self._pActiveChild = None + self._pWindowMenu = None + self._pMyMenuBar = None + + + def RemoveWindowMenu(self, pMenuBar): + + if pMenuBar and self._pWindowMenu: + + # Remove old window menu + pos = pMenuBar.FindMenu(_("&Window")) + if pos != wx.NOT_FOUND: + pMenuBar.Remove(pos) + + + def AddWindowMenu(self, pMenuBar): + + if pMenuBar and self._pWindowMenu: + + pos = pMenuBar.FindMenu(wx.GetStockLabel(wx.ID_HELP, wx.STOCK_NOFLAGS)) + if pos == wx.NOT_FOUND: + pMenuBar.Append(self._pWindowMenu, _("&Window")) + else: + pMenuBar.Insert(pos, self._pWindowMenu, _("&Window")) + + + def DoHandleMenu(self, event): + + evId = event.GetId() + + if evId == wxWINDOWCLOSE: + if self._pActiveChild: + self._pActiveChild.Close() + + elif evId == wxWINDOWCLOSEALL: + + while self._pActiveChild: + if not self._pActiveChild.Close(): + return # failure + + elif evId == wxWINDOWNEXT: + self.ActivateNext() + + elif evId == wxWINDOWPREV: + self.ActivatePrevious() + + else: + event.Skip() + + + def Tile(self, orient=wx.HORIZONTAL): + + client_window = self.GetClientWindow() + if not client_window: + raise Exception("Missing MDI Client Window") + + cur_idx = client_window.GetSelection() + if cur_idx == -1: + return + + if orient == wx.VERTICAL: + + client_window.Split(cur_idx, wx.LEFT) + + elif orient == wx.HORIZONTAL: + + client_window.Split(cur_idx, wx.TOP) + + +#----------------------------------------------------------------------------- +# AuiMDIChildFrame +#----------------------------------------------------------------------------- + +class AuiMDIChildFrame(wx.PyPanel): + + def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="AuiMDIChildFrame"): + + pClientWindow = parent.GetClientWindow() + if pClientWindow is None: + raise Exception("Missing MDI client window.") + + self.Init() + + # see comment in constructor + if style & wx.MINIMIZE: + self._activate_on_create = False + + cli_size = pClientWindow.GetClientSize() + + # create the window off-screen to prevent flicker + wx.PyPanel.__init__(self, pClientWindow, id, wx.Point(cli_size.x+1, cli_size.y+1), + size, wx.NO_BORDER, name=name) + + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.Show(False) + self.SetMDIParentFrame(parent) + + # this is the currently active child + parent.SetActiveChild(self) + self._title = title + + pClientWindow.AddPage(self, title, self._activate_on_create) + pClientWindow.Refresh() + + self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight) + self.Bind(wx.EVT_ACTIVATE, self.OnActivate) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + + def Init(self): + + # There are two ways to create an tabbed mdi child fram without + # making it the active document. Either Show(False) can be called + # before Create() (as is customary on some ports with wxFrame-type + # windows), or wx.MINIMIZE can be passed in the style flags. Note that + # AuiMDIChildFrame is not really derived from wxFrame, as MDIChildFrame + # is, but those are the expected symantics. No style flag is passed + # onto the panel underneath. + + self._activate_on_create = True + + self._pMDIParentFrame = None + self._pMenuBar = None + + self._mdi_currect = None + self._mdi_newrect = wx.Rect() + self._icon = None + self._icon_bundle = None + + + def Destroy(self): + + pParentFrame = self.GetMDIParentFrame() + if not pParentFrame: + raise Exception("Missing MDI Parent Frame") + + pClientWindow = pParentFrame.GetClientWindow() + if not pClientWindow: + raise Exception("Missing MDI Client Window") + + if pParentFrame.GetActiveChild() == self: + + # deactivate ourself + event = wx.ActivateEvent(wx.wxEVT_ACTIVATE, False, self.GetId()) + event.SetEventObject(self) + self.GetEventHandler().ProcessEvent(event) + + pParentFrame.SetActiveChild(None) + pParentFrame.SetChildMenuBar(None) + + for pos in xrange(pClientWindow.GetPageCount()): + if pClientWindow.GetPage(pos) == self: + return pClientWindow.DeletePage(pos) + + return False + + + def SetMenuBar(self, menu_bar): + + pOldMenuBar = self._pMenuBar + self._pMenuBar = menu_bar + + if self._pMenuBar: + + pParentFrame = self.GetMDIParentFrame() + if not pParentFrame: + raise Exception("Missing MDI Parent Frame") + + self._pMenuBar.Reparent(pParentFrame) + if pParentFrame.GetActiveChild() == self: + + # replace current menu bars + if pOldMenuBar: + pParentFrame.SetChildMenuBar(None) + + pParentFrame.SetChildMenuBar(self) + + + def GetMenuBar(self): + + return self._pMenuBar + + + def SetTitle(self, title): + + self._title = title + + pParentFrame = self.GetMDIParentFrame() + if not pParentFrame: + raise Exception("Missing MDI Parent Frame") + + pClientWindow = pParentFrame.GetClientWindow() + if pClientWindow is not None: + + for pos in xrange(pClientWindow.GetPageCount()): + if pClientWindow.GetPage(pos) == self: + pClientWindow.SetPageText(pos, self._title) + break + + + def GetTitle(self): + + return self._title + + + def SetIcons(self, icons): + + # get icon with the system icon size + self.SetIcon(icons.GetIcon(-1)) + self._icon_bundle = icons + + + def GetIcons(self): + + return self._icon_bundle + + + def SetIcon(self, icon): + + pParentFrame = self.GetMDIParentFrame() + if not pParentFrame: + raise Exception("Missing MDI Parent Frame") + + self._icon = icon + + bmp = wx.BitmapFromIcon(self._icon) + + pClientWindow = pParentFrame.GetClientWindow() + if pClientWindow is not None: + idx = pClientWindow.GetPageIndex(self) + if idx != -1: + pClientWindow.SetPageBitmap(idx, bmp) + + + def GetIcon(self): + + return self._icon + + + def Activate(self): + + pParentFrame = self.GetMDIParentFrame() + if not pParentFrame: + raise Exception("Missing MDI Parent Frame") + + pClientWindow = pParentFrame.GetClientWindow() + if pClientWindow is not None: + + for pos in xrange(pClientWindow.GetPageCount()): + if pClientWindow.GetPage(pos) == self: + pClientWindow.SetSelection(pos) + break + + + def OnMenuHighlight(self, event): + + if self._pMDIParentFrame: + + # we don't have any help text for this item, + # but may be the MDI frame does? + self._pMDIParentFrame.OnMenuHighlight(event) + + + def OnActivate(self, event): + + # do nothing + pass + + + def OnCloseWindow(self, event): + + pParentFrame = self.GetMDIParentFrame() + if pParentFrame: + if pParentFrame.GetActiveChild() == self: + + pParentFrame.SetActiveChild(None) + pParentFrame.SetChildMenuBar(None) + + pClientWindow = pParentFrame.GetClientWindow() + idx = pClientWindow.GetPageIndex(self) + + if idx != wx.NOT_FOUND: + pClientWindow.RemovePage(idx) + + self.Destroy() + + + def SetMDIParentFrame(self, parentFrame): + + self._pMDIParentFrame = parentFrame + + + def GetMDIParentFrame(self): + + return self._pMDIParentFrame + + + def CreateStatusBar(self, number=1, style=1, winid=1, name=""): + + return None + + + def GetStatusBar(self): + + return None + + + def SetStatusText(self, text, number=0): + + pass + + + def SetStatusWidths(self, widths_field): + + pass + + + # no toolbar bars + def CreateToolBar(self, style=1, winid=-1, name=""): + + return None + + + def GetToolBar(self): + + return None + + + # no maximize etc + def Maximize(self, maximize=True): + + pass + + + def Restore(self): + + pass + + + def Iconize(self, iconize=True): + + pass + + + def IsMaximized(self): + + return True + + + def IsIconized(self): + + return False + + + def ShowFullScreen(self, show=True, style=0): + + return False + + + def IsFullScreen(self): + + return False + + + def IsTopLevel(self): + + return False + + + # renamed from Show(). + def ActivateOnCreate(self, activate_on_create): + + self._activate_on_create = activate_on_create + return True + + + def Show(self, show=True): + + wx.PyPanel.Show(self, show) + + + def ApplyMDIChildFrameRect(self): + + if self._mdi_currect != self._mdi_newrect: + self.SetDimensions(*self._mdi_newrect) + self._mdi_currect = wx.Rect(*self._mdi_newrect) + + +#----------------------------------------------------------------------------- +# AuiMDIClientWindow +#----------------------------------------------------------------------------- + +class AuiMDIClientWindow(auibook.AuiNotebook): + + def __init__(self, parent, agwStyle=0): + + auibook.AuiNotebook.__init__(self, parent, wx.ID_ANY, wx.Point(0, 0), wx.Size(100, 100), + agwStyle=AUI_NB_DEFAULT_STYLE|wx.NO_BORDER) + + caption_icon_size = wx.Size(wx.SystemSettings.GetMetric(wx.SYS_SMALLICON_X), + wx.SystemSettings.GetMetric(wx.SYS_SMALLICON_Y)) + self.SetUniformBitmapSize(caption_icon_size) + + bkcolour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_APPWORKSPACE) + self.SetOwnBackgroundColour(bkcolour) + + self._mgr.GetArtProvider().SetColour(AUI_DOCKART_BACKGROUND_COLOUR, bkcolour) + + self.Bind(auibook.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnPageChanged) + self.Bind(auibook.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnPageClose) + self.Bind(wx.EVT_SIZE, self.OnSize) + + + def SetSelection(self, nPage): + + return auibook.AuiNotebook.SetSelection(self, nPage) + + + def PageChanged(self, old_selection, new_selection): + + # don't do anything if the page doesn't actually change + if old_selection == new_selection: + return + + # notify old active child that it has been deactivated + if old_selection != -1 and old_selection < self.GetPageCount(): + + old_child = self.GetPage(old_selection) + if not old_child: + raise Exception("AuiMDIClientWindow.PageChanged - null page pointer") + + event = wx.ActivateEvent(wx.wxEVT_ACTIVATE, False, old_child.GetId()) + event.SetEventObject(old_child) + old_child.GetEventHandler().ProcessEvent(event) + + # notify new active child that it has been activated + if new_selection != -1: + + active_child = self.GetPage(new_selection) + if not active_child: + raise Exception("AuiMDIClientWindow.PageChanged - null page pointer") + + event = wx.ActivateEvent(wx.wxEVT_ACTIVATE, True, active_child.GetId()) + event.SetEventObject(active_child) + active_child.GetEventHandler().ProcessEvent(event) + + if active_child.GetMDIParentFrame(): + active_child.GetMDIParentFrame().SetActiveChild(active_child) + active_child.GetMDIParentFrame().SetChildMenuBar(active_child) + + + def OnPageClose(self, event): + + wnd = self.GetPage(event.GetSelection()) + wnd.Close() + + # regardless of the result of wnd.Close(), we've + # already taken care of the close operations, so + # suppress further processing + event.Veto() + + + def OnPageChanged(self, event): + + self.PageChanged(event.GetOldSelection(), event.GetSelection()) + + + def OnSize(self, event): + + auibook.AuiNotebook.OnSize(self, event) + + for pos in xrange(self.GetPageCount()): + self.GetPage(pos).ApplyMDIChildFrameRect() diff --git a/chemins.py b/chemins.py index 3b3ee17..b441a32 100644 --- a/chemins.py +++ b/chemins.py @@ -221,7 +221,6 @@ ChdTxtPathOut = {'TableUc1': 'TableUc1.csv', 'R3DCoul': ffr(tempfile.mkstemp(prefix='iramuteq')[1]), 'RESULT_CHD': 'resultats-chd.html', 'RESULT_AFC': 'resultats-afc.html', - 'Act01': 'Act01.csv', 'Et01': 'Et01.csv', 'Rchdquest':ffr(tempfile.mkstemp(prefix='iramuteq')[1]), 'RTxtProfGraph':ffr(tempfile.mkstemp(prefix='iramuteq')[1]), diff --git a/corpus.py b/corpus.py index e043707..7cc9306 100644 --- a/corpus.py +++ b/corpus.py @@ -11,7 +11,6 @@ from time import time from functions import decoupercharact, ReadDicoAsDico, DoConf import re import sqlite3 -import numpy import itertools import logging from operator import itemgetter diff --git a/functions.py b/functions.py index 8a10f67..e72ebd9 100644 --- a/functions.py +++ b/functions.py @@ -45,14 +45,17 @@ class History : def read(self) : d = shelve.open(self.filein) self.history = d.get('history', []) + self.matrix = d.get('matrix', []) self.ordercorpus = dict([[corpus['uuid'], i] for i, corpus in enumerate(self.history)]) - self.corpus = dict([[corpus['uuid'], corpus] for i, corpus in enumerate(self.history)]) + self.corpus = dict([[corpus['uuid'], corpus] for corpus in self.history]) self.analyses = dict([[analyse['uuid'], analyse] for corpus in self.history for analyse in corpus.get('analyses', [])]) + self.matrixanalyse = dict([[mat['uuid'], mat] for mat in self.matrix]) d.close() def write(self) : d = shelve.open(self.filein) d['history'] = self.history + d['matrix'] = self.matrix d.close() def add(self, analyse) : @@ -77,6 +80,13 @@ class History : self.write() self.read() + def addMatrix(self, analyse) : + tosave = {'uuid' : analyse['uuid'], 'ira': analyse['ira'], 'type' : analyse['type']} + tosave['name'] = analyse['name'] + self.matrix.append(tosave) + self.write() + self.read() + def addmultiple(self, analyses) : for analyse in analyses : tosave = {'uuid' : analyse['uuid'], 'ira': analyse['ira'], 'type' : analyse['type']} @@ -96,9 +106,11 @@ class History : self.history.pop(self.ordercorpus[analyse['uuid']]) if analyse['uuid'] in self.openedcorpus : del self.openedcorpus[analyse['uuid']] - else : + elif analyse['uuid'] in self.analyses : todel = [i for i, ana in enumerate(self.corpus[analyse['corpus']]['analyses']) if ana['uuid'] == analyse['uuid']][0] self.history[self.ordercorpus[analyse['corpus']]]['analyses'].pop(todel) + elif analyse['uuid'] in self.matrixanalyse : + self.matrix = [mat for mat in self.matrix if mat['uuid'] != analyse['uuid']] self.write() self.read() diff --git a/iramuteq.py b/iramuteq.py index aad2ebd..c59894b 100644 --- a/iramuteq.py +++ b/iramuteq.py @@ -28,8 +28,10 @@ import logging #------------------------------------ import wx #import wx.aui -import wx.lib.agw.aui as aui -#import agw.aui as aui +if wx.__version__ >= '2.11' : + import wx.lib.agw.aui as aui +else : + import aui import wx.html import wx.grid import wx.lib.hyperlink as hl diff --git a/layout.py b/layout.py index 0e2d5a9..2a30f5c 100644 --- a/layout.py +++ b/layout.py @@ -7,8 +7,10 @@ import os import wx import wx.lib.hyperlink as hl -#import agw.aui as aui -import wx.lib.agw.aui as aui +if wx.__version__ >= '2.11' : + import wx.lib.agw.aui as aui +else : + import aui from chemins import ConstructPathOut, ChdTxtPathOut, FFF, ffr, PathOut, StatTxtPathOut, simipath from ConfigParser import ConfigParser from functions import ReadProfileAsDico, GetTxtProfile, read_list_file, ReadList, exec_rcode, print_liste, BugReport, DoConf, indices_simi, check_Rresult, progressbar @@ -303,16 +305,16 @@ class OpenCHDS(): Profile = DictPathOut['PROFILE_OUT'] AntiProfile = DictPathOut['ANTIPRO_OUT'] + self.encoding = self.corpus.parametres['syscoding'] if isinstance(self.corpus, Corpus) : - self.encoding = self.corpus.parametres['syscoding'] self.corpus.make_ucecl_from_R(self.pathout['uce']) - elif 'tableau' in dir(gparent) : - self.encoding = gparent.tableau.parametres['syscoding'] + corpname = self.corpus.parametres['corpus_name'] + else : + corpname = self.corpus.parametres['name'] clnb = parametres['clnb'] dlg = progressbar(self, maxi = 4 + clnb) self.clnb = clnb - corpname = self.corpus.parametres['corpus_name'] print 'lecture des profils' dlg.Update(2, u'lecture des profils') @@ -336,12 +338,14 @@ class OpenCHDS(): if isinstance(self.corpus, Corpus) : panel.corpus = self.corpus - panel.dictpathout = self.DictPathOut - panel.pathout = self.DictPathOut - panel.parent = self.parent - panel.DictProfile = self.DictProfile - panel.cluster_size = self.cluster_size - panel.debtext = self.debtext + else : + panel.tableau = self.corpus + panel.dictpathout = self.DictPathOut + panel.pathout = self.DictPathOut + panel.parent = self.parent + panel.DictProfile = self.DictProfile + panel.cluster_size = self.cluster_size + panel.debtext = self.debtext # self.ID_rapport = wx.NewId() # #rap_img = wx.Image(os.path.join(self.parent.images_path,'icone_rap_16.png'), wx.BITMAP_TYPE_ANY).ConvertToBitmap() @@ -365,8 +369,11 @@ class OpenCHDS(): if isinstance(self.corpus, Corpus) : panel.TabChdSim.corpus = corpus panel.TabChdSim.corpus.dictpathout = self.DictPathOut - panel.parametres = self.parametres - self.panel = panel + else : + panel.TabChdSim.tableau = corpus + panel.TabChdSim.tableau.dictpathout = self.DictPathOut + panel.parametres = self.parametres + self.panel = panel self.notenb = self.parent.nb.GetPageCount() @@ -542,7 +549,7 @@ class OpenCHDS(): -def PrintRapport(self, corpus, parametres, txt = True): +def PrintRapport(self, corpus, parametres, istxt = True): #if sys.platform == 'win32': # sep = '\r\n' #else: @@ -554,8 +561,9 @@ def PrintRapport(self, corpus, parametres, txt = True): """ % datetime.datetime.now().ctime() - totocc = corpus.gettotocc() - if txt : + print istxt + if istxt : + totocc = corpus.gettotocc() txt += u'nombre d\'uci: %i%s' % (corpus.getucinb(), sep) txt += u'nombre d\'uce: %i%s' % (corpus.getucenb(), sep) txt += u'nombre de formes: %i%s' % (len(corpus.formes), sep) @@ -571,24 +579,24 @@ def PrintRapport(self, corpus, parametres, txt = True): txt += u'taille uc1 : %i\n' % parametres['tailleuc1'] else: txt += u'taille uc1 / uc2: %i / %i - %i / %i%s' % (parametres['tailleuc1'], parametres['tailleuc2'], parametres['lenuc1'], parametres['lenuc2'], sep) - elif not txt : + else : self.Ucenb = self.nbind txt += u'nombre d\'individus : %i%s' % (self.nbind, sep) txt += u'nombre de classes : %i%s' % (self.clnb, sep) - if txt : + if istxt : txt += u'nombre de classes : %i%s' % (parametres['clnb'], sep) if parametres['classif_mode'] == 0 or parametres['classif_mode'] == 1 : txt += u'%i uce classées sur %i (%.2f%%)%s' % (sum([len(cl) for cl in corpus.lc]), corpus.getucenb(), (float(sum([len(cl) for cl in corpus.lc])) / float(corpus.getucenb())) * 100, sep) elif self.parametres['classif_mode'] == 2 : txt += u'%i uci classées sur %i (%.2f%%)%s' % (sum([len(cl) for cl in corpus.lc]), corpus.getucinb(), (float(sum([len(cl) for cl in corpus.lc]))) / float(corpus.getucinb()) * 100, sep) - elif analyse == 'quest' : + else : txt += u'%i uce classées sur %i (%.2f%%)%s' % (self.ucecla, self.Ucenb, (float(self.ucecla) / float(self.Ucenb)) * 100, sep) txt += """ ########################### temps d'analyse : %s ########################### -""" % parametres['time'] +""" % parametres.get('time', '') with open(self.pathout['pre_rapport'], 'w') as f : f.write(txt) diff --git a/listlex.py b/listlex.py index 2237fcc..75607f3 100644 --- a/listlex.py +++ b/listlex.py @@ -67,7 +67,7 @@ class ListForSpec(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSor self.SetColumnWidth(0, 180) for i in range(1,len(first)-1): - self.SetColumnWidth(i, len(first[i]) * 10) + self.SetColumnWidth(i, self.checkcolumnwidth(len(first[i]) * 10)) self.itemDataMap = dlist self.itemIndexMap = dlist.keys() @@ -88,6 +88,12 @@ class ListForSpec(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSor #----------------------------------------------------------------------------------------- + def checkcolumnwidth(self, width) : + if width < 80 : + return 80 + else : + return width + def OnGetItemText(self, item, col): index=self.itemIndexMap[item] s = self.itemDataMap[index][col] diff --git a/openanalyse.py b/openanalyse.py index 29a0d63..2cf40c8 100644 --- a/openanalyse.py +++ b/openanalyse.py @@ -4,7 +4,7 @@ #Copyright (c) 2008-2012, Pierre Ratinaud #Lisense: GNU/GPL -from chemins import ChdTxtPathOut, StatTxtPathOut, construct_simipath +from chemins import ChdTxtPathOut, StatTxtPathOut, construct_simipath, PathOut from layout import OpenCHDS, dolexlayout, StatLayout, WordCloudLayout, OpenCorpus, SimiLayout from corpus import Corpus, copycorpus from tableau import Tableau @@ -12,6 +12,7 @@ import os import shelve from tabsimi import DoSimi from functions import BugReport, DoConf +from tableau import Tableau import logging log = logging.getLogger('iramuteq.openanalyse') @@ -30,7 +31,7 @@ class OpenAnalyse(): if self.conf['type'] == 'corpus' : corpus = self.opencorpus() - elif self.conf['corpus'] in self.parent.history.corpus : + elif self.conf.get('corpus', False) in self.parent.history.corpus : if self.conf['uuid'] in self.parent.history.analyses : intree = True else : @@ -47,6 +48,19 @@ class OpenAnalyse(): self.doopen(corpus) else : corpus = None + if isinstance(parametres, dict) : + tableau = Tableau(parent, parametres['ira']) + else : + tableau = Tableau(parent, parametres) + tableau.parametres = self.conf + tableau.dictpathout = PathOut(filename = tableau.parametres['filename'], dirout = self.conf['pathout'], analyse_type = self.conf['type']) + tableau.dictpathout.basefiles(ChdTxtPathOut) + tableau.read_tableau(tableau.dictpathout['db']) + if self.parent.tree.IsInTree(uuid = self.conf['uuid']) : + self.parent.tree.GiveFocus(uuid = self.conf['uuid'], bold = True) + else : + self.parent.tree.AddAnalyse(self.conf, bold = True) + self.doopen(tableau) self.parent.history.addtab(self.conf) def redopath(self, conf, path) : @@ -116,4 +130,7 @@ class OpenAnalyse(): elif self.conf['type'] == 'wordcloud' : self.parent.ShowMenu(_("Text analysis")) WordCloudLayout(self.parent, corpus, self.conf) + elif self.conf['type'] == 'gnepamatrix' : + self.parent.ShowMenu(_("Spreadsheet analysis")) + OpenCHDS(self.parent, corpus, self.conf, Alceste = False) diff --git a/profile_segment.py b/profile_segment.py index 9a89195..1eeee9c 100644 --- a/profile_segment.py +++ b/profile_segment.py @@ -6,8 +6,12 @@ import tempfile from ProfList import * +import wx +if wx.__version__ >= '2.11' : #import agw.aui as aui -import wx.lib.agw.aui as aui + import wx.lib.agw.aui as aui +else : + import aui from functions import exec_rcode, check_Rresult, ReadProfileAsDico, ReadList from listlex import * from dialog import PrefSegProf, PrefProfTypes diff --git a/tabchdalc.py b/tabchdalc.py index 39fd377..03d7a66 100644 --- a/tabchdalc.py +++ b/tabchdalc.py @@ -4,15 +4,15 @@ #Copyright (c) 2008-2009 Pierre Ratinaud #Lisense: GNU/GPL -from chemins import ConstructPathOut, ChdTxtPathOut, ConstructAfcUciPath, ffr +from chemins import ConstructPathOut, ChdTxtPathOut, ConstructAfcUciPath, ffr, PathOut from functions import sortedby, CreateIraFile, print_liste, exec_rcode, check_Rresult from PrintRScript import RchdQuest from layout import OpenCHDS, PrintRapport from dialog import PrefQuestAlc +from analysematrix import AnalyseMatrix import os import sys import wx -from numpy import * import tempfile import time @@ -22,52 +22,63 @@ class AnalyseQuest(): dlg = PrefQuestAlc(parent) dlg.CenterOnParent() self.val = dlg.ShowModal() + parametres = parent.tableau.parametre if self.val == wx.ID_OK : + parametres['nbcl_p1'] = dlg.spin_nbcl.GetValue() + parametres['mincl'] = dlg.spin_mincl.GetValue() if dlg.m_radioBox1.GetSelection() == 1 : - ListAct = dlg.nactives - ListSup = dlg.varsup - nbcl_p1 = dlg.spin_nbcl.GetValue() - mincl = dlg.spin_mincl.GetValue() - DoQuestAlceste(parent, ListAct, ListSup, nbcl = nbcl_p1, mincl = mincl) - else: - nbcl_p1 = dlg.spin_nbcl.GetValue() - mincl = dlg.spin_mincl.GetValue() - DoQuestAlceste(parent, nbcl = nbcl_p1, mincl = mincl) + parametres['listact'] = dlg.nactives + parametres['listsup'] = dlg.varsup + else : + parametres['formatted'] = 1 + DoQuestAlceste(parent, parametres) -class DoQuestAlceste: - def __init__(self, parent, ListAct=False, ListSup=False, nbcl = 10, mincl = 10): - self.t1 = time.time() -#------------------------------------------------------------------- - dlg = wx.ProgressDialog("Traitements", - "Veuillez patienter...", - maximum=5, - parent=parent, - style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_ELAPSED_TIME - ) - dlg.Center() - count = 1 - keepGoing = dlg.Update(count) -#------------------------------------------------------------------- - self.pathout = ConstructPathOut(parent.tableau.parametre['filename'], 'AlcesteQuest') +class DoQuestAlceste(AnalyseMatrix): + def __init__(self, parent, parametres): + parametres['pathout'] = ConstructPathOut(parent.tableau.parametre['filename'], 'gnepaMatrix') + self.parametres = parametres + self.parametres['type'] = 'gnepamatrix' self.DictForme = {} self.DictFormeSup = {} self.Min = 10 self.Linecontent = [] self.parent = parent self.RPath = self.parent.PathPath.get('PATHS', 'rpath') - self.dictpathout = ChdTxtPathOut(self.pathout) - self.parent.tableau.dictpathout = self.dictpathout + #self.dictpathout = PathOut(dirout = self.pathout) + #self.dictpathout = self.pathout + #self.dictpathout.basefiles(ChdTxtPathOut) + #self.pathout = self.dictpathout self.clnb = '' - self.ListAct = ListAct + self.ListAct = parametres.get('listact', False) self.ucecla = '' - self.parent = parent + dlg = wx.ProgressDialog("Traitements", + "Veuillez patienter...", + maximum=5, + parent=self.parent, + style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_ELAPSED_TIME + ) + + AnalyseMatrix.__init__(self, parent, parent.tableau, self.parametres, dlg = dlg) + + + #----------------------------------------------------------- + def doanalyse(self) : +#------------------------------------------------------------------- + self.dictpathout = self.pathout + self.dictpathout.basefiles(ChdTxtPathOut) + self.parent.tableau.dictpathout = self.dictpathout + + self.dlg.Center() + count = 1 + keepGoing = self.dlg.Update(count) +#------------------------------------------------------------------- count += 1 - dlg.Update(count, u"passage en O/1") - if not ListAct: - self.parent.tableau.make_01_alc_format(self.dictpathout['Act01']) + self.dlg.Update(count, u"passage en O/1") + if 'formatted' in self.parametres: + self.parent.tableau.make_01_alc_format(self.dictpathout['mat01']) else: - self.parent.tableau.make_01_from_selection(ListAct, ListSup) + self.parent.tableau.make_01_from_selection(self.parametres['listact'], self.parametres['listsup']) file = open(self.dictpathout['listeuce1'], 'w') file.write('num uce;num uc\n') for i in range(0, len(self.parent.tableau.linecontent)): @@ -75,27 +86,28 @@ class DoQuestAlceste: file.close() self.nbind = len(self.parent.tableau.linecontent) #------------------------------------------------------------ - RchdQuest(self.dictpathout, parent.RscriptsPath, nbcl, mincl) + RchdQuest(self.dictpathout, self.parent.RscriptsPath, self.parametres['nbcl_p1'], self.parametres['mincl']) #------------------------------------------------------------ count += 1 - dlg.Update(count, u"Analyse (patientez...)") + self.dlg.Update(count, u"Analyse (patientez...)") pid = exec_rcode(self.RPath, self.dictpathout['Rchdquest'], wait = False) while pid.poll() == None : - dlg.Pulse(u"Analyse (patientez...)") + self.dlg.Pulse(u"Analyse (patientez...)") time.sleep(0.2) check_Rresult(self.parent, pid) #------------------------------------------------------------ count += 1 - dlg.Update(count, u"Ecriture des résultats") + self.dlg.Update(count, u"Ecriture des résultats") self.parent.tableau.buildprofil() self.clnb = self.parent.tableau.clnb + self.parametres['clnb'] = self.clnb self.ucecla = self.parent.tableau.ucecla self.BuildProfile() temps = time.time() - self.t1 - PrintRapport(self, 'quest') + PrintRapport(self, self, {}, istxt = False) self.parent.tableau.save_tableau(self.dictpathout['db']) - CreateIraFile(self.dictpathout, self.clnb, corpname = os.path.basename(parent.filename), section = 'questionnaire') + #CreateIraFile(self.dictpathout, self.clnb, corpname = os.path.basename(self.parent.filename), section = 'questionnaire') afc_graph_list = [[os.path.basename(self.dictpathout['AFC2DL_OUT']), u'Variables actives - coordonnées - facteurs 1 / 2'], [os.path.basename(self.dictpathout['AFC2DSL_OUT']), u'variables illustratives - coordonnées - facteurs 1 / 2'], [os.path.basename(self.dictpathout['AFC2DCL_OUT']), u'Classes - Coordonnées - facteur 1 / 2'], @@ -108,11 +120,11 @@ class DoQuestAlceste: print_liste(self.dictpathout['liste_graph_chd'], chd_graph_list) self.tableau = self.parent.tableau - OpenCHDS(self.parent, self, self.dictpathout['ira'], False) + #OpenCHDS(self.parent, self, self.dictpathout['ira'], False) #------------------------------------------------------------ print 'fini', time.time() - self.t1 count += 1 - dlg.Update(count, "Fini") + self.dlg.Update(count, "Fini") def BuildProfile(self): print 'build profile' diff --git a/tabchddist.py b/tabchddist.py index 2508bb4..6010eaa 100644 --- a/tabchddist.py +++ b/tabchddist.py @@ -13,7 +13,6 @@ from ConfigParser import ConfigParser from functions import CreateIraFile, print_liste, exec_rcode, check_Rresult from dialog import CHDDialog, PrefQuestAlc, ClusterNbDialog import tempfile -from numpy import * import time diff --git a/tableau.py b/tableau.py index a8f7428..06f58e8 100644 --- a/tableau.py +++ b/tableau.py @@ -7,12 +7,15 @@ import codecs import sys import xlrd import ooolib +import os import tempfile import re import htmlentitydefs -from numpy import * import shelve +from uuid import uuid4 +import logging +log = logging.getLogger('iramuteq.tableau') ## # Removes HTML or XML character references and entities from a text string. @@ -55,8 +58,11 @@ class Tableau() : self.parametre = {'filename' : filename} self.parametre['filetype'] = filetype self.parametre['encodage'] = encodage + self.parametre['pathout'] = os.path.dirname(os.path.abspath(filename)) self.parametre['mineff'] = 3 self.parametre['syscoding'] = sys.getdefaultencoding() + self.parametre['type'] = 'matrix' + self.parametre['name'] = 'unNOm' self.sups = {} self.actives = {} self.listactives = None @@ -71,6 +77,7 @@ class Tableau() : self.colnb = 0 self.rownb = 0 self.classes = [] + self.parametres = self.parametre def read_tableau(self, fileout) : d=shelve.open(fileout) @@ -200,9 +207,8 @@ class Tableau() : return dico def select_col(self, listcol) : - ArTable = array(self.linecontent) - selcol = ArTable[:, listcol] - selcol = selcol.tolist() + dc = dict(zip(listcol, listcol)) + selcol = [[val for i, val in enumerate(row) if i in dc] for row in self.linecontent] return selcol def write01(self, fileout, dico, linecontent) : @@ -220,10 +226,7 @@ class Tableau() : def make_01_from_selection(self, listact, listsup = None, dowrite = True) : selcol = self.select_col(listact) self.actives = self.make_dico(selcol) - if 'Act01' in self.dictpathout and dowrite: - self.write01(self.dictpathout['Act01'], self.actives, selcol) - elif 'mat01' in self.dictpathout and dowrite: - self.write01(self.dictpathout['mat01'], self.actives, selcol) + self.write01(self.dictpathout['mat01'], self.actives, selcol) if listsup is not None : selcol = self.select_col(listsup) self.sups = self.make_dico(selcol) diff --git a/tabsimi.py b/tabsimi.py index 2af9792..8a5ca35 100644 --- a/tabsimi.py +++ b/tabsimi.py @@ -10,8 +10,10 @@ from dialog import SelectColDial, OptLexi from guifunct import PrefSimi from listlex import * import wx -import wx.lib.agw.aui as aui -from numpy import * +if wx.__version__ >= '2.11' : + import wx.lib.agw.aui as aui +else : + import aui import os import tempfile import datetime diff --git a/tabstudent.py b/tabstudent.py index 031e4e7..fd34232 100644 --- a/tabstudent.py +++ b/tabstudent.py @@ -12,7 +12,6 @@ import wx import os import sys import tempfile -from numpy import * from functions import exec_rcode, check_Rresult from time import sleep diff --git a/textaslexico.py b/textaslexico.py index a866276..5ff4c37 100644 --- a/textaslexico.py +++ b/textaslexico.py @@ -208,10 +208,11 @@ class Lexico(AnalyseText) : tmpscript = open(tmpfile, 'w') tmpscript.write(txt) tmpscript.close() - pid = exec_rcode(self.parent.RPath, tmpfile, wait = False) - while pid.poll() == None : - sleep(0.2) - check_Rresult(self.parent, pid) + self.doR(tmpfile, dlg = self.dlg, message = 'R...') + #pid = exec_rcode(self.parent.RPath, tmpfile, wait = False) + #while pid.poll() == None : + # sleep(0.2) + #check_Rresult(self.parent, pid) def preferences(self) : listet = self.corpus.make_etoiles() @@ -294,9 +295,11 @@ class Lexico(AnalyseText) : tabout = self.corpus.make_efftype_from_etoiles(self.listet) write_tab(tabout, self.dictpathout['tabletypem']) - #dlg.Update(2, u'R...') + if self.dlg : + self.dlg.Update(2, u'R...') self.DoR() - #dlg.Update(3, u'Chargement...') + if self.dlg : + self.dlg.Update(3, u'Chargement...') afcf_graph_list = [[os.path.basename(self.dictpathout['afcf_row']), u'lignes'],\ [os.path.basename(self.dictpathout['afcf_col']), u'colonnes']] afct_graph_list = [[os.path.basename(self.dictpathout['afct_row']), u'lignes'],\ diff --git a/textsimi.py b/textsimi.py index 918ee44..8650be1 100644 --- a/textsimi.py +++ b/textsimi.py @@ -61,21 +61,21 @@ class SimiTxt(AnalyseText): else : return False - def preferences(self) : - dial = StatDialog(self, self.parent) - dial.CenterOnParent() - val = dial.ShowModal() - if val == 5100 : - if dial.radio_lem.GetSelection() == 0 : - lem = 1 - else : - lem = 0 - self.parametres['lem'] = lem - dial.Destroy() - return self.parametres - else : - dial.Destroy() - return None +# def preferences(self) : +# dial = StatDialog(self, self.parent) +# dial.CenterOnParent() +# val = dial.ShowModal() +# if val == 5100 : +# if dial.radio_lem.GetSelection() == 0 : +# lem = 1 +# else : +# lem = 0 +# self.parametres['lem'] = lem +# dial.Destroy() +# return self.parametres +# else : +# dial.Destroy() +# return None def makesimiparam(self) : self.paramsimi = {'coeff' : 0, diff --git a/textstat.py b/textstat.py index d14522f..afc9ff1 100644 --- a/textstat.py +++ b/textstat.py @@ -31,20 +31,7 @@ class Stat(AnalyseText) : self.make_stats() def preferences(self) : - dial = StatDialog(self, self.parent) - dial.CenterOnParent() - val = dial.ShowModal() - if val == 5100 : - if dial.radio_lem.GetSelection() == 0 : - lem = 1 - else : - lem = 0 - self.parametres['lem'] = lem - dial.Destroy() - return self.parametres - else : - dial.Destroy() - return None + return self.parametres def make_stats(self): if self.dlg : diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..3b1b0d9 --- /dev/null +++ b/tools.py @@ -0,0 +1,156 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +#Author: Pierre Ratinaud +#Copyright (c) 2008-2013, Pierre Ratinaud +#Lisense: GNU GPL + +import codecs +import os +from dialog import ExtractDialog +from corpus import Corpus, copycorpus +import wx + + +parametres = {'filein' : 'corpus/lru2.txt', + 'encodein' : 'utf8', + 'encodeout' : 'utf8', + 'mods' : [u'*annee_2010', u'*annee_2011']} + +def istext(line) : + if line.startswith(u'**** ') : + return True + else : + return False + +def testvar(line, variable) : + line = line.split() + varmod = [val.split('_') for val in line[1:]] + vars = [var[0] for var in varmod] + if variable in vars : + return '_'.join([variable, varmod[vars.index(variable)][1]]).replace(u'*','') + else : + return False + +def testmod(line, mods) : + line = line.split() + for mod in mods : + if mod in line[1:] : + return mod.replace(u'*','') + return False + + +class Extract : + def __init__(self, parent, option) : + dial = ExtractDialog(parent, option) + dial.CenterOnParent() + res = dial.ShowModal() + if res == wx.ID_OK : + parametres = dial.make_param() + if option == 'splitvar' : + SplitFromVar(parametres) + else : + ExtractMods(parametres) + +class SplitFromVar : + def __init__(self, parametres) : + self.filein = parametres['filein'] + self.var = parametres['var'] + self.encodein = parametres['encodein'] + self.encodeout = parametres['encodeout'] + self.basepath = os.path.dirname(self.filein) + self.doparse() + + def doparse(self) : + keepline = False + filedict = {} + with codecs.open(self.filein, 'r', self.encodein) as fin : + for line in fin : + if istext(line) : + varmod = testvar(line, self.var) + if varmod : + keepline = True + if varmod not in filedict : + filename = os.path.join(self.basepath, varmod + '.txt') + filedict[varmod] = open(filename, 'w') + fileout = filedict[varmod] + else : + keepline = False + if keepline : + fileout.write(line.encode(self.encodeout)) + for f in filedict : + filedict[f].close() + +class ExtractMods : + def __init__(self, parametres) : + self.onefile = parametres.get('onefile', False) + self.filein = parametres['filein'] + self.mods = parametres['mods'] + self.encodein = parametres['encodein'] + self.encodeout = parametres['encodeout'] + self.basepath = os.path.dirname(self.filein) + if self.onefile : + filename = os.path.join(self.basepath, '_'.join([mod.replace(u'*','') for mod in self.mods])+'.txt') + self.fileout = open(filename, 'w') + self.doparse() + + def doparse(self) : + keepline = False + filedict = {} + with codecs.open(self.filein, 'r', self.encodein) as fin : + for line in fin : + if istext(line) : + modinline = testmod(line, self.mods) + if modinline : + keepline = True + if not self.onefile : + if modinline not in filedict : + filename = os.path.join(self.basepath, modinline + '.txt') + filedict[modinline] = open(filename, 'w') + fileout = filedict[modinline] + else : + fileout = self.fileout + else : + keepline = False + if keepline : + fileout.write(line.encode(self.encodeout)) + if not self.onefile : + for f in filedict : + filedict[f].close() + else : + self.fileout.close() + + +class SubCorpus(Corpus) : + def __init__(self, parent, corpus, sgts) : + Corpus.__init__(self, parent, corpus.parametres) + self.sgts = sgts + self.corpus = copycorpus(corpus) + self.corpus.make_lems(self.parametres['lem']) + textes = list(set([corpus.getucefromid(sgt).uci for sgt in sgts])) + self.ucis = [corpus.ucis[i] for i in textes] + for texte in self.ucis : + texte.uces = [uce for uce in texte.uces if uce.ident in self.sgts] + self.make_formes(corpus) + self.pathout = corpus.pathout + self.parametres['sub'] = self.sgts + + def make_formes(self, corpus) : + self.formes = {} + for forme in self.corpus.formes : + sgtseff = self.corpus.getformeuceseff(forme) + sgts = set(self.sgts).intersection(sgtseff.keys()) + if len(sgts) : + self.formes[forme] = self.corpus.formes[forme] + self.formes[forme].freq = sum([sgtseff[sgt] for sgt in sgts]) + + def getlemuces(self, lem) : + return list(set(self.sgts).intersection(self.corpus.getlemuces(lem))) + + + + + + +if __name__ == '__main__' : + #SplitFromVar(parametres) + ExtractMods(parametres, True) diff --git a/tree.py b/tree.py index ea3d153..e9ab091 100644 --- a/tree.py +++ b/tree.py @@ -114,7 +114,7 @@ class LeftTree(CT.CustomTreeCtrl): self.history = parent.history self.h = self.history.history self.root = self.AddRoot("Iramuteq") - + if not(self.GetAGWWindowStyleFlag() & CT.TR_HIDE_ROOT): self.SetPyData(self.root, None) self.SetItemImage(self.root, 24, CT.TreeItemIcon_Normal) @@ -129,11 +129,17 @@ class LeftTree(CT.CustomTreeCtrl): if 'analyses' in corpus : for y in corpus['analyses'] : last = self.AppendItem(child, y['name'], ct_type=0) - self.SetPyData(last, y) self.SetItemImage(last, 24, CT.TreeItemIcon_Normal) self.SetItemImage(last, 13, CT.TreeItemIcon_Expanded) - + + for matrix in self.history.matrix : + last = self.AppendItem(self.root, matrix['name']) + self.SetPyData(last, matrix) + self.SetItemImage(last, 24, CT.TreeItemIcon_Normal) + self.SetItemImage(last, 13, CT.TreeItemIcon_Expanded) + + self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) #self.Bind(wx.EVT_IDLE, self.OnIdle) @@ -258,6 +264,19 @@ class LeftTree(CT.CustomTreeCtrl): self.GiveFocus(child, uuid, bold) child, cookie = self.GetNextChild(itemParent, cookie) + def IsInTree(self, itemParent = None, uuid = None) : + if itemParent is None : + itemParent = self.root + child, cookie = self.GetFirstChild(itemParent) + while child : + pydata = self.GetPyData(child) + if pydata['uuid'] == uuid : + return True + self.GiveFocus(child, uuid) + child, cookie = self.GetNextChild(itemParent, cookie) + return False + + def OnRightDown(self, event): pt = event.GetPosition() @@ -656,21 +675,24 @@ class LeftTree(CT.CustomTreeCtrl): dlg.Destroy() def AddAnalyse(self, parametres, itemParent = None, bold = True) : - uuid = parametres['corpus'] - if itemParent is None : - itemParent = self.root - child, cookie = self.GetFirstChild(itemParent) - corpus = None - while child : - pydata = self.GetPyData(child) - if pydata['uuid'] == uuid : - corpus = child - break - self.GiveFocus(child, uuid) - child, cookie = self.GetNextChild(itemParent, cookie) - #item = self.AppendItem(child, parametres['name']) - if corpus is not None : - item = self.AppendItem(corpus, parametres['name']) + uuid = parametres.get('corpus', None) + if uuid is not None : + if itemParent is None : + itemParent = self.root + child, cookie = self.GetFirstChild(itemParent) + corpus = None + while child : + pydata = self.GetPyData(child) + if pydata['uuid'] == uuid : + corpus = child + break + self.GiveFocus(child, uuid) + child, cookie = self.GetNextChild(itemParent, cookie) + #item = self.AppendItem(child, parametres['name']) + if corpus is not None : + item = self.AppendItem(corpus, parametres['name']) + else : + item = self.AppendItem(self.root, parametres['name']) else : item = self.AppendItem(self.root, parametres['name']) self.SetPyData(item, parametres) diff --git a/ttparser.py b/ttparser.py index 33a3766..d300d3a 100644 --- a/ttparser.py +++ b/ttparser.py @@ -4,7 +4,6 @@ #a simple treetagger parser import codecs -import numpy import time import re from functions import ReadDicoAsDico