# application.py: Application class to store properties of the application # being created # $Id: application.py,v 1.64 2007/08/07 12:13:44 agriggio Exp $ # # Copyright (c) 2002-2007 Alberto Griggio # License: MIT (see license.txt) # THIS PROGRAM COMES WITH NO WARRANTY import wx from widget_properties import * from tree import Tree, WidgetTree import common, math, misc, os, config import traceback, re class FileDirDialog: """\ Custom class which displays a FileDialog or a DirDialog, according to the value of the codegen_opt of its parent (instance of Application) """ def __init__(self, owner, parent, wildcard="All Files|*", file_message="Choose a file", dir_message=None, style=0): self.owner = owner self.prev_dir = config.preferences.codegen_path self.wildcard = wildcard self.file_message = file_message self.dir_message = dir_message self.file_style = style self.dir_style = wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON self.parent = parent self.value = None def ShowModal(self): if self.owner.codegen_opt == 0: self.value = misc.FileSelector( self.file_message, self.prev_dir or "", wildcard=self.wildcard, flags=self.file_style) else: self.value = misc.DirSelector( self.dir_message, self.prev_dir or "", style=self.dir_style) if self.value: self.prev_dir = self.value if not os.path.isdir(self.prev_dir): self.prev_dir = os.path.dirname(self.prev_dir) return wx.ID_OK return wx.ID_CANCEL def get_value(self): return self.value def set_wildcard(self, wildcard): self.wildcard = wildcard # end of class FileDirDialog class Application(object): """\ properties of the application being created """ def __init__(self, property_window): self.property_window = property_window self.notebook = wx.Notebook(self.property_window, -1) if not misc.check_wx_version(2, 5, 2): nb_sizer = wx.NotebookSizer(self.notebook) self.notebook.sizer = nb_sizer else: self.notebook.sizer = None self.notebook.SetAutoLayout(True) self.notebook.Hide() panel = wx.ScrolledWindow( self.notebook, -1, style=wx.TAB_TRAVERSAL|wx.FULL_REPAINT_ON_RESIZE) self.name = "app" # name of the wxApp instance to generate self.__saved = True # if True, there are no changes to save self.__filename = None # name of the output xml file self.klass = "MyApp" self.codegen_opt = 0 # if != 0, generates a separate file # for each class def set_codegen_opt(value): try: opt = int(value) except ValueError: pass else: self.codegen_opt = opt self.output_path = "" self.language = 'python' # output language def get_output_path(): return os.path.expanduser(self.output_path) def set_output_path(value): self.output_path = value self.is_template = False self.use_gettext = False def set_use_gettext(value): self.use_gettext = bool(int(value)) self.for_version = wx.VERSION_STRING[:3] def set_for_version(value): self.for_version = self.for_version_prop.get_str_value() self.access_functions = { 'name': (lambda : self.name, self.set_name), 'class': (lambda : self.klass, self.set_klass), 'code_generation': (lambda : self.codegen_opt, set_codegen_opt), 'output_path': (get_output_path, set_output_path), 'language': (self.get_language, self.set_language), 'encoding': (self.get_encoding, self.set_encoding), 'use_gettext': (lambda : self.use_gettext, set_use_gettext), 'for_version': (lambda : self.for_version, set_for_version), } self.name_prop = TextProperty(self, "name", panel, True) self.klass_prop = TextProperty(self, "class", panel, True) self.encoding = self._get_default_encoding() self.encoding_prop = TextProperty(self, 'encoding', panel) self.use_gettext_prop = CheckBoxProperty(self, "use_gettext", panel, "Enable gettext support") TOP_WIN_ID = wx.NewId() self.top_win_prop = wx.Choice(panel, TOP_WIN_ID, choices=[], size=(1, -1)) self.top_window = '' # name of the top window of the generated app self.codegen_prop = RadioProperty(self, "code_generation", panel, ["Single file", "Separate file for" \ " each class"]) ext = getattr(common.code_writers.get('python'), 'default_extensions', []) wildcard = [] for e in ext: wildcard.append('%s files (*.%s)|*.%s' % ('Python', e, e)) wildcard.append('All files|*') dialog = FileDirDialog(self, panel, '|'.join(wildcard), "Select output file", "Select output directory", wx.SAVE|wx.OVERWRITE_PROMPT) _writers = common.code_writers.keys() columns = 3 self.codewriters_prop = RadioProperty(self, "language", panel, _writers, columns=columns) self.codewriters_prop.set_str_value('python') self.for_version_prop = RadioProperty(self, "for_version", panel, ['2.4', '2.6', '2.8'], columns=3, label="wxWidgets compatibility") self.for_version_prop.set_str_value(self.for_version) # ALB 2004-01-18 self.access_functions['use_new_namespace'] = ( self.get_use_old_namespace, self.set_use_old_namespace) self.use_old_namespace_prop = CheckBoxProperty( self, 'use_new_namespace', panel, 'Use old "from wxPython.wx"\n' 'import (python output only)') # `overwrite' property - added 2003-07-15 self.overwrite = False def get_overwrite(): return self.overwrite def set_overwrite(val): self.overwrite = bool(int(val)) self.access_functions['overwrite'] = (get_overwrite, set_overwrite) self.overwrite_prop = CheckBoxProperty(self, 'overwrite', panel, 'Overwrite existing sources') self.outpath_prop = DialogProperty(self, "output_path", panel, dialog) BTN_ID = wx.NewId() btn = wx.Button(panel, BTN_ID, "Generate code") # layout of self.notebook sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.name_prop.panel, 0, wx.EXPAND) sizer.Add(self.klass_prop.panel, 0, wx.EXPAND) sizer.Add(self.encoding_prop.panel, 0, wx.EXPAND) sizer.Add(self.use_gettext_prop.panel, 0, wx.EXPAND) szr = wx.BoxSizer(wx.HORIZONTAL) from widget_properties import _label_initial_width as _w label = wx.StaticText(panel, -1, "Top window", size=(_w, -1)) label.SetToolTip(wx.ToolTip("Top window")) szr.Add(label, 2, wx.ALL|wx.ALIGN_CENTER, 3) szr.Add(self.top_win_prop, 5, wx.ALL|wx.ALIGN_CENTER, 3) sizer.Add(szr, 0, wx.EXPAND) sizer.Add(self.codegen_prop.panel, 0, wx.ALL|wx.EXPAND, 4) sizer.Add(self.codewriters_prop.panel, 0, wx.ALL|wx.EXPAND, 4) sizer.Add(self.for_version_prop.panel, 0, wx.ALL|wx.EXPAND, 4) sizer.Add(self.use_old_namespace_prop.panel, 0, wx.EXPAND) sizer.Add(self.overwrite_prop.panel, 0, wx.EXPAND) sizer.Add(self.outpath_prop.panel, 0, wx.EXPAND) sizer.Add(btn, 0, wx.ALL|wx.EXPAND, 5) panel.SetAutoLayout(True) panel.SetSizer(sizer) sizer.Layout() sizer.Fit(panel) h = panel.GetSize()[1] self.notebook.AddPage(panel, "Application") import math panel.SetScrollbars(1, 5, 1, int(math.ceil(h/5.0))) wx.EVT_BUTTON(btn, BTN_ID, self.generate_code) wx.EVT_CHOICE(self.top_win_prop, TOP_WIN_ID, self.set_top_window) # this is here to keep the interface similar to the various widgets # (to simplify Tree) self.widget = None # this is always None def set_name(self, value): value = "%s" % value if not re.match(self.set_name_pattern, value): self.name_prop.set_value(self.name) else: self.name = value set_name_pattern = re.compile('^[a-zA-Z]+[\w0-9-]*$') def set_klass(self, value): value = "%s" % value if not re.match(self.set_klass_pattern, value): self.klass_prop.set_value(self.klass) else: self.klass = value set_klass_pattern = re.compile('^[a-zA-Z]+[\w:.0-9-]*$') def _get_default_encoding(self): """\ Returns the name of the default character encoding of this machine """ import locale locale.setlocale(locale.LC_ALL) try: return locale.nl_langinfo(locale.CODESET) except AttributeError: return 'ISO-8859-15' # this is what I use... def get_encoding(self): return self.encoding def set_encoding(self, value): try: unicode('a', value) except LookupError, e: wx.MessageBox(str(e), "Error", wx.OK|wx.CENTRE|wx.ICON_ERROR) self.encoding_prop.set_value(self.encoding) else: self.encoding = value def set_language(self, value): language = self.codewriters_prop.get_str_value() ext = getattr(common.code_writers[language], 'default_extensions', []) wildcard = [] for e in ext: wildcard.append('%s files (*.%s)|*.%s' % (language.capitalize(), e, e)) wildcard.append('All files|*') self.outpath_prop.dialog.set_wildcard('|'.join(wildcard)) # check that the new language supports all the widgets in the tree if self.language != language: self.language = language self.check_codegen() def get_language(self): return self.language #codewriters_prop.get_str_value() def _get_saved(self): return self.__saved def _set_saved(self, value): if self.__saved != value: self.__saved = value t = common.app_tree.get_title().strip() if not value: common.app_tree.set_title('* ' + t) else: if t[0] == '*': common.app_tree.set_title(t[1:].strip()) saved = property(_get_saved, _set_saved) def _get_filename(self): return self.__filename def _set_filename(self, value): if not misc.streq(self.__filename, value): self.__filename = value if self.__saved: flag = ' ' else: flag = '* ' if self.__filename is not None: common.app_tree.set_title('%s(%s)' % (flag, self.__filename)) else: common.app_tree.set_title(flag) filename = property(_get_filename, _set_filename) def get_top_window(self): return self.top_window def set_top_window(self, *args): self.top_window = self.top_win_prop.GetStringSelection() def add_top_window(self, name): self.top_win_prop.Append("%s" % name) if not self.top_window: self.top_win_prop.SetSelection(self.top_win_prop.GetCount()-1) self.set_top_window() def remove_top_window(self, name): index = self.top_win_prop.FindString("%s" % name) if index != -1: if wx.Platform == '__WXGTK__': choices = [ self.top_win_prop.GetString(i) for i in \ range(self.top_win_prop.GetCount()) if i != index ] self.top_win_prop.Clear() for c in choices: self.top_win_prop.Append(c) else: self.top_win_prop.Delete(index) def update_top_window_name(self, oldname, newname): index = self.top_win_prop.FindString(oldname) if index != -1: if self.top_window == oldname: self.top_window = newname if wx.Platform == '__WXGTK__': sel_index = self.top_win_prop.GetSelection() choices = [ self.top_win_prop.GetString(i) for i in \ range(self.top_win_prop.GetCount()) ] choices[index] = newname self.top_win_prop.Clear() for c in choices: self.top_win_prop.Append(c) self.top_win_prop.SetSelection(sel_index) else: self.top_win_prop.SetString(index, newname) def reset(self): """\ resets the default values of the attributes of the app """ self.klass = "MyApp"; self.klass_prop.set_value("MyApp") self.klass_prop.toggle_active(False) self.name = "app"; self.name_prop.set_value("app") self.name_prop.toggle_active(False) self.codegen_opt = 0; self.codegen_prop.set_value(0) self.output_path = ""; self.outpath_prop.set_value("") # do not reset language, but call set_language anyway to update the # wildcard of the file dialog self.set_language(self.get_language()) self.top_window = '' self.top_win_prop.Clear() # ALB 2004-01-18 #self.set_use_new_namespace(True) #self.use_new_namespace_prop.set_value(True) self.set_use_old_namespace(False) self.use_old_namespace_prop.set_value(False) def show_properties(self, *args): sizer_tmp = self.property_window.GetSizer() child = sizer_tmp.GetChildren()[0] w = child.GetWindow() if w is self.notebook: return w.Hide() self.notebook.Reparent(self.property_window) child.SetWindow(self.notebook) w.Reparent(misc.hidden_property_panel) self.notebook.Show(True) self.property_window.Layout() self.property_window.SetTitle('Properties - <%s>' % self.name) try: common.app_tree.select_item(self.node) except AttributeError: pass def __getitem__(self, name): return self.access_functions[name] def generate_code(self, *args, **kwds): preview = kwds.get('preview', False) if not self.output_path: return wx.MessageBox("You must specify an output file\n" "before generating any code", "Error", wx.OK|wx.CENTRE|wx.ICON_EXCLAMATION, self.notebook) if not preview and \ ((self.name_prop.is_active() or self.klass_prop.is_active()) \ and self.top_win_prop.GetSelection() < 0): return wx.MessageBox("Please select a top window " "for the application", "Error", wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION, self.notebook) from cStringIO import StringIO out = StringIO() #common.app_tree.write(out) # write the xml onto a temporary buffer from xml_parse import CodeWriter try: # generate the code from the xml buffer cw = self.get_language() #self.codewriters_prop.get_str_value() if preview and cw == 'python': # of course cw == 'python', but... old = common.code_writers[cw].use_new_namespace common.code_writers[cw].use_new_namespace = True #False overwrite = self.overwrite self.overwrite = True class_names = common.app_tree.write(out) # write the xml onto a # temporary buffer if not os.path.isabs(self.output_path) and \ self.filename is not None: out_path = os.path.join(os.path.dirname(self.filename), self.output_path) else: out_path = None CodeWriter(common.code_writers[cw], out.getvalue(), True, preview=preview, out_path=out_path, class_names=class_names) if preview and cw == 'python': common.code_writers[cw].use_new_namespace = old self.overwrite = overwrite except (IOError, OSError), msg: wx.MessageBox("Error generating code:\n%s" % msg, "Error", wx.OK|wx.CENTRE|wx.ICON_ERROR) except Exception, msg: import traceback; traceback.print_exc() wx.MessageBox("An exception occurred while generating the code " "for the application.\n" "This is the error message associated with it:\n" " %s\n" "For more details, look at the full traceback " "on the console.\nIf you think this is a wxGlade bug," " please report it." % msg, "Error", wx.OK|wx.CENTRE|wx.ICON_ERROR) else: if not preview: wx.MessageBox("Code generation completed successfully", "Information", wx.OK|wx.CENTRE|wx.ICON_INFORMATION) def get_name(self): if self.name_prop.is_active(): return self.name return '' def get_class(self): if self.klass_prop.is_active(): return self.klass return '' def update_view(self, *args): pass def is_visible(self): return True def preview(self, widget, out_name=[None]): if out_name[0] is None: import warnings warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, "application") out_name[0] = os.tempnam(None, 'wxg') + '.py' #print 'Temporary name:', out_name[0] widget_class_name = widget.klass # make a valid name for the class (this can be invalid for # some sensible reasons...) widget.klass = widget.klass[widget.klass.rfind('.')+1:] widget.klass = widget.klass[widget.klass.rfind(':')+1:] #if widget.klass == widget.base: # ALB 2003-11-08: always randomize the class name: this is to make # preview work even when there are multiple classes with the same name # (which makes sense for XRC output...) import random widget.klass = '_%d_%s' % \ (random.randrange(10**8, 10**9), widget.klass) self.real_output_path = self.output_path self.output_path = out_name[0] real_codegen_opt = self.codegen_opt real_language = self.language real_use_gettext = self.use_gettext self.use_gettext = False self.language = 'python' self.codegen_opt = 0 overwrite = self.overwrite self.overwrite = 0 frame = None try: self.generate_code(preview=True) # dynamically import the generated module FrameClass = misc.import_name(self.output_path, widget.klass) if issubclass(FrameClass, wx.MDIChildFrame): frame = wx.MDIParentFrame(None, -1, '') child = FrameClass(frame, -1, '') child.SetTitle(' - ' + child.GetTitle()) w, h = child.GetSize() frame.SetClientSize((w+20, h+20)) elif not (issubclass(FrameClass, wx.Frame) or issubclass(FrameClass, wx.Dialog)): # the toplevel class isn't really toplevel, add a frame... frame = wx.Frame(None, -1, widget_class_name) if issubclass(FrameClass, wx.MenuBar): menubar = FrameClass() frame.SetMenuBar(menubar) elif issubclass(FrameClass, wx.ToolBar): toolbar = FrameClass(frame, -1) frame.SetToolBar(toolbar) else: panel = FrameClass(frame, -1) frame.Fit() else: frame = FrameClass(None, -1, '') # make sure we don't get a modal dialog... s = frame.GetWindowStyleFlag() frame.SetWindowStyleFlag(s & ~wx.DIALOG_MODAL) def on_close(event): frame.Destroy() widget.preview_widget = None widget.preview_button.SetLabel('Preview') wx.EVT_CLOSE(frame, on_close) frame.SetTitle(' - %s' % frame.GetTitle()) # raise the frame frame.CenterOnScreen() frame.Show() # remove the temporary file (and the .pyc/.pyo ones too) for ext in '', 'c', 'o', '~': name = self.output_path + ext if os.path.isfile(name): os.unlink(name) except Exception, e: #traceback.print_exc() widget.preview_widget = None widget.preview_button.SetLabel('Preview') wx.MessageBox("Problem previewing gui: %s" % str(e), "Error", wx.OK|wx.CENTRE|wx.ICON_EXCLAMATION)#, self.notebook) # restore app state widget.klass = widget_class_name self.output_path = self.real_output_path del self.real_output_path self.codegen_opt = real_codegen_opt self.language = real_language self.use_gettext = real_use_gettext self.overwrite = overwrite return frame def get_use_old_namespace(self): try: return not common.code_writers['python'].use_new_namespace except: return False def set_use_old_namespace(self, val): #print "set use old namespace" try: common.code_writers['python'].use_new_namespace = not bool(int(val)) except: pass def check_codegen(self, widget=None, language=None): """\ Checks whether widget has a suitable code generator for the given language (default: the current active language). If not, the user is informed with a message. """ if language is None: language = self.language if widget is not None: cname = common.class_names[widget.__class__.__name__] if language != 'XRC': ok = cname in common.code_writers[language].obj_builders else: # xrc is special... xrcgen = common.code_writers['XRC'] ok = xrcgen.obj_builders.get(cname, None) is not \ xrcgen.NotImplementedXrcObject if not ok: common.message('WARNING', 'No %s code generator for %s (of type %s)' ' available', language.capitalize(), widget.name, cname) else: # in this case, we check all the widgets in the tree def check_rec(node): if node.widget is not None: self.check_codegen(node.widget) if node.children: for c in node.children: check_rec(c) if common.app_tree.root.children: for c in common.app_tree.root.children: check_rec(c) # end of class Application