mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-27 21:52:25 -06:00
2113 lines
71 KiB
Python
2113 lines
71 KiB
Python
# --------------------------------------------------------------------------------- #
|
|
# XLSGRID wxPython IMPLEMENTATION
|
|
#
|
|
# Andrea Gavana @ 08 Aug 2011
|
|
# Latest Revision: 27 Dec 2012, 21.00 GMT
|
|
#
|
|
#
|
|
# TODO List
|
|
#
|
|
# Current todo list:
|
|
#
|
|
# 1. There is currently no support for rich text, i.e. strings containing partial
|
|
# bold, italic and underlined text, change of font inside a string, etc... xlrd
|
|
# supports those (from version 0.7.2 in SVN) but there is no easy way to handle
|
|
# changing fonts/colours/formatting in the same string in wxPython;
|
|
#
|
|
# 2. XLSGrid is sufficiently efficient and fast for reasonably small Excel files.
|
|
# There might be some improvement to be made in the code to make it work with
|
|
# bigger files and in a faster way;
|
|
#
|
|
# 3. There is currently no support for strikethrough fonts, although xlrd correctly
|
|
# reports this format. The issue is a bug in wxWidgets itself which doesn't
|
|
# allow the creation of strikethrough fonts (http://trac.wxwidgets.org/ticket/9907).
|
|
#
|
|
# For all kind of problems, requests of enhancements and bug reports, please write
|
|
# to me at:
|
|
#
|
|
# andrea.gavana@gmail.com
|
|
# andrea.gavana@maerskoil.com
|
|
#
|
|
# Or, obviously, to the wxPython mailing list!!!
|
|
#
|
|
# Tags: phoenix-port, documented, unittest, py3-port
|
|
#
|
|
# End of comments
|
|
# --------------------------------------------------------------------------------- #
|
|
|
|
|
|
"""
|
|
:class:`XLSGrid` is a class based on :class:`grid.Grid` that can be used to faithfully
|
|
reproduce the appearance of a Microsoft Excel spreadsheet (one worksheet per
|
|
every instance of :class:`XLSGrid`).
|
|
|
|
|
|
Description
|
|
===========
|
|
|
|
:class:`XLSGrid` is a class based on :class:`grid.Grid` that can be used to faithfully
|
|
reproduce the appearance of a Microsoft Excel spreadsheet (one worksheet per
|
|
every instance of :class:`XLSGrid`).
|
|
|
|
:class:`XLSGrid` is a completely owner-drawn control, and it relies on the power of
|
|
:class:`grid.PyGridTableBase` and :class:`grid.PyGridCellRenderer` to draw the cell
|
|
content. For this reasons (and for some others, see the TODOs section), it will
|
|
work efficiently only for relatively small Excel files.
|
|
|
|
:note:
|
|
|
|
:class:`XLSGrid` **requires** the `xlrd` package from:
|
|
|
|
http://pypi.python.org/pypi/xlrd
|
|
|
|
Minimum version requirement for `xlrd` is 0.7.1. If you wish to have
|
|
support for hyperlinks inside cells and rich text content, you need the
|
|
SVN version of `xlrd`.
|
|
|
|
|
|
:note:
|
|
|
|
On Windows, it is **strongly** recommended to install Mark Hammonds'
|
|
`pywin32` package:
|
|
|
|
http://sourceforge.net/projects/pywin32/
|
|
|
|
This will allow you to perfectly reproduce the appearance of the Excel
|
|
worksheet in your instance of :class:`XLSGrid`.
|
|
|
|
.. warning::
|
|
|
|
If Mark Hammonds' `pywin32` package is not available, the formatting
|
|
capabilities of :class:`XLSGrid` are severely limited; for instance, you won't
|
|
probably get the exact WYSIWYG between the Excel spreadsheet and :class:`XLSGrid`.
|
|
|
|
|
|
.. warning::
|
|
|
|
:class:`XLSGrid` can only read Excel `.xls` files, not the newer versions
|
|
`.xlsx` generated by Office 2007/2010. If you have a `.xlsx` file, you will
|
|
need to save it in 1997-2003 Office compatibility mode.
|
|
|
|
|
|
Currently this class provides a read-only subclass of :class:`grid.Grid`, with
|
|
the following formatting features already implemented:
|
|
|
|
* Cell background: support for any cell background colour and fill pattern
|
|
(hatching) in the Excel default set. There currently is no support for
|
|
gradient shading inside a cell as `xlrd` doesn't report this information.
|
|
|
|
* Cell borders: support for all the border types and colours exposed by Excel
|
|
(left, top, bottom, right and diagonal borders, thin, double, thick, ect...
|
|
line styles).
|
|
|
|
* Cell text: support for all kind of fonts (except strikethrough, but this is
|
|
a bug in wxWidgets), and font colours. As a subset of text/font capabilities,
|
|
:class:`XLSGrid` supports the following features found in Excel:
|
|
|
|
- Horizontal alignment: left, right, centered, left-indented;
|
|
- Vertical alignment: left, right, centered;
|
|
- Text direction: left-to-right or right-to-left;
|
|
- Text-wrapping: wrapping long texts inside a grid cell;
|
|
- Shrink-to-fit: text font is reduced until the text can fit in a one-line
|
|
inside the grid cell;
|
|
- Text rotation: text can be rotated from +90 to -90 degrees.
|
|
|
|
* Cell rich text (new in version 0.2): support for strings containing partial
|
|
bold, italic and underlined text, change of font inside a string etc...
|
|
Cells with rich text content can not be multi-line and they will not honour
|
|
the `shrink-to-fit` and `wrapping` settings.
|
|
|
|
* Cell text appearance: if you are using Mark Hammonds' `pywin32` package, the
|
|
text displayed in the :class:`XLSGrid` cells has exactly the same appearance as in
|
|
the Excel spreadsheet.
|
|
|
|
* Cell comments (notes): if you are using Mark Hammonds' `pywin32` package,
|
|
cell comments (notes) are extracted and you will see a small red triangle at
|
|
the top-right corner of any cell containing a comment. Hovering with the
|
|
mouse on that cell will pop-up a "comment-window" displaying the comment
|
|
text (the comment window is based on :mod:`lib.agw.supertooltip`).
|
|
|
|
* Cell hyperlinks: starting from version 0.7.2 (SVN), `xlrd` is capable of
|
|
extracting hyperlinks from Excel cells. This will be appropriately displayed
|
|
in :class:`XLSGrid` with a cursor changing and a tooltip on that cell.
|
|
|
|
* Cell merging: merged cells in the Excel spreadsheet will be correctly handled
|
|
by :class:`XLSGrid`.
|
|
|
|
* Columns and rows sizes: :class:`XLSGrid` calculates the correct rows and columns
|
|
sizes based on the Excel reported values in characters. The calculations are
|
|
based on the default width of the text in 1/256 of the width of the zero
|
|
character, using default Excel font (first FONT record in the Excel file).
|
|
|
|
|
|
And a lot more. Check the demo for an almost complete review of the functionalities.
|
|
|
|
|
|
Usage
|
|
=====
|
|
|
|
Sample usage::
|
|
|
|
import wx
|
|
import xlrd
|
|
import os
|
|
|
|
import xlsgrid as XG
|
|
|
|
class MyFrame(wx.Frame):
|
|
|
|
def __init__(self):
|
|
|
|
wx.Frame.__init__(self, parent, -1, "XLSGrid Demo", size=(1000, 800))
|
|
|
|
filename = os.path.join(os.getcwd(), "Excel", "Example_1.xls")
|
|
sheetname = "Example_1"
|
|
|
|
book = xlrd.open_workbook(filename, formatting_info=1)
|
|
|
|
sheet = book.sheet_by_name(sheetname)
|
|
rows, cols = sheet.nrows, sheet.ncols
|
|
|
|
comments, texts = XG.ReadExcelCOM(filename, sheetname, rows, cols)
|
|
|
|
xls_grid = XG.XLSGrid(self)
|
|
xls_grid.PopulateGrid(book, sheet, texts, comments)
|
|
|
|
|
|
# our normal wxApp-derived class, as usual
|
|
|
|
app = wx.App(0)
|
|
|
|
frame = MyFrame(None)
|
|
app.SetTopWindow(frame)
|
|
frame.Show()
|
|
|
|
app.MainLoop()
|
|
|
|
|
|
:note: Please note that you **have to** pass the keyword `formatting_info` to
|
|
the method `xlrd.open_workbook` to obtain the cell formatting.
|
|
|
|
|
|
TODOs
|
|
=====
|
|
|
|
1. :class:`XLSGrid` is sufficiently efficient and fast for reasonably small Excel files.
|
|
There might be some improvement to be made in the code to make it work with
|
|
bigger files and in a faster way;
|
|
2. :class:`grid.Grid` seems to completely redraw itself at every resize event, even
|
|
if the cell content has not changed and it has not been damaged (this seems
|
|
to be fixed in wxPython 2.9.2.1);
|
|
3. There is currently no support for strikethrough fonts, although `xlrd` correctly
|
|
reports this format. The issue is a bug in wxWidgets itself which doesn't
|
|
allow the creation of strikethrough fonts (http://trac.wxwidgets.org/ticket/9907).
|
|
|
|
|
|
Supported Platforms
|
|
===================
|
|
|
|
:class:`XLSGrid` has been tested on the following platforms:
|
|
* Windows (Windows Vista and 7);
|
|
|
|
|
|
Window Styles
|
|
=============
|
|
|
|
`No particular window styles are available for this class.`
|
|
|
|
|
|
Events Processing
|
|
=================
|
|
|
|
`No custom events are available for this class.`
|
|
|
|
|
|
License And Version
|
|
===================
|
|
|
|
:class:`XLSGrid` is distributed under the wxPython license.
|
|
|
|
Latest Revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT
|
|
|
|
Version 0.4
|
|
|
|
"""
|
|
|
|
# Version Info
|
|
__version__ = "0.4"
|
|
|
|
# Start the imports
|
|
import wx
|
|
import os
|
|
|
|
try:
|
|
import xlrd
|
|
except ImportError:
|
|
pass
|
|
|
|
import datetime
|
|
import string
|
|
|
|
import wx.grid as gridlib
|
|
|
|
import wx.lib.six as six
|
|
|
|
from wx.lib.embeddedimage import PyEmbeddedImage
|
|
from wx.lib.wordwrap import wordwrap
|
|
|
|
from . import supertooltip as STT
|
|
|
|
from math import pi, sin, cos
|
|
from operator import attrgetter
|
|
|
|
_hasWin32 = False
|
|
|
|
if wx.Platform == "__WXMSW__":
|
|
try:
|
|
from win32com.client import Dispatch
|
|
import pywintypes
|
|
_hasWin32 = True
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# Constants used to translate xlrd stuff into wxPython stuff
|
|
#----------------------------------------------------------------------
|
|
|
|
BOTTOM = wx.BOTTOM
|
|
TOP = wx.TOP
|
|
LEFT = wx.LEFT
|
|
RIGHT = wx.RIGHT
|
|
DIAGONAL = 2 << 7
|
|
|
|
XF_BORDER_STYLES = {"bottom": BOTTOM, "left": LEFT, "right": RIGHT,
|
|
"top": TOP, "diag": DIAGONAL}
|
|
|
|
HORIZONTAL_ALIGNMENTS = {0: 0, 1: 0, 2: wx.ALIGN_CENTER_HORIZONTAL, 3: wx.ALIGN_RIGHT}
|
|
VERTICAL_ALIGNMENTS = {0: 0, 1: wx.ALIGN_CENTER_VERTICAL, 2: wx.ALIGN_BOTTOM,
|
|
3: wx.ALIGN_CENTER_VERTICAL, 4:wx.ALIGN_CENTER_VERTICAL}
|
|
|
|
NO_LINE = 0x0
|
|
THIN = 0x1
|
|
MEDIUM = 0x2
|
|
DASHED = 0x3
|
|
DOTTED = 0x4
|
|
THICK = 0x5
|
|
DOUBLE = 0x6
|
|
HAIR = 0x7
|
|
MEDIUM_DASHED = 0x8
|
|
THIN_DASH_DOTTED = 0x9
|
|
MEDIUM_DASH_DOTTED = 0xA
|
|
THIN_DASH_DOT_DOTTED = 0xB
|
|
MEDIUM_DASH_DOT_DOTTED = 0xC
|
|
SLANTED_MEDIUM_DASH_DOTTED = 0xD
|
|
|
|
XF_PEN_STYLES = {NO_LINE: (0, None), THIN: (1, wx.SOLID), MEDIUM: (2, wx.SOLID),
|
|
DASHED: (1, wx.SHORT_DASH), DOTTED: (1, wx.DOT),
|
|
THICK: (3, wx.SOLID), DOUBLE: (1, wx.SOLID), HAIR: (1, wx.DOT),
|
|
MEDIUM_DASHED: (2, wx.LONG_DASH), THIN_DASH_DOTTED: (1, wx.DOT_DASH),
|
|
MEDIUM_DASH_DOTTED: (2, wx.DOT_DASH), THIN_DASH_DOT_DOTTED: (1, wx.DOT_DASH),
|
|
MEDIUM_DASH_DOT_DOTTED: (2, wx.DOT_DASH),
|
|
SLANTED_MEDIUM_DASH_DOTTED: (2, wx.DOT_DASH)
|
|
}
|
|
|
|
XF_FONT_FAMILY = {0: wx.SWISS, 1: wx.ROMAN, 2: wx.SWISS,
|
|
3: wx.MODERN, 4: wx.SCRIPT, 5: wx.DECORATIVE}
|
|
|
|
# Unicode ordinals for Hebrew, Arabic and Syriac
|
|
# I don't know if there are other RTL languages
|
|
RTL_UNICODE = list(range(1424, 1872))
|
|
|
|
# To guess text direction we exclude digits and punctuation
|
|
USELESS_CHARS = string.punctuation + string.digits + " "
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# Images used to draw the hatching on Excel cells background
|
|
#----------------------------------------------------------------------
|
|
|
|
_xls_background_01 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAADElE"
|
|
"QVQImWNgIB0AAAA0AAEjQ4N1AAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_02 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAFklE"
|
|
"QVQImWNgYGD4//8/lESwIAC7DABt4hfpI2a12wAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_03 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAE0lE"
|
|
"QVQImWP4//8/AxqACuGUAQBI+Qv1NTPP3AAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_04 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAH0lE"
|
|
"QVQImXXJsQ0AIAAEIc79d34LazsSwjbgPFXoOxdcCQwBh7OgqgAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_05 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAFUlE"
|
|
"QVQImWNgwAUY////D+cwIcsAAEggAwHHgMubAAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_06 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAG0lE"
|
|
"QVQImWNkYGBgYGD4//8/AwMDEwMSwM0BAISAAwUnufp7AAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_07 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAHklE"
|
|
"QVQImWNkYGBgYGD4//8/EgVh/P//H86HikH4APOCFO3yiGicAAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_08 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAHUlE"
|
|
"QVQImWP4////////GSAAzvr//z8jmhADXCUAQSkU7eggG3gAAAAASUVORK5CYII=")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_09 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAIElE"
|
|
"QVQImWNkYGBgYGD4//8/AwMDEwMy+P//P0QYXQYACtQI/cTE6U0AAAAASUVORK5CYII=")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_10 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAG0lE"
|
|
"QVQImW3IsQ0AAAjAIPz/6LobGRmgclVfswpsCPmQczsGAAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_11 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAFklE"
|
|
"QVQImWNgQAKM////h3OYkGVQOABvOgMD4NUKkwAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_12 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAG0lE"
|
|
"QVQImWNkYGD4//8/AwMDAwMDEwMSwM0BAI13AwWY+Mx+AAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_13 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAI0lE"
|
|
"QVQImT3KsQ0AMAyAMMj/P9MhUUcLBCoAmEo9bFn7H/UBXEwMBN75abEAAAAASUVORK5CYII=")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_14 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAIElE"
|
|
"QVQImT3KoQEAAAwCINz/P7tmI5C2QJKb2t6EYPMBOOUMBIWcMEIAAAAASUVORK5CYII=")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_15 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAGUlE"
|
|
"QVQImWNgQAKMDAwM////h3CYkGVQOABmQwMDJpgq9gAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_16 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAHklE"
|
|
"QVQImWNgYGD4//8/lISzIAwEnxEqwMDAyMgIALKwF+2ym+hoAAAAAElFTkSuQmCC")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_17 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAIklE"
|
|
"QVQImWNkYGD4//8/AwMDAwMDI5zFwMDABBVjZESXAQAc+AkAQ4bzDAAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
_xls_background_18 = PyEmbeddedImage(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r+mnAAAAA3NCSVQICAjb4U/gAAAAH0lE"
|
|
"QVQImWNkYGD4//8/AwZgxCrKwMDAhKKKkZGwDgA83QkAy10JvwAAAABJRU5ErkJggg==")
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
def SplitThousands(s, tSep=',', dSep='.'):
|
|
"""
|
|
Splits a general float on thousands. GIGO on general input.
|
|
|
|
:param `s`: can be a float or a string, representing a number;
|
|
:param `tSep`: the character to be used as thousands separator;
|
|
:param `dSep`: the character to be used as decimal separator.
|
|
|
|
:returns: a string properly formatted with thousands and decimal
|
|
separators in it.
|
|
|
|
:note: This method is used only if Mark Hammonds' `pywin32` package is
|
|
not available to try and format a number in an intelligent way.
|
|
|
|
:note: This code has been obtained from the public domain:
|
|
|
|
http://code.activestate.com/recipes/498181-add-thousands-separator-commas-to-formatted-number/#c14
|
|
|
|
"""
|
|
|
|
if not isinstance(s, six.string_types):
|
|
s = six.u(s)
|
|
|
|
cnt = 0
|
|
numChars = dSep + '0123456789'
|
|
ls = len(s)
|
|
|
|
while cnt < ls and s[cnt] not in numChars:
|
|
cnt += 1
|
|
|
|
lhs = s[0:cnt]
|
|
s = s[cnt:]
|
|
|
|
if dSep == '':
|
|
cnt = -1
|
|
else:
|
|
cnt = s.rfind(dSep)
|
|
|
|
if cnt > 0:
|
|
rhs = dSep + s[cnt+1:]
|
|
s = s[:cnt]
|
|
else:
|
|
rhs = ''
|
|
|
|
splt = ''
|
|
while s != '':
|
|
splt = s[-3:] + tSep + splt
|
|
s = s[:-3]
|
|
|
|
return lhs + splt[:-1] + rhs
|
|
|
|
|
|
def ReadExcelCOM(filename, sheetname, rows, cols):
|
|
"""
|
|
Reads and Excel spreadsheet (a single worksheet) using Mark Hammonds' `pywin32`
|
|
package. If this package is not available, it returns two empty nested lists.
|
|
|
|
:param `filename`: a valid Excel `.xls` filename;
|
|
:param `sheetname`: the worksheet name inside the Excel file (i.e., the label
|
|
on the workbook tab at the bottom of the workbook);
|
|
:param `rows`: the number of significant rows in the worksheet, as returned
|
|
by `xlrd`;
|
|
:param `cols`: the number of significant columns in the worksheet, as returned
|
|
by `xlrd`.
|
|
|
|
:returns: two nested lists representing the comments (notes) on every cell and
|
|
the WYSIWYG representation of the cell content.
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, this method
|
|
returns two empty nested lists.
|
|
"""
|
|
|
|
comments = [["" for i in range(cols)] for j in range(rows)]
|
|
texts = [[None for i in range(cols)] for j in range(rows)]
|
|
|
|
if not _hasWin32:
|
|
return comments, texts
|
|
|
|
try:
|
|
workbook = Excel(filename, sheetname)
|
|
|
|
for i in range(1, rows+1):
|
|
for j in range(1, cols+1):
|
|
texts[i-1][j-1] = workbook.GetText(i, j)
|
|
|
|
comm_range = workbook.GetCommentsRange()
|
|
if comm_range is not None:
|
|
for comm in comm_range:
|
|
comments[comm.Row-1][comm.Column-1] = comm.Comment.Text()
|
|
|
|
workbook.Close()
|
|
|
|
except pywintypes.com_error:
|
|
pass
|
|
|
|
return comments, texts
|
|
|
|
|
|
def FontFromFont(font):
|
|
"""
|
|
Creates a copy of the input `font`.
|
|
|
|
:param `font`: an instance of :class:`Font`.
|
|
"""
|
|
|
|
new_font = wx.Font(font.GetPointSize(), font.GetFamily(), font.GetStyle(),
|
|
font.GetWeight(), font.GetUnderlined(),
|
|
font.GetFaceName(), font.GetEncoding())
|
|
|
|
return new_font
|
|
|
|
|
|
class Excel(object):
|
|
"""
|
|
A simple class that holds a COM interface to Excel.
|
|
|
|
By using the `win32com` module from Mark Hammonds' `pywin32` package, we
|
|
can manipulate various workbook/worksheet methods inside this class.
|
|
"""
|
|
|
|
def __init__(self, filename, sheetname):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `filename`: a valid Excel `.xls` filename;
|
|
:param `sheetname`: the worksheet name inside the Excel file (i.e., the label
|
|
on the workbook tab at the bottom of the workbook).
|
|
"""
|
|
|
|
self.xlApp = Dispatch('Excel.Application')
|
|
self.filename = filename
|
|
self.xlBook = self.xlApp.Workbooks.Open(filename)
|
|
self.sheet = self.xlBook.Worksheets(sheetname)
|
|
|
|
self.xlApp.Visible = 0
|
|
|
|
|
|
def Close(self, save=False):
|
|
"""
|
|
Closes the Excel workbook, interrupting the COM interface.
|
|
|
|
:param `save`: ``True`` to save the changes you made to the workbook,
|
|
``False`` otherwise.
|
|
"""
|
|
|
|
self.xlBook.Close(SaveChanges=save)
|
|
del self.xlApp
|
|
|
|
|
|
def GetCommentsRange(self):
|
|
"""
|
|
Returns a range of cells containing comments, using the VBA API.
|
|
|
|
"""
|
|
|
|
try:
|
|
return self.sheet.Cells.SpecialCells(-4144)
|
|
except pywintypes.com_error:
|
|
return None
|
|
|
|
|
|
def GetText(self, row, col):
|
|
"""
|
|
Returns the WYSIWYG text contained in a cell.
|
|
|
|
:param `row`: the row in which the cell lives;
|
|
:param `col`: the column in which the cell lives.
|
|
|
|
:note: The `row` and `col` parameters are not real Python index, as they
|
|
use the Excel indexing mode (i.e., first index is 1 and not 0).
|
|
"""
|
|
|
|
cell = self.sheet.Cells(row, col)
|
|
|
|
if cell:
|
|
return cell.Text
|
|
|
|
|
|
class XLSText(object):
|
|
"""
|
|
This is a class which holds information about the cell content, in terms
|
|
of actual cell value, font, text colour, alignment and formatting.
|
|
"""
|
|
|
|
def __init__(self, book, cell, xf_index, display_text=None, hyperlink=None, default_width=10):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `cell`: an instance of `xlrd.sheet.Cell` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`);
|
|
:param `display_text`: if Mark Hammonds' `pywin32` package is available,
|
|
this is the WYSIWYG cell content;
|
|
:param `hyperlink`: if this cell contains a hyperlink, it will be displayed
|
|
accordingly;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
"""
|
|
|
|
XFClass = book.xf_list[xf_index]
|
|
|
|
font = book.font_list[XFClass.font_index]
|
|
self.font = self.CreateFont(font)
|
|
|
|
text_colour = book.colour_map[font.colour_index]
|
|
self.text_colour = self.CreateTextColour(text_colour)
|
|
|
|
if display_text is not None:
|
|
self.value = display_text
|
|
else:
|
|
format = book.format_map[XFClass.format_key]
|
|
self.CreateFormat(format, cell, book.datemode)
|
|
|
|
alignment = XFClass.alignment
|
|
self.CreateAlignment(alignment, default_width)
|
|
|
|
if hyperlink is not None:
|
|
self.SetupHyperlink(hyperlink)
|
|
else:
|
|
self.tooltip = None
|
|
|
|
|
|
def CreateFont(self, font):
|
|
"""
|
|
Creates a suitable wxPython font starting from an Excel font.
|
|
|
|
:param `font`: an instance of `xlrd.formatting.Font` class.
|
|
|
|
:note: There is currently no support for strikethrough fonts, although
|
|
`xlrd` correctly reports this format. The issue is a bug in wxWidgets
|
|
itself which doesn't allow the creation of strikethrough fonts. See
|
|
(http://trac.wxwidgets.org/ticket/9907).
|
|
"""
|
|
|
|
style, bold, underline = wx.FONTSTYLE_NORMAL, wx.NORMAL, False
|
|
if font.italic:
|
|
style = wx.FONTSTYLE_ITALIC
|
|
if font.underline_type > 0:
|
|
underline = True
|
|
if font.weight > 600:
|
|
bold = wx.BOLD
|
|
|
|
family = XF_FONT_FAMILY[font.family]
|
|
name = font.name
|
|
size = int(font.height/20.0)
|
|
|
|
if font.escapement > 0:
|
|
# subscript/superscript
|
|
size = int(size*0.7)
|
|
|
|
# No support for strike-through fonts in wxWidgets
|
|
# if font.struck_out:
|
|
# style = wx.FONTFLAG_DEFAULT
|
|
# if bold:
|
|
# style += wx.FONTFLAG_BOLD
|
|
# if underline:
|
|
# style += wx.FONTFLAG_UNDERLINED
|
|
# if font.italic:
|
|
# style += wx.FONTFLAG_ITALIC
|
|
#
|
|
# style += wx.FONTFLAG_STRIKETHROUGH
|
|
# self.font = wx.FFont(size, family, style, name.encode())
|
|
# else:
|
|
|
|
return wx.Font(size, family, style, bold, underline, name.encode())
|
|
|
|
|
|
def CreateTextColour(self, text_colour):
|
|
"""
|
|
Creates a suitable wxPython colour for the text starting from a `xlrd`
|
|
tuple representing this colour.
|
|
|
|
:param `text_colour`: a tuple representing the RGB components of the
|
|
colour. If `text_colour` is ``None``, use the default ``wx.SYS_COLOUR_WINDOWTEXT``.
|
|
"""
|
|
|
|
if text_colour is not None:
|
|
text_colour = wx.Colour(*text_colour)
|
|
else:
|
|
text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
|
|
|
return text_colour
|
|
|
|
|
|
def CreateAlignment(self, alignment, default_width):
|
|
"""
|
|
Creates a suitable wxPython alignment flag for the text starting from a
|
|
`xlrd` class representing this alignment.
|
|
|
|
:param `alignment`: an instance of `xlrd.formatting.XFAlignment` class;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file).
|
|
"""
|
|
|
|
hor_align, vert_align = alignment.hor_align, alignment.vert_align
|
|
self.horizontal_alignment = HORIZONTAL_ALIGNMENTS[hor_align]
|
|
self.vertical_alignment = VERTICAL_ALIGNMENTS[vert_align]
|
|
|
|
self.indent_level = alignment.indent_level
|
|
self.shrink_to_fit = alignment.shrink_to_fit
|
|
self.text_wrapped = alignment.text_wrapped
|
|
|
|
text_direction = 1
|
|
|
|
if alignment.text_direction == 0:
|
|
for char in self.value:
|
|
if char not in USELESS_CHARS:
|
|
if ord(char) in RTL_UNICODE:
|
|
text_direction = 2
|
|
break
|
|
|
|
self.text_direction = text_direction
|
|
self.default_width = default_width
|
|
|
|
if alignment.rotation > 90:
|
|
self.rotation = 90 - alignment.rotation
|
|
else:
|
|
self.rotation = alignment.rotation
|
|
|
|
|
|
def CreateFormat(self, format, cell, datemode):
|
|
"""
|
|
This method tries to guess the best format to apply to the current text
|
|
value.
|
|
|
|
:param `format`: an instance of `xlrd.formatting.Format` class;
|
|
:param `cell`: an instance of `xlrd.sheet.Cell` class;
|
|
:param `datemode`: the datemode associated with this Excel workbook.
|
|
|
|
:note: This method is used only if Mark Hammonds' `pywin32` package is
|
|
not available to try and format the cell text in an intelligent way.
|
|
|
|
.. warning::
|
|
|
|
The formatting applied by this method is severely limited; for
|
|
instance, you won't probably get the exact WYSIWYG between the Excel
|
|
spreadsheet and :class:`XLSGrid`.
|
|
"""
|
|
|
|
ctype, value = cell.ctype, cell.value
|
|
|
|
self.value = "%s"%value
|
|
isDate = False
|
|
|
|
if ctype == xlrd.XL_CELL_DATE:
|
|
value = xlrd.xldate_as_tuple(value, datemode)
|
|
isDate = True
|
|
elif ctype in [xlrd.XL_CELL_EMPTY, xlrd.XL_CELL_BLANK]:
|
|
return
|
|
elif ctype == xlrd.XL_CELL_TEXT:
|
|
self.value = "%s"%value
|
|
return
|
|
elif ctype == xlrd.XL_CELL_ERROR:
|
|
value = xlrd.error_text_from_code(ctype)
|
|
self.value = "%s"%value
|
|
return
|
|
|
|
self.FormatString(value, isDate, format.format_str)
|
|
|
|
|
|
def FormatString(self, value, isDate, format_str):
|
|
"""
|
|
This method tries to guess the best format to apply to the current text
|
|
value.
|
|
|
|
:param `value`: the actual raw cell text value;
|
|
:param `isDate`: ``True`` if this value represents a `xlrd` date object,
|
|
``False`` otherwise;
|
|
:param `format_str`: the actual formatting string as extracted from Excel.
|
|
|
|
:note: This method is used only if Mark Hammonds' `pywin32` package is
|
|
not available to try and format the cell text in an intelligent way.
|
|
|
|
.. warning::
|
|
|
|
The formatting applied by this method is severely limited; for
|
|
instance, you won't probably get the exact WYSIWYG between the Excel
|
|
spreadsheet and :class:`XLSGrid`.
|
|
|
|
"""
|
|
|
|
if "General" in format_str:
|
|
self.value = "%s"%value
|
|
return
|
|
|
|
number_format = format_str
|
|
currency = percentage = ""
|
|
|
|
if not isDate:
|
|
symbol = ""
|
|
split = format_str.split()
|
|
if len(split) > 1:
|
|
# Accounting and currency shit
|
|
currency = split[0].split(".")[0] + "."
|
|
number_format = ".".join(split[1:])
|
|
|
|
if "%" in number_format:
|
|
percentage = "%"
|
|
value = 100*value
|
|
number_format = number_format[0:-1]
|
|
|
|
representation = "%d"
|
|
if "." in number_format:
|
|
split = number_format.split(".")
|
|
num_decimals = len(split[1])
|
|
representation = "%0." + str(num_decimals) + "f"
|
|
|
|
try:
|
|
value = representation%value
|
|
except ValueError:
|
|
# Fall back to string
|
|
value = six.u(value)
|
|
|
|
if "#," in number_format:
|
|
value = SplitThousands(value)
|
|
|
|
value = currency + value + percentage
|
|
|
|
else:
|
|
number_format = format_str.replace("\\", "")
|
|
number_format = number_format.replace("-", "-%").replace("/", "/%")
|
|
value = datetime.datetime(*value)
|
|
try:
|
|
value = value.strftime(number_format)
|
|
except (ValueError, TypeError):
|
|
value = value.strftime("%d.%m.%Y")
|
|
|
|
self.value = value
|
|
|
|
|
|
def SetupHyperlink(self, hyperlink):
|
|
"""
|
|
Sets up the cell text value in case it represents a hyperlink.
|
|
|
|
:param `hyperlink`: an instance of `xlrd.sheet.hyperlink`.
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
"""
|
|
|
|
url = (hyperlink.url_or_path and [hyperlink.url_or_path] or [hyperlink.textmark])[0]
|
|
self.tooltip = url
|
|
|
|
|
|
def IsHyperLink(self):
|
|
"""
|
|
Returns whether the cell text is representing a hyperlink.
|
|
|
|
:returns: ``True`` if the cell text represents a hyperlink, ``False``
|
|
otherwise.
|
|
"""
|
|
|
|
return self.tooltip is not None
|
|
|
|
|
|
def CombineAttr(self, attr):
|
|
"""
|
|
Combines the input attribute `attr` with the features of the :class:`XLSText` class.
|
|
|
|
:param `attr`: an instance of :class:`grid.GridCellAttr`.
|
|
"""
|
|
|
|
attr.SetAlignment(self.horizontal_alignment, self.vertical_alignment)
|
|
attr.SetTextColour(self.text_colour)
|
|
attr.SetFont(self.font)
|
|
|
|
|
|
def GetValue(self):
|
|
""" Returns the string representation of the cell text value. """
|
|
|
|
return self.value
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws the text value on a grid cell.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
new_rect = wx.Rect(*rect)
|
|
|
|
xshift = yshift = 0
|
|
if self.rotation:
|
|
xshift = cos(self.rotation*pi/180)
|
|
yshift = sin(self.rotation*pi/180)
|
|
|
|
dc.SetTextForeground(self.text_colour)
|
|
dc.SetFont(self.font)
|
|
|
|
value = self.value
|
|
text_width, text_height = dc.GetTextExtent(value)
|
|
|
|
default_width = int(round(float(self.default_width)*text_width/256.0))
|
|
|
|
indentation = int(256.0*default_width/float(self.default_width))
|
|
|
|
if xshift == 0 and self.indent_level:
|
|
new_rect.SetLeft(new_rect.x + indentation)
|
|
else:
|
|
if self.horizontal_alignment == wx.ALIGN_LEFT:
|
|
new_rect.SetLeft(new_rect.x + 3)
|
|
elif self.horizontal_alignment == wx.ALIGN_RIGHT:
|
|
new_rect.SetWidth(new_rect.width - 1)
|
|
|
|
new_width = rect.width
|
|
|
|
if xshift > 0:
|
|
new_width = new_width/xshift
|
|
|
|
if self.shrink_to_fit:
|
|
|
|
font = FontFromFont(self.font)
|
|
point_size = font.GetPointSize()
|
|
|
|
while 1:
|
|
value = wordwrap(self.value, new_width, dc)
|
|
if "\n" not in value or point_size < 2:
|
|
break
|
|
|
|
point_size -= 1
|
|
font.SetPointSize(point_size)
|
|
dc.SetFont(font)
|
|
|
|
elif self.text_wrapped:
|
|
|
|
value = wordwrap(self.value, new_width, dc)
|
|
text_width, text_height, dummy = dc.GetFullMultiLineTextExtent(value)
|
|
|
|
if self.rotation:
|
|
if self.shrink_to_fit:
|
|
text_width, text_height = dc.GetTextExtent(value)
|
|
|
|
xc, yc = (rect.x+rect.width/2, rect.y+rect.height/2)
|
|
xp = xc - (text_width/2)*xshift - (text_height/2)*yshift
|
|
yp = yc + (text_width/2)*yshift - (text_height/2)*xshift
|
|
|
|
dc.DrawRotatedText(value, xp, yp, self.rotation)
|
|
|
|
else:
|
|
|
|
dc.DrawLabel(value, new_rect, self.horizontal_alignment|self.vertical_alignment)
|
|
|
|
|
|
class XLSRichText(XLSText):
|
|
"""
|
|
This is a class which holds information about the cell content, in terms
|
|
of actual cell value, font, text colour, alignment and formatting. In addition
|
|
to what :class:`XLSText` does, this class attempts to handle cells with rich text
|
|
content.
|
|
"""
|
|
|
|
def __init__(self, book, cell, xf_index, display_text=None, hyperlink=None, rich_text=None, default_width=10):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `cell`: an instance of `xlrd.sheet.Cell` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`);
|
|
:param `display_text`: if Mark Hammonds' `pywin32` package is available,
|
|
this is the WYSIWYG cell content;
|
|
:param `hyperlink`: if this cell contains a hyperlink, it will be displayed
|
|
accordingly;
|
|
:param `rich_text`: if this cell contains text in rich text format, :class:`XLSGrid`
|
|
will do its best to render the text as rich text;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, this class will
|
|
note be used by :class:`XLSGrid`.
|
|
|
|
.. warning::
|
|
|
|
This class currently supports only single-line non-rotated text,
|
|
and it discards properties like `shrink-to-fit` and `wrapping`.
|
|
|
|
"""
|
|
|
|
XLSText.__init__(self, book, cell, xf_index, display_text, hyperlink, default_width)
|
|
|
|
self.BuildChunks(book, xf_index, rich_text)
|
|
|
|
|
|
def BuildChunks(self, book, xf_index, rich_text):
|
|
"""
|
|
Splits the cell content accordingly to their rich text format index.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`);
|
|
:param `rich_text`: if this cell contains text in rich text format, :class:`XLSGrid`
|
|
will do its best to render the text as rich text.
|
|
"""
|
|
|
|
XFClass = book.xf_list[xf_index]
|
|
offset, index = rich_text[0]
|
|
|
|
if offset != 0:
|
|
new_tuple = (0, XFClass.font_index)
|
|
rich_text.insert(0, new_tuple)
|
|
|
|
value = self.value
|
|
rich_text.append((len(value), rich_text[-1][1]))
|
|
attributes = []
|
|
|
|
for indx in range(len(rich_text)-1):
|
|
offset_start, index_start = rich_text[indx]
|
|
offset_end, index_end = rich_text[indx+1]
|
|
|
|
chunk = value[offset_start:offset_end]
|
|
|
|
font = book.font_list[index_start]
|
|
ffont = self.CreateFont(font)
|
|
text_colour = book.colour_map[font.colour_index]
|
|
colour = self.CreateTextColour(text_colour)
|
|
|
|
ffont.escapement = font.escapement
|
|
attributes.append([chunk, ffont, colour])
|
|
|
|
self.attributes = attributes
|
|
|
|
|
|
def Measure(self, dc):
|
|
"""
|
|
Convenience method to measure the maximum height and total width of all
|
|
the chunks of text composing our rich text string.
|
|
|
|
:param `dc`: an instance of :class:`DC`.
|
|
"""
|
|
|
|
maxH = -1
|
|
full_width = 0
|
|
for chunk, font, colour in self.attributes:
|
|
dc.SetFont(font)
|
|
width, height, descent, leading = dc.GetFullTextExtent(chunk, font)
|
|
maxH = max(maxH, height-leading)
|
|
full_width += width
|
|
|
|
return maxH, full_width
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws all the chunks of text on a grid cell, one by one.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
new_rect = wx.Rect(*rect)
|
|
|
|
text_width, text_height = dc.GetTextExtent(self.value)
|
|
default_width = int(round(float(self.default_width)*text_width/256.0))
|
|
indentation = int(256.0*default_width/float(self.default_width))
|
|
|
|
maxH, full_width = self.Measure(dc)
|
|
|
|
if self.indent_level:
|
|
new_rect.SetLeft(new_rect.x + indentation)
|
|
else:
|
|
if self.horizontal_alignment == wx.ALIGN_LEFT:
|
|
new_rect.SetLeft(new_rect.x + 3)
|
|
elif self.horizontal_alignment == wx.ALIGN_RIGHT:
|
|
new_rect.SetLeft(new_rect.x + (new_rect.width - full_width) - 1)
|
|
else:
|
|
space = int((new_rect.width - full_width)/2.0)
|
|
new_rect.SetLeft(new_rect.x + space)
|
|
new_rect.SetWidth(full_width+space)
|
|
|
|
if self.vertical_alignment == wx.ALIGN_TOP:
|
|
vspace = 0
|
|
elif self.vertical_alignment == wx.ALIGN_BOTTOM:
|
|
vspace = (new_rect.height - maxH - 1)
|
|
else:
|
|
vspace = int((new_rect.height - maxH)/2.0)
|
|
|
|
start = new_rect.x
|
|
y = new_rect.y
|
|
|
|
for chunk, font, colour in self.attributes:
|
|
dc.SetTextForeground(colour)
|
|
dc.SetFont(font)
|
|
width, height, descent, leading = dc.GetFullTextExtent(chunk, font)
|
|
if font.escapement > 0:
|
|
height = height*0.7
|
|
|
|
ypos = y-height+maxH+vspace
|
|
|
|
if font.escapement == 1:
|
|
ypos = ypos - maxH + height
|
|
|
|
dc.DrawText(chunk, start, ypos)
|
|
start += width
|
|
|
|
|
|
class XLSBackground(object):
|
|
"""
|
|
This is a class which holds information about the cell background, in terms
|
|
of background colour and background pattern (hatching).
|
|
"""
|
|
|
|
def __init__(self, book, xf_index):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`).
|
|
"""
|
|
|
|
XFClass = book.xf_list[xf_index]
|
|
|
|
background = XFClass.background
|
|
background_colour = book.colour_map[background.background_colour_index]
|
|
pattern_colour = book.colour_map[background.pattern_colour_index]
|
|
fill_pattern = background.fill_pattern
|
|
|
|
self.CreateBackgroundColour(background_colour, pattern_colour, fill_pattern)
|
|
|
|
|
|
def CreateBackgroundColour(self, background_colour, pattern_colour, fill_pattern):
|
|
"""
|
|
Creates a suitable wxPython colour for the cell background starting from
|
|
a `xlrd` tuple representing this colour.
|
|
|
|
:param `background_colour`: a tuple representing the RGB components of the
|
|
cell background colour. If `background_colour` is ``None``, use the
|
|
default ``wx.SYS_COLOUR_WINDOW``;
|
|
:param `pattern_colour`: a tuple representing the RGB components of the
|
|
cell pattern colour;
|
|
:param `fill_pattern`: the pattern to use to draw hatches on top of the
|
|
background.
|
|
"""
|
|
|
|
if background_colour is not None:
|
|
background_colour = wx.Colour(*background_colour)
|
|
else:
|
|
background_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
|
|
|
|
if pattern_colour is not None:
|
|
pattern_colour = wx.Colour(*pattern_colour)
|
|
|
|
self.background_brush = wx.Brush(background_colour)
|
|
self.background_colour = background_colour
|
|
|
|
self.fill_brush = None
|
|
|
|
if fill_pattern <= 0:
|
|
return
|
|
|
|
r, g, b, a = pattern_colour
|
|
|
|
fill_image = eval("_xls_background_%02d.GetImage()"%fill_pattern)
|
|
fill_image.Replace(0, 0, 0, r, g, b)
|
|
r, g, b = background_colour.Red(), background_colour.Green(), background_colour.Blue()
|
|
fill_image.Replace(255, 255, 255, r, g, b)
|
|
fill_bitmap = fill_image.ConvertToBitmap()
|
|
|
|
self.fill_brush = wx.Brush(fill_bitmap)
|
|
|
|
|
|
def CombineAttr(self, attr):
|
|
"""
|
|
Combines the input attribute `attr` with the features of the :class:`XLSBackground` class.
|
|
|
|
:param `attr`: an instance of :class:`grid.GridCellAttr`.
|
|
"""
|
|
|
|
attr.SetBackgroundColour(self.background_colour)
|
|
return attr
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws the cell background and pattern hatching on a grid cell.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
dc.SetClippingRegion(rect)
|
|
|
|
dc.SetBackgroundMode(wx.SOLID)
|
|
dc.SetBrush(self.background_brush)
|
|
dc.SetPen(wx.TRANSPARENT_PEN)
|
|
dc.DrawRectangle(rect)
|
|
|
|
if self.fill_brush:
|
|
|
|
dc.SetBrush(self.fill_brush)
|
|
dc.SetBackgroundMode(wx.TRANSPARENT)
|
|
dc.DrawRectangle(rect)
|
|
|
|
dc.DestroyClippingRegion()
|
|
|
|
|
|
class XLSBorder(object):
|
|
"""
|
|
This is a class which holds information about a single cell border, in terms
|
|
of its location (top, left, bottom, right, diagonal), its colour, width and
|
|
shape.
|
|
"""
|
|
|
|
def __init__(self, location, line_style, border_colour, default_colour, diagonals):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `location`: the actual border location (top, left, bottom, right,
|
|
diagonal);
|
|
:param `line_style`: the line style used by Excel to draw this border;
|
|
:param `border_colour`: the colour used by Excel to draw this border;
|
|
:param `default_colour`: the "magic" colour used by Excel to draw non-custom
|
|
border lines;
|
|
:param `diagonals`: a tuple containing whether or not to draw the up and down
|
|
diagonal borders.
|
|
"""
|
|
|
|
self.draw_priority = 2
|
|
|
|
if line_style == NO_LINE:
|
|
|
|
if border_colour == (0, 0, 0):
|
|
self.draw_priority = 0
|
|
border_colour = wx.Colour(*default_colour)
|
|
else:
|
|
self.draw_priority = 1
|
|
border_colour = wx.BLACK
|
|
|
|
self.pen = wx.Pen(border_colour, 1, wx.SOLID)
|
|
pen_style = THIN
|
|
|
|
else:
|
|
|
|
if border_colour == (0, 0, 0):
|
|
border_colour = wx.Colour(*default_colour)
|
|
self.draw_priority = 2
|
|
elif border_colour is None:
|
|
border_colour = wx.BLACK
|
|
self.draw_priority = 2
|
|
else:
|
|
border_colour = wx.Colour(*border_colour)
|
|
|
|
pen_width, pen_style = XF_PEN_STYLES[line_style]
|
|
if pen_width > 2:
|
|
self.draw_priority = 4
|
|
elif pen_width > 1:
|
|
self.draw_priority = 3
|
|
|
|
self.pen = wx.Pen(border_colour, pen_width, pen_style)
|
|
|
|
self.diagonals = diagonals
|
|
self.location = location
|
|
self.pen_style = pen_style
|
|
self.line_style = line_style
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws the cell border.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
dc.SetBackgroundMode(wx.TRANSPARENT)
|
|
dc.SetPen(self.pen)
|
|
|
|
if self.location == DIAGONAL:
|
|
self.DrawDiagonals(dc, rect)
|
|
else:
|
|
self.DrawBorder(dc, rect)
|
|
|
|
|
|
def DrawDiagonals(self, dc, rect):
|
|
"""
|
|
Actually draws the cell diagonal border.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
diag_up, diag_down = self.diagonals
|
|
|
|
if diag_down:
|
|
xstart, ystart = rect.GetTopLeft()
|
|
xend, yend = rect.GetBottomRight()
|
|
|
|
if self.line_style == DOUBLE:
|
|
dc.DrawLine(xstart+2, ystart, xend, yend-2)
|
|
dc.DrawLine(xstart, ystart+2, xend-2, yend)
|
|
else:
|
|
dc.DrawLine(xstart, ystart, xend, yend)
|
|
|
|
if diag_up:
|
|
|
|
xstart, ystart = rect.GetBottomLeft()
|
|
xend, yend = rect.GetTopRight()
|
|
|
|
if self.line_style == DOUBLE:
|
|
dc.DrawLine(xstart, ystart-2, xend-2, yend)
|
|
dc.DrawLine(xstart+2, ystart, xend, yend+2)
|
|
else:
|
|
dc.DrawLine(xstart, ystart, xend, yend)
|
|
|
|
|
|
def DrawBorder(self, dc, rect):
|
|
"""
|
|
Actually draws the cell border (one of left, right, bottom, top).
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
pen_width = self.pen.GetWidth()
|
|
location = self.location
|
|
new_rect = wx.Rect(*rect)
|
|
|
|
x, y, w, h = new_rect
|
|
line_style = self.line_style
|
|
|
|
shift = 0
|
|
if pen_width == 2:
|
|
shift = pen_width - 1
|
|
if pen_width > 2:
|
|
shift = pen_width - 2
|
|
|
|
if location == BOTTOM:
|
|
|
|
if self.draw_priority < 2:
|
|
return
|
|
|
|
if line_style == DOUBLE:
|
|
dc.DrawLine(x, y+h-1, x+w, y+h-1)
|
|
dc.DrawLine(x, y+h+1, x+w, y+h+1)
|
|
else:
|
|
dc.DrawLine(x+1, y+h, x+w, y+h)
|
|
|
|
|
|
elif location == TOP:
|
|
|
|
if line_style == DOUBLE:
|
|
dc.DrawLine(x, y-1, x+w, y-1)
|
|
dc.DrawLine(x, y+1, x+w, y+1)
|
|
else:
|
|
dc.DrawLine(x+1, y+shift, x+w, y+shift)
|
|
|
|
elif location == LEFT:
|
|
|
|
if line_style == DOUBLE:
|
|
dc.DrawLine(x-1, y, x-1, y+h)
|
|
dc.DrawLine(x+1, y, x+1, y+h)
|
|
else:
|
|
dc.DrawLine(x+shift, y+1, x+shift, y+h)
|
|
|
|
elif location == RIGHT:
|
|
|
|
if self.draw_priority < 2:
|
|
return
|
|
|
|
if line_style == DOUBLE:
|
|
dc.DrawLine(x+w-1, y, x+w-1, y+h)
|
|
dc.DrawLine(x+w+1, y, x+w+1, y+h)
|
|
else:
|
|
dc.DrawLine(x+w+1, y+1, x+w+1, y+h)
|
|
|
|
|
|
class XLSBorderFactory(object):
|
|
"""
|
|
This is a factory class which holds information about all the borders in a
|
|
cell. Its implementation and use is merely to simplify the handling of the
|
|
different cell borders (left, top, bottom, right, diagonal).
|
|
"""
|
|
|
|
def __init__(self, book, border, default_colour):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `border`: an instance of `xlrd.formatting.XFBorder` class;
|
|
:param `default_colour`: the "magic" colour used by Excel to draw non-custom
|
|
border lines.
|
|
"""
|
|
|
|
borders = {}
|
|
diagonals = border.diag_up, border.diag_down
|
|
|
|
for label, location in list(XF_BORDER_STYLES.items()):
|
|
line_style = getattr(border, "%s_line_style"%label)
|
|
colour_index = getattr(border, "%s_colour_index"%label)
|
|
border_colour = book.colour_map[colour_index]
|
|
|
|
border_class = XLSBorder(location, line_style, border_colour, default_colour, diagonals)
|
|
borders[location] = border_class
|
|
|
|
self.draw_priority = sorted(list(borders.values()), key=attrgetter('draw_priority'))
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws all the cell borders based on their drawing priority.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
|
|
:note: The drawing priority is assigned depending on if the border is a
|
|
custom one or not. Customized borders are drawn last.
|
|
"""
|
|
|
|
for border in self.draw_priority:
|
|
border.Draw(dc, rect)
|
|
|
|
|
|
class XLSComment(object):
|
|
"""
|
|
This is a class which holds information about the content of the "comment
|
|
window" (aka note) in Excel.
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, this class can
|
|
not be used.
|
|
"""
|
|
|
|
def __init__(self, comment):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `comment`: the actual text contained in the Excel cell comment (note).
|
|
"""
|
|
|
|
self.comment = comment
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
"""
|
|
Actually draws a small red triangle in the top-right corder of the cell
|
|
to indicate that a comment is present.
|
|
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle.
|
|
"""
|
|
|
|
right = rect.GetTopRight()
|
|
points = [wx.Point(right.x-5, right.y),
|
|
right,
|
|
wx.Point(right.x, right.y+5)]
|
|
|
|
dc.SetBrush(wx.RED_BRUSH)
|
|
dc.SetPen(wx.RED_PEN)
|
|
dc.DrawPolygon(points)
|
|
|
|
|
|
class XLSCell(object):
|
|
"""
|
|
This is a class which holds information about a single cell in :class:`XLSGrid`.
|
|
It stores (via auxiliary classes), all details about cell background, text,
|
|
font, colours and borders.
|
|
"""
|
|
|
|
def __init__(self, book, cell, xf_index, xls_text, xls_comment, hyperlink, rich_text, default_width, default_colour):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `cell`: an instance of `xlrd.sheet.Cell` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`);
|
|
:param `xls_text`: the actual WYSIWYG cell text, if available;
|
|
:param `xls_comment`: the cell comment (note), if any;
|
|
:param `hyperlink`: an instance of `xlrd.sheet.hyperlink`;
|
|
:param `rich_text`: if this cell contains text in rich text format, :class:`XLSGrid`
|
|
will do its best to render the text as rich text;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file);
|
|
:param `default_colour`: the "magic" colour used by Excel to draw non-custom
|
|
border lines.
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the `rich_text`
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: if Mark Hammonds' `pywin32` package is not available, the `xls_text`
|
|
parameter will almost surely not be the WYSIWYG representation of the cell
|
|
text.
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, the `xls_comment`
|
|
parameter will always be ``None``.
|
|
"""
|
|
|
|
self.size = 1, 1
|
|
|
|
self.comment = None
|
|
self.hyperlink = None
|
|
|
|
self.SetupCell(book, cell, xf_index, xls_text, xls_comment, hyperlink, rich_text, default_width, default_colour)
|
|
|
|
|
|
def SetupCell(self, book, cell, xf_index, xls_text, xls_comment, hyperlink, rich_text, default_width, default_colour):
|
|
"""
|
|
Actually sets up the :class:`XLSCell` class. This is an auxiliary method to
|
|
avoid cluttering the :meth:`~xlsgrid.XLSCell.__init__` method.
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `cell`: an instance of `xlrd.sheet.Cell` class;
|
|
:param `xf_index`: an index into `xlrd.Book.xf_list`, which holds a
|
|
reference to the `xlrd.sheet.Cell` class (the actual cell for `xlrd`);
|
|
:param `xls_text`: the actual WYSIWYG cell text, if available;
|
|
:param `xls_comment`: the cell comment (note), if any;
|
|
:param `hyperlink`: an instance of `xlrd.sheet.hyperlink`;
|
|
:param `rich_text`: if this cell contains text in rich text format, :class:`XLSGrid`
|
|
will do its best to render the text as rich text;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file);
|
|
:param `default_colour`: the "magic" colour used by Excel to draw non-custom
|
|
border lines.
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the `rich_text`
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: if Mark Hammonds' `pywin32` package is not available, the `xls_text`
|
|
parameter will almost surely not be the WYSIWYG representation of the cell
|
|
text.
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, the `xls_comment`
|
|
parameter will always be ``None``.
|
|
"""
|
|
|
|
cvalue = cell.value
|
|
self.raw_value = cvalue
|
|
|
|
if rich_text:
|
|
self.text = XLSRichText(book, cell, xf_index, xls_text, hyperlink, rich_text, default_width)
|
|
else:
|
|
self.text = XLSText(book, cell, xf_index, xls_text, hyperlink, default_width)
|
|
|
|
self.background = XLSBackground(book, xf_index)
|
|
|
|
XFClass = book.xf_list[xf_index]
|
|
border = XFClass.border
|
|
|
|
self.borders = XLSBorderFactory(book, border, default_colour)
|
|
|
|
if xls_comment:
|
|
self.comment = XLSComment(xls_comment)
|
|
|
|
self.attr = None
|
|
|
|
|
|
def GetAttr(self):
|
|
"""
|
|
Returns the attribute to use for this specific cell.
|
|
|
|
:returns: an instance of :class:`grid.GridCellAttr`.
|
|
"""
|
|
|
|
if self.attr is not None:
|
|
self.attr.IncRef()
|
|
return self.attr
|
|
|
|
attr = gridlib.GridCellAttr()
|
|
|
|
attr.SetRenderer(XLSRenderer(self))
|
|
|
|
attr.SetSize(*self.size)
|
|
attr.SetOverflow(True)
|
|
self.attr = attr
|
|
self.attr.IncRef()
|
|
|
|
return self.attr
|
|
|
|
|
|
def GetValue(self):
|
|
""" Returns the actual WYSIWYG representation of the cell value. """
|
|
|
|
return self.text.GetValue()
|
|
|
|
|
|
def SetValue(self, value):
|
|
"""
|
|
Sets the actual WYSIWYG representation of the cell value.
|
|
|
|
:param `value`: the current text value to insert in the cell.
|
|
|
|
:note: This method is currently unused as everything is handled inside the :class:`XLSText` class.
|
|
|
|
:see: :meth:`~xlsgrid.XLSCell.GetValue`
|
|
"""
|
|
|
|
self.value = value
|
|
|
|
|
|
def SetCellSize(self, rows, cols):
|
|
"""
|
|
Sets the size of the cell.
|
|
|
|
Specifying a value of more than 1 in `rows` or `cols` will make the cell
|
|
at (`row`, `col`) span the block of the specified size, covering the other
|
|
cells which would be normally shown in it. Passing 1 for both arguments
|
|
resets the cell to normal appearance.
|
|
|
|
:param `rows`: number of rows to be occupied by this cell, must be >= 1;
|
|
:param `cols`: number of columns to be occupied by this cell, must be >= 1.
|
|
"""
|
|
|
|
self.size = (rows, cols)
|
|
|
|
|
|
def GetComment(self):
|
|
"""
|
|
Returns the cell comment, if any.
|
|
|
|
:returns: an instance of :class:`XLSComment`.
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, this method
|
|
always returns ``None``.
|
|
"""
|
|
|
|
return self.comment
|
|
|
|
|
|
class XLSRenderer(gridlib.GridCellRenderer):
|
|
"""
|
|
This class is responsible for actually drawing the cell in the grid.
|
|
|
|
"""
|
|
|
|
def __init__(self, cell):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `cell`: an instance of :class:`XLSCell`.
|
|
"""
|
|
|
|
gridlib.GridCellRenderer.__init__(self)
|
|
self.cell = cell
|
|
|
|
|
|
def Draw(self, grid, attr, dc, rect, row, col, isSelected):
|
|
"""
|
|
Draw the given cell on the provided `dc` inside the given rectangle using
|
|
default or selected state corresponding to the `isSelected` value.
|
|
|
|
:param `grid`: an instance of :class:`grid.Grid`;
|
|
:param `attr`: an instance of :class:`grid.GridCellAttr`;
|
|
:param `dc`: an instance of :class:`DC`;
|
|
:param `rect`: an instance of :class:`Rect`, representing the cell rectangle;
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives;
|
|
:param `isSelected`: ``True`` if the cell is selected, ``False`` otherwise.
|
|
"""
|
|
|
|
# clear the background
|
|
dc.SetBackgroundMode(wx.SOLID)
|
|
|
|
cell = self.cell
|
|
|
|
cell.background.Draw(dc, rect)
|
|
|
|
if cell.borders:
|
|
cell.borders.Draw(dc, rect)
|
|
|
|
cell.text.Draw(dc, rect)
|
|
|
|
if cell.comment:
|
|
cell.comment.Draw(dc, rect)
|
|
|
|
if isSelected:
|
|
|
|
gdc = wx.GCDC(dc)
|
|
|
|
sys_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
|
|
brush_colour = wx.Colour(sys_colour.Red(), sys_colour.Green(), sys_colour.Blue(), 90)
|
|
|
|
gdc.SetBrush(wx.Brush(brush_colour))
|
|
gdc.SetPen(wx.TRANSPARENT_PEN)
|
|
|
|
gdc.DrawRectangle(rect)
|
|
|
|
|
|
class XLSTable(gridlib.GridTableBase):
|
|
"""
|
|
The almost abstract base class for grid tables.
|
|
|
|
A grid table is responsible for storing the grid data and, indirectly, grid
|
|
cell attributes. The data can be stored in the way most convenient for the
|
|
application but has to be provided in string form to :class:`grid.Grid`.
|
|
"""
|
|
|
|
def __init__(self, grid, cells, rows, cols):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `grid`: an instance of :class:`grid.Grid`;
|
|
:param `cells`: a Python dictionary. For every key `(row, col)`, the
|
|
corresponding value is an instance of :class:`XLSCell`;
|
|
:param `rows`: the number of rows in the table;
|
|
:param `cols`: the number of columns in the table.
|
|
"""
|
|
|
|
# The base class must be initialized *first*
|
|
gridlib.GridTableBase.__init__(self)
|
|
|
|
self.cells = cells
|
|
self.dimens = (rows, cols)
|
|
|
|
|
|
def GetNumberCols(self):
|
|
""" Returns the number of columns in the table. """
|
|
|
|
return self.dimens[1]
|
|
|
|
|
|
def GetNumberRows(self):
|
|
""" Returns the number of rows in the table. """
|
|
|
|
return self.dimens[0]
|
|
|
|
|
|
def GetValue(self, row, col):
|
|
"""
|
|
Returns the cell content for the specified row and column.
|
|
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives.
|
|
"""
|
|
|
|
cell = self.cells[(row, col)]
|
|
return cell.GetValue()
|
|
|
|
|
|
def SetValue(self, row, col, value):
|
|
"""
|
|
sets the cell content for the specified row and column.
|
|
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives;
|
|
:param `value`: the new value to assign to the specified cell.
|
|
"""
|
|
|
|
cell = self.cells[(row, col)]
|
|
cell.SetValue(value)
|
|
|
|
|
|
def GetAttr(self, row, col, kind):
|
|
"""
|
|
Return the attribute for the given cell.
|
|
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives;
|
|
:param `kind`: the kind of the attribute to return.
|
|
"""
|
|
|
|
cell = self.cells[(row, col)]
|
|
return cell.GetAttr()
|
|
|
|
|
|
def GetRawValue(self, row, col):
|
|
"""
|
|
Returns the "raw" value for the cell content.
|
|
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives.
|
|
"""
|
|
|
|
cell = self.cells[(row, col)]
|
|
return cell.raw_value
|
|
|
|
|
|
class XLSGrid(gridlib.Grid):
|
|
"""
|
|
:class:`XLSGrid` is a class based on :class:`grid.Grid` that can be used to faithfully
|
|
reproduce the appearance of a Microsoft Excel spreadsheet (one worksheet per
|
|
every instance of :class:`XLSGrid`).
|
|
|
|
:class:`XLSGrid` is a completely owner-drawn control, and it relies on the power of
|
|
:class:`grid.PyGridTableBase` and :class:`grid.PyGridCellRenderer` to draw the cell
|
|
content. For this reasons (and for some others, see the TODOs section), it will
|
|
work efficiently only for relatively small Excel files.
|
|
"""
|
|
|
|
def __init__(self, parent):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `parent`: the grid parent window. Must not be ``None``.
|
|
"""
|
|
|
|
gridlib.Grid.__init__(self, parent)
|
|
|
|
self.SetMargins(0, 0)
|
|
self.SetDefaultCellBackgroundColour(parent.GetBackgroundColour())
|
|
self.SetDefaultCellOverflow(True)
|
|
|
|
self.tip_window = None
|
|
self.tip_shown = False
|
|
|
|
|
|
def DestroyTip(self):
|
|
"""
|
|
If a comment window or a tooltip over a hyperlink have been created, this
|
|
method destroys them.
|
|
"""
|
|
|
|
if self.tip_window:
|
|
try:
|
|
self.tip_window.GetTipWindow().Destroy()
|
|
except RuntimeError:
|
|
pass
|
|
|
|
del self.tip_window
|
|
self.tip_window = None
|
|
|
|
if self.tip_shown:
|
|
self.GetGridWindow().SetToolTip("")
|
|
self.GetGridWindow().SetCursor(wx.NullCursor)
|
|
self.tip_shown = False
|
|
|
|
|
|
def InstallGridHint(self):
|
|
"""
|
|
Auxiliary method used to bind a ``wx.EVT_MOTION`` event to :class:`XLSGrid`.
|
|
"""
|
|
|
|
self.prev_rowcol = [None, None]
|
|
|
|
def OnMouseMotion(event):
|
|
"""
|
|
Handles a the ``wx.EVT_MOTION`` events for :class:`XLSGrid`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
# evt.GetRow() and evt.GetCol() would be nice to have here,
|
|
# but as this is a mouse event, not a grid event, they are not
|
|
# available and we need to compute them by hand.
|
|
position = event.GetPosition()
|
|
x, y = self.CalcUnscrolledPosition(position)
|
|
row = self.YToRow(y)
|
|
col = self.XToCol(x)
|
|
|
|
if [row, col] != self.prev_rowcol and row >= 0 and col >= 0:
|
|
|
|
self.prev_rowcol[:] = [row, col]
|
|
self.DestroyTip()
|
|
cell = self.cells[(row, col)]
|
|
rect = self.CellToRect(row, col)
|
|
comment = cell.GetComment()
|
|
|
|
window = self.GetGridWindow()
|
|
if cell.text.IsHyperLink():
|
|
window.SetCursor(wx.Cursor(wx.CURSOR_HAND))
|
|
window.SetToolTip(cell.text.tooltip)
|
|
self.tip_shown = True
|
|
if not comment:
|
|
return
|
|
|
|
if comment:
|
|
self.tip_window = TransientPopup(window, comment, wx.GetMousePosition())
|
|
event.Skip()
|
|
|
|
self.GetGridWindow().Bind(wx.EVT_MOTION, OnMouseMotion)
|
|
|
|
|
|
def PopulateGrid(self, book, sheet, display_texts, comments):
|
|
"""
|
|
This is the main method of this class, and it is used to actually create
|
|
the cells, size the columns and rows, merging cells, etc...
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `sheet`: an instance of the `xlrd.sheet` class;
|
|
:param `display_texts`: if Mark Hammonds' `pywin32` package is available,
|
|
this is the WYSIWYG cell content for all the cells in the Excel worksheet;
|
|
:param `comments`: if Mark Hammonds' `pywin32` package is available,
|
|
this is a nested list of cell comments (notes) for all the cells in the
|
|
Excel worksheet.
|
|
"""
|
|
|
|
self.BeginBatch()
|
|
|
|
nrows = sheet.nrows
|
|
ncols = sheet.ncols
|
|
|
|
default_width, default_height = self.GetDefaultFontData(book)
|
|
default_colour = self.GetGridLineColour()
|
|
|
|
hyperlinks, rich_text_list = {}, {}
|
|
if hasattr(sheet, "hyperlink_map"):
|
|
# New in xlrd version 0.7.2 from SVN
|
|
hyperlinks = sheet.hyperlink_map
|
|
|
|
if hasattr(sheet, "rich_text_runlist_map"):
|
|
# New in xlrd version 0.7.2 from SVN
|
|
rich_text_list = sheet.rich_text_runlist_map
|
|
|
|
self.cells = {}
|
|
|
|
for i in range(nrows):
|
|
|
|
for j in range(ncols):
|
|
|
|
hyperlink = rich_text = None
|
|
|
|
if (i, j) in hyperlinks:
|
|
hyperlink = hyperlinks[(i, j)]
|
|
if (i, j) in rich_text_list:
|
|
rich_text = rich_text_list[(i, j)]
|
|
|
|
self.FormatCell(book, sheet, i, j, display_texts, comments, hyperlink, rich_text, default_width, default_colour)
|
|
|
|
self.table = XLSTable(self, self.cells, nrows, ncols)
|
|
self.SetTable(self.table)
|
|
|
|
row_height = sheet.default_row_height
|
|
col_width = sheet.defcolwidth
|
|
|
|
for i in range(nrows):
|
|
if i in sheet.rowinfo_map:
|
|
current = sheet.rowinfo_map[i].height
|
|
else:
|
|
current = sheet.default_row_height
|
|
|
|
row_height = int(round(float(default_height)*current/256.0))
|
|
self.SetRowSize(i, row_height)
|
|
|
|
for j in range(ncols):
|
|
if j in sheet.colinfo_map:
|
|
current = sheet.colinfo_map[j].width
|
|
else:
|
|
current = sheet.defcolwidth
|
|
|
|
col_width = int(round(float(default_width)*current/256.0))
|
|
self.SetColSize(j, col_width)
|
|
|
|
for merged in sheet.merged_cells:
|
|
rlo, rhi, clo, chi = merged
|
|
if rlo >= 0 and rlo < len(self.cells) and clo >= 0:
|
|
self.cells[(rlo, clo)].SetCellSize(rhi-rlo, chi-clo)
|
|
|
|
self.EnableEditing(False)
|
|
self.EnableGridLines(False)
|
|
self.EndBatch()
|
|
self.ForceRefresh()
|
|
self.InstallGridHint()
|
|
|
|
|
|
def FormatCell(self, book, sheet, row, col, display_texts, comments, hyperlink, rich_text, default_width, default_colour):
|
|
"""
|
|
Processes the creation of a single cell (an instance of :class:`XLSCell`).
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class;
|
|
:param `sheet`: an instance of the `xlrd.sheet` class;
|
|
:param `row`: the row in which this cell lives;
|
|
:param `col`: the column in which this cell lives;
|
|
:param `display_texts`: if Mark Hammonds' `pywin32` package is available,
|
|
this is the WYSIWYG cell content for all the cells in the Excel worksheet;
|
|
:param `comments`: if Mark Hammonds' `pywin32` package is available,
|
|
this is a nested list of cell comments (notes) for all the cells in the
|
|
Excel worksheet;
|
|
:param `hyperlink`: if this cell contains a hyperlink, it will be displayed
|
|
accordingly;
|
|
:param `rich_text`: if this cell contains text in rich text format, :class:`XLSGrid`
|
|
will do its best to render the text as rich text;
|
|
:param `default_width`: this is the default width of the text in 1/256
|
|
of the width of the zero character, using default Excel font (first FONT
|
|
record in the Excel file);
|
|
:param `default_colour`: the "magic" colour used by Excel to draw non-custom
|
|
border lines.
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the *hyperlink*
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: If you are using version 0.7.1 or lower for `xlrd`, the `rich_text`
|
|
parameter will always be ``None`` as this feature is available only in
|
|
`xlrd` 0.7.2 (SVN).
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, the `display_texts`
|
|
and `comments` parameter will be two empty nested lists.
|
|
"""
|
|
|
|
cell = sheet.cell(row, col)
|
|
|
|
xf_index = sheet.cell_xf_index(row, col)
|
|
xls_text, xls_comment = display_texts[row][col], comments[row][col]
|
|
|
|
gridCell = XLSCell(book, cell, xf_index, xls_text, xls_comment, hyperlink, rich_text, default_width, default_colour)
|
|
|
|
self.cells[(row, col)] = gridCell
|
|
|
|
|
|
def GetDefaultFontData(self, book):
|
|
"""
|
|
Returns suitable width and height (in pixels) starting from Excel's own
|
|
measurements (in characters, whatever that means).
|
|
|
|
:param `book`: an instance of the `xlrd.Book` class.
|
|
|
|
:returns: a `default_width` and `default_height` in pixels, based on the
|
|
default width of the text in 1/256 of the width of the zero character,
|
|
using default Excel font (first FONT record in the Excel file).
|
|
"""
|
|
|
|
font = book.font_list[0]
|
|
style, bold, underline = wx.FONTSTYLE_NORMAL, wx.NORMAL, False
|
|
|
|
if font.italic:
|
|
style = wx.FONTSTYLE_ITALIC
|
|
if font.underline_type > 0:
|
|
underline = True
|
|
if font.weight > 600:
|
|
bold = wx.BOLD
|
|
|
|
family = XF_FONT_FAMILY[font.family]
|
|
name = font.name
|
|
size = int(font.height/20.0)
|
|
|
|
dc = wx.ClientDC(self)
|
|
font = wx.Font(size, family, style, bold, underline, name.encode())
|
|
dc.SetFont(font)
|
|
width, height, descent, leading = dc.GetFullTextExtent("0", font)
|
|
|
|
return width, height + descent - leading
|
|
|
|
|
|
class TransientPopup(STT.SuperToolTip):
|
|
"""
|
|
This is a sublass of :class:`SuperToolTip` and it is used to display a
|
|
"comment-window" on the cells containing a comment (a note).
|
|
|
|
:note: If Mark Hammonds' `pywin32` package is not available, this class is
|
|
never invoked.
|
|
"""
|
|
|
|
def __init__(self, grid_window, comment, position):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `grid_window`: the actual window representing the grid;
|
|
:param `comment`: an instance of :class:`XLSComment`, containing the
|
|
text for this comment;
|
|
:param `position`: the position at which we pop up the comment
|
|
window (currently unused).
|
|
"""
|
|
|
|
STT.SuperToolTip.__init__(self, grid_window)
|
|
|
|
xls_comment = comment.comment
|
|
|
|
split = xls_comment.split(":")
|
|
header, rest = split[0], split[1:]
|
|
rest = ":".join(rest)
|
|
|
|
dc = wx.ClientDC(grid_window)
|
|
rest = wordwrap(rest, 400, dc)
|
|
|
|
self.SetHeader(header)
|
|
self.SetMessage(rest)
|
|
self.SetTarget(grid_window)
|
|
self.SetDrawHeaderLine(True)
|
|
|
|
self.SetStartDelay(100000)
|
|
self.SetEndDelay(100000)
|
|
self.ApplyStyle("Office 2007 Blue")
|
|
|
|
self.SetDropShadow(True)
|
|
self.DoShowNow()
|
|
|
|
grid_window.SetFocus()
|