#=============================================================================== # Copyright (C) 2010 Darriele # # This file is part of pyfa. # # pyfa is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pyfa is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pyfa. If not, see . #=============================================================================== import wx import wx.lib.newevent import gui.utils.colorUtils as colorUtils import gui.utils.drawUtils as drawUtils import gui.utils.fonts as fonts from gui.bitmapLoader import BitmapLoader import gui.utils.fonts as fonts import service _PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent() _PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent() _PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent() _PageClosing, EVT_NOTEBOOK_PAGE_CLOSING = wx.lib.newevent.NewEvent() PageAdded, EVT_NOTEBOOK_PAGE_ADDED = wx.lib.newevent.NewEvent() PageClosed, EVT_NOTEBOOK_PAGE_CLOSED = wx.lib.newevent.NewEvent() class VetoAble(): def __init__(self): self.__vetoed = False def Veto(self): self.__vetoed = True def isVetoed(self): return self.__vetoed class NotebookTabChangeEvent(): def __init__(self, old, new): self.__old = old self.__new = new def GetOldSelection(self): return self.__old def GetSelection(self): return self.__new OldSelection = property(GetOldSelection) Selection = property(GetSelection) class PageChanging(_PageChanging, NotebookTabChangeEvent, VetoAble): def __init__(self, old, new): NotebookTabChangeEvent.__init__(self, old, new) _PageChanging.__init__(self) VetoAble.__init__(self) class PageChanged(_PageChanged, NotebookTabChangeEvent): def __init__(self, old, new): NotebookTabChangeEvent.__init__(self, old, new) _PageChanged.__init__(self) class PageClosing(_PageClosing, VetoAble): def __init__(self, i): self.__index = i _PageClosing.__init__(self) VetoAble.__init__(self) self.Selection = property(self.GetSelection) def GetSelection(self): return self.__index class PageAdding(_PageAdding, VetoAble): def __init__(self): _PageAdding.__init__(self) VetoAble.__init__(self) class PFNotebook(wx.Panel): def __init__(self, parent, canAdd=True): """ Instance of Pyfa Notebook. Initializes general layout, includes methods for setting current page, replacing pages, etc parent - wx parent element canAdd - True if tabs be deleted and added, passed directly to PFTabsContainer """ wx.Panel.__init__(self, parent, wx.ID_ANY, size=(-1, -1)) self.pages = [] self.activePage = None mainSizer = wx.BoxSizer(wx.VERTICAL) tabsSizer = wx.BoxSizer(wx.VERTICAL) self.tabsContainer = PFTabsContainer(self, canAdd=canAdd) tabsSizer.Add(self.tabsContainer, 0, wx.EXPAND) style = wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER contentSizer = wx.BoxSizer(wx.VERTICAL) self.pageContainer = wx.Panel(self, style=style) self.pageContainer.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) contentSizer.Add(self.pageContainer, 1, wx.EXPAND, 5) mainSizer.Add(tabsSizer, 0, wx.EXPAND, 5) mainSizer.Add(contentSizer, 1, wx.EXPAND | wx.BOTTOM, 2) self.SetSizer(mainSizer) self.Bind(wx.EVT_SIZE, self.OnSize) self.Layout() def GetPage(self, i): return self.pages[i] def SetPage(self, i, page): if i >= len(self.pages) or i is None or page is None: return oldPage = self.pages[i] self.pages[i] = page if oldPage == self.activePage: oldPage.Destroy() self.activePage = page else: oldPage.Destroy() page.Reparent(self.pageContainer) if self.activePage == page: self.ShowActive() def GetBorders(self): """Gets border widths to better determine page size in ShowActive()""" bx = wx.SystemSettings_GetMetric(wx.SYS_BORDER_X) by = wx.SystemSettings_GetMetric(wx.SYS_BORDER_Y) if bx < 0: bx = 1 if by < 0: by = 1 return bx, by def ReplaceActivePage(self, page): self.SetPage(self.GetSelection(), page) def GetSelectedPage(self): return self.activePage def GetPageIndex(self, page): return self.pages.index(page) if page in self.pages else None def GetSelection(self): return self.GetPageIndex(self.activePage) def GetCurrentPage(self): return self.activePage def GetPageCount(self): return len(self.pages) def NextPage(self): """Used with keyboard shortcut for next page navigation""" cpage = self.GetSelection() if cpage is None: return if cpage < self.GetPageCount() - 1: self.SetSelection(cpage + 1) npage = cpage + 1 else: self.SetSelection(0) npage = 0 wx.PostEvent(self, PageChanged(cpage, npage)) def PrevPage(self): """Used with keyboard shortcut for previous page navigation""" cpage = self.GetSelection() if cpage is None: return if cpage > 0: self.SetSelection(cpage - 1) npage = cpage - 1 else: self.SetSelection(self.GetPageCount() - 1) npage = self.GetPageCount() - 1 wx.PostEvent(self, PageChanged(cpage, npage)) def AddPage(self, tabWnd=None, tabTitle="Empty Tab", tabImage=None, showClose=True): if self.activePage: self.activePage.Hide() if not tabWnd: tabWnd = wx.Panel(self) tabWnd.Reparent(self.pageContainer) self.pageContainer.Layout() self.pages.append(tabWnd) self.tabsContainer.AddTab(tabTitle, tabImage, showClose) self.activePage = tabWnd self.ShowActive(True) def SetSelection(self, page): oldsel = self.GetSelection() if oldsel != page: self.activePage.Hide() self.activePage = self.pages[page] self.tabsContainer.SetSelected(page) self.ShowActive() def DeletePage(self, n, internal=False): """ Deletes page. n -- index of page to be deleted internal -- True if we're deleting the page from the PFTabsContainer """ page = self.pages[n] self.pages.remove(page) page.Destroy() if not internal: # If we're not deleting from the tab, delete the tab # (deleting from the tab automatically deletes itself) self.tabsContainer.DeleteTab(n, True) sel = self.tabsContainer.GetSelected() if sel is not None: self.activePage = self.pages[sel] self.ShowActive() wx.PostEvent(self, PageChanged(-1, sel)) else: self.activePage = None def SwitchPages(self, src, dest): self.pages[src], self.pages[dest] = self.pages[dest], self.pages[src] def ShowActive(self, resizeOnly=False): """ Sets the size of the page and shows. The sizing logic adjusts for some minor sizing errors (scrollbars going beyond bounds) resizeOnly -- if we are not interested in showing the page, only setting the size @todo: is resizeOnly still needed? Was introduced with 8b8b97 in mid 2011 to fix a resizing bug with blank pages, cannot reproduce 13Sept2014 """ ww, wh = self.pageContainer.GetSize() bx, by = self.GetBorders() ww -= bx * 4 wh -= by * 4 self.activePage.SetSize((max(ww, -1), max(wh, -1))) self.activePage.SetPosition((0, 0)) if not resizeOnly: self.activePage.Show() self.Layout() def IsActive(self, page): return self.activePage == page def SetPageTitle(self, i, text, refresh=True): tab = self.tabsContainer.tabs[i] tab.text = text if refresh: self.tabsContainer.AdjustTabsSize() self.Refresh() def SetPageIcon(self, i, icon, refresh=True): tab = self.tabsContainer.tabs[i] tab.tabImg = icon if refresh: self.tabsContainer.AdjustTabsSize() self.Refresh() def SetPageTextIcon(self, i, text=wx.EmptyString, icon=None): self.SetPageTitle(i, text, False) self.SetPageIcon(i, icon, False) self.tabsContainer.AdjustTabsSize() self.Refresh() def Refresh(self): self.tabsContainer.Refresh() def OnSize(self, event): w, h = self.GetSize() self.tabsContainer.SetSize((w, -1)) self.tabsContainer.UpdateSize() self.tabsContainer.Refresh() self.Layout() if self.activePage: self.ShowActive() event.Skip() class PFTabRenderer: def __init__(self, size=(36, 24), text=wx.EmptyString, img=None, inclination=6 , closeButton=True): """ Renders a new tab text -- tab label img -- wxImage of tab icon inclination -- does not seem to affect class, maybe used to be a variable for custom drawn tab inclinations before there were bitmaps? closeButton -- True if tab can be closed """ # tab left/right zones inclination self.ctabLeft = BitmapLoader.getImage("ctableft", "gui") self.ctabMiddle = BitmapLoader.getImage("ctabmiddle", "gui") self.ctabRight = BitmapLoader.getImage("ctabright", "gui") self.ctabClose = BitmapLoader.getImage("ctabclose", "gui") self.leftWidth = self.ctabLeft.GetWidth() self.rightWidth = self.ctabRight.GetWidth() self.middleWidth = self.ctabMiddle.GetWidth() self.closeBtnWidth = self.ctabClose.GetWidth() width, height = size if width < self.leftWidth + self.rightWidth + self.middleWidth: width = self.leftWidth + self.rightWidth + self.middleWidth if height < self.ctabMiddle.GetHeight(): height = self.ctabMiddle.GetHeight() self.inclination = inclination self.text = text self.tabSize = (width, height) self.closeButton = closeButton self.selected = False self.closeBtnHovering = False self.tabBitmap = None self.tabBackBitmap = None self.cbSize = 5 self.padding = 4 self.font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) self.tabImg = img self.position = (0, 0) # Not used internally for rendering - helper for tab container self.InitTab() def SetPosition(self, position): self.position = position def GetPosition(self): return self.position def GetSize(self): return self.tabSize def SetSize(self, size): w, h = size if w < self.leftWidth + self.rightWidth + self.middleWidth: w = self.leftWidth + self.rightWidth + self.middleWidth if h < self.ctabMiddle.GetHeight(): h = self.ctabMiddle.GetHeight() self.tabSize = (w, h) self.InitTab() def SetSelected(self, sel=True): self.selected = sel self.InitTab() def GetSelected(self): return self.selected def IsSelected(self): return self.selected def ShowCloseButtonHovering(self, hover=True): if self.closeBtnHovering != hover: self.closeBtnHovering = hover self._Render() def GetCloseButtonHoverStatus(self): return self.closeBtnHovering def GetTabRegion(self): nregion = self.CopyRegion(self.tabRegion) nregion.SubtractRegion(self.closeBtnRegion) if self.closeButton else self.tabRegion return nregion def GetCloseButtonRegion(self): return self.CopyRegion(self.closeBtnRegion) def GetMinSize(self): ebmp = wx.EmptyBitmap(1, 1) mdc = wx.MemoryDC() mdc.SelectObject(ebmp) mdc.SetFont(self.font) textSizeX, textSizeY = mdc.GetTextExtent(self.text) totalSize = self.leftWidth + self.rightWidth + textSizeX + self.closeBtnWidth/2 + 16 + self.padding*2 mdc.SelectObject(wx.NullBitmap) return totalSize, self.tabHeight def SetTabImage(self, img): self.tabImg = img def CopyRegion(self, region): rect = region.GetBox() newRegion = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) newRegion.IntersectRegion(region) return newRegion def InitTab(self): self.tabWidth, self.tabHeight = self.tabSize self.contentWidth = self.tabWidth - self.leftWidth - self.rightWidth self.tabRegion = None self.closeBtnRegion = None self.InitColors() self.InitBitmaps() self.ComposeTabBack() self.InitTabRegions() self._Render() def InitBitmaps(self): """ Creates bitmap for tab Takes the bitmaps already set and replaces a known color (black) with the needed color, while also considering selected state. Color dependant on platform -- see InitColors(). """ if self.selected: tr, tg, tb = self.selectedColor else: tr, tg, tb = self.inactiveColor ctabLeft = self.ctabLeft.Copy() ctabRight = self.ctabRight.Copy() ctabMiddle = self.ctabMiddle.Copy() ctabLeft.Replace(0, 0, 0, tr, tg, tb) ctabRight.Replace(0, 0, 0, tr, tg, tb) ctabMiddle.Replace(0, 0, 0, tr, tg, tb) self.ctabLeftBmp = wx.BitmapFromImage(ctabLeft) self.ctabRightBmp = wx.BitmapFromImage(ctabRight) self.ctabMiddleBmp = wx.BitmapFromImage(ctabMiddle) self.ctabCloseBmp = wx.BitmapFromImage(self.ctabClose) def ComposeTabBack(self): """ Creates the tab background bitmap based upon calculated dimension values and modified bitmaps via InitBitmaps() """ bkbmp = wx.EmptyBitmap(self.tabWidth, self.tabHeight) mdc = wx.MemoryDC() mdc.SelectObject(bkbmp) #mdc.SetBackground(wx.Brush((0x12, 0x23, 0x32))) mdc.Clear() mdc.DrawBitmap(self.ctabLeftBmp, 0, 0) # set the left bitmap # convert middle bitmap and scale to tab width cm = self.ctabMiddleBmp.ConvertToImage() mimg = cm.Scale(self.contentWidth, self.ctabMiddle.GetHeight(), wx.IMAGE_QUALITY_NORMAL) mbmp = wx.BitmapFromImage(mimg) mdc.DrawBitmap(mbmp, self.leftWidth, 0 ) # set middle bitmap, offset by left # set right bitmap offset by left + middle mdc.DrawBitmap(self.ctabRightBmp, self.contentWidth + self.leftWidth, 0) mdc.SelectObject(wx.NullBitmap) #bkbmp.SetMaskColour((0x12, 0x23, 0x32)) if self.tabBackBitmap: del self.tabBackBitmap self.tabBackBitmap = bkbmp def InitTabRegions(self): """ Initializes regions for tab, which makes it easier to determine if given coordinates are incluced in a region """ self.tabRegion = wx.RegionFromBitmap(self.tabBackBitmap) self.closeBtnRegion = wx.RegionFromBitmap(self.ctabCloseBmp) self.closeBtnRegion.Offset(self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth()/2, (self.tabHeight - self.ctabCloseBmp.GetHeight())/2) def InitColors(self): """Determines colors used for tab, based on system settings""" self.tabColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) self.inactiveColor = colorUtils.GetSuitableColor(self.tabColor, 0.25) self.selectedColor = colorUtils.GetSuitableColor(self.tabColor, 0.10) def Render(self): return self.tabBitmap def _Render(self): """Renders the tab, complete with the icon, text, and close button""" if self.tabBitmap: del self.tabBitmap height = self.tabHeight #rect = wx.Rect(0, 0, self.tabWidth, self.tabHeight) canvas = wx.EmptyBitmap(self.tabWidth, self.tabHeight, 24) mdc = wx.MemoryDC() mdc.SelectObject(canvas) #mdc.SetBackground(wx.Brush ((0x12,0x23,0x32))) mdc.Clear() #r = copy.copy(rect) #r.top = r.left = 0 #r.height = height mdc.DrawBitmap(self.tabBackBitmap, 0, 0, True) if self.tabImg: bmp = wx.BitmapFromImage(self.tabImg) if self.contentWidth > 16: # @todo: is this conditional relevant anymore? # Draw tab icon mdc.DrawBitmap(bmp, self.leftWidth + self.padding - bmp.GetWidth()/2, (height - bmp.GetHeight())/2) textStart = self.leftWidth + self.padding + bmp.GetWidth()/2 else: textStart = self.leftWidth mdc.SetFont(self.font) maxsize = self.tabWidth - textStart - self.rightWidth - self.padding*4 color = self.selectedColor if self.selected else self.inactiveColor mdc.SetTextForeground(colorUtils.GetSuitableColor(color, 1)) text = drawUtils.GetPartialText(mdc, self.text, maxsize, "") tx, ty = mdc.GetTextExtent(text) mdc.DrawText(text, textStart + self.padding, height / 2 - ty / 2) if self.closeButton: if self.closeBtnHovering: cbmp = self.ctabCloseBmp else: cimg = self.ctabCloseBmp.ConvertToImage() cimg = cimg.AdjustChannels(0.7, 0.7, 0.7, 0.3) cbmp = wx.BitmapFromImage(cimg) mdc.DrawBitmap( cbmp, self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth()/2, (height - self.ctabCloseBmp.GetHeight())/2) mdc.SelectObject(wx.NullBitmap) canvas.SetMaskColour((0x12, 0x23, 0x32)) img = canvas.ConvertToImage() if not img.HasAlpha(): img.InitAlpha() bmp = wx.BitmapFromImage(img) self.tabBitmap = bmp class PFAddRenderer: def __init__(self): """Renders the add tab button""" self.addImg = BitmapLoader.getImage("ctabadd", "gui") self.width = self.addImg.GetWidth() self.height = self.addImg.GetHeight() self.region = None self.tbmp = wx.BitmapFromImage(self.addImg) self.addBitmap = None self.position = (0, 0) self.highlighted = False self.InitRenderer() def GetPosition(self): return self.position def SetPosition(self,pos): self.position = pos def GetSize(self): return self.width, self.height def GetHeight(self): return self.height def GetWidth(self): return self.width def InitRenderer(self): self.region = self.CreateRegion() self._Render() def CreateRegion(self): region = wx.RegionFromBitmap(self.tbmp) return region def CopyRegion(self, region): rect = region.GetBox() newRegion = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) newRegion.IntersectRegion(region) return newRegion def GetRegion(self): return self.CopyRegion(self.region) def Highlight(self, highlight=False): self.highlighted = highlight self._Render() def IsHighlighted(self): return self.highlighted def Render(self): return self.addBitmap def _Render(self): if self.addBitmap: del self.addBitmap alpha = 1 if self.highlighted else 0.3 img = self.addImg.AdjustChannels(1, 1, 1, alpha) bbmp = wx.BitmapFromImage(img) self.addBitmap = bbmp class PFTabsContainer(wx.Panel): def __init__(self, parent, pos=(0, 0), size=(100, 22), id=wx.ID_ANY, canAdd=True): """ Defines the tab container. Handles functions such as tab selection and dragging, and defines minimum width of tabs (all tabs are of equal width, which is determined via widest tab). Also handles the tab preview, if any. """ wx.Panel.__init__(self, parent, id, pos, size) if wx.VERSION >= (3,0): self.SetBackgroundStyle(wx.BG_STYLE_PAINT) self.tabs = [] width, height = size self.width = width self.height = height self.containerHeight = height self.startDrag = False self.dragging = False self.sFit = service.Fit.getInstance() self.inclination = 7 if canAdd: self.reserved = 48 else: self.reserved = self.inclination * 4 self.dragTrail = 3 # pixel distance to drag before we actually start dragging self.dragx = 0 self.dragy = 0 self.draggedTab = None self.dragTrigger = self.dragTrail self.showAddButton = canAdd self.tabContainerWidth = width - self.reserved self.tabMinWidth = width self.tabShadow = None self.addButton = PFAddRenderer() self.addBitmap = self.addButton.Render() self.previewTimer = None self.previewTimerID = wx.ID_ANY self.previewWnd = None self.previewBmp = None self.previewPos = None self.previewTab = None self.Bind(wx.EVT_TIMER, self.OnTimer) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_SYS_COLOUR_CHANGED, self.OnSysColourChanged) self.tabShadow = PFTabRenderer((self.tabMinWidth, self.height + 1), inclination=self.inclination) def OnSysColourChanged(self, event): for tab in self.tabs: tab.InitTab() self.Refresh() def OnSize(self, event): self.UpdateSize() event.Skip() def UpdateSize(self): width, _ = self.GetSize() if width != self.width: self.width = width self.tabContainerWidth = self.width - self.reserved self.AdjustTabsSize() def OnLeftDown(self, event): """Determines what happens when user left clicks (down)""" mposx, mposy = event.GetPosition() if not self.startDrag: tab = self.FindTabAtPos(mposx, mposy) if tab: self.CheckTabSelected(tab, mposx, mposy) if self.showAddButton: # If we can add tabs, we can drag them. Set flag self.startDrag = True tx, ty = tab.GetPosition() self.dragx = mposx - tx self.dragy = self.containerHeight - self.height self.Refresh() self.draggedTab = tab def OnLeftUp(self, event): """Determines what happens when user left clicks (up)""" mposx, mposy = event.GetPosition() if self.startDrag and self.dragging: self.dragging = False self.startDrag = False self.draggedTab = None self.dragTrigger = self.dragTrail self.UpdateTabsPosition() self.Refresh() if self.HasCapture(): self.ReleaseMouse() return if self.startDrag: self.startDrag = False self.dragTrigger = self.dragTrail # Checks if we selected the add button and, if True, returns if self.CheckAddButton(mposx, mposy): return # If there are no tabs, don't waste time if self.GetTabsCount() == 0: return # Gets selected tab (was set when user down clicked) selTab = self.GetSelectedTab() # Check if we selected close button for selected tab if self.CheckTabClose(selTab, mposx, mposy): return # Check if we selected close button for all others for tab in self.tabs: if self.CheckTabClose(tab, mposx, mposy): return def GetSelectedTab(self): for tab in self.tabs: if tab.GetSelected(): return tab return None def GetSelected(self): for tab in self.tabs: if tab.GetSelected(): return self.tabs.index(tab) return None def SetSelected(self, tabIndex): oldSelTab = self.GetSelectedTab() oldSelTab.SetSelected(False) self.tabs[tabIndex].SetSelected(True) self.Refresh() def CheckTabSelected(self, tab, x, y): """ Selects the tab at x, y. If the tab at x, y is already selected, simply return true. Otherwise, perform TabHitTest and set tab at position to selected """ oldSelTab = self.GetSelectedTab() if oldSelTab == tab: return True if self.TabHitTest(tab, x, y): tab.SetSelected(True) oldSelTab.SetSelected(False) ev = PageChanging(self.tabs.index(oldSelTab), self.tabs.index(tab)) wx.PostEvent(self.Parent, ev) if ev.isVetoed(): return False self.Refresh() selTab = self.tabs.index(tab) self.Parent.SetSelection(selTab) wx.PostEvent(self.Parent, PageChanged(self.tabs.index(oldSelTab), self.tabs.index(tab))) return True return False def CheckTabClose(self, tab, x, y): """Determines if close button was selected for the given tab.""" if not tab.closeButton: # if not able to close, return False return False closeBtnReg = tab.GetCloseButtonRegion() tabPosX, tabPosY = tab.GetPosition() closeBtnReg.Offset(tabPosX, tabPosY) if closeBtnReg.Contains(x, y): index = self.tabs.index(tab) ev = PageClosing(index) wx.PostEvent(self.Parent, ev) if ev.isVetoed(): return False index = self.GetTabIndex(tab) self.DeleteTab(index) wx.PostEvent(self.Parent, PageClosed(index=index)) sel = self.GetSelected() if sel is not None: wx.PostEvent(self.Parent, PageChanged(-1, sel)) return True return False def CheckAddButton(self, x, y): """Determines if add button was selected.""" if not self.showAddButton: # if not able to add, return False return reg = self.addButton.GetRegion() ax, ay = self.addButton.GetPosition() reg.Offset(ax, ay) if reg.Contains(x, y): ev = PageAdding() wx.PostEvent(self.Parent, ev) if ev.isVetoed(): return False self.Parent.AddPage() wx.PostEvent(self.Parent, PageAdded()) return True def CheckCloseButtons(self, x, y): """ Checks if mouse pos at x, y is over a close button. If so, set the close hovering flag for that tab """ dirty = False # @todo: maybe change to for...else for tab in self.tabs: closeBtnReg = tab.GetCloseButtonRegion() tabPos = tab.GetPosition() tabPosX, tabPosY = tabPos closeBtnReg.Offset(tabPosX,tabPosY) if closeBtnReg.Contains(x, y): if not tab.GetCloseButtonHoverStatus(): tab.ShowCloseButtonHovering(True) dirty = True else: if tab.GetCloseButtonHoverStatus(): tab.ShowCloseButtonHovering(False) dirty = True if dirty: self.Refresh() def FindTabAtPos(self, x, y): if self.GetTabsCount() == 0: return None selTab = self.GetSelectedTab() if self.TabHitTest(selTab, x, y): return selTab for tab in self.tabs: if self.TabHitTest(tab, x, y): return tab return None def TabHitTest(self, tab, x, y): tabRegion = tab.GetTabRegion() tabPos = tab.GetPosition() tabPosX, tabPosY = tabPos tabRegion.Offset(tabPosX, tabPosY) if tabRegion.Contains(x, y): return True return False def GetTabAtLeft(self, tabIndex): return self.tabs[tabIndex - 1] if tabIndex > 0 else None def GetTabAtRight(self, tabIndex): return self.tabs[tabIndex + 1] if tabIndex < self.GetTabsCount() - 1 else None def SwitchTabs(self, src, dest, draggedTab=None): self.tabs[src], self.tabs[dest] = self.tabs[dest], self.tabs[src] self.UpdateTabsPosition(draggedTab) self.Parent.SwitchPages(src, dest) self.Refresh() def GetTabIndex(self, tab): return self.tabs.index(tab) def OnMotion(self, event): """ Determines what happens when the mouse moves. This handles primarily dragging (region tab can be dragged) as well as checking if we are over an actionable button. """ mposx, mposy = event.GetPosition() if self.startDrag: if not self.dragging: if self.dragTrigger < 0: self.dragging = True self.dragTrigger = self.dragTrail self.CaptureMouse() else: self.dragTrigger -= 1 if self.dragging: dtx = mposx - self.dragx w, h = self.draggedTab.GetSize() if dtx < 0: dtx = 0 if dtx + w > self.tabContainerWidth + self.inclination * 2: dtx = self.tabContainerWidth - w + self.inclination * 2 self.draggedTab.SetPosition((dtx, self.dragy)) index = self.GetTabIndex(self.draggedTab) leftTab = self.GetTabAtLeft(index) rightTab = self.GetTabAtRight(index) if leftTab: lw, lh = leftTab.GetSize() lx, ly = leftTab.GetPosition() if lx + lw / 2 - self.inclination * 2 > dtx: self.SwitchTabs(index - 1, index, self.draggedTab) return if rightTab: rw, rh = rightTab.GetSize() rx, ry = rightTab.GetPosition() if rx + rw / 2 + self.inclination * 2 < dtx + w: self.SwitchTabs(index + 1, index, self.draggedTab) return self.UpdateTabsPosition(self.draggedTab) self.Refresh() return return self.CheckCloseButtons(mposx, mposy) self.CheckAddHighlighted(mposx, mposy) self.CheckTabPreview(mposx, mposy) event.Skip() def CheckTabPreview(self, mposx, mposy): """ Checks to see if we have a tab preview and sets up the timer for it to display """ if not self.sFit.serviceFittingOptions["showTooltip"] or False: return if self.previewTimer: if self.previewTimer.IsRunning(): if self.previewWnd: self.previewTimer.Stop() return if self.previewWnd: self.previewWnd.Show(False) del self.previewWnd self.previewWnd = None for tab in self.tabs: if not tab.GetSelected(): if self.TabHitTest(tab, mposx, mposy): try: page = self.Parent.GetPage(self.GetTabIndex(tab)) if hasattr(page, "Snapshot"): if not self.previewTimer: self.previewTimer = wx.Timer(self, self.previewTimerID) self.previewTab = tab self.previewTimer.Start(500, True) break except: pass def CheckAddHighlighted(self, x, y): """ Checks to see if x, y are in add button region, and sets the highlight flag """ if not self.showAddButton: return reg = self.addButton.GetRegion() ax, ay = self.addButton.GetPosition() reg.Offset(ax, ay) if reg.Contains(x, y): if not self.addButton.IsHighlighted(): self.addButton.Highlight(True) self.Refresh() else: if self.addButton.IsHighlighted(): self.addButton.Highlight(False) self.Refresh() def OnPaint(self, event): if "wxGTK" in wx.PlatformInfo: mdc = wx.AutoBufferedPaintDC(self) else: rect = self.GetRect() mdc = wx.BufferedPaintDC(self) selected = 0 if 'wxMac' in wx.PlatformInfo and wx.VERSION < (3,0): color = wx.Colour(0, 0, 0) brush = wx.Brush(color) from Carbon.Appearance import kThemeBrushDialogBackgroundActive brush.MacSetTheme(kThemeBrushDialogBackgroundActive) else: color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) brush = wx.Brush(color) if "wxGTK" not in wx.PlatformInfo: mdc.SetBackground (brush) mdc.Clear() selected = None tabsWidth = 0 for tab in self.tabs: tabsWidth += tab.tabWidth - self.inclination*2 pos = tabsWidth if self.showAddButton: ax,ay = self.addButton.GetPosition() mdc.DrawBitmap(self.addButton.Render(), ax, ay, True) for i in xrange(len(self.tabs) - 1, -1, -1): tab = self.tabs[i] width = tab.tabWidth - 6 posx, posy = tab.GetPosition() if not tab.IsSelected(): mdc.DrawBitmap(self.efxBmp, posx, posy, True ) bmp = tab.Render() img = bmp.ConvertToImage() img = img.AdjustChannels(1, 1, 1, 0.85) bmp = wx.BitmapFromImage(img) mdc.DrawBitmap(bmp, posx, posy, True) else: selected = tab if selected: posx, posy = selected.GetPosition() mdc.DrawBitmap(self.efxBmp, posx, posy, True) bmp = selected.Render() if self.dragging: img = bmp.ConvertToImage() img = img.AdjustChannels(1.2, 1.2, 1.2, 0.7) bmp = wx.BitmapFromImage(img) mdc.DrawBitmap(bmp, posx, posy, True) def OnErase(self, event): pass def UpdateTabFX(self): w, h = self.tabShadow.GetSize() if w != self.tabMinWidth: self.tabShadow.SetSize((self.tabMinWidth, self.height + 1)) fxBmp = self.tabShadow.Render() simg = fxBmp.ConvertToImage() if not simg.HasAlpha(): simg.InitAlpha() simg = simg.Blur(2) simg = simg.AdjustChannels(0.3, 0.3, 0.3, 0.35) self.efxBmp = wx.BitmapFromImage(simg) def AddTab(self, title=wx.EmptyString, img=None, showClose=False): self.ClearTabsSelected() tabRenderer = PFTabRenderer((120, self.height), title, img, self.inclination, closeButton=showClose) tabRenderer.SetSelected(True) self.tabs.append(tabRenderer) self.AdjustTabsSize() self.Refresh() def ClearTabsSelected(self): for tab in self.tabs: tab.SetSelected(False) def DeleteTab(self, tab, external=False): tabRenderer = self.tabs[tab] wasSelected = tabRenderer.GetSelected() self.tabs.remove(tabRenderer) if tabRenderer: del tabRenderer if wasSelected and self.GetTabsCount() > 0: if tab > self.GetTabsCount() - 1: self.tabs[self.GetTabsCount() - 1].SetSelected(True) else: self.tabs[tab].SetSelected(True) if not external: self.Parent.DeletePage(tab, True) self.AdjustTabsSize() self.Refresh() def GetTabsCount(self): return len(self.tabs) def AdjustTabsSize(self): tabMinWidth = 9000000 # Really, it should be over 9000 tabMaxWidth = 0 for tab in self.tabs: mw, mh = tab.GetMinSize() if tabMinWidth > mw: tabMinWidth = mw if tabMaxWidth < mw: tabMaxWidth = mw if tabMaxWidth < 100: tabMaxWidth = 100 if self.GetTabsCount() > 0: if (self.GetTabsCount()) * (tabMaxWidth - self.inclination * 2) > self.tabContainerWidth: self.tabMinWidth = float(self.tabContainerWidth) / float(self.GetTabsCount()) + self.inclination * 2 else: self.tabMinWidth = tabMaxWidth if self.tabMinWidth < 1: self.tabMinWidth = 1 for tab in self.tabs: w, h = tab.GetSize() tab.SetSize((self.tabMinWidth, self.height)) if self.GetTabsCount() > 0: self.UpdateTabFX() self.UpdateTabsPosition() def UpdateTabsPosition(self, skipTab=None): tabsWidth = 0 for tab in self.tabs: tabsWidth += tab.tabWidth - self.inclination*2 pos = tabsWidth selected = None for i in xrange(len(self.tabs) - 1, -1, -1): tab = self.tabs[i] width = tab.tabWidth - self.inclination*2 pos -= width if not tab.IsSelected(): tab.SetPosition((pos, self.containerHeight - self.height)) else: selected = tab selpos = pos if selected is not skipTab: selected.SetPosition((selpos, self.containerHeight - self.height)) self.addButton.SetPosition((round(tabsWidth) + self.inclination*2, self.containerHeight - self.height/2 - self.addButton.GetHeight()/3)) def OnLeaveWindow(self, event): if self.startDrag and not self.dragging: self.dragging = False self.startDrag = False self.draggedTab = None self.dragTrigger = self.dragTrail if self.HasCapture(): self.ReleaseMouse() if self.previewWnd: self.previewWnd.Show(False) del self.previewWnd self.previewWnd = None event.Skip() def OnTimer(self, event): mposx, mposy = wx.GetMousePosition() cposx, cposy = self.ScreenToClient((mposx, mposy)) if self.FindTabAtPos(cposx, cposy) == self.previewTab: if not self.previewTab.GetSelected(): page = self.Parent.GetPage(self.GetTabIndex(self.previewTab)) if page.Snapshot(): self.previewWnd = PFNotebookPagePreview(self, (mposx+3, mposy+3), page.Snapshot(), self.previewTab.text) self.previewWnd.Show() event.Skip() class PFNotebookPagePreview(wx.Frame): def __init__ (self,parent, pos, bitmap, title): wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=pos, size=wx.DefaultSize, style= wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) self.title = title self.bitmap = bitmap self.SetSize((bitmap.GetWidth(), bitmap.GetHeight())) self.Bind(wx.EVT_PAINT, self.OnWindowPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnWindowEraseBk) self.Bind(wx.EVT_TIMER, self.OnTimer) self.timer = wx.Timer(self, wx.ID_ANY) self.timerSleep = None self.timerSleepId = wx.NewId() self.direction = 1 self.padding = 15 self.transp = 0 hfont = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) self.SetFont(hfont) tx, ty = self.GetTextExtent(self.title) tx += self.padding * 2 if bitmap.GetWidth() < tx: width = tx else: width = bitmap.GetWidth() self.SetSize((width, bitmap.GetHeight()+16)) self.SetTransparent(0) self.Refresh() def OnTimer(self, event): self.transp += 20*self.direction if self.transp > 220: self.transp = 220 self.timer.Stop() if self.transp < 0: self.transp = 0 self.timer.Stop() wx.Frame.Show(self,False) self.Destroy() return self.SetTransparent(self.transp) def RaiseParent(self): wnd = self lastwnd = None while wnd is not None: lastwnd = wnd wnd = wnd.Parent if lastwnd: lastwnd.Raise() def Show(self, showWnd=True): if showWnd: wx.Frame.Show(self, showWnd) self.RaiseParent() self.direction = 1 self.timer.Start(10) else: self.direction = -1 self.timer.Start(10) def OnWindowEraseBk(self,event): pass def OnWindowPaint(self, event): rect = self.GetRect() canvas = wx.EmptyBitmap(rect.width, rect.height) mdc = wx.BufferedPaintDC(self) mdc.SelectObject(canvas) color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) mdc.SetBackground(wx.Brush(color)) mdc.Clear() font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL,wx.NORMAL, False) mdc.SetFont(font) x,y = mdc.GetTextExtent(self.title) mdc.SetBrush(wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))) mdc.DrawRectangle(0, 0, rect.width, 16) mdc.SetTextForeground(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) mdc.DrawText(self.title, (rect.width - x)/2, (16 - y)/2) mdc.DrawBitmap(self.bitmap, 0, 16) mdc.SetPen(wx.Pen("#000000", width=1)) mdc.SetBrush(wx.TRANSPARENT_BRUSH) mdc.DrawRectangle(0, 16, rect.width, rect.height - 16)