BeremizIDE.py
author Andrey Skvortsov <andrej.skvortzov@gmail.com>
Tue, 28 Mar 2017 19:48:51 +0300
changeset 1670 cd03b8432541
parent 1662 8816f7316d9c
child 1680 6db967480b7d
permissions -rwxr-xr-x
enable to write SFC transition in IL

This commit reverts previous commit 3f3d97be9a9f.
Because the fix for the matiec problem is already upstream
(https://bitbucket.org/mjsousa/matiec/commits/ce026aee03e00c0790948a18bd7b35805457da81)
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Beremiz, a Integrated Development Environment for
# programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
#
# Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD
# Copyright (C) 2016: Andrey Skvortsov <andrej.skvortzov@gmail.com>
#
# See COPYING file for copyrights details.
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


import os, sys
import tempfile
import shutil
import random
import time
import version
from types import ListType

beremiz_dir = os.path.dirname(os.path.realpath(__file__))

def Bpath(*args):
    return os.path.join(beremiz_dir,*args)



import wx.lib.buttons, wx.lib.statbmp, wx.stc
import cPickle
import types, time, re, platform, time, traceback, commands

from docutil import OpenHtmlFrame
from editors.EditorPanel import EditorPanel
from editors.Viewer import Viewer
from editors.TextViewer import TextViewer
from editors.ResourceEditor import ConfigurationEditor, ResourceEditor
from editors.DataTypeEditor import DataTypeEditor
from util.MiniTextControler import MiniTextControler
from util.ProcessLogger import ProcessLogger
from controls.LogViewer import LogViewer
from controls.CustomStyledTextCtrl import CustomStyledTextCtrl
from controls import EnhancedStatusBar as esb
from dialogs.AboutDialog import ShowAboutDialog

from PLCControler import LOCATION_CONFNODE, LOCATION_MODULE, LOCATION_GROUP, LOCATION_VAR_INPUT, LOCATION_VAR_OUTPUT, LOCATION_VAR_MEMORY, ITEM_PROJECT, ITEM_RESOURCE
from ProjectController import ProjectController, GetAddMenuItems, MATIEC_ERROR_MODEL, ITEM_CONFNODE


MAX_RECENT_PROJECTS = 9

if wx.Platform == '__WXMSW__':
    faces = {
        'mono' : 'Courier New',
        'size' : 8,
    }
else:
    faces = {
        'mono' : 'Courier',
        'size' : 10,
    }

from threading import Lock,Timer,currentThread
MainThread = currentThread().ident
REFRESH_PERIOD = 0.1
from time import time as gettime
class LogPseudoFile:
    """ Base class for file like objects to facilitate StdOut for the Shell."""
    def __init__(self, output, risecall):
        self.red_white = 1
        self.red_yellow = 2
        self.black_white = wx.stc.STC_STYLE_DEFAULT
        self.output = output
        self.risecall = risecall
        # to prevent rapid fire on rising log panel
        self.rising_timer = 0
        self.lock = Lock()
        self.YieldLock = Lock()
        self.RefreshLock = Lock()
        self.TimerAccessLock = Lock()
        self.stack = []
        self.LastRefreshTime = gettime()
        self.LastRefreshTimer = None

    def write(self, s, style = None):
        if self.lock.acquire():
            self.stack.append((s,style))
            self.lock.release()
            current_time = gettime()
            self.TimerAccessLock.acquire()
            if self.LastRefreshTimer:
                self.LastRefreshTimer.cancel()
                self.LastRefreshTimer=None
            self.TimerAccessLock.release()
            if current_time - self.LastRefreshTime > REFRESH_PERIOD and self.RefreshLock.acquire(False):
                self._should_write()
            else:
                self.TimerAccessLock.acquire()
                self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired)
                self.LastRefreshTimer.start()
                self.TimerAccessLock.release()

    def _timer_expired(self):
        if self.RefreshLock.acquire(False):
            self._should_write()
        else:
            self.TimerAccessLock.acquire()
            self.LastRefreshTimer = Timer(REFRESH_PERIOD, self._timer_expired)
            self.LastRefreshTimer.start()
            self.TimerAccessLock.release()

    def _should_write(self):
        wx.CallAfter(self._write)
        if MainThread == currentThread().ident:
            app = wx.GetApp()
            if app is not None:
                if self.YieldLock.acquire(0):
                    app.Yield()
                    self.YieldLock.release()

    def _write(self):
        if self.output :
            self.output.Freeze()
            self.lock.acquire()
            for s, style in self.stack:
                if style is None : style=self.black_white
                if style != self.black_white:
                    self.output.StartStyling(self.output.GetLength(), 0xff)

                # Temporary deactivate read only mode on StyledTextCtrl for
                # adding text. It seems that text modifications, even
                # programmatically, are disabled in StyledTextCtrl when read
                # only is active
                start_pos = self.output.GetLength()
                self.output.SetReadOnly(False)
                self.output.AppendText(s)
                self.output.SetReadOnly(True)
                text_len = self.output.GetLength() - start_pos

                if style != self.black_white:
                    self.output.SetStyling(text_len, style)
            self.stack = []
            self.lock.release()
            self.output.Thaw()
            self.LastRefreshTime = gettime()
            try:
                self.RefreshLock.release()
            except:
                pass
            newtime = time.time()
            if newtime - self.rising_timer > 1:
                self.risecall(self.output)
            self.rising_timer = newtime

    def write_warning(self, s):
        self.write(s,self.red_white)

    def write_error(self, s):
        self.write(s,self.red_yellow)

    def writeyield(self, s):
        self.write(s)
        wx.GetApp().Yield()

    def flush(self):
        # Temporary deactivate read only mode on StyledTextCtrl for clearing
        # text. It seems that text modifications, even programmatically, are
        # disabled in StyledTextCtrl when read only is active
        self.output.SetReadOnly(False)
        self.output.SetText("")
        self.output.SetReadOnly(True)

    def isatty(self):
        return False

ID_FILEMENURECENTPROJECTS = wx.NewId()

