#!/usr/bin/env python

# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun.  If not, see <http://www.gnu.org/licenses/>.

# Set up Internationalization using gettext
# searching for installed locales on /usr/share; uses relative folder if not found (windows)
import os, gettext, Queue, re

if os.path.exists('/usr/share/pronterface/locale'):
    gettext.install('pronterface', '/usr/share/pronterface/locale', unicode=1)
else:
    gettext.install('pronterface', './locale', unicode=1)

try:
    import wx
except:
    print _("WX is not installed. This program requires WX to run.")
    raise
import printcore, sys, glob, time, threading, traceback, gviz, traceback, cStringIO, subprocess
try:
    os.chdir(os.path.split(__file__)[0])
except:
    pass
StringIO=cStringIO

thread=threading.Thread
winsize=(800,500)
if os.name=="nt":
    winsize=(800,530)
    try:
        import _winreg
    except:
        pass


from xybuttons import XYButtons
from zbuttons import ZButtons
from graph import Graph
import pronsole

import cherrypy, webinterface
from threading import Thread

def dosify(name):
    return os.path.split(name)[1].split(".")[0][:8]+".g"

class Tee(object):
    def __init__(self, target):
        self.stdout = sys.stdout
        sys.stdout = self
        self.target=target
    def __del__(self):
        sys.stdout = self.stdout
    def write(self, data):
        self.target(data)
        self.stdout.write(data.encode("utf-8"))
    def flush(self):
        self.stdout.flush()


