From 676be70ebc00b0ae33fbe237f9a67525c553aa5b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 6 May 2010 07:27:59 -0400 Subject: [PATCH] Beginning to rewrite Hooke to decouple from GUI. This commit: * Started documenting with reStructuredText (for later use by Sphinx) * Added hooke.config module to handle configure files (via ConfigParser) * Added hooke.compat module to hold compatibility code (e.g. workarounds to allow Hooke to work on older Python versions). * Added config input/output to hooke.hooke.Hooke. * Stubbed out driver/plugin initialization in Hooke --- hooke/compat/odict.py | 336 ++++++++++++++++++++++++++++++++++++ hooke/config.py | 385 ++++++++++++++++++++++++++---------------- hooke/hooke.py | 188 +++++---------------- 3 files changed, 619 insertions(+), 290 deletions(-) create mode 100644 hooke/compat/odict.py diff --git a/hooke/compat/odict.py b/hooke/compat/odict.py new file mode 100644 index 0000000..22de85c --- /dev/null +++ b/hooke/compat/odict.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- + +# An ordered dictionary implementation. OrderedDict entered the +# Python Standard Library in Python 2.7 and 3.1. See `PEP 372`_ for +# details. Code_ by Armin Ronacher and the PEP 273 authors. +# +# .. _PEP 372: http://www.python.org/dev/peps/pep-0372/ +# .. _Code: http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py + +""" + odict + ~~~~~ + + This module is an example implementation of an ordered dict for the + collections module. It's not written for performance (it actually + performs pretty bad) but to show how the API works. + + + Questions and Answers + ===================== + + Why would anyone need ordered dicts? + + Dicts in python are unordered which means that the order of items when + iterating over dicts is undefined. As a matter of fact it is most of + the time useless and differs from implementation to implementation. + + Many developers stumble upon that problem sooner or later when + comparing the output of doctests which often does not match the order + the developer thought it would. + + Also XML systems such as Genshi have their problems with unordered + dicts as the input and output ordering of tag attributes is often + mixed up because the ordering is lost when converting the data into + a dict. Switching to lists is often not possible because the + complexity of a lookup is too high. + + Another very common case is metaprogramming. The default namespace + of a class in python is a dict. With Python 3 it becomes possible + to replace it with a different object which could be an ordered dict. + Django is already doing something similar with a hack that assigns + numbers to some descriptors initialized in the class body of a + specific subclass to restore the ordering after class creation. + + When porting code from programming languages such as PHP and Ruby + where the item-order in a dict is guaranteed it's also a great help + to have an equivalent data structure in Python to ease the transition. + + Where are new keys added? + + At the end. This behavior is consistent with Ruby 1.9 Hashmaps + and PHP Arrays. It also matches what common ordered dict + implementations do currently. + + What happens if an existing key is reassigned? + + The key is *not* moved. This is consitent with existing + implementations and can be changed by a subclass very easily:: + + class movingodict(odict): + def __setitem__(self, key, value): + self.pop(key, None) + odict.__setitem__(self, key, value) + + Moving keys to the end of a ordered dict on reassignment is not + very useful for most applications. + + Does it mean the dict keys are sorted by a sort expression? + + That's not the case. The odict only guarantees that there is an order + and that newly inserted keys are inserted at the end of the dict. If + you want to sort it you can do so, but newly added keys are again added + at the end of the dict. + + I initializes the odict with a dict literal but the keys are not + ordered like they should! + + Dict literals in Python generate dict objects and as such the order of + their items is not guaranteed. Before they are passed to the odict + constructor they are already unordered. + + What happens if keys appear multiple times in the list passed to the + constructor? + + The same as for the dict. The latter item overrides the former. This + has the side-effect that the position of the first key is used because + the key is actually overwritten: + + >>> odict([('a', 1), ('b', 2), ('a', 3)]) + odict.odict([('a', 3), ('b', 2)]) + + This behavor is consistent with existing implementation in Python + and the PHP array and the hashmap in Ruby 1.9. + + This odict doesn't scale! + + Yes it doesn't. The delitem operation is O(n). This is file is a + mockup of a real odict that could be implemented for collections + based on an linked list. + + Why is there no .insert()? + + There are few situations where you really want to insert a key at + an specified index. To now make the API too complex the proposed + solution for this situation is creating a list of items, manipulating + that and converting it back into an odict: + + >>> d = odict([('a', 42), ('b', 23), ('c', 19)]) + >>> l = d.items() + >>> l.insert(1, ('x', 0)) + >>> odict(l) + odict.odict([('a', 42), ('x', 0), ('b', 23), ('c', 19)]) + + :copyright: (c) 2008 by Armin Ronacher and PEP 273 authors. + :license: modified BSD license. +""" +from itertools import izip, imap +from copy import deepcopy + +missing = object() + + +class odict(dict): + """ + Ordered dict example implementation. + + This is the proposed interface for a an ordered dict as proposed on the + Python mailinglist (proposal_). + + It's a dict subclass and provides some list functions. The implementation + of this class is inspired by the implementation of Babel but incorporates + some ideas from the `ordereddict`_ and Django's ordered dict. + + The constructor and `update()` both accept iterables of tuples as well as + mappings: + + >>> d = odict([('a', 'b'), ('c', 'd')]) + >>> d.update({'foo': 'bar'}) + >>> d + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')]) + + Keep in mind that when updating from dict-literals the order is not + preserved as these dicts are unsorted! + + You can copy an odict like a dict by using the constructor, `copy.copy` + or the `copy` method and make deep copies with `copy.deepcopy`: + + >>> from copy import copy, deepcopy + >>> copy(d) + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')]) + >>> d.copy() + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')]) + >>> odict(d) + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')]) + >>> d['spam'] = [] + >>> d2 = deepcopy(d) + >>> d2['spam'].append('eggs') + >>> d + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]) + >>> d2 + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', ['eggs'])]) + + All iteration methods as well as `keys`, `values` and `items` return + the values ordered by the the time the key-value pair is inserted: + + >>> d.keys() + ['a', 'c', 'foo', 'spam'] + >>> d.values() + ['b', 'd', 'bar', []] + >>> d.items() + [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])] + >>> list(d.iterkeys()) + ['a', 'c', 'foo', 'spam'] + >>> list(d.itervalues()) + ['b', 'd', 'bar', []] + >>> list(d.iteritems()) + [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])] + + Index based lookup is supported too by `byindex` which returns the + key/value pair for an index: + + >>> d.byindex(2) + ('foo', 'bar') + + You can reverse the odict as well: + + >>> d.reverse() + >>> d + odict.odict([('spam', []), ('foo', 'bar'), ('c', 'd'), ('a', 'b')]) + + And sort it like a list: + + >>> d.sort(key=lambda x: x[0].lower()) + >>> d + odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]) + + .. _proposal: http://thread.gmane.org/gmane.comp.python.devel/95316 + .. _ordereddict: http://www.xs4all.nl/~anthon/Python/ordereddict/ + """ + + def __init__(self, *args, **kwargs): + dict.__init__(self) + self._keys = [] + self.update(*args, **kwargs) + + def __delitem__(self, key): + dict.__delitem__(self, key) + self._keys.remove(key) + + def __setitem__(self, key, item): + if key not in self: + self._keys.append(key) + dict.__setitem__(self, key, item) + + def __deepcopy__(self, memo=None): + if memo is None: + memo = {} + d = memo.get(id(self), missing) + if d is not missing: + return d + memo[id(self)] = d = self.__class__() + dict.__init__(d, deepcopy(self.items(), memo)) + d._keys = self._keys[:] + return d + + def __getstate__(self): + return {'items': dict(self), 'keys': self._keys} + + def __setstate__(self, d): + self._keys = d['keys'] + dict.update(d['items']) + + def __reversed__(self): + return reversed(self._keys) + + def __eq__(self, other): + if isinstance(other, odict): + if not dict.__eq__(self, other): + return False + return self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __cmp__(self, other): + if isinstance(other, odict): + return cmp(self.items(), other.items()) + elif isinstance(other, dict): + return dict.__cmp__(self, other) + return NotImplemented + + @classmethod + def fromkeys(cls, iterable, default=None): + return cls((key, default) for key in iterable) + + def clear(self): + del self._keys[:] + dict.clear(self) + + def copy(self): + return self.__class__(self) + + def items(self): + return zip(self._keys, self.values()) + + def iteritems(self): + return izip(self._keys, self.itervalues()) + + def keys(self): + return self._keys[:] + + def iterkeys(self): + return iter(self._keys) + + def pop(self, key, default=missing): + if default is missing: + return dict.pop(self, key) + elif key not in self: + return default + self._keys.remove(key) + return dict.pop(self, key, default) + + def popitem(self, key): + self._keys.remove(key) + return dict.popitem(key) + + def setdefault(self, key, default=None): + if key not in self: + self._keys.append(key) + dict.setdefault(self, key, default) + + def update(self, *args, **kwargs): + sources = [] + if len(args) == 1: + if hasattr(args[0], 'iteritems'): + sources.append(args[0].iteritems()) + else: + sources.append(iter(args[0])) + elif args: + raise TypeError('expected at most one positional argument') + if kwargs: + sources.append(kwargs.iteritems()) + for iterable in sources: + for key, val in iterable: + self[key] = val + + def values(self): + return map(self.get, self._keys) + + def itervalues(self): + return imap(self.get, self._keys) + + def index(self, item): + return self._keys.index(item) + + def byindex(self, item): + key = self._keys[item] + return (key, dict.__getitem__(self, key)) + + def reverse(self): + self._keys.reverse() + + def sort(self, *args, **kwargs): + self._keys.sort(*args, **kwargs) + + def __repr__(self): + return 'odict.odict(%r)' % self.items() + + __copy__ = copy + __iter__ = iterkeys + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/hooke/config.py b/hooke/config.py index 75ea680..7f10c15 100644 --- a/hooke/config.py +++ b/hooke/config.py @@ -2,154 +2,247 @@ # -*- coding: utf-8 -*- ''' -libhooke.py +config.py -General library of internal objects and utilities for Hooke. +Configuration defaults, read/write, and template file creation for Hooke. -Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). -With algorithms contributed by Francesco Musiani (University of Bologna, Italy) - -This program is released under the GNU General Public License version 2. +COPYRIGHT ''' -import scipy -import numpy -import xml.dom.minidom -import os +import ConfigParser as configparser import os.path -import string - -#import ConfigParser - -def config_file_path(filename, config_dir=None): - if config_dir == None: - config_dir = os.path.abspath( - os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf')) - return os.path.join(config_dir, filename) - -class HookeConfig(object): - ''' - Handling of Hooke configuration file - - Mostly based on the simple-yet-useful examples of the Python Library Reference - about xml.dom.minidom - - FIXME: starting to look a mess, should require refactoring - ''' - - def __init__(self, config_dir=None): - self.config={} - self.config['install']={} - self.config['plugins']=[] - self.config['drivers']=[] - self.config['plotmanips']=[] - self.config_dir = config_dir - - def load_config(self, filename): - myconfig=file(config_file_path(filename, config_dir=self.config_dir)) - - #the following 3 lines are needed to strip newlines. otherwise, since newlines - #are XML elements too, the parser would read them (and re-save them, multiplying - #newlines...) - #yes, I'm an XML n00b - the_file=myconfig.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.config_tree=xml.dom.minidom.parseString(the_file) - - def getText(nodelist): - #take the text from a nodelist - #from Python Library Reference 13.7.2 - rc = '' - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc += node.data - return rc - - def handleConfig(config): - install_elements=config.getElementsByTagName("install") - display_elements=config.getElementsByTagName("display") - plugins_elements=config.getElementsByTagName("plugins") - drivers_elements=config.getElementsByTagName("drivers") - defaultlist_elements=config.getElementsByTagName("defaultlist") - plotmanip_elements=config.getElementsByTagName("plotmanips") - handleInstall(install_elements) - handleDisplay(display_elements) - handlePlugins(plugins_elements) - handleDrivers(drivers_elements) - handleDefaultlist(defaultlist_elements) - handlePlotmanip(plotmanip_elements) - - def handleInstall(install_elements): - for install in install_elements: - for node in install.childNodes: - if node.nodeType == node.TEXT_NODE: - continue - path = os.path.abspath(getText(node.childNodes).strip()) - self.config['install'][str(node.tagName)] = path - - def handleDisplay(display_elements): - for element in display_elements: - for attribute in element.attributes.keys(): - self.config[attribute]=element.getAttribute(attribute) - - def handlePlugins(plugins): - for plugin in plugins[0].childNodes: - try: - self.config['plugins'].append(str(plugin.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - #FIXME: code duplication - def handleDrivers(drivers): - for driver in drivers[0].childNodes: - try: - self.config['drivers'].append(str(driver.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handlePlotmanip(plotmanips): - for plotmanip in plotmanips[0].childNodes: - try: - self.config['plotmanips'].append(str(plotmanip.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handleDefaultlist(defaultlist): - ''' - default playlist - ''' - dflist=getText(defaultlist[0].childNodes) - self.config['defaultlist']=dflist.strip() - - handleConfig(self.config_tree) - #making items in the dictionary more machine-readable - for item in self.config.keys(): - try: - self.config[item]=float(self.config[item]) - except TypeError: #we are dealing with a list, probably. keep it this way. - try: - self.config[item]=eval(self.config[item]) - except: #not a list, not a tuple, probably a string? - pass - except ValueError: #if we can't get it to a number, it must be None or a string - if string.lower(self.config[item])=='none': - self.config[item]=None - else: - pass - - return self.config - - - def save_config(self, config_filename): - print 'Not Implemented.' - pass - - -def debug(): - ''' - debug stuff from latest rewrite of hooke_playlist.py - should be removed sooner or later (or substituted with new debug code!) - ''' - confo=HookeConfig() - print confo.load_config('hooke.conf') +import textwrap + +from .compat.odict import odict as OrderedDict + +DEFAULT_PATHS = [ + '/usr/share/hooke/hooke.cfg', + '/etc/hooke/hooke.cfg', + '~/.hooke.cfg', + ] +"""We start with the system files, and work our way to the more +specific user files, so the user can override the sysadmin who +in turn overrides the developer defaults. +""" + +class Setting (object): + """An entry (section or option) in HookeConfigParser. + """ + def __init__(self, section, option=None, value=None, help=None, wrap=True): + self.section = section + self.option = option + self.value = value + self.help = help + self.wrap = True + + def is_section(self): + return self.option == None + + def is_option(self): + return not self.is_section() + + def write(self, fp, value=None, comments=True, wrapper=None): + if comments == True and self.help != None: + text = self.help + if self.wrap == True: + if wrapper == None: + wrapper = textwrap.TextWrapper( + initial_indent="# ", + subsequent_indent="# ") + text = wrapper.fill(text) + fp.write(text.rstrip()+'\n') + if self.is_section(): + fp.write("[%s]\n" % self.section) + else: + fp.write("%s = %s\n" % (self.option, + str(value).replace('\n', '\n\t'))) + +DEFAULT_SETTINGS = [ + Setting('display', help='Control display appearance: colour, ???, etc.'), + Setting('display', 'colour_ext', 'None', help=None), + Setting('display', 'colour_ret', 'None', help=None), + Setting('display', 'ext', '1', help=None), + Setting('display', 'ret', '1', help=None), + + Setting('display', 'correct', '1', help=None), + Setting('display', 'colout_correct', 'None', help=None), + Setting('display', 'contact_point', '0', help=None), + Setting('display', 'medfilt', '0', help=None), + + Setting('display', 'xaxes', '0', help=None), + Setting('display', 'yaxes', '0', help=None), + Setting('display', 'flatten', '1', help=None), + + Setting('conditions', 'temperature', '301', help=None), + + Setting('fitting', 'auto_fit_points', '50', help=None), + Setting('fitting', 'auto_slope_span', '20', help=None), + Setting('fitting', 'auto_delta_force', '1-', help=None), + Setting('fitting', 'auto_fit_nm', '5', help=None), + Setting('fitting', 'auto_min_p', '0.005', help=None), + Setting('fitting', 'auto_max_p', '10', help=None), + + Setting('?', 'baseline_clicks', '0', help=None), + Setting('fitting', 'auto_left_baseline', '20', help=None), + Setting('fitting', 'auto_right_baseline', '20', help=None), + Setting('fitting', 'force_multiplier', '1', help=None), + + Setting('display', 'fc_showphase', '0', help=None), + Setting('display', 'fc_showimposed', '0', help=None), + Setting('display', 'fc_interesting', '0', help=None), + Setting('?', 'tccd_threshold', '0', help=None), + Setting('?', 'tccd_coincident', '0', help=None), + Setting('display', '', '', help=None), + Setting('display', '', '', help=None), + + Setting('filesystem', 'filterindex', '0', help=None), + Setting('filesystem', 'filters', + "Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')", + help=None), + Setting('filesystem', 'workdir', 'test', + help='\n'.join(['# Substitute your work directory', + '#workdir = D:\hooke']), + wrap=False), + Setting('filesystem', 'playlist', 'test.hkp', help=None), + ] + +def get_setting(settings, match): + """Return the first Setting object matching both match.section and + match.option. + """ + for s in settings: + if s.section == match.section and s.option == match.option: + return s + return None + +class HookeConfigParser (configparser.SafeConfigParser): + """A wrapper around configparser.SafeConfigParser. + + You will probably only need .read and .write. + + Examples + -------- + + >>> import sys + >>> c = HookeConfigParser(paths=DEFAULT_PATHS, + ... default_settings=DEFAULT_SETTINGS) + >>> c.write(sys.stdout) # doctest: +ELLIPSIS + # Control display appearance: colour, ???, etc. + [display] + colour_ext = None + colour_ret = None + ... + """ + def __init__(self, paths=None, default_settings=None, defaults=None, + dict_type=OrderedDict, indent='# ', **kwargs): + # Can't use super() because SafeConfigParser is a classic class + #super(HookeConfigParser, self).__init__(defaults, dict_type) + configparser.SafeConfigParser.__init__(self, defaults, dict_type) + if paths == None: + paths = [] + self._config_paths = paths + if default_settings == None: + default_settings = [] + self._default_settings = default_settings + for key in ['initial_indent', 'subsequent_indent']: + if key not in kwargs: + kwargs[key] = indent + self._comment_textwrap = textwrap.TextWrapper(**kwargs) + self._setup_default_settings() + + def _setup_default_settings(self): + for setting in self._default_settings: #reversed(self._default_settings): + # reversed cause: http://docs.python.org/library/configparser.html + # "When adding sections or items, add them in the reverse order of + # how you want them to be displayed in the actual file." + if setting.section not in self.sections(): + self.add_section(setting.section) + if setting.option != None: + self.set(setting.section, setting.option, setting.value) + + def read(self, filenames=None): + """Read and parse a filename or a list of filenames. + + If filenames is None, it defaults to ._config_paths. If a + filename is not in ._config_paths, it gets appended to the + list. We also run os.path.expanduser() on the input filenames + internally so you don't have to worry about it. + + Files that cannot be opened are silently ignored; this is + designed so that you can specify a list of potential + configuration file locations (e.g. current directory, user's + home directory, systemwide directory), and all existing + configuration files in the list will be read. A single + filename may also be given. + + Return list of successfully read files. + """ + if filenames == None: + filenames = [os.path.expanduser(p) for p in self._config_paths] + else: + if isinstance(filenames, basestring): + filenames = [filenames] + paths = [os.path.abspath(os.path.expanduser(p)) + for p in self._config_paths] + for filename in filenames: + if os.path.abspath(os.path.expanduser(filename)) not in paths: + self._config_paths.append(filename) + # Can't use super() because SafeConfigParser is a classic class + #return super(HookeConfigParser, self).read(filenames) + return configparser.SafeConfigParser.read(self, filenames) + + def _write_setting(self, fp, section=None, option=None, value=None, + **kwargs): + """Print, if known, a nicely wrapped comment form of a + setting's .help. + """ + match = get_setting(self._default_settings, Setting(section, option)) + if match == None: + match = Setting(section, option, value, **kwargs) + match.write(fp, value=value, wrapper=self._comment_textwrap) + + def write(self, fp=None, comments=True): + """Write an .ini-format representation of the configuration state. + + This expands on RawConfigParser.write() by optionally adding + comments when they are known (i.e. for ._default_settings). + However, comments are not read in during a read, so .read(x) + .write(x) may `not preserve comments`_. + + .. _not preserve comments: http://bugs.python.org/issue1410680 + + Examples + -------- + + >>> import sys, StringIO + >>> c = HookeConfigParser() + >>> instring = ''' + ... # Some comment + ... [section] + ... option = value + ... + ... ''' + >>> c._read(StringIO.StringIO(instring), 'example.cfg') + >>> c.write(sys.stdout) + [section] + option = value + + """ + local_fp = fp == None + if local_fp: + fp = open(self._config_paths[-1], 'w') + if self._defaults: + print self._defaults + self._write_setting(fp, configparser.DEFAULTSECT, + help="Miscellaneous options") + for (key, value) in self._defaults.items(): + self._write_setting(fp, configparser.DEFAULTSECT, key, value) + fp.write("\n") + for section in self._sections: + self._write_setting(fp, section) + for (key, value) in self._sections[section].items(): + if key != "__name__": + self._write_setting(fp, section, key, value) + fp.write("\n") + if local_fp: + fp.close() diff --git a/hooke/hooke.py b/hooke/hooke.py index 547d7cc..3d137f3 100644 --- a/hooke/hooke.py +++ b/hooke/hooke.py @@ -1,18 +1,19 @@ #!/usr/bin/env python ''' -HOOKE - A force spectroscopy review & analysis tool +Hooke - A force spectroscopy review & analysis tool. -Copyright (C) 2008-2010 Massimo Sandal (University of Bologna, Italy). - Rolf Schmidt (Concordia University, Canada). - -This program is released under the GNU General Public License version 2. +COPYRIGHT ''' -from libhooke import WX_GOOD +import Queue as queue +import multiprocessing + +from . import config as config_mod +from .plugin import PLUGINS +from .driver import DRIVERS + -import wxversion -wxversion.select(WX_GOOD) import copy import cStringIO import os @@ -21,119 +22,44 @@ import sys import glob import time -import imp -import wx -import wx.html -import wx.aui -import wxmpl -import wx.lib.agw.aui as aui -import wx.propgrid as wxpg - -import libhooke as lh -from config import config -import drivers -import plugins -import hookecommands -import hookeplaylist -import hookepropertyeditor -import hookeresults -import playlist - -global __version__ - -__version__ = lh.HOOKE_VERSION[0] -__release_name__ = lh.HOOKE_VERSION[1] - -#TODO: order menu items, get rid of all unused IDs -ID_ExportText = wx.NewId() -ID_ExportImage = wx.NewId() -ID_Config = wx.NewId() -ID_About = wx.NewId() -ID_Next = wx.NewId() -ID_Previous = wx.NewId() - -ID_ViewAssistant = wx.NewId() -ID_ViewCommands = wx.NewId() -ID_ViewFolders = wx.NewId() -ID_ViewOutput = wx.NewId() -ID_ViewPlaylists = wx.NewId() -ID_ViewProperties = wx.NewId() -ID_ViewResults = wx.NewId() - -ID_CommandsList = wx.NewId() -ID_CommandsListBox = wx.NewId() - -ID_TextContent = wx.NewId() -ID_TreeContent = wx.NewId() -ID_HTMLContent = wx.NewId() -ID_SizeReportContent = wx.NewId() -ID_DeletePerspective = wx.NewId() -ID_SavePerspective = wx.NewId() - -ID_FirstPerspective = ID_SavePerspective + 1000 -#I hope we'll never have more than 1000 perspectives -ID_FirstPlot = ID_SavePerspective + 2000 - -class Hooke(wx.App): - - def OnInit(self): - self.SetAppName('Hooke') - self.SetVendorName('') - - #set the Hooke directory - lh.hookeDir = os.path.abspath(os.path.dirname(__file__)) - - windowPosition = (config['main']['left'], config['main']['top']) - windowSize = (config['main']['width'], config['main']['height']) - - #setup the splashscreen - if config['splashscreen']['show']: - filename = lh.get_file_path('hooke.jpg', ['resources']) - if os.path.isfile(filename): - bitmap = wx.Image(filename).ConvertToBitmap() - splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT - splashDuration = config['splashscreen']['duration'] - wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1) - wx.Yield() - ''' - we need for the splash screen to disappear - for whatever reason splashDuration and sleep do not correspond to each other - at least not on Windows - maybe it's because duration is in milliseconds and sleep in seconds - thus we need to increase the sleep time a bit - a factor of 1.2 seems to work quite well - ''' - sleepFactor = 1.2 - time.sleep(sleepFactor * splashDuration / 1000) - - plugin_objects = [] - for plugin in config['plugins']: - if config['plugins'][plugin]: - filename = ''.join([plugin, '.py']) - path = lh.get_file_path(filename, ['plugins']) - if os.path.isfile(path): - #get the corresponding filename and path - plugin_name = ''.join(['plugins.', plugin]) - module = __import__(plugin_name) - #get the file that contains the plugin - class_file = getattr(plugins, plugin) - #get the class that contains the commands - class_object = getattr(class_file, plugin + 'Commands') - plugin_objects.append(class_object) +#import libhooke as lh +#import drivers +#import plugins +#import hookecommands +#import hookeplaylist +#import hookepropertyeditor +#import hookeresults +#import playlist + +class Hooke(object): + def __init__(self, config=None, debug=0): + self.debug = debug + config_mod.DEFAULTS.extend(self.plugin_default_settings()) + config_mod.DEFAULTS.extend(self.driver_default_settings()) + if config == None: + config = config_mod.HookeConfigParser( + paths=config_mod.DEFAULT_PATHS, + default_settings=config_mod.DEFAULT_SETTINGS) + config.read_configs() + self.config = config + self.load_plugins() + self.load_drivers() + + def plugin_default_settings(self): + pass - def make_command_class(*bases): - #create metaclass with plugins and plotmanipulators - return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {}) - frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize) - frame.Show(True) - self.SetTopWindow(frame) + def driver_default_settings(self): + pass - return True + def load_plugins(self): + pass - def OnExit(self): - #TODO: write values to ini file if necessary - return True + def load_drivers(self) + pass + def close(self): + if self.config.changed: + self.config.write() # Does not preserve original comments class HookeFrame(wx.Frame): @@ -1229,34 +1155,8 @@ class HookeFrame(wx.Frame): self.AppendToOutput('Not able to plot.') -ID_PaneBorderSize = wx.ID_HIGHEST + 1 -ID_SashSize = ID_PaneBorderSize + 1 -ID_CaptionSize = ID_PaneBorderSize + 2 -ID_BackgroundColor = ID_PaneBorderSize + 3 -ID_SashColor = ID_PaneBorderSize + 4 -ID_InactiveCaptionColor = ID_PaneBorderSize + 5 -ID_InactiveCaptionGradientColor = ID_PaneBorderSize + 6 -ID_InactiveCaptionTextColor = ID_PaneBorderSize + 7 -ID_ActiveCaptionColor = ID_PaneBorderSize + 8 -ID_ActiveCaptionGradientColor = ID_PaneBorderSize + 9 -ID_ActiveCaptionTextColor = ID_PaneBorderSize + 10 -ID_BorderColor = ID_PaneBorderSize + 11 -ID_GripperColor = ID_PaneBorderSize + 12 - - -#---------------------------------------------------------------------- - if __name__ == '__main__': - - ## now, silence a deprecation warning for py2.3 - #import warnings - #warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi") - - redirect=True - if __debug__: - redirect=False - app = Hooke(redirect=redirect) - + app = Hooke(debug=__debug__) app.MainLoop() -- 2.26.2