from IDEFrame import TITLE,\
                     EDITORTOOLBAR,\
                     FILEMENU,\
                     EDITMENU,\
                     DISPLAYMENU,\
                     PROJECTTREE,\
                     POUINSTANCEVARIABLESPANEL,\
                     LIBRARYTREE,\
                     SCALING,\
                     PAGETITLES,\
                     IDEFrame, AppendMenu,\
                     EncodeFileSystemPath, DecodeFileSystemPath
from util.BitmapLibrary import GetBitmap

class Beremiz(IDEFrame):

    def _init_utils(self):
        self.ConfNodeMenu = wx.Menu(title='')
        self.RecentProjectsMenu = wx.Menu(title='')

        IDEFrame._init_utils(self)

    def _init_coll_FileMenu_Items(self, parent):
        AppendMenu(parent, help='', id=wx.ID_NEW,
              kind=wx.ITEM_NORMAL, text=_(u'New') + '\tCTRL+N')
        AppendMenu(parent, help='', id=wx.ID_OPEN,
              kind=wx.ITEM_NORMAL, text=_(u'Open') + '\tCTRL+O')
        parent.AppendMenu(ID_FILEMENURECENTPROJECTS, _("&Recent Projects"), self.RecentProjectsMenu)
        parent.AppendSeparator()
        AppendMenu(parent, help='', id=wx.ID_SAVE,
              kind=wx.ITEM_NORMAL, text=_(u'Save') + '\tCTRL+S')
        AppendMenu(parent, help='', id=wx.ID_SAVEAS,
              kind=wx.ITEM_NORMAL, text=_(u'Save as') + '\tCTRL+SHIFT+S')
        AppendMenu(parent, help='', id=wx.ID_CLOSE,
              kind=wx.ITEM_NORMAL, text=_(u'Close Tab') + '\tCTRL+W')
        AppendMenu(parent, help='', id=wx.ID_CLOSE_ALL,
              kind=wx.ITEM_NORMAL, text=_(u'Close Project') + '\tCTRL+SHIFT+W')
        parent.AppendSeparator()
        AppendMenu(parent, help='', id=wx.ID_PAGE_SETUP,
              kind=wx.ITEM_NORMAL, text=_(u'Page Setup') + '\tCTRL+ALT+P')
        AppendMenu(parent, help='', id=wx.ID_PREVIEW,
              kind=wx.ITEM_NORMAL, text=_(u'Preview') + '\tCTRL+SHIFT+P')
        AppendMenu(parent, help='', id=wx.ID_PRINT,
              kind=wx.ITEM_NORMAL, text=_(u'Print') + '\tCTRL+P')
        parent.AppendSeparator()
        AppendMenu(parent, help='', id=wx.ID_EXIT,
              kind=wx.ITEM_NORMAL, text=_(u'Quit') + '\tCTRL+Q')

        self.Bind(wx.EVT_MENU, self.OnNewProjectMenu, id=wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnOpenProjectMenu, id=wx.ID_OPEN)
        self.Bind(wx.EVT_MENU, self.OnSaveProjectMenu, id=wx.ID_SAVE)
        self.Bind(wx.EVT_MENU, self.OnSaveProjectAsMenu, id=wx.ID_SAVEAS)
        self.Bind(wx.EVT_MENU, self.OnCloseTabMenu, id=wx.ID_CLOSE)
        self.Bind(wx.EVT_MENU, self.OnCloseProjectMenu, id=wx.ID_CLOSE_ALL)
        self.Bind(wx.EVT_MENU, self.OnPageSetupMenu, id=wx.ID_PAGE_SETUP)
        self.Bind(wx.EVT_MENU, self.OnPreviewMenu, id=wx.ID_PREVIEW)
        self.Bind(wx.EVT_MENU, self.OnPrintMenu, id=wx.ID_PRINT)
        self.Bind(wx.EVT_MENU, self.OnQuitMenu, id=wx.ID_EXIT)

        self.AddToMenuToolBar([(wx.ID_NEW, "new", _(u'New'), None),
                               (wx.ID_OPEN, "open", _(u'Open'), None),
                               (wx.ID_SAVE, "save", _(u'Save'), None),
                               (wx.ID_SAVEAS, "saveas", _(u'Save As...'), None),
                               (wx.ID_PRINT, "print", _(u'Print'), None)])

    def _RecursiveAddMenuItems(self, menu, items):
        for name, text, help, children in items:
            new_id = wx.NewId()
            if len(children) > 0:
                new_menu = wx.Menu(title='')
                menu.AppendMenu(new_id, text, new_menu)
                self._RecursiveAddMenuItems(new_menu, children)
            else:
                AppendMenu(menu, help=help, id=new_id,
                       kind=wx.ITEM_NORMAL, text=text)
                self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name),
                          id=new_id)

    def _init_coll_AddMenu_Items(self, parent):
        IDEFrame._init_coll_AddMenu_Items(self, parent, False)
        self._RecursiveAddMenuItems(parent, GetAddMenuItems())

    def _init_coll_HelpMenu_Items(self, parent):
        parent.Append(help='', id=wx.ID_ABOUT,
              kind=wx.ITEM_NORMAL, text=_(u'About'))
        self.Bind(wx.EVT_MENU, self.OnAboutMenu, id=wx.ID_ABOUT)

    def _init_coll_ConnectionStatusBar_Fields(self, parent):
        parent.SetFieldsCount(3)

        parent.SetStatusText(number=0, text='')
        parent.SetStatusText(number=1, text='')
        parent.SetStatusText(number=2, text='')

        parent.SetStatusWidths([-1, 300, 200])

    def _init_ctrls(self, prnt):
        IDEFrame._init_ctrls(self, prnt)

        self.EditMenuSize = self.EditMenu.GetMenuItemCount()

        inspectorID = wx.NewId()
        self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, id=inspectorID)
        accels = [wx.AcceleratorEntry(wx.ACCEL_CTRL|wx.ACCEL_ALT, ord('I'), inspectorID)]

        keyID = wx.NewId()
        self.Bind(wx.EVT_MENU, self.SwitchFullScrMode, id=keyID)
        accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, wx.WXK_F12, keyID)]

        for method,shortcut in [("Stop",     wx.WXK_F4),
                                ("Run",      wx.WXK_F5),
                                ("Transfer", wx.WXK_F6),
                                ("Connect",  wx.WXK_F7),
                                ("Build",    wx.WXK_F11)]:
            def OnMethodGen(obj,meth):
                def OnMethod(evt):
                    if obj.CTR is not None:
                       obj.CTR.CallMethod('_'+meth)
                    wx.CallAfter(self.RefreshStatusToolBar)
                return OnMethod
            newid = wx.NewId()
            self.Bind(wx.EVT_MENU, OnMethodGen(self,method), id=newid)
            accels += [wx.AcceleratorEntry(wx.ACCEL_NORMAL, shortcut,newid)]

        self.SetAcceleratorTable(wx.AcceleratorTable(accels))

        self.LogConsole = CustomStyledTextCtrl(
                  name='LogConsole', parent=self.BottomNoteBook, pos=wx.Point(0, 0),
                  size=wx.Size(0, 0))
        self.LogConsole.Bind(wx.EVT_SET_FOCUS, self.OnLogConsoleFocusChanged)
        self.LogConsole.Bind(wx.EVT_KILL_FOCUS, self.OnLogConsoleFocusChanged)
        self.LogConsole.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnLogConsoleUpdateUI)
        self.LogConsole.SetReadOnly(True)
        self.LogConsole.SetWrapMode(wx.stc.STC_WRAP_CHAR)

        # Define Log Console styles
        self.LogConsole.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
        self.LogConsole.StyleClearAll()
        self.LogConsole.StyleSetSpec(1, "face:%(mono)s,fore:#FF0000,size:%(size)d" % faces)
        self.LogConsole.StyleSetSpec(2, "face:%(mono)s,fore:#FF0000,back:#FFFF00,size:%(size)d" % faces)

        # Define Log Console markers
        self.LogConsole.SetMarginSensitive(1, True)
        self.LogConsole.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
        self.LogConsole.MarkerDefine(0, wx.stc.STC_MARK_CIRCLE, "BLACK", "RED")

        self.LogConsole.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT)

        self.LogConsole.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnLogConsoleMarginClick)
        self.LogConsole.Bind(wx.stc.EVT_STC_MODIFIED, self.OnLogConsoleModified)

        self.MainTabs["LogConsole"] = (self.LogConsole, _("Console"))
        self.BottomNoteBook.AddPage(*self.MainTabs["LogConsole"])
        #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogConsole), wx.RIGHT)

        self.LogViewer = LogViewer(self.BottomNoteBook, self)
        self.MainTabs["LogViewer"] = (self.LogViewer, _("PLC Log"))
        self.BottomNoteBook.AddPage(*self.MainTabs["LogViewer"])
        #self.BottomNoteBook.Split(self.BottomNoteBook.GetPageIndex(self.LogViewer), wx.RIGHT)

        StatusToolBar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
                wx.TB_FLAT | wx.TB_NODIVIDER | wx.NO_BORDER)
        StatusToolBar.SetToolBitmapSize(wx.Size(25, 25))
        StatusToolBar.Realize()
        self.Panes["StatusToolBar"] = StatusToolBar
        self.AUIManager.AddPane(StatusToolBar, wx.aui.AuiPaneInfo().
                  Name("StatusToolBar").Caption(_("Status ToolBar")).
                  ToolbarPane().Top().Position(1).
                  LeftDockable(False).RightDockable(False))

        self.AUIManager.Update()

        self.ConnectionStatusBar = esb.EnhancedStatusBar(self, style=wx.ST_SIZEGRIP)
        self._init_coll_ConnectionStatusBar_Fields(self.ConnectionStatusBar)
        self.ProgressStatusBar = wx.Gauge(self.ConnectionStatusBar, -1, range = 100)
        self.ConnectionStatusBar.AddWidget(self.ProgressStatusBar, esb.ESB_EXACT_FIT, esb.ESB_EXACT_FIT, 2)        
        self.ProgressStatusBar.Hide()
        self.SetStatusBar(self.ConnectionStatusBar)

    def __init_execute_path(self):
        if os.name == 'nt':
            # on windows, desktop shortcut launches Beremiz.py
            # with working dir set to mingw/bin.
            # then we prefix CWD to PATH in order to ensure that
            # commands invoked by build process by default are
            # found here.
            os.environ["PATH"] = os.getcwd()+';'+os.environ["PATH"]
        
        
    def __init__(self, parent, projectOpen=None, buildpath=None, ctr=None, debug=True):
        # Add beremiz's icon in top left corner of the frame
        self.icon = wx.Icon(Bpath("images", "brz.ico"), wx.BITMAP_TYPE_ICO)
        self.__init_execute_path()
        
        IDEFrame.__init__(self, parent, debug)
        self.Log = LogPseudoFile(self.LogConsole,self.SelectTab)

        self.local_runtime = None
        self.runtime_port = None
        self.local_runtime_tmpdir = None

        self.LastPanelSelected = None

        # Define Tree item icon list
        self.LocationImageList = wx.ImageList(16, 16)
        self.LocationImageDict = {}

        # Icons for location items
        for imgname, itemtype in [
            ("CONFIGURATION", LOCATION_CONFNODE),
            ("RESOURCE",      LOCATION_MODULE),
            ("PROGRAM",       LOCATION_GROUP),
            ("VAR_INPUT",     LOCATION_VAR_INPUT),
            ("VAR_OUTPUT",    LOCATION_VAR_OUTPUT),
            ("VAR_LOCAL",     LOCATION_VAR_MEMORY)]:
            self.LocationImageDict[itemtype] = self.LocationImageList.Add(GetBitmap(imgname))

        # Icons for other items
        for imgname, itemtype in [
            ("Extension", ITEM_CONFNODE)]:
            self.TreeImageDict[itemtype] = self.TreeImageList.Add(GetBitmap(imgname))

        if projectOpen is not None:
            projectOpen = DecodeFileSystemPath(projectOpen, False)

        if projectOpen is not None and os.path.isdir(projectOpen):
            self.CTR = ProjectController(self, self.Log)
            self.Controler = self.CTR
            result, err = self.CTR.LoadProject(projectOpen, buildpath)
            if not result:
                self.LibraryPanel.SetController(self.Controler)
                self.ProjectTree.Enable(True)
                self.PouInstanceVariablesPanel.SetController(self.Controler)
                self.RefreshConfigRecentProjects(os.path.abspath(projectOpen))
                self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
            else:
                self.ResetView()
                self.ShowErrorMessage(result)
        else:
            self.CTR = ctr
            self.Controler = ctr
            if ctr is not None:
                self.LibraryPanel.SetController(self.Controler)
                self.ProjectTree.Enable(True)
                self.PouInstanceVariablesPanel.SetController(self.Controler)
                self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
        if self.EnableDebug:
            self.DebugVariablePanel.SetDataProducer(self.CTR)

        self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)

        self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU, DISPLAYMENU)
        self.RefreshAll()
        self.LogConsole.SetFocus()

    def RefreshTitle(self):
        name = _("Beremiz")
        if self.CTR is not None:
            projectname = self.CTR.GetProjectName()
            if self.CTR.ProjectTestModified():
                projectname = "~%s~" % projectname
            self.SetTitle("%s - %s" % (name, projectname))
        else:
            self.SetTitle(name)

    def StartLocalRuntime(self, taskbaricon = True):
        if (self.local_runtime is None) or (self.local_runtime.exitcode is not None):
            # create temporary directory for runtime working directory
            self.local_runtime_tmpdir = tempfile.mkdtemp()
            # choose an arbitrary random port for runtime
            self.runtime_port = int(random.random() * 1000) + 61131
            # launch local runtime
            self.local_runtime = ProcessLogger(self.Log,
                "\"%s\" \"%s\" -p %s -i localhost %s %s"%(
                    sys.executable,
                    Bpath("Beremiz_service.py"),
                    self.runtime_port,
                    {False : "-x 0", True :"-x 1"}[taskbaricon],
                    self.local_runtime_tmpdir),
                no_gui=False,
                timeout=500, keyword = self.local_runtime_tmpdir,
                cwd = self.local_runtime_tmpdir)
            self.local_runtime.spin()
        return self.runtime_port

    def KillLocalRuntime(self):
        if self.local_runtime is not None:
            # shutdown local runtime
            self.local_runtime.kill(gently=False)
            # clear temp dir
            shutil.rmtree(self.local_runtime_tmpdir)

            self.local_runtime = None

    def OnOpenWidgetInspector(self, evt):
        # Activate the widget inspection tool
        from wx.lib.inspection import InspectionTool
        if not InspectionTool().initialized:
            InspectionTool().Init()

        # Find a widget to be selected in the tree.  Use either the
        # one under the cursor, if any, or this frame.
        wnd = wx.FindWindowAtPointer()
        if not wnd:
            wnd = self
        InspectionTool().Show(wnd, True)

    def OnLogConsoleFocusChanged(self, event):
        self.RefreshEditMenu()
        event.Skip()

    def OnLogConsoleUpdateUI(self, event):
        self.SetCopyBuffer(self.LogConsole.GetSelectedText(), True)
        event.Skip()

    def OnLogConsoleMarginClick(self, event):
        line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
        wx.CallAfter(self.SearchLineForError, self.LogConsole.GetLine(line_idx))
        event.Skip()

    def OnLogConsoleModified(self, event):
        line_idx = self.LogConsole.LineFromPosition(event.GetPosition())
        line = self.LogConsole.GetLine(line_idx)
        if line:
            result = MATIEC_ERROR_MODEL.match(line)
            if result is not None:
                self.LogConsole.MarkerAdd(line_idx, 0)
        event.Skip()

    def SearchLineForError(self, line):
        if self.CTR is not None:
            result = MATIEC_ERROR_MODEL.match(line)
            if result is not None:
                first_line, first_column, last_line, last_column, error = result.groups()
                infos = self.CTR.ShowError(self.Log,
                                                  (int(first_line), int(first_column)),
                                                  (int(last_line), int(last_column)))

    ## Function displaying an Error dialog in PLCOpenEditor.
    #  @return False if closing cancelled.
    def CheckSaveBeforeClosing(self, title=_("Close Project")):
        if self.CTR.ProjectTestModified():
            dialog = wx.MessageDialog(self,
                                      _("There are changes, do you want to save?"),
                                      title,
                                      wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
            answer = dialog.ShowModal()
            dialog.Destroy()
            if answer == wx.ID_YES:
                self.CTR.SaveProject()
            elif answer == wx.ID_CANCEL:
                return False

        for idx in xrange(self.TabsOpened.GetPageCount()):
            window = self.TabsOpened.GetPage(idx)
            if not window.CheckSaveBeforeClosing():
                return False

        return True

    def GetTabInfos(self, tab):
        if (isinstance(tab, EditorPanel) and
            not isinstance(tab, (Viewer,
                                 TextViewer,
                                 ResourceEditor,
                                 ConfigurationEditor,
                                 DataTypeEditor))):
            return ("confnode", tab.Controler.CTNFullName(), tab.GetTagName())
        elif (isinstance(tab, TextViewer) and
              (tab.Controler is None or isinstance(tab.Controler, MiniTextControler))):
            return ("confnode", None, tab.GetInstancePath())
        else:
            return IDEFrame.GetTabInfos(self, tab)

    def LoadTab(self, notebook, page_infos):
        if page_infos[0] == "confnode":
            if page_infos[1] is None:
                confnode = self.CTR
            else:
                confnode = self.CTR.GetChildByName(page_infos[1])
            return notebook.GetPageIndex(confnode._OpenView(*page_infos[2:]))
        else:
            return IDEFrame.LoadTab(self, notebook, page_infos)

    # Strange hack required by WAMP connector, using twisted.
    # Twisted reactor needs to be stopped only before quit,
    # since it cannot be restarted
    ToDoBeforeQuit = []
    def AddToDoBeforeQuit(self, Thing):
        self.ToDoBeforeQuit.append(Thing)

    def OnCloseFrame(self, event):
        for evt_type in [wx.EVT_SET_FOCUS,
                         wx.EVT_KILL_FOCUS,
                         wx.stc.EVT_STC_UPDATEUI]:
            self.LogConsole.Unbind(evt_type)
        if self.CTR is None or self.CheckSaveBeforeClosing(_("Close Application")):
            if self.CTR is not None:
                self.CTR.KillDebugThread()
            self.KillLocalRuntime()

            self.SaveLastState()

            for Thing in self.ToDoBeforeQuit :
                Thing()
            self.ToDoBeforeQuit = []

            event.Skip()
        else:
            event.Veto()

    def RefreshFileMenu(self):
        self.RefreshRecentProjectsMenu()

        MenuToolBar = self.Panes["MenuToolBar"]
        if self.CTR is not None:
            selected = self.TabsOpened.GetSelection()
            if selected >= 0:
                window = self.TabsOpened.GetPage(selected)
                viewer_is_modified = window.IsModified()
                is_viewer = isinstance(window, Viewer)
            else:
                viewer_is_modified = is_viewer = False
            if self.TabsOpened.GetPageCount() > 0:
                self.FileMenu.Enable(wx.ID_CLOSE, True)
                if is_viewer:
                    self.FileMenu.Enable(wx.ID_PREVIEW, True)
                    self.FileMenu.Enable(wx.ID_PRINT, True)
                    MenuToolBar.EnableTool(wx.ID_PRINT, True)
                else:
                    self.FileMenu.Enable(wx.ID_PREVIEW, False)
                    self.FileMenu.Enable(wx.ID_PRINT, False)
                    MenuToolBar.EnableTool(wx.ID_PRINT, False)
            else:
                self.FileMenu.Enable(wx.ID_CLOSE, False)
                self.FileMenu.Enable(wx.ID_PREVIEW, False)
                self.FileMenu.Enable(wx.ID_PRINT, False)
                MenuToolBar.EnableTool(wx.ID_PRINT, False)
            self.FileMenu.Enable(wx.ID_PAGE_SETUP, True)
            project_modified = self.CTR.ProjectTestModified() or viewer_is_modified
            self.FileMenu.Enable(wx.ID_SAVE, project_modified)
            MenuToolBar.EnableTool(wx.ID_SAVE, project_modified)
            self.FileMenu.Enable(wx.ID_SAVEAS, True)
            MenuToolBar.EnableTool(wx.ID_SAVEAS, True)
            self.FileMenu.Enable(wx.ID_CLOSE_ALL, True)
        else:
            self.FileMenu.Enable(wx.ID_CLOSE, False)
            self.FileMenu.Enable(wx.ID_PAGE_SETUP, False)
            self.FileMenu.Enable(wx.ID_PREVIEW, False)
            self.FileMenu.Enable(wx.ID_PRINT, False)
            MenuToolBar.EnableTool(wx.ID_PRINT, False)
            self.FileMenu.Enable(wx.ID_SAVE, False)
            MenuToolBar.EnableTool(wx.ID_SAVE, False)
            self.FileMenu.Enable(wx.ID_SAVEAS, False)
            MenuToolBar.EnableTool(wx.ID_SAVEAS, False)
            self.FileMenu.Enable(wx.ID_CLOSE_ALL, False)

    def RefreshRecentProjectsMenu(self):
        try:
            recent_projects = map(DecodeFileSystemPath,
                                  self.GetConfigEntry("RecentProjects", []))
        except:
            recent_projects = []

        while self.RecentProjectsMenu.GetMenuItemCount() > len(recent_projects):
            item = self.RecentProjectsMenu.FindItemByPosition(0)
            self.RecentProjectsMenu.RemoveItem(item)

        self.FileMenu.Enable(ID_FILEMENURECENTPROJECTS, len(recent_projects) > 0)
        for idx, projectpath in enumerate(recent_projects):
            text = u'&%d: %s' % (idx + 1, projectpath)

            if idx < self.RecentProjectsMenu.GetMenuItemCount():
                item = self.RecentProjectsMenu.FindItemByPosition(idx)
                id = item.GetId()
                item.SetItemLabel(text)
                self.Disconnect(id, id, wx.EVT_BUTTON._getEvtType())
            else:
                id = wx.NewId()
                AppendMenu(self.RecentProjectsMenu, help='', id=id,
                           kind=wx.ITEM_NORMAL, text=text)
            self.Bind(wx.EVT_MENU, self.GenerateOpenRecentProjectFunction(projectpath), id=id)

    def GenerateOpenRecentProjectFunction(self, projectpath):
        def OpenRecentProject(event):
            if self.CTR is not None and not self.CheckSaveBeforeClosing():
                return

            self.OpenProject(projectpath)
        return OpenRecentProject

    def GenerateMenuRecursive(self, items, menu):
        for kind, infos in items:
            if isinstance(kind, ListType):
                text, id = infos
                submenu = wx.Menu('')
                self.GenerateMenuRecursive(kind, submenu)
                menu.AppendMenu(id, text, submenu)
            elif kind == wx.ITEM_SEPARATOR:
                menu.AppendSeparator()
            else:
                text, id, help, callback = infos
                AppendMenu(menu, help='', id=id, kind=kind, text=text)
                if callback is not None:
                    self.Bind(wx.EVT_MENU, callback, id=id)

    def RefreshEditorToolBar(self):
        IDEFrame.RefreshEditorToolBar(self)
        self.AUIManager.GetPane("EditorToolBar").Position(2)
        self.AUIManager.GetPane("StatusToolBar").Position(1)
        self.AUIManager.Update()

    def RefreshStatusToolBar(self):
        StatusToolBar = self.Panes["StatusToolBar"]
        StatusToolBar.ClearTools()

        if self.CTR is not None:

            for confnode_method in self.CTR.StatusMethods:
                if "method" in confnode_method and confnode_method.get("shown",True):
                    id = wx.NewId()
                    StatusToolBar.AddSimpleTool(id,
                        GetBitmap(confnode_method.get("bitmap", "Unknown")),
                        confnode_method["tooltip"])
                    self.Bind(wx.EVT_MENU, self.GetMenuCallBackFunction(confnode_method["method"]), id=id)

            StatusToolBar.Realize()
            self.AUIManager.GetPane("StatusToolBar").BestSize(StatusToolBar.GetBestSize()).Show()
        else:
            self.AUIManager.GetPane("StatusToolBar").Hide()
        self.AUIManager.GetPane("EditorToolBar").Position(2)
        self.AUIManager.GetPane("StatusToolBar").Position(1)
        self.AUIManager.Update()

    def RefreshEditMenu(self):
        IDEFrame.RefreshEditMenu(self)
        if self.FindFocus() == self.LogConsole:
            self.EditMenu.Enable(wx.ID_COPY, True)
            self.Panes["MenuToolBar"].EnableTool(wx.ID_COPY, True)

        if self.CTR is not None:
            selected = self.TabsOpened.GetSelection()
            if selected >= 0:
                panel = self.TabsOpened.GetPage(selected)
            else:
                panel = None
            if panel != self.LastPanelSelected:
                for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
                    item = self.EditMenu.FindItemByPosition(self.EditMenuSize)
                    if item is not None:
                        if item.IsSeparator():
                            self.EditMenu.RemoveItem(item)
                        else:
                            self.EditMenu.Delete(item.GetId())
                self.LastPanelSelected = panel
                if panel is not None:
                    items = panel.GetConfNodeMenuItems()
                else:
                    items = []
                if len(items) > 0:
                    self.EditMenu.AppendSeparator()
                    self.GenerateMenuRecursive(items, self.EditMenu)
            if panel is not None:
                panel.RefreshConfNodeMenu(self.EditMenu)
        else:
            for i in xrange(self.EditMenuSize, self.EditMenu.GetMenuItemCount()):
                item = self.EditMenu.FindItemByPosition(i)
                if item is not None:
                    if item.IsSeparator():
                        self.EditMenu.RemoveItem(item)
                    else:
                        self.EditMenu.Delete(item.GetId())
            self.LastPanelSelected = None
        self.MenuBar.UpdateMenus()

    def RefreshAll(self):
        self.RefreshStatusToolBar()

    def GetMenuCallBackFunction(self, method):
        """ Generate the callbackfunc for a given CTR method"""
        def OnMenu(event):
            # Disable button to prevent re-entrant call
            event.GetEventObject().Disable()
            # Call
            getattr(self.CTR, method)()
            # Re-enable button
            event.GetEventObject().Enable()
        return OnMenu

    def GetConfigEntry(self, entry_name, default):
        return cPickle.loads(str(self.Config.Read(entry_name, cPickle.dumps(default))))

    def ResetConnectionStatusBar(self):
        for field in xrange(self.ConnectionStatusBar.GetFieldsCount()):
            self.ConnectionStatusBar.SetStatusText('', field)

    def ResetView(self):
        IDEFrame.ResetView(self)
        self.ConfNodeInfos = {}
        if self.CTR is not None:
            self.CTR.CloseProject()
        self.CTR = None
        self.Log.flush()
        if self.EnableDebug:
            self.DebugVariablePanel.SetDataProducer(None)
            self.ResetConnectionStatusBar()

    def RefreshConfigRecentProjects(self, projectpath, err=False):
        try:
            recent_projects = map(DecodeFileSystemPath,
                                  self.GetConfigEntry("RecentProjects", []))
        except:
            recent_projects = []
        if projectpath in recent_projects:
            recent_projects.remove(projectpath)
        if not err:
            recent_projects.insert(0, projectpath)
        self.Config.Write("RecentProjects", cPickle.dumps(
            map(EncodeFileSystemPath, recent_projects[:MAX_RECENT_PROJECTS])))
        self.Config.Flush()

    def ResetPerspective(self):
        IDEFrame.ResetPerspective(self)
        self.RefreshStatusToolBar()

    def OnNewProjectMenu(self, event):
        if self.CTR is not None and not self.CheckSaveBeforeClosing():
            return

        try:
            defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
        except:
            defaultpath = os.path.expanduser("~")

        dialog = wx.DirDialog(self , _("Choose a project"), defaultpath)
        if dialog.ShowModal() == wx.ID_OK:
            projectpath = dialog.GetPath()
            self.Config.Write("lastopenedfolder",
                              EncodeFileSystemPath(os.path.dirname(projectpath)))
            self.Config.Flush()
            self.ResetView()
            ctr = ProjectController(self, self.Log)
            result = ctr.NewProject(projectpath)
            if not result:
                self.CTR = ctr
                self.Controler = self.CTR
                self.LibraryPanel.SetController(self.Controler)
                self.ProjectTree.Enable(True)
                self.PouInstanceVariablesPanel.SetController(self.Controler)
                self.RefreshConfigRecentProjects(projectpath)
                if self.EnableDebug:
                    self.DebugVariablePanel.SetDataProducer(self.CTR)
                self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
            else:
                self.ResetView()
                self.ShowErrorMessage(result)
            self.RefreshAll()
            self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
        dialog.Destroy()

    def OnOpenProjectMenu(self, event):
        if self.CTR is not None and not self.CheckSaveBeforeClosing():
            return

        try:
            defaultpath = DecodeFileSystemPath(self.Config.Read("lastopenedfolder"))
        except:
            defaultpath = os.path.expanduser("~")

        dialog = wx.DirDialog(self , _("Choose a project"), defaultpath, style=wx.DEFAULT_DIALOG_STYLE|
                                                                               wx.RESIZE_BORDER)
        if dialog.ShowModal() == wx.ID_OK:
            self.OpenProject(dialog.GetPath())
        dialog.Destroy()

    def OpenProject(self, projectpath):
        if os.path.isdir(projectpath):
            self.Config.Write("lastopenedfolder",
                              EncodeFileSystemPath(os.path.dirname(projectpath)))
            self.Config.Flush()
            self.ResetView()
            self.CTR = ProjectController(self, self.Log)
            self.Controler = self.CTR
            result, err = self.CTR.LoadProject(projectpath)
            if not result:
                self.LibraryPanel.SetController(self.Controler)
                self.ProjectTree.Enable(True)
                self.PouInstanceVariablesPanel.SetController(self.Controler)
                if self.EnableDebug:
                    self.DebugVariablePanel.SetDataProducer(self.CTR)
                self._Refresh(PROJECTTREE, POUINSTANCEVARIABLESPANEL, LIBRARYTREE)
            else:
                self.ResetView()
                self.ShowErrorMessage(result)
            self.RefreshAll()
            self.SearchResultPanel.ResetSearchResults()
        else:
            self.ShowErrorMessage(_("\"%s\" folder is not a valid Beremiz project\n") % projectpath)
            err = True
        self.RefreshConfigRecentProjects(projectpath, err)
        self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)

    def OnCloseProjectMenu(self, event):
        if self.CTR is not None and not self.CheckSaveBeforeClosing():
            return

        self.ResetView()
        self._Refresh(TITLE, EDITORTOOLBAR, FILEMENU, EDITMENU)
        self.RefreshAll()

    def OnSaveProjectMenu(self, event):
        selected = self.TabsOpened.GetSelection()
        if selected != -1:
            window = self.TabsOpened.GetPage(selected)
            window.Save()
        if self.CTR is not None:
            self.CTR.SaveProject()
            self.RefreshAll()
            self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)

    def OnSaveProjectAsMenu(self, event):
        selected = self.TabsOpened.GetSelection()
        if selected != -1:
            window = self.TabsOpened.GetPage(selected)
            window.SaveAs()
        if self.CTR is not None:
            self.CTR.SaveProjectAs()
            self.RefreshAll()
            self.RefreshConfigRecentProjects(self.CTR.ProjectPath)
            self._Refresh(TITLE, FILEMENU, EDITMENU, PAGETITLES)

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

    def OnAboutMenu(self, event):
        info = version.GetAboutDialogInfo()        
        ShowAboutDialog(self, info)

    def OnProjectTreeItemBeginEdit(self, event):
        selected = event.GetItem()
        if self.ProjectTree.GetPyData(selected)["type"] == ITEM_CONFNODE:
            event.Veto()
        else:
            IDEFrame.OnProjectTreeItemBeginEdit(self, event)

    def OnProjectTreeRightUp(self, event):
        item = event.GetItem()
        item_infos = self.ProjectTree.GetPyData(item)

        if item_infos["type"] == ITEM_CONFNODE:
            confnode_menu = wx.Menu(title='')

            confnode = item_infos["confnode"]
            if confnode is not None:
                menu_items = confnode.GetContextualMenuItems()
                if menu_items is not None:
                    for text, help, callback in menu_items:
                        new_id = wx.NewId()
                        confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=text)
                        self.Bind(wx.EVT_MENU, callback, id=new_id)
                else:
                    for name, XSDClass, help in confnode.CTNChildrenTypes:
                        new_id = wx.NewId()
                        confnode_menu.Append(help=help, id=new_id, kind=wx.ITEM_NORMAL, text=_("Add") + " " + name)
                        self.Bind(wx.EVT_MENU, self.GetAddConfNodeFunction(name, confnode), id=new_id)

            new_id = wx.NewId()
            AppendMenu(confnode_menu, help='', id=new_id, kind=wx.ITEM_NORMAL, text=_("Delete"))
            self.Bind(wx.EVT_MENU, self.GetDeleteMenuFunction(confnode), id=new_id)

            self.PopupMenu(confnode_menu)
            confnode_menu.Destroy()

            event.Skip()
        elif item_infos["type"] == ITEM_RESOURCE:
            # prevent last resource to be delted
            parent = self.ProjectTree.GetItemParent(item)
            parent_name = self.ProjectTree.GetItemText(parent)
            if parent_name == _("Resources"):
                IDEFrame.OnProjectTreeRightUp(self, event)
        else:
            IDEFrame.OnProjectTreeRightUp(self, event)

    def OnProjectTreeItemActivated(self, event):
        selected = event.GetItem()
        name = self.ProjectTree.GetItemText(selected)
        item_infos = self.ProjectTree.GetPyData(selected)
        if item_infos["type"] == ITEM_CONFNODE:
            item_infos["confnode"]._OpenView()
            event.Skip()
        elif item_infos["type"] == ITEM_PROJECT:
            self.CTR._OpenView()
        else:
            IDEFrame.OnProjectTreeItemActivated(self, event)

    def ProjectTreeItemSelect(self, select_item):
        if select_item is not None and select_item.IsOk():
            name = self.ProjectTree.GetItemText(select_item)
            item_infos = self.ProjectTree.GetPyData(select_item)
            if item_infos["type"] == ITEM_CONFNODE:
                item_infos["confnode"]._OpenView(onlyopened=True)
            elif item_infos["type"] == ITEM_PROJECT:
                self.CTR._OpenView(onlyopened=True)
            else:
                IDEFrame.ProjectTreeItemSelect(self, select_item)

    def SelectProjectTreeItem(self, tagname):
        if self.ProjectTree is not None:
            root = self.ProjectTree.GetRootItem()
            if root.IsOk():
                words = tagname.split("::")
                if len(words) == 1:
                    if tagname == "Project":
                        self.SelectedItem = root
                        self.ProjectTree.SelectItem(root)
                        self.ResetSelectedItem()
                    else:
                        return self.RecursiveProjectTreeItemSelection(root,
                              [(word, ITEM_CONFNODE) for word in tagname.split(".")])
                elif words[0] == "R":
                    return self.RecursiveProjectTreeItemSelection(root, [(words[2], ITEM_RESOURCE)])
                elif not os.path.exists(words[0]):
                    IDEFrame.SelectProjectTreeItem(self, tagname)

    def GetAddConfNodeFunction(self, name, confnode=None):
        def AddConfNodeMenuFunction(event):
            wx.CallAfter(self.AddConfNode, name, confnode)
        return AddConfNodeMenuFunction

    def GetDeleteMenuFunction(self, confnode):
        def DeleteMenuFunction(event):
            wx.CallAfter(self.DeleteConfNode, confnode)
        return DeleteMenuFunction

    def AddConfNode(self, ConfNodeType, confnode=None):
        if self.CTR.CheckProjectPathPerm():
            ConfNodeName = "%s_0" % ConfNodeType
            if confnode is not None:
                confnode.CTNAddChild(ConfNodeName, ConfNodeType)
            else:
                self.CTR.CTNAddChild(ConfNodeName, ConfNodeType)
            self._Refresh(TITLE, FILEMENU, PROJECTTREE)

    def DeleteConfNode(self, confnode):
        if self.CTR.CheckProjectPathPerm():
            dialog = wx.MessageDialog(self,
                _("Really delete node '%s'?") % confnode.CTNName(),
                _("Remove %s node") % confnode.CTNType,
                wx.YES_NO|wx.NO_DEFAULT)
            if dialog.ShowModal() == wx.ID_YES:
                confnode.CTNRemove()
                del confnode
                self._Refresh(TITLE, FILEMENU, PROJECTTREE)
            dialog.Destroy()