class PronterWindow(wx.Frame,pronsole.pronsole):
    def __init__(self, filename=None,size=winsize):
        pronsole.pronsole.__init__(self)
        self.settings.build_dimensions = '200x200x100+0+0+0' #default build dimensions are 200x200x100 with 0,0,0 in the corner of the bed
        self.settings.last_bed_temperature = 0.0
        self.settings.last_file_path = ""
        self.settings.last_temperature = 0.0
        self.settings.preview_extrusion_width = 0.5
        self.settings.preview_grid_step1 = 10.
        self.settings.preview_grid_step2 = 50.
        self.settings.bgcolor = "#FFFFFF"
        self.helpdict["build_dimensions"] = _("Dimensions of Build Platform\n & optional offset of origin\n\nExamples:\n   XXXxYYY\n   XXX,YYY,ZZZ\n   XXXxYYYxZZZ+OffX+OffY+OffZ")
        self.helpdict["last_bed_temperature"] = _("Last Set Temperature for the Heated Print Bed")
        self.helpdict["last_file_path"] = _("Folder of last opened file")
        self.helpdict["last_temperature"] = _("Last Temperature of the Hot End")
        self.helpdict["preview_extrusion_width"] = _("Width of Extrusion in Preview (default: 0.5)")
        self.helpdict["preview_grid_step1"] = _("Fine Grid Spacing (default: 10)")
        self.helpdict["preview_grid_step2"] = _("Coarse Grid Spacing (default: 50)")
        self.helpdict["bgcolor"] = _("Pronterface background color (default: #FFFFFF)")
        self.filename=filename
        os.putenv("UBUNTU_MENUPROXY","0")
        wx.Frame.__init__(self,None,title=_("Printer Interface"),size=size);
        self.SetIcon(wx.Icon("P-face.ico",wx.BITMAP_TYPE_ICO))
        self.panel=wx.Panel(self,-1,size=size)

        self.statuscheck=False
        self.capture_skip={}
        self.capture_skip_newline=False
        self.tempreport=""
        self.monitor=0
        self.f=None
        self.skeinp=None
        self.monitor_interval=3
        self.paused=False
        self.sentlines=Queue.Queue(30)
        xcol=(245,245,108)
        ycol=(180,180,255)
        zcol=(180,255,180)
        self.cpbuttons=[
            [_("Motors off"),("M84"),(1,0),(250,250,250),(1,2)],
            [_("Check temp"),("M105"),(3,5),(225,200,200),(1,3)],
            [_("Extrude"),("extrude"),(5,0),(225,200,200),(1,2)],
            [_("Reverse"),("reverse"),(6,0),(225,200,200),(1,2)],
        ]
        self.custombuttons=[]
        self.btndict={}
        self.parse_cmdline(sys.argv[1:])
        self.build_dimensions_list = self.get_build_dimensions(self.settings.build_dimensions)
        self.panel.SetBackgroundColour(self.settings.bgcolor)
        customdict={}
        try:
            execfile("custombtn.txt",customdict)
            if len(customdict["btns"]):
                if not len(self.custombuttons):
                    try:
                        self.custombuttons = customdict["btns"]
                        for n in xrange(len(self.custombuttons)):
                            self.cbutton_save(n,self.custombuttons[n])
                        os.rename("custombtn.txt","custombtn.old")
                        rco=open("custombtn.txt","w")
                        rco.write(_("# I moved all your custom buttons into .pronsolerc.\n# Please don't add them here any more.\n# Backup of your old buttons is in custombtn.old\n"))
                        rco.close()
                    except IOError,x:
                        print str(x)
                else:
                    print _("Note!!! You have specified custom buttons in both custombtn.txt and .pronsolerc")
                    print _("Ignoring custombtn.txt. Remove all current buttons to revert to custombtn.txt")

        except:
            pass
        self.popmenu()
        self.popwindow()
        self.t=Tee(self.catchprint)
        self.stdout=sys.stdout
        self.skeining=0
        self.mini=False
        self.p.sendcb=self.sentcb
        self.p.startcb=self.startcb
        self.p.endcb=self.endcb
        self.starttime=0
        self.extra_print_time=0
        self.curlayer=0
        self.cur_button=None
        self.hsetpoint=0.0
        self.bsetpoint=0.0
        self.webInterface=webinterface.WebInterface(self)
        self.webThread = Thread(target=webinterface.StartWebInterfaceThread, args=(self.webInterface, ))
        self.webThread.start()

    def startcb(self):
        self.starttime=time.time()
        print "Print Started at: " +time.strftime('%H:%M:%S',time.localtime(self.starttime))

    def endcb(self):
        if(self.p.queueindex==0):
            print "Print ended at: " +time.strftime('%H:%M:%S',time.localtime(time.time()))
            print "and took: "+time.strftime('%H:%M:%S', time.gmtime(int(time.time()-self.starttime+self.extra_print_time)))  #+str(int(time.time()-self.starttime)/60)+" minutes "+str(int(time.time()-self.starttime)%60)+" seconds."
            wx.CallAfter(self.pausebtn.Disable)
            wx.CallAfter(self.printbtn.SetLabel,_("Print"))


    def online(self):
        print _("Printer is now online.")
        self.connectbtn.SetLabel(_("Disconnect"))
        self.connectbtn.Bind(wx.EVT_BUTTON,self.disconnect)

        for i in self.printerControls:
            wx.CallAfter(i.Enable)

        # Enable XYButtons and ZButtons
        wx.CallAfter(self.xyb.enable)
        wx.CallAfter(self.zb.enable)

        if self.filename:
            wx.CallAfter(self.printbtn.Enable)


    def sentcb(self,line):
        if("G1" in line):
            if("Z" in line):
                try:
                    layer=float(line.split("Z")[1].split()[0])
                    if(layer!=self.curlayer):
                        self.curlayer=layer
                        self.gviz.hilight=[]
                        threading.Thread(target=wx.CallAfter,args=(self.gviz.setlayer,layer)).start()
                except:
                    pass
            try:
                self.sentlines.put_nowait(line)
            except:
                pass
            #threading.Thread(target=self.gviz.addgcode,args=(line,1)).start()
            #self.gwindow.p.addgcode(line,hilight=1)
        if("M104" in line or "M109" in line):
            if("S" in line):
                try:
                    temp=float(line.split("S")[1].split("*")[0])
                    self.hottgauge.SetTarget(temp)
                    self.graph.SetExtruder0TargetTemperature(temp)
                except:
                    pass
            try:
                self.sentlines.put_nowait(line)
            except:
                pass
        if("M140" in line):
            if("S" in line):
                try:
                    temp=float(line.split("S")[1].split("*")[0])
                    self.bedtgauge.SetTarget(temp)
                    self.graph.SetBedTargetTemperature(temp)
                except:
                    pass
            try:
                self.sentlines.put_nowait(line)
            except:
                pass

    def do_extrude(self,l=""):
        try:
            if not (l.__class__=="".__class__ or l.__class__==u"".__class__) or (not len(l)):
                l=str(self.edist.GetValue())
            pronsole.pronsole.do_extrude(self,l)
        except:
            raise

    def do_reverse(self,l=""):
        try:
            if not (l.__class__=="".__class__ or l.__class__==u"".__class__) or (not len(l)):
                l=str(float(self.edist.GetValue())*-1.0)
            pronsole.pronsole.do_extrude(self,l)
        except:
            pass

    def do_settemp(self,l=""):
        try:
            if not (l.__class__=="".__class__ or l.__class__==u"".__class__) or (not len(l)):
                l=str(self.htemp.GetValue().split()[0])
            l=l.lower().replace(",",".")
            for i in self.temps.keys():
                l=l.replace(i,self.temps[i])
            f=float(l)
            if f>=0:
                if self.p.online:
                    self.p.send_now("M104 S"+l)
                    print _("Setting hotend temperature to %f degrees Celsius.") % f
                    self.hsetpoint=f
                    self.hottgauge.SetTarget(int(f))
                    self.graph.SetExtruder0TargetTemperature(int(f))
                    if f>0:
                        wx.CallAfter(self.htemp.SetValue,l)
                        self.set("last_temperature",str(f))
                        wx.CallAfter(self.settoff.SetBackgroundColour,"")
                        wx.CallAfter(self.settoff.SetForegroundColour,"")
                        wx.CallAfter(self.settbtn.SetBackgroundColour,"#FFAA66")
                        wx.CallAfter(self.settbtn.SetForegroundColour,"#660000")
                        wx.CallAfter(self.htemp.SetBackgroundColour,"#FFDABB")
                    else:
                        wx.CallAfter(self.settoff.SetBackgroundColour,"#0044CC")
                        wx.CallAfter(self.settoff.SetForegroundColour,"white")
                        wx.CallAfter(self.settbtn.SetBackgroundColour,"")
                        wx.CallAfter(self.settbtn.SetForegroundColour,"")
                        wx.CallAfter(self.htemp.SetBackgroundColour,"white")
                        wx.CallAfter(self.htemp.Refresh)
                else:
                    print _("Printer is not online.")
            else:
                print _("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")
        except Exception,x:
            print _("You must enter a temperature. (%s)" % (repr(x),)); self.webInterface.AddLog("You must enter a temperature. (%s)" % (repr(x),))

    def do_bedtemp(self,l=""):
        try:
            if not (l.__class__=="".__class__ or l.__class__==u"".__class__) or (not len(l)):
                l=str(self.btemp.GetValue().split()[0])
            l=l.lower().replace(",",".")
            for i in self.bedtemps.keys():
                l=l.replace(i,self.bedtemps[i])
            f=float(l)
            if f>=0:
                if self.p.online:
                    self.p.send_now("M140 S"+l)
                    print _("Setting bed temperature to %f degrees Celsius.") % f
                    self.bsetpoint=f
                    self.bedtgauge.SetTarget(int(f))
                    self.graph.SetBedTargetTemperature(int(f))
                    if f>0:
                        wx.CallAfter(self.btemp.SetValue,l)
                        self.set("last_bed_temperature",str(f))
                        wx.CallAfter(self.setboff.SetBackgroundColour,"")
                        wx.CallAfter(self.setboff.SetForegroundColour,"")
                        wx.CallAfter(self.setbbtn.SetBackgroundColour,"#FFAA66")
                        wx.CallAfter(self.setbbtn.SetForegroundColour,"#660000")
                        wx.CallAfter(self.btemp.SetBackgroundColour,"#FFDABB")
                    else:
                        wx.CallAfter(self.setboff.SetBackgroundColour,"#0044CC")
                        wx.CallAfter(self.setboff.SetForegroundColour,"white")
                        wx.CallAfter(self.setbbtn.SetBackgroundColour,"")
                        wx.CallAfter(self.setbbtn.SetForegroundColour,"")
                        wx.CallAfter(self.btemp.SetBackgroundColour,"white")
                        wx.CallAfter(self.btemp.Refresh)
                else:
                    print _("Printer is not online."); self.webInterface.AddLog("Printer is not online.")
            else:
                print _("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0."); self.webInterface.AddLog("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
        except:
            print _("You must enter a temperature."); self.webInterface.AddLog("You must enter a temperature.")

    def end_macro(self):
        pronsole.pronsole.end_macro(self)
        self.update_macros_menu()

    def delete_macro(self,macro_name):
        pronsole.pronsole.delete_macro(self,macro_name)
        self.update_macros_menu()

    def start_macro(self,macro_name,old_macro_definition=""):
        if not self.processing_rc:
            def cb(definition):
                if len(definition.strip())==0:
                    if old_macro_definition!="":
                        dialog = wx.MessageDialog(self,_("Do you want to erase the macro?"),style=wx.YES_NO|wx.YES_DEFAULT|wx.ICON_QUESTION)
                        if dialog.ShowModal()==wx.ID_YES:
                            self.delete_macro(macro_name)
                            return
                    print _("Cancelled."); self.webInterface.AddLog("Cancelled.")
                    return
                self.cur_macro_name = macro_name
                self.cur_macro_def = definition
                self.end_macro()
            macroed(macro_name,old_macro_definition,cb)
        else:
            pronsole.pronsole.start_macro(self,macro_name,old_macro_definition)

    def catchprint(self,l):
        if self.capture_skip_newline and len(l) and not len(l.strip("\n\r")):
            self.capture_skip_newline = False
            return
        for pat in self.capture_skip.keys():
            if self.capture_skip[pat] > 0 and pat.match(l):
                self.capture_skip[pat] -= 1
                self.capture_skip_newline = True
                return
        wx.CallAfter(self.logbox.AppendText,l)
        self.webInterface.AppendLog(l)

    def scanserial(self):
        """scan for available ports. return a list of device names."""
        baselist=[]
        if os.name=="nt":
            try:
                key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
                i=0
                while(1):
                    baselist+=[_winreg.EnumValue(key,i)[1]]
                    i+=1
            except:
                pass
        return baselist+glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') +glob.glob("/dev/tty.*")+glob.glob("/dev/cu.*")+glob.glob("/dev/rfcomm*")

    def project(self,event):
        import projectlayer
        if(self.p.online):
            projectlayer.setframe(self,self.p).Show()
        else:
            print _("Printer is not online."); self.webInterface.AddLog("Printer is not online.")

    def popmenu(self):
        self.menustrip = wx.MenuBar()
        # File menu
        m = wx.Menu()
        self.Bind(wx.EVT_MENU, self.loadfile, m.Append(-1,_("&Open..."),_(" Opens file")))
        self.Bind(wx.EVT_MENU, self.do_editgcode, m.Append(-1,_("&Edit..."),_(" Edit open file")))
        self.Bind(wx.EVT_MENU, self.clearOutput, m.Append(-1,_("Clear console"),_(" Clear output console")))
        self.Bind(wx.EVT_MENU, self.project, m.Append(-1,_("Projector"),_(" Project slices")))
        self.Bind(wx.EVT_MENU, self.OnExit, m.Append(wx.ID_EXIT,_("E&xit"),_(" Closes the Window")))
        self.menustrip.Append(m,_("&File"))

        # Settings menu
        m = wx.Menu()
        self.macros_menu = wx.Menu()
        m.AppendSubMenu(self.macros_menu, _("&Macros"))
        self.Bind(wx.EVT_MENU, self.new_macro, self.macros_menu.Append(-1, _("<&New...>")))
        self.Bind(wx.EVT_MENU, lambda *e:options(self), m.Append(-1,_("&Options"),_(" Options dialog")))

        self.Bind(wx.EVT_MENU, lambda x:threading.Thread(target=lambda :self.do_skein("set")).start(), m.Append(-1,_("Slicing Settings"),_(" Adjust slicing settings")))
        #try:
        #    from SkeinforgeQuickEditDialog import SkeinforgeQuickEditDialog
        #    self.Bind(wx.EVT_MENU, lambda *e:SkeinforgeQuickEditDialog(self), m.Append(-1,_("SFACT Quick Settings"),_(" Quickly adjust SFACT settings for active profile")))
        #except:
        #    pass

        self.menustrip.Append(m,_("&Settings"))
        self.update_macros_menu()
        self.SetMenuBar(self.menustrip)


    def doneediting(self,gcode):
        f=open(self.filename,"w")
        f.write("\n".join(gcode))
        f.close()
        wx.CallAfter(self.loadfile,None,self.filename)

    def do_editgcode(self,e=None):
        if(self.filename is not None):
            macroed(self.filename,self.f,self.doneediting,1)

    def new_macro(self,e=None):
        dialog = wx.Dialog(self,-1,_("Enter macro name"),size=(260,85))
        panel = wx.Panel(dialog,-1)
        vbox = wx.BoxSizer(wx.VERTICAL)
        wx.StaticText(panel,-1,_("Macro name:"),(8,14))
        dialog.namectrl = wx.TextCtrl(panel,-1,'',(110,8),size=(130,24),style=wx.TE_PROCESS_ENTER)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        okb = wx.Button(dialog,wx.ID_OK,_("Ok"),size=(60,24))
        dialog.Bind(wx.EVT_TEXT_ENTER,lambda e:dialog.EndModal(wx.ID_OK),dialog.namectrl)
        #dialog.Bind(wx.EVT_BUTTON,lambda e:self.new_macro_named(dialog,e),okb)
        hbox.Add(okb)
        hbox.Add(wx.Button(dialog,wx.ID_CANCEL,_("Cancel"),size=(60,24)))
        vbox.Add(panel)
        vbox.Add(hbox,1,wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM,10)
        dialog.SetSizer(vbox)
        dialog.Centre()
        macro = ""
        if dialog.ShowModal()==wx.ID_OK:
            macro = dialog.namectrl.GetValue()
            if macro != "":
                wx.CallAfter(self.edit_macro,macro)
        dialog.Destroy()
        return macro

    def edit_macro(self,macro):
        if macro == "": return self.new_macro()
        if self.macros.has_key(macro):
            old_def = self.macros[macro]
        elif hasattr(self.__class__,"do_"+macro):
            print _("Name '%s' is being used by built-in command") % macro
            return
        elif len([c for c in macro if not c.isalnum() and c != "_"]):
            print _("Macro name may contain only alphanumeric symbols and underscores"); self.webInterface.AddLog("Macro name may contain only alphanumeric symbols and underscores")
            return
        else:
            old_def = ""
        self.start_macro(macro,old_def)
        return macro

    def update_macros_menu(self):
        if not hasattr(self,"macros_menu"):
            return # too early, menu not yet built
        try:
            while True:
                item = self.macros_menu.FindItemByPosition(1)
                if item is None: return
                self.macros_menu.DeleteItem(item)
        except:
            pass
        for macro in self.macros.keys():
            self.Bind(wx.EVT_MENU, lambda x,m=macro:self.start_macro(m,self.macros[m]), self.macros_menu.Append(-1, macro))

    def OnExit(self, event):
        self.Close()

    def rescanports(self,event=None):
        scan=self.scanserial()
        portslist=list(scan)
        if self.settings.port != "" and self.settings.port not in portslist:
            portslist += [self.settings.port]
            self.serialport.Clear()
            self.serialport.AppendItems(portslist)
        try:
            if os.path.exists(self.settings.port) or self.settings.port in scan:
                self.serialport.SetValue(self.settings.port)
            elif len(portslist)>0:
                self.serialport.SetValue(portslist[0])
        except:
            pass


    def popwindow(self):
        # this list will contain all controls that should be only enabled
        # when we're connected to a printer
        self.printerControls = []

        #sizer layout: topsizer is a column sizer containing two sections
        #upper section contains the mini view buttons
        #lower section contains the rest of the window - manual controls, console, visualizations
        #TOP ROW:
        uts=self.uppertopsizer=wx.BoxSizer(wx.HORIZONTAL)
        self.rescanbtn=wx.Button(self.panel,-1,_("Port"))
        self.rescanbtn.Bind(wx.EVT_BUTTON,self.rescanports)

        uts.Add(self.rescanbtn,0,wx.TOP|wx.LEFT,0)
        self.serialport = wx.ComboBox(self.panel, -1,
                choices=self.scanserial(),
                style=wx.CB_DROPDOWN)
        self.rescanports()
        uts.Add(self.serialport)
        uts.Add(wx.StaticText(self.panel,-1,"@"),0,wx.RIGHT|wx.ALIGN_CENTER,0)
        self.baud = wx.ComboBox(self.panel, -1,
                choices=["2400", "9600", "19200", "38400", "57600", "115200", "250000"],
                style=wx.CB_DROPDOWN)
        try:
            self.baud.SetValue("115200")
            self.baud.SetValue(str(self.settings.baudrate))
        except:
            pass
        uts.Add(self.baud)
        self.connectbtn=wx.Button(self.panel,-1,_("Connect"))
        uts.Add(self.connectbtn)
        self.connectbtn.SetToolTipString(_("Connect to the printer"))
        self.connectbtn.Bind(wx.EVT_BUTTON,self.connect)
        self.resetbtn=wx.Button(self.panel,-1,_("Reset"))
        self.resetbtn.Bind(wx.EVT_BUTTON,self.reset)
        uts.Add(self.resetbtn)
        self.minibtn=wx.Button(self.panel,-1,_("Mini mode"))
        self.minibtn.Bind(wx.EVT_BUTTON,self.toggleview)

        uts.Add((25,-1))
        self.monitorbox=wx.CheckBox(self.panel,-1,_("Monitor Printer"))
        uts.Add(self.monitorbox,0,wx.ALIGN_CENTER)
        self.monitorbox.Bind(wx.EVT_CHECKBOX,self.setmonitor)

        uts.Add((15,-1),flag=wx.EXPAND)
        uts.Add(self.minibtn,0,wx.ALIGN_CENTER)

        #SECOND ROW
        ubs=self.upperbottomsizer=wx.BoxSizer(wx.HORIZONTAL)

        self.loadbtn=wx.Button(self.panel,-1,_("Load file"))
        self.loadbtn.Bind(wx.EVT_BUTTON,self.loadfile)
        ubs.Add(self.loadbtn)
        self.platebtn=wx.Button(self.panel,-1,_("Compose"))
        self.platebtn.Bind(wx.EVT_BUTTON,self.plate)
        #self.printerControls.append(self.uploadbtn)
        ubs.Add(self.platebtn)
        self.sdbtn=wx.Button(self.panel,-1,_("SD"))
        self.sdbtn.Bind(wx.EVT_BUTTON,self.sdmenu)
        self.printerControls.append(self.sdbtn)
        ubs.Add(self.sdbtn)
        self.printbtn=wx.Button(self.panel,-1,_("Print"))
        self.printbtn.Bind(wx.EVT_BUTTON,self.printfile)
        self.printbtn.Disable()
        ubs.Add(self.printbtn)
        self.pausebtn=wx.Button(self.panel,-1,_("Pause"))
        self.pausebtn.Bind(wx.EVT_BUTTON,self.pause)
        ubs.Add(self.pausebtn)
        #Right full view
        lrs=self.lowerrsizer=wx.BoxSizer(wx.VERTICAL)
        self.logbox=wx.TextCtrl(self.panel,style = wx.TE_MULTILINE,size=(350,-1))
        self.logbox.SetEditable(0)
        lrs.Add(self.logbox,1,wx.EXPAND)
        lbrs=wx.BoxSizer(wx.HORIZONTAL)
        self.commandbox=wx.TextCtrl(self.panel,style = wx.TE_PROCESS_ENTER)
        self.commandbox.Bind(wx.EVT_TEXT_ENTER,self.sendline)
        #self.printerControls.append(self.commandbox)
        lbrs.Add(self.commandbox,1)
        self.sendbtn=wx.Button(self.panel,-1,_("Send"))
        self.sendbtn.Bind(wx.EVT_BUTTON,self.sendline)
        #self.printerControls.append(self.sendbtn)
        lbrs.Add(self.sendbtn)
        lrs.Add(lbrs,0,wx.EXPAND)

        #left pane
        lls=self.lowerlsizer=wx.GridBagSizer()
        lls.Add(wx.StaticText(self.panel,-1,_("mm/min")),pos=(0,4),span=(1,4))
        self.xyfeedc=wx.SpinCtrl(self.panel,-1,str(self.settings.xy_feedrate),min=0,max=50000,size=(70,-1))
        lls.Add(wx.StaticText(self.panel,-1,_("XY:")),pos=(1,3),span=(1,1), flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        lls.Add(self.xyfeedc,pos=(1,4),span=(1,2))
        lls.Add(wx.StaticText(self.panel,-1,_("Z:")),pos=(1,6),span=(1,1), flag=wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        self.zfeedc=wx.SpinCtrl(self.panel,-1,str(self.settings.z_feedrate),min=0,max=50000,size=(70,-1))
        lls.Add(self.zfeedc,pos=(1,7),span=(1,3))

        #lls.Add((200,375))

        self.xyb = XYButtons(self.panel, self.moveXY, self.homeButtonClicked)
        lls.Add(self.xyb, pos=(2,0), span=(1,6), flag=wx.ALIGN_CENTER)
        self.zb = ZButtons(self.panel, self.moveZ)
        lls.Add(self.zb, pos=(2,7), span=(1,2), flag=wx.ALIGN_CENTER)
        wx.CallAfter(self.xyb.SetFocus)
        
        for i in self.cpbuttons:
            btn=wx.Button(self.panel,-1,i[0])#)
            btn.SetBackgroundColour(i[3])
            btn.SetForegroundColour("black")
            btn.properties=i
            btn.Bind(wx.EVT_BUTTON,self.procbutton)
            self.btndict[i[1]]=btn
            self.printerControls.append(btn)
            lls.Add(btn,pos=i[2],span=i[4])


        lls.Add(wx.StaticText(self.panel,-1,_("Heater:")),pos=(3,0),span=(1,1),flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        htemp_choices=[self.temps[i]+" ("+i+")" for i in sorted(self.temps.keys(),key=lambda x:self.temps[x])]

        self.settoff=wx.Button(self.panel,-1,_("Off"),size=(36,-1))
        self.settoff.Bind(wx.EVT_BUTTON,lambda e:self.do_settemp("off"))
        self.printerControls.append(self.settoff)
        lls.Add(self.settoff,pos=(3,1),span=(1,1))

        if self.settings.last_temperature not in map(float,self.temps.values()):
            htemp_choices = [str(self.settings.last_temperature)] + htemp_choices
        self.htemp=wx.ComboBox(self.panel, -1,
                choices=htemp_choices,style=wx.CB_DROPDOWN, size=(80,-1))
        self.htemp.Bind(wx.EVT_COMBOBOX,self.htemp_change)

        lls.Add(self.htemp,pos=(3,2),span=(1,2))
        self.settbtn=wx.Button(self.panel,-1,_("Set"),size=(38,-1))
        self.settbtn.Bind(wx.EVT_BUTTON,self.do_settemp)
        self.printerControls.append(self.settbtn)
        lls.Add(self.settbtn,pos=(3,4),span=(1,1))

        lls.Add(wx.StaticText(self.panel,-1,_("Bed:")),pos=(4,0),span=(1,1),flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
        btemp_choices=[self.bedtemps[i]+" ("+i+")" for i in sorted(self.bedtemps.keys(),key=lambda x:self.temps[x])]

        self.setboff=wx.Button(self.panel,-1,_("Off"),size=(36,-1))
        self.setboff.Bind(wx.EVT_BUTTON,lambda e:self.do_bedtemp("off"))
        self.printerControls.append(self.setboff)
        lls.Add(self.setboff,pos=(4,1),span=(1,1))

        if self.settings.last_bed_temperature not in map(float,self.bedtemps.values()):
            btemp_choices = [str(self.settings.last_bed_temperature)] + btemp_choices
        self.btemp=wx.ComboBox(self.panel, -1,
                choices=btemp_choices,style=wx.CB_DROPDOWN, size=(80,-1))
        self.btemp.Bind(wx.EVT_COMBOBOX,self.btemp_change)
        lls.Add(self.btemp,pos=(4,2),span=(1,2))

        self.setbbtn=wx.Button(self.panel,-1,_("Set"),size=(38,-1))
        self.setbbtn.Bind(wx.EVT_BUTTON,self.do_bedtemp)
        self.printerControls.append(self.setbbtn)
        lls.Add(self.setbbtn,pos=(4,4),span=(1,1))

        self.btemp.SetValue(str(self.settings.last_bed_temperature))
        self.htemp.SetValue(str(self.settings.last_temperature))

        ## added for an error where only the bed would get (pla) or (abs).
        #This ensures, if last temp is a default pla or abs, it will be marked so.
        # if it is not, then a (user) remark is added. This denotes a manual entry

        for i in btemp_choices:
            if i.split()[0] == str(self.settings.last_bed_temperature).split('.')[0] or i.split()[0] == str(self.settings.last_bed_temperature):
                self.btemp.SetValue(i)
        for i in htemp_choices:
            if i.split()[0] == str(self.settings.last_temperature).split('.')[0] or i.split()[0] == str(self.settings.last_temperature) :
               self.htemp.SetValue(i)

        if( '(' not in self.btemp.Value):
            self.btemp.SetValue(self.btemp.Value + ' (user)')
        if( '(' not in self.htemp.Value):
            self.htemp.SetValue(self.htemp.Value + ' (user)')

        #lls.Add(self.btemp,pos=(4,1),span=(1,3))
        #lls.Add(self.setbbtn,pos=(4,4),span=(1,2))
        self.tempdisp=wx.StaticText(self.panel,-1,"")
        lls.Add(self.tempdisp,pos=(4,5),span=(1,3))

        self.edist=wx.SpinCtrl(self.panel,-1,"5",min=0,max=1000,size=(60,-1))
        self.edist.SetBackgroundColour((225,200,200))
        self.edist.SetForegroundColour("black")
        lls.Add(self.edist,pos=(5,2),span=(1,1))
        lls.Add(wx.StaticText(self.panel,-1,_("mm")),pos=(5,3),span=(1,1))
        self.efeedc=wx.SpinCtrl(self.panel,-1,str(self.settings.e_feedrate),min=0,max=50000,size=(60,-1))
        self.efeedc.SetBackgroundColour((225,200,200))
        self.efeedc.SetForegroundColour("black")
        self.efeedc.Bind(wx.EVT_SPINCTRL,self.setfeeds)
        lls.Add(self.efeedc,pos=(6,2),span=(1,1))
        lls.Add(wx.StaticText(self.panel,-1,_("mm/min")),pos=(6,3),span=(1,1))
        self.xyfeedc.Bind(wx.EVT_SPINCTRL,self.setfeeds)
        self.zfeedc.Bind(wx.EVT_SPINCTRL,self.setfeeds)
        self.zfeedc.SetBackgroundColour((180,255,180))
        self.zfeedc.SetForegroundColour("black")
        # lls.Add((10,0),pos=(0,11),span=(1,1))

        self.hottgauge=TempGauge(self.panel,size=(200,24),title=_("Heater:"),maxval=230)
        lls.Add(self.hottgauge,pos=(7,0),span=(1,4))
        self.bedtgauge=TempGauge(self.panel,size=(200,24),title=_("Bed:"),maxval=130)
        lls.Add(self.bedtgauge,pos=(8,0),span=(1,4))
        #def scroll_setpoint(e):
        #   if e.GetWheelRotation()>0:
        #       self.do_settemp(str(self.hsetpoint+1))
        #   elif e.GetWheelRotation()<0:
        #       self.do_settemp(str(max(0,self.hsetpoint-1)))
        #self.tgauge.Bind(wx.EVT_MOUSEWHEEL,scroll_setpoint)

        self.graph = Graph(self.panel, wx.ID_ANY)
        lls.Add(self.graph, pos=(5, 4), span=(4,4), flag=wx.ALIGN_LEFT)

        self.gviz=gviz.gviz(self.panel,(300,300),
            build_dimensions=self.build_dimensions_list,
            grid=(self.settings.preview_grid_step1,self.settings.preview_grid_step2),
            extrusion_width=self.settings.preview_extrusion_width)
        self.gviz.showall=1
        try:
            raise ""
            import stlview
            self.gwindow=stlview.GCFrame(None, wx.ID_ANY, 'Gcode view, shift to move view, mousewheel to set layer', size=(600,600))
        except:
            self.gwindow=gviz.window([],
            build_dimensions=self.build_dimensions_list,
            grid=(self.settings.preview_grid_step1,self.settings.preview_grid_step2),
            extrusion_width=self.settings.preview_extrusion_width)
        self.gviz.Bind(wx.EVT_LEFT_DOWN,self.showwin)
        self.gwindow.Bind(wx.EVT_CLOSE,lambda x:self.gwindow.Hide())
        vcs=wx.BoxSizer(wx.VERTICAL)
        vcs.Add(self.gviz,1,flag=wx.SHAPED)
        cs=self.centersizer=wx.GridBagSizer()
        vcs.Add(cs,0,flag=wx.EXPAND)

        self.uppersizer=wx.BoxSizer(wx.VERTICAL)
        self.uppersizer.Add(self.uppertopsizer)
        self.uppersizer.Add(self.upperbottomsizer)

        self.lowersizer=wx.BoxSizer(wx.HORIZONTAL)
        self.lowersizer.Add(lls)
        self.lowersizer.Add(vcs,1,wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL)
        self.lowersizer.Add(lrs,0,wx.EXPAND)
        self.topsizer=wx.BoxSizer(wx.VERTICAL)
        self.topsizer.Add(self.uppersizer)
        self.topsizer.Add(self.lowersizer,1,wx.EXPAND)
        self.panel.SetSizer(self.topsizer)
        self.status=self.CreateStatusBar()
        self.status.SetStatusText(_("Not connected to printer."))
        self.panel.Bind(wx.EVT_MOUSE_EVENTS,self.editbutton)
        self.Bind(wx.EVT_CLOSE, self.kill)

        self.topsizer.Layout()
        self.topsizer.Fit(self)

        # disable all printer controls until we connect to a printer
        self.pausebtn.Disable()
        for i in self.printerControls:
            i.Disable()

        #self.panel.Fit()
        #uts.Layout()
        self.cbuttons_reload()

    def plate(self,e):
        import plater
        print "plate function activated"
        plater.stlwin(size=(800,580),callback=self.platecb,parent=self).Show()

    def platecb(self,name):
        print "plated: "+name
        self.loadfile(None,name)

    def sdmenu(self,e):
        obj = e.GetEventObject()
        popupmenu=wx.Menu()
        item = popupmenu.Append(-1,_("SD Upload"))
        if not self.f or not len(self.f):
            item.Enable(False)
        self.Bind(wx.EVT_MENU,self.upload,id=item.GetId())
        item = popupmenu.Append(-1,_("SD Print"))
        self.Bind(wx.EVT_MENU,self.sdprintfile,id=item.GetId())
        self.panel.PopupMenu(popupmenu, obj.GetPosition())

    def htemp_change(self,event):
        if self.hsetpoint > 0:
            self.do_settemp("")
        wx.CallAfter(self.htemp.SetInsertionPoint,0)

    def btemp_change(self,event):
        if self.bsetpoint > 0:
            self.do_bedtemp("")
        wx.CallAfter(self.btemp.SetInsertionPoint,0)

    def showwin(self,event):
        if(self.f is not None):
            self.gwindow.Show(True)
            self.gwindow.Raise()

    def setfeeds(self,e):
        self.feedrates_changed = True
        try:
            self.settings._set("e_feedrate",self.efeedc.GetValue())
        except:
            pass
        try:
            self.settings._set("z_feedrate",self.zfeedc.GetValue())
        except:
            pass
        try:
            self.settings._set("xy_feedrate",self.xyfeedc.GetValue())
        except:
            pass


    def toggleview(self,e):
        if(self.mini):
            self.mini=False
            self.topsizer.Fit(self)

            #self.SetSize(winsize)
            wx.CallAfter(self.minibtn.SetLabel, _("Mini mode"))

        else:
            self.mini=True
            self.uppersizer.Fit(self)

            #self.SetSize(winssize)
            wx.CallAfter(self.minibtn.SetLabel, _("Full mode"))

    def cbuttons_reload(self):
        allcbs = []
        ubs=self.upperbottomsizer
        cs=self.centersizer
        for item in ubs.GetChildren():
            if hasattr(item.GetWindow(),"custombutton"):
                allcbs += [(ubs,item.GetWindow())]
        for item in cs.GetChildren():
            if hasattr(item.GetWindow(),"custombutton"):
                allcbs += [(cs,item.GetWindow())]
        for sizer,button in allcbs:
            #sizer.Remove(button)
            button.Destroy()
        self.custombuttonbuttons=[]
        newbuttonbuttonindex = len(self.custombuttons)
        while newbuttonbuttonindex>0 and self.custombuttons[newbuttonbuttonindex-1] is None:
            newbuttonbuttonindex -= 1
        while len(self.custombuttons) < 13:
            self.custombuttons.append(None)
        for i in xrange(len(self.custombuttons)):
            btndef = self.custombuttons[i]
            try:
                b=wx.Button(self.panel,-1,btndef[0])
                b.SetToolTip(wx.ToolTip(_("Execute command: ")+btndef[1]))
                if len(btndef)>2:
                    b.SetBackgroundColour(btndef[2])
                    rr,gg,bb=b.GetBackgroundColour().Get()
                    if 0.3*rr+0.59*gg+0.11*bb < 60:
                        b.SetForegroundColour("#ffffff")
            except:
                if i == newbuttonbuttonindex:
                    self.newbuttonbutton=b=wx.Button(self.panel,-1,"+",size=(19,18))
                    #b.SetFont(wx.Font(12,wx.FONTFAMILY_SWISS,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_BOLD))
                    b.SetForegroundColour("#4444ff")
                    b.SetToolTip(wx.ToolTip(_("click to add new custom button")))
                    b.Bind(wx.EVT_BUTTON,self.cbutton_edit)
                else:
                    continue
            b.custombutton=i
            b.properties=btndef
            if btndef is not None:
                b.Bind(wx.EVT_BUTTON,self.procbutton)
                b.Bind(wx.EVT_MOUSE_EVENTS,self.editbutton)
            #else:
            #    b.Bind(wx.EVT_BUTTON,lambda e:e.Skip())
            self.custombuttonbuttons.append(b)
            if i<4:
                ubs.Add(b)
            else:
                cs.Add(b,pos=((i-4)/3,(i-4)%3))
        self.topsizer.Layout()

    def help_button(self):
        print _('Defines custom button. Usage: button <num> "title" [/c "colour"] command'); self.webInterface.AddLog('Defines custom button. Usage: button <num> "title" [/c "colour"] command')

    def do_button(self,argstr):
        def nextarg(rest):
            rest=rest.lstrip()
            if rest.startswith('"'):
               return rest[1:].split('"',1)
            else:
               return rest.split(None,1)
        #try:
        num,argstr=nextarg(argstr)
        num=int(num)
        title,argstr=nextarg(argstr)
        colour=None
        try:
            c1,c2=nextarg(argstr)
            if c1=="/c":
                colour,argstr=nextarg(c2)
        except:
            pass
        command=argstr.strip()
        if num<0 or num>=64:
            print _("Custom button number should be between 0 and 63"); self.webInterface.AddLog("Custom button number should be between 0 and 63")
            return
        while num >= len(self.custombuttons):
            self.custombuttons+=[None]
        self.custombuttons[num]=[title,command]
        if colour is not None:
            self.custombuttons[num]+=[colour]
        if not self.processing_rc:
            self.cbuttons_reload()
        #except Exception,x:
        #    print "Bad syntax for button definition, see 'help button'"
        #    print x


    def cbutton_save(self,n,bdef,new_n=None):
        if new_n is None: new_n=n
        if bdef is None or bdef == "":
            self.save_in_rc(("button %d" % n),'')
        elif len(bdef)>2:
            colour=bdef[2]
            if type(colour) not in (str,unicode):
                #print type(colour),map(type,colour)
                if type(colour)==tuple and tuple(map(type,colour))==(int,int,int):
                    colour = map(lambda x:x%256,colour)
                    colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME|wx.C2S_HTML_SYNTAX)
                else:
                    colour = wx.Colour(colour).GetAsString(wx.C2S_NAME|wx.C2S_HTML_SYNTAX)
            self.save_in_rc(("button %d" % n),'button %d "%s" /c "%s" %s' % (new_n,bdef[0],colour,bdef[1]))
        else:
            self.save_in_rc(("button %d" % n),'button %d "%s" %s' % (new_n,bdef[0],bdef[1]))

    def cbutton_edit(self,e,button=None):
        bedit=ButtonEdit(self)
        def okhandler(event):
            if event.GetId()==wx.ID_OK:
                if n==len(self.custombuttons):
                    self.custombuttons+=[None]
                self.custombuttons[n]=[bedit.name.GetValue().strip(),bedit.command.GetValue().strip()]
                if bedit.color.GetValue().strip()!="":
                    self.custombuttons[n]+=[bedit.color.GetValue()]
                self.cbutton_save(n,self.custombuttons[n])
            bedit.Destroy()
            self.cbuttons_reload()

        bedit.Bind(wx.EVT_BUTTON,okhandler)
        if button is not None:
            n = button.custombutton
            bedit.name.SetValue(button.properties[0])
            bedit.command.SetValue(button.properties[1])
            if len(button.properties)>2:
                colour=button.properties[2]
                if type(colour) not in (str,unicode):
                    #print type(colour)
                    if type(colour)==tuple and tuple(map(type,colour))==(int,int,int):
                        colour = map(lambda x:x%256,colour)
                        colour = wx.Colour(*colour).GetAsString(wx.C2S_NAME|wx.C2S_HTML_SYNTAX)
                    else:
                        colour = wx.Colour(colour).GetAsString(wx.C2S_NAME|wx.C2S_HTML_SYNTAX)
                bedit.color.SetValue(colour)
        else:
            n = len(self.custombuttons)
            while n>0 and self.custombuttons[n-1] is None:
                n -= 1
        bedit.Show()
        
        
    def cbutton_remove(self,e,button):
        n = button.custombutton
        self.custombuttons[n]=None
        self.cbutton_save(n,None)
        #while len(self.custombuttons) and self.custombuttons[-1] is None:
        #    del self.custombuttons[-1]
        wx.CallAfter(self.cbuttons_reload)

    def cbutton_order(self,e,button,dir):
        n = button.custombutton
        if dir<0:
            n=n-1
        if n+1 >= len(self.custombuttons):
            self.custombuttons+=[None] # pad
        # swap
        self.custombuttons[n],self.custombuttons[n+1] = self.custombuttons[n+1],self.custombuttons[n]
        self.cbutton_save(n,self.custombuttons[n])
        self.cbutton_save(n+1,self.custombuttons[n+1])
        #if self.custombuttons[-1] is None:
        #    del self.custombuttons[-1]
        self.cbuttons_reload()

    def editbutton(self,e):
        if e.IsCommandEvent() or e.ButtonUp(wx.MOUSE_BTN_RIGHT):
            if e.IsCommandEvent():
                pos = (0,0)
            else:
                pos = e.GetPosition()
            popupmenu = wx.Menu()
            obj = e.GetEventObject()
            if hasattr(obj,"custombutton"):
                item = popupmenu.Append(-1,_("Edit custom button '%s'") % e.GetEventObject().GetLabelText())
                self.Bind(wx.EVT_MENU,lambda e,button=e.GetEventObject():self.cbutton_edit(e,button),item)
                item = popupmenu.Append(-1,_("Move left <<"))
                self.Bind(wx.EVT_MENU,lambda e,button=e.GetEventObject():self.cbutton_order(e,button,-1),item)
                if obj.custombutton == 0: item.Enable(False)
                item = popupmenu.Append(-1,_("Move right >>"))
                self.Bind(wx.EVT_MENU,lambda e,button=e.GetEventObject():self.cbutton_order(e,button,1),item)
                if obj.custombutton == 63: item.Enable(False)
                pos = self.panel.ScreenToClient(e.GetEventObject().ClientToScreen(pos))
                item = popupmenu.Append(-1,_("Remove custom button '%s'") % e.GetEventObject().GetLabelText())
                self.Bind(wx.EVT_MENU,lambda e,button=e.GetEventObject():self.cbutton_remove(e,button),item)
            else:
                item = popupmenu.Append(-1,_("Add custom button"))
                self.Bind(wx.EVT_MENU,self.cbutton_edit,item)
            self.panel.PopupMenu(popupmenu, pos)
        elif e.Dragging() and e.ButtonIsDown(wx.MOUSE_BTN_LEFT):
            obj = e.GetEventObject()
            scrpos = obj.ClientToScreen(e.GetPosition())
            if not hasattr(self,"dragpos"):
                self.dragpos = scrpos
                e.Skip()
                return
            else:
                dx,dy=self.dragpos[0]-scrpos[0],self.dragpos[1]-scrpos[1]
                if dx*dx+dy*dy < 5*5: # threshold to detect dragging for jittery mice
                    e.Skip()
                    return
            if not hasattr(self,"dragging"):
                # init dragging of the custom button
                if hasattr(obj,"custombutton") and obj.properties is not None:
                    self.newbuttonbutton.SetLabel("")
                    self.newbuttonbutton.SetFont(wx.Font(10,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL))
                    self.newbuttonbutton.SetForegroundColour("black")
                    self.newbuttonbutton.SetSize(obj.GetSize())
                    if self.upperbottomsizer.GetItem(self.newbuttonbutton) is not None:
                        self.upperbottomsizer.SetItemMinSize(self.newbuttonbutton,obj.GetSize())
                        self.topsizer.Layout()
                    self.dragging = wx.Button(self.panel,-1,obj.GetLabel())
                    self.dragging.SetBackgroundColour(obj.GetBackgroundColour())
                    self.dragging.SetForegroundColour(obj.GetForegroundColour())
                    self.dragging.sourcebutton = obj
                    self.dragging.Raise()
                    self.dragging.Disable()
                    self.dragging.SetPosition(self.panel.ScreenToClient(scrpos))
                    for b in self.custombuttonbuttons:
                        #if b.IsFrozen(): b.Thaw()
                        if b.properties is None:
                            b.Enable()
                        #    b.SetStyle(wx.ALIGN_CENTRE+wx.ST_NO_AUTORESIZE+wx.SIMPLE_BORDER)
                    self.last_drag_dest = obj
                    self.dragging.label = obj.s_label = obj.GetLabel()
                    self.dragging.bgc = obj.s_bgc = obj.GetBackgroundColour()
                    self.dragging.fgc = obj.s_fgc = obj.GetForegroundColour()
            else:
                # dragging in progress
                self.dragging.SetPosition(self.panel.ScreenToClient(scrpos))
                wx.CallAfter(self.dragging.Refresh)
                btns = self.custombuttonbuttons
                dst = None
                src = self.dragging.sourcebutton
                drg = self.dragging
                for b in self.custombuttonbuttons:
                    if b.GetScreenRect().Contains(scrpos):
                        dst = b
                        break
                #if dst is None and self.panel.GetScreenRect().Contains(scrpos):
                #    # try to check if it is after buttons at the end
                #    tspos = self.panel.ClientToScreen(self.upperbottomsizer.GetPosition())
                #    bspos = self.panel.ClientToScreen(self.centersizer.GetPosition())
                #    tsrect = wx.Rect(*(tspos.Get()+self.upperbottomsizer.GetSize().Get()))
                #    bsrect = wx.Rect(*(bspos.Get()+self.centersizer.GetSize().Get()))
                #    lbrect = btns[-1].GetScreenRect()
                #    p = scrpos.Get()
                #    if len(btns)<4 and tsrect.Contains(scrpos):
                #        if lbrect.GetRight() < p[0]:
                #            print "Right of last button on upper cb sizer"
                #    if bsrect.Contains(scrpos):
                #        if lbrect.GetBottom() < p[1]:
                #            print "Below last button on lower cb sizer"
                #        if lbrect.GetRight() < p[0] and lbrect.GetTop() <= p[1] and lbrect.GetBottom() >= p[1]:
                #            print "Right to last button on lower cb sizer"
                if dst is not self.last_drag_dest:
                    if self.last_drag_dest is not None:
                        self.last_drag_dest.SetBackgroundColour(self.last_drag_dest.s_bgc)
                        self.last_drag_dest.SetForegroundColour(self.last_drag_dest.s_fgc)
                        self.last_drag_dest.SetLabel(self.last_drag_dest.s_label)
                    if dst is not None and dst is not src:
                        dst.s_bgc = dst.GetBackgroundColour()
                        dst.s_fgc = dst.GetForegroundColour()
                        dst.s_label = dst.GetLabel()
                        src.SetBackgroundColour(dst.GetBackgroundColour())
                        src.SetForegroundColour(dst.GetForegroundColour())
                        src.SetLabel(dst.GetLabel())
                        dst.SetBackgroundColour(drg.bgc)
                        dst.SetForegroundColour(drg.fgc)
                        dst.SetLabel(drg.label)
                    else:
                        src.SetBackgroundColour(drg.bgc)
                        src.SetForegroundColour(drg.fgc)
                        src.SetLabel(drg.label)
                    self.last_drag_dest = dst
        elif hasattr(self,"dragging") and not e.ButtonIsDown(wx.MOUSE_BTN_LEFT):
            # dragging finished
            obj = e.GetEventObject()
            scrpos = obj.ClientToScreen(e.GetPosition())
            dst = None
            src = self.dragging.sourcebutton
            drg = self.dragging
            for b in self.custombuttonbuttons:
                if b.GetScreenRect().Contains(scrpos):
                    dst = b
                    break
            if dst is not None:
                src_i = src.custombutton
                dst_i = dst.custombutton
                self.custombuttons[src_i],self.custombuttons[dst_i] = self.custombuttons[dst_i],self.custombuttons[src_i]
                self.cbutton_save(src_i,self.custombuttons[src_i])
                self.cbutton_save(dst_i,self.custombuttons[dst_i])
                while self.custombuttons[-1] is None:
                    del self.custombuttons[-1]
            wx.CallAfter(self.dragging.Destroy)
            del self.dragging
            wx.CallAfter(self.cbuttons_reload)
            del self.last_drag_dest
            del self.dragpos
        else:
            e.Skip()

    def homeButtonClicked(self, corner):
        if corner == 0: # upper-left
            self.onecmd('home X')
        if corner == 1: # upper-right
            self.onecmd('home Y')
        if corner == 2: # lower-right
            self.onecmd('home Z')
        if corner == 3: # lower-left
            self.onecmd('home')

    def moveXY(self, x, y):
        if x != 0:
            self.onecmd('move X %s' % x)
        if y != 0:
            self.onecmd('move Y %s' % y)

    def moveZ(self, z):
        if z != 0:
            self.onecmd('move Z %s' % z)

    def procbutton(self,e):
        try:
            if hasattr(e.GetEventObject(),"custombutton"):
                if wx.GetKeyState(wx.WXK_CONTROL) or wx.GetKeyState(wx.WXK_ALT):
                    return self.editbutton(e)
                self.cur_button=e.GetEventObject().custombutton
            self.onecmd(e.GetEventObject().properties[1])
            self.cur_button=None
        except:
            print _("event object missing"); self.webInterface.AddLog("event object missing")
            self.cur_button=None
            raise

    def kill(self,e):
        self.statuscheck=0
        self.p.recvcb=None
        self.p.disconnect()
        if hasattr(self,"feedrates_changed"):
            self.save_in_rc("set xy_feedrate","set xy_feedrate %d" % self.settings.xy_feedrate)
            self.save_in_rc("set z_feedrate","set z_feedrate %d" % self.settings.z_feedrate)
            self.save_in_rc("set e_feedrate","set e_feedrate %d" % self.settings.e_feedrate)
        try:
            self.gwindow.Destroy()
        except:
            pass
        self.Destroy()
        webinterface.KillWebInterfaceThread()

    def do_monitor(self,l=""):
        if l.strip()=="":
            self.monitorbox.SetValue(not self.monitorbox.GetValue())
        elif l.strip()=="off":
            wx.CallAfter(self.monitorbox.SetValue,False)
        else:
            try:
                self.monitor_interval=float(l)
                wx.CallAfter(self.monitorbox.SetValue,self.monitor_interval>0)
            except:
                print _("Invalid period given."); self.webInterface.AddLog("Invalid period given.")
        self.setmonitor(None)
        if self.monitor:
            print _("Monitoring printer."); self.webInterface.AddLog("Monitoring printer.")
        else:
            print _("Done monitoring."); self.webInterface.AddLog("Done monitoring.")


    def setmonitor(self,e):
        self.monitor=self.monitorbox.GetValue()
        if self.monitor:
            self.graph.StartPlotting(1000)
        else:
            self.graph.StopPlotting()



    def sendline(self,e):
        command=self.commandbox.GetValue()
        if not len(command):
            return
        wx.CallAfter(self.logbox.AppendText,">>>"+command+"\n")
        self.webInterface.AppendLog(">>>"+command+"\n")
        self.onecmd(str(command))
        self.commandbox.SetSelection(0,len(command))

    def clearOutput(self,e):
        self.logbox.Clear()

    def statuschecker(self):
        try:
            while(self.statuscheck):
                string=""
                if(self.p.online):
                    string+=_("Printer is online. ")
                try:
                    string+=_("Loaded ")+os.path.split(self.filename)[1]+" "
                except:
                    pass
                string+=(self.tempreport.replace("\r","").replace("T:",_("Hotend") + ":").replace("B:",_("Bed") + ":").replace("\n","").replace("ok ",""))+" "
                wx.CallAfter(self.tempdisp.SetLabel,self.tempreport.strip().replace("ok ",""))
                try:
                    self.hottgauge.SetValue(float(filter(lambda x:x.startswith("T:"),self.tempreport.split())[0].split(":")[1]))
                    self.graph.SetExtruder0Temperature(float(filter(lambda x:x.startswith("T:"),self.tempreport.split())[0].split(":")[1]))
                    self.bedtgauge.SetValue(float(filter(lambda x:x.startswith("B:"),self.tempreport.split())[0].split(":")[1]))
                    self.graph.SetBedTemperature(float(filter(lambda x:x.startswith("B:"),self.tempreport.split())[0].split(":")[1]))
                except:
                    pass
                fractioncomplete = 0.0
                if self.sdprinting:
                    fractioncomplete = float(self.percentdone/100.0)
                    string+= _(" SD printing:%04.2f %%") % (self.percentdone,)
                if self.p.printing:
                    fractioncomplete = float(self.p.queueindex)/len(self.p.mainqueue)
                    string+= _(" Printing:%04.2f %% |") % (100*float(self.p.queueindex)/len(self.p.mainqueue),)
                    string+= _(" Line# %d of %d lines |" ) % (self.p.queueindex, len(self.p.mainqueue))
                if fractioncomplete > 0.0:
                    secondselapsed = int(time.time()-self.starttime+self.extra_print_time)
                    secondsestimate = secondselapsed/fractioncomplete
                    secondsremain = secondsestimate - secondselapsed
                    string+= _(" Est: %s of %s remaining | ") % (time.strftime('%H:%M:%S', time.gmtime(secondsremain)),
                                                                 time.strftime('%H:%M:%S', time.gmtime(secondsestimate)))
                    string+= _(" Z: %0.2f mm") % self.curlayer
                wx.CallAfter(self.status.SetStatusText,string)
                wx.CallAfter(self.gviz.Refresh)
                if(self.monitor and self.p.online):
                    if self.sdprinting:
                        self.p.send_now("M27")
                    if not hasattr(self,"auto_monitor_pattern"):
                        self.auto_monitor_pattern = re.compile(r"(ok\s+)?T:[\d\.]+(\s+B:[\d\.]+)?(\s+@:[\d\.]+)?\s*")
                    self.capture_skip[self.auto_monitor_pattern]=self.capture_skip.setdefault(self.auto_monitor_pattern,0)+1
                    self.p.send_now("M105")
                time.sleep(self.monitor_interval)
                while not self.sentlines.empty():
                    try:
                        gc=self.sentlines.get_nowait()
                        wx.CallAfter(self.gviz.addgcode,gc,1)
                    except:
                        break
            wx.CallAfter(self.status.SetStatusText,_("Not connected to printer."))
        except:
            pass #if window has been closed
    def capture(self, func, *args, **kwargs):
        stdout=sys.stdout
        cout=None
        try:
            cout=self.cout
        except:
            pass
        if cout is None:
            cout=cStringIO.StringIO()

        sys.stdout=cout
        retval=None
        try:
            retval=func(*args,**kwargs)
        except:
            traceback.print_exc()
        sys.stdout=stdout
        return retval

    def recvcb(self,l):
        if "T:" in l:
            self.tempreport=l
            wx.CallAfter(self.tempdisp.SetLabel,self.tempreport.strip().replace("ok ",""))
            try:
                self.hottgauge.SetValue(float(filter(lambda x:x.startswith("T:"),self.tempreport.split())[0].split(":")[1]))
                self.graph.SetExtruder0Temperature(float(filter(lambda x:x.startswith("T:"),self.tempreport.split())[0].split(":")[1]))
                self.graph.SetBedTemperature(float(filter(lambda x:x.startswith("B:"),self.tempreport.split())[0].split(":")[1]))
            except:
                pass
        tstring=l.rstrip()
        #print tstring
        if(tstring!="ok"):
            print tstring
            #wx.CallAfter(self.logbox.AppendText,tstring+"\n")
        for i in self.recvlisteners:
            i(l)

    def listfiles(self,line):
        if "Begin file list" in line:
            self.listing=1
        elif "End file list" in line:
            self.listing=0
            self.recvlisteners.remove(self.listfiles)
            wx.CallAfter(self.filesloaded)
        elif self.listing:
            self.sdfiles+=[line.replace("\n","").replace("\r","").lower()]

    def waitforsdresponse(self,l):
        if "file.open failed" in l:
            wx.CallAfter(self.status.SetStatusText,_("Opening file failed."))
            self.recvlisteners.remove(self.waitforsdresponse)
            return
        if "File opened" in l:
            wx.CallAfter(self.status.SetStatusText,l)
        if "File selected" in l:
            wx.CallAfter(self.status.SetStatusText,_("Starting print"))
            self.sdprinting=1
            self.p.send_now("M24")
            self.startcb()
            return
        if "Done printing file" in l:
            wx.CallAfter(self.status.SetStatusText,l)
            self.sdprinting=0
            self.recvlisteners.remove(self.waitforsdresponse)
            self.endcb()
            return
        if "SD printing byte" in l:
            #M27 handler
            try:
                resp=l.split()
                vals=resp[-1].split("/")
                self.percentdone=100.0*int(vals[0])/int(vals[1])
            except:
                pass



    def filesloaded(self):
        dlg=wx.SingleChoiceDialog(self, _("Select the file to print"), _("Pick SD file"), self.sdfiles)
        if(dlg.ShowModal()==wx.ID_OK):
            target=dlg.GetStringSelection()
            if len(target):
                self.recvlisteners+=[self.waitforsdresponse]
                self.p.send_now("M23 "+target.lower())

        #print self.sdfiles
        pass

    def getfiles(self):
        if not self.p.online:
            self.sdfiles=[]
            return
        self.listing=0
        self.sdfiles=[]
        self.recvlisteners+=[self.listfiles]
        self.p.send_now("M21")
        self.p.send_now("M20")

    def skein_func(self):
        try:
            import shlex
            param = self.expandcommand(self.settings.slicecommand).encode()
            print "Slicing: ",param; self.webInterface.AddLog("Slicing: "+param)
            pararray=[i.replace("$s",self.filename).replace("$o",self.filename.replace(".stl","_export.gcode").replace(".STL","_export.gcode")).encode() for i in shlex.split(param.replace("\\","\\\\").encode())]
                #print pararray
            self.skeinp=subprocess.Popen(pararray,stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
            while True:
                o = self.skeinp.stdout.read(1)
                if o == '' and self.skeinp.poll() != None: break
                sys.stdout.write(o)
            self.skeinp.wait()
            self.stopsf=1
        except:
            print _("Failed to execute slicing software: "); self.webInterface.AddLog("Failed to execute slicing software: ")
            self.stopsf=1
            traceback.print_exc(file=sys.stdout)

    def skein_monitor(self):
        while(not self.stopsf):
            try:
                wx.CallAfter(self.status.SetStatusText,_("Slicing..."))#+self.cout.getvalue().split("\n")[-1])
            except:
                pass
            time.sleep(0.1)
        fn=self.filename
        try:
            self.filename=self.filename.replace(".stl","_export.gcode").replace(".STL","_export.gcode").replace(".obj","_export.gcode").replace(".OBJ","_export.gcode")
            of=open(self.filename)
            self.f=[i.replace("\n","").replace("\r","") for i in of]
            of.close
            if self.p.online:
                    wx.CallAfter(self.printbtn.Enable)

            wx.CallAfter(self.status.SetStatusText,_("Loaded ")+self.filename+_(", %d lines") % (len(self.f),))
            wx.CallAfter(self.pausebtn.Disable)
            wx.CallAfter(self.printbtn.SetLabel,_("Print"))

            threading.Thread(target=self.loadviz).start()
        except:
            self.filename=fn
        wx.CallAfter(self.loadbtn.SetLabel,_("Load File"))
        self.skeining=0
        self.skeinp=None


    def skein(self,filename):
        wx.CallAfter(self.loadbtn.SetLabel,_("Cancel"))
        print _("Slicing ") + filename
        self.cout=StringIO.StringIO()
        self.filename=filename
        self.stopsf=0
        self.skeining=1
        thread(target=self.skein_func).start()
        thread(target=self.skein_monitor).start()

    def loadfile(self,event,filename=None):
        if self.skeining and self.skeinp is not None:
            self.skeinp.terminate()
            return
        basedir=self.settings.last_file_path
        if not os.path.exists(basedir):
            basedir = "."
            try:
                basedir=os.path.split(self.filename)[0]
            except:
                pass
        dlg=wx.FileDialog(self,_("Open file to print"),basedir,style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
	dlg.SetWildcard(_("OBJ, STL, and GCODE files (*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ)|*.gcode;*.gco;*.g;*.stl;*.STL;*.obj;*.OBJ|All Files (*.*)|*.*"))
        if(filename is not None or dlg.ShowModal() == wx.ID_OK):
            if filename is not None:
                name=filename
            else:
                name=dlg.GetPath()
            if not(os.path.exists(name)):
                self.status.SetStatusText(_("File not found!"))
                return
            path = os.path.split(name)[0]
            if path != self.settings.last_file_path:
                self.set("last_file_path",path)
            if name.lower().endswith(".stl"):
                self.skein(name)
            elif name.lower().endswith(".obj"):
                self.skein(name)
            else:
                self.filename=name
                of=open(self.filename)
                self.f=[i.replace("\n","").replace("\r","") for i in of]
                of.close
                self.status.SetStatusText(_("Loaded %s, %d lines") % (name, len(self.f)))
                wx.CallAfter(self.printbtn.SetLabel, _("Print"))
                wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))
                wx.CallAfter(self.pausebtn.Disable)
                if self.p.online:
                    wx.CallAfter(self.printbtn.Enable)
                threading.Thread(target=self.loadviz).start()
        dlg.Destroy()
        
    def loadviz(self):
        Xtot,Ytot,Ztot,Xmin,Xmax,Ymin,Ymax,Zmin,Zmax = pronsole.measurements(self.f)
        print pronsole.totalelength(self.f), _("mm of filament used in this print\n")
        print _("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot); self.webInterface.AddLog("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot)
        print _("the print goes from %f mm to %f mm in Y\nand is %f mm wide\n") % (Ymin, Ymax, Ytot)
        print _("the print goes from %f mm to %f mm in Z\nand is %f mm high\n") % (Zmin, Zmax, Ztot)
        print _("Estimated duration (pessimistic): "), pronsole.estimate_duration(self.f)
        #import time
        #t0=time.time()
        self.gviz.clear()
        self.gwindow.p.clear()
        self.gviz.addfile(self.f)
        #print "generated 2d view in %f s"%(time.time()-t0)
        #t0=time.time()
        self.gwindow.p.addfile(self.f)
        #print "generated 3d view in %f s"%(time.time()-t0)
        self.gviz.showall=1
        wx.CallAfter(self.gviz.Refresh)

    def printfile(self,event):
        self.extra_print_time=0
        if self.paused:
            self.p.paused=0
            self.paused=0
            self.on_startprint()
            if self.sdprinting:
                self.p.send_now("M26 S0")
                self.p.send_now("M24")
                return

        if self.f is None or not len(self.f):
            wx.CallAfter(self.status.SetStatusText, _("No file loaded. Please use load first."))
            return
        if not self.p.online:
            wx.CallAfter(self.status.SetStatusText,_("Not connected to printer."))
            return
        self.on_startprint()
        self.p.startprint(self.f)

    def on_startprint(self):
        wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))
        wx.CallAfter(self.pausebtn.Enable)
        wx.CallAfter(self.printbtn.SetLabel, _("Restart"))

    def endupload(self):
        self.p.send_now("M29 ")
        wx.CallAfter(self.status.SetStatusText, _("File upload complete"))
        time.sleep(0.5)
        self.p.clear=True
        self.uploading=False

    def uploadtrigger(self,l):
        if "Writing to file" in l:
            self.uploading=True
            self.p.startprint(self.f)
            self.p.endcb=self.endupload
            self.recvlisteners.remove(self.uploadtrigger)
        elif "open failed, File" in l:
            self.recvlisteners.remove(self.uploadtrigger)

    def upload(self,event):
        if not self.f or not len(self.f):
            return
        if not self.p.online:
            return
        dlg=wx.TextEntryDialog(self, ("Enter a target filename in 8.3 format:"), _("Pick SD filename") ,dosify(self.filename))
        if dlg.ShowModal()==wx.ID_OK:
            self.p.send_now("M21")
            self.p.send_now("M28 "+str(dlg.GetValue()))
            self.recvlisteners+=[self.uploadtrigger]
        pass

    def pause(self,event):
        print _("Paused.")
        if not self.paused:
            if self.sdprinting:
                self.p.send_now("M25")
            else:
                if(not self.p.printing):
                    #print "Not printing, cannot pause."
                    return
                self.p.pause()
            self.paused=True
            self.extra_print_time += int(time.time() - self.starttime)
            wx.CallAfter(self.pausebtn.SetLabel, _("Resume"))
        else:
            self.paused=False
            if self.sdprinting:
                self.p.send_now("M24")
            else:
                self.p.resume()
            wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))


    def sdprintfile(self,event):
        self.on_startprint()
        threading.Thread(target=self.getfiles).start()
        pass

    def connect(self,event):
        print _("Connecting...");
        
        port=None
        try:
            port=self.scanserial()[0]
        except:
            pass
        if self.serialport.GetValue()!="":
            port=str(self.serialport.GetValue())
        baud=115200
        try:
            baud=int(self.baud.GetValue())
        except:
            pass
        if self.paused:
            self.p.paused=0
            self.p.printing=0
            wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))
            wx.CallAfter(self.printbtn.SetLabel, _("Print"))
            self.paused=0
            if self.sdprinting:
                self.p.send_now("M26 S0")
        self.p.connect(port,baud)
        self.statuscheck=True
        if port != self.settings.port:
            self.set("port",port)
        if baud != self.settings.baudrate:
            self.set("baudrate",str(baud))
        threading.Thread(target=self.statuschecker).start()


    def disconnect(self,event):
        print _("Disconnected.")
        self.p.disconnect()
        self.statuscheck=False

        self.connectbtn.SetLabel("Connect")
        self.connectbtn.Bind(wx.EVT_BUTTON,self.connect)

        wx.CallAfter(self.printbtn.Disable);
        wx.CallAfter(self.pausebtn.Disable);
        for i in self.printerControls:
            wx.CallAfter(i.Disable)

        # Disable XYButtons and ZButtons
        wx.CallAfter(self.xyb.disable)
        wx.CallAfter(self.zb.disable)

        if self.paused:
            self.p.paused=0
            self.p.printing=0
            wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))
            wx.CallAfter(self.printbtn.SetLabel, _("Print"))
            self.paused=0
            if self.sdprinting:
                self.p.send_now("M26 S0")


    def reset(self,event):
        print _("Reset.")
        dlg=wx.MessageDialog(self, _("Are you sure you want to reset the printer?"), _("Reset?"), wx.YES|wx.NO)
        if dlg.ShowModal()==wx.ID_YES:
            self.p.reset()
            self.p.printing=0
            wx.CallAfter(self.printbtn.SetLabel, _("Print"))
            if self.paused:
                self.p.paused=0
                wx.CallAfter(self.pausebtn.SetLabel, _("Pause"))
                self.paused=0

    def get_build_dimensions(self,bdim):
        import re
        # a string containing up to six numbers delimited by almost anything
        # first 0-3 numbers specify the build volume, no sign, always positive
        # remaining 0-3 numbers specify the coordinates of the "southwest" corner of the build platform
        # "XXX,YYY"
        # "XXXxYYY+xxx-yyy"
        # "XXX,YYY,ZZZ+xxx+yyy-zzz"
        # etc
        bdl = re.match(
        "[^\d+-]*(\d+)?" + # X build size
        "[^\d+-]*(\d+)?" + # Y build size
        "[^\d+-]*(\d+)?" + # Z build size
        "[^\d+-]*([+-]\d+)?" + # X corner coordinate
        "[^\d+-]*([+-]\d+)?" + # Y corner coordinate
        "[^\d+-]*([+-]\d+)?"   # Z corner coordinate
        ,bdim).groups()
        defaults = [200, 200, 100, 0, 0, 0]
        bdl_float = [float(value) if value else defaults[i] for i, value in enumerate(bdl)]
        return bdl_float

