459 lines
13 KiB
Python
459 lines
13 KiB
Python
|
|
import os
|
|
import wx
|
|
import re
|
|
|
|
from sys import platform
|
|
from configtool.decoration import Decoration
|
|
from configtool.data import (defineValueFormat, defineBoolFormat,
|
|
reHelpTextStart, reHelpTextEnd,
|
|
reDefine, reDefineBL, reDefQS, reDefQSm,
|
|
reDefQSm2, reDefBool, reDefBoolBL)
|
|
from configtool.mechanicalpage import MechanicalPage
|
|
from configtool.accelerationpage import AccelerationPage
|
|
from configtool.miscellaneouspage import MiscellaneousPage
|
|
from configtool.protectedfiles import protectedFiles
|
|
|
|
|
|
class PrinterPanel(wx.Panel):
|
|
def __init__(self, parent, nb, settings):
|
|
wx.Panel.__init__(self, nb, wx.ID_ANY)
|
|
self.parent = parent
|
|
|
|
self.deco = Decoration()
|
|
self.configFile = None
|
|
self.protFileLoaded = False
|
|
|
|
self.settings = settings
|
|
|
|
self.cfgValues = {}
|
|
self.heaters = []
|
|
self.dir = os.path.join(self.settings.folder, "config")
|
|
self.cfgDir = os.path.join(self.settings.folder, "configtool")
|
|
|
|
self.SetBackgroundColour(self.deco.getBackgroundColour())
|
|
self.Bind(wx.EVT_PAINT, self.deco.onPaintBackground)
|
|
sz = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
self.nb = wx.Notebook(self, wx.ID_ANY, size = (21, 21),
|
|
style = wx.BK_DEFAULT)
|
|
self.nb.SetBackgroundColour(self.deco.getBackgroundColour())
|
|
self.nb.SetFont(self.settings.font)
|
|
|
|
self.pages = []
|
|
self.titles = []
|
|
self.pageModified = []
|
|
self.pageValid = []
|
|
|
|
self.pgMech = self.registerPage(MechanicalPage, "Mechanical")
|
|
self.pgAcc = self.registerPage(AccelerationPage, "Acceleration")
|
|
self.pgMiscellaneous = self.registerPage(MiscellaneousPage,
|
|
"Miscellaneous")
|
|
|
|
sz.Add(self.nb, 1, wx.EXPAND + wx.ALL, 5)
|
|
|
|
self.SetSizer(sz)
|
|
self.Fit()
|
|
|
|
def registerPage(self, klass, label, *args, **kwargs):
|
|
page = klass(self, self.nb, len(self.pages), *args,
|
|
font = self.settings.font, **kwargs)
|
|
self.nb.AddPage(page, label)
|
|
self.pages.append(page)
|
|
self.titles.append(label)
|
|
self.pageModified.append(False)
|
|
self.pageValid.append(True)
|
|
return page
|
|
|
|
def getFileName(self):
|
|
return self.configFile
|
|
|
|
def assertModified(self, pg, flag = True):
|
|
self.pageModified[pg] = flag
|
|
self.modifyTab(pg)
|
|
|
|
def isModified(self):
|
|
return (True in self.pageModified)
|
|
|
|
def isValid(self):
|
|
return not (False in self.pageValid)
|
|
|
|
def hasData(self):
|
|
return (self.configFile != None)
|
|
|
|
def assertValid(self, pg, flag = True):
|
|
self.pageValid[pg] = flag
|
|
self.modifyTab(pg)
|
|
|
|
if False in self.pageValid:
|
|
self.parent.enableSavePrinter(False, False)
|
|
else:
|
|
self.parent.enableSavePrinter(not self.protFileLoaded, True)
|
|
|
|
def modifyTab(self, pg):
|
|
if self.pageModified[pg] and not self.pageValid[pg]:
|
|
pfx = "?* "
|
|
elif self.pageModified[pg]:
|
|
pfx = "* "
|
|
elif not self.pageValid[pg]:
|
|
pfx = "? "
|
|
else:
|
|
pfx = ""
|
|
|
|
self.nb.SetPageText(pg, pfx + self.titles[pg])
|
|
if True in self.pageModified and False in self.pageValid:
|
|
pfx = "?* "
|
|
elif True in self.pageModified:
|
|
pfx = "* "
|
|
elif False in self.pageValid:
|
|
pfx = "? "
|
|
else:
|
|
pfx = ""
|
|
self.parent.setPrinterTabDecor(pfx)
|
|
|
|
def setHeaters(self, ht):
|
|
return self.pgMiscellaneous.setHeaters(ht)
|
|
|
|
def onClose(self, evt):
|
|
if not self.confirmLoseChanges("exit"):
|
|
return
|
|
|
|
self.Destroy()
|
|
|
|
def confirmLoseChanges(self, msg):
|
|
if True not in self.pageModified:
|
|
return True
|
|
|
|
dlg = wx.MessageDialog(self, "Are you sure you want to " + msg + "?\n"
|
|
"There are changes to your printer "
|
|
"configuration that will be lost.",
|
|
"Changes pending",
|
|
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
|
|
rc = dlg.ShowModal()
|
|
dlg.Destroy()
|
|
|
|
if rc != wx.ID_YES:
|
|
return False
|
|
|
|
return True
|
|
|
|
def onLoadConfig(self, evt):
|
|
if not self.confirmLoseChanges("load a new printer configuration"):
|
|
return
|
|
|
|
if platform == "darwin":
|
|
# Mac OS X appears to be a bit limited on wildcards.
|
|
wildcard = "Printer configuration (printer.*.h)|*.h"
|
|
else:
|
|
wildcard = "Printer configuration (printer.*.h)|printer.*.h"
|
|
|
|
dlg = wx.FileDialog(self, message = "Choose a printer config file",
|
|
defaultDir = self.dir, defaultFile = "",
|
|
wildcard = wildcard, style = wx.OPEN | wx.CHANGE_DIR)
|
|
|
|
path = None
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
path = dlg.GetPath()
|
|
|
|
dlg.Destroy()
|
|
if path is None:
|
|
return
|
|
|
|
self.dir = os.path.dirname(path)
|
|
rc, efn = self.loadConfigFile(path)
|
|
|
|
if not rc:
|
|
dlg = wx.MessageDialog(self, "Unable to process file %s." % efn,
|
|
"File error", wx.OK + wx.ICON_ERROR)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
return
|
|
|
|
def loadConfigFile(self, fn):
|
|
cfgFn = os.path.join(self.cfgDir, "printer.generic.h")
|
|
try:
|
|
self.cfgBuffer = list(open(cfgFn))
|
|
except:
|
|
return False, cfgFn
|
|
|
|
try:
|
|
self.userBuffer = list(open(fn))
|
|
except:
|
|
return False, fn
|
|
|
|
self.configFile = fn
|
|
|
|
gatheringHelpText = False
|
|
helpTextString = ""
|
|
helpKey = None
|
|
|
|
self.cfgValues = {}
|
|
self.cfgNames = []
|
|
self.helpText = {}
|
|
|
|
prevLines = ""
|
|
for ln in self.cfgBuffer:
|
|
if gatheringHelpText:
|
|
if reHelpTextEnd.match(ln):
|
|
gatheringHelpText = False
|
|
helpTextString = helpTextString.strip()
|
|
# Keep paragraphs with double-newline.
|
|
helpTextString = helpTextString.replace("\n\n ", "\n\n")
|
|
# Keep indented lines, typically a list.
|
|
helpTextString = helpTextString.replace("\n\n ", "\n\n ")
|
|
helpTextString = helpTextString.replace("\n ", "\n\n ")
|
|
# Remove all other newlines and indents.
|
|
helpTextString = helpTextString.replace("\n ", " ")
|
|
hk = helpKey.split()
|
|
for k in hk:
|
|
self.helpText[k] = helpTextString
|
|
helpTextString = ""
|
|
helpKey = None
|
|
continue
|
|
else:
|
|
helpTextString += ln
|
|
continue
|
|
|
|
m = reHelpTextStart.match(ln)
|
|
if m:
|
|
t = m.groups()
|
|
gatheringHelpText = True
|
|
helpKey = t[0]
|
|
continue
|
|
|
|
if ln.rstrip().endswith("\\"):
|
|
prevLines += ln.rstrip()[:-1]
|
|
continue
|
|
|
|
if prevLines != "":
|
|
ln = prevLines + ln
|
|
prevLines = ""
|
|
|
|
self.parseDefineName(ln)
|
|
self.parseDefineValue(ln)
|
|
|
|
# Read the user configuration. This usually overwrites all of the items
|
|
# read above, but not those missing in the user configuration, e.g.
|
|
# when reading an older config.
|
|
gatheringHelpText = False
|
|
prevLines = ""
|
|
for ln in self.userBuffer:
|
|
if gatheringHelpText:
|
|
if reHelpTextEnd.match(ln):
|
|
gatheringHelpText = False
|
|
continue
|
|
|
|
if reHelpTextStart.match(ln):
|
|
gatheringHelpText = True
|
|
continue
|
|
|
|
if ln.rstrip().endswith("\\"):
|
|
prevLines += ln.rstrip()[:-1]
|
|
continue
|
|
|
|
if prevLines != "":
|
|
ln = prevLines + ln
|
|
prevLines = ""
|
|
|
|
self.parseDefineValue(ln)
|
|
|
|
# Parsing done. All parsed stuff is now in these arrays and dicts.
|
|
# Uncomment for debugging.
|
|
#print self.cfgValues # #defines with a value and booleans.
|
|
#print self.cfgNames # Names found in the generic file.
|
|
#print self.helpText
|
|
|
|
if os.path.basename(fn) in protectedFiles:
|
|
self.parent.enableSavePrinter(False, True)
|
|
self.protFileLoaded = True
|
|
else:
|
|
self.protFileLoaded = False
|
|
self.parent.enableSavePrinter(True, True)
|
|
self.parent.setPrinterTabFile(os.path.basename(fn))
|
|
|
|
for pg in self.pages:
|
|
pg.insertValues(self.cfgValues)
|
|
pg.setHelpText(self.helpText)
|
|
|
|
k = 'DC_EXTRUDER'
|
|
if k in self.cfgValues.keys() and self.cfgValues[k][1] == True:
|
|
self.pgMiscellaneous.setOriginalHeater(self.cfgValues[k][0])
|
|
else:
|
|
self.pgMiscellaneous.setOriginalHeater(None)
|
|
|
|
return True, None
|
|
|
|
def parseDefineName(self, ln):
|
|
m = reDefBool.search(ln)
|
|
if m:
|
|
t = m.groups()
|
|
if len(t) == 1:
|
|
self.cfgNames.append(t[0])
|
|
return True
|
|
|
|
return False
|
|
|
|
def parseDefineValue(self, ln):
|
|
m = reDefQS.search(ln)
|
|
if m:
|
|
t = m.groups()
|
|
if len(t) == 2:
|
|
m = reDefQSm.search(ln)
|
|
if m:
|
|
t = m.groups()
|
|
tt = re.findall(reDefQSm2, t[1])
|
|
if len(tt) == 1 and (t[0] in self.cfgNames):
|
|
self.cfgValues[t[0]] = tt[0], True
|
|
return True
|
|
elif len(tt) > 1 and (t[0] in self.cfgNames):
|
|
self.cfgValues[t[0]] = tt, True
|
|
return True
|
|
|
|
m = reDefine.search(ln)
|
|
if m:
|
|
t = m.groups()
|
|
if len(t) == 2 and (t[0] in self.cfgNames):
|
|
if reDefineBL.search(ln):
|
|
self.cfgValues[t[0]] = t[1], True
|
|
else:
|
|
self.cfgValues[t[0]] = t[1], False
|
|
return True
|
|
|
|
m = reDefBool.search(ln)
|
|
if m:
|
|
t = m.groups()
|
|
# Accept booleans, but not those for which a value exists already.
|
|
# Booleans already existing as values are most likely misconfigured
|
|
# manual edits (or result of a bug).
|
|
if len(t) == 1 and t[0] in self.cfgNames \
|
|
and not (t[0] in self.cfgValues \
|
|
and isinstance(self.cfgValues[t[0]], tuple)):
|
|
if reDefBoolBL.search(ln):
|
|
self.cfgValues[t[0]] = True
|
|
else:
|
|
self.cfgValues[t[0]] = False
|
|
return True
|
|
|
|
return False
|
|
|
|
def onSaveConfig(self, evt):
|
|
path = self.configFile
|
|
return self.saveConfigFile(path)
|
|
|
|
def onSaveConfigAs(self, evt):
|
|
wildcard = "Printer configuration (printer.*.h)|printer.*.h"
|
|
|
|
dlg = wx.FileDialog(self, message = "Save as ...", defaultDir = self.dir,
|
|
defaultFile = "", wildcard = wildcard,
|
|
style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
|
|
|
|
val = dlg.ShowModal()
|
|
|
|
if val != wx.ID_OK:
|
|
dlg.Destroy()
|
|
return
|
|
|
|
path = dlg.GetPath()
|
|
dlg.Destroy()
|
|
|
|
rc = self.saveConfigFile(path)
|
|
if rc:
|
|
self.parent.setPrinterTabFile(os.path.basename(path))
|
|
self.protFileLoaded = False
|
|
self.parent.enableSavePrinter(True, True)
|
|
return rc
|
|
|
|
def saveConfigFile(self, path):
|
|
if os.path.basename(path) in protectedFiles:
|
|
dlg = wx.MessageDialog(self, "It's not allowed to overwrite files "
|
|
"distributed by Teacup. Choose another name.",
|
|
"Protected file error", wx.OK + wx.ICON_ERROR)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
if not os.path.basename(path).startswith("printer."):
|
|
dlg = wx.MessageDialog(self, "Illegal file name: %s.\n"
|
|
"File name must begin with \"printer.\"" % path,
|
|
"Illegal file name", wx.OK + wx.ICON_ERROR)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
ext = os.path.splitext(os.path.basename(path))[1]
|
|
self.dir = os.path.dirname(path)
|
|
|
|
if ext == "":
|
|
path += ".h"
|
|
|
|
try:
|
|
fp = file(path, 'w')
|
|
except:
|
|
dlg = wx.MessageDialog(self, "Unable to write to file %s." % path,
|
|
"File error", wx.OK + wx.ICON_ERROR)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
return False
|
|
|
|
self.configFile = path
|
|
|
|
values = {}
|
|
|
|
for pg in self.pages:
|
|
v1 = pg.getValues()
|
|
for k in v1.keys():
|
|
values[k] = v1[k]
|
|
|
|
for ln in self.cfgBuffer:
|
|
m = reDefine.match(ln)
|
|
if m:
|
|
t = m.groups()
|
|
if len(t) == 2 and t[0] in values.keys():
|
|
v = values[t[0]]
|
|
self.cfgValues[t[0]] = v
|
|
if v[1] == False:
|
|
fp.write("//")
|
|
fp.write(defineValueFormat % (t[0], v[0]))
|
|
else:
|
|
if t[0] == 'CANNED_CYCLE':
|
|
# Known to be absent in the GUI. Worse, this value is replaced
|
|
# by the one in the metadata file.
|
|
#
|
|
# TODO: make value reading above recognize wether this value is
|
|
# commented out or not. Reading the value its self works
|
|
# already. Hint: it's the rule using reDefQS, reDefQSm, etc.
|
|
#
|
|
# TODO: add a multiline text field in the GUI to deal with this.
|
|
#
|
|
# TODO: write this value out properly. In /* comments */, if
|
|
# disabled.
|
|
#
|
|
# TODO: currently, the lines beyond the ones with the #define are
|
|
# treated like arbitrary comments. Having the former TODOs
|
|
# done, this will lead to duplicates.
|
|
fp.write(ln)
|
|
else:
|
|
print "Value key " + t[0] + " not found in GUI."
|
|
|
|
continue
|
|
|
|
m = reDefBoolBL.match(ln)
|
|
if m:
|
|
t = m.groups()
|
|
if len(t) == 1 and t[0] in values.keys():
|
|
v = values[t[0]]
|
|
self.cfgValues[t[0]] = v
|
|
if v == "" or v == False:
|
|
fp.write("//")
|
|
fp.write(defineBoolFormat % t[0])
|
|
else:
|
|
print "Boolean key " + t[0] + " not found in GUI."
|
|
|
|
continue
|
|
|
|
fp.write(ln)
|
|
|
|
fp.close()
|
|
|
|
return True
|