#-------------------------------------------------------------------------------
#                        Highlights showing functions
#-------------------------------------------------------------------------------

    def ShowHighlight(self, infos, start, end, highlight_type):
        config_name = self.Controler.GetProjectMainConfigurationName()
        if config_name is not None and infos[0] == self.Controler.ComputeConfigurationName(config_name):
            self.CTR._OpenView()
            selected = self.TabsOpened.GetSelection()
            if selected != -1:
                viewer = self.TabsOpened.GetPage(selected)
                viewer.AddHighlight(infos[1:], start, end, highlight_type)
        else:
            IDEFrame.ShowHighlight(self, infos, start, end, highlight_type)

#-------------------------------------------------------------------------------
#                               Exception Handler
#-------------------------------------------------------------------------------
import threading, traceback

Max_Traceback_List_Size = 20

def Display_Exception_Dialog(e_type, e_value, e_tb, bug_report_path):
    trcbck_lst = []
    for i,line in enumerate(traceback.extract_tb(e_tb)):
        trcbck = " " + str(i+1) + ". "
        if line[0].find(os.getcwd()) == -1:
            trcbck += "file : " + str(line[0]) + ",   "
        else:
            trcbck += "file : " + str(line[0][len(os.getcwd()):]) + ",   "
        trcbck += "line : " + str(line[1]) + ",   " + "function : " + str(line[2])
        trcbck_lst.append(trcbck)

    # Allow clicking....
    cap = wx.Window_GetCapture()
    if cap:
        cap.ReleaseMouse()

    dlg = wx.SingleChoiceDialog(None,
        _("""
An unhandled exception (bug) occured. Bug report saved at :
(%s)

Please be kind enough to send this file to:
beremiz-devel@lists.sourceforge.net

You should now restart program.

Traceback:
""") % bug_report_path +
        repr(e_type) + " : " + repr(e_value),
        _("Error"),
        trcbck_lst)
    try:
        res = (dlg.ShowModal() == wx.ID_OK)
    finally:
        dlg.Destroy()

    return res

