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 c5691de253b8e6789d6d08dcff03fbcfb7283f78..add94b8d31ce5823a1938f90354fb7fc023fbc50 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 fe6a3f171310bdcff9e3585ed1281fcdaa1850b7..6276712801418be5c71c22792284a405925d5123 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 de40aee5e671fbc02d0426ee3beea304807d6843..1630d05ddca13786a5231acf5a60d82002b632ba 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 4740be7247419a6cb4dee931b39387785f61f1fd..5759c05046c807271fd099ff70e410964f8870fe 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 fb91500ab005ab0601f62542c6f7556eaa872a54..346db391dbeff190df0af125295eb22e5cc89406 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 c8b1253e99422cfba8f52d269855c8a81b50942c..17c38ce8ca0c457e48d1b2a45ed0c8bb1dac6a25 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 e4bb9c16baa03f5cf2aa771c945c8f9c134976cb..974e3b5e7f121b65d153c517de027f164c74ac11 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 ad3d6858138226a392d6c7e6612ccb4e4b8c1765..28c46919a36d83ba9693b5b24a1131728f8226b2 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 b9ff34368796f1116e8178f77f6de3dfbb03f3c4..d2025cfe5847438541f3a499643906610205d916 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 97d645b8658d09c5390820a518fc5bec1ba08c91..11ee1a8649d0f954187c435b0e1c9feb887c4978 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 db313182c7f95b0f138fc351cd232b8f39829bc2..7e3d084c213e909ae2d36ba74a35b608232ab514 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 e51fb225e9bc5add71a229b143e4d3ab26cf7973..0e860c18cbccbb69e220259adc823d66879f0df6 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 89ff5ff52022430b104476196770f5f648ad023b..3ec4cbe054abe49178b273249a20a8c4e9d2a7e6 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 4a05474339eee56c38d65d48bd76901f864432ef..2614319c01f9fe1dfbd2714a8d1c9a7f9c20dde6 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 8cd5f5e0547e55640b877f3a2471ff4c4cdac1f8..4f837c9f7f1e13b55bd6b24a2442c0d349352eb1 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 a25f33e0106195f2610caeebdb9f9073ec0d7ece..209bbc70c3d51c99fdfd9fe2215d8310b10287e6 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 e6964f6058808dd343b5c698e3c702751b7008c8..2cbb852d627ec5e93ccbeb928ce1dbce18b54e64 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 f8208c04aea545dbfe482eeec35ff48e68feada7..366e9784938ec5018cec102ee28c8859c7e876f5 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 4c5363cc3e55e0caae5a824b2e70be04eaf06fa9..aefb5a5b12ad98e2f6f570b149e9e713346be3ff 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 fce318c0a8e87d1c346b4124e8a9d1a4fc0e3a43..c57643cc67fad1a206dffcbe256e54923d4f0ddd 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 295480404e545c41f464c6d01180b06e8d94df4c..19f8e3dd2c1b62c5e7094ec202ef824227df060a 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 42204d3d5670665fae4b99832e54ad1350825e72..7dfb6e84365185e3527fde152ed57d816dedd9b2 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 8cef93947c75f1a217887d0c87a746793d6805b0..893512076eafdd6d2f144e1620d75c58be7bcd7f 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 a973730f65a4af3c422bff3951dc96605f58c250..b91551c2985ecd44528b292b27f7cb057dd00560 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 9f944888caf62a7f2cc52d40c68819891261741f..81decf943971a269fcc6c067718904bf18f41540 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 d1c3b9c7a9fd4d4a93e3294ae3bda41f705b2bfd..1923d229c6151bdc241c51f6e4df99dcd047e43d 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 97ac0b29fcc599944266a72fb687e98e52aa83da..35492a79f351a43f21fc5c08eab03c46ed87bb60 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 bba2990604927b1fbcdc624dfe7992a94e678295..c57604ff08872fd8f9ab3aef584551947eac191c 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 80c2b52816411b12275b635ab6778a46d99b066a..f589e3591fbc5dfc7ce9a892d9d8b0682a3b5442 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 ac76228a63b55167f18cad4a12fa1eb37a44b4fb..7488c3a299ba582cc325a4da0cc64bcfe347b13d 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 21fd56ae2264f0ecdc6c0e8ad18efbeadab2e1d8..b0bcf3b50619f2cdf53207b3cd7c35f8dbf7100b 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 3d1e883365ed3eee8393c83f27580bd78da3756d..22b26c765d1285fb82e37501103832c4ccd40268 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 56b43799ffd358096b6ddd142853bf38acc42771..b517c0b73f23c9290196734b50ff5b5bddbf9aab 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 9eb112b23cd40647aa621528b44fc1ca4d0ba8c3..ad1a0679a00781f790964db28a7dbdab2a831ea1 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 a2e88127887c79570afe75b6a7da77db01625652..1855450d4d3293ecd6b33d05bba95e48afa7179a 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 54aff5f123c391b113c4f49580f3e24ba703f914..b97022e5be93560c2c0f67a279125c7baf66a09d 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 7ea27262aa9df338751e7881d6ceeb1cc658bdf8..c566a8bcf25478b83fe6d75013ed37c73c706981 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 716a4be7f61df45684d554e2915a1519d6664e7f..9bf6db54ec64bbb5b39a6d7fda4a7a3963e8dff7 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 a469f8e7cee4b3b3a5d92eceec2b0728cfa6d64f..1c530524c0f34599072c7b3d8833ac9cf1d825d6 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 a205a752962343ea0ec9728534b872e1c44d811f..2e02b4b41a8e4b0f4f6b4e329fa7846dea6ab23d 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 d405bf9b2ad1bd0cba84cee1a00c998a68df7b96..21fecdb3da8f61d6a7ca18d0710c0b0e2b530246 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 c7cd1e79776a81b702ca5def0166e0fb315d1469..bfc15d5578296537461f3e41b543a2a10a462a5c 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 c5fcd868a42d722faf16c891aca2a3e8cd676b4e..c657af9ddb302db0771a2f4ce39a142840bb60f2 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 610043e919afb73e39fa14cfbb0b5c397cde6d5b..7c8291ac4135680619a098ac28d779a77c4033bc 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 56befcaeef7f8c9d6d757993029da4f77e14e617..73924184d0d2ba07550afcc84a75e0f2fa61b68a 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 3847dff523e61d8add24d00e85cf5061d0eaf3e0..b36c8013c84962e9c02fffa690d034d632bcccd4 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 d8525e2033407494d25350fb360a96157faa88ac..87ebdd5e5dd926b44a65eb72b4f745de80fd3839 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 b9d5240da7fe69d214fc4b5549aaa5a0b176ca1b..82d0c83c4ddaf9975716fde060183f331919861d 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 485904fcd4c911b571e4eb7463c7a274c797d661..2be0c9bdef44b78bcd3f0463da1f0015dd49dddd 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 fcfdd666f19c0910691ab4747fae9dc87eeff573..f1d856060db58f29d9400f4f136364952fcaaf45 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 1bae5bc6f43d9d2831a68728e0bf853880c56344..b580c07307a1f5050ea18034db3126b4691a1ae4 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 ea916e14b826d944a5ac7ee59589c33d5d3824b4..dcb5bf053c8f88e5524674f86a6a2de66e236ac0 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 8a0923597bab30dceb0fbd6ba8a2c9ca0f2141a1..84f9ee6f7fa2883f66239fd97ff17230ccde0e8c 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 ae318afdceab3be4f926402e33b1fd828e095c0f..f68169304f61e5bd08f22d5d4624caa9af986a27 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 95f1f0cfa0503ed93ebc6f95b9d3820a6c76bb49..9f0fb5181658651139c9fa0af2ebedecb13d3848 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 fca2cd3be5352d51cef2c463dc2cc25b390b961d..fcf49e8598c9c7a0cb1793716ae607bf5d132881 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 18a220657dc06a037b4c283ad816a38ea1900d91..ad09e0236135c6b8fa0b91e0ede1f74e0c407dbc 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 291f39558e9592f18492b985a21a985a91798764..c601ddcc27e9f2bb6568274508503142567bbe7c 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 e9a394552a07e03c602da4ab334251a76a1eab6c..9059b8c85088c1d3b8390c99ad068b2f01d365df 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 377ff2aa72fcdd701fb3febd142522dfc86023a7..35c244cfe33edffe237b19cf9befafd2ec775223 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 0cb446e2537a13bb1361e48eb2ec4aa6c2215e7d..7f7b9ff0041e22fea7f723127ff01fda4c49eb1b 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 1e86ee14dc80b3aa29a80607923f7700fc3dfb4f..65ea3aad8f8a03936d7d145a2afaf282b95f92c5 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 ade99b2cf2357488e503903d70d9b0a9b9c602df..0d72db0ae109fe7845343a93701a3048dfa4be9e 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 bc5f18587c366a4dc1c45f2859e51187393d1048..ff7fbb70b059558c26668b6bd4445efe339a0e42 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 ae5643e38fde44474134d737e33bd64ab4b731b3..0ebf65960168ea09e0bf8b45d33205214d4ab4d7 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 0d037942e7f8592fc9833a6f074c72c159b13557..98bdb06a257eae2e0d4cf1abeee652598ceae7b7 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 56404d761aa8e1ba39d44dbfcb79c096bb271b81..c87bf83e5bf2fd09b077334fd771d7620af903f5 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 89ef42650895249afea9979ce0e53ebd5b842914..ee0e5e0d10f43c0a5aec8e5a4854a4cb317147cd 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 fe2d7e3ffa1d6edcae84b0e833339f036b471458..4147026327d549465db740ca7a07f08fa3d8b876 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 068e32e625442919e9a7724f13a2bd93b90f8943..d918a94988a54847523c0eb5d21b858b31b1d96e 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 535f0f6ea857f45258b719b61b41d2b961a323d7..64e5895c597acf7434f935a3df493ec078602f23 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 cbb4aee87dd3d49140ca6f3d110e9241d0f69389..a1be1ebf337a7afc51e2f8be032487594a1f0d7c 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 f46f098bd21713c841759ec15c6de7f7b2e163b2..5d27cc0e4bdaeede7ccc743ef937c42c1a3755da 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 30de470cb0546466d6c000461a8b0763b7ea7409..741c6df436e5efc5313c826d995e63ac4f8e37eb 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 43b8bb7ba59b2b3c9fef4124f805636480188f0c..e00a8015b6628cc1f0aa72c97e06098a002c31af 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 d3b66b5640fa0cba9b76499fbab87442a1d5f637..5edb72fd1533d153ebd0904b478c218393558e5f 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 85184737b3d441a95cf2a7091fb36fc9b6ecefec..f5c840fd52c1b30dcbd91a1d6fdfb5b7f8a9481f 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 3652579aceb6349969e33b3f0c6a94201bf43b11..65f054291d5b51cbd3fcd30476a89253c6314fb6 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 edf0968ab8ace51886c548f0530683874dbd51d7..9686e4bf58482e094c1277782ab653c105e331cc 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 " delimiters\r
-                    values = [value.strip('"') for value in property_value]\r
-                    pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
-\r
-                if element[1]['type'] == 'boolean':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1].as_bool('value')\r
-                    else:\r
-                        property_value = element[1].as_bool('default')\r
-                    property_control = wxpg.BoolProperty(element[0], value=property_value)\r
-                    pg.Append(property_control)\r
-                    pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
-\r
-                #if element[0] == 'category':\r
-                    #pg.Append(wxpg.PropertyCategory(element[1]))\r
-\r
-                if element[1]['type'] == 'color':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1]['value']\r
-                    else:\r
-                        property_value = element[1]['default']\r
-                    property_value = eval(property_value)\r
-                    pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
-\r
-                if element[1]['type'] == 'enum':\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
-                    pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
-\r
-                if element[1]['type'] == 'filename':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1]['value']\r
-                    else:\r
-                        property_value = element[1]['default']\r
-                    pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
-\r
-                if element[1]['type'] == 'float':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1].as_float('value')\r
-                    else:\r
-                        property_value = element[1].as_float('default')\r
-                    property_control = wxpg.FloatProperty(element[0], value=property_value)\r
-                    pg.Append(property_control)\r
-\r
-                if element[1]['type'] == 'folder':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1]['value']\r
-                    else:\r
-                        property_value = element[1]['default']\r
-                    pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
-\r
-                if element[1]['type'] == 'integer':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1].as_int('value')\r
-                    else:\r
-                        property_value = element[1].as_int('default')\r
-                    property_control = wxpg.IntProperty(element[0], value=property_value)\r
-                    if 'maximum' in element[1]:\r
-                        property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
-                    if 'minimum' in element[1]:\r
-                        property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
-                    property_control.SetAttribute('Wrap', True)\r
-                    pg.Append(property_control)\r
-                    pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
-\r
-                if element[1]['type'] == 'string':\r
-                    if 'value' in element[1]:\r
-                        property_value = element[1]['value']\r
-                    else:\r
-                        property_value = element[1]['default']\r
-                    pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
-\r
-        pg.Refresh()\r
-\r
-    def OnReserved(self, event):\r
-        pass\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/>.
+
+"""Property editor panel for Hooke.
+"""
+
+import sys
+import os.path
+
+import wx
+import wx.propgrid as wxpg
+
+# There are many comments and code fragments in here from the demo app.
+# They should come in handy to expand the functionality in the future.
+
+class Display (object):
+    property_descriptor = []
+    def __init__(self):
+        pass
+
+class ValueObject (object):
+    def __init__(self):
+        pass
+
+
+class IntProperty2 (wxpg.PyProperty):
+    """This is a simple re-implementation of wxIntProperty.
+    """
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):
+        wxpg.PyProperty.__init__(self, label, name)
+        self.SetValue(value)
+
+    def GetClassName(self):
+        return "IntProperty2"
+
+    def GetEditor(self):
+        return "TextCtrl"
+
+    def GetValueAsString(self, flags):
+        return str(self.GetValue())
+
+    def PyStringToValue(self, s, flags):
+        try:
+            v = int(s)
+            if self.GetValue() != v:
+                return v
+        except TypeError:
+            if flags & wxpg.PG_REPORT_ERROR:
+                wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")
+        return False
+
+    def PyIntToValue(self, v, flags):
+        if (self.GetValue() != v):
+            return v
+
+
+class PyFilesProperty(wxpg.PyArrayStringProperty):
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):
+        wxpg.PyArrayStringProperty.__init__(self, label, name, value)
+        self.SetValue(value)
+
+    def OnSetValue(self, v):
+        self.value = v
+        self.display = ', '.join(self.value)
+
+    def GetValueAsString(self, argFlags):
+        return self.display
+
+    def PyStringToValue(self, s, flags):
+        return [a.strip() for a in s.split(',')]
+
+    def OnEvent(self, propgrid, ctrl, event):
+        if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:
+            # Show dialog to select a string, call DoSetValue and
+            # return True, if value changed.
+            return True
+
+        return False
+
+
+class PyObjectPropertyValue:
+    """\
+    Value type of our sample PyObjectProperty. We keep a simple dash-delimited
+    list of string given as argument to constructor.
+    """
+    def __init__(self, s=None):
+        try:
+            self.ls = [a.strip() for a in s.split('-')]
+        except:
+            self.ls = []
+
+    def __repr__(self):
+        return ' - '.join(self.ls)
+
+
+class PyObjectProperty(wxpg.PyProperty):
+    """\
+    Another simple example. This time our value is a PyObject (NOTE: we can't
+    return an arbitrary python object in DoGetValue. It cannot be a simple
+    type such as int, bool, double, or string, nor an array or wxObject based.
+    Dictionary, None, or any user-specified Python object is allowed).
+    """
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):
+        wxpg.PyProperty.__init__(self, label, name)
+        self.SetValue(value)
+
+    def GetClassName(self):
+        return self.__class__.__name__
+
+    def GetEditor(self):
+        return "TextCtrl"
+
+    def GetValueAsString(self, flags):
+        return repr(self.GetValue())
+
+    def PyStringToValue(self, s, flags):
+        return PyObjectPropertyValue(s)
+
+
+class ShapeProperty(wxpg.PyEnumProperty):
+    """\
+    Demonstrates use of OnCustomPaint method.
+    """
+    def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):
+        wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)
+
+    def OnMeasureImage(self, index):
+        return wxpg.DEFAULT_IMAGE_SIZE
+
+    def OnCustomPaint(self, dc, rect, paint_data):
+        """\
+        paint_data.m_choiceItem is -1 if we are painting the control,
+        in which case we need to get the drawn item using DoGetValue.
+        """
+        item = paint_data.m_choiceItem
+        if item == -1:
+            item = self.DoGetValue()
+
+        dc.SetPen(wx.Pen(wx.BLACK))
+        dc.SetBrush(wx.Brush(wx.BLACK))
+
+        if item == 0:
+            dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)
+        elif item == 1:
+            half_width = rect.width / 2
+            dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)
+        elif item == 2:
+            dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
+
+
+class LargeImagePickerCtrl(wx.Window):
+    """\
+    Control created and used by LargeImageEditor.
+    """
+    def __init__(self):
+        pre = wx.PreWindow()
+        self.PostCreate(pre)
+
+    def Create(self, parent, id_, pos, size, style = 0):
+        wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)
+        img_spc = size[1]
+        self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)
+        self.SetBackgroundColour(wx.WHITE)
+        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        self.property = None
+        self.bmp = None
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+
+    def OnPaint(self, event):
+        dc = wx.BufferedPaintDC(self)
+
+        whiteBrush = wx.Brush(wx.WHITE)
+        dc.SetBackground(whiteBrush)
+        dc.Clear()
+
+        bmp = self.bmp
+        if bmp:
+            dc.DrawBitmap(bmp, 2, 2)
+        else:
+            dc.SetPen(wx.Pen(wx.BLACK))
+            dc.SetBrush(whiteBrush)
+            dc.DrawRectangle(2, 2, 64, 64)
+
+    def RefreshThumbnail(self):
+        """\
+        We use here very simple image scaling code.
+        """
+        if not self.property:
+            self.bmp = None
+            return
+
+        path = self.property.DoGetValue()
+
+        if not os.path.isfile(path):
+            self.bmp = None
+            return
+
+        image = wx.Image(path)
+        image.Rescale(64, 64)
+        self.bmp = wx.BitmapFromImage(image)
+
+    def SetProperty(self, property):
+        self.property = property
+        self.tc.SetValue(property.GetDisplayedString())
+        self.RefreshThumbnail()
+
+    def SetValue(self, s):
+        self.RefreshThumbnail()
+        self.tc.SetValue(s)
+
+    def GetLastPosition(self):
+        return self.tc.GetLastPosition()
+
+
+class LargeImageEditor(wxpg.PyEditor):
+    """\
+    Double-height text-editor with image in front.
+    """
+    def __init__(self):
+        wxpg.PyEditor.__init__(self)
+
+    def CreateControls(self, propgrid, property, pos, sz):
+        try:
+            h = 64 + 6
+            x = propgrid.GetSplitterPosition()
+            x2 = propgrid.GetClientSize().x
+            bw = propgrid.GetRowHeight()
+            lipc = LargeImagePickerCtrl()
+            if sys.platform == 'win32':
+                lipc.Hide()
+            lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))
+            lipc.SetProperty(property)
+            # Hmmm.. how to have two-stage creation without subclassing?
+            #btn = wx.PreButton()
+            #pre = wx.PreWindow()
+            #self.PostCreate(pre)
+            #if sys.platform == 'win32':
+            #    btn.Hide()
+            #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)
+            btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)
+            return (lipc, btn)
+        except:
+            import traceback
+            print traceback.print_exc()
+
+    def UpdateControl(self, property, ctrl):
+        ctrl.SetValue(property.GetDisplayedString())
+
+    def DrawValue(self, dc, property, rect):
+        if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):
+            dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );
+
+    def OnEvent(self, propgrid, ctrl, event):
+        if not ctrl:
+            return False
+
+        evtType = event.GetEventType()
+
+        if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:
+            if propgrid.IsEditorsValueModified():
+                return True
+
+        elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:
+            if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \
+               ctrl.GetLastPosition() > 0:
+
+                # We must check this since an 'empty' text event
+                # may be triggered when creating the property.
+                PG_FL_IN_SELECT_PROPERTY = 0x00100000
+                if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):
+                    event.Skip();
+                    event.SetId(propgrid.GetId());
+
+                propgrid.EditorsValueWasModified();
+
+        return False
+
+
+    def CopyValueFromControl(self, property, ctrl):
+        tc = ctrl.tc
+        res = property.SetValueFromString(tc.GetValue(),0)
+        # Changing unspecified always causes event (returning
+        # true here should be enough to trigger it).
+        if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):
+            res = True
+
+        return res
+
+    def SetValueToUnspecified(self, ctrl):
+        ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));
+
+    def SetControlStringValue(self, ctrl, txt):
+        ctrl.SetValue(txt)
+
+    def OnFocus(self, property, ctrl):
+        ctrl.tc.SetSelection(-1,-1)
+        ctrl.tc.SetFocus()
+
+
+class PropertyEditor(wx.Panel):
+
+    def __init__(self, parent):
+        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
+        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)
+
+        # Show help as tooltips
+        self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
+
+        #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)
+        #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)
+        #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)
+
+        # Needed by custom image editor
+        wx.InitAllImageHandlers()
+
+        #
+        # Let's create a simple custom editor
+        #
+        # NOTE: Editor must be registered *before* adding a property that uses it.
+        self.pg.RegisterEditor(LargeImageEditor)
+
+        '''
+        #
+        # Add properties
+        #
+
+        pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )
+        pg.Append( wxpg.StringProperty("String",value="Some Text") )
+        pg.Append( wxpg.IntProperty("Int",value=100) )
+        pg.Append( wxpg.FloatProperty("Float",value=100.0) )
+        pg.Append( wxpg.BoolProperty("Bool",value=True) )
+        pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )
+        pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)
+
+        pg.Append( wxpg.PropertyCategory("2 - More Properties") )
+        pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )
+        pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )
+        pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )
+        pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )
+
+        pg.Append( wxpg.EnumProperty("Enum","Enum",
+                                     ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],
+                                     [10,11,12],0) )
+        pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )
+
+        pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )
+        pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )
+        pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )
+        pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )
+        pg.Append( wxpg.SystemColourProperty("SystemColour") )
+        pg.Append( wxpg.ImageFileProperty("ImageFile") )
+        pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )
+
+        pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )
+        pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )
+        pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )
+        pg.Append( wxpg.FontDataProperty("FontData") )
+        pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )
+        pg.SetPropertyEditor("IntWithSpin","SpinCtrl")
+        pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )
+        pg.SetPropertyHelpString( "String", "String Property help string!" )
+        pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )
+
+        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )
+        pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )
+        pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )
+
+        pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )
+        pg.Append( IntProperty2("IntProperty2", value=1024) )
+
+        pg.Append( ShapeProperty("ShapeProperty", value=0) )
+        pg.Append( PyObjectProperty("PyObjectProperty") )
+
+        pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )
+        pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")
+
+
+        pg.SetPropertyClientData( "Point", 1234 )
+        if pg.GetPropertyClientData( "Point" ) != 1234:
+            raise ValueError("Set/GetPropertyClientData() failed")
+
+        # Test setting unicode string
+        pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")
+
+        #
+        # Test some code that *should* fail (but not crash)
+        #try:
+            #a_ = pg.GetPropertyValue( "NotARealProperty" )
+            #pg.EnableProperty( "NotAtAllRealProperty", False )
+            #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )
+        #except:
+            #pass
+            #raise
+
+        '''
+        sizer.Add(self.pg, 1, wx.EXPAND)
+        self.SetSizer(sizer)
+        sizer.SetSizeHints(self)
+
+        self.SelectedTreeItem = None
+
+    def GetPropertyValues(self):
+        return self.pg.GetPropertyValues()
+
+    def Initialize(self, properties):
+        pg = self.pg
+        pg.Clear()
+
+        if properties:
+            for element in properties:
+                if element[1]['type'] == 'arraystring':
+                    elements = element[1]['elements']
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    #retrieve individual strings
+                    property_value = split(property_value, ' ')
+                    #remove " delimiters
+                    values = [value.strip('"') for value in property_value]
+                    pg.Append(wxpg.ArrayStringProperty(element[0], value=values))
+
+                if element[1]['type'] == 'boolean':
+                    if 'value' in element[1]:
+                        property_value = element[1].as_bool('value')
+                    else:
+                        property_value = element[1].as_bool('default')
+                    property_control = wxpg.BoolProperty(element[0], value=property_value)
+                    pg.Append(property_control)
+                    pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)
+
+                #if element[0] == 'category':
+                    #pg.Append(wxpg.PropertyCategory(element[1]))
+
+                if element[1]['type'] == 'color':
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    property_value = eval(property_value)
+                    pg.Append(wxpg.ColourProperty(element[0], value=property_value))
+
+                if element[1]['type'] == 'enum':
+                    elements = element[1]['elements']
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))
+
+                if element[1]['type'] == 'filename':
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    pg.Append(wxpg.FileProperty(element[0], value=property_value))
+
+                if element[1]['type'] == 'float':
+                    if 'value' in element[1]:
+                        property_value = element[1].as_float('value')
+                    else:
+                        property_value = element[1].as_float('default')
+                    property_control = wxpg.FloatProperty(element[0], value=property_value)
+                    pg.Append(property_control)
+
+                if element[1]['type'] == 'folder':
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    pg.Append(wxpg.DirProperty(element[0], value=property_value))
+
+                if element[1]['type'] == 'integer':
+                    if 'value' in element[1]:
+                        property_value = element[1].as_int('value')
+                    else:
+                        property_value = element[1].as_int('default')
+                    property_control = wxpg.IntProperty(element[0], value=property_value)
+                    if 'maximum' in element[1]:
+                        property_control.SetAttribute('Max', element[1].as_int('maximum'))
+                    if 'minimum' in element[1]:
+                        property_control.SetAttribute('Min', element[1].as_int('minimum'))
+                    property_control.SetAttribute('Wrap', True)
+                    pg.Append(property_control)
+                    pg.SetPropertyEditor(element[0], 'SpinCtrl')
+
+                if element[1]['type'] == 'string':
+                    if 'value' in element[1]:
+                        property_value = element[1]['value']
+                    else:
+                        property_value = element[1]['default']
+                    pg.Append(wxpg.StringProperty(element[0], value=property_value))
+
+        pg.Refresh()
+
+    def OnReserved(self, event):
+        pass
index ab2518d3c34b8aaa3bbb57d87a5800999bbffe8e..559ba42c8485dc155640429b775d54975ebb38d7 100644 (file)
-# Copyright\r
-\r
-"""Property editor panel for Hooke.\r
-\r
-wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_.  Until\r
-then, we'll avoid it because of the *nix build problems.\r
-\r
-This module hacks together a workaround to be used until 2.9.1 is\r
-widely installed (or at least released ;).\r
-\r
-.. _included:\r
-  http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
-"""\r
-\r
-import wx.grid\r
-\r
-from . import Panel\r
-\r
-\r
-def prop_from_argument(argument, curves=None, playlists=None):\r
-    """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
-    """\r
-    type = argument.type\r
-    if type in ['driver']:  # intentionally not handled (yet)\r
-        return None\r
-    if argument.count != 1:\r
-        raise NotImplementedError(argument)\r
-    kwargs = {\r
-        'label':argument.name,\r
-        'default':argument.default,\r
-        'help':argument.help(),\r
-        }\r
-    # type consolidation\r
-    if type == 'file':\r
-        type = 'path'\r
-    # type handling\r
-    if type in ['string', 'bool', 'int', 'float', 'path']:\r
-        _class = globals()['%sProperty' % type.capitalize()]\r
-        return _class(**kwargs)\r
-    elif type in ['curve', 'playlist']:\r
-        if type == 'curve':\r
-            choices = curves  # extracted from the current playlist\r
-        else:\r
-            choices = playlists\r
-        return ChoiceProperty(choices=choices, **kwargs)\r
-    raise NotImplementedError(argument.type)\r
-\r
-def prop_from_setting(setting):\r
-    """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
-    """    \r
-    raise NotImplementedError()\r
-\r
-\r
-class Property (object):\r
-    def __init__(self, type, label, default, help=None):\r
-        self.type = type\r
-        self.label = label\r
-        self.default = default\r
-        self.help = help\r
-\r
-    def get_editor(self):\r
-        """Return a suitable grid editor.\r
-        """\r
-        raise NotImplementedError()\r
-\r
-    def get_renderer(self):\r
-        """Return a suitable grid renderer.\r
-\r
-        Returns `None` if no special renderer is required.\r
-        """\r
-        return None\r
-\r
-    def string_for_value(self, value):\r
-        """Return a string representation of `value` for loading the table.\r
-        """\r
-        return str(value)\r
-\r
-    def value_for_string(self, string):\r
-        """Return the value represented by `string`.\r
-        """\r
-        return string\r
-\r
-\r
-class StringProperty (Property):\r
-    def __init__(self, **kwargs):\r
-        assert 'type' not in kwargs, kwargs\r
-        if 'default' not in kwargs:\r
-            kwargs['default'] = 0\r
-        super(StringProperty, self).__init__(type='string', **kwargs)\r
-\r
-    def get_editor(self):\r
-        return wx.grid.GridCellTextEditor()\r
-\r
-    def get_renderer(self):\r
-        return wx.grid.GridCellStringRenderer()\r
-\r
-\r
-class BoolProperty (Property):\r
-    """A boolean property.\r
-\r
-    Notes\r
-    -----\r
-    Unfortunately, changing a boolean property takes two clicks:\r
-\r
-    1) create the editor\r
-    2) change the value\r
-\r
-    There are `ways around this`_, but it's not pretty.\r
-\r
-    .. _ways around this:\r
-      http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click\r
-    """\r
-    def __init__(self, **kwargs):\r
-        assert 'type' not in kwargs, kwargs\r
-        if 'default' not in kwargs:\r
-            kwargs['default'] = True\r
-        super(BoolProperty, self).__init__(type='bool', **kwargs)\r
-\r
-    def get_editor(self):\r
-        return wx.grid.GridCellBoolEditor()\r
-\r
-    def get_renderer(self):\r
-        return wx.grid.GridCellBoolRenderer()\r
-\r
-    def string_for_value(self, value):\r
-        if value == True:\r
-            return '1'\r
-        return ''\r
-\r
-    def value_for_string(self, string):\r
-        return string == '1'\r
-\r
-\r
-class IntProperty (Property):\r
-    def __init__(self, **kwargs):\r
-        assert 'type' not in kwargs, kwargs\r
-        if 'default' not in kwargs:\r
-            kwargs['default'] = 0\r
-        super(IntProperty, self).__init__(type='int', **kwargs)\r
-\r
-    def get_editor(self):\r
-        return wx.grid.GridCellNumberEditor()\r
-\r
-    def get_renderer(self):\r
-        return wx.grid.GridCellNumberRenderer()\r
-\r
-    def value_for_string(self, string):\r
-        return int(string)\r
-\r
-\r
-class FloatProperty (Property):\r
-    def __init__(self, **kwargs):\r
-        assert 'type' not in kwargs, kwargs\r
-        if 'default' not in kwargs:\r
-            kwargs['default'] = 0.0\r
-        super(FloatProperty, self).__init__(type='float', **kwargs)\r
-\r
-    def get_editor(self):\r
-        return wx.grid.GridCellFloatEditor()\r
-\r
-    def get_renderer(self):\r
-        return wx.grid.GridCellFloatRenderer()\r
-\r
-    def value_for_string(self, string):\r
-        return float(string)\r
-\r
-\r
-class ChoiceProperty (Property):\r
-    def __init__(self, choices, **kwargs):\r
-        assert 'type' not in kwargs, kwargs\r
-        if 'default' in kwargs:\r
-            if kwargs['default'] not in choices:\r
-                choices.insert(0, kwargs['default'])\r
-        else:\r
-            kwargs['default'] = choices[0]\r
-        super(ChoiceProperty, self).__init__(type='choice', **kwargs)\r
-        self._choices = choices\r
-\r
-    def get_editor(self):\r
-        choices = [self.string_for_value(c) for c in self._choices]\r
-        return wx.grid.GridCellChoiceEditor(choices=choices)\r
-\r
-    def get_renderer(self):\r
-        return None\r
-        #return wx.grid.GridCellChoiceRenderer()\r
-\r
-    def string_for_value(self, value):\r
-        if hasattr(value, 'name'):\r
-            return value.name\r
-        return str(value)\r
-\r
-    def value_for_string(self, string):\r
-        for choice in self._choices:\r
-            if self.string_for_value(choice) == string:\r
-               return choice\r
-        raise ValueError(string)\r
-\r
-\r
-class PathProperty (StringProperty):\r
-    """Simple file or path property.\r
-\r
-    Currently there isn't a fancy file-picker popup.  Perhaps in the\r
-    future.\r
-    """\r
-    def __init__(self, **kwargs):\r
-        super(PathProperty, self).__init__(**kwargs)\r
-        self.type = 'path'\r
-\r
-\r
-class PropertyPanel(Panel, wx.grid.Grid):\r
-    """UI to view/set config values and command argsuments.\r
-    """\r
-    def __init__(self, callbacks=None, **kwargs):\r
-        super(PropertyPanel, self).__init__(\r
-            name='property editor', callbacks=callbacks, **kwargs)\r
-        self._properties = []\r
-\r
-        self.CreateGrid(numRows=0, numCols=1)\r
-        self.SetColLabelValue(0, 'value')\r
-\r
-        self._last_tooltip = None\r
-        self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_motion)\r
-\r
-    def _on_motion(self, event):\r
-        """Enable tooltips.\r
-        """\r
-        x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
-        col,row = self.XYToCell(x, y)\r
-        if col == -1 or row == -1:\r
-            msg = ''\r
-        else:\r
-            msg = self._properties[row].help or ''\r
-        if msg != self._last_tooltip:\r
-            self._last_tooltip = msg\r
-            event.GetEventObject().SetToolTipString(msg)\r
-\r
-    def append_property(self, property):\r
-        if len([p for p in self._properties if p.label == property.label]) > 0:\r
-            raise ValueError(property)  # property.label collision\r
-        self._properties.append(property)\r
-        row = len(self._properties) - 1\r
-        self.AppendRows(numRows=1)\r
-        self.SetRowLabelValue(row, property.label)\r
-        self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
-        r = property.get_renderer()\r
-        if r != None:\r
-            self.SetCellRenderer(row=row, col=0, renderer=r)\r
-        self.set_property(property.label, property.default)\r
-\r
-    def remove_property(self, label):\r
-        row,property = self._property_by_label(label)\r
-        self._properties.pop(row)\r
-        self.DeleteRows(pos=row)\r
-\r
-    def clear(self):\r
-        while(len(self._properties) > 0):\r
-            self.remove_property(self._properties[-1].label)\r
-\r
-    def set_property(self, label, value):\r
-        row,property = self._property_by_label(label)\r
-        self.SetCellValue(row=row, col=0, s=property.string_for_value(value))\r
-\r
-    def get_property(self, label):\r
-        row,property = self._property_by_label(label)\r
-        string = self.GetCellValue(row=row, col=0)\r
-        return property.value_for_string(string)\r
-\r
-    def get_values(self):\r
-        return dict([(p.label, self.get_property(p.label))\r
-                     for p in self._properties])\r
-\r
-    def _property_by_label(self, label):\r
-        props = [(i,p) for i,p in enumerate(self._properties)\r
-                 if p.label == label]\r
-        assert len(props) == 1, props\r
-        row,property = props[0]\r
-        return (row, property)\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/>.
+
+"""Property editor panel for Hooke.
+
+wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_.  Until
+then, we'll avoid it because of the *nix build problems.
+
+This module hacks together a workaround to be used until 2.9.1 is
+widely installed (or at least released ;).
+
+.. _included:
+  http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
+"""
+
+import wx.grid
+
+from . import Panel
+
+
+def prop_from_argument(argument, curves=None, playlists=None):
+    """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.
+    """
+    type = argument.type
+    if type in ['driver']:  # intentionally not handled (yet)
+        return None
+    if argument.count != 1:
+        raise NotImplementedError(argument)
+    kwargs = {
+        'label':argument.name,
+        'default':argument.default,
+        'help':argument.help(),
+        }
+    # type consolidation
+    if type == 'file':
+        type = 'path'
+    # type handling
+    if type in ['string', 'bool', 'int', 'float', 'path']:
+        _class = globals()['%sProperty' % type.capitalize()]
+        return _class(**kwargs)
+    elif type in ['curve', 'playlist']:
+        if type == 'curve':
+            choices = curves  # extracted from the current playlist
+        else:
+            choices = playlists
+        return ChoiceProperty(choices=choices, **kwargs)
+    raise NotImplementedError(argument.type)
+
+def prop_from_setting(setting):
+    """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.
+    """    
+    raise NotImplementedError()
+
+
+class Property (object):
+    def __init__(self, type, label, default, help=None):
+        self.type = type
+        self.label = label
+        self.default = default
+        self.help = help
+
+    def get_editor(self):
+        """Return a suitable grid editor.
+        """
+        raise NotImplementedError()
+
+    def get_renderer(self):
+        """Return a suitable grid renderer.
+
+        Returns `None` if no special renderer is required.
+        """
+        return None
+
+    def string_for_value(self, value):
+        """Return a string representation of `value` for loading the table.
+        """
+        return str(value)
+
+    def value_for_string(self, string):
+        """Return the value represented by `string`.
+        """
+        return string
+
+
+class StringProperty (Property):
+    def __init__(self, **kwargs):
+        assert 'type' not in kwargs, kwargs
+        if 'default' not in kwargs:
+            kwargs['default'] = 0
+        super(StringProperty, self).__init__(type='string', **kwargs)
+
+    def get_editor(self):
+        return wx.grid.GridCellTextEditor()
+
+    def get_renderer(self):
+        return wx.grid.GridCellStringRenderer()
+
+
+class BoolProperty (Property):
+    """A boolean property.
+
+    Notes
+    -----
+    Unfortunately, changing a boolean property takes two clicks:
+
+    1) create the editor
+    2) change the value
+
+    There are `ways around this`_, but it's not pretty.
+
+    .. _ways around this:
+      http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click
+    """
+    def __init__(self, **kwargs):
+        assert 'type' not in kwargs, kwargs
+        if 'default' not in kwargs:
+            kwargs['default'] = True
+        super(BoolProperty, self).__init__(type='bool', **kwargs)
+
+    def get_editor(self):
+        return wx.grid.GridCellBoolEditor()
+
+    def get_renderer(self):
+        return wx.grid.GridCellBoolRenderer()
+
+    def string_for_value(self, value):
+        if value == True:
+            return '1'
+        return ''
+
+    def value_for_string(self, string):
+        return string == '1'
+
+
+class IntProperty (Property):
+    def __init__(self, **kwargs):
+        assert 'type' not in kwargs, kwargs
+        if 'default' not in kwargs:
+            kwargs['default'] = 0
+        super(IntProperty, self).__init__(type='int', **kwargs)
+
+    def get_editor(self):
+        return wx.grid.GridCellNumberEditor()
+
+    def get_renderer(self):
+        return wx.grid.GridCellNumberRenderer()
+
+    def value_for_string(self, string):
+        return int(string)
+
+
+class FloatProperty (Property):
+    def __init__(self, **kwargs):
+        assert 'type' not in kwargs, kwargs
+        if 'default' not in kwargs:
+            kwargs['default'] = 0.0
+        super(FloatProperty, self).__init__(type='float', **kwargs)
+
+    def get_editor(self):
+        return wx.grid.GridCellFloatEditor()
+
+    def get_renderer(self):
+        return wx.grid.GridCellFloatRenderer()
+
+    def value_for_string(self, string):
+        return float(string)
+
+
+class ChoiceProperty (Property):
+    def __init__(self, choices, **kwargs):
+        assert 'type' not in kwargs, kwargs
+        if 'default' in kwargs:
+            if kwargs['default'] not in choices:
+                choices.insert(0, kwargs['default'])
+        else:
+            kwargs['default'] = choices[0]
+        super(ChoiceProperty, self).__init__(type='choice', **kwargs)
+        self._choices = choices
+
+    def get_editor(self):
+        choices = [self.string_for_value(c) for c in self._choices]
+        return wx.grid.GridCellChoiceEditor(choices=choices)
+
+    def get_renderer(self):
+        return None
+        #return wx.grid.GridCellChoiceRenderer()
+
+    def string_for_value(self, value):
+        if hasattr(value, 'name'):
+            return value.name
+        return str(value)
+
+    def value_for_string(self, string):
+        for choice in self._choices:
+            if self.string_for_value(choice) == string:
+               return choice
+        raise ValueError(string)
+
+
+class PathProperty (StringProperty):
+    """Simple file or path property.
+
+    Currently there isn't a fancy file-picker popup.  Perhaps in the
+    future.
+    """
+    def __init__(self, **kwargs):
+        super(PathProperty, self).__init__(**kwargs)
+        self.type = 'path'
+
+
+class PropertyPanel(Panel, wx.grid.Grid):
+    """UI to view/set config values and command argsuments.
+    """
+    def __init__(self, callbacks=None, **kwargs):
+        super(PropertyPanel, self).__init__(
+            name='property editor', callbacks=callbacks, **kwargs)
+        self._properties = []
+
+        self.CreateGrid(numRows=0, numCols=1)
+        self.SetColLabelValue(0, 'value')
+
+        self._last_tooltip = None
+        self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_motion)
+
+    def _on_motion(self, event):
+        """Enable tooltips.
+        """
+        x,y = self.CalcUnscrolledPosition(event.GetPosition())
+        col,row = self.XYToCell(x, y)
+        if col == -1 or row == -1:
+            msg = ''
+        else:
+            msg = self._properties[row].help or ''
+        if msg != self._last_tooltip:
+            self._last_tooltip = msg
+            event.GetEventObject().SetToolTipString(msg)
+
+    def append_property(self, property):
+        if len([p for p in self._properties if p.label == property.label]) > 0:
+            raise ValueError(property)  # property.label collision
+        self._properties.append(property)
+        row = len(self._properties) - 1
+        self.AppendRows(numRows=1)
+        self.SetRowLabelValue(row, property.label)
+        self.SetCellEditor(row=row, col=0, editor=property.get_editor())
+        r = property.get_renderer()
+        if r != None:
+            self.SetCellRenderer(row=row, col=0, renderer=r)
+        self.set_property(property.label, property.default)
+
+    def remove_property(self, label):
+        row,property = self._property_by_label(label)
+        self._properties.pop(row)
+        self.DeleteRows(pos=row)
+
+    def clear(self):
+        while(len(self._properties) > 0):
+            self.remove_property(self._properties[-1].label)
+
+    def set_property(self, label, value):
+        row,property = self._property_by_label(label)
+        self.SetCellValue(row=row, col=0, s=property.string_for_value(value))
+
+    def get_property(self, label):
+        row,property = self._property_by_label(label)
+        string = self.GetCellValue(row=row, col=0)
+        return property.value_for_string(string)
+
+    def get_values(self):
+        return dict([(p.label, self.get_property(p.label))
+                     for p in self._properties])
+
+    def _property_by_label(self, label):
+        props = [(i,p) for i,p in enumerate(self._properties)
+                 if p.label == label]
+        assert len(props) == 1, props
+        row,property = props[0]
+        return (row, property)
index 1588dbdcb2659f0de91d0fdf1a0f917b4cae4605..35568b6ed860bf054598eb300a76e64143a860f1 100644 (file)
@@ -1,77 +1,95 @@
-# Copyright\r
-\r
-"""Fitting results panel for Hooke.\r
-"""\r
-\r
-import sys\r
-\r
-import wx\r
-from wx.lib.mixins.listctrl import CheckListCtrlMixin\r
-\r
-class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):\r
-    def __init__(self, parent):\r
-        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)\r
-        CheckListCtrlMixin.__init__(self)\r
-        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)\r
-\r
-    def OnItemActivated(self, evt):\r
-        self.ToggleItem(evt.m_itemIndex)\r
-\r
-\r
-class Results(wx.Panel):\r
-    def __init__(self, parent):\r
-        wx.Panel.__init__(self, parent, -1)\r
-        self.results_list = CheckListCtrl(self)\r
-        sizer = wx.BoxSizer()\r
-        sizer.Add(self.results_list, 1, wx.EXPAND)\r
-        self.SetSizer(sizer)\r
-        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list)\r
-        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list)\r
-\r
-    def _GetWidthInPixels(self, text):\r
-        #TODO:\r
-        #Returns the width of a string in pixels\r
-        #Unfortunately, it does not work terribly well (although it should).\r
-        #Thus, we have to add a bit afterwards.\r
-        #Annoys the heck out of me (illysam).\r
-        font = self.results_list.GetFont()\r
-        dc = wx.WindowDC(self.results_list)\r
-        dc.SetFont(font)\r
-        width, height = dc.GetTextExtent(text)\r
-        return width\r
-\r
-    def ClearResults(self):\r
-        self.results_list.ClearAll()\r
-\r
-    def DisplayResults(self, results):\r
-        self.ClearResults()\r
-        header = results.get_header_as_list()\r
-        self.results_list.InsertColumn(0, 'Show')\r
-        for index, column in enumerate(header):\r
-            self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT)\r
-\r
-        for result in results.results:\r
-            done = False\r
-            for index, column in enumerate(results.columns):\r
-                value_str = results.get_pretty_value(column, result.result[column])\r
-                if not done:\r
-                    index_col = self.results_list.InsertStringItem(sys.maxint, '')\r
-                    done = True\r
-                column_width = len(self.results_list.GetColumn(index + 1).GetText())\r
-                value_str = value_str.center(column_width)\r
-                self.results_list.SetStringItem(index_col, index + 1, value_str)\r
-\r
-        for index, result in enumerate(results.results):\r
-            if result.visible:\r
-                #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates)\r
-                self.results_list.SetItemImage(index, 1)\r
-        for index in range(self.results_list.GetColumnCount()):\r
-            column_text = self.results_list.GetColumn(index).GetText()\r
-            column_width = self._GetWidthInPixels(column_text)\r
-            self.results_list.SetColumnWidth(index, column_width + 15)\r
-\r
-    def OnItemSelected(self, evt):\r
-        pass\r
-\r
-    def OnItemDeselected(self, evt):\r
-        pass\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/>.
+
+"""Fitting results panel for Hooke.
+"""
+
+import sys
+
+import wx
+from wx.lib.mixins.listctrl import CheckListCtrlMixin
+
+class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):
+    def __init__(self, parent):
+        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
+        CheckListCtrlMixin.__init__(self)
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
+
+    def OnItemActivated(self, evt):
+        self.ToggleItem(evt.m_itemIndex)
+
+
+class Results(wx.Panel):
+    def __init__(self, parent):
+        wx.Panel.__init__(self, parent, -1)
+        self.results_list = CheckListCtrl(self)
+        sizer = wx.BoxSizer()
+        sizer.Add(self.results_list, 1, wx.EXPAND)
+        self.SetSizer(sizer)
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list)
+        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list)
+
+    def _GetWidthInPixels(self, text):
+        #TODO:
+        #Returns the width of a string in pixels
+        #Unfortunately, it does not work terribly well (although it should).
+        #Thus, we have to add a bit afterwards.
+        #Annoys the heck out of me (illysam).
+        font = self.results_list.GetFont()
+        dc = wx.WindowDC(self.results_list)
+        dc.SetFont(font)
+        width, height = dc.GetTextExtent(text)
+        return width
+
+    def ClearResults(self):
+        self.results_list.ClearAll()
+
+    def DisplayResults(self, results):
+        self.ClearResults()
+        header = results.get_header_as_list()
+        self.results_list.InsertColumn(0, 'Show')
+        for index, column in enumerate(header):
+            self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT)
+
+        for result in results.results:
+            done = False
+            for index, column in enumerate(results.columns):
+                value_str = results.get_pretty_value(column, result.result[column])
+                if not done:
+                    index_col = self.results_list.InsertStringItem(sys.maxint, '')
+                    done = True
+                column_width = len(self.results_list.GetColumn(index + 1).GetText())
+                value_str = value_str.center(column_width)
+                self.results_list.SetStringItem(index_col, index + 1, value_str)
+
+        for index, result in enumerate(results.results):
+            if result.visible:
+                #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates)
+                self.results_list.SetItemImage(index, 1)
+        for index in range(self.results_list.GetColumnCount()):
+            column_text = self.results_list.GetColumn(index).GetText()
+            column_width = self._GetWidthInPixels(column_text)
+            self.results_list.SetColumnWidth(index, column_width + 15)
+
+    def OnItemSelected(self, evt):
+        pass
+
+    def OnItemDeselected(self, evt):
+        pass
index a42ecb31d31271597071f5e6cae44e8f39a28950..13da12ec416742ab8bc5a3ff3f05edbbdc9906fb 100644 (file)
-# Copyright\r
-\r
-"""Selection dialog.\r
-"""\r
-\r
-from os import remove\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-\r
-\r
-class SelectionDialog (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,\r
-                 default=None, selection_style='single', *args, **kwargs):\r
-        super(Selection, self).__init__(*args, **kwargs)\r
-\r
-        self._options = options\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, list=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, list=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
-        border_width = 5\r
-\r
-        b = wx.BoxSizer(wx.HORIZONTAL)\r
-        b.Add(window=self._c['button'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        b.Add(window=self._c['cancel'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-\r
-        v = wx.BoxSizer(wx.VERTICAL)\r
-        v.Add(window=self._c['text'],\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        v.Add(window=self._c['listbox'],\r
-              proportion=1,\r
-              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
-              border=border_width)\r
-        v.Add(window=wx.StaticLine(\r
-                parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),\r
-              flag=wx.GROW,\r
-              border=border_width)\r
-        v.Add(window=b,\r
-              flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,\r
-              border=border_width)\r
-        self.SetSizer(v)\r
-        v.Fit(self)\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
-        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 wx
+
+from ....util.callback import callback, in_callback
+
+
+class SelectionDialog (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,
+                 default=None, selection_style='single', *args, **kwargs):
+        super(Selection, self).__init__(*args, **kwargs)
+
+        self._options = options
+        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, list=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, list=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'])
+
+        border_width = 5
+
+        b = wx.BoxSizer(wx.HORIZONTAL)
+        b.Add(window=self._c['button'],
+              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+              border=border_width)
+        b.Add(window=self._c['cancel'],
+              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+              border=border_width)
+
+        v = wx.BoxSizer(wx.VERTICAL)
+        v.Add(window=self._c['text'],
+              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+              border=border_width)
+        v.Add(window=self._c['listbox'],
+              proportion=1,
+              flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+              border=border_width)
+        v.Add(window=wx.StaticLine(
+                parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),
+              flag=wx.GROW,
+              border=border_width)
+        v.Add(window=b,
+              flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,
+              border=border_width)
+        self.SetSizer(v)
+        v.Fit(self)
+
+    @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())
+        in_callback(self, options=self._options, selected=selected)
+        self.EndModal(wx.ID_CLOSE)
index 5b5a5ae35ce0e9f4f903454b518ba37a48af6433..6e9047c50a9296f2b25e1a21c5de0c8dcac68dcd 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/>.
 
 """Welcome panel for Hooke.
 """
index 9b7af3f9fa9773e0a422b68b99ab09bdcdcb17ba..530049fc1dc1498127315a9beb697e4c43eaa46d 100644 (file)
-#!/usr/bin/env python\r
-\r
-'''\r
-playlist.py\r
-\r
-Playlist 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
-import os.path\r
-import xml.dom.minidom\r
-\r
-import lib.libhooke\r
-import lib.file\r
-\r
-class Playlist(object):\r
-\r
-    def __init__(self, filename=None):\r
-        self._saved = False\r
-        self.count = 0\r
-        self.figure = None\r
-        self.files = []\r
-        self.generics_dict = {}\r
-        self.hidden_attributes = ['curve', 'data', 'driver', 'fits', 'name', 'plot', 'plots']\r
-        self.index = -1\r
-        self.name = None\r
-        self.path = None\r
-        self.plot_panel = None\r
-        self.plot_tab = None\r
-        if filename is None:\r
-            self.filename = None\r
-        else:\r
-            self.load(filename)\r
-\r
-    def add_file(self, filename):\r
-        if os.path.isfile(filename):\r
-            file_to_add = lib.file.File(filename)\r
-            self.files.append(file_to_add)\r
-            self._saved = False\r
-        self.count = len(self.files)\r
-\r
-    def delete_file(self, name):\r
-        for index, item in enumerate(self.files):\r
-            if item.name == name:\r
-                del self.files[index]\r
-                self.index = index\r
-        self.count = len(self.files)\r
-        if self.index > self.count - 1:\r
-            self.index = 0\r
-\r
-    def filter_curves(self, curves_to_keep=[]):\r
-        playlist = Playlist()\r
-        for curve_index in curves_to_keep:\r
-            playlist.files.append(self.files[curve_index])\r
-        playlist.count = len(playlist.files)\r
-        playlist.index = 0\r
-        return playlist\r
-\r
-    def get_active_file(self):\r
-        return self.files[self.index]\r
-\r
-    #def get_active_plot(self):\r
-        ##TODO: is this the only active (or default?) plot?\r
-        #return self.files[self.index].plots[0]\r
-\r
-    def load(self, filename):\r
-        '''\r
-        Loads a playlist file\r
-        '''\r
-        self.filename = filename\r
-        self.path, self.name = os.path.split(filename)\r
-        playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename)\r
-        self.xml = xml.dom.minidom.parseString(playlist_file)\r
-        #TODO: rename 'element' to 'curve' or something in hkp file\r
-        #TODO: rename 'path' to 'filename'\r
-        #TODO: switch from attributes to nodes, it's cleaner XML in my eyes\r
-\r
-        element_list = self.xml.getElementsByTagName('element')\r
-        #populate playlist with files\r
-        for index, element in enumerate(element_list):\r
-            #rebuild a data structure from the xml attributes\r
-            #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path'\r
-            if element.hasAttribute('path'):\r
-                #path, name = os.path.split(element.getAttribute('path'))\r
-                #path = path.split(os.sep)\r
-                #filename = lib.libhooke.get_file_path(name, path)\r
-                filename = element.getAttribute('path')\r
-            if element.hasAttribute('filename'):\r
-                #path, name = os.path.split(element.getAttribute('filename'))\r
-                #path = path.split(os.sep)\r
-                #filename = lib.libhooke.get_file_path(name, path)\r
-                filename = element.getAttribute('filename')\r
-            if os.path.isfile(filename):\r
-                data_file = lib.file.File(filename)\r
-                if element.hasAttribute('note'):\r
-                    data_file.note = element.getAttribute('note')\r
-                self.files.append(data_file)\r
-        self.count = len(self.files)\r
-        if self.count > 0:\r
-            #populate generics\r
-            genericsDict = {}\r
-            generics_list = self.xml.getElementsByTagName('generics')\r
-            if generics_list:\r
-                for attribute in generics_list[0].attributes.keys():\r
-                    genericsDict[attribute] = generics_list[0].getAttribute(attribute)\r
-            if genericsDict.has_key('pointer'):\r
-                index = int(genericsDict['pointer'])\r
-                if index >= 0 and index < self.count:\r
-                    self.index = index\r
-                else:\r
-                    index = 0\r
-            self._saved = True\r
-\r
-    def next(self):\r
-        self.index += 1\r
-        if self.index > self.count - 1:\r
-            self.index = 0\r
-\r
-    def previous(self):\r
-        self.index -= 1\r
-        if self.index < 0:\r
-            self.index = self.count - 1\r
-\r
-    def reset(self):\r
-        if self.count > 0:\r
-            self.index = 0\r
-        else:\r
-            self.index = -1\r
-\r
-    def save(self, filename):\r
-        '''\r
-        Saves a playlist from a list of files.\r
-        A playlist is an XML document with the following syntax:\r
-        <playlist>\r
-        <element path="/my/file/path/"/ attribute="attribute">\r
-        <element path="...">\r
-        </playlist>\r
-        '''\r
-        try:\r
-            output_file = file(filename, 'w')\r
-        except IOError:\r
-            self.AppendToOutput('Cannot save playlist. Wrong path or filename')\r
-            return\r
-        #create the output playlist, a simple XML document\r
-        implementation = xml.dom.minidom.getDOMImplementation()\r
-        #create the document DOM object and the root element\r
-        self.xml = implementation.createDocument(None, 'playlist', None)\r
-        root = self.xml.documentElement\r
-\r
-        #save generics variables\r
-        playlist_generics = self.xml.createElement('generics')\r
-        root.appendChild(playlist_generics)\r
-        self.generics_dict['pointer'] = self.index\r
-        for key in self.generics_dict.keys():\r
-        #for key in generics.keys():\r
-            self.xml.createAttribute(key)\r
-            playlist_generics.setAttribute(key, str(self.generics_dict[key]))\r
-\r
-        #save files and their attributes\r
-        for item in self.files:\r
-            #playlist_element=newdoc.createElement("file")\r
-            playlist_element = self.xml.createElement('element')\r
-            root.appendChild(playlist_element)\r
-            for key in item.__dict__:\r
-                if not (key in self.hidden_attributes):\r
-                    self.xml.createAttribute(key)\r
-                    playlist_element.setAttribute(key, str(item.__dict__[key]))\r
-        self._saved = False\r
-\r
-        self.xml.writexml(output_file, indent='\n')\r
-        output_file.close()\r
-        self._saved = True\r
+#!/usr/bin/env python
+
+'''
+playlist.py
+
+Playlist class for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import os.path
+import xml.dom.minidom
+
+import lib.libhooke
+import lib.file
+
+class Playlist(object):
+
+    def __init__(self, filename=None):
+        self._saved = False
+        self.count = 0
+        self.figure = None
+        self.files = []
+        self.generics_dict = {}
+        self.hidden_attributes = ['curve', 'data', 'driver', 'fits', 'name', 'plot', 'plots']
+        self.index = -1
+        self.name = None
+        self.path = None
+        self.plot_panel = None
+        self.plot_tab = None
+        if filename is None:
+            self.filename = None
+        else:
+            self.load(filename)
+
+    def add_file(self, filename):
+        if os.path.isfile(filename):
+            file_to_add = lib.file.File(filename)
+            self.files.append(file_to_add)
+            self._saved = False
+        self.count = len(self.files)
+
+    def delete_file(self, name):
+        for index, item in enumerate(self.files):
+            if item.name == name:
+                del self.files[index]
+                self.index = index
+        self.count = len(self.files)
+        if self.index > self.count - 1:
+            self.index = 0
+
+    def filter_curves(self, curves_to_keep=[]):
+        playlist = Playlist()
+        for curve_index in curves_to_keep:
+            playlist.files.append(self.files[curve_index])
+        playlist.count = len(playlist.files)
+        playlist.index = 0
+        return playlist
+
+    def get_active_file(self):
+        return self.files[self.index]
+
+    #def get_active_plot(self):
+        ##TODO: is this the only active (or default?) plot?
+        #return self.files[self.index].plots[0]
+
+    def load(self, filename):
+        '''
+        Loads a playlist file
+        '''
+        self.filename = filename
+        self.path, self.name = os.path.split(filename)
+        playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename)
+        self.xml = xml.dom.minidom.parseString(playlist_file)
+        #TODO: rename 'element' to 'curve' or something in hkp file
+        #TODO: rename 'path' to 'filename'
+        #TODO: switch from attributes to nodes, it's cleaner XML in my eyes
+
+        element_list = self.xml.getElementsByTagName('element')
+        #populate playlist with files
+        for index, element in enumerate(element_list):
+            #rebuild a data structure from the xml attributes
+            #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path'
+            if element.hasAttribute('path'):
+                #path, name = os.path.split(element.getAttribute('path'))
+                #path = path.split(os.sep)
+                #filename = lib.libhooke.get_file_path(name, path)
+                filename = element.getAttribute('path')
+            if element.hasAttribute('filename'):
+                #path, name = os.path.split(element.getAttribute('filename'))
+                #path = path.split(os.sep)
+                #filename = lib.libhooke.get_file_path(name, path)
+                filename = element.getAttribute('filename')
+            if os.path.isfile(filename):
+                data_file = lib.file.File(filename)
+                if element.hasAttribute('note'):
+                    data_file.note = element.getAttribute('note')
+                self.files.append(data_file)
+        self.count = len(self.files)
+        if self.count > 0:
+            #populate generics
+            genericsDict = {}
+            generics_list = self.xml.getElementsByTagName('generics')
+            if generics_list:
+                for attribute in generics_list[0].attributes.keys():
+                    genericsDict[attribute] = generics_list[0].getAttribute(attribute)
+            if genericsDict.has_key('pointer'):
+                index = int(genericsDict['pointer'])
+                if index >= 0 and index < self.count:
+                    self.index = index
+                else:
+                    index = 0
+            self._saved = True
+
+    def next(self):
+        self.index += 1
+        if self.index > self.count - 1:
+            self.index = 0
+
+    def previous(self):
+        self.index -= 1
+        if self.index < 0:
+            self.index = self.count - 1
+
+    def reset(self):
+        if self.count > 0:
+            self.index = 0
+        else:
+            self.index = -1
+
+    def save(self, filename):
+        '''
+        Saves a playlist from a list of files.
+        A playlist is an XML document with the following syntax:
+        <playlist>
+        <element path="/my/file/path/"/ attribute="attribute">
+        <element path="...">
+        </playlist>
+        '''
+        try:
+            output_file = file(filename, 'w')
+        except IOError:
+            self.AppendToOutput('Cannot save playlist. Wrong path or filename')
+            return
+        #create the output playlist, a simple XML document
+        implementation = xml.dom.minidom.getDOMImplementation()
+        #create the document DOM object and the root element
+        self.xml = implementation.createDocument(None, 'playlist', None)
+        root = self.xml.documentElement
+
+        #save generics variables
+        playlist_generics = self.xml.createElement('generics')
+        root.appendChild(playlist_generics)
+        self.generics_dict['pointer'] = self.index
+        for key in self.generics_dict.keys():
+        #for key in generics.keys():
+            self.xml.createAttribute(key)
+            playlist_generics.setAttribute(key, str(self.generics_dict[key]))
+
+        #save files and their attributes
+        for item in self.files:
+            #playlist_element=newdoc.createElement("file")
+            playlist_element = self.xml.createElement('element')
+            root.appendChild(playlist_element)
+            for key in item.__dict__:
+                if not (key in self.hidden_attributes):
+                    self.xml.createAttribute(key)
+                    playlist_element.setAttribute(key, str(item.__dict__[key]))
+        self._saved = False
+
+        self.xml.writexml(output_file, indent='\n')
+        output_file.close()
+        self._saved = True
index ea256a7826e092e7da5e8fd26553c70506d73d72..ae17f2ecbcedaf8471cf91dbbe5b52ad31ccdde8 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 c8999ea352aa914cd1908fe2845bc9a3f7231a03..ef17dcef8fa362ba52a62a3028fe82bb716b1635 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 ca458bd474c03747df079e11b1ad6791b3286d69..01f859d913b501d69df886c0f1dcce9eb0e71dfb 100644 (file)
-#!/usr/bin/env python\r
-\r
-'''\r
-results.py\r
-\r
-Result and Results classes for Hooke.\r
-\r
-Copyright 2009 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 numpy import nan\r
-\r
-import prettyformat\r
-import lib.curve\r
-\r
-DEFAULT_COLOR = 'green'\r
-DEFAULT_DECIMAL = 2\r
-DEFAULT_STYLE = 'scatter'\r
-\r
-class Result(lib.curve.Curve):\r
-    def __init__(self):\r
-        lib.curve.Curve.__init__(self)\r
-        self.color = DEFAULT_COLOR\r
-        self.result = {}\r
-        self.style = DEFAULT_STYLE\r
-\r
-class Results(object):\r
-    def __init__(self):\r
-        self.columns = []\r
-        self.decimals = {}\r
-        self.has_multipliers = False\r
-        self.multipliers = {}\r
-        self.results = []\r
-        self.separator='\t'\r
-        self.units = {}\r
-\r
-    def get_pretty_value(self, column, value):\r
-        if self.has_multipliers and self.has_results():\r
-            multiplier = self.multipliers[column]\r
-            decimals = self.decimals[column]\r
-            return prettyformat.pretty_format(value, '', decimals, multiplier, True)\r
-        return str(value)\r
-\r
-    def has_results(self):\r
-        return len(self.results) > 0\r
-\r
-    def get_header_as_list(self):\r
-        header_list = []\r
-        if self.has_results():\r
-            if not self.has_multipliers:\r
-                self.set_multipliers()\r
-            for column in self.columns:\r
-                unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])\r
-                header_str = ''.join([column, ' [', unit_str, ']'])\r
-                header_list.append(header_str)\r
-        return header_list\r
-\r
-    def get_header_as_str(self, separator=None):\r
-        if separator is None:\r
-            separator = self.separator\r
-        return separator.join(map(str, self.get_header_as_list()))\r
-\r
-    def get_result_as_list(self, index=0):\r
-        if index >= 0 and index < len(self.results):\r
-            result_list = []\r
-            if self.has_results():\r
-                if not self.has_multipliers:\r
-                    self.set_multipliers()\r
-                for column in self.columns:\r
-                    result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True)\r
-                    result_list.append(result_str)\r
-            return result_list\r
-        else:\r
-            return None\r
-\r
-    def get_result_as_string(self, index=0):\r
-        results_list = self.get_result_as_list(index)\r
-        if results_list is not None:\r
-            return self.separator.join(map(str, results_list))\r
-        else:\r
-            return ''\r
-\r
-    def set_decimal(self, column, decimal=DEFAULT_DECIMAL):\r
-        if self.decimals.has_key(column):\r
-            self.decimals[column] = decimal\r
-\r
-    def set_decimals(self, decimals=DEFAULT_DECIMAL):\r
-        if decimals < 0:\r
-            #set default value if necessary\r
-            decimals = DEFAULT_DECIMAL\r
-        for column in self.columns:\r
-            self.decimals[column] = decimals\r
-\r
-    def set_multipliers(self, index=0):\r
-        if self.has_results():\r
-            for column in self.columns:\r
-                #result will contain the results dictionary at 'index'\r
-                result = self.results[index].result\r
-                #in position 0 of the result we find the value\r
-                self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
-            self.has_multipliers = True\r
-        else:\r
-            self.has_multipliers = False\r
-\r
-    def update(self):\r
-        pass\r
-\r
-\r
-class ResultsFJC(Results):\r
-    def __init__(self):\r
-        Results.__init__(self)\r
-        self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate']\r
-        self.units['Contour length'] = 'm'\r
-        self.units['sigma contour length'] = 'm'\r
-        self.units['Kuhn length'] = 'm'\r
-        self.units['sigma Kuhn length'] = 'm'\r
-        self.units['Rupture force'] = 'N'\r
-        self.units['Slope'] = 'N/m'\r
-        self.units['Loading rate'] = 'N/s'\r
-        self.set_decimals(2)\r
-\r
-    def set_multipliers(self, index=0):\r
-        if self.has_results():\r
-            for column in self.columns:\r
-                #result will contain the results dictionary at 'index'\r
-                result = self.results[index].result\r
-                #in position 0 of the result we find the value\r
-                if column == 'sigma contour length':\r
-                    self.multipliers[column] = self.multipliers['Contour length']\r
-                elif column == 'sigma Kuhn length':\r
-                    self.multipliers[column] = self.multipliers['Kuhn length']\r
-                else:\r
-                    self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
-            self.has_multipliers = True\r
-        else:\r
-            self.has_multipliers = False\r
-\r
-class ResultsMultiDistance(Results):\r
-    def __init__(self):\r
-        Results.__init__(self)\r
-        self.columns = ['Distance']\r
-        self.units['Distance'] = 'm'\r
-        self.set_decimals(2)\r
-\r
-    def update(self):\r
-        if (self.results) > 0:\r
-            for result in self.results:\r
-                if result.visible:\r
-                    reference_peak = result.x\r
-                    break\r
-\r
-            for result in self.results:\r
-                if result.visible:\r
-                    result.result['Distance'] = reference_peak - result.x\r
-                    reference_peak = result.x\r
-                else:\r
-                    result.result['Distance'] = nan\r
-\r
-\r
-class ResultsWLC(Results):\r
-    def __init__(self):\r
-        Results.__init__(self)\r
-        self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Slope', 'Loading rate']\r
-        self.units['Contour length'] = 'm'\r
-        self.units['sigma contour length'] = 'm'\r
-        self.units['Persistence length'] = 'm'\r
-        self.units['sigma persistence length'] = 'm'\r
-        self.units['Rupture force'] = 'N'\r
-        self.units['Slope'] = 'N/m'\r
-        self.units['Loading rate'] = 'N/s'\r
-        self.set_decimals(2)\r
-\r
-    def set_multipliers(self, index=0):\r
-        if self.has_results():\r
-            for column in self.columns:\r
-                #result will contain the results dictionary at 'index'\r
-                result = self.results[index].result\r
-                #in position 0 of the result we find the value\r
-                if column == 'sigma contour length':\r
-                    self.multipliers[column] = self.multipliers['Contour length']\r
-                elif column == 'sigma persistence length':\r
-                    self.multipliers[column] = self.multipliers['Persistence length']\r
-                else:\r
-                    self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
-            self.has_multipliers = True\r
-        else:\r
-            self.has_multipliers = False\r
+#!/usr/bin/env python
+
+'''
+results.py
+
+Result and Results classes for Hooke.
+
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from numpy import nan
+
+import prettyformat
+import lib.curve
+
+DEFAULT_COLOR = 'green'
+DEFAULT_DECIMAL = 2
+DEFAULT_STYLE = 'scatter'
+
+class Result(lib.curve.Curve):
+    def __init__(self):
+        lib.curve.Curve.__init__(self)
+        self.color = DEFAULT_COLOR
+        self.result = {}
+        self.style = DEFAULT_STYLE
+
+class Results(object):
+    def __init__(self):
+        self.columns = []
+        self.decimals = {}
+        self.has_multipliers = False
+        self.multipliers = {}
+        self.results = []
+        self.separator='\t'
+        self.units = {}
+
+    def get_pretty_value(self, column, value):
+        if self.has_multipliers and self.has_results():
+            multiplier = self.multipliers[column]
+            decimals = self.decimals[column]
+            return prettyformat.pretty_format(value, '', decimals, multiplier, True)
+        return str(value)
+
+    def has_results(self):
+        return len(self.results) > 0
+
+    def get_header_as_list(self):
+        header_list = []
+        if self.has_results():
+            if not self.has_multipliers:
+                self.set_multipliers()
+            for column in self.columns:
+                unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])
+                header_str = ''.join([column, ' [', unit_str, ']'])
+                header_list.append(header_str)
+        return header_list
+
+    def get_header_as_str(self, separator=None):
+        if separator is None:
+            separator = self.separator
+        return separator.join(map(str, self.get_header_as_list()))
+
+    def get_result_as_list(self, index=0):
+        if index >= 0 and index < len(self.results):
+            result_list = []
+            if self.has_results():
+                if not self.has_multipliers:
+                    self.set_multipliers()
+                for column in self.columns:
+                    result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True)
+                    result_list.append(result_str)
+            return result_list
+        else:
+            return None
+
+    def get_result_as_string(self, index=0):
+        results_list = self.get_result_as_list(index)
+        if results_list is not None:
+            return self.separator.join(map(str, results_list))
+        else:
+            return ''
+
+    def set_decimal(self, column, decimal=DEFAULT_DECIMAL):
+        if self.decimals.has_key(column):
+            self.decimals[column] = decimal
+
+    def set_decimals(self, decimals=DEFAULT_DECIMAL):
+        if decimals < 0:
+            #set default value if necessary
+            decimals = DEFAULT_DECIMAL
+        for column in self.columns:
+            self.decimals[column] = decimals
+
+    def set_multipliers(self, index=0):
+        if self.has_results():
+            for column in self.columns:
+                #result will contain the results dictionary at 'index'
+                result = self.results[index].result
+                #in position 0 of the result we find the value
+                self.multipliers[column] = prettyformat.get_multiplier(result[column])
+            self.has_multipliers = True
+        else:
+            self.has_multipliers = False
+
+    def update(self):
+        pass
+
+
+class ResultsFJC(Results):
+    def __init__(self):
+        Results.__init__(self)
+        self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate']
+        self.units['Contour length'] = 'm'
+        self.units['sigma contour length'] = 'm'
+        self.units['Kuhn length'] = 'm'
+        self.units['sigma Kuhn length'] = 'm'
+        self.units['Rupture force'] = 'N'
+        self.units['Slope'] = 'N/m'
+        self.units['Loading rate'] = 'N/s'
+        self.set_decimals(2)
+
+    def set_multipliers(self, index=0):
+        if self.has_results():
+            for column in self.columns:
+                #result will contain the results dictionary at 'index'
+                result = self.results[index].result
+                #in position 0 of the result we find the value
+                if column == 'sigma contour length':
+                    self.multipliers[column] = self.multipliers['Contour length']
+                elif column == 'sigma Kuhn length':
+                    self.multipliers[column] = self.multipliers['Kuhn length']
+                else:
+                    self.multipliers[column] = prettyformat.get_multiplier(result[column])
+            self.has_multipliers = True
+        else:
+            self.has_multipliers = False
+
+class ResultsMultiDistance(Results):
+    def __init__(self):
+        Results.__init__(self)
+        self.columns = ['Distance']
+        self.units['Distance'] = 'm'
+        self.set_decimals(2)
+
+    def update(self):
+        if (self.results) > 0:
+            for result in self.results:
+                if result.visible:
+                    reference_peak = result.x
+                    break
+
+            for result in self.results:
+                if result.visible:
+                    result.result['Distance'] = reference_peak - result.x
+                    reference_peak = result.x
+                else:
+                    result.result['Distance'] = nan
+
+
+class ResultsWLC(Results):
+    def __init__(self):
+        Results.__init__(self)
+        self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Slope', 'Loading rate']
+        self.units['Contour length'] = 'm'
+        self.units['sigma contour length'] = 'm'
+        self.units['Persistence length'] = 'm'
+        self.units['sigma persistence length'] = 'm'
+        self.units['Rupture force'] = 'N'
+        self.units['Slope'] = 'N/m'
+        self.units['Loading rate'] = 'N/s'
+        self.set_decimals(2)
+
+    def set_multipliers(self, index=0):
+        if self.has_results():
+            for column in self.columns:
+                #result will contain the results dictionary at 'index'
+                result = self.results[index].result
+                #in position 0 of the result we find the value
+                if column == 'sigma contour length':
+                    self.multipliers[column] = self.multipliers['Contour length']
+                elif column == 'sigma persistence length':
+                    self.multipliers[column] = self.multipliers['Persistence length']
+                else:
+                    self.multipliers[column] = prettyformat.get_multiplier(result[column])
+            self.has_multipliers = True
+        else:
+            self.has_multipliers = False
index 17de9662d4f75f3b6b9e96603869eafccaba5bd1..758a4121f22312372088027b25943557f273e913 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/>.
 
 """Status bar for Hooke.
 """
