Ran update_copyright.py
authorW. Trevor King <wking@drexel.edu>
Tue, 3 Aug 2010 00:10:15 +0000 (20:10 -0400)
committerW. Trevor King <wking@drexel.edu>
Tue, 3 Aug 2010 00:10:15 +0000 (20:10 -0400)
112 files changed:
conf/hooke configspec.ini
conf/hooke.ini
contrib/mfp_igor_scripts/FMjoin.py
contrib/mfp_igor_scripts/h5export.py
doc/generate-hooke-txt.py
hooke/__init__.py
hooke/command.py
hooke/compat/__init__.py
hooke/compat/minidom.py
hooke/config.py
hooke/curve.py
hooke/driver/__init__.py
hooke/driver/csvdriver.py
hooke/driver/hdf5.py
hooke/driver/hemingway.py
hooke/driver/igorbinarywave.py
hooke/driver/jpk.py
hooke/driver/mcs.py
hooke/driver/mfp1dexport.py
hooke/driver/mfp3d.py
hooke/driver/picoforce.py
hooke/driver/tutorial.py
hooke/driver/wtk.py
hooke/engine.py
hooke/experiment.py
hooke/hooke.py
hooke/interaction.py
hooke/playlist.py
hooke/plugin/__init__.py
hooke/plugin/autopeak.py
hooke/plugin/config.py
hooke/plugin/convfilt.py
hooke/plugin/curve.py
hooke/plugin/cut.py
hooke/plugin/debug.py
hooke/plugin/fclamp.py
hooke/plugin/fit.py
hooke/plugin/flatfilt.py
hooke/plugin/flatfilts-rolf.py
hooke/plugin/jumpstat.py
hooke/plugin/license.py
hooke/plugin/macro.py
hooke/plugin/massanalysis.py
hooke/plugin/multidistance.py
hooke/plugin/multifit.py
hooke/plugin/note.py
hooke/plugin/pcluster.py
hooke/plugin/playlist.py
hooke/plugin/playlists.py
hooke/plugin/review.py
hooke/plugin/showconvoluted.py
hooke/plugin/superimpose.py
hooke/plugin/system.py
hooke/plugin/tccd.py
hooke/plugin/tutorial.py
hooke/plugin/vclamp.py
hooke/ui/__init__.py
hooke/ui/commandline.py
hooke/ui/gui/__init__.py
hooke/ui/gui/clickedpoint.py
hooke/ui/gui/dialog/__init__.py
hooke/ui/gui/dialog/points.py
hooke/ui/gui/dialog/save_file.py
hooke/ui/gui/dialog/selection.py
hooke/ui/gui/dialog/text.py
hooke/ui/gui/handler/__init__.py
hooke/ui/gui/handler/boolean.py
hooke/ui/gui/handler/selection.py
hooke/ui/gui/handler/string.py
hooke/ui/gui/menu.py
hooke/ui/gui/navbar.py
hooke/ui/gui/panel/__init__.py
hooke/ui/gui/panel/commands.py
hooke/ui/gui/panel/note.py
hooke/ui/gui/panel/notebook.py
hooke/ui/gui/panel/output.py
hooke/ui/gui/panel/playlist.py
hooke/ui/gui/panel/plot.py
hooke/ui/gui/panel/propertyeditor-propgrid.py
hooke/ui/gui/panel/propertyeditor.py
hooke/ui/gui/panel/results.py
hooke/ui/gui/panel/selection.py
hooke/ui/gui/panel/welcome.py
hooke/ui/gui/playlist.py
hooke/ui/gui/plotcommands.py
hooke/ui/gui/point_request.py
hooke/ui/gui/results.py
hooke/ui/gui/statusbar.py
hooke/util/__init__.py
hooke/util/calculus.py
hooke/util/callback.py
hooke/util/caller.py
hooke/util/encoding.py
hooke/util/fft.py
hooke/util/fit.py
hooke/util/graph.py
hooke/util/peak.py
hooke/util/pluggable.py
hooke/util/si.py
hooke/util/util.py
setup.py
test/__init__.py
test/curve_info.py
test/flat_filter.py
test/hemingway_driver.py
test/jpk_driver.py
test/load_playlist.py
test/mfp3d_driver.py
test/picoforce_driver.py
test/unicode_output.py
test/wtk_driver.py
update_copyright.py

index c5691de..add94b8 100644 (file)
@@ -1,40 +1,40 @@
-[drivers]\r
-csvdriver = boolean(default = False)\r
-hemingclamp = boolean(default = False)\r
-jpk = boolean(default = False)\r
-mcs = boolean(default = False)\r
-mfp1d = boolean(default = True)\r
-mfp1dexport = boolean(default = True)\r
-mfp3d = boolean(default = True)\r
-picoforce = boolean(default = True)\r
-picoforcealt = boolean(default = False)\r
-tutorialdriver = boolean(default = False)\r
-\r
-[folders]\r
-filterindex = integer(default = 0)\r
-filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
-\r
-[main]\r
-height = integer(default = 500)\r
-left = integer(default = 50)\r
-top = integer(default = 50)\r
-width = integer(default = 700)\r
-\r
-[perspectives]\r
-active = string(default = Default)\r
-\r
-[plugins]\r
-autopeak = boolean(default = True)\r
-export = boolean(default = True)\r
-fit = boolean(default = True)\r
-flatfilts = boolean(default = True)\r
-generalvclamp = boolean(default = True)\r
-playlist = boolean(default = True)\r
-plot = boolean(default = True)\r
-procplots = boolean(default = True)\r
-results = boolean(default = True)\r
-\r
-[splashscreen]\r
-#duration in milliseconds\r
-duration = integer(default = 1000)\r
-show = boolean(default = True)\r
+[drivers]
+csvdriver = boolean(default = False)
+hemingclamp = boolean(default = False)
+jpk = boolean(default = False)
+mcs = boolean(default = False)
+mfp1d = boolean(default = True)
+mfp1dexport = boolean(default = True)
+mfp3d = boolean(default = True)
+picoforce = boolean(default = True)
+picoforcealt = boolean(default = False)
+tutorialdriver = boolean(default = False)
+
+[folders]
+filterindex = integer(default = 0)
+filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+[main]
+height = integer(default = 500)
+left = integer(default = 50)
+top = integer(default = 50)
+width = integer(default = 700)
+
+[perspectives]
+active = string(default = Default)
+
+[plugins]
+autopeak = boolean(default = True)
+export = boolean(default = True)
+fit = boolean(default = True)
+flatfilts = boolean(default = True)
+generalvclamp = boolean(default = True)
+playlist = boolean(default = True)
+plot = boolean(default = True)
+procplots = boolean(default = True)
+results = boolean(default = True)
+
+[splashscreen]
+#duration in milliseconds
+duration = integer(default = 1000)
+show = boolean(default = True)
index fe6a3f1..6276712 100644 (file)
@@ -1,51 +1,51 @@
-#prefix with '#' to add a comment\r
-\r
-[command]\r
-command = autopeak\r
-plugin = autopeak\r
-\r
-#this section defines which drivers have to be loaded by Hooke\r
-[drivers]\r
-csvdriver = False\r
-hemingclamp = False\r
-jpk = False\r
-mcs = False\r
-mfp1d = True\r
-mfp3d = True\r
-mfp1dexport = True\r
-picoforce = True\r
-picoforcealt = False\r
-tutorialdriver = False\r
-\r
-[folders]\r
-filterindex = 0\r
-filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
-\r
-#this section defines the window size and position\r
-[main]\r
-height = 500\r
-left = 20\r
-top = 20\r
-width = 600\r
-\r
-[perspectives]\r
-active = Default\r
-default = Default\r
-\r
-#this section defines which plugins have to be loaded by Hooke\r
-[plugins]\r
-autopeak = True\r
-export = True\r
-fit = True\r
-flatfilts = True\r
-generalvclamp = True\r
-multidistance = True\r
-playlist = True\r
-plot = True\r
-procplots = True\r
-results = True\r
-\r
-[splashscreen]\r
-#duration in milliseconds\r
-duration = 1000\r
-show = True\r
+#prefix with '#' to add a comment
+
+[command]
+command = autopeak
+plugin = autopeak
+
+#this section defines which drivers have to be loaded by Hooke
+[drivers]
+csvdriver = False
+hemingclamp = False
+jpk = False
+mcs = False
+mfp1d = True
+mfp3d = True
+mfp1dexport = True
+picoforce = True
+picoforcealt = False
+tutorialdriver = False
+
+[folders]
+filterindex = 0
+filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+#this section defines the window size and position
+[main]
+height = 500
+left = 20
+top = 20
+width = 600
+
+[perspectives]
+active = Default
+default = Default
+
+#this section defines which plugins have to be loaded by Hooke
+[plugins]
+autopeak = True
+export = True
+fit = True
+flatfilts = True
+generalvclamp = True
+multidistance = True
+playlist = True
+plot = True
+procplots = True
+results = True
+
+[splashscreen]
+#duration in milliseconds
+duration = 1000
+show = True
index de40aee..1630d05 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 4740be7..5759c05 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index fb91500..346db39 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index c8b1253..17c38ce 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index e4bb9c1..974e3b5 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index ad3d685..28c4691 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index b9ff343..d2025cf 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Dynamically patch :mod:`xml.dom.minidom`'s attribute value escaping.
 
index 97d645b..11ee1a8 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index db31318..7e3d084 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index e51fb22..0e860c1 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 89ff5ff..3ec4cbe 100644 (file)
@@ -1,4 +1,21 @@
-# Copyright
+# Copyright (C) 2008-2010 Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Simple driver to read general comma-separated values in Hooke
 