def get_last_traceback(tb):
    while tb.tb_next:
        tb = tb.tb_next
    return tb


def format_namespace(d, indent='    '):
    return '\n'.join(['%s%s: %s' % (indent, k, repr(v)[:10000]) for k, v in d.iteritems()])


ignored_exceptions = [] # a problem with a line in a module is only reported once per session

def AddExceptHook(path, app_version='[No version]'):#, ignored_exceptions=[]):

    def save_bug_report(e_type, e_value, e_traceback, bug_report_path,date):
        info = {
            'app-title': wx.GetApp().GetAppName(),  # app_title
            'app-version': app_version,
            'wx-version': wx.VERSION_STRING,
            'wx-platform': wx.Platform,
            'python-version': platform.python_version(),  # sys.version.split()[0],
            'platform': platform.platform(),
            'e-type': e_type,
            'e-value': e_value,
            'date': date,
            'cwd': os.getcwd(),
        }
        if e_traceback:
            info['traceback'] = ''.join(traceback.format_tb(e_traceback)) + '%s: %s' % (e_type, e_value)
            last_tb = get_last_traceback(e_traceback)
            exception_locals = last_tb.tb_frame.f_locals  # the locals at the level of the stack trace where the exception actually occurred
            info['locals'] = format_namespace(exception_locals)
            if 'self' in exception_locals:
                try:
                    info['self'] = format_namespace(exception_locals['self'].__dict__)
                except:
                    pass
        if not os.path.exists(path):
            os.mkdir(path)
        output = open(bug_report_path, 'w')
        lst = info.keys()
        lst.sort()
        for a in lst:
            output.write(a + ":\n" + str(info[a]) + "\n\n")
        output.close()

    def handle_exception(e_type, e_value, e_traceback):
        traceback.print_exception(e_type, e_value, e_traceback) # this is very helpful when there's an exception in the rest of this func
        last_tb = get_last_traceback(e_traceback)
        ex = (last_tb.tb_frame.f_code.co_filename, last_tb.tb_frame.f_lineno)
        if ex not in ignored_exceptions:
            ignored_exceptions.append(ex)
            date = time.ctime()
            bug_report_path = path + os.sep + "bug_report_" + date.replace(':', '-').replace(' ', '_') + ".txt"
            save_bug_report(e_type, e_value, e_traceback, bug_report_path, date)
            Display_Exception_Dialog(e_type, e_value, e_traceback, bug_report_path)
    #sys.excepthook = lambda *args: wx.CallAfter(handle_exception, *args)
    sys.excepthook = handle_exception

    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init