index 01e5db88b160b12b260058c7e5e5f8581105c8ee..ffef0a818c892a99ae823c8126a99d834f95ac77 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 ca60ce0ef003dbaeac9e98b18f043602ae04feab..e75a37138b8a5d0391bf01e10884cf691d7ea5fe 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 ec7895c6569c48fa63148ec0a4b3dd1336231d8b..3a8f92789f59ea0bfdd2c1bb15f56efb21abcec9 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 the `@callback` decorator.
 
index e2e592dd92cf584b546f57e9a1f8625632bba063..b41b0243c54fa28912abbfb79032ed26d13b12ff 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:`caller_name`.
 
index 41c98a4b4afba3263ad28815c9adae6909fec691..acbd7a6c00e8908921047278a4b40407f93fd552 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 04fab7d71f2712792d012c697550bc7c241a17cc..4b13b31fa4047de7fe8068ace98d53ba9b283a19 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 da120d730115db98787ae93caae45d7e2ee475ee..8b519e45737531aa7f3ce29f694ab9b423bdcf22 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 1556e3759ea004302e77d7350c4469f62751c935..d374c2a540a7548558c711e9d12ab4b319edf859 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 137763baaf21bfa0250aa308d7eccbce9e815b11..ef168910c6c3eb4ce542e0c7262cb9bd13aebdb0 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 ad2d85b89ed37f26a3a8629ee38e4c603f5bdab3..1379c82c4ed5f6d05d950743d127a08656d3bd99 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 ebb3c1927fa1983efde21bedbc2006aa8c62e797..6108b9fd1efb1f1f0270f6ade2fc9c48a548949d 100644 (file)
-# Copyright\r
-\r
-"""Define functions for handling numbers in SI notation.\r
-\r
-Notes\r
------\r
-To output a scale, choose any value on the axis and find the\r
-multiplier and prefix for it.  Use those to format the rest of the\r
-scale.  As values can span several orders of magnitude, you have\r
-to decide what units to use.\r
-\r
->>> xs = (985e-12, 1e-9, 112358e-12)\r
-\r
-Get the power from the first (or last, or middle, ...) value\r
-\r
->>> p = get_power(xs[0])\r
->>> for x in xs:\r
-...     print ppSI(x, decimals=2, power=p)\r
-985.00 p\r
-1000.00 p\r
-112358.00 p\r
->>> print prefix_from_value(xs[0]) + 'N'\r
-pN\r
-"""\r
-\r
-import math\r
-from numpy import isnan\r
-import re\r
-\r
-\r
-PREFIX = {\r
-    24: 'Y',\r
-    21: 'Z',\r
-    18: 'E',\r
-    15: 'P',\r
-    12: 'T',\r
-    9: 'G',\r
-    6: 'M',\r
-    3: 'k',\r
-    0: '',\r
-    -3: 'm',\r
-    -6: u'\u00B5',\r
-    -9: 'n',\r
-    -12: 'p',\r
-    -15: 'f',\r
-    -18: 'a',\r
-    -21: 'z',\r
-    -24: 'y',\r
-    }\r
-"""A dictionary of SI prefixes from 10**24 to 10**-24.\r
-\r
-Examples\r
---------\r
->>> PREFIX[0]\r
-''\r
->>> PREFIX[6]\r
-'M'\r
->>> PREFIX[-9]\r
-'n'\r
-"""\r
-\r
-_DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')\r
-"""Used by :func:`data_label_unit`.\r
-"""\r
-\r
-\r
-def ppSI(value, unit='', decimals=None, power=None, pad=False):\r
-    """Pretty-print `value` in SI notation.\r
-\r
-    The current implementation ignores `pad` if `decimals` is `None`.\r
-\r
-    Examples\r
-    --------\r
-    >>> x = math.pi * 1e-8\r
-    >>> print ppSI(x, 'N')\r
-    31.415927 nN\r
-    >>> print ppSI(x, 'N', 3)\r
-    31.416 nN\r
-    >>> print ppSI(x, 'N', 4, power=-12)\r
-    31415.9265 pN\r
-    >>> print ppSI(x, 'N', 5, pad=True)\r
-       31.41593 nN\r
-\r
-    If you want the decimal indented by six spaces with `decimal=2`,\r
-    `pad` should be the sum of\r
-\r
-    * 6 (places before the decimal point)\r
-    * 1 (length of the decimal point)\r
-    * 2 (places after the decimal point)\r
-\r
-    >>> print ppSI(-x, 'N', 2, pad=(6+1+2))\r
-       -31.42 nN\r
-    """\r
-    if value == 0:\r
-        return '0'\r
-    if isnan(value):\r
-        return 'NaN'\r
-\r
-    if power == None:  # auto-detect power\r
-        power = get_power(value)\r
-\r
-    if decimals == None:\r
-        format = lambda n: '%f' % n\r
-    else:\r
-        if pad == False:  # no padding\r
-            format = lambda n: '%.*f' % (decimals, n)            \r
-        else:\r
-            if pad == True:  # auto-generate pad\r
-                # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.\r
-                pad = 6 + decimals\r
-            format = lambda n: '%*.*f' % (pad, decimals, n)\r
-    return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)\r
-\r
-\r
-def get_power(value):\r
-    """Return the SI power for which `0 <= |value|/10**pow < 1000`. \r
-    \r
-    Exampes\r
-    -------\r
-    >>> get_power(0)\r
-    0\r
-    >>> get_power(123)\r
-    0\r
-    >>> get_power(-123)\r
-    0\r
-    >>> get_power(1e8)\r
-    6\r
-    >>> get_power(1e-16)\r
-    -18\r
-    """\r
-    if value != 0 and not isnan(value):\r
-        # get log10(|value|)\r
-        value_temp = math.floor(math.log10(math.fabs(value)))\r
-        # reduce the log10 to a multiple of 3\r
-        return int(value_temp - (value_temp % 3))\r
-    else:\r
-        return 0\r
-\r
-def prefix_from_value(value):\r
-    """Determine the SI power of `value` and return its prefix.\r
-\r
-    Examples\r
-    --------\r
-    >>> prefix_from_value(0)\r
-    ''\r
-    >>> prefix_from_value(1e10)\r
-    'G'\r
-    """\r
-    return PREFIX[get_power(value)]\r
-\r
-def split_data_label(label):\r
-    """Split `curve.data[i].info['name']` labels into `(name, unit)`.\r
-\r
-    Examples\r
-    --------\r
-    >>> split_data_label('z piezo (m)')\r
-    ('z piezo', 'm')\r
-    >>> split_data_label('deflection (N)')\r
-    ('deflection', 'N')\r
-    """\r
-    m = _DATA_LABEL_REGEXP.match(label)\r
-    assert m != None, label\r
-    return m.groups()\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/>.
+
+"""Define functions for handling numbers in SI notation.
+
+Notes
+-----
+To output a scale, choose any value on the axis and find the
+multiplier and prefix for it.  Use those to format the rest of the
+scale.  As values can span several orders of magnitude, you have
+to decide what units to use.
+
+>>> xs = (985e-12, 1e-9, 112358e-12)
+
+Get the power from the first (or last, or middle, ...) value
+
+>>> p = get_power(xs[0])
+>>> for x in xs:
+...     print ppSI(x, decimals=2, power=p)
+985.00 p
+1000.00 p
+112358.00 p
+>>> print prefix_from_value(xs[0]) + 'N'
+pN
+"""
+
+import math
+from numpy import isnan
+import re
+
+
+PREFIX = {
+    24: 'Y',
+    21: 'Z',
+    18: 'E',
+    15: 'P',
+    12: 'T',
+    9: 'G',
+    6: 'M',
+    3: 'k',
+    0: '',
+    -3: 'm',
+    -6: u'\u00B5',
+    -9: 'n',
+    -12: 'p',
+    -15: 'f',
+    -18: 'a',
+    -21: 'z',
+    -24: 'y',
+    }
+"""A dictionary of SI prefixes from 10**24 to 10**-24.
+
+Examples
+--------
+>>> PREFIX[0]
+''
+>>> PREFIX[6]
+'M'
+>>> PREFIX[-9]
+'n'
+"""
+
+_DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')
+"""Used by :func:`data_label_unit`.
+"""
+
+
+def ppSI(value, unit='', decimals=None, power=None, pad=False):
+    """Pretty-print `value` in SI notation.
+
+    The current implementation ignores `pad` if `decimals` is `None`.
+
+    Examples
+    --------
+    >>> x = math.pi * 1e-8
+    >>> print ppSI(x, 'N')
+    31.415927 nN
+    >>> print ppSI(x, 'N', 3)
+    31.416 nN
+    >>> print ppSI(x, 'N', 4, power=-12)
+    31415.9265 pN
+    >>> print ppSI(x, 'N', 5, pad=True)
+       31.41593 nN
+
+    If you want the decimal indented by six spaces with `decimal=2`,
+    `pad` should be the sum of
+
+    * 6 (places before the decimal point)
+    * 1 (length of the decimal point)
+    * 2 (places after the decimal point)
+
+    >>> print ppSI(-x, 'N', 2, pad=(6+1+2))
+       -31.42 nN
+    """
+    if value == 0:
+        return '0'
+    if isnan(value):
+        return 'NaN'
+
+    if power == None:  # auto-detect power
+        power = get_power(value)
+
+    if decimals == None:
+        format = lambda n: '%f' % n
+    else:
+        if pad == False:  # no padding
+            format = lambda n: '%.*f' % (decimals, n)            
+        else:
+            if pad == True:  # auto-generate pad
+                # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.
+                pad = 6 + decimals
+            format = lambda n: '%*.*f' % (pad, decimals, n)
+    return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)
+
+
+def get_power(value):
+    """Return the SI power for which `0 <= |value|/10**pow < 1000`. 
+    
+    Exampes
+    -------
+    >>> get_power(0)
+    0
+    >>> get_power(123)
+    0
+    >>> get_power(-123)
+    0
+    >>> get_power(1e8)
+    6
+    >>> get_power(1e-16)
+    -18
+    """
+    if value != 0 and not isnan(value):
+        # get log10(|value|)
+        value_temp = math.floor(math.log10(math.fabs(value)))
+        # reduce the log10 to a multiple of 3
+        return int(value_temp - (value_temp % 3))
+    else:
+        return 0
+
+def prefix_from_value(value):
+    """Determine the SI power of `value` and return its prefix.
+
+    Examples
+    --------
+    >>> prefix_from_value(0)
+    ''
+    >>> prefix_from_value(1e10)
+    'G'
+    """
+    return PREFIX[get_power(value)]
+
+def split_data_label(label):
+    """Split `curve.data[i].info['name']` labels into `(name, unit)`.
+
+    Examples
+    --------
+    >>> split_data_label('z piezo (m)')
+    ('z piezo', 'm')
+    >>> split_data_label('deflection (N)')
+    ('deflection', 'N')
+    """
+    m = _DATA_LABEL_REGEXP.match(label)
+    assert m != None, label
+    return m.groups()
index 49b0a210d266f0d10f16aa8e69254e0a4db7d94d..3f20ec99feaf9d50ff4c8b90667b2d4446bce9d6 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/>.
 
 
 class Closing (object):
