1429 lines
44 KiB
Python
1429 lines
44 KiB
Python
# =============================================================================
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
# =============================================================================
|
|
|
|
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
|
|
|
|
from service.fit import Fit
|
|
|
|
_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 DisablePage(self, page, toggle):
|
|
idx = self.GetPageIndex(page)
|
|
|
|
if toggle and page == self.activePage:
|
|
try:
|
|
# Set page to the first non-disabled page
|
|
self.SetSelection(next(i for i, _ in enumerate(self.pages) if not self.tabsContainer.tabs[i].disabled))
|
|
except StopIteration:
|
|
self.SetSelection(0)
|
|
|
|
self.tabsContainer.DisableTab(idx, toggle)
|
|
|
|
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.disabled = False
|
|
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.ConvertToGreyscale() if self.disabled else 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
|
|
|
|
def __repr__(self):
|
|
return "PFTabRenderer(text={}, disabled={}) at {}".format(
|
|
self.text, self.disabled, hex(id(self))
|
|
)
|
|
|
|
|
|
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 = 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_MIDDLE_UP, self.OnMiddleUp)
|
|
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 OnMiddleUp(self, event):
|
|
mposx, mposy = event.GetPosition()
|
|
|
|
tab = self.FindTabAtPos(mposx, mposy)
|
|
|
|
if tab is None or not tab.closeButton: # if not able to close, return False
|
|
return False
|
|
|
|
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))
|
|
|
|
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):
|
|
if tab.disabled:
|
|
return
|
|
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:
|
|
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
|
|
|
|
if self.showAddButton:
|
|
ax, ay = self.addButton.GetPosition()
|
|
mdc.DrawBitmap(self.addButton.Render(), ax, ay, True)
|
|
|
|
for i in range(len(self.tabs) - 1, -1, -1):
|
|
tab = self.tabs[i]
|
|
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 DisableTab(self, tab, disabled=True):
|
|
tabRenderer = self.tabs[tab]
|
|
tabRenderer.disabled = disabled
|
|
|
|
self.AdjustTabsSize()
|
|
self.Refresh()
|
|
|
|
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 range(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)
|