index 4a05474..2614319 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 8cd5f5e..4f837c9 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index a25f33e..209bbc7 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index e6964f6..2cbb852 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index f8208c0..366e978 100644 (file)
@@ -1,4 +1,22 @@
-# Copyright
+# Copyright (C) 2009-2010 Allen Chen
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Driver for mcs fluorescence files.
 """
index 4c5363c..aefb5a5 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index fce318c..c57643c 100644 (file)
@@ -6,15 +6,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 2954804..19f8e3d 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 42204d3..7dfb6e8 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 8cef939..8935120 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index a973730..b91551c 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 9f94488..81decf9 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index d1c3b9c..1923d22 100644 (file)
@@ -5,15 +5,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 97ac0b2..35492a7 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index bba2990..c57604f 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 80c2b52..f589e35 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index ac76228..7488c3a 100644 (file)
@@ -6,15 +6,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 21fd56a..b0bcf3b 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 3d1e883..22b26c7 100644 (file)
@@ -7,15 +7,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 56b4379..b517c0b 100644 (file)
@@ -5,15 +5,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 9eb112b..ad1a067 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index a2e8812..1855450 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 54aff5f..b97022e 100644 (file)
@@ -5,15 +5,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 7ea2726..c566a8b 100644 (file)
@@ -5,15 +5,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 716a4be..9bf6db5 100644 (file)
@@ -7,15 +7,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index a469f8e..1c53052 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index a205a75..2e02b4b 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index d405bf9..21fecdb 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index c7cd1e7..bfc15d5 100644 (file)
@@ -4,15 +4,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index c5fcd86..c657af9 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 610043e..7c8291a 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 56befca..7392418 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 3847dff..b36c801 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index d8525e2..87ebdd5 100644 (file)
@@ -5,15 +5,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index b9d5240..82d0c83 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 485904f..2be0c9b 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index fcfdd66..f1d8560 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 1bae5bc..b580c07 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index ea916e1..dcb5bf0 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 8a09235..84f9ee6 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index ae318af..f681693 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 95f1f0c..9f0fb51 100644 (file)
@@ -3,15 +3,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index fca2cd3..fcf49e8 100644 (file)
@@ -6,15 +6,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 18a2206..ad09e02 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 291f395..c601ddc 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index e9a3945..9059b8c 100644 (file)
-# Copyright\r
-\r
-"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.\r
-\r
-"""\r
-\r
-WX_GOOD=['2.8']\r
-\r
-import wxversion\r
-wxversion.select(WX_GOOD)\r
-\r
-import copy\r
-import logging\r
-import os\r
-import os.path\r
-import platform\r
-import shutil\r
-import time\r
-\r
-import wx.html\r
-import wx.aui as aui\r
-import wx.lib.evtmgr as evtmgr\r
-# wxPropertyGrid is included in wxPython >= 2.9.1, see\r
-#   http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
-# until then, we'll avoid it because of the *nix build problems.\r
-#import wx.propgrid as wxpg\r
-\r
-from ...command import CommandExit, Exit, Success, Failure, Command, Argument\r
-from ...config import Setting\r
-from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
-from ...ui import UserInterface, CommandMessage\r
-from .dialog.selection import Selection as SelectionDialog\r
-from .dialog.save_file import select_save_file\r
-from . import menu as menu\r
-from . import navbar as navbar\r
-from . import panel as panel\r
-from .panel.propertyeditor import prop_from_argument, prop_from_setting\r
-from . import statusbar as statusbar\r
-\r
-\r
-class HookeFrame (wx.Frame):\r
-    """The main Hooke-interface window.    \r
-    """\r
-    def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):\r
-        super(HookeFrame, self).__init__(*args, **kwargs)\r
-        self.log = logging.getLogger('hooke')\r
-        self.gui = gui\r
-        self.commands = commands\r
-        self.inqueue = inqueue\r
-        self.outqueue = outqueue\r
-        self._perspectives = {}  # {name: perspective_str}\r
-        self._c = {}\r
-\r
-        self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))\r
-\r
-        # setup frame manager\r
-        self._c['manager'] = aui.AuiManager()\r
-        self._c['manager'].SetManagedWindow(self)\r
-\r
-        # set the gradient and drag styles\r
-        self._c['manager'].GetArtProvider().SetMetric(\r
-            aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)\r
-        self._c['manager'].SetFlags(\r
-            self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)\r
-\r
-        # Min size for the frame itself isn't completely done.  See\r
-        # the end of FrameManager::Update() for the test code. For\r
-        # now, just hard code a frame minimum size.\r
-        #self.SetMinSize(wx.Size(500, 500))\r
-\r
-        self._setup_panels()\r
-        self._setup_toolbars()\r
-        self._c['manager'].Update()  # commit pending changes\r
-\r
-        # Create the menubar after the panes so that the default\r
-        # perspective is created with all panes open\r
-        panels = [p for p in self._c.values() if isinstance(p, panel.Panel)]\r
-        self._c['menu bar'] = menu.HookeMenuBar(\r
-            parent=self,\r
-            panels=panels,\r
-            callbacks={\r
-                'close': self._on_close,\r
-                'about': self._on_about,\r
-                'view_panel': self._on_panel_visibility,\r
-                'save_perspective': self._on_save_perspective,\r
-                'delete_perspective': self._on_delete_perspective,\r
-                'select_perspective': self._on_select_perspective,\r
-                })\r
-        self.SetMenuBar(self._c['menu bar'])\r
-\r
-        self._c['status bar'] = statusbar.StatusBar(\r
-            parent=self,\r
-            style=wx.ST_SIZEGRIP)\r
-        self.SetStatusBar(self._c['status bar'])\r
-\r
-        self._setup_perspectives()\r
-        self._bind_events()\r
-\r
-        self.execute_command(\r
-                command=self._command_by_name('load playlist'),\r
-                args={'input':'test/data/vclamp_picoforce/playlist'},\r
-                )\r
-        return # TODO: cleanup\r
-        self.playlists = self._c['playlist'].Playlists\r
-        self._displayed_plot = None\r
-        #load default list, if possible\r
-        self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists'))\r
-\r
-\r
-    # GUI maintenance\r
-\r
-    def _setup_panels(self):\r
-        client_size = self.GetClientSize()\r
-        for p,style in [\r
-#            ('folders', wx.GenericDirCtrl(\r
-#                    parent=self,\r
-#                    dir=self.gui.config['folders-workdir'],\r
-#                    size=(200, 250),\r
-#                    style=wx.DIRCTRL_SHOW_FILTERS,\r
-#                    filter=self.gui.config['folders-filters'],\r
-#                    defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'),  #HACK: config should convert\r
-            (panel.PANELS['playlist'](\r
-                    callbacks={\r
-                        'delete_playlist':self._on_user_delete_playlist,\r
-                        '_delete_playlist':self._on_delete_playlist,\r
-                        'delete_curve':self._on_user_delete_curve,\r
-                        '_delete_curve':self._on_delete_curve,\r
-                        '_on_set_selected_playlist':self._on_set_selected_playlist,\r
-                        '_on_set_selected_curve':self._on_set_selected_curve,\r
-                        },\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
-#                    size=(160, 200),\r
-                    ), 'left'),\r
-            (panel.PANELS['note'](\r
-                    callbacks = {\r
-                        '_on_update':self._on_update_note,\r
-                        },\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-#                    size=(160, 200),\r
-                    ), 'left'),\r
-#            ('notebook', Notebook(\r
-#                    parent=self,\r
-#                    pos=wx.Point(client_size.x, client_size.y),\r
-#                    size=wx.Size(430, 200),\r
-#                    style=aui.AUI_NB_DEFAULT_STYLE\r
-#                    | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
-            (panel.PANELS['commands'](\r
-                    commands=self.commands,\r
-                    selected=self.gui.config['selected command'],\r
-                    callbacks={\r
-                        'execute': self.execute_command,\r
-                        'select_plugin': self.select_plugin,\r
-                        'select_command': self.select_command,\r
-#                        'selection_changed': self.panelProperties.select(self, method, command),  #SelectedTreeItem = selected_item,\r
-                        },\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
-#                    size=(160, 200),\r
-                    ), 'right'),\r
-            (panel.PANELS['propertyeditor'](\r
-                    callbacks={},\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS,\r
-                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
-                    ), 'center'),\r
-#            ('assistant', wx.TextCtrl(\r
-#                    parent=self,\r
-#                    pos=wx.Point(0, 0),\r
-#                    size=wx.Size(150, 90),\r
-#                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
-            (panel.PANELS['plot'](\r
-                    callbacks={\r
-                        },\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
-#                    size=(160, 200),\r
-                    ), 'center'),\r
-            (panel.PANELS['output'](\r
-                    parent=self,\r
-                    pos=wx.Point(0, 0),\r
-                    size=wx.Size(150, 90),\r
-                    style=wx.TE_READONLY|wx.NO_BORDER|wx.TE_MULTILINE),\r
-             'bottom'),\r
-#            ('results', panel.results.Results(self), 'bottom'),\r
-            ]:\r
-            self._add_panel(p, style)\r
-        #self._c['assistant'].SetEditable(False)\r
-\r
-    def _add_panel(self, panel, style):\r
-        self._c[panel.name] = panel\r
-        m_name = panel.managed_name\r
-        info = aui.AuiPaneInfo().Name(m_name).Caption(m_name)\r
-        info.PaneBorder(False).CloseButton(True).MaximizeButton(False)\r
-        if style == 'top':\r
-            info.Top()\r
-        elif style == 'center':\r
-            info.CenterPane()\r
-        elif style == 'left':\r
-            info.Left()\r
-        elif style == 'right':\r
-            info.Right()\r
-        else:\r
-            assert style == 'bottom', style\r
-            info.Bottom()\r
-        self._c['manager'].AddPane(panel, info)\r
-\r
-    def _setup_toolbars(self):\r
-        self._c['navigation bar'] = navbar.NavBar(\r
-            callbacks={\r
-                'next': self._next_curve,\r
-                'previous': self._previous_curve,\r
-                },\r
-            parent=self,\r
-            style=wx.TB_FLAT | wx.TB_NODIVIDER)\r
-        self._c['manager'].AddPane(\r
-            self._c['navigation bar'],\r
-            aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'\r
-                ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False\r
-                ).RightDockable(False))\r
-\r
-    def _bind_events(self):\r
-        # TODO: figure out if we can use the eventManager for menu\r
-        # ranges and events of 'self' without raising an assertion\r
-        # fail error.\r
-        self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)\r
-        self.Bind(wx.EVT_SIZE, self._on_size)\r
-        self.Bind(wx.EVT_CLOSE, self._on_close)\r
-        self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
-        self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)\r
-\r
-        return # TODO: cleanup\r
-        treeCtrl = self._c['folders'].GetTreeCtrl()\r
-        treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)\r
-        \r
-        #property editor\r
-        self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
-        #results panel\r
-        self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
-\r
-    def _on_about(self, *args):\r
-        dialog = wx.MessageDialog(\r
-            parent=self,\r
-            message=self.gui._splash_text(extra_info={\r
-                    'get-details':'click "Help -> License"'},\r
-                                          wrap=False),\r
-            caption='About Hooke',\r
-            style=wx.OK|wx.ICON_INFORMATION)\r
-        dialog.ShowModal()\r
-        dialog.Destroy()\r
-\r
-    def _on_close(self, *args):\r
-        self.log.info('closing GUI framework')\r
-        # apply changes\r
-        self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
-        self.gui.config['main left'] = str(self.GetPosition()[0])\r
-        self.gui.config['main top'] = str(self.GetPosition()[1])\r
-        self.gui.config['main width'] = str(self.GetSize().GetWidth())\r
-        # push changes back to Hooke.config?\r
-        self._c['manager'].UnInit()\r
-        del self._c['manager']\r
-        self.Destroy()\r
-\r
-\r
-\r
-    # Panel utility functions\r
-\r
-    def _file_name(self, name):\r
-        """Cleanup names according to configured preferences.\r
-        """\r
-        if self.gui.config['hide extensions'] == 'True':  # HACK: config should decode\r
-            name,ext = os.path.splitext(name)\r
-        return name\r
-\r
-\r
-\r
-    # Command handling\r
-\r
-    def _command_by_name(self, name):\r
-        cs = [c for c in self.commands if c.name == name]\r
-        if len(cs) == 0:\r
-            raise KeyError(name)\r
-        elif len(cs) > 1:\r
-            raise Exception('Multiple commands named "%s"' % name)\r
-        return cs[0]\r
-\r
-    def execute_command(self, _class=None, method=None,\r
-                        command=None, args=None):\r
-        if args == None:\r
-            args = {}\r
-        if ('property editor' in self._c\r
-            and self.gui.config['selected command'] == command):\r
-            arg_names = [arg.name for arg in command.arguments]\r
-            for name,value in self._c['property editor'].get_values().items():\r
-                if name in arg_names:\r
-                    args[name] = value\r
-        self.log.debug('executing %s with %s' % (command.name, args))\r
-        self.inqueue.put(CommandMessage(command, args))\r
-        results = []\r
-        while True:\r
-            msg = self.outqueue.get()\r
-            results.append(msg)\r
-            if isinstance(msg, Exit):\r
-                self._on_close()\r
-                break\r
-            elif isinstance(msg, CommandExit):\r
-                # TODO: display command complete\r
-                break\r
-            elif isinstance(msg, ReloadUserInterfaceConfig):\r
-                self.gui.reload_config(msg.config)\r
-                continue\r
-            elif isinstance(msg, Request):\r
-                h = handler.HANDLERS[msg.type]\r
-                h.run(self, msg)  # TODO: pause for response?\r
-                continue\r
-        pp = getattr(\r
-            self, '_postprocess_%s' % command.name.replace(' ', '_'),\r
-            self._postprocess_text)\r
-        pp(command=command, args=args, results=results)\r
-        return results\r
-\r
-    def _handle_request(self, msg):\r
-        """Repeatedly try to get a response to `msg`.\r
-        """\r
-        if prompt == None:\r
-            raise NotImplementedError('_%s_request_prompt' % msg.type)\r
-        prompt_string = prompt(msg)\r
-        parser = getattr(self, '_%s_request_parser' % msg.type, None)\r
-        if parser == None:\r
-            raise NotImplementedError('_%s_request_parser' % msg.type)\r
-        error = None\r
-        while True:\r
-            if error != None:\r
-                self.cmd.stdout.write(''.join([\r
-                        error.__class__.__name__, ': ', str(error), '\n']))\r
-            self.cmd.stdout.write(prompt_string)\r
-            value = parser(msg, self.cmd.stdin.readline())\r
-            try:\r
-                response = msg.response(value)\r
-                break\r
-            except ValueError, error:\r
-                continue\r
-        self.inqueue.put(response)\r
-\r
-\r
-\r
-    # Command-specific postprocessing\r
-\r
-    def _postprocess_text(self, command, args={}, results=[]):\r
-        """Print the string representation of the results to the Results window.\r
-\r
-        This is similar to :class:`~hooke.ui.commandline.DoCommand`'s\r
-        approach, except that :class:`~hooke.ui.commandline.DoCommand`\r
-        doesn't print some internally handled messages\r
-        (e.g. :class:`~hooke.interaction.ReloadUserInterfaceConfig`).\r
-        """\r
-        for result in results:\r
-            if isinstance(result, CommandExit):\r
-                self._c['output'].write(result.__class__.__name__+'\n')\r
-            self._c['output'].write(str(result).rstrip()+'\n')\r
-\r
-    def _postprocess_load_playlist(self, command, args={}, results=None):\r
-        """Update `self` to show the playlist.\r
-        """\r
-        if not isinstance(results[-1], Success):\r
-            self._postprocess_text(command, results=results)\r
-            return\r
-        assert len(results) == 2, results\r
-        playlist = results[0]\r
-        self._c['playlist']._c['tree'].add_playlist(playlist)\r
-\r
-    def _postprocess_get_playlist(self, command, args={}, results=[]):\r
-        if not isinstance(results[-1], Success):\r
-            self._postprocess_text(command, results=results)\r
-            return\r
-        assert len(results) == 2, results\r
-        playlist = results[0]\r
-        self._c['playlist']._c['tree'].update_playlist(playlist)\r
-\r
-    def _postprocess_get_curve(self, command, args={}, results=[]):\r
-        """Update `self` to show the curve.\r
-        """\r
-        if not isinstance(results[-1], Success):\r
-            self._postprocess_text(command, results=results)\r
-            return\r
-        assert len(results) == 2, results\r
-        curve = results[0]\r
-        if args.get('curve', None) == None:\r
-            # the command defaults to the current curve of the current playlist\r
-            results = self.execute_command(\r
-                command=self._command_by_name('get playlist'))\r
-            playlist = results[0]\r
-        else:\r
-            raise NotImplementedError()\r
-        if 'note' in self._c:\r
-            print sorted(curve.info.keys())\r
-            self._c['note'].set_text(curve.info['note'])\r
-        if 'playlist' in self._c:\r
-            self._c['playlist']._c['tree'].set_selected_curve(\r
-                playlist, curve)\r
-        if 'plot' in self._c:\r
-            self._c['plot'].set_curve(curve, config=self.gui.config)\r
-\r
-    def _postprocess_next_curve(self, command, args={}, results=[]):\r
-        """No-op.  Only call 'next curve' via `self._next_curve()`.\r
-        """\r
-        pass\r
-\r
-    def _postprocess_previous_curve(self, command, args={}, results=[]):\r
-        """No-op.  Only call 'previous curve' via `self._previous_curve()`.\r
-        """\r
-        pass\r
-\r
-    def _postprocess_zero_block_surface_contact_point(\r
-        self, command, args={}, results=[]):\r
-        """Update the curve, since the available columns may have changed.\r
-        """\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
\r
-    def _postprocess_add_block_force_array(\r
-        self, command, args={}, results=[]):\r
-        """Update the curve, since the available columns may have changed.\r
-        """\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
-    # TODO: cruft\r
-\r
-    def _GetActiveFileIndex(self):\r
-        lib.playlist.Playlist = self.GetActivePlaylist()\r
-        #get the selected item from the tree\r
-        selected_item = self._c['playlist']._c['tree'].GetSelection()\r
-        #test if a playlist or a curve was double-clicked\r
-        if self._c['playlist']._c['tree'].ItemHasChildren(selected_item):\r
-            return -1\r
-        else:\r
-            count = 0\r
-            selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
-            while selected_item.IsOk():\r
-                count += 1\r
-                selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
-            return count\r
-\r
-    def _GetPlaylistTab(self, name):\r
-        for index, page in enumerate(self._c['notebook']._tabs._pages):\r
-            if page.caption == name:\r
-                return index\r
-        return -1\r
-\r
-    def select_plugin(self, _class=None, method=None, plugin=None):\r
-        pass\r
-\r
-    def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
-        if files:\r
-            playlist = lib.playlist.Playlist(self, self.drivers)\r
-            for item in files:\r
-                playlist.add_curve(item)\r
-        if playlist.count > 0:\r
-            playlist.name = self._GetUniquePlaylistName(name)\r
-            playlist.reset()\r
-            self.AddTayliss(playlist)\r
-\r
-    def AppliesPlotmanipulator(self, name):\r
-        '''\r
-        Returns True if the plotmanipulator 'name' is applied, False otherwise\r
-        name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
-        '''\r
-        return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
-\r
-    def ApplyPlotmanipulators(self, plot, plot_file):\r
-        '''\r
-        Apply all active plotmanipulators.\r
-        '''\r
-        if plot is not None and plot_file is not None:\r
-            manipulated_plot = copy.deepcopy(plot)\r
-            for plotmanipulator in self.plotmanipulators:\r
-                if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
-                    manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
-            return manipulated_plot\r
-\r
-    def GetActiveFigure(self):\r
-        playlist_name = self.GetActivePlaylistName()\r
-        figure = self.playlists[playlist_name].figure\r
-        if figure is not None:\r
-            return figure\r
-        return None\r
-\r
-    def GetActiveFile(self):\r
-        playlist = self.GetActivePlaylist()\r
-        if playlist is not None:\r
-            return playlist.get_active_file()\r
-        return None\r
-\r
-    def GetActivePlot(self):\r
-        playlist = self.GetActivePlaylist()\r
-        if playlist is not None:\r
-            return playlist.get_active_file().plot\r
-        return None\r
-\r
-    def GetDisplayedPlot(self):\r
-        plot = copy.deepcopy(self.displayed_plot)\r
-        #plot.curves = []\r
-        #plot.curves = copy.deepcopy(plot.curves)\r
-        return plot\r
-\r
-    def GetDisplayedPlotCorrected(self):\r
-        plot = copy.deepcopy(self.displayed_plot)\r
-        plot.curves = []\r
-        plot.curves = copy.deepcopy(plot.corrected_curves)\r
-        return plot\r
-\r
-    def GetDisplayedPlotRaw(self):\r
-        plot = copy.deepcopy(self.displayed_plot)\r
-        plot.curves = []\r
-        plot.curves = copy.deepcopy(plot.raw_curves)\r
-        return plot\r
-\r
-    def GetDockArt(self):\r
-        return self._c['manager'].GetArtProvider()\r
-\r
-    def GetPlotmanipulator(self, name):\r
-        '''\r
-        Returns a plot manipulator function from its name\r
-        '''\r
-        for plotmanipulator in self.plotmanipulators:\r
-            if plotmanipulator.name == name:\r
-                return plotmanipulator\r
-        return None\r
-\r
-    def HasPlotmanipulator(self, name):\r
-        '''\r
-        returns True if the plotmanipulator 'name' is loaded, False otherwise\r
-        '''\r
-        for plotmanipulator in self.plotmanipulators:\r
-            if plotmanipulator.command == name:\r
-                return True\r
-        return False\r
-\r
-\r
-    def _on_dir_ctrl_left_double_click(self, event):\r
-        file_path = self.panelFolders.GetPath()\r
-        if os.path.isfile(file_path):\r
-            if file_path.endswith('.hkp'):\r
-                self.do_loadlist(file_path)\r
-        event.Skip()\r
-\r
-    def _on_erase_background(self, event):\r
-        event.Skip()\r
-\r
-    def _on_notebook_page_close(self, event):\r
-        ctrl = event.GetEventObject()\r
-        playlist_name = ctrl.GetPageText(ctrl._curpage)\r
-        self.DeleteFromPlaylists(playlist_name)\r
-\r
-    def OnPaneClose(self, event):\r
-        event.Skip()\r
-\r
-    def OnPropGridChanged (self, event):\r
-        prop = event.GetProperty()\r
-        if prop:\r
-            item_section = self.panelProperties.SelectedTreeItem\r
-            item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)\r
-            plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)\r
-            config = self.gui.config[plugin]\r
-            property_section = self._c['commands']._c['tree'].GetItemText(item_section)\r
-            property_key = prop.GetName()\r
-            property_value = prop.GetDisplayedString()\r
-\r
-            config[property_section][property_key]['value'] = property_value\r
-\r
-    def OnResultsCheck(self, index, flag):\r
-        results = self.GetActivePlot().results\r
-        if results.has_key(self.results_str):\r
-            results[self.results_str].results[index].visible = flag\r
-            results[self.results_str].update()\r
-            self.UpdatePlot()\r
-\r
-\r
-    def _on_size(self, event):\r
-        event.Skip()\r
-\r
-    def UpdatePlaylistsTreeSelection(self):\r
-        playlist = self.GetActivePlaylist()\r
-        if playlist is not None:\r
-            if playlist.index >= 0:\r
-                self._c['status bar'].set_playlist(playlist)\r
-                self.UpdateNote()\r
-                self.UpdatePlot()\r
-\r
-    def _on_curve_select(self, playlist, curve):\r
-        #create the plot tab and add playlist to the dictionary\r
-        plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
-        notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)\r
-        #tab_index = self._c['notebook'].GetSelection()\r
-        playlist.figure = plotPanel.get_figure()\r
-        self.playlists[playlist.name] = playlist\r
-        #self.playlists[playlist.name] = [playlist, figure]\r
-        self._c['status bar'].set_playlist(playlist)\r
-        self.UpdateNote()\r
-        self.UpdatePlot()\r
-\r
-\r
-    def _on_playlist_left_doubleclick(self):\r
-        index = self._c['notebook'].GetSelection()\r
-        current_playlist = self._c['notebook'].GetPageText(index)\r
-        if current_playlist != playlist_name:\r
-            index = self._GetPlaylistTab(playlist_name)\r
-            self._c['notebook'].SetSelection(index)\r
-        self._c['status bar'].set_playlist(playlist)\r
-        self.UpdateNote()\r
-        self.UpdatePlot()\r
-\r
-    def _on_playlist_delete(self, playlist):\r
-        notebook = self.Parent.plotNotebook\r
-        index = self.Parent._GetPlaylistTab(playlist.name)\r
-        notebook.SetSelection(index)\r
-        notebook.DeletePage(notebook.GetSelection())\r
-        self.Parent.DeleteFromPlaylists(playlist_name)\r
-\r
-\r
-\r
-    # Command panel interface\r
-\r
-    def select_command(self, _class, method, command):\r
-        #self.select_plugin(plugin=command.plugin)\r
-        if 'assistant' in self._c:\r
-            self._c['assitant'].ChangeValue(command.help)\r
-        self._c['property editor'].clear()\r
-        for argument in command.arguments:\r
-            if argument.name == 'help':\r
-                continue\r
-\r
-            results = self.execute_command(\r
-                command=self._command_by_name('playlists'))\r
-            if not isinstance(results[-1], Success):\r
-                self._postprocess_text(command, results=results)\r
-                playlists = []\r
-            else:\r
-                playlists = results[0]\r
-\r
-            results = self.execute_command(\r
-                command=self._command_by_name('playlist curves'))\r
-            if not isinstance(results[-1], Success):\r
-                self._postprocess_text(command, results=results)\r
-                curves = []\r
-            else:\r
-                curves = results[0]\r
-\r
-            p = prop_from_argument(\r
-                argument, curves=curves, playlists=playlists)\r
-            if p == None:\r
-                continue  # property intentionally not handled (yet)\r
-            self._c['property editor'].append_property(p)\r
-\r
-        self.gui.config['selected command'] = command  # TODO: push to engine\r
-\r
-\r
-\r
-    # Note panel interface\r
-\r
-    def _on_update_note(self, _class, method, text):\r
-        """Sets the note for the active curve.\r
-        """\r
-        # TODO: note list interface in NotePanel.\r
-        self.execute_command(\r
-            command=self._command_by_name('set note'),\r
-            args={'note':text})\r
-\r
-\r
-\r
-    # Playlist panel interface\r
-\r
-    def _on_user_delete_playlist(self, _class, method, playlist):\r
-        pass\r
-\r
-    def _on_delete_playlist(self, _class, method, playlist):\r
-        if hasattr(playlist, 'path') and playlist.path != None:\r
-            os.remove(playlist.path)\r
-\r
-    def _on_user_delete_curve(self, _class, method, playlist, curve):\r
-        pass\r
-\r
-    def _on_delete_curve(self, _class, method, playlist, curve):\r
-        os.remove(curve.path)\r
-\r
-    def _on_set_selected_playlist(self, _class, method, playlist):\r
-        """TODO: playlists plugin with `jump to playlist`.\r
-        """\r
-        pass\r
-\r
-    def _on_set_selected_curve(self, _class, method, playlist, curve):\r
-        """Call the `jump to curve` command.\r
-\r
-        TODO: playlists plugin.\r
-        """\r
-        # TODO: jump to playlist, get playlist\r
-        index = playlist.index(curve)\r
-        results = self.execute_command(\r
-            command=self._command_by_name('jump to curve'),\r
-            args={'index':index})\r
-        if not isinstance(results[-1], Success):\r
-            return\r
-        #results = self.execute_command(\r
-        #    command=self._command_by_name('get playlist'))\r
-        #if not isinstance(results[-1], Success):\r
-        #    return\r
-        self.execute_command(\r
-            command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
-    # Navbar interface\r
-\r
-    def _next_curve(self, *args):\r
-        """Call the `next curve` command.\r
-        """\r
-        results = self.execute_command(\r
-            command=self._command_by_name('next curve'))\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
-\r
-    def _previous_curve(self, *args):\r
-        """Call the `previous curve` command.\r
-        """\r
-        results = self.execute_command(\r
-            command=self._command_by_name('previous curve'))\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
-    # Panel display handling\r
-\r
-    def _on_panel_visibility(self, _class, method, panel_name, visible):\r
-        pane = self._c['manager'].GetPane(panel_name)\r
-        pane.Show(visible)\r
-        #if we don't do the following, the Folders pane does not resize properly on hide/show\r
-        if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
-            #folders_size = pane.GetSize()\r
-            self.panelFolders.Fit()\r
-        self._c['manager'].Update()\r
-\r
-    def _setup_perspectives(self):\r
-        """Add perspectives to menubar and _perspectives.\r
-        """\r
-        self._perspectives = {\r
-            'Default': self._c['manager'].SavePerspective(),\r
-            }\r
-        path = self.gui.config['perspective path']\r
-        if os.path.isdir(path):\r
-            files = sorted(os.listdir(path))\r
-            for fname in files:\r
-                name, extension = os.path.splitext(fname)\r
-                if extension != self.gui.config['perspective extension']:\r
-                    continue\r
-                fpath = os.path.join(path, fname)\r
-                if not os.path.isfile(fpath):\r
-                    continue\r
-                perspective = None\r
-                with open(fpath, 'rU') as f:\r
-                    perspective = f.readline()\r
-                if perspective:\r
-                    self._perspectives[name] = perspective\r
-\r
-        selected_perspective = self.gui.config['active perspective']\r
-        if not self._perspectives.has_key(selected_perspective):\r
-            self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke\r
-\r
-        self._restore_perspective(selected_perspective, force=True)\r
-        self._update_perspective_menu()\r
-\r
-    def _update_perspective_menu(self):\r
-        self._c['menu bar']._c['perspective'].update(\r
-            sorted(self._perspectives.keys()),\r
-            self.gui.config['active perspective'])\r
-\r
-    def _save_perspective(self, perspective, perspective_dir, name,\r
-                          extension=None):\r
-        path = os.path.join(perspective_dir, name)\r
-        if extension != None:\r
-            path += extension\r
-        if not os.path.isdir(perspective_dir):\r
-            os.makedirs(perspective_dir)\r
-        with open(path, 'w') as f:\r
-            f.write(perspective)\r
-        self._perspectives[name] = perspective\r
-        self._restore_perspective(name)\r
-        self._update_perspective_menu()\r
-\r
-    def _delete_perspectives(self, perspective_dir, names,\r
-                             extension=None):\r
-        self.log.debug('remove perspectives %s from %s'\r
-                       % (names, perspective_dir))\r
-        for name in names:\r
-            path = os.path.join(perspective_dir, name)\r
-            if extension != None:\r
-                path += extension\r
-            os.remove(path)\r
-            del(self._perspectives[name])\r
-        self._update_perspective_menu()\r
-        if self.gui.config['active perspective'] in names:\r
-            self._restore_perspective('Default')\r
-        # TODO: does this bug still apply?\r
-        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
-        #   http://trac.wxwidgets.org/ticket/3258 \r
-        # ) that makes the radio item indicator in the menu disappear.\r
-        # The code should be fine once this issue is fixed.\r
-\r
-    def _restore_perspective(self, name, force=False):\r
-        if name != self.gui.config['active perspective'] or force == True:\r
-            self.log.debug('restore perspective %s' % name)\r
-            self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
-            self._c['manager'].LoadPerspective(self._perspectives[name])\r
-            self._c['manager'].Update()\r
-            for pane in self._c['manager'].GetAllPanes():\r
-                view = self._c['menu bar']._c['view']\r
-                if pane.name in view._c.keys():\r
-                    view._c[pane.name].Check(pane.window.IsShown())\r
-\r
-    def _on_save_perspective(self, *args):\r
-        perspective = self._c['manager'].SavePerspective()\r
-        name = self.gui.config['active perspective']\r
-        if name == 'Default':\r
-            name = 'New perspective'\r
-        name = select_save_file(\r
-            directory=self.gui.config['perspective path'],\r
-            name=name,\r
-            extension=self.gui.config['perspective extension'],\r
-            parent=self,\r
-            message='Enter a name for the new perspective:',\r
-            caption='Save perspective')\r
-        if name == None:\r
-            return\r
-        self._save_perspective(\r
-            perspective, self.gui.config['perspective path'], name=name,\r
-            extension=self.gui.config['perspective extension'])\r
-\r
-    def _on_delete_perspective(self, *args, **kwargs):\r
-        options = sorted([p for p in self._perspectives.keys()\r
-                          if p != 'Default'])\r
-        dialog = SelectionDialog(\r
-            options=options,\r
-            message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
-            button_id=wx.ID_DELETE,\r
-            selection_style='multiple',\r
-            parent=self,\r
-            title='Delete perspective(s)',\r
-            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
-        dialog.CenterOnScreen()\r
-        dialog.ShowModal()\r
-        names = [options[i] for i in dialog.selected]\r
-        dialog.Destroy()\r
-        self._delete_perspectives(\r
-            self.gui.config['perspective path'], names=names,\r
-            extension=self.gui.config['perspective extension'])\r
-\r
-    def _on_select_perspective(self, _class, method, name):\r
-        self._restore_perspective(name)\r
-\r
-\r
-\r
-class HookeApp (wx.App):\r
-    """A :class:`wx.App` wrapper around :class:`HookeFrame`.\r
-\r
-    Tosses up a splash screen and then loads :class:`HookeFrame` in\r
-    its own window.\r
-    """\r
-    def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):\r
-        self.gui = gui\r
-        self.commands = commands\r
-        self.inqueue = inqueue\r
-        self.outqueue = outqueue\r
-        super(HookeApp, self).__init__(*args, **kwargs)\r
-\r
-    def OnInit(self):\r
-        self.SetAppName('Hooke')\r
-        self.SetVendorName('')\r
-        self._setup_splash_screen()\r
-\r
-        height = int(self.gui.config['main height']) # HACK: config should convert\r
-        width = int(self.gui.config['main width'])\r
-        top = int(self.gui.config['main top'])\r
-        left = int(self.gui.config['main left'])\r
-\r
-        # Sometimes, the ini file gets confused and sets 'left' and\r
-        # 'top' to large negative numbers.  Here we catch and fix\r
-        # this.  Keep small negative numbers, the user might want\r
-        # those.\r
-        if left < -width:\r
-            left = 0\r
-        if top < -height:\r
-            top = 0\r
-\r
-        self._c = {\r
-            'frame': HookeFrame(\r
-                self.gui, self.commands, self.inqueue, self.outqueue,\r
-                parent=None, title='Hooke',\r
-                pos=(left, top), size=(width, height),\r
-                style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),\r
-            }\r
-        self._c['frame'].Show(True)\r
-        self.SetTopWindow(self._c['frame'])\r
-        return True\r
-\r
-    def _setup_splash_screen(self):\r
-        if self.gui.config['show splash screen'] == 'True': # HACK: config should decode\r
-            path = self.gui.config['splash screen image']\r
-            if os.path.isfile(path):\r
-                duration = int(self.gui.config['splash screen duration'])  # HACK: config should decode types\r
-                wx.SplashScreen(\r
-                    bitmap=wx.Image(path).ConvertToBitmap(),\r
-                    splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,\r
-                    milliseconds=duration,\r
-                    parent=None)\r
-                wx.Yield()\r
-                # For some reason splashDuration and sleep do not\r
-                # correspond to each other at least not on Windows.\r
-                # Maybe it's because duration is in milliseconds and\r
-                # sleep in seconds.  Thus we need to increase the\r
-                # sleep time a bit. A factor of 1.2 seems to work.\r
-                sleepFactor = 1.2\r
-                time.sleep(sleepFactor * duration / 1000)\r
-\r
-\r
-class GUI (UserInterface):\r
-    """wxWindows graphical user interface.\r
-    """\r
-    def __init__(self):\r
-        super(GUI, self).__init__(name='gui')\r
-\r
-    def default_settings(self):\r
-        """Return a list of :class:`hooke.config.Setting`\s for any\r
-        configurable UI settings.\r
-\r
-        The suggested section setting is::\r
-\r
-            Setting(section=self.setting_section, help=self.__doc__)\r
-        """\r
-        return [\r
-            Setting(section=self.setting_section, help=self.__doc__),\r
-            Setting(section=self.setting_section, option='icon image',\r
-                    value=os.path.join('doc', 'img', 'microscope.ico'),\r
-                    help='Path to the hooke icon image.'),\r
-            Setting(section=self.setting_section, option='show splash screen',\r
-                    value=True,\r
-                    help='Enable/disable the splash screen'),\r
-            Setting(section=self.setting_section, option='splash screen image',\r
-                    value=os.path.join('doc', 'img', 'hooke.jpg'),\r
-                    help='Path to the Hooke splash screen image.'),\r
-            Setting(section=self.setting_section, option='splash screen duration',\r
-                    value=1000,\r
-                    help='Duration of the splash screen in milliseconds.'),\r
-            Setting(section=self.setting_section, option='perspective path',\r
-                    value=os.path.join('resources', 'gui', 'perspective'),\r
-                    help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.\r
-            Setting(section=self.setting_section, option='perspective extension',\r
-                    value='.txt',\r
-                    help='Extension for perspective files.'),\r
-            Setting(section=self.setting_section, option='hide extensions',\r
-                    value=False,\r
-                    help='Hide file extensions when displaying names.'),\r
-            Setting(section=self.setting_section, option='plot legend',\r
-                    value=True,\r
-                    help='Enable/disable the plot legend.'),\r
-            Setting(section=self.setting_section, option='plot SI format',\r
-                    value='True',\r
-                    help='Enable/disable SI plot axes numbering.'),\r
-            Setting(section=self.setting_section, option='plot decimals',\r
-                    value=2,\r
-                    help='Number of decimal places to show if "plot SI format" is enabled.'),\r
-            Setting(section=self.setting_section, option='folders-workdir',\r
-                    value='.',\r
-                    help='This should probably go...'),\r
-            Setting(section=self.setting_section, option='folders-filters',\r
-                    value='.',\r
-                    help='This should probably go...'),\r
-            Setting(section=self.setting_section, option='active perspective',\r
-                    value='Default',\r
-                    help='Name of active perspective file (or "Default").'),\r
-            Setting(section=self.setting_section, option='folders-filter-index',\r
-                    value='0',\r
-                    help='This should probably go...'),\r
-            Setting(section=self.setting_section, option='main height',\r
-                    value=450,\r
-                    help='Height of main window in pixels.'),\r
-            Setting(section=self.setting_section, option='main width',\r
-                    value=800,\r
-                    help='Width of main window in pixels.'),\r
-            Setting(section=self.setting_section, option='main top',\r
-                    value=0,\r
-                    help='Pixels from screen top to top of main window.'),\r
-            Setting(section=self.setting_section, option='main left',\r
-                    value=0,\r
-                    help='Pixels from screen left to left of main window.'),            \r
-            Setting(section=self.setting_section, option='selected command',\r
-                    value='load playlist',\r
-                    help='Name of the initially selected command.'),\r
-            ]\r
-\r
-    def _app(self, commands, ui_to_command_queue, command_to_ui_queue):\r
-        redirect = True\r
-        if __debug__:\r
-            redirect=False\r
-        app = HookeApp(gui=self,\r
-                       commands=commands,\r
-                       inqueue=ui_to_command_queue,\r
-                       outqueue=command_to_ui_queue,\r
-                       redirect=redirect)\r
-        return app\r
-\r
-    def run(self, commands, ui_to_command_queue, command_to_ui_queue):\r
-        app = self._app(commands, ui_to_command_queue, command_to_ui_queue)\r
-        app.MainLoop()\r
+# Copyright (C) 2008-2010 Fabrizio Benedetti
+#                         Massimo Sandal <devicerandom@gmail.com>
+#                         Rolf Schmidt <rschmidt@alcor.concordia.ca>
+#                         W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.
+
+"""
+
+WX_GOOD=['2.8']
+
+import wxversion
+wxversion.select(WX_GOOD)
+
+import copy
+import logging
+import os
+import os.path
+import platform
+import shutil
+import time
+
+import wx.html
+import wx.aui as aui
+import wx.lib.evtmgr as evtmgr
+# wxPropertyGrid is included in wxPython >= 2.9.1, see
+#   http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
+# until then, we'll avoid it because of the *nix build problems.
+#import wx.propgrid as wxpg
+
+from ...command import CommandExit, Exit, Success, Failure, Command, Argument
+from ...config import Setting
+from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig
+from ...ui import UserInterface, CommandMessage
+from .dialog.selection import Selection as SelectionDialog
+from .dialog.save_file import select_save_file
+from . import menu as menu
+from . import navbar as navbar
+from . import panel as panel
+from .panel.propertyeditor import prop_from_argument, prop_from_setting
+from . import statusbar as statusbar
+
+
+class HookeFrame (wx.Frame):
+    """The main Hooke-interface window.    
+    """
+    def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
+        super(HookeFrame, self).__init__(*args, **kwargs)
+        self.log = logging.getLogger('hooke')
+        self.gui = gui
+        self.commands = commands
+        self.inqueue = inqueue
+        self.outqueue = outqueue
+        self._perspectives = {}  # {name: perspective_str}
+        self._c = {}
+
+        self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))
+
+        # setup frame manager
+        self._c['manager'] = aui.AuiManager()
+        self._c['manager'].SetManagedWindow(self)
+
+        # set the gradient and drag styles
+        self._c['manager'].GetArtProvider().SetMetric(
+            aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)
+        self._c['manager'].SetFlags(
+            self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)
+
+        # Min size for the frame itself isn't completely done.  See
+        # the end of FrameManager::Update() for the test code. For
+        # now, just hard code a frame minimum size.
+        #self.SetMinSize(wx.Size(500, 500))
+
+        self._setup_panels()
+        self._setup_toolbars()
+        self._c['manager'].Update()  # commit pending changes
+
+        # Create the menubar after the panes so that the default
+        # perspective is created with all panes open
+        panels = [p for p in self._c.values() if isinstance(p, panel.Panel)]
+        self._c['menu bar'] = menu.HookeMenuBar(
+            parent=self,
+            panels=panels,
+            callbacks={
+                'close': self._on_close,
+                'about': self._on_about,
+                'view_panel': self._on_panel_visibility,
+                'save_perspective': self._on_save_perspective,
+                'delete_perspective': self._on_delete_perspective,
+                'select_perspective': self._on_select_perspective,
+                })
+        self.SetMenuBar(self._c['menu bar'])
+
+        self._c['status bar'] = statusbar.StatusBar(
+            parent=self,
+            style=wx.ST_SIZEGRIP)
+        self.SetStatusBar(self._c['status bar'])
+
+        self._setup_perspectives()
+        self._bind_events()
+
+        self.execute_command(
+                command=self._command_by_name('load playlist'),
+                args={'input':'test/data/vclamp_picoforce/playlist'},
+                )
+        return # TODO: cleanup
+        self.playlists = self._c['playlist'].Playlists
+        self._displayed_plot = None
+        #load default list, if possible
+        self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists'))
+
+
+    # GUI maintenance
+
+    def _setup_panels(self):
+        client_size = self.GetClientSize()
+        for p,style in [
+#            ('folders', wx.GenericDirCtrl(
+#                    parent=self,
+#                    dir=self.gui.config['folders-workdir'],
+#                    size=(200, 250),
+#                    style=wx.DIRCTRL_SHOW_FILTERS,
+#                    filter=self.gui.config['folders-filters'],
+#                    defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'),  #HACK: config should convert
+            (panel.PANELS['playlist'](
+                    callbacks={
+                        'delete_playlist':self._on_user_delete_playlist,
+                        '_delete_playlist':self._on_delete_playlist,
+                        'delete_curve':self._on_user_delete_curve,
+                        '_delete_curve':self._on_delete_curve,
+                        '_on_set_selected_playlist':self._on_set_selected_playlist,
+                        '_on_set_selected_curve':self._on_set_selected_curve,
+                        },
+                    parent=self,
+                    style=wx.WANTS_CHARS|wx.NO_BORDER,
+                    # WANTS_CHARS so the panel doesn't eat the Return key.
+#                    size=(160, 200),
+                    ), 'left'),
+            (panel.PANELS['note'](
+                    callbacks = {
+                        '_on_update':self._on_update_note,
+                        },
+                    parent=self,
+                    style=wx.WANTS_CHARS|wx.NO_BORDER,
+#                    size=(160, 200),
+                    ), 'left'),
+#            ('notebook', Notebook(
+#                    parent=self,
+#                    pos=wx.Point(client_size.x, client_size.y),
+#                    size=wx.Size(430, 200),
+#                    style=aui.AUI_NB_DEFAULT_STYLE
+#                    | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),
+            (panel.PANELS['commands'](
+                    commands=self.commands,
+                    selected=self.gui.config['selected command'],
+                    callbacks={
+                        'execute': self.execute_command,
+                        'select_plugin': self.select_plugin,
+                        'select_command': self.select_command,
+#                        'selection_changed': self.panelProperties.select(self, method, command),  #SelectedTreeItem = selected_item,
+                        },
+                    parent=self,
+                    style=wx.WANTS_CHARS|wx.NO_BORDER,
+                    # WANTS_CHARS so the panel doesn't eat the Return key.
+#                    size=(160, 200),
+                    ), 'right'),
+            (panel.PANELS['propertyeditor'](
+                    callbacks={},
+                    parent=self,
+                    style=wx.WANTS_CHARS,
+                    # WANTS_CHARS so the panel doesn't eat the Return key.
+                    ), 'center'),
+#            ('assistant', wx.TextCtrl(
+#                    parent=self,
+#                    pos=wx.Point(0, 0),
+#                    size=wx.Size(150, 90),
+#                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),
+            (panel.PANELS['plot'](
+                    callbacks={
+                        },
+                    parent=self,
+                    style=wx.WANTS_CHARS|wx.NO_BORDER,
+                    # WANTS_CHARS so the panel doesn't eat the Return key.
+#                    size=(160, 200),
+                    ), 'center'),
+            (panel.PANELS['output'](
+                    parent=self,
+                    pos=wx.Point(0, 0),
+                    size=wx.Size(150, 90),
+                    style=wx.TE_READONLY|wx.NO_BORDER|wx.TE_MULTILINE),
+             'bottom'),
+#            ('results', panel.results.Results(self), 'bottom'),
+            ]:
+            self._add_panel(p, style)
+        #self._c['assistant'].SetEditable(False)
+
+    def _add_panel(self, panel, style):
+        self._c[panel.name] = panel
+        m_name = panel.managed_name
+        info = aui.AuiPaneInfo().Name(m_name).Caption(m_name)
+        info.PaneBorder(False).CloseButton(True).MaximizeButton(False)
+        if style == 'top':
+            info.Top()
+        elif style == 'center':
+            info.CenterPane()
+        elif style == 'left':
+            info.Left()
+        elif style == 'right':
+            info.Right()
+        else:
+            assert style == 'bottom', style
+            info.Bottom()
+        self._c['manager'].AddPane(panel, info)
+
+    def _setup_toolbars(self):
+        self._c['navigation bar'] = navbar.NavBar(
+            callbacks={
+                'next': self._next_curve,
+                'previous': self._previous_curve,
+                },
+            parent=self,
+            style=wx.TB_FLAT | wx.TB_NODIVIDER)
+        self._c['manager'].AddPane(
+            self._c['navigation bar'],
+            aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'
+                ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False
+                ).RightDockable(False))
+
+    def _bind_events(self):
+        # TODO: figure out if we can use the eventManager for menu
+        # ranges and events of 'self' without raising an assertion
+        # fail error.
+        self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)
+        self.Bind(wx.EVT_SIZE, self._on_size)
+        self.Bind(wx.EVT_CLOSE, self._on_close)
+        self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)
+        self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)
+
+        return # TODO: cleanup
+        treeCtrl = self._c['folders'].GetTreeCtrl()
+        treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)
+        
+        #property editor
+        self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
+        #results panel
+        self.panelResults.results_list.OnCheckItem = self.OnResultsCheck
+
+    def _on_about(self, *args):
+        dialog = wx.MessageDialog(
+            parent=self,
+            message=self.gui._splash_text(extra_info={
+                    'get-details':'click "Help -> License"'},
+                                          wrap=False),
+            caption='About Hooke',
+            style=wx.OK|wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def _on_close(self, *args):
+        self.log.info('closing GUI framework')
+        # apply changes
+        self.gui.config['main height'] = str(self.GetSize().GetHeight())
+        self.gui.config['main left'] = str(self.GetPosition()[0])
+        self.gui.config['main top'] = str(self.GetPosition()[1])
+        self.gui.config['main width'] = str(self.GetSize().GetWidth())
+        # push changes back to Hooke.config?
+        self._c['manager'].UnInit()
+        del self._c['manager']
+        self.Destroy()
+
+
+
+    # Panel utility functions
+
+    def _file_name(self, name):
+        """Cleanup names according to configured preferences.
+        """
+        if self.gui.config['hide extensions'] == 'True':  # HACK: config should decode
+            name,ext = os.path.splitext(name)
+        return name
+
+
+
+    # Command handling
+
+    def _command_by_name(self, name):
+        cs = [c for c in self.commands if c.name == name]
+        if len(cs) == 0:
+            raise KeyError(name)
+        elif len(cs) > 1:
+            raise Exception('Multiple commands named "%s"' % name)
+        return cs[0]
+
+    def execute_command(self, _class=None, method=None,
+                        command=None, args=None):
+        if args == None:
+            args = {}
+        if ('property editor' in self._c
+            and self.gui.config['selected command'] == command):
+            arg_names = [arg.name for arg in command.arguments]
+            for name,value in self._c['property editor'].get_values().items():
+                if name in arg_names:
+                    args[name] = value
+        self.log.debug('executing %s with %s' % (command.name, args))
+        self.inqueue.put(CommandMessage(command, args))
+        results = []
+        while True:
+            msg = self.outqueue.get()
+            results.append(msg)
+            if isinstance(msg, Exit):
+                self._on_close()
+                break
+            elif isinstance(msg, CommandExit):
+                # TODO: display command complete
+                break
+            elif isinstance(msg, ReloadUserInterfaceConfig):
+                self.gui.reload_config(msg.config)
+                continue
+            elif isinstance(msg, Request):
+                h = handler.HANDLERS[msg.type]
+                h.run(self, msg)  # TODO: pause for response?
+                continue
+        pp = getattr(
+            self, '_postprocess_%s' % command.name.replace(' ', '_'),
+            self._postprocess_text)
+        pp(command=command, args=args, results=results)
+        return results
+
+    def _handle_request(self, msg):
+        """Repeatedly try to get a response to `msg`.
+        """
+        if prompt == None:
+            raise NotImplementedError('_%s_request_prompt' % msg.type)
+        prompt_string = prompt(msg)
+        parser = getattr(self, '_%s_request_parser' % msg.type, None)
+        if parser == None:
+            raise NotImplementedError('_%s_request_parser' % msg.type)
+        error = None
+        while True:
+            if error != None:
+                self.cmd.stdout.write(''.join([
+                        error.__class__.__name__, ': ', str(error), '\n']))
+            self.cmd.stdout.write(prompt_string)
+            value = parser(msg, self.cmd.stdin.readline())
+            try:
+                response = msg.response(value)
+                break
+            except ValueError, error:
+                continue
+        self.inqueue.put(response)
+
+
+
+    # Command-specific postprocessing
+
+    def _postprocess_text(self, command, args={}, results=[]):
+        """Print the string representation of the results to the Results window.
+
+        This is similar to :class:`~hooke.ui.commandline.DoCommand`'s
+        approach, except that :class:`~hooke.ui.commandline.DoCommand`
+        doesn't print some internally handled messages
+        (e.g. :class:`~hooke.interaction.ReloadUserInterfaceConfig`).
+        """
+        for result in results:
+            if isinstance(result, CommandExit):
+                self._c['output'].write(result.__class__.__name__+'\n')
+            self._c['output'].write(str(result).rstrip()+'\n')
+
+    def _postprocess_load_playlist(self, command, args={}, results=None):
+        """Update `self` to show the playlist.
+        """
+        if not isinstance(results[-1], Success):
+            self._postprocess_text(command, results=results)
+            return
+        assert len(results) == 2, results
+        playlist = results[0]
+        self._c['playlist']._c['tree'].add_playlist(playlist)
+
+    def _postprocess_get_playlist(self, command, args={}, results=[]):
+        if not isinstance(results[-1], Success):
+            self._postprocess_text(command, results=results)
+            return
+        assert len(results) == 2, results
+        playlist = results[0]
+        self._c['playlist']._c['tree'].update_playlist(playlist)
+
+    def _postprocess_get_curve(self, command, args={}, results=[]):
+        """Update `self` to show the curve.
+        """
+        if not isinstance(results[-1], Success):
+            self._postprocess_text(command, results=results)
+            return
+        assert len(results) == 2, results
+        curve = results[0]
+        if args.get('curve', None) == None:
+            # the command defaults to the current curve of the current playlist
+            results = self.execute_command(
+                command=self._command_by_name('get playlist'))
+            playlist = results[0]
+        else:
+            raise NotImplementedError()
+        if 'note' in self._c:
+            print sorted(curve.info.keys())
+            self._c['note'].set_text(curve.info['note'])
+        if 'playlist' in self._c:
+            self._c['playlist']._c['tree'].set_selected_curve(
+                playlist, curve)
+        if 'plot' in self._c:
+            self._c['plot'].set_curve(curve, config=self.gui.config)
+
+    def _postprocess_next_curve(self, command, args={}, results=[]):
+        """No-op.  Only call 'next curve' via `self._next_curve()`.
+        """
+        pass
+
+    def _postprocess_previous_curve(self, command, args={}, results=[]):
+        """No-op.  Only call 'previous curve' via `self._previous_curve()`.
+        """
+        pass
+
+    def _postprocess_zero_block_surface_contact_point(
+        self, command, args={}, results=[]):
+        """Update the curve, since the available columns may have changed.
+        """
+        if isinstance(results[-1], Success):
+            self.execute_command(
+                command=self._command_by_name('get curve'))
+    def _postprocess_add_block_force_array(
+        self, command, args={}, results=[]):
+        """Update the curve, since the available columns may have changed.
+        """
+        if isinstance(results[-1], Success):
+            self.execute_command(
+                command=self._command_by_name('get curve'))
+
+
+
+    # TODO: cruft
+
+    def _GetActiveFileIndex(self):
+        lib.playlist.Playlist = self.GetActivePlaylist()
+        #get the selected item from the tree
+        selected_item = self._c['playlist']._c['tree'].GetSelection()
+        #test if a playlist or a curve was double-clicked
+        if self._c['playlist']._c['tree'].ItemHasChildren(selected_item):
+            return -1
+        else:
+            count = 0
+            selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)
+            while selected_item.IsOk():
+                count += 1
+                selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)
+            return count
+
+    def _GetPlaylistTab(self, name):
+        for index, page in enumerate(self._c['notebook']._tabs._pages):
+            if page.caption == name:
+                return index
+        return -1
+
+    def select_plugin(self, _class=None, method=None, plugin=None):
+        pass
+
+    def AddPlaylistFromFiles(self, files=[], name='Untitled'):
+        if files:
+            playlist = lib.playlist.Playlist(self, self.drivers)
+            for item in files:
+                playlist.add_curve(item)
+        if playlist.count > 0:
+            playlist.name = self._GetUniquePlaylistName(name)
+            playlist.reset()
+            self.AddTayliss(playlist)
+
+    def AppliesPlotmanipulator(self, name):
+        '''
+        Returns True if the plotmanipulator 'name' is applied, False otherwise
+        name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')
+        '''
+        return self.GetBoolFromConfig('core', 'plotmanipulators', name)
+
+    def ApplyPlotmanipulators(self, plot, plot_file):
+        '''
+        Apply all active plotmanipulators.
+        '''
+        if plot is not None and plot_file is not None:
+            manipulated_plot = copy.deepcopy(plot)
+            for plotmanipulator in self.plotmanipulators:
+                if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):
+                    manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)
+            return manipulated_plot
+
+    def GetActiveFigure(self):
+        playlist_name = self.GetActivePlaylistName()
+        figure = self.playlists[playlist_name].figure
+        if figure is not None:
+            return figure
+        return None
+
+    def GetActiveFile(self):
+        playlist = self.GetActivePlaylist()
+        if playlist is not None:
+            return playlist.get_active_file()
+        return None
+
+    def GetActivePlot(self):
+        playlist = self.GetActivePlaylist()
+        if playlist is not None:
+            return playlist.get_active_file().plot
+        return None
+
+    def GetDisplayedPlot(self):
+        plot = copy.deepcopy(self.displayed_plot)
+        #plot.curves = []
+        #plot.curves = copy.deepcopy(plot.curves)
+        return plot
+
+    def GetDisplayedPlotCorrected(self):
+        plot = copy.deepcopy(self.displayed_plot)
+        plot.curves = []
+        plot.curves = copy.deepcopy(plot.corrected_curves)
+        return plot
+
+    def GetDisplayedPlotRaw(self):
+        plot = copy.deepcopy(self.displayed_plot)
+        plot.curves = []
+        plot.curves = copy.deepcopy(plot.raw_curves)
+        return plot
+
+    def GetDockArt(self):
+        return self._c['manager'].GetArtProvider()
+
+    def GetPlotmanipulator(self, name):
+        '''
+        Returns a plot manipulator function from its name
+        '''
+        for plotmanipulator in self.plotmanipulators:
+            if plotmanipulator.name == name:
+                return plotmanipulator
+        return None
+
+    def HasPlotmanipulator(self, name):
+        '''
+        returns True if the plotmanipulator 'name' is loaded, False otherwise
+        '''
+        for plotmanipulator in self.plotmanipulators:
+            if plotmanipulator.command == name:
+                return True
+        return False
+
+
+    def _on_dir_ctrl_left_double_click(self, event):
+        file_path = self.panelFolders.GetPath()
+        if os.path.isfile(file_path):
+            if file_path.endswith('.hkp'):
+                self.do_loadlist(file_path)
+        event.Skip()
+
+    def _on_erase_background(self, event):
+        event.Skip()
+
+    def _on_notebook_page_close(self, event):
+        ctrl = event.GetEventObject()
+        playlist_name = ctrl.GetPageText(ctrl._curpage)
+        self.DeleteFromPlaylists(playlist_name)
+
+    def OnPaneClose(self, event):
+        event.Skip()
+
+    def OnPropGridChanged (self, event):
+        prop = event.GetProperty()
+        if prop:
+            item_section = self.panelProperties.SelectedTreeItem
+            item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)
+            plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)
+            config = self.gui.config[plugin]
+            property_section = self._c['commands']._c['tree'].GetItemText(item_section)
+            property_key = prop.GetName()
+            property_value = prop.GetDisplayedString()
+
+            config[property_section][property_key]['value'] = property_value
+
+    def OnResultsCheck(self, index, flag):
+        results = self.GetActivePlot().results
+        if results.has_key(self.results_str):
+            results[self.results_str].results[index].visible = flag
+            results[self.results_str].update()
+            self.UpdatePlot()
+
+
+    def _on_size(self, event):
+        event.Skip()
+
+    def UpdatePlaylistsTreeSelection(self):
+        playlist = self.GetActivePlaylist()
+        if playlist is not None:
+            if playlist.index >= 0:
+                self._c['status bar'].set_playlist(playlist)
+                self.UpdateNote()
+                self.UpdatePlot()
+
+    def _on_curve_select(self, playlist, curve):
+        #create the plot tab and add playlist to the dictionary
+        plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))
+        notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)
+        #tab_index = self._c['notebook'].GetSelection()
+        playlist.figure = plotPanel.get_figure()
+        self.playlists[playlist.name] = playlist
+        #self.playlists[playlist.name] = [playlist, figure]
+        self._c['status bar'].set_playlist(playlist)
+        self.UpdateNote()
+        self.UpdatePlot()
+
+
+    def _on_playlist_left_doubleclick(self):
+        index = self._c['notebook'].GetSelection()
+        current_playlist = self._c['notebook'].GetPageText(index)
+        if current_playlist != playlist_name:
+            index = self._GetPlaylistTab(playlist_name)
+            self._c['notebook'].SetSelection(index)
+        self._c['status bar'].set_playlist(playlist)
+        self.UpdateNote()
+        self.UpdatePlot()
+
+    def _on_playlist_delete(self, playlist):
+        notebook = self.Parent.plotNotebook
+        index = self.Parent._GetPlaylistTab(playlist.name)
+        notebook.SetSelection(index)
+        notebook.DeletePage(notebook.GetSelection())
+        self.Parent.DeleteFromPlaylists(playlist_name)
+
+
+
+    # Command panel interface
+
+    def select_command(self, _class, method, command):
+        #self.select_plugin(plugin=command.plugin)
+        if 'assistant' in self._c:
+            self._c['assitant'].ChangeValue(command.help)
+        self._c['property editor'].clear()
+        for argument in command.arguments:
+            if argument.name == 'help':
+                continue
+
+            results = self.execute_command(
+                command=self._command_by_name('playlists'))
+            if not isinstance(results[-1], Success):
+                self._postprocess_text(command, results=results)
+                playlists = []
+            else:
+                playlists = results[0]
+
+            results = self.execute_command(
+                command=self._command_by_name('playlist curves'))
+            if not isinstance(results[-1], Success):
+                self._postprocess_text(command, results=results)
+                curves = []
+            else:
+                curves = results[0]
+
+            p = prop_from_argument(
+                argument, curves=curves, playlists=playlists)
+            if p == None:
+                continue  # property intentionally not handled (yet)
+            self._c['property editor'].append_property(p)
+
+        self.gui.config['selected command'] = command  # TODO: push to engine
+
+
+
+    # Note panel interface
+
+    def _on_update_note(self, _class, method, text):
+        """Sets the note for the active curve.
+        """
+        # TODO: note list interface in NotePanel.
+        self.execute_command(
+            command=self._command_by_name('set note'),
+            args={'note':text})
+
+
+
+    # Playlist panel interface
+
+    def _on_user_delete_playlist(self, _class, method, playlist):
+        pass
+
+    def _on_delete_playlist(self, _class, method, playlist):
+        if hasattr(playlist, 'path') and playlist.path != None:
+            os.remove(playlist.path)
+
+    def _on_user_delete_curve(self, _class, method, playlist, curve):
+        pass
+
+    def _on_delete_curve(self, _class, method, playlist, curve):
+        os.remove(curve.path)
+
+    def _on_set_selected_playlist(self, _class, method, playlist):
+        """TODO: playlists plugin with `jump to playlist`.
+        """
+        pass
+
+    def _on_set_selected_curve(self, _class, method, playlist, curve):
+        """Call the `jump to curve` command.
+
+        TODO: playlists plugin.
+        """
+        # TODO: jump to playlist, get playlist
+        index = playlist.index(curve)
+        results = self.execute_command(
+            command=self._command_by_name('jump to curve'),
+            args={'index':index})
+        if not isinstance(results[-1], Success):
+            return
+        #results = self.execute_command(
+        #    command=self._command_by_name('get playlist'))
+        #if not isinstance(results[-1], Success):
+        #    return
+        self.execute_command(
+            command=self._command_by_name('get curve'))
+
+
+
+    # Navbar interface
+
+    def _next_curve(self, *args):
+        """Call the `next curve` command.
+        """
+        results = self.execute_command(
+            command=self._command_by_name('next curve'))
+        if isinstance(results[-1], Success):
+            self.execute_command(
+                command=self._command_by_name('get curve'))
+
+    def _previous_curve(self, *args):
+        """Call the `previous curve` command.
+        """
+        results = self.execute_command(
+            command=self._command_by_name('previous curve'))
+        if isinstance(results[-1], Success):
+            self.execute_command(
+                command=self._command_by_name('get curve'))
+
+
+
+    # Panel display handling
+
+    def _on_panel_visibility(self, _class, method, panel_name, visible):
+        pane = self._c['manager'].GetPane(panel_name)
+        pane.Show(visible)
+        #if we don't do the following, the Folders pane does not resize properly on hide/show
+        if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():
+            #folders_size = pane.GetSize()
+            self.panelFolders.Fit()
+        self._c['manager'].Update()
+
+    def _setup_perspectives(self):
+        """Add perspectives to menubar and _perspectives.
+        """
+        self._perspectives = {
+            'Default': self._c['manager'].SavePerspective(),
+            }
+        path = self.gui.config['perspective path']
+        if os.path.isdir(path):
+            files = sorted(os.listdir(path))
+            for fname in files:
+                name, extension = os.path.splitext(fname)
+                if extension != self.gui.config['perspective extension']:
+                    continue
+                fpath = os.path.join(path, fname)
+                if not os.path.isfile(fpath):
+                    continue
+                perspective = None
+                with open(fpath, 'rU') as f:
+                    perspective = f.readline()
+                if perspective:
+                    self._perspectives[name] = perspective
+
+        selected_perspective = self.gui.config['active perspective']
+        if not self._perspectives.has_key(selected_perspective):
+            self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke
+
+        self._restore_perspective(selected_perspective, force=True)
+        self._update_perspective_menu()
+
+    def _update_perspective_menu(self):
+        self._c['menu bar']._c['perspective'].update(
+            sorted(self._perspectives.keys()),
+            self.gui.config['active perspective'])
+
+    def _save_perspective(self, perspective, perspective_dir, name,
+                          extension=None):
+        path = os.path.join(perspective_dir, name)
+        if extension != None:
+            path += extension
+        if not os.path.isdir(perspective_dir):
+            os.makedirs(perspective_dir)
+        with open(path, 'w') as f:
+            f.write(perspective)
+        self._perspectives[name] = perspective
+        self._restore_perspective(name)
+        self._update_perspective_menu()
+
+    def _delete_perspectives(self, perspective_dir, names,
+                             extension=None):
+        self.log.debug('remove perspectives %s from %s'
+                       % (names, perspective_dir))
+        for name in names:
+            path = os.path.join(perspective_dir, name)
+            if extension != None:
+                path += extension
+            os.remove(path)
+            del(self._perspectives[name])
+        self._update_perspective_menu()
+        if self.gui.config['active perspective'] in names:
+            self._restore_perspective('Default')
+        # TODO: does this bug still apply?
+        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258
+        #   http://trac.wxwidgets.org/ticket/3258 
+        # ) that makes the radio item indicator in the menu disappear.
+        # The code should be fine once this issue is fixed.
+
+    def _restore_perspective(self, name, force=False):
+        if name != self.gui.config['active perspective'] or force == True:
+            self.log.debug('restore perspective %s' % name)
+            self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke
+            self._c['manager'].LoadPerspective(self._perspectives[name])
+            self._c['manager'].Update()
+            for pane in self._c['manager'].GetAllPanes():
+                view = self._c['menu bar']._c['view']
+                if pane.name in view._c.keys():
+                    view._c[pane.name].Check(pane.window.IsShown())
+
+    def _on_save_perspective(self, *args):
+        perspective = self._c['manager'].SavePerspective()
+        name = self.gui.config['active perspective']
+        if name == 'Default':
+            name = 'New perspective'
+        name = select_save_file(
+            directory=self.gui.config['perspective path'],
+            name=name,
+            extension=self.gui.config['perspective extension'],
+            parent=self,
+            message='Enter a name for the new perspective:',
+            caption='Save perspective')
+        if name == None:
+            return
+        self._save_perspective(
+            perspective, self.gui.config['perspective path'], name=name,
+            extension=self.gui.config['perspective extension'])
+
+    def _on_delete_perspective(self, *args, **kwargs):
+        options = sorted([p for p in self._perspectives.keys()
+                          if p != 'Default'])
+        dialog = SelectionDialog(
+            options=options,
+            message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",
+            button_id=wx.ID_DELETE,
+            selection_style='multiple',
+            parent=self,
+            title='Delete perspective(s)',
+            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
+        dialog.CenterOnScreen()
+        dialog.ShowModal()
+        names = [options[i] for i in dialog.selected]
+        dialog.Destroy()
+        self._delete_perspectives(
+            self.gui.config['perspective path'], names=names,
+            extension=self.gui.config['perspective extension'])
+
+    def _on_select_perspective(self, _class, method, name):
+        self._restore_perspective(name)
+
+
+
+class HookeApp (wx.App):
+    """A :class:`wx.App` wrapper around :class:`HookeFrame`.
+
+    Tosses up a splash screen and then loads :class:`HookeFrame` in
+    its own window.
+    """
+    def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
+        self.gui = gui
+        self.commands = commands
+        self.inqueue = inqueue
+        self.outqueue = outqueue
+        super(HookeApp, self).__init__(*args, **kwargs)
+
+    def OnInit(self):
+        self.SetAppName('Hooke')
+        self.SetVendorName('')
+        self._setup_splash_screen()
+
+        height = int(self.gui.config['main height']) # HACK: config should convert
+        width = int(self.gui.config['main width'])
+        top = int(self.gui.config['main top'])
+        left = int(self.gui.config['main left'])
+
+        # Sometimes, the ini file gets confused and sets 'left' and
+        # 'top' to large negative numbers.  Here we catch and fix
+        # this.  Keep small negative numbers, the user might want
+        # those.
+        if left < -width:
+            left = 0
+        if top < -height:
+            top = 0
+
+        self._c = {
+            'frame': HookeFrame(
+                self.gui, self.commands, self.inqueue, self.outqueue,
+                parent=None, title='Hooke',
+                pos=(left, top), size=(width, height),
+                style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),
+            }
+        self._c['frame'].Show(True)
+        self.SetTopWindow(self._c['frame'])
+        return True
+
+    def _setup_splash_screen(self):
+        if self.gui.config['show splash screen'] == 'True': # HACK: config should decode
+            path = self.gui.config['splash screen image']
+            if os.path.isfile(path):
+                duration = int(self.gui.config['splash screen duration'])  # HACK: config should decode types
+                wx.SplashScreen(
+                    bitmap=wx.Image(path).ConvertToBitmap(),
+                    splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
+                    milliseconds=duration,
+                    parent=None)
+                wx.Yield()
+                # For some 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.
+                sleepFactor = 1.2
+                time.sleep(sleepFactor * duration / 1000)
+
+
+class GUI (UserInterface):
+    """wxWindows graphical user interface.
+    """
+    def __init__(self):
+        super(GUI, self).__init__(name='gui')
+
+    def default_settings(self):
+        """Return a list of :class:`hooke.config.Setting`\s for any
+        configurable UI settings.
+
+        The suggested section setting is::
+
+            Setting(section=self.setting_section, help=self.__doc__)
+        """
+        return [
+            Setting(section=self.setting_section, help=self.__doc__),
+            Setting(section=self.setting_section, option='icon image',
+                    value=os.path.join('doc', 'img', 'microscope.ico'),
+                    help='Path to the hooke icon image.'),
+            Setting(section=self.setting_section, option='show splash screen',
+                    value=True,
+                    help='Enable/disable the splash screen'),
+            Setting(section=self.setting_section, option='splash screen image',
+                    value=os.path.join('doc', 'img', 'hooke.jpg'),
+                    help='Path to the Hooke splash screen image.'),
+            Setting(section=self.setting_section, option='splash screen duration',
+                    value=1000,
+                    help='Duration of the splash screen in milliseconds.'),
+            Setting(section=self.setting_section, option='perspective path',
+                    value=os.path.join('resources', 'gui', 'perspective'),
+                    help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.
+            Setting(section=self.setting_section, option='perspective extension',
+                    value='.txt',
+                    help='Extension for perspective files.'),
+            Setting(section=self.setting_section, option='hide extensions',
+                    value=False,
+                    help='Hide file extensions when displaying names.'),
+            Setting(section=self.setting_section, option='plot legend',
+                    value=True,
+                    help='Enable/disable the plot legend.'),
+            Setting(section=self.setting_section, option='plot SI format',
+                    value='True',
+                    help='Enable/disable SI plot axes numbering.'),
+            Setting(section=self.setting_section, option='plot decimals',
+                    value=2,
+                    help='Number of decimal places to show if "plot SI format" is enabled.'),
+            Setting(section=self.setting_section, option='folders-workdir',
+                    value='.',
+                    help='This should probably go...'),
+            Setting(section=self.setting_section, option='folders-filters',
+                    value='.',
+                    help='This should probably go...'),
+            Setting(section=self.setting_section, option='active perspective',
+                    value='Default',
+                    help='Name of active perspective file (or "Default").'),
+            Setting(section=self.setting_section, option='folders-filter-index',
+                    value='0',
+                    help='This should probably go...'),
+            Setting(section=self.setting_section, option='main height',
+                    value=450,
+                    help='Height of main window in pixels.'),
+            Setting(section=self.setting_section, option='main width',
+                    value=800,
+                    help='Width of main window in pixels.'),
+            Setting(section=self.setting_section, option='main top',
+                    value=0,
+                    help='Pixels from screen top to top of main window.'),
+            Setting(section=self.setting_section, option='main left',
+                    value=0,
+                    help='Pixels from screen left to left of main window.'),            
+            Setting(section=self.setting_section, option='selected command',
+                    value='load playlist',
+                    help='Name of the initially selected command.'),
+            ]
+
+    def _app(self, commands, ui_to_command_queue, command_to_ui_queue):
+        redirect = True
+        if __debug__:
+            redirect=False
+        app = HookeApp(gui=self,
+                       commands=commands,
+                       inqueue=ui_to_command_queue,
+                       outqueue=command_to_ui_queue,
+                       redirect=redirect)
+        return app
+
+    def run(self, commands, ui_to_command_queue, command_to_ui_queue):
+        app = self._app(commands, ui_to_command_queue, command_to_ui_queue)
+        app.MainLoop()
index 377ff2a..35c244c 100644 (file)
@@ -1,38 +1,38 @@
-#!/usr/bin/env python\r
-\r
-'''\r
-clickedpoint.py\r
-\r
-ClickedPoint class for Hooke.\r
-\r
-Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
-\r
-This program is released under the GNU General Public License version 2.\r
-'''\r
-\r
-from scipy import arange\r
-\r
-class ClickedPoint(object):\r
-    '''\r
-    This class defines what a clicked point on the curve plot is.\r
-    '''\r
-    def __init__(self):\r
-\r
-        self.is_marker = None #boolean ; decides if it is a marker\r
-        self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused)\r
-        self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph\r
-        self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point\r
-        self.index = None #integer ; the index of the clicked point with respect to the vector selected\r
-        self.dest = None #0 or 1 ; 0=top plot 1=bottom plot\r
-\r
-    def find_graph_coords(self, xvector, yvector):\r
-        '''\r
-        Given a clicked point on the plot, finds the nearest point in the dataset (in X) that\r
-        corresponds to the clicked point.\r
-        '''\r
-        dists = []\r
-        for index in arange(1, len(xvector), 1):\r
-            dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2))\r
-\r
-        self.index=dists.index(min(dists))\r
-        self.graph_coords=(xvector[self.index], yvector[self.index])\r
+#!/usr/bin/env python
+
+'''
+clickedpoint.py
+
+ClickedPoint class for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from scipy import arange
+
+class ClickedPoint(object):
+    '''
+    This class defines what a clicked point on the curve plot is.
+    '''
+    def __init__(self):
+
+        self.is_marker = None #boolean ; decides if it is a marker
+        self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused)
+        self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph
+        self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
+        self.index = None #integer ; the index of the clicked point with respect to the vector selected
+        self.dest = None #0 or 1 ; 0=top plot 1=bottom plot
+
+    def find_graph_coords(self, xvector, yvector):
+        '''
+        Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
+        corresponds to the clicked point.
+        '''
+        dists = []
+        for index in arange(1, len(xvector), 1):
+            dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2))
+
+        self.index=dists.index(min(dists))
+        self.graph_coords=(xvector[self.index], yvector[self.index])
index 0cb446e..7f7b9ff 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright\r
-\r
-"""A collection of useful popup dialogs for user interaction.\r
-"""\r
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""A collection of useful popup dialogs for user interaction.
+"""
index 1e86ee1..65ea3aa 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 import wx
 
index ade99b2..0d72db0 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Define :func:`select_save_file`
 """
index bc5f185..ff7fbb7 100644 (file)
-# Copyright\r
-\r
-"""Selection dialog.\r
-"""\r
-\r
-from os import remove\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-\r
-\r
-class Selection (wx.Dialog):\r
-    """A selection dialog box.\r
-\r
-    Lists options and two buttons.  The first button is setup by the\r
-    caller.  The second button cancels the dialog.\r
-\r
-    The button appearance can be specified by selecting one of the\r
-    `standard wx IDs`_.\r
-\r
-    .. _standard wx IDs:\r
-      http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid\r
-    """\r
-    def __init__(self, options, message, button_id, callbacks=None,\r
-                 default=None, selection_style='single', *args, **kwargs):\r
-        super(Selection, self).__init__(*args, **kwargs)\r
-\r
-        self._options = options\r
-        if callbacks == None:\r
-            callbacks = {}\r
-        self._callbacks = callbacks\r
-        self._selection_style = selection_style\r
-\r
-        self._c = {\r
-            'text': wx.StaticText(\r
-                parent=self, label=message, style=wx.ALIGN_CENTRE),\r
-            'button': wx.Button(parent=self, id=button_id),\r
-            'cancel': wx.Button(self, wx.ID_CANCEL),\r
-            }\r
-        size = wx.Size(175, 200)\r
-        if selection_style == 'single':\r
-            self._c['listbox'] = wx.ListBox(\r
-                parent=self, size=size, choices=options)\r
-            if default != None:\r
-                self._c['listbox'].SetSelection(default)\r
-        else:\r
-            assert selection_style == 'multiple', selection_style\r
-            self._c['listbox'] = wx.CheckListBox(\r
-                parent=self, size=size, choices=options)\r
-            if default != None:\r
-                self._c['listbox'].Check(default)\r
-        self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])\r
-        self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])\r
-\r
-        b = wx.BoxSizer(wx.HORIZONTAL)\r
-        self._add(b, 'button')\r
-        self._add(b, 'cancel')\r
-        v = wx.BoxSizer(wx.VERTICAL)\r
-        self._add(v, 'text')\r
-        self._add(v, 'listbox')\r
-        self._add(v, wx.StaticLine(\r
-                parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),\r
-                  flag=wx.GROW)\r
-        self._add(v, b)\r
-        self.SetSizer(v)\r
-        v.Fit(self)\r
-\r
-    def _add(self, sizer, item,\r
-            flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-            border=5):\r
-        kwargs = {'flag':flag, 'border':border}\r
-        if isinstance(item, types.StringTypes):\r
-            item = self._c[item]\r
-        kwargs['item'] = item # window\r
-        sizer.Add(**kwargs)\r
-\r
-    @callback\r
-    def cancel(self, event):\r
-        """Close the dialog.\r
-        """\r
-        self.EndModal(wx.ID_CANCEL)\r
-\r
-    def button(self, event):\r
-        """Call ._button_callback() and close the dialog.\r
-        """\r
-        if self._selection_style == 'single':\r
-            selected = self._c['listbox'].GetSelection()\r
-        else:\r
-            assert self._selection_style == 'multiple', self._selection_style\r
-            selected = self._c['listbox'].GetChecked()\r
-        self.selected = selected\r
-        in_callback(self, options=self._options, selected=selected)\r
-        self.EndModal(wx.ID_CLOSE)\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Selection dialog.
+"""
+
+from os import remove
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+
+
+class Selection (wx.Dialog):
+    """A selection dialog box.
+
+    Lists options and two buttons.  The first button is setup by the
+    caller.  The second button cancels the dialog.
+
+    The button appearance can be specified by selecting one of the
+    `standard wx IDs`_.
+
+    .. _standard wx IDs:
+      http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid
+    """
+    def __init__(self, options, message, button_id, callbacks=None,
+                 default=None, selection_style='single', *args, **kwargs):
+        super(Selection, self).__init__(*args, **kwargs)
+
+        self._options = options
+        if callbacks == None:
+            callbacks = {}
+        self._callbacks = callbacks
+        self._selection_style = selection_style
+
+        self._c = {
+            'text': wx.StaticText(
+                parent=self, label=message, style=wx.ALIGN_CENTRE),
+            'button': wx.Button(parent=self, id=button_id),
+            'cancel': wx.Button(self, wx.ID_CANCEL),
+            }
+        size = wx.Size(175, 200)
+        if selection_style == 'single':
+            self._c['listbox'] = wx.ListBox(
+                parent=self, size=size, choices=options)
+            if default != None:
+                self._c['listbox'].SetSelection(default)
+        else:
+            assert selection_style == 'multiple', selection_style
+            self._c['listbox'] = wx.CheckListBox(
+                parent=self, size=size, choices=options)
+            if default != None:
+                self._c['listbox'].Check(default)
+        self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])
+        self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])
+
+        b = wx.BoxSizer(wx.HORIZONTAL)
+        self._add(b, 'button')
+        self._add(b, 'cancel')
+        v = wx.BoxSizer(wx.VERTICAL)
+        self._add(v, 'text')
+        self._add(v, 'listbox')
+        self._add(v, wx.StaticLine(
+                parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),
+                  flag=wx.GROW)
+        self._add(v, b)
+        self.SetSizer(v)
+        v.Fit(self)
+
+    def _add(self, sizer, item,
+            flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+            border=5):
+        kwargs = {'flag':flag, 'border':border}
+        if isinstance(item, types.StringTypes):
+            item = self._c[item]
+        kwargs['item'] = item # window
+        sizer.Add(**kwargs)
+
+    @callback
+    def cancel(self, event):
+        """Close the dialog.
+        """
+        self.EndModal(wx.ID_CANCEL)
+
+    def button(self, event):
+        """Call ._button_callback() and close the dialog.
+        """
+        if self._selection_style == 'single':
+            selected = self._c['listbox'].GetSelection()
+        else:
+            assert self._selection_style == 'multiple', self._selection_style
+            selected = self._c['listbox'].GetChecked()
+        self.selected = selected
+        in_callback(self, options=self._options, selected=selected)
+        self.EndModal(wx.ID_CLOSE)
index ae5643e..0ebf659 100644 (file)
@@ -2,15 +2,15 @@
 #
 # This file is part of Hooke.
 #
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation, either
-# version 3 of the License, or (at your option) any later version.
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
 #