index 6e1d3d0079016582a7a65bc051b131428e730a93..47d6a33f445e082e45fce508bf5ecb92d2c407c7 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -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 9fc0fd665d54f929b52cf6944e3e7318dfce8a1a..b1a0962e943dafed577e1a62aebbbbb44bec4d56 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 21b39af6f21be61f5e3ab09bb720c33209b7c423..9ab0ce60f947248facd54a27815c175bc732b8fd 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 c8cc84eeaa6e0ff1a53c8be6c6751c277974dce8..d16c9b5e08ba9d82a4033ecd840d2d0ce2baea50 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 d43d2c3bcb481a6b7bd772ee2f1c6135da27ae87..374af169193ac62283f185bd21dc7e9e0af166d1 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 d35934afd08a8341ee096540c012a2e3726f86be..2515273c5efc9e4f0750bcc48ecb5499ff6a5c09 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 18d829510752a78792da5ab0267742ba7180f6ad..1bce4f3f24ed24a696c7d10976bb0b0ff3363da8 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 06b8adba3b3cdb2b1eba446e2e88264a0dd60735..ba9a3b87bb50537edcfc44f2a4e026e336f823d4 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 a40ade7e82e4fdb8a6d9eaed369014f90a23fa55..ffe823c1cd64b7e2cf9489ce8403cf8efcfee943 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 39ed7b0498a891b997fc9a637c5cb87bc5a92794..293b80c5a1ea337b6d4b8c2e596237a854bc5af8 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 bf2af11070959ce9457f43dad916b6ba5b36622f..0953a3a3232ac7506304ead1ac3c19e26283d244 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 631adeef6f5bc0719f7c35627311405da41f60d2..a2dd25204578dab620923154a7a71cfddc5981c0 100755 (executable)
@@ -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