Files
create/src/Mod/Spreadsheet/Spreadsheet.py
2013-08-05 15:57:00 -03:00

386 lines
14 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2013 - Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program 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 Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
import re, math, FreeCAD, FreeCADGui
from PyQt4 import QtCore,QtGui
class Spreadsheet(object):
"""An object representing a spreadsheet. Can be used as a
FreeCAD object or as a standalone python object.
Cells of the spreadsheet can be got/set as arguments, as:
myspreadsheet.a1 = 54
print(myspreadsheet.a1)
myspreadsheet.a2 = "My text"
myspreadsheet.b1 = "=a1*3"
print(myspreadsheet.a3)
Functions usable in formulae are limited to the contents of
the math module."""
def __init__(self,obj=None):
if obj:
obj.Proxy = self
self._cells = {}
self._relations = {}
self.cols = []
self.rows = []
self.Type = "Spreadsheet"
def __repr__(self):
return "Spreadsheet object containing " + str(len(self._cells)) + " cells"
def __setattr__(self, key, value):
#print "setting key:",key," to value:",value
if self.isKey(key):
self._cells[key] = value
if value:
if self.isFunction(value):
self._updateDependencies(key,value)
c,r = self.splitKey(key)
if not c in self.cols:
self.cols.append(c)
self.cols.sort()
if not r in self.rows:
self.rows.append(r)
self.rows.sort()
else:
self.__dict__.__setitem__(key,value)
def __getattr__(self, key):
if key in self._cells:
if self.isFunction(self._cells[key]):
#print "result = ",self.getFunction(key)
# building a list of safe functions allowed in eval
safe_list = ['acos', 'asin', 'atan', 'atan2', 'ceil',
'cos', 'cosh', 'e', 'exp', 'fabs',
'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log',
'log10', 'modf', 'pi', 'pow', 'radians', 'sin',
'sinh', 'sqrt', 'tan', 'tanh']
tools = dict((k, getattr(math, k)) for k in safe_list)
# adding abs
tools["abs"] = abs
# removing all builtins from allowed functions
tools["__builtins__"] = None
try:
e = eval(self._format(key),tools,{"self":self})
except:
print "Error evaluating formula"
return self._cells[key]
else:
return e
else:
return self._cells[key]
else:
return None
def __getstate__(self):
return self._cells
def __setstate__(self,state):
if state:
self._cells = state
# updating relation tables
self.rows = []
self.cols = []
self._relations = []
for key in self._cells.keys():
r,c = self.splitKey(key)
if not r in self.rows:
self.rows.append(r)
self.rows.sort()
if not c in self.cols:
self.cols.append(c)
self.cols.sort()
if self.isFunction(key):
self._updateDependencies(key)
def _format(self,key):
"formats all cellnames in the function a the given cell"
elts = re.split(r'(\W+)',self._cells[key][1:])
#print elts
result = ''
for e in elts:
if self.isKey(e):
result += "self."+e
else:
result += e
return result
def _updateDependencies(self,key,value=None):
"search for ancestors in the value and updates the table"
ancestors = []
if not value:
value = self._cells[key]
for v in re.findall(r"[\w']+",value):
if self.isKey(v):
ancestors.append(v)
for a in ancestors:
if a in self._relations:
if not key in self._relations[a]:
self._relations[a].append(key)
else:
self._relations[a] = [key]
def execute(self,obj=None):
pass
def isFunction(self,key):
"isFunction(cell): returns True if the given cell or value is a function"
if key in self._cells:
if str(self._cells[key])[0] == "=":
return True
elif str(key)[0] == "=":
return True
else:
return False
def isKey(self,value):
"isKey(val): returns True if the given value is a valid cell number"
allowMoreThanOneLetter = False
al = False
nu = False
for v in value:
if not v.isalnum():
return False
elif not al:
if v.isalpha():
al = True
else:
return False
else:
if not nu:
# forbidden to set items at row 0
if v == "0":
return False
if v.isalpha():
if not allowMoreThanOneLetter:
return False
elif nu:
return False
elif v.isdigit():
nu = True
if not nu:
return False
return True
def splitKey(self,key):
"splitKey(cell): splits a key between column and row"
c = ''
r = ''
for ch in key:
if ch.isalpha():
c += ch
else:
r += ch
return c,r
def getFunction(self,key):
"getFunction(cell): returns the function contained in the given cell, instead of the value"
if key in self._cells:
return self._cells[key]
else:
return None
def getSize(self):
"getSize(): returns a tuple with number of columns and rows of this spreadsheet"
return (len(self.columns),len(self.rows))
def getCells(self,index):
"getCells(index): returns the cells from the given column of row number"
cells = {}
for k in self._cells.keys():
c,r = self.splitKey(k)
if index in [c,r]:
cells[k] = self._cells[k]
return cells
class ViewProviderSpreadsheet(object):
def __init__(self, vobj):
vobj.Proxy = self
def getIcon(self):
import Spreadsheet_rc
return ":/icons/Spreadsheet.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def getDisplayModes(self,vobj):
return ["None"]
def getDefaultDisplayMode(self):
return "None"
def setDisplayMode(self,mode):
return mode
def setEdit(self,vobj,mode):
if hasattr(self,"editor"):
pass
else:
self.editor = SpreadsheetView(vobj.Object)
addSpreadsheetView(self.editor)
return True
def unsetEdit(self,vobj,mode):
return False
class SpreadsheetView(QtGui.QWidget):
"A spreadsheet viewer for FreeCAD"
def __init__(self,spreadsheet=None):
from DraftTools import translate
QtGui.QWidget.__init__(self)
self.setWindowIcon(QtGui.QIcon(":/icons/Spreadsheet.svg"))
self.setWindowTitle(str(translate("Spreadsheet","Spreadsheet")))
self.setObjectName("Spreadsheet viewer")
self.verticalLayout = QtGui.QVBoxLayout(self)
# add editor line
self.horizontalLayout = QtGui.QHBoxLayout()
self.label = QtGui.QLabel(self)
self.label.setMinimumSize(QtCore.QSize(82, 0))
self.label.setText(str(translate("Spreadsheet","Cell"))+":")
self.horizontalLayout.addWidget(self.label)
self.lineEdit = QtGui.QLineEdit(self)
self.horizontalLayout.addWidget(self.lineEdit)
self.verticalLayout.addLayout(self.horizontalLayout)
# add table
self.table = QtGui.QTableWidget(20,26,self)
for i in range(26):
ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i]
self.table.setHorizontalHeaderItem(i, QtGui.QTableWidgetItem(ch))
self.verticalLayout.addWidget(self.table)
self.spreadsheet = spreadsheet
self.update()
QtCore.QObject.connect(self.table, QtCore.SIGNAL("cellChanged(int,int)"), self.changeCell)
QtCore.QObject.connect(self.table, QtCore.SIGNAL("currentCellChanged(int,int,int,int)"), self.activeCell)
self.doNotChange = False
def __del__(self):
if self.spreadsheet:
if hasattr(self.spreadsheet,"ViewObject"):
if self.spreadsheet.ViewObject:
if hasattr(self.spreadsheet.ViewObject,"editor"):
del self.spreadsheet.ViewObject.editor
def update(self):
"fills the cells with the contents of the spreadsheet"
if self.spreadsheet:
for cell in self.spreadsheet.Proxy._cells.keys():
c,r = self.spreadsheet.Proxy.splitKey(cell)
c = "abcdefghijklmnopqrstuvwxyz".index(c)
r = int(str(r))-1
content = getattr(self.spreadsheet.Proxy,cell)
if content == None:
content = ""
print "Updating ",cell," to ",content
if self.table.item(r,c):
self.table.item(r,c).setText(content)
def changeCell(self,r,c):
"changes the contens of a cell"
if self.doNotChange:
self.doNotChange = False
else:
key = "abcdefghijklmnopqrstuvwxyz"[c]+str(r+1)
value = self.table.item(r,c).text()
print "Changing "+key+" to "+value
if self.spreadsheet:
# store the entry as best as possible
try:
v = int(value)
except:
try:
v = float(value)
except:
try:
v = v = str(value)
except:
v = value
setattr(self.spreadsheet.Proxy,key,v)
if self.spreadsheet.Proxy.isFunction(key):
# if we have a formula, change the displayed value
content = getattr(self.spreadsheet.Proxy,key)
if content == None:
content = ""
# do not trigger a cell change
self.doNotChange = True
self.table.item(r,c).setText(str(content))
self.activeCell(r,c)
def activeCell(self,r,c,orr=None,orc=None):
"sets the contents of the active cell to the header"
c = "abcdefghijklmnopqrstuvwxyz"[c]
r = r+1
print "Active cell "+c+str(r)
from DraftTools import translate
self.label.setText(str(translate("Spreadsheet","Cell"))+" "+c.upper()+str(r)+" :")
content = self.spreadsheet.Proxy.getFunction(c+str(r))
if content == None:
content = ""
self.lineEdit.setText(str(content))
class _CommandSpreadsheet:
"the Spreadsheet FreeCAD command"
def GetResources(self):
return {'Pixmap' : 'Spreadsheet',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Create","Spreadsheet"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Spreadsheet_Create","Adds a spreadsheet object to the active document")}
def Activated(self):
from DraftTools import translate
FreeCAD.ActiveDocument.openTransaction(str(translate("Arch","Create Spreadsheet")))
FreeCADGui.doCommand("import Spreadsheet")
FreeCADGui.doCommand("Spreadsheet.makeSpreadsheet()")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
def makeSpreadsheet():
"makeSpreadsheet(): adds a spreadsheet object to the active document"
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","Spreadsheet")
Spreadsheet(obj)
if FreeCAD.GuiUp:
ViewProviderSpreadsheet(obj.ViewObject)
return obj
def addSpreadsheetView(view):
"addSpreadsheetView(view): adds the given spreadsheet view to the FreeCAD MDI area"
if FreeCAD.GuiUp:
import Spreadsheet_rc
mdi = FreeCADGui.getMainWindow().findChild(QtGui.QMdiArea)
mdi.addSubWindow(view)
FreeCADGui.addCommand('Spreadsheet_Create',_CommandSpreadsheet())