class macroed(wx.Dialog):
    """Really simple editor to edit macro definitions"""
    def __init__(self,macro_name,definition,callback,gcode=False):
        self.indent_chars = "  "
        title="  macro %s"
        if gcode:
            title="  %s"
        self.gcode=gcode
        wx.Dialog.__init__(self,None,title=title % macro_name,style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.callback = callback
        self.panel=wx.Panel(self,-1)
        titlesizer=wx.BoxSizer(wx.HORIZONTAL)
        title = wx.StaticText(self.panel,-1,title%macro_name)
        #title.SetFont(wx.Font(11,wx.NORMAL,wx.NORMAL,wx.BOLD))
        titlesizer.Add(title,1)
        self.okb = wx.Button(self.panel, -1, _("Save"))
        self.okb.Bind(wx.EVT_BUTTON, self.save)
        self.Bind(wx.EVT_CLOSE, self.close)
        titlesizer.Add(self.okb)
        self.cancelb = wx.Button(self.panel, -1, _("Cancel"))
        self.cancelb.Bind(wx.EVT_BUTTON, self.close)
        titlesizer.Add(self.cancelb)
        topsizer=wx.BoxSizer(wx.VERTICAL)
        topsizer.Add(titlesizer,0,wx.EXPAND)
        self.e=wx.TextCtrl(self.panel,style=wx.TE_MULTILINE+wx.HSCROLL,size=(200,200))
        if not self.gcode:
            self.e.SetValue(self.unindent(definition))
        else:
            self.e.SetValue("\n".join(definition))
        topsizer.Add(self.e,1,wx.ALL+wx.EXPAND)
        self.panel.SetSizer(topsizer)
        topsizer.Layout()
        topsizer.Fit(self)
        self.Show()
        self.e.SetFocus()
        
    def save(self,ev):
        self.Destroy()
        if not self.gcode:
            self.callback(self.reindent(self.e.GetValue()))
        else:
            self.callback(self.e.GetValue().split("\n"))
    def close(self,ev):
        self.Destroy()
        webinterface.KillWebInterfaceThread()
    def unindent(self,text):
        self.indent_chars = text[:len(text)-len(text.lstrip())]
        if len(self.indent_chars)==0:
            self.indent_chars="  "
        unindented = ""
        lines = re.split(r"(?:\r\n?|\n)",text)
        #print lines
        if len(lines) <= 1:
            return text
        for line in lines:
            if line.startswith(self.indent_chars):
                unindented += line[len(self.indent_chars):] + "\n"
            else:
                unindented += line + "\n"
        return unindented
    def reindent(self,text):
        lines = re.split(r"(?:\r\n?|\n)",text)
        if len(lines) <= 1:
            return text
        reindented = ""
        for line in lines:
            if line.strip() != "":
                reindented += self.indent_chars + line + "\n"
        return reindented

class options(wx.Dialog):
    """Options editor"""
    def __init__(self,pronterface):
        wx.Dialog.__init__(self, None, title=_("Edit settings"), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        topsizer=wx.BoxSizer(wx.VERTICAL)
        vbox=wx.StaticBoxSizer(wx.StaticBox(self, label=_("Defaults")) ,wx.VERTICAL)
        topsizer.Add(vbox,1,wx.ALL+wx.EXPAND)
        grid=wx.FlexGridSizer(rows=0,cols=2,hgap=8,vgap=2)
        grid.SetFlexibleDirection( wx.BOTH )
        grid.AddGrowableCol( 1 )
        grid.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
        vbox.Add(grid,0,wx.EXPAND)
        ctrls = {}
        for k,v in sorted(pronterface.settings._all_settings().items()):
            ctrls[k,0] = wx.StaticText(self,-1,k)
            ctrls[k,1] = wx.TextCtrl(self,-1,str(v))
            if k in pronterface.helpdict:
                ctrls[k,0].SetToolTipString(pronterface.helpdict.get(k))
                ctrls[k,1].SetToolTipString(pronterface.helpdict.get(k))
            grid.Add(ctrls[k,0],0,wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.ALIGN_RIGHT)
            grid.Add(ctrls[k,1],1,wx.ALIGN_CENTER_VERTICAL|wx.ALL|wx.EXPAND)
        topsizer.Add(self.CreateSeparatedButtonSizer(wx.OK+wx.CANCEL),0,wx.EXPAND)
        self.SetSizer(topsizer)
        topsizer.Layout()
        topsizer.Fit(self)
        if self.ShowModal()==wx.ID_OK:
            for k,v in pronterface.settings._all_settings().items():
                if ctrls[k,1].GetValue() != str(v):
                    pronterface.set(k,str(ctrls[k,1].GetValue()))
        self.Destroy()

class ButtonEdit(wx.Dialog):
    """Custom button edit dialog"""
    def __init__(self,pronterface):
        wx.Dialog.__init__(self, None, title=_("Custom button"),style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.pronterface=pronterface
        topsizer=wx.BoxSizer(wx.VERTICAL)
        grid=wx.FlexGridSizer(rows=0,cols=2,hgap=4,vgap=2)
        grid.AddGrowableCol(1,1)
        grid.Add(wx.StaticText(self,-1, _("Button title")), 0, wx.BOTTOM|wx.RIGHT)
        self.name=wx.TextCtrl(self,-1,"")
        grid.Add(self.name,1,wx.EXPAND)
        grid.Add(wx.StaticText(self, -1, _("Command")), 0, wx.BOTTOM|wx.RIGHT)
        self.command=wx.TextCtrl(self,-1,"")
        xbox=wx.BoxSizer(wx.HORIZONTAL)
        xbox.Add(self.command,1,wx.EXPAND)
        self.command.Bind(wx.EVT_TEXT,self.macrob_enabler)
        self.macrob=wx.Button(self,-1,"..",style=wx.BU_EXACTFIT)
        self.macrob.Bind(wx.EVT_BUTTON,self.macrob_handler)
        xbox.Add(self.macrob,0)
        grid.Add(xbox,1,wx.EXPAND)
        grid.Add(wx.StaticText(self,-1, _("Color")),0,wx.BOTTOM|wx.RIGHT)
        self.color=wx.TextCtrl(self,-1,"")
        grid.Add(self.color,1,wx.EXPAND)
        topsizer.Add(grid,0,wx.EXPAND)
        topsizer.Add( (0,0),1)
        topsizer.Add(self.CreateStdDialogButtonSizer(wx.OK|wx.CANCEL),0,wx.ALIGN_CENTER)
        self.SetSizer(topsizer)
        self.handler=None
    
    def macrob_enabler(self,e):
        macro = self.command.GetValue()
        valid = False
        if macro == "":
            valid = True
        elif self.pronterface.macros.has_key(macro):
            valid = True
        elif hasattr(self.pronterface.__class__,"do_"+macro):
            valid = False
        elif len([c for c in macro if not c.isalnum() and c != "_"]):
            valid = False
        else:
            valid = True
        self.macrob.Enable(valid)
    def macrob_handler(self,e):
        macro = self.command.GetValue()
        macro = self.pronterface.edit_macro(macro)
        self.command.SetValue(macro)
        if self.name.GetValue()=="":
            self.name.SetValue(macro)

class TempGauge(wx.Panel):
    def __init__(self,parent,size=(200,22),title="",maxval=240,gaugeColour=None):
        wx.Panel.__init__(self,parent,-1,size=size)
        self.Bind(wx.EVT_PAINT,self.paint)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.width,self.height=size
        self.title=title
        self.max=maxval
        self.gaugeColour=gaugeColour
        self.value=0
        self.setpoint=0
        self.recalc()
    def recalc(self):
        mmax=max(int(self.setpoint*1.05),self.max)
        self.scale=float(self.width-2)/float(mmax)
        self.ypt=max(16,int(self.scale*max(self.setpoint,self.max/6)))
    def SetValue(self,value):
        self.value=value
        wx.CallAfter(self.Refresh)
    def SetTarget(self,value):
        self.setpoint=value
        self.recalc()
        wx.CallAfter(self.Refresh)
    def interpolatedColour(self,val,vmin,vmid,vmax,cmin,cmid,cmax):
        if val < vmin: return cmin
        if val > vmax: return cmax
        if val <= vmid:
            lo,hi,val,valhi = cmin,cmid,val-vmin,vmid-vmin
        else:
            lo,hi,val,valhi = cmid,cmax,val-vmid,vmax-vmid
        vv = float(val)/valhi
        rgb=lo.Red()+(hi.Red()-lo.Red())*vv,lo.Green()+(hi.Green()-lo.Green())*vv,lo.Blue()+(hi.Blue()-lo.Blue())*vv
        rgb=map(lambda x:x*0.8,rgb)
        return wx.Colour(*map(int,rgb))
    def paint(self,ev):
        x0,y0,x1,y1,xE,yE = 1,1,self.ypt+1,1,self.width+1-2,20
        dc=wx.PaintDC(self)
        dc.SetBackground(wx.Brush((255,255,255)))
        dc.Clear()
        cold,medium,hot = wx.Colour(0,167,223),wx.Colour(239,233,119),wx.Colour(210,50.100)
        gauge1,gauge2 = wx.Colour(255,255,210),(self.gaugeColour or wx.Colour(234,82,0))
        shadow1,shadow2 = wx.Colour(110,110,110),wx.Colour(255,255,255)
        gc = wx.GraphicsContext.Create(dc)
        # draw shadow first
        # corners
        gc.SetBrush(gc.CreateRadialGradientBrush(xE-7,9,xE-7,9,8,shadow1,shadow2))
        gc.DrawRectangle(xE-7,1,8,8)
        gc.SetBrush(gc.CreateRadialGradientBrush(xE-7,17,xE-7,17,8,shadow1,shadow2))
        gc.DrawRectangle(xE-7,17,8,8)
        gc.SetBrush(gc.CreateRadialGradientBrush(x0+6,17,x0+6,17,8,shadow1,shadow2))
        gc.DrawRectangle(0,17,x0+6,8)
        # edges
        gc.SetBrush(gc.CreateLinearGradientBrush(xE-13,0,xE-6,0,shadow1,shadow2))
        gc.DrawRectangle(xE-6,9,10,8)
        gc.SetBrush(gc.CreateLinearGradientBrush(x0,yE-2,x0,yE+5,shadow1,shadow2))
        gc.DrawRectangle(x0+6,yE-2,xE-12,7)
        # draw gauge background
        gc.SetBrush(gc.CreateLinearGradientBrush(x0,y0,x1+1,y1,cold,medium))
        gc.DrawRoundedRectangle(x0,y0,x1+4,yE,6)
        gc.SetBrush(gc.CreateLinearGradientBrush(x1-2,y1,xE,y1,medium,hot))
        gc.DrawRoundedRectangle(x1-2,y1,xE-x1,yE,6)
        # draw gauge
        width=12
        w1=y0+9-width/2
        w2=w1+width
        value=x0+max(10,min(self.width+1-2,int(self.value*self.scale)))
        #gc.SetBrush(gc.CreateLinearGradientBrush(x0,y0+3,x0,y0+15,gauge1,gauge2))
        #gc.SetBrush(gc.CreateLinearGradientBrush(0,3,0,15,wx.Colour(255,255,255),wx.Colour(255,90,32)))
        gc.SetBrush(gc.CreateLinearGradientBrush(x0,y0+3,x0,y0+15,gauge1,self.interpolatedColour(value,x0,x1,xE,cold,medium,hot)))
        val_path = gc.CreatePath()
        val_path.MoveToPoint(x0,w1)
        val_path.AddLineToPoint(value,w1)
        val_path.AddLineToPoint(value+2,w1+width/4)
        val_path.AddLineToPoint(value+2,w2-width/4)
        val_path.AddLineToPoint(value,w2)
        #val_path.AddLineToPoint(value-4,10)
        val_path.AddLineToPoint(x0,w2)
        gc.DrawPath(val_path)
        # draw setpoint markers
        setpoint=x0+max(10,int(self.setpoint*self.scale))
        gc.SetBrush(gc.CreateBrush(wx.Brush(wx.Colour(0,0,0))))
        setp_path = gc.CreatePath()
        setp_path.MoveToPoint(setpoint-4,y0)
        setp_path.AddLineToPoint(setpoint+4,y0)
        setp_path.AddLineToPoint(setpoint,y0+5)
        setp_path.MoveToPoint(setpoint-4,yE)
        setp_path.AddLineToPoint(setpoint+4,yE)
        setp_path.AddLineToPoint(setpoint,yE-5)
        gc.DrawPath(setp_path)
        # draw readout
        text=u"T\u00B0 %u/%u"%(self.value,self.setpoint)
        #gc.SetFont(gc.CreateFont(wx.Font(12,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_BOLD),wx.WHITE))
        #gc.DrawText(text,29,-2)
        gc.SetFont(gc.CreateFont(wx.Font(10,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_BOLD),wx.WHITE))
        gc.DrawText(self.title,x0+19,y0+4)
        gc.DrawText(text,      x0+133,y0+4)
        gc.SetFont(gc.CreateFont(wx.Font(10,wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_BOLD)))
        gc.DrawText(self.title,x0+18,y0+3)
        gc.DrawText(text,      x0+132,y0+3)

if __name__ == '__main__':
    app = wx.App(False)
    main = PronterWindow()
    main.Show()
    try:
        app.MainLoop()
    except:
        pass