-# Hooke 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 Lesser General Public License for more details.
+# Hooke 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 Lesser General
+# Public License for more details.
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with Hooke.  If not, see
index 0d03794..98bdb06 100644 (file)
@@ -1,39 +1,55 @@
-# Copyright\r
-\r
-from ....util.pluggable import IsSubclass, construct_graph\r
-\r
-\r
-HANDLER_MODULES = [\r
-    'boolean',\r
-    'float',\r
-#    'int'\r
-#    'point',\r
-    'selection',\r
-    'string'\r
-    ]\r
-"""List of handler modules.  TODO: autodiscovery\r
-"""\r
-\r
-class Handler (object):\r
-    """Base class for :class:`~hooke.interaction.Request` handlers.\r
-    \r
-    :attr:`name` identifies the request type and should match the\r
-    module name.\r
-    """\r
-    def __init__(self, name):\r
-        self.name = name\r
-\r
-    def run(self, hooke_frame, msg):\r
-        raise NotImplemented\r
-\r
-    def _cancel(self, *args, **kwargs):\r
-        # TODO: somehow abort the running command\r
-\r
-\r
-HANDLERS = construct_odict(\r
-    this_modname=__name__,\r
-    submodnames=USER_INTERFACE_MODULES,\r
-    class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))\r
-""":class:`hooke.compat.odict.odict` of :class:`Handler`\r
-instances keyed by `.name`.\r
-"""\r
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+from ....util.pluggable import IsSubclass, construct_graph
+
+
+HANDLER_MODULES = [
+    'boolean',
+    'float',
+#    'int'
+#    'point',
+    'selection',
+    'string'
+    ]
+"""List of handler modules.  TODO: autodiscovery
+"""
+
+class Handler (object):
+    """Base class for :class:`~hooke.interaction.Request` handlers.
+    
+    :attr:`name` identifies the request type and should match the
+    module name.
+    """
+    def __init__(self, name):
+        self.name = name
+
+    def run(self, hooke_frame, msg):
+        raise NotImplemented
+
+    def _cancel(self, *args, **kwargs):
+        # TODO: somehow abort the running command
+
+
+HANDLERS = construct_odict(
+    this_modname=__name__,
+    submodnames=USER_INTERFACE_MODULES,
+    class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
+""":class:`hooke.compat.odict.odict` of :class:`Handler`
+instances keyed by `.name`.
+"""
index 56404d7..c87bf83 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 import wx
 
index 89ef426..ee0e5e0 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Define :class:`SelectionHandler` to handle
 :class:`~hooke.interaction.SelectionRequest`\s.
index fe2d7e3..4147026 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Define :class:`StringHandler` to handle
 :class:`~hooke.interaction.StringRequest`\s.
index 068e32e..d918a94 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Menu bar for Hooke.
 """
index 535f0f6..64e5895 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Navigation bar for Hooke.
 """
index cbb4aee..a1be1eb 100644 (file)
@@ -1,47 +1,64 @@
-# Copyright\r
-\r
-"""The `panel` module provides optional submodules that add GUI panels.\r
-"""\r
-\r
-from ....util.pluggable import IsSubclass, construct_odict\r
-\r
-\r
-PANEL_MODULES = [\r
-    'commands',\r
-    'note',\r
-#    'notebook',\r
-    'output',\r
-    'playlist',\r
-    'plot',\r
-    'propertyeditor',\r
-#    'results',\r
-#    'selection',\r
-#    'welcome',\r
-    ]\r
-"""List of panel modules.  TODO: autodiscovery\r
-"""\r
-\r
-class Panel (object):\r
-    """Base class for Hooke GUI panels.\r
-    \r
-    :attr:`name` identifies the request type and should match the\r
-    module name.\r
-    """\r
-    def __init__(self, name=None, callbacks=None, **kwargs):\r
-        super(Panel, self).__init__(**kwargs)\r
-        self.name = name\r
-        self.managed_name = name.capitalize()\r
-        self._hooke_frame = kwargs.get('parent', None)\r
-        if callbacks == None:\r
-            callbacks = {}\r
-        self._callbacks = callbacks\r
-\r
-\r
-PANELS = construct_odict(\r
-    this_modname=__name__,\r
-    submodnames=PANEL_MODULES,\r
-    class_selector=IsSubclass(Panel, blacklist=[Panel]),\r
-    instantiate=False)\r
-""":class:`hooke.compat.odict.odict` of :class:`Panel`\r
-instances keyed by `.name`.\r
-"""\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `panel` module provides optional submodules that add GUI panels.
+"""
+
+from ....util.pluggable import IsSubclass, construct_odict
+
+
+PANEL_MODULES = [
+    'commands',
+    'note',
+#    'notebook',
+    'output',
+    'playlist',
+    'plot',
+    'propertyeditor',
+#    'results',
+#    'selection',
+#    'welcome',
+    ]
+"""List of panel modules.  TODO: autodiscovery
+"""
+
+class Panel (object):
+    """Base class for Hooke GUI panels.
+    
+    :attr:`name` identifies the request type and should match the
+    module name.
+    """
+    def __init__(self, name=None, callbacks=None, **kwargs):
+        super(Panel, self).__init__(**kwargs)
+        self.name = name
+        self.managed_name = name.capitalize()
+        self._hooke_frame = kwargs.get('parent', None)
+        if callbacks == None:
+            callbacks = {}
+        self._callbacks = callbacks
+
+
+PANELS = construct_odict(
+    this_modname=__name__,
+    submodnames=PANEL_MODULES,
+    class_selector=IsSubclass(Panel, blacklist=[Panel]),
+    instantiate=False)
+""":class:`hooke.compat.odict.odict` of :class:`Panel`
+instances keyed by `.name`.
+"""
index f46f098..5d27cc0 100644 (file)
-#!/usr/bin/env python\r
-\r
-"""Commands and settings panel for Hooke.\r
-\r
-This panel handles command generation of\r
-:class:`hooke.ui.CommandMessage`\s for all of the commands that don't\r
-have panels of their own.  Actually it can generate\r
-:class:`hooke.ui.CommandMessage`\s for those as well, but the\r
-command-specific panel will probably have a nicer interface.\r
-\r
-# TODO: command arguments.\r
-"""\r
-\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class Tree (wx.TreeCtrl):\r
-    """The main widget of :class:`CommandsPanel`.\r
-\r
-    `callbacks` is shared with the parent :class:`Commands`.\r
-    """\r
-    def __init__(self, commands, selected, callbacks, *args, **kwargs):\r
-        super(Tree, self).__init__(*args, **kwargs)\r
-        imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
-        imglist.Add(wx.ArtProvider.GetBitmap(\r
-                wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
-        imglist.Add(wx.ArtProvider.GetBitmap(\r
-                wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
-        self.AssignImageList(imglist)\r
-        self.image = {\r
-            'root': 0,\r
-            'plugin': 0,\r
-            'command': 1,\r
-            }\r
-        self._c = {\r
-            'root': self.AddRoot(\r
-                text='Commands and Settings', image=self.image['root']),\r
-            }\r
-        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)\r
-        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)\r
-        self.Bind(wx.EVT_MOTION, self._on_motion)\r
-\r
-        self._callbacks = callbacks\r
-        self._setup_commands(commands, selected)\r
-        self._last_tooltip = None\r
-\r
-    def _setup_commands(self, commands, selected):\r
-        self._plugins = {}    # {name: hooke.plugin.Plugin()}\r
-        self._commands = {}   # {name: hooke.command.Command()}\r
-\r
-        # In both of the following dicts, command names are\r
-        # (plugin.name, command.name) to avoid cross-plugin\r
-        # collisions.  See ._is_command().\r
-        self._id_for_name = {}  # {name: id}\r
-        self._name_for_id = {}  # {id: name}\r
-\r
-        selected = None\r
-        plugins = sorted(set([c.plugin for c in commands]),\r
-                         key=lambda p:p.name)\r
-        for plugin in plugins:\r
-            self._plugins[plugin.name] = plugin\r
-            _id = self.AppendItem(\r
-                parent=self._c['root'],\r
-                text=plugin.name,\r
-                image=self.image['plugin'])\r
-            self._id_for_name[plugin.name] = _id\r
-            self._name_for_id[_id] = plugin.name\r
-        for command in sorted(commands, key=lambda c:c.name):\r
-            name = (command.plugin.name, command.name)\r
-            self._commands[name] = command\r
-            _id = self.AppendItem(\r
-                parent=self._id_for_name[command.plugin.name],\r
-                text=command.name,\r
-                image=self.image['command'])\r
-            self._id_for_name[name] = _id\r
-            self._name_for_id[_id] = name\r
-            if command.name == selected:\r
-                selected = _id\r
-\r
-        #for plugin in self._plugins.values():\r
-        #    self.Expand(self._id_for_name[plugin.name])\r
-        # make sure the selected command/plugin is visible in the tree\r
-        if selected is not None:\r
-            self.SelectItem(selected, True)\r
-            self.EnsureVisible(selected)\r
-\r
-    def _is_command(self, name):  # name from ._id_for_name / ._name_for_id\r
-        """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.\r
-        """\r
-        # Plugin names are strings, Command names are tuples.\r
-        # See ._setup_commands().\r
-        return not isinstance(name, types.StringTypes)\r
-\r
-    def _canonical_id(self, _id):\r
-        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
-\r
-        For some reason, `.GetSelection()`, etc. return items that\r
-        hash differently than the original `.AppendItem()`-returned\r
-        IDs.  This means that `._name_for_id[self.GetSelection()]`\r
-        will raise `KeyError`, even if there is an id `X` in\r
-        `._name_for_id` for which `X == self.GetSelection()` will\r
-        return `True`.  This method "canonicalizes" IDs so that the\r
-        hashing is consistent.\r
-        """\r
-        for c_id in self._name_for_id.keys():\r
-            if c_id == _id:\r
-                return c_id\r
-        raise KeyError(_id)\r
-\r
-    def _on_selection_changed(self, event):\r
-        active_id = event.GetItem()\r
-        selected_id = self.GetSelection()\r
-        name = self._name_for_id[self._canonical_id(selected_id)]\r
-        if self._is_command(name):\r
-            self.select_command(self._commands[name])\r
-        else:\r
-            self.select_plugin(self._plugins[name])\r
-\r
-    def _on_execute(self, event):\r
-        self.execute()\r
-\r
-    def _on_motion(self, event):\r
-        """Enable tooltips.\r
-        """\r
-        hit_id,hit_flags = self.HitTest(event.GetPosition())\r
-        try:\r
-            hit_id = self._canonical_id(hit_id)\r
-        except KeyError:\r
-            hit_id = None\r
-        if hit_id == None:\r
-            msg = ''\r
-        else:\r
-            name = self._name_for_id[hit_id]\r
-            if self._is_command(name):\r
-                msg = self._commands[name].help()\r
-            else:\r
-                msg = ''  # self._plugins[name].help() TODO: Plugin.help method\r
-        if msg != self._last_tooltip:\r
-            self._last_tooltip = msg\r
-            event.GetEventObject().SetToolTipString(msg)\r
-\r
-    def select_plugin(self, plugin):\r
-        in_callback(self, plugin)\r
-\r
-    def select_command(self, command):\r
-        in_callback(self, command)\r
-\r
-    def execute(self):\r
-        _id = self.GetSelection()\r
-        name = self._name_for_id[self._canonical_id(_id)]\r
-        if self._is_command(name):\r
-            command = self._commands[name]\r
-            in_callback(self, command)\r
-\r
-\r
-class CommandsPanel (Panel, wx.Panel):\r
-    """UI for selecting from available commands.\r
-\r
-    `callbacks` is shared with the underlying :class:`Tree`.\r
-    """\r
-    def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):\r
-        super(CommandsPanel, self).__init__(\r
-            name='commands', callbacks=callbacks, **kwargs)\r
-        self._c = {\r
-            'tree': Tree(\r
-                commands=commands,\r
-                selected=selected,\r
-                callbacks=callbacks,\r
-                parent=self,\r
-                pos=wx.Point(0, 0),\r
-                size=wx.Size(160, 250),\r
-                style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),\r
-            'execute': wx.Button(self, label='Execute'),\r
-            }\r
-        sizer = wx.BoxSizer(wx.VERTICAL)\r
-        sizer.Add(self._c['execute'], 0, wx.EXPAND)\r
-        sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
-        # Put 'tree' second because its min size may be large enough\r
-        # to push the button out of view.\r
-        self.SetSizer(sizer)\r
-        sizer.Fit(self)\r
-\r
-        self.Bind(wx.EVT_BUTTON, self._on_execute_button)\r
-\r
-    def _on_execute_button(self, event):\r
-        self._c['tree'].execute()\r
+#!/usr/bin/env python
+
+"""Commands and settings panel for Hooke.
+
+This panel handles command generation of
+:class:`hooke.ui.CommandMessage`\s for all of the commands that don't
+have panels of their own.  Actually it can generate
+:class:`hooke.ui.CommandMessage`\s for those as well, but the
+command-specific panel will probably have a nicer interface.
+
+# TODO: command arguments.
+"""
+
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class Tree (wx.TreeCtrl):
+    """The main widget of :class:`CommandsPanel`.
+
+    `callbacks` is shared with the parent :class:`Commands`.
+    """
+    def __init__(self, commands, selected, callbacks, *args, **kwargs):
+        super(Tree, self).__init__(*args, **kwargs)
+        imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
+        imglist.Add(wx.ArtProvider.GetBitmap(
+                wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
+        imglist.Add(wx.ArtProvider.GetBitmap(
+                wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
+        self.AssignImageList(imglist)
+        self.image = {
+            'root': 0,
+            'plugin': 0,
+            'command': 1,
+            }
+        self._c = {
+            'root': self.AddRoot(
+                text='Commands and Settings', image=self.image['root']),
+            }
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
+        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
+        self.Bind(wx.EVT_MOTION, self._on_motion)
+
+        self._callbacks = callbacks
+        self._setup_commands(commands, selected)
+        self._last_tooltip = None
+
+    def _setup_commands(self, commands, selected):
+        self._plugins = {}    # {name: hooke.plugin.Plugin()}
+        self._commands = {}   # {name: hooke.command.Command()}
+
+        # In both of the following dicts, command names are
+        # (plugin.name, command.name) to avoid cross-plugin
+        # collisions.  See ._is_command().
+        self._id_for_name = {}  # {name: id}
+        self._name_for_id = {}  # {id: name}
+
+        selected = None
+        plugins = sorted(set([c.plugin for c in commands]),
+                         key=lambda p:p.name)
+        for plugin in plugins:
+            self._plugins[plugin.name] = plugin
+            _id = self.AppendItem(
+                parent=self._c['root'],
+                text=plugin.name,
+                image=self.image['plugin'])
+            self._id_for_name[plugin.name] = _id
+            self._name_for_id[_id] = plugin.name
+        for command in sorted(commands, key=lambda c:c.name):
+            name = (command.plugin.name, command.name)
+            self._commands[name] = command
+            _id = self.AppendItem(
+                parent=self._id_for_name[command.plugin.name],
+                text=command.name,
+                image=self.image['command'])
+            self._id_for_name[name] = _id
+            self._name_for_id[_id] = name
+            if command.name == selected:
+                selected = _id
+
+        #for plugin in self._plugins.values():
+        #    self.Expand(self._id_for_name[plugin.name])
+        # make sure the selected command/plugin is visible in the tree
+        if selected is not None:
+            self.SelectItem(selected, True)
+            self.EnsureVisible(selected)
+
+    def _is_command(self, name):  # name from ._id_for_name / ._name_for_id
+        """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
+        """
+        # Plugin names are strings, Command names are tuples.
+        # See ._setup_commands().
+        return not isinstance(name, types.StringTypes)
+
+    def _canonical_id(self, _id):
+        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
+
+        For some reason, `.GetSelection()`, etc. return items that
+        hash differently than the original `.AppendItem()`-returned
+        IDs.  This means that `._name_for_id[self.GetSelection()]`
+        will raise `KeyError`, even if there is an id `X` in
+        `._name_for_id` for which `X == self.GetSelection()` will
+        return `True`.  This method "canonicalizes" IDs so that the
+        hashing is consistent.
+        """
+        for c_id in self._name_for_id.keys():
+            if c_id == _id:
+                return c_id
+        raise KeyError(_id)
+
+    def _on_selection_changed(self, event):
+        active_id = event.GetItem()
+        selected_id = self.GetSelection()
+        name = self._name_for_id[self._canonical_id(selected_id)]
+        if self._is_command(name):
+            self.select_command(self._commands[name])
+        else:
+            self.select_plugin(self._plugins[name])
+
+    def _on_execute(self, event):
+        self.execute()
+
+    def _on_motion(self, event):
+        """Enable tooltips.
+        """
+        hit_id,hit_flags = self.HitTest(event.GetPosition())
+        try:
+            hit_id = self._canonical_id(hit_id)
+        except KeyError:
+            hit_id = None
+        if hit_id == None:
+            msg = ''
+        else:
+            name = self._name_for_id[hit_id]
+            if self._is_command(name):
+                msg = self._commands[name].help()
+            else:
+                msg = ''  # self._plugins[name].help() TODO: Plugin.help method
+        if msg != self._last_tooltip:
+            self._last_tooltip = msg
+            event.GetEventObject().SetToolTipString(msg)
+
+    def select_plugin(self, plugin):
+        in_callback(self, plugin)
+
+    def select_command(self, command):
+        in_callback(self, command)
+
+    def execute(self):
+        _id = self.GetSelection()
+        name = self._name_for_id[self._canonical_id(_id)]
+        if self._is_command(name):
+            command = self._commands[name]
+            in_callback(self, command)
+
+
+class CommandsPanel (Panel, wx.Panel):
+    """UI for selecting from available commands.
+
+    `callbacks` is shared with the underlying :class:`Tree`.
+    """
+    def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):
+        super(CommandsPanel, self).__init__(
+            name='commands', callbacks=callbacks, **kwargs)
+        self._c = {
+            'tree': Tree(
+                commands=commands,
+                selected=selected,
+                callbacks=callbacks,
+                parent=self,
+                pos=wx.Point(0, 0),
+                size=wx.Size(160, 250),
+                style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
+            'execute': wx.Button(self, label='Execute'),
+            }
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self._c['execute'], 0, wx.EXPAND)
+        sizer.Add(self._c['tree'], 1, wx.EXPAND)
+        # Put 'tree' second because its min size may be large enough
+        # to push the button out of view.
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+        self.Bind(wx.EVT_BUTTON, self._on_execute_button)
+
+    def _on_execute_button(self, event):
+        self._c['tree'].execute()
index 30de470..741c6df 100644 (file)
@@ -1,38 +1,55 @@
-# Copyright\r
-\r
-"""Note panel for Hooke.\r
-"""\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class NotePanel (Panel, wx.Panel):\r
-    def __init__(self, callbacks=None, **kwargs):\r
-        super(NotePanel, self).__init__(\r
-            name='note', callbacks=callbacks, **kwargs)\r
-\r
-        self._c = {\r
-            'editor': wx.TextCtrl(\r
-                parent=self,\r
-                style=wx.TE_MULTILINE),\r
-            'update': wx.Button(\r
-                parent=self,\r
-                label='Update note'),\r
-            }\r
-        sizer = wx.BoxSizer(wx.VERTICAL)\r
-        sizer.Add(self._c['editor'], 1, wx.EXPAND)\r
-        sizer.Add(self._c['update'], 0, wx.EXPAND)\r
-        self.SetSizer(sizer)\r
-        self.SetAutoLayout(True)\r
-\r
-        self.Bind(wx.EVT_BUTTON, self._on_update)\r
-\r
-    def set_text(self, text):\r
-        self._c['editor'].SetValue(text)\r
-\r
-    def _on_update(self, event):\r
-        text = self._c['editor'].GetValue()\r
-        in_callback(self, text)\r
+# Copyright (C) 2010 Rolf Schmidt <rschmidt@alcor.concordia.ca>
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Note panel for Hooke.
+"""
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class NotePanel (Panel, wx.Panel):
+    def __init__(self, callbacks=None, **kwargs):
+        super(NotePanel, self).__init__(
+            name='note', callbacks=callbacks, **kwargs)
+
+        self._c = {
+            'editor': wx.TextCtrl(
+                parent=self,
+                style=wx.TE_MULTILINE),
+            'update': wx.Button(
+                parent=self,
+                label='Update note'),
+            }
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self._c['editor'], 1, wx.EXPAND)
+        sizer.Add(self._c['update'], 0, wx.EXPAND)
+        self.SetSizer(sizer)
+        self.SetAutoLayout(True)
+
+        self.Bind(wx.EVT_BUTTON, self._on_update)
+
+    def set_text(self, text):
+        self._c['editor'].SetValue(text)
+
+    def _on_update(self, event):
+        text = self._c['editor'].GetValue()
+        in_callback(self, text)
index 43b8bb7..e00a801 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Notebook panel for Hooke.
 """
index d3b66b5..5edb72f 100644 (file)
@@ -1,4 +1,20 @@
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
 
 """Scrolling text buffer panel for Hooke.
 """
index 8518473..f5c840f 100644 (file)
-# Copyright\r
-\r
-"""Playlist panel for Hooke.\r
-\r
-Provides a nice GUI interface to the\r
-:class:`~hooke.plugin.playlist.PlaylistPlugin`.\r
-"""\r
-\r
-import logging\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class Menu (wx.Menu):\r
-    """Popup menu for selecting playlist :class:`Tree` actions.\r
-    """\r
-    def __init__(self, on_delete, *args, **kwargs):\r
-        super(Menu, self).__init__(*args, **kwargs)\r
-        self._c = {\r
-            'delete': self.Append(id=wx.ID_ANY, text='Delete'),\r
-            }\r
-        self.Bind(wx.EVT_MENU, on_delete)\r
-\r
-\r
-class Tree (wx.TreeCtrl):\r
-    """:class:`wx.TreeCtrl` subclass handling playlist and curve selection.\r
-    """\r
-    def __init__(self, *args, **kwargs):\r
-        self.log = logging.getLogger('hooke')\r
-        self._panel = kwargs['parent']\r
-        self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()\r
-        super(Tree, self).__init__(*args, **kwargs)\r
-        imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
-        imglist.Add(wx.ArtProvider.GetBitmap(\r
-                wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
-        imglist.Add(wx.ArtProvider.GetBitmap(\r
-                wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
-        self.AssignImageList(imglist)\r
-        self.image = {\r
-            'root': 0,\r
-            'playlist': 0,\r
-            'curve': 1,\r
-            }\r
-        self._c = {\r
-            'menu': Menu(self._on_delete),\r
-            'root': self.AddRoot(text='Playlists', image=self.image['root'])\r
-            }\r
-        self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)\r
-        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
-\r
-        self._setup_playlists()\r
-\r
-    def _setup_playlists(self):\r
-        self._playlists = {}    # {name: hooke.playlist.Playlist()}\r
-\r
-        # In both of the following dicts, curve names are\r
-        # (playlist.name, curve.name) to avoid cross-playlist\r
-        # collisions.  See ._is_curve().\r
-        self._id_for_name = {}  # {name: id}\r
-        self._name_for_id = {}  # {id: name}\r
-\r
-    def _is_curve(self, name):  # name from ._id_for_name / ._name_for_id\r
-        """Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.\r
-        """\r
-        # Playlist names are strings, Curve names are tuples.\r
-        # See ._setup_playlists().\r
-        return not isinstance(name, types.StringTypes)\r
-\r
-    def _canonical_id(self, _id):\r
-        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
-\r
-        For some reason, `.GetSelection()`, etc. return items that\r
-        hash differently than the original `.AppendItem()`-returned\r
-        IDs.  This means that `._name_for_id[self.GetSelection()]`\r
-        will raise `KeyError`, even if there is an id `X` in\r
-        `._name_for_id` for which `X == self.GetSelection()` will\r
-        return `True`.  This method "canonicalizes" IDs so that the\r
-        hashing is consistent.\r
-        """\r
-        for c_id in self._name_for_id.keys():\r
-            if c_id == _id:\r
-                return c_id\r
-        raise KeyError(_id)\r
-\r
-\r
-    # Context menu\r
-\r
-    def _on_context_menu(self, event):\r
-        """Launch a popup :class:`Menu` with per-playlist/curve activities.\r
-        """\r
-        hit_id,hit_flags = self.HitTest(event.GetPosition())\r
-        if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
-            self._hit_id = self._canonical_id(hit_id)  # store for callbacks\r
-            menu = Menu(self._on_delete)\r
-            self.PopupMenu(menu, event.GetPosition())\r
-            menu.Destroy()\r
-\r
-    # Add\r
-    #   add_* called directly by HookeFrame\r
-    #   _add_* called on every addition\r
-\r
-    def add_playlist(self, playlist):\r
-        """Add a :class:`hooke.playlist.Playlist` to the tree.\r
-\r
-        Calls :meth:`_add_playlist` and triggers a callback.\r
-        """\r
-        self._add_playlist(playlist)\r
-        in_callback(self, playlist)\r
-\r
-    def _add_playlist(self, playlist):\r
-        """Add a class:`hooke.playlist.Playlist` to the tree.\r
-\r
-        No callback triggered.\r
-        """\r
-        if playlist.name not in self._playlists:\r
-            pass\r
-        else:\r
-            raise ValueError('duplicate playlist: %s' % playlist.name)\r
-        self._playlists[playlist.name] = playlist\r
-        p_id = self.AppendItem(\r
-            parent=self._c['root'],\r
-            text=self._panel._hooke_frame._file_name(playlist.name),\r
-            image=self.image['playlist'])\r
-        self._id_for_name[playlist.name] = p_id\r
-        self._name_for_id[p_id] = playlist.name\r
-        for curve in playlist:\r
-            self._add_curve(playlist.name, curve)\r
-\r
-    def add_curve(self, playlist_name, curve):\r
-        """Add a :class:`hooke.curve.Curve` to a curently loaded playlist.\r
-\r
-        Calls :meth:`_add_curve` and triggers a callback.\r
-        """\r
-        self._add_curve(playlist_name, curve)\r
-        playlist = self._playlists[playlist_name]\r
-        in_callback(self, playlist, curve)\r
-\r
-    def _add_curve(self, playlist_name, curve):\r
-        """Add a class:`hooke.curve.Curve` to the tree.\r
-\r
-        No callback triggered.\r
-        """\r
-        p = self._playlists[playlist_name]\r
-        if curve not in p:\r
-            p.append(curve)\r
-        c_id = self.AppendItem(\r
-            parent=self._id_for_name[playlist_name],\r
-            text=self._panel._hooke_frame._file_name(curve.name),\r
-            image=self.image['curve'])\r
-        self._id_for_name[(p.name, curve.name)] = c_id\r
-        self._name_for_id[c_id] = (p.name, curve.name)\r
-\r
-    @callback\r
-    def generate_new_playlist(self):\r
-        pass  # TODO\r
-\r
-    def _GetUniquePlaylistName(self, name):  # TODO\r
-        playlist_name = name\r
-        count = 1\r
-        while playlist_name in self.playlists:\r
-            playlist_name = ''.join([name, str(count)])\r
-            count += 1\r
-        return playlist_name\r
-\r
-    # Delete\r
-    #   delete_* called by _on_delete handler (user click) or HookeFrame\r
-    #   _delete_* called on every deletion\r
-\r
-    def _on_delete(self, event):\r
-        """Handler for :class:`Menu`'s `Delete` button.\r
-\r
-        Determines the clicked item and calls the appropriate\r
-        `.delete_*()` method on it.\r
-        """\r
-        #if hasattr(self, '_hit_id'):  # called via ._c['menu']\r
-        _id = self._hit_id\r
-        del(self._hit_id)\r
-        name = self._name_for_id[_id]\r
-        if self._is_curve(name):\r
-            self.delete_curve(playlist_name=name[0], name=name[1])\r
-        else:\r
-            self.delete_playlist(name)\r
-\r
-    def delete_playlist(self, name):\r
-        """Delete a :class:`hooke.playlist.Playlist` by name.\r
-\r
-        Called by the :meth:`_on_delete` handler.\r
-\r
-        Removes the playlist and its curves from the tree, then calls\r
-        :meth:`_delete_playlist`.\r
-        """\r
-        _id = self._id_for_name[name]\r
-        self.Delete(_id)\r
-        playlist = self._playlists[name]\r
-        self._delete_playlist(playlist)\r
-        in_callback(self, playlist)\r
-\r
-    def _delete_playlist(self, playlist):\r
-        """Adjust name/id caches for the playlist and its curves.\r
-\r
-        Called on *every* playlist deletion.\r
-        """\r
-        self._playlists.pop(playlist.name)\r
-        _id = self._id_for_name.pop(playlist.name)\r
-        del(self._name_for_id[_id])\r
-        for curve in playlist:\r
-            self._delete_curve(playlist, curve)\r
-        in_callback(self, playlist)\r
-\r
-    def delete_curve(self, playlist_name, name):\r
-        """Delete a :class:`hooke.curve.Curve` by name.\r
-\r
-        Called by the :meth:`_on_delete` handler.\r
-\r
-        Removes the curve from the tree, then calls\r
-        :meth:`_delete_curve`.\r
-        """\r
-        _id = self._id_for_name[(playlist_name, name)]\r
-        self.Delete(_id)\r
-        playlist = self._playlists[playlist_name]\r
-        curve = None\r
-        for i,c in enumerate(playlist):\r
-            if c.name == name:\r
-                curve = c\r
-                break\r
-        self._delete_curve(playlist, curve)\r
-        in_callback(self, playlist, curve)\r
-\r
-    def _delete_curve(self, playlist, curve):\r
-        """Adjust name/id caches.\r
-\r
-        Called on _every_ curve deletion.\r
-        """\r
-        _id = self._id_for_name.pop((playlist.name, curve.name))\r
-        del(self._name_for_id[_id])\r
-        in_callback(self, playlist, curve)\r
-\r
-    # Get selection\r
-\r
-    def get_selected_playlist(self):\r
-        """Return the selected :class:`hooke.playlist.Playlist`.\r
-        """\r
-        _id = self.GetSelection()\r
-        try:\r
-            _id = self._canonical_id(_id)\r
-        except KeyError:  # no playlist selected\r
-            return None\r
-        name = self._name_for_id[_id]\r
-        if self._is_curve(name):\r
-            name = name[0]\r
-        return self._playlists[name]\r
-\r
-    def get_selected_curve(self):\r
-        """Return the selected :class:`hooke.curve.Curve`.\r
-        """\r
-        _id = self.GetSelection()\r
-        name = self._name_for_id[self._canonical_id(_id)]\r
-        if self._is_curve(name):\r
-            p_name,c_name = name\r
-            playlist = self._playlists[p_name]\r
-            c = playlist.current()\r
-            assert c.name == c_name, '%s != %s' % (c.name, c_name)\r
-        else:\r
-            playlist = self._playlists[name]\r
-        return playlist.current()\r
-\r
-    # Set selection (via user interaction with this panel)\r
-    #\r
-    # These are hooks for HookeFrame callbacks which will send\r
-    # the results back via 'get curve' calling 'set_selected_curve'.\r
-\r
-    def _on_select(self, event):\r
-        """Select the clicked-on curve/playlist.\r
-        """\r
-        _id = self.GetSelection()\r
-        name = self._name_for_id[self._canonical_id(_id)]\r
-        if self._is_curve(name):\r
-            p_name,c_name = name\r
-            self._on_set_selected_curve(p_name, c_name)\r
-        else:\r
-            self._on_set_selected_playlist(name)\r
-\r
-    def _on_set_selected_playlist(self, name):\r
-        in_callback(self, self._playlists[name])\r
-\r
-    def _on_set_selected_curve(self, playlist_name, name):\r
-        playlist = self._playlists[playlist_name]\r
-        curve = None\r
-        for i,c in enumerate(playlist):\r
-            if c.name == name:\r
-                curve = c\r
-                break\r
-        if curve == None:\r
-            raise ValueError(name)\r
-        in_callback(self, playlist, curve)\r
-        \r
-    # Set selection (from the HookeFrame)\r
-\r
-    def set_selected_curve(self, playlist, curve):\r
-        """Make the curve the playlist's current curve.\r
-        """\r
-        self.log.debug('playlist tree expanding %s' % playlist.name)\r
-        self.Expand(self._id_for_name[playlist.name])\r
-        self.Unbind(wx.EVT_TREE_SEL_CHANGED)\r
-        self.log.debug('playlist tree selecting %s' % curve.name)\r
-        self.SelectItem(self._id_for_name[(playlist.name, curve.name)])\r
-        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
-\r
-    def update_playlist(self, playlist):\r
-        """Absorb changed `._index`, etc.\r
-        """\r
-        self._playlists[playlist.name] = playlist\r
-\r
-\r
-class Playlist (Panel, wx.Panel):\r
-    """:class:`wx.Panel` subclass wrapper for :class:`Tree`.\r
-    """\r
-    def __init__(self, callbacks=None, **kwargs):\r
-        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
-        super(Playlist, self).__init__(\r
-            name='playlist', callbacks=callbacks, **kwargs)\r
-        self._c = {\r
-            'tree': Tree(\r
-                parent=self,\r
-                size=wx.Size(160, 250),\r
-                style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),\r
-            }\r
-\r
-        sizer = wx.BoxSizer(wx.VERTICAL)\r
-        sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
-        self.SetSizer(sizer)\r
-        sizer.Fit(self)\r
-\r
-        # Expose all Tree's public curve/playlist methods directly.\r
-        # Following DRY and the LoD.\r
-        for attribute_name in dir(self._c['tree']):\r
-            if (attribute_name.startswith('_')\r
-                or 'playlist' not in attribute_name\r
-                or 'curve' not in attribute_name):\r
-                continue  # not an attribute we're interested in\r
-            attr = getattr(self._c['tree'], attribute_name)\r
-            if hasattr(attr, '__call__'):  # attr is a function / method\r
-                setattr(self, attribute_name, attr)  # expose it\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Playlist panel for Hooke.
+
+Provides a nice GUI interface to the
+:class:`~hooke.plugin.playlist.PlaylistPlugin`.
+"""
+
+import logging
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class Menu (wx.Menu):
+    """Popup menu for selecting playlist :class:`Tree` actions.
+    """
+    def __init__(self, on_delete, *args, **kwargs):
+        super(Menu, self).__init__(*args, **kwargs)
+        self._c = {
+            'delete': self.Append(id=wx.ID_ANY, text='Delete'),
+            }
+        self.Bind(wx.EVT_MENU, on_delete)
+
+
+class Tree (wx.TreeCtrl):
+    """:class:`wx.TreeCtrl` subclass handling playlist and curve selection.
+    """
+    def __init__(self, *args, **kwargs):
+        self.log = logging.getLogger('hooke')
+        self._panel = kwargs['parent']
+        self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()
+        super(Tree, self).__init__(*args, **kwargs)
+        imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
+        imglist.Add(wx.ArtProvider.GetBitmap(
+                wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
+        imglist.Add(wx.ArtProvider.GetBitmap(
+                wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)))
+        self.AssignImageList(imglist)
+        self.image = {
+            'root': 0,
+            'playlist': 0,
+            'curve': 1,
+            }
+        self._c = {
+            'menu': Menu(self._on_delete),
+            'root': self.AddRoot(text='Playlists', image=self.image['root'])
+            }
+        self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)
+
+        self._setup_playlists()
+
+    def _setup_playlists(self):
+        self._playlists = {}    # {name: hooke.playlist.Playlist()}
+
+        # In both of the following dicts, curve names are
+        # (playlist.name, curve.name) to avoid cross-playlist
+        # collisions.  See ._is_curve().
+        self._id_for_name = {}  # {name: id}
+        self._name_for_id = {}  # {id: name}
+
+    def _is_curve(self, name):  # name from ._id_for_name / ._name_for_id
+        """Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.
+        """
+        # Playlist names are strings, Curve names are tuples.
+        # See ._setup_playlists().
+        return not isinstance(name, types.StringTypes)
+
+    def _canonical_id(self, _id):
+        """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
+
+        For some reason, `.GetSelection()`, etc. return items that
+        hash differently than the original `.AppendItem()`-returned
+        IDs.  This means that `._name_for_id[self.GetSelection()]`
+        will raise `KeyError`, even if there is an id `X` in
+        `._name_for_id` for which `X == self.GetSelection()` will
+        return `True`.  This method "canonicalizes" IDs so that the
+        hashing is consistent.
+        """
+        for c_id in self._name_for_id.keys():
+            if c_id == _id:
+                return c_id
+        raise KeyError(_id)
+
+
+    # Context menu
+
+    def _on_context_menu(self, event):
+        """Launch a popup :class:`Menu` with per-playlist/curve activities.
+        """
+        hit_id,hit_flags = self.HitTest(event.GetPosition())
+        if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:
+            self._hit_id = self._canonical_id(hit_id)  # store for callbacks
+            menu = Menu(self._on_delete)
+            self.PopupMenu(menu, event.GetPosition())
+            menu.Destroy()
+
+    # Add
+    #   add_* called directly by HookeFrame
+    #   _add_* called on every addition
+
+    def add_playlist(self, playlist):
+        """Add a :class:`hooke.playlist.Playlist` to the tree.
+
+        Calls :meth:`_add_playlist` and triggers a callback.
+        """
+        self._add_playlist(playlist)
+        in_callback(self, playlist)
+
+    def _add_playlist(self, playlist):
+        """Add a class:`hooke.playlist.Playlist` to the tree.
+
+        No callback triggered.
+        """
+        if playlist.name not in self._playlists:
+            pass
+        else:
+            raise ValueError('duplicate playlist: %s' % playlist.name)
+        self._playlists[playlist.name] = playlist
+        p_id = self.AppendItem(
+            parent=self._c['root'],
+            text=self._panel._hooke_frame._file_name(playlist.name),
+            image=self.image['playlist'])
+        self._id_for_name[playlist.name] = p_id
+        self._name_for_id[p_id] = playlist.name
+        for curve in playlist:
+            self._add_curve(playlist.name, curve)
+
+    def add_curve(self, playlist_name, curve):
+        """Add a :class:`hooke.curve.Curve` to a curently loaded playlist.
+
+        Calls :meth:`_add_curve` and triggers a callback.
+        """
+        self._add_curve(playlist_name, curve)
+        playlist = self._playlists[playlist_name]
+        in_callback(self, playlist, curve)
+
+    def _add_curve(self, playlist_name, curve):
+        """Add a class:`hooke.curve.Curve` to the tree.
+
+        No callback triggered.
+        """
+        p = self._playlists[playlist_name]
+        if curve not in p:
+            p.append(curve)
+        c_id = self.AppendItem(
+            parent=self._id_for_name[playlist_name],
+            text=self._panel._hooke_frame._file_name(curve.name),
+            image=self.image['curve'])
+        self._id_for_name[(p.name, curve.name)] = c_id
+        self._name_for_id[c_id] = (p.name, curve.name)
+
+    @callback
+    def generate_new_playlist(self):
+        pass  # TODO
+
+    def _GetUniquePlaylistName(self, name):  # TODO
+        playlist_name = name
+        count = 1
+        while playlist_name in self.playlists:
+            playlist_name = ''.join([name, str(count)])
+            count += 1
+        return playlist_name
+
+    # Delete
+    #   delete_* called by _on_delete handler (user click) or HookeFrame
+    #   _delete_* called on every deletion
+
+    def _on_delete(self, event):
+        """Handler for :class:`Menu`'s `Delete` button.
+
+        Determines the clicked item and calls the appropriate
+        `.delete_*()` method on it.
+        """
+        #if hasattr(self, '_hit_id'):  # called via ._c['menu']
+        _id = self._hit_id
+        del(self._hit_id)
+        name = self._name_for_id[_id]
+        if self._is_curve(name):
+            self.delete_curve(playlist_name=name[0], name=name[1])
+        else:
+            self.delete_playlist(name)
+
+    def delete_playlist(self, name):
+        """Delete a :class:`hooke.playlist.Playlist` by name.
+
+        Called by the :meth:`_on_delete` handler.
+
+        Removes the playlist and its curves from the tree, then calls
+        :meth:`_delete_playlist`.
+        """
+        _id = self._id_for_name[name]
+        self.Delete(_id)
+        playlist = self._playlists[name]
+        self._delete_playlist(playlist)
+        in_callback(self, playlist)
+
+    def _delete_playlist(self, playlist):
+        """Adjust name/id caches for the playlist and its curves.
+
+        Called on *every* playlist deletion.
+        """
+        self._playlists.pop(playlist.name)
+        _id = self._id_for_name.pop(playlist.name)
+        del(self._name_for_id[_id])
+        for curve in playlist:
+            self._delete_curve(playlist, curve)
+        in_callback(self, playlist)
+
+    def delete_curve(self, playlist_name, name):
+        """Delete a :class:`hooke.curve.Curve` by name.
+
+        Called by the :meth:`_on_delete` handler.
+
+        Removes the curve from the tree, then calls
+        :meth:`_delete_curve`.
+        """
+        _id = self._id_for_name[(playlist_name, name)]
+        self.Delete(_id)
+        playlist = self._playlists[playlist_name]
+        curve = None
+        for i,c in enumerate(playlist):
+            if c.name == name:
+                curve = c
+                break
+        self._delete_curve(playlist, curve)
+        in_callback(self, playlist, curve)
+
+    def _delete_curve(self, playlist, curve):
+        """Adjust name/id caches.
+
+        Called on _every_ curve deletion.
+        """
+        _id = self._id_for_name.pop((playlist.name, curve.name))
+        del(self._name_for_id[_id])
+        in_callback(self, playlist, curve)
+
+    # Get selection
+
+    def get_selected_playlist(self):
+        """Return the selected :class:`hooke.playlist.Playlist`.
+        """
+        _id = self.GetSelection()
+        try:
+            _id = self._canonical_id(_id)
+        except KeyError:  # no playlist selected
+            return None
+        name = self._name_for_id[_id]
+        if self._is_curve(name):
+            name = name[0]
+        return self._playlists[name]
+
+    def get_selected_curve(self):
+        """Return the selected :class:`hooke.curve.Curve`.
+        """
+        _id = self.GetSelection()
+        name = self._name_for_id[self._canonical_id(_id)]
+        if self._is_curve(name):
+            p_name,c_name = name
+            playlist = self._playlists[p_name]
+            c = playlist.current()
+            assert c.name == c_name, '%s != %s' % (c.name, c_name)
+        else:
+            playlist = self._playlists[name]
+        return playlist.current()
+
+    # Set selection (via user interaction with this panel)
+    #
+    # These are hooks for HookeFrame callbacks which will send
+    # the results back via 'get curve' calling 'set_selected_curve'.
+
+    def _on_select(self, event):
+        """Select the clicked-on curve/playlist.
+        """
+        _id = self.GetSelection()
+        name = self._name_for_id[self._canonical_id(_id)]
+        if self._is_curve(name):
+            p_name,c_name = name
+            self._on_set_selected_curve(p_name, c_name)
+        else:
+            self._on_set_selected_playlist(name)
+
+    def _on_set_selected_playlist(self, name):
+        in_callback(self, self._playlists[name])
+
+    def _on_set_selected_curve(self, playlist_name, name):
+        playlist = self._playlists[playlist_name]
+        curve = None
+        for i,c in enumerate(playlist):
+            if c.name == name:
+                curve = c
+                break
+        if curve == None:
+            raise ValueError(name)
+        in_callback(self, playlist, curve)
+        
+    # Set selection (from the HookeFrame)
+
+    def set_selected_curve(self, playlist, curve):
+        """Make the curve the playlist's current curve.
+        """
+        self.log.debug('playlist tree expanding %s' % playlist.name)
+        self.Expand(self._id_for_name[playlist.name])
+        self.Unbind(wx.EVT_TREE_SEL_CHANGED)
+        self.log.debug('playlist tree selecting %s' % curve.name)
+        self.SelectItem(self._id_for_name[(playlist.name, curve.name)])
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)
+
+    def update_playlist(self, playlist):
+        """Absorb changed `._index`, etc.
+        """
+        self._playlists[playlist.name] = playlist
+
+
+class Playlist (Panel, wx.Panel):
+    """:class:`wx.Panel` subclass wrapper for :class:`Tree`.
+    """
+    def __init__(self, callbacks=None, **kwargs):
+        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
+        super(Playlist, self).__init__(
+            name='playlist', callbacks=callbacks, **kwargs)
+        self._c = {
+            'tree': Tree(
+                parent=self,
+                size=wx.Size(160, 250),
+                style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),
+            }
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self._c['tree'], 1, wx.EXPAND)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+        # Expose all Tree's public curve/playlist methods directly.
+        # Following DRY and the LoD.
+        for attribute_name in dir(self._c['tree']):
+            if (attribute_name.startswith('_')
+                or 'playlist' not in attribute_name
+                or 'curve' not in attribute_name):
+                continue  # not an attribute we're interested in
+            attr = getattr(self._c['tree'], attribute_name)
+            if hasattr(attr, '__call__'):  # attr is a function / method
+                setattr(self, attribute_name, attr)  # expose it
index 3652579..65f0542 100644 (file)
-# Copyright\r
-\r
-"""Plot panel for Hooke.\r
-\r
-Notes\r
------\r
-Originally based on `this example`_.\r
-\r
-.. _this example:\r
-  http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html\r
-"""\r
-\r
-import matplotlib\r
-matplotlib.use('WXAgg')  # use wxpython with antigrain (agg) rendering\r
-from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
-from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar\r
-from matplotlib.figure import Figure\r
-from matplotlib.ticker import Formatter, ScalarFormatter\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from ....util.si import ppSI, split_data_label\r
-from . import Panel\r
-\r
-\r
-class HookeFormatter (Formatter):\r
-    """:class:`matplotlib.ticker.Formatter` using SI prefixes.\r
-    """\r
-    def __init__(self, unit='', decimals=2):\r
-        self.decimals = decimals\r
-        self.unit = unit\r
-\r
-    def __call__(self, x, pos=None):\r
-        """Return the format for tick val `x` at position `pos`.\r
-        """\r
-        if x == 0:\r
-            return '0'\r
-        return ppSI(value=x, unit=self.unit, decimals=self.decimals)\r
-\r
-\r
-class HookeScalarFormatter (ScalarFormatter):\r
-    """:class:`matplotlib.ticker.ScalarFormatter` using only multiples\r
-    of three in the mantissa.\r
-\r
-    A fixed number of decimals can be displayed with the optional\r
-    parameter `decimals` . If `decimals` is `None` (default), the number\r
-    of decimals is defined from the current ticks.\r
-    """\r
-    def __init__(self, decimals=None, **kwargs):\r
-        # Can't use super() because ScalarFormatter is an old-style class :(.\r
-        ScalarFormatter.__init__(self, **kwargs)\r
-        self._decimals = decimals\r
-\r
-    def _set_orderOfMagnitude(self, *args, **kwargs):\r
-        """Sets the order of magnitude."""        \r
-        # Can't use super() because ScalarFormatter is an old-style class :(.\r
-        ScalarFormatter._set_orderOfMagnitude(self, *args, **kwargs)\r
-        self.orderOfMagnitude -= self.orderOfMagnitude % 3\r
-\r
-    def _set_format(self, *args, **kwargs):\r
-        """Sets the format string to format all ticklabels."""\r
-        # Can't use super() because ScalarFormatter is an old-style class :(.\r
-        ScalarFormatter._set_format(self, *args, **kwargs)\r
-        if self._decimals is None or self._decimals < 0:\r
-            locs = (np.asarray(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15\r
-            sigfigs = [len(str('%1.8f'% loc).split('.')[1].rstrip('0')) \\r
-                   for loc in locs]\r
-            sigfigs.sort()\r
-            decimals = sigfigs[-1]\r
-        else:\r
-            decimals = self._decimals\r
-        self.format = '%1.' + str(decimals) + 'f'\r
-        if self._usetex:\r
-            self.format = '$%s$' % self.format\r
-        elif self._useMathText:\r
-            self.format = '$\mathdefault{%s}$' % self.format\r
-\r
-\r
-class PlotPanel (Panel, wx.Panel):\r
-    """UI for graphical curve display.\r
-    """\r
-    def __init__(self, callbacks=None, **kwargs):\r
-        self.display_coordinates = False\r
-        self.style = 'line'\r
-        self._curve = None\r
-        self._x_column = None\r
-        self._y_column = None\r
-        super(PlotPanel, self).__init__(\r
-            name='plot', callbacks=callbacks, **kwargs)\r
-        self._c = {}\r
-        self._c['figure'] = Figure()\r
-        self._c['canvas'] = FigureCanvas(\r
-            parent=self, id=wx.ID_ANY, figure=self._c['figure'])\r
-\r
-        self._set_color(wx.NamedColor('WHITE'))\r
-        sizer = wx.BoxSizer(wx.VERTICAL)\r
-        sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)\r
-        self._setup_toolbar(sizer=sizer)  # comment out to remove plot toolbar.\r
-        self.SetSizer(sizer)\r
-        self.Fit()\r
-\r
-        self.Bind(wx.EVT_SIZE, self._on_size) \r
-        self._c['figure'].canvas.mpl_connect(\r
-            'button_press_event', self._on_click)\r
-        self._c['figure'].canvas.mpl_connect(\r
-            'axes_enter_event', self._on_enter_axes)\r
-        self._c['figure'].canvas.mpl_connect(\r
-            'axes_leave_event', self._on_leave_axes)\r
-        self._c['figure'].canvas.mpl_connect(\r
-            'motion_notify_event', self._on_mouse_move)\r
-\r
-    def _setup_toolbar(self, sizer):\r
-        self._c['toolbar'] = NavToolbar(self._c['canvas'])\r
-        self._c['x column'] = wx.Choice(\r
-            parent=self._c['toolbar'], choices=[])\r
-        self._c['x column'].SetToolTip(wx.ToolTip('x column'))\r
-        self._c['toolbar'].AddControl(self._c['x column'])\r
-        self._c['x column'].Bind(wx.EVT_CHOICE, self._on_x_column)\r
-        self._c['y column'] = wx.Choice(\r
-            parent=self._c['toolbar'], choices=[])\r
-        self._c['y column'].SetToolTip(wx.ToolTip('y column'))\r
-        self._c['toolbar'].AddControl(self._c['y column'])\r
-        self._c['y column'].Bind(wx.EVT_CHOICE, self._on_y_column)\r
-\r
-        self._c['toolbar'].Realize()  # call after putting items in the toolbar\r
-        if wx.Platform == '__WXMAC__':\r
-            # Mac platform (OSX 10.3, MacPython) does not seem to cope with\r
-            # having a toolbar in a sizer. This work-around gets the buttons\r
-            # back, but at the expense of having the toolbar at the top\r
-            self.SetToolBar(self._c['toolbar'])\r
-        elif wx.Platform == '__WXMSW__':\r
-            # On Windows platform, default window size is incorrect, so set\r
-            # toolbar width to figure width.\r
-            tw, th = toolbar.GetSizeTuple()\r
-            fw, fh = self._c['canvas'].GetSizeTuple()\r
-            # By adding toolbar in sizer, we are able to put it at the bottom\r
-            # of the frame - so appearance is closer to GTK version.\r
-            # As noted above, doesn't work for Mac.\r
-            self._c['toolbar'].SetSize(wx.Size(fw, th))\r
-            sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)\r
-        else:\r
-            sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)\r
-        self._c['toolbar'].update()  # update the axes menu on the toolbar\r
-\r
-    def _set_color(self, rgbtuple=None):\r
-        """Set both figure and canvas colors to `rgbtuple`.\r
-        """\r
-        if rgbtuple == None:\r
-            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()\r
-        col = [c/255.0 for c in rgbtuple]\r
-        self._c['figure'].set_facecolor(col)\r
-        self._c['figure'].set_edgecolor(col)\r
-        self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))\r
-\r
-    #def SetStatusText(self, text, field=1):\r
-    #    self.Parent.Parent.statusbar.SetStatusText(text, field)\r
-\r
-    def _on_size(self, event):\r
-        event.Skip()\r
-        wx.CallAfter(self._resize_canvas)\r
-\r
-    def _on_click(self, event):\r
-        #self.SetStatusText(str(event.xdata))\r
-        #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)\r
-        pass\r
-\r
-    def _on_enter_axes(self, event):\r
-        self.display_coordinates = True\r
-\r
-    def _on_leave_axes(self, event):\r
-        self.display_coordinates = False\r
-        #self.SetStatusText('')\r
-\r
-    def _on_mouse_move(self, event):\r
-        if 'toolbar' in self._c:\r
-            if event.guiEvent.m_shiftDown:\r
-                self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)\r
-            else:\r
-                self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)\r
-        if self.display_coordinates:\r
-            coordinateString = ''.join(\r
-                ['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
-            #TODO: pretty format\r
-            #self.SetStatusText(coordinateString)\r
-\r
-    def _on_x_column(self, event):\r
-        self._x_column = self._c['x column'].GetStringSelection()\r
-        self.update()\r
-\r
-    def _on_y_column(self, event):\r
-        self._y_column = self._c['y column'].GetStringSelection()\r
-        self.update()\r
-\r
-    def _resize_canvas(self):\r
-        w,h = self.GetClientSize()\r
-        if 'toolbar' in self._c:\r
-            tw,th = self._c['toolbar'].GetSizeTuple()\r
-        else:\r
-            th = 0\r
-        dpi = float(self._c['figure'].get_dpi())\r
-        self._c['figure'].set_figwidth(w/dpi)\r
-        self._c['figure'].set_figheight((h-th)/dpi)\r
-        self._c['canvas'].draw()\r
-        self.Refresh()\r
-\r
-    def OnPaint(self, event):\r
-        print 'painting'\r
-        super(PlotPanel, self).OnPaint(event)\r
-        self._c['canvas'].draw()\r
-\r
-    def set_curve(self, curve, config=None):\r
-        self._curve = curve\r
-        columns = set()\r
-        for data in curve.data:\r
-            columns = columns.union(set(data.info['columns']))\r
-        self._columns = sorted(columns)\r
-        if self._x_column not in self._columns:\r
-            self._x_column = self._columns[0]\r
-        if self._y_column not in self._columns:\r
-            self._y_column = self._columns[-1]\r
-        if 'x column' in self._c:\r
-            for i in range(self._c['x column'].GetCount()):\r
-                self._c['x column'].Delete(0)\r
-            self._c['x column'].AppendItems(self._columns)\r
-            self._c['x column'].SetStringSelection(self._x_column)\r
-        if 'y column' in self._c:\r
-            for i in range(self._c['y column'].GetCount()):\r
-                self._c['y column'].Delete(0)\r
-            self._c['y column'].AppendItems(self._columns)\r
-            self._c['y column'].SetStringSelection(self._y_column)\r
-        self.update(config=config)\r
-\r
-    def update(self, config=None):\r
-        if config == None:\r
-            config = self._config  # use the last cached value\r
-        else:\r
-            self._config = config  # cache for later refreshes\r
-        self._c['figure'].clear()\r
-        self._c['figure'].suptitle(\r
-            self._hooke_frame._file_name(self._curve.name),\r
-            fontsize=12)\r
-        axes = self._c['figure'].add_subplot(1, 1, 1)\r
-\r
-        if config['plot SI format'] == 'True':  # TODO: config should convert\r
-            d = int(config['plot decimals'])  # TODO: config should convert\r
-            x_n, x_unit = split_data_label(self._x_column)\r
-            y_n, y_unit = split_data_label(self._y_column)\r
-            fx = HookeFormatter(decimals=d, unit=x_unit)\r
-            axes.xaxis.set_major_formatter(fx)\r
-            fy = HookeFormatter(decimals=d, unit=y_unit)\r
-            axes.yaxis.set_major_formatter(fy)\r
-            axes.set_xlabel(x_n)\r
-            axes.set_ylabel(y_n)\r
-        else:\r
-            axes.set_xlabel(self._x_column)\r
-            axes.set_ylabel(self._y_column)\r
-\r
-        self._c['figure'].hold(True)\r
-        for i,data in enumerate(self._curve.data):\r
-            try:\r
-                x_col = data.info['columns'].index(self._x_column)\r
-                y_col = data.info['columns'].index(self._y_column)\r
-            except ValueError:\r
-                continue  # data is missing a required column\r
-            axes.plot(data[:,x_col], data[:,y_col],\r
-                      '.',\r
-                      label=data.info['name'])\r
-        if config['plot legend'] == 'True':  # HACK: config should convert\r
-            axes.legend(loc='best')\r
-        self._c['canvas'].draw()\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+#                    Rolf Schmidt <rschmidt@alcor.concordia.ca>
+#                    W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Plot panel for Hooke.
+
+Notes
+-----
+Originally based on `this example`_.
+
+.. _this example:
+  http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html
+"""
+
+import matplotlib
+matplotlib.use('WXAgg')  # use wxpython with antigrain (agg) rendering
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
+from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar
+from matplotlib.figure import Figure
+from matplotlib.ticker import Formatter, ScalarFormatter
+import wx
+
+from ....util.callback import callback, in_callback
+from ....util.si import ppSI, split_data_label
+from . import Panel
+
+
+class HookeFormatter (Formatter):
+    """:class:`matplotlib.ticker.Formatter` using SI prefixes.
+    """
+    def __init__(self, unit='', decimals=2):
+        self.decimals = decimals
+        self.unit = unit
+
+    def __call__(self, x, pos=None):
+        """Return the format for tick val `x` at position `pos`.
+        """
+        if x == 0:
+            return '0'
+        return ppSI(value=x, unit=self.unit, decimals=self.decimals)
+
+
+class HookeScalarFormatter (ScalarFormatter):
+    """:class:`matplotlib.ticker.ScalarFormatter` using only multiples
+    of three in the mantissa.
+
+    A fixed number of decimals can be displayed with the optional
+    parameter `decimals` . If `decimals` is `None` (default), the number
+    of decimals is defined from the current ticks.
+    """
+    def __init__(self, decimals=None, **kwargs):
+        # Can't use super() because ScalarFormatter is an old-style class :(.
+        ScalarFormatter.__init__(self, **kwargs)
+        self._decimals = decimals
+
+    def _set_orderOfMagnitude(self, *args, **kwargs):
+        """Sets the order of magnitude."""        
+        # Can't use super() because ScalarFormatter is an old-style class :(.
+        ScalarFormatter._set_orderOfMagnitude(self, *args, **kwargs)
+        self.orderOfMagnitude -= self.orderOfMagnitude % 3
+
+    def _set_format(self, *args, **kwargs):
+        """Sets the format string to format all ticklabels."""
+        # Can't use super() because ScalarFormatter is an old-style class :(.
+        ScalarFormatter._set_format(self, *args, **kwargs)
+        if self._decimals is None or self._decimals < 0:
+            locs = (np.asarray(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15
+            sigfigs = [len(str('%1.8f'% loc).split('.')[1].rstrip('0')) \
+                   for loc in locs]
+            sigfigs.sort()
+            decimals = sigfigs[-1]
+        else:
+            decimals = self._decimals
+        self.format = '%1.' + str(decimals) + 'f'
+        if self._usetex:
+            self.format = '$%s$' % self.format
+        elif self._useMathText:
+            self.format = '$\mathdefault{%s}$' % self.format
+
+
+class PlotPanel (Panel, wx.Panel):
+    """UI for graphical curve display.
+    """
+    def __init__(self, callbacks=None, **kwargs):
+        self.display_coordinates = False
+        self.style = 'line'
+        self._curve = None
+        self._x_column = None
+        self._y_column = None
+        super(PlotPanel, self).__init__(
+            name='plot', callbacks=callbacks, **kwargs)
+        self._c = {}
+        self._c['figure'] = Figure()
+        self._c['canvas'] = FigureCanvas(
+            parent=self, id=wx.ID_ANY, figure=self._c['figure'])
+
+        self._set_color(wx.NamedColor('WHITE'))
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)
+        self._setup_toolbar(sizer=sizer)  # comment out to remove plot toolbar.
+        self.SetSizer(sizer)
+        self.Fit()
+
+        self.Bind(wx.EVT_SIZE, self._on_size) 
+        self._c['figure'].canvas.mpl_connect(
+            'button_press_event', self._on_click)
+        self._c['figure'].canvas.mpl_connect(
+            'axes_enter_event', self._on_enter_axes)
+        self._c['figure'].canvas.mpl_connect(
+            'axes_leave_event', self._on_leave_axes)
+        self._c['figure'].canvas.mpl_connect(
+            'motion_notify_event', self._on_mouse_move)
+
+    def _setup_toolbar(self, sizer):
+        self._c['toolbar'] = NavToolbar(self._c['canvas'])
+        self._c['x column'] = wx.Choice(
+            parent=self._c['toolbar'], choices=[])
+        self._c['x column'].SetToolTip(wx.ToolTip('x column'))
+        self._c['toolbar'].AddControl(self._c['x column'])
+        self._c['x column'].Bind(wx.EVT_CHOICE, self._on_x_column)
+        self._c['y column'] = wx.Choice(
+            parent=self._c['toolbar'], choices=[])
+        self._c['y column'].SetToolTip(wx.ToolTip('y column'))
+        self._c['toolbar'].AddControl(self._c['y column'])
+        self._c['y column'].Bind(wx.EVT_CHOICE, self._on_y_column)
+
+        self._c['toolbar'].Realize()  # call after putting items in the toolbar
+        if wx.Platform == '__WXMAC__':
+            # Mac platform (OSX 10.3, MacPython) does not seem to cope with
+            # having a toolbar in a sizer. This work-around gets the buttons
+            # back, but at the expense of having the toolbar at the top
+            self.SetToolBar(self._c['toolbar'])
+        elif wx.Platform == '__WXMSW__':
+            # On Windows platform, default window size is incorrect, so set
+            # toolbar width to figure width.
+            tw, th = toolbar.GetSizeTuple()
+            fw, fh = self._c['canvas'].GetSizeTuple()
+            # By adding toolbar in sizer, we are able to put it at the bottom
+            # of the frame - so appearance is closer to GTK version.
+            # As noted above, doesn't work for Mac.
+            self._c['toolbar'].SetSize(wx.Size(fw, th))
+            sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)
+        else:
+            sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)
+        self._c['toolbar'].update()  # update the axes menu on the toolbar
+
+    def _set_color(self, rgbtuple=None):
+        """Set both figure and canvas colors to `rgbtuple`.
+        """
+        if rgbtuple == None:
+            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
+        col = [c/255.0 for c in rgbtuple]
+        self._c['figure'].set_facecolor(col)
+        self._c['figure'].set_edgecolor(col)
+        self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))
+
+    #def SetStatusText(self, text, field=1):
+    #    self.Parent.Parent.statusbar.SetStatusText(text, field)
+
+    def _on_size(self, event):
+        event.Skip()
+        wx.CallAfter(self._resize_canvas)
+
+    def _on_click(self, event):
+        #self.SetStatusText(str(event.xdata))
+        #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)
+        pass
+
+    def _on_enter_axes(self, event):
+        self.display_coordinates = True
+
+    def _on_leave_axes(self, event):
+        self.display_coordinates = False
+        #self.SetStatusText('')
+
+    def _on_mouse_move(self, event):
+        if 'toolbar' in self._c:
+            if event.guiEvent.m_shiftDown:
+                self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)
+            else:
+                self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)
+        if self.display_coordinates:
+            coordinateString = ''.join(
+                ['x: ', str(event.xdata), ' y: ', str(event.ydata)])
+            #TODO: pretty format
+            #self.SetStatusText(coordinateString)
+
+    def _on_x_column(self, event):
+        self._x_column = self._c['x column'].GetStringSelection()
+        self.update()
+
+    def _on_y_column(self, event):
+        self._y_column = self._c['y column'].GetStringSelection()
+        self.update()
+
+    def _resize_canvas(self):
+        w,h = self.GetClientSize()
+        if 'toolbar' in self._c:
+            tw,th = self._c['toolbar'].GetSizeTuple()
+        else:
+            th = 0
+        dpi = float(self._c['figure'].get_dpi())
+        self._c['figure'].set_figwidth(w/dpi)
+        self._c['figure'].set_figheight((h-th)/dpi)
+        self._c['canvas'].draw()
+        self.Refresh()
+
+    def OnPaint(self, event):
+        print 'painting'
+        super(PlotPanel, self).OnPaint(event)
+        self._c['canvas'].draw()
+
+    def set_curve(self, curve, config=None):
+        self._curve = curve
+        columns = set()
+        for data in curve.data:
+            columns = columns.union(set(data.info['columns']))
+        self._columns = sorted(columns)
+        if self._x_column not in self._columns:
+            self._x_column = self._columns[0]
+        if self._y_column not in self._columns:
+            self._y_column = self._columns[-1]
+        if 'x column' in self._c:
+            for i in range(self._c['x column'].GetCount()):
+                self._c['x column'].Delete(0)
+            self._c['x column'].AppendItems(self._columns)
+            self._c['x column'].SetStringSelection(self._x_column)
+        if 'y column' in self._c:
+            for i in range(self._c['y column'].GetCount()):
+                self._c['y column'].Delete(0)
+            self._c['y column'].AppendItems(self._columns)
+            self._c['y column'].SetStringSelection(self._y_column)
+        self.update(config=config)
+
+    def update(self, config=None):
+        if config == None:
+            config = self._config  # use the last cached value
+        else:
+            self._config = config  # cache for later refreshes
+        self._c['figure'].clear()
+        self._c['figure'].suptitle(
+            self._hooke_frame._file_name(self._curve.name),
+            fontsize=12)
+        axes = self._c['figure'].add_subplot(1, 1, 1)
+
+        if config['plot SI format'] == 'True':  # TODO: config should convert
+            d = int(config['plot decimals'])  # TODO: config should convert
+            x_n, x_unit = split_data_label(self._x_column)
+            y_n, y_unit = split_data_label(self._y_column)
+            fx = HookeFormatter(decimals=d, unit=x_unit)
+            axes.xaxis.set_major_formatter(fx)
+            fy = HookeFormatter(decimals=d, unit=y_unit)
+            axes.yaxis.set_major_formatter(fy)
+            axes.set_xlabel(x_n)
+            axes.set_ylabel(y_n)
+        else:
+            axes.set_xlabel(self._x_column)
+            axes.set_ylabel(self._y_column)
+
+        self._c['figure'].hold(True)
+        for i,data in enumerate(self._curve.data):
+            try:
+                x_col = data.info['columns'].index(self._x_column)
+                y_col = data.info['columns'].index(self._y_column)
+            except ValueError:
+                continue  # data is missing a required column
+            axes.plot(data[:,x_col], data[:,y_col],
+                      '.',
+                      label=data.info['name'])
+        if config['plot legend'] == 'True':  # HACK: config should convert
+            axes.legend(loc='best')
+        self._c['canvas'].draw()
index edf0968..9686e4b 100644 (file)
-# Copyright\r
-\r
-"""Property editor panel for Hooke.\r
-"""\r
-\r
-import sys\r
-import os.path\r
-\r
-import wx\r
-import wx.propgrid as wxpg\r
-\r
-# There are many comments and code fragments in here from the demo app.\r
-# They should come in handy to expand the functionality in the future.\r
-\r
-class Display (object):\r
-    property_descriptor = []\r
-    def __init__(self):\r
-        pass\r
-\r
-class ValueObject (object):\r
-    def __init__(self):\r
-        pass\r
-\r
-\r
-class IntProperty2 (wxpg.PyProperty):\r
-    """This is a simple re-implementation of wxIntProperty.\r
-    """\r
-    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
-        wxpg.PyProperty.__init__(self, label, name)\r
-        self.SetValue(value)\r
-\r
-    def GetClassName(self):\r
-        return "IntProperty2"\r
-\r
-    def GetEditor(self):\r
-        return "TextCtrl"\r
-\r
-    def GetValueAsString(self, flags):\r
-        return str(self.GetValue())\r
-\r
-    def PyStringToValue(self, s, flags):\r
-        try:\r
-            v = int(s)\r
-            if self.GetValue() != v:\r
-                return v\r
-        except TypeError:\r
-            if flags & wxpg.PG_REPORT_ERROR:\r
-                wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
-        return False\r
-\r
-    def PyIntToValue(self, v, flags):\r
-        if (self.GetValue() != v):\r
-            return v\r
-\r
-\r
-class PyFilesProperty(wxpg.PyArrayStringProperty):\r
-    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
-        wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
-        self.SetValue(value)\r
-\r
-    def OnSetValue(self, v):\r
-        self.value = v\r
-        self.display = ', '.join(self.value)\r
-\r
-    def GetValueAsString(self, argFlags):\r
-        return self.display\r
-\r
-    def PyStringToValue(self, s, flags):\r
-        return [a.strip() for a in s.split(',')]\r
-\r
-    def OnEvent(self, propgrid, ctrl, event):\r
-        if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
-            # Show dialog to select a string, call DoSetValue and\r
-            # return True, if value changed.\r
-            return True\r
-\r
-        return False\r
-\r
-\r
-class PyObjectPropertyValue:\r
-    """\\r
-    Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
-    list of string given as argument to constructor.\r
-    """\r
-    def __init__(self, s=None):\r
-        try:\r
-            self.ls = [a.strip() for a in s.split('-')]\r
-        except:\r
-            self.ls = []\r
-\r
-    def __repr__(self):\r
-        return ' - '.join(self.ls)\r
-\r
-\r
-class PyObjectProperty(wxpg.PyProperty):\r
-    """\\r
-    Another simple example. This time our value is a PyObject (NOTE: we can't\r
-    return an arbitrary python object in DoGetValue. It cannot be a simple\r
-    type such as int, bool, double, or string, nor an array or wxObject based.\r
-    Dictionary, None, or any user-specified Python object is allowed).\r
-    """\r
-    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
-        wxpg.PyProperty.__init__(self, label, name)\r
-        self.SetValue(value)\r
-\r
-    def GetClassName(self):\r
-        return self.__class__.__name__\r
-\r
-    def GetEditor(self):\r
-        return "TextCtrl"\r
-\r
-    def GetValueAsString(self, flags):\r
-        return repr(self.GetValue())\r
-\r
-    def PyStringToValue(self, s, flags):\r
-        return PyObjectPropertyValue(s)\r
-\r
-\r
-class ShapeProperty(wxpg.PyEnumProperty):\r
-    """\\r
-    Demonstrates use of OnCustomPaint method.\r
-    """\r
-    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
-        wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
-\r
-    def OnMeasureImage(self, index):\r
-        return wxpg.DEFAULT_IMAGE_SIZE\r
-\r
-    def OnCustomPaint(self, dc, rect, paint_data):\r
-        """\\r
-        paint_data.m_choiceItem is -1 if we are painting the control,\r
-        in which case we need to get the drawn item using DoGetValue.\r
-        """\r
-        item = paint_data.m_choiceItem\r
-        if item == -1:\r
-            item = self.DoGetValue()\r
-\r
-        dc.SetPen(wx.Pen(wx.BLACK))\r
-        dc.SetBrush(wx.Brush(wx.BLACK))\r
-\r
-        if item == 0:\r
-            dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
-        elif item == 1:\r
-            half_width = rect.width / 2\r
-            dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
-        elif item == 2:\r
-            dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
-\r
-\r
-class LargeImagePickerCtrl(wx.Window):\r
-    """\\r
-    Control created and used by LargeImageEditor.\r
-    """\r
-    def __init__(self):\r
-        pre = wx.PreWindow()\r
-        self.PostCreate(pre)\r
-\r
-    def Create(self, parent, id_, pos, size, style = 0):\r
-        wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
-        img_spc = size[1]\r
-        self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
-        self.SetBackgroundColour(wx.WHITE)\r
-        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
-        self.property = None\r
-        self.bmp = None\r
-        self.Bind(wx.EVT_PAINT, self.OnPaint)\r
-\r
-    def OnPaint(self, event):\r
-        dc = wx.BufferedPaintDC(self)\r
-\r
-        whiteBrush = wx.Brush(wx.WHITE)\r
-        dc.SetBackground(whiteBrush)\r
-        dc.Clear()\r
-\r
-        bmp = self.bmp\r
-        if bmp:\r
-            dc.DrawBitmap(bmp, 2, 2)\r
-        else:\r
-            dc.SetPen(wx.Pen(wx.BLACK))\r
-            dc.SetBrush(whiteBrush)\r
-            dc.DrawRectangle(2, 2, 64, 64)\r
-\r
-    def RefreshThumbnail(self):\r
-        """\\r
-        We use here very simple image scaling code.\r
-        """\r
-        if not self.property:\r
-            self.bmp = None\r
-            return\r
-\r
-        path = self.property.DoGetValue()\r
-\r
-        if not os.path.isfile(path):\r
-            self.bmp = None\r
-            return\r
-\r
-        image = wx.Image(path)\r
-        image.Rescale(64, 64)\r
-        self.bmp = wx.BitmapFromImage(image)\r
-\r
-    def SetProperty(self, property):\r
-        self.property = property\r
-        self.tc.SetValue(property.GetDisplayedString())\r
-        self.RefreshThumbnail()\r
-\r
-    def SetValue(self, s):\r
-        self.RefreshThumbnail()\r
-        self.tc.SetValue(s)\r
-\r
-    def GetLastPosition(self):\r
-        return self.tc.GetLastPosition()\r
-\r
-\r
-class LargeImageEditor(wxpg.PyEditor):\r
-    """\\r
-    Double-height text-editor with image in front.\r
-    """\r
-    def __init__(self):\r
-        wxpg.PyEditor.__init__(self)\r
-\r
-    def CreateControls(self, propgrid, property, pos, sz):\r
-        try:\r
-            h = 64 + 6\r
-            x = propgrid.GetSplitterPosition()\r
-            x2 = propgrid.GetClientSize().x\r
-            bw = propgrid.GetRowHeight()\r
-            lipc = LargeImagePickerCtrl()\r
-            if sys.platform == 'win32':\r
-                lipc.Hide()\r
-            lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
-            lipc.SetProperty(property)\r
-            # Hmmm.. how to have two-stage creation without subclassing?\r
-            #btn = wx.PreButton()\r
-            #pre = wx.PreWindow()\r
-            #self.PostCreate(pre)\r
-            #if sys.platform == 'win32':\r
-            #    btn.Hide()\r
-            #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
-            btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
-            return (lipc, btn)\r
-        except:\r
-            import traceback\r
-            print traceback.print_exc()\r
-\r
-    def UpdateControl(self, property, ctrl):\r
-        ctrl.SetValue(property.GetDisplayedString())\r
-\r
-    def DrawValue(self, dc, property, rect):\r
-        if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
-            dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
-\r
-    def OnEvent(self, propgrid, ctrl, event):\r
-        if not ctrl:\r
-            return False\r
-\r
-        evtType = event.GetEventType()\r
-\r
-        if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
-            if propgrid.IsEditorsValueModified():\r
-                return True\r
-\r
-        elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
-            if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
-               ctrl.GetLastPosition() > 0:\r
-\r
-                # We must check this since an 'empty' text event\r
-                # may be triggered when creating the property.\r
-                PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
-                if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
-                    event.Skip();\r
-                    event.SetId(propgrid.GetId());\r
-\r
-                propgrid.EditorsValueWasModified();\r
-\r
-        return False\r
-\r
-\r
-    def CopyValueFromControl(self, property, ctrl):\r
-        tc = ctrl.tc\r
-        res = property.SetValueFromString(tc.GetValue(),0)\r
-        # Changing unspecified always causes event (returning\r
-        # true here should be enough to trigger it).\r
-        if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
-            res = True\r
-\r
-        return res\r
-\r
-    def SetValueToUnspecified(self, ctrl):\r
-        ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
-\r
-    def SetControlStringValue(self, ctrl, txt):\r
-        ctrl.SetValue(txt)\r
-\r
-    def OnFocus(self, property, ctrl):\r
-        ctrl.tc.SetSelection(-1,-1)\r
-        ctrl.tc.SetFocus()\r
-\r
-\r
-class PropertyEditor(wx.Panel):\r
-\r
-    def __init__(self, parent):\r
-        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
-        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
-\r
-        sizer = wx.BoxSizer(wx.VERTICAL)\r
-\r
-        self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
-\r
-        # Show help as tooltips\r
-        self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
-\r
-        #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
-        #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
-        #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
-\r
-        # Needed by custom image editor\r
-        wx.InitAllImageHandlers()\r
-\r
-        #\r
-        # Let's create a simple custom editor\r
-        #\r
-        # NOTE: Editor must be registered *before* adding a property that uses it.\r
-        self.pg.RegisterEditor(LargeImageEditor)\r
-\r
-        '''\r
-        #\r
-        # Add properties\r
-        #\r
-\r
-        pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
-        pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
-        pg.Append( wxpg.IntProperty("Int",value=100) )\r
-        pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
-        pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
-        pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
-        pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
-\r
-        pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
-        pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
-        pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
-        pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
-        pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
-\r
-        pg.Append( wxpg.EnumProperty("Enum","Enum",\r
-                                     ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
-                                     [10,11,12],0) )\r
-        pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
-\r
-        pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
-        pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
-        pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
-        pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
-        pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
-        pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
-        pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
-\r
-        pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
-        pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
-        pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
-        pg.Append( wxpg.FontDataProperty("FontData") )\r
-        pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
-        pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
-        pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
-        pg.SetPropertyHelpString( "String", "String Property help string!" )\r
-        pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
-\r
-        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
-        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
-        pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
-\r
-        pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
-        pg.Append( IntProperty2("IntProperty2", value=1024) )\r
-\r
-        pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
-        pg.Append( PyObjectProperty("PyObjectProperty") )\r
-\r
-        pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
-        pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
-\r
-\r
-        pg.SetPropertyClientData( "Point", 1234 )\r
-        if pg.GetPropertyClientData( "Point" ) != 1234:\r
-            raise ValueError("Set/GetPropertyClientData() failed")\r
-\r
-        # Test setting unicode string\r
-        pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
-\r
-        #\r
-        # Test some code that *should* fail (but not crash)\r
-        #try:\r
-            #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
-            #pg.EnableProperty( "NotAtAllRealProperty", False )\r
-            #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
-        #except:\r
-            #pass\r
-            #raise\r
-\r
-        '''\r
-        sizer.Add(self.pg, 1, wx.EXPAND)\r
-        self.SetSizer(sizer)\r
-        sizer.SetSizeHints(self)\r
-\r
-        self.SelectedTreeItem = None\r
-\r
-    def GetPropertyValues(self):\r
-        return self.pg.GetPropertyValues()\r
-\r
-    def Initialize(self, properties):\r
-        pg = self.pg\r
-        pg.Clear()\r
-\r
-        if properties:\r
-            for element in properties:\r
-                if element[1]['type'] == 'arraystring':\r
-                    elements = element[1]['elements']\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1]['value']\r
-                    else:\r
-                        property_value = element[1]['default']\r
-                    #retrieve individual strings\r
-                    property_value = split(property_value, ' ')\r
-                    #remove&n