From 565f9d7b69d2e4a9ea447d7a50f8f835c3e08642 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 2 Aug 2010 20:10:15 -0400 Subject: [PATCH] Ran update_copyright.py --- conf/hooke configspec.ini | 80 +- conf/hooke.ini | 102 +- contrib/mfp_igor_scripts/FMjoin.py | 16 +- contrib/mfp_igor_scripts/h5export.py | 16 +- doc/generate-hooke-txt.py | 16 +- hooke/__init__.py | 16 +- hooke/command.py | 16 +- hooke/compat/__init__.py | 16 +- hooke/compat/minidom.py | 18 +- hooke/config.py | 16 +- hooke/curve.py | 16 +- hooke/driver/__init__.py | 16 +- hooke/driver/csvdriver.py | 19 +- hooke/driver/hdf5.py | 16 +- hooke/driver/hemingway.py | 16 +- hooke/driver/igorbinarywave.py | 16 +- hooke/driver/jpk.py | 16 +- hooke/driver/mcs.py | 20 +- hooke/driver/mfp1dexport.py | 16 +- hooke/driver/mfp3d.py | 16 +- hooke/driver/picoforce.py | 16 +- hooke/driver/tutorial.py | 16 +- hooke/driver/wtk.py | 16 +- hooke/engine.py | 16 +- hooke/experiment.py | 16 +- hooke/hooke.py | 16 +- hooke/interaction.py | 16 +- hooke/playlist.py | 16 +- hooke/plugin/__init__.py | 16 +- hooke/plugin/autopeak.py | 16 +- hooke/plugin/config.py | 16 +- hooke/plugin/convfilt.py | 16 +- hooke/plugin/curve.py | 16 +- hooke/plugin/cut.py | 16 +- hooke/plugin/debug.py | 16 +- hooke/plugin/fclamp.py | 16 +- hooke/plugin/fit.py | 16 +- hooke/plugin/flatfilt.py | 16 +- hooke/plugin/flatfilts-rolf.py | 16 +- hooke/plugin/jumpstat.py | 16 +- hooke/plugin/license.py | 16 +- hooke/plugin/macro.py | 16 +- hooke/plugin/massanalysis.py | 16 +- hooke/plugin/multidistance.py | 16 +- hooke/plugin/multifit.py | 16 +- hooke/plugin/note.py | 16 +- hooke/plugin/pcluster.py | 16 +- hooke/plugin/playlist.py | 16 +- hooke/plugin/playlists.py | 16 +- hooke/plugin/review.py | 16 +- hooke/plugin/showconvoluted.py | 16 +- hooke/plugin/superimpose.py | 16 +- hooke/plugin/system.py | 16 +- hooke/plugin/tccd.py | 16 +- hooke/plugin/tutorial.py | 16 +- hooke/plugin/vclamp.py | 16 +- hooke/ui/__init__.py | 16 +- hooke/ui/commandline.py | 16 +- hooke/ui/gui/__init__.py | 2067 +++++++++-------- hooke/ui/gui/clickedpoint.py | 76 +- hooke/ui/gui/dialog/__init__.py | 24 +- hooke/ui/gui/dialog/points.py | 18 +- hooke/ui/gui/dialog/save_file.py | 18 +- hooke/ui/gui/dialog/selection.py | 207 +- hooke/ui/gui/dialog/text.py | 16 +- hooke/ui/gui/handler/__init__.py | 94 +- hooke/ui/gui/handler/boolean.py | 18 +- hooke/ui/gui/handler/selection.py | 18 +- hooke/ui/gui/handler/string.py | 18 +- hooke/ui/gui/menu.py | 18 +- hooke/ui/gui/navbar.py | 18 +- hooke/ui/gui/panel/__init__.py | 111 +- hooke/ui/gui/panel/commands.py | 382 +-- hooke/ui/gui/panel/note.py | 93 +- hooke/ui/gui/panel/notebook.py | 18 +- hooke/ui/gui/panel/output.py | 18 +- hooke/ui/gui/panel/playlist.py | 711 +++--- hooke/ui/gui/panel/plot.py | 558 ++--- hooke/ui/gui/panel/propertyeditor-propgrid.py | 1017 ++++---- hooke/ui/gui/panel/propertyeditor.py | 571 ++--- hooke/ui/gui/panel/results.py | 172 +- hooke/ui/gui/panel/selection.py | 211 +- hooke/ui/gui/panel/welcome.py | 18 +- hooke/ui/gui/playlist.py | 350 +-- hooke/ui/gui/plotcommands.py | 16 +- hooke/ui/gui/point_request.py | 16 +- hooke/ui/gui/results.py | 378 +-- hooke/ui/gui/statusbar.py | 18 +- hooke/util/__init__.py | 16 +- hooke/util/calculus.py | 16 +- hooke/util/callback.py | 18 +- hooke/util/caller.py | 18 +- hooke/util/encoding.py | 16 +- hooke/util/fft.py | 16 +- hooke/util/fit.py | 16 +- hooke/util/graph.py | 16 +- hooke/util/peak.py | 16 +- hooke/util/pluggable.py | 16 +- hooke/util/si.py | 344 +-- hooke/util/util.py | 18 +- setup.py | 16 +- test/__init__.py | 16 +- test/curve_info.py | 16 +- test/flat_filter.py | 16 +- test/hemingway_driver.py | 16 +- test/jpk_driver.py | 16 +- test/load_playlist.py | 16 +- test/mfp3d_driver.py | 16 +- test/picoforce_driver.py | 16 +- test/unicode_output.py | 16 +- test/wtk_driver.py | 16 +- update_copyright.py | 16 +- 112 files changed, 4786 insertions(+), 4287 deletions(-) diff --git a/conf/hooke configspec.ini b/conf/hooke configspec.ini index c5691de..add94b8 100644 --- a/conf/hooke configspec.ini +++ b/conf/hooke configspec.ini @@ -1,40 +1,40 @@ -[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) +[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) diff --git a/conf/hooke.ini b/conf/hooke.ini index fe6a3f1..6276712 100644 --- a/conf/hooke.ini +++ b/conf/hooke.ini @@ -1,51 +1,51 @@ -#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 +#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 diff --git a/contrib/mfp_igor_scripts/FMjoin.py b/contrib/mfp_igor_scripts/FMjoin.py index de40aee..1630d05 100644 --- a/contrib/mfp_igor_scripts/FMjoin.py +++ b/contrib/mfp_igor_scripts/FMjoin.py @@ -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 diff --git a/contrib/mfp_igor_scripts/h5export.py b/contrib/mfp_igor_scripts/h5export.py index 4740be7..5759c05 100644 --- a/contrib/mfp_igor_scripts/h5export.py +++ b/contrib/mfp_igor_scripts/h5export.py @@ -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 diff --git a/doc/generate-hooke-txt.py b/doc/generate-hooke-txt.py index fb91500..346db39 100644 --- a/doc/generate-hooke-txt.py +++ b/doc/generate-hooke-txt.py @@ -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 diff --git a/hooke/__init__.py b/hooke/__init__.py index c8b1253..17c38ce 100644 --- a/hooke/__init__.py +++ b/hooke/__init__.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 diff --git a/hooke/command.py b/hooke/command.py index e4bb9c1..974e3b5 100644 --- a/hooke/command.py +++ b/hooke/command.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 diff --git a/hooke/compat/__init__.py b/hooke/compat/__init__.py index ad3d685..28c4691 100644 --- a/hooke/compat/__init__.py +++ b/hooke/compat/__init__.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 diff --git a/hooke/compat/minidom.py b/hooke/compat/minidom.py index b9ff343..d2025cf 100644 --- a/hooke/compat/minidom.py +++ b/hooke/compat/minidom.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Dynamically patch :mod:`xml.dom.minidom`'s attribute value escaping. diff --git a/hooke/config.py b/hooke/config.py index 97d645b..11ee1a8 100644 --- a/hooke/config.py +++ b/hooke/config.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 diff --git a/hooke/curve.py b/hooke/curve.py index db31318..7e3d084 100644 --- a/hooke/curve.py +++ b/hooke/curve.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 diff --git a/hooke/driver/__init__.py b/hooke/driver/__init__.py index e51fb22..0e860c1 100644 --- a/hooke/driver/__init__.py +++ b/hooke/driver/__init__.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 diff --git a/hooke/driver/csvdriver.py b/hooke/driver/csvdriver.py index 89ff5ff..3ec4cbe 100644 --- a/hooke/driver/csvdriver.py +++ b/hooke/driver/csvdriver.py @@ -1,4 +1,21 @@ -# Copyright +# Copyright (C) 2008-2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . """Simple driver to read general comma-separated values in Hooke diff --git a/hooke/driver/hdf5.py b/hooke/driver/hdf5.py index 4a05474..2614319 100644 --- a/hooke/driver/hdf5.py +++ b/hooke/driver/hdf5.py @@ -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 diff --git a/hooke/driver/hemingway.py b/hooke/driver/hemingway.py index 8cd5f5e..4f837c9 100644 --- a/hooke/driver/hemingway.py +++ b/hooke/driver/hemingway.py @@ -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 diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py index a25f33e..209bbc7 100644 --- a/hooke/driver/igorbinarywave.py +++ b/hooke/driver/igorbinarywave.py @@ -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 diff --git a/hooke/driver/jpk.py b/hooke/driver/jpk.py index e6964f6..2cbb852 100644 --- a/hooke/driver/jpk.py +++ b/hooke/driver/jpk.py @@ -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 diff --git a/hooke/driver/mcs.py b/hooke/driver/mcs.py index f8208c0..366e978 100644 --- a/hooke/driver/mcs.py +++ b/hooke/driver/mcs.py @@ -1,4 +1,22 @@ -# Copyright +# Copyright (C) 2009-2010 Allen Chen +# Massimo Sandal +# W. Trevor King +# +# 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 +# . """Driver for mcs fluorescence files. """ diff --git a/hooke/driver/mfp1dexport.py b/hooke/driver/mfp1dexport.py index 4c5363c..aefb5a5 100644 --- a/hooke/driver/mfp1dexport.py +++ b/hooke/driver/mfp1dexport.py @@ -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 diff --git a/hooke/driver/mfp3d.py b/hooke/driver/mfp3d.py index fce318c..c57643c 100644 --- a/hooke/driver/mfp3d.py +++ b/hooke/driver/mfp3d.py @@ -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 diff --git a/hooke/driver/picoforce.py b/hooke/driver/picoforce.py index 2954804..19f8e3d 100644 --- a/hooke/driver/picoforce.py +++ b/hooke/driver/picoforce.py @@ -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 diff --git a/hooke/driver/tutorial.py b/hooke/driver/tutorial.py index 42204d3..7dfb6e8 100644 --- a/hooke/driver/tutorial.py +++ b/hooke/driver/tutorial.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 diff --git a/hooke/driver/wtk.py b/hooke/driver/wtk.py index 8cef939..8935120 100644 --- a/hooke/driver/wtk.py +++ b/hooke/driver/wtk.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 diff --git a/hooke/engine.py b/hooke/engine.py index a973730..b91551c 100644 --- a/hooke/engine.py +++ b/hooke/engine.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 diff --git a/hooke/experiment.py b/hooke/experiment.py index 9f94488..81decf9 100644 --- a/hooke/experiment.py +++ b/hooke/experiment.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 diff --git a/hooke/hooke.py b/hooke/hooke.py index d1c3b9c..1923d22 100644 --- a/hooke/hooke.py +++ b/hooke/hooke.py @@ -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 diff --git a/hooke/interaction.py b/hooke/interaction.py index 97ac0b2..35492a7 100644 --- a/hooke/interaction.py +++ b/hooke/interaction.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 diff --git a/hooke/playlist.py b/hooke/playlist.py index bba2990..c57604f 100644 --- a/hooke/playlist.py +++ b/hooke/playlist.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 diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 80c2b52..f589e35 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.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 diff --git a/hooke/plugin/autopeak.py b/hooke/plugin/autopeak.py index ac76228..7488c3a 100644 --- a/hooke/plugin/autopeak.py +++ b/hooke/plugin/autopeak.py @@ -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 diff --git a/hooke/plugin/config.py b/hooke/plugin/config.py index 21fd56a..b0bcf3b 100644 --- a/hooke/plugin/config.py +++ b/hooke/plugin/config.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 diff --git a/hooke/plugin/convfilt.py b/hooke/plugin/convfilt.py index 3d1e883..22b26c7 100644 --- a/hooke/plugin/convfilt.py +++ b/hooke/plugin/convfilt.py @@ -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 diff --git a/hooke/plugin/curve.py b/hooke/plugin/curve.py index 56b4379..b517c0b 100644 --- a/hooke/plugin/curve.py +++ b/hooke/plugin/curve.py @@ -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 diff --git a/hooke/plugin/cut.py b/hooke/plugin/cut.py index 9eb112b..ad1a067 100644 --- a/hooke/plugin/cut.py +++ b/hooke/plugin/cut.py @@ -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 diff --git a/hooke/plugin/debug.py b/hooke/plugin/debug.py index a2e8812..1855450 100644 --- a/hooke/plugin/debug.py +++ b/hooke/plugin/debug.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 diff --git a/hooke/plugin/fclamp.py b/hooke/plugin/fclamp.py index 54aff5f..b97022e 100644 --- a/hooke/plugin/fclamp.py +++ b/hooke/plugin/fclamp.py @@ -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 diff --git a/hooke/plugin/fit.py b/hooke/plugin/fit.py index 7ea2726..c566a8b 100644 --- a/hooke/plugin/fit.py +++ b/hooke/plugin/fit.py @@ -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 diff --git a/hooke/plugin/flatfilt.py b/hooke/plugin/flatfilt.py index 716a4be..9bf6db5 100644 --- a/hooke/plugin/flatfilt.py +++ b/hooke/plugin/flatfilt.py @@ -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 diff --git a/hooke/plugin/flatfilts-rolf.py b/hooke/plugin/flatfilts-rolf.py index a469f8e..1c53052 100644 --- a/hooke/plugin/flatfilts-rolf.py +++ b/hooke/plugin/flatfilts-rolf.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 diff --git a/hooke/plugin/jumpstat.py b/hooke/plugin/jumpstat.py index a205a75..2e02b4b 100644 --- a/hooke/plugin/jumpstat.py +++ b/hooke/plugin/jumpstat.py @@ -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 diff --git a/hooke/plugin/license.py b/hooke/plugin/license.py index d405bf9..21fecdb 100644 --- a/hooke/plugin/license.py +++ b/hooke/plugin/license.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 diff --git a/hooke/plugin/macro.py b/hooke/plugin/macro.py index c7cd1e7..bfc15d5 100644 --- a/hooke/plugin/macro.py +++ b/hooke/plugin/macro.py @@ -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 diff --git a/hooke/plugin/massanalysis.py b/hooke/plugin/massanalysis.py index c5fcd86..c657af9 100644 --- a/hooke/plugin/massanalysis.py +++ b/hooke/plugin/massanalysis.py @@ -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 diff --git a/hooke/plugin/multidistance.py b/hooke/plugin/multidistance.py index 610043e..7c8291a 100644 --- a/hooke/plugin/multidistance.py +++ b/hooke/plugin/multidistance.py @@ -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 diff --git a/hooke/plugin/multifit.py b/hooke/plugin/multifit.py index 56befca..7392418 100644 --- a/hooke/plugin/multifit.py +++ b/hooke/plugin/multifit.py @@ -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 diff --git a/hooke/plugin/note.py b/hooke/plugin/note.py index 3847dff..b36c801 100644 --- a/hooke/plugin/note.py +++ b/hooke/plugin/note.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 diff --git a/hooke/plugin/pcluster.py b/hooke/plugin/pcluster.py index d8525e2..87ebdd5 100644 --- a/hooke/plugin/pcluster.py +++ b/hooke/plugin/pcluster.py @@ -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 diff --git a/hooke/plugin/playlist.py b/hooke/plugin/playlist.py index b9d5240..82d0c83 100644 --- a/hooke/plugin/playlist.py +++ b/hooke/plugin/playlist.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 diff --git a/hooke/plugin/playlists.py b/hooke/plugin/playlists.py index 485904f..2be0c9b 100644 --- a/hooke/plugin/playlists.py +++ b/hooke/plugin/playlists.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 diff --git a/hooke/plugin/review.py b/hooke/plugin/review.py index fcfdd66..f1d8560 100644 --- a/hooke/plugin/review.py +++ b/hooke/plugin/review.py @@ -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 diff --git a/hooke/plugin/showconvoluted.py b/hooke/plugin/showconvoluted.py index 1bae5bc..b580c07 100644 --- a/hooke/plugin/showconvoluted.py +++ b/hooke/plugin/showconvoluted.py @@ -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 diff --git a/hooke/plugin/superimpose.py b/hooke/plugin/superimpose.py index ea916e1..dcb5bf0 100644 --- a/hooke/plugin/superimpose.py +++ b/hooke/plugin/superimpose.py @@ -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 diff --git a/hooke/plugin/system.py b/hooke/plugin/system.py index 8a09235..84f9ee6 100644 --- a/hooke/plugin/system.py +++ b/hooke/plugin/system.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 diff --git a/hooke/plugin/tccd.py b/hooke/plugin/tccd.py index ae318af..f681693 100644 --- a/hooke/plugin/tccd.py +++ b/hooke/plugin/tccd.py @@ -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 diff --git a/hooke/plugin/tutorial.py b/hooke/plugin/tutorial.py index 95f1f0c..9f0fb51 100644 --- a/hooke/plugin/tutorial.py +++ b/hooke/plugin/tutorial.py @@ -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 diff --git a/hooke/plugin/vclamp.py b/hooke/plugin/vclamp.py index fca2cd3..fcf49e8 100644 --- a/hooke/plugin/vclamp.py +++ b/hooke/plugin/vclamp.py @@ -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 diff --git a/hooke/ui/__init__.py b/hooke/ui/__init__.py index 18a2206..ad09e02 100644 --- a/hooke/ui/__init__.py +++ b/hooke/ui/__init__.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 diff --git a/hooke/ui/commandline.py b/hooke/ui/commandline.py index 291f395..c601ddc 100644 --- a/hooke/ui/commandline.py +++ b/hooke/ui/commandline.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 diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py index e9a3945..9059b8c 100644 --- a/hooke/ui/gui/__init__.py +++ b/hooke/ui/gui/__init__.py @@ -1,1024 +1,1043 @@ -# Copyright - -"""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() +# Copyright (C) 2008-2010 Fabrizio Benedetti +# Massimo Sandal +# Rolf Schmidt +# W. Trevor King +# +# 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 +# . + +"""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() diff --git a/hooke/ui/gui/clickedpoint.py b/hooke/ui/gui/clickedpoint.py index 377ff2a..35c244c 100644 --- a/hooke/ui/gui/clickedpoint.py +++ b/hooke/ui/gui/clickedpoint.py @@ -1,38 +1,38 @@ -#!/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]) +#!/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]) diff --git a/hooke/ui/gui/dialog/__init__.py b/hooke/ui/gui/dialog/__init__.py index 0cb446e..7f7b9ff 100644 --- a/hooke/ui/gui/dialog/__init__.py +++ b/hooke/ui/gui/dialog/__init__.py @@ -1,4 +1,20 @@ -# Copyright - -"""A collection of useful popup dialogs for user interaction. -""" +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . + +"""A collection of useful popup dialogs for user interaction. +""" diff --git a/hooke/ui/gui/dialog/points.py b/hooke/ui/gui/dialog/points.py index 1e86ee1..65ea3aa 100644 --- a/hooke/ui/gui/dialog/points.py +++ b/hooke/ui/gui/dialog/points.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . import wx diff --git a/hooke/ui/gui/dialog/save_file.py b/hooke/ui/gui/dialog/save_file.py index ade99b2..0d72db0 100644 --- a/hooke/ui/gui/dialog/save_file.py +++ b/hooke/ui/gui/dialog/save_file.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Define :func:`select_save_file` """ diff --git a/hooke/ui/gui/dialog/selection.py b/hooke/ui/gui/dialog/selection.py index bc5f185..ff7fbb7 100644 --- a/hooke/ui/gui/dialog/selection.py +++ b/hooke/ui/gui/dialog/selection.py @@ -1,95 +1,112 @@ -# Copyright - -"""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) +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""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) diff --git a/hooke/ui/gui/dialog/text.py b/hooke/ui/gui/dialog/text.py index ae5643e..0ebf659 100644 --- a/hooke/ui/gui/dialog/text.py +++ b/hooke/ui/gui/dialog/text.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 diff --git a/hooke/ui/gui/handler/__init__.py b/hooke/ui/gui/handler/__init__.py index 0d03794..98bdb06 100644 --- a/hooke/ui/gui/handler/__init__.py +++ b/hooke/ui/gui/handler/__init__.py @@ -1,39 +1,55 @@ -# Copyright - -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`. -""" +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . + +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`. +""" diff --git a/hooke/ui/gui/handler/boolean.py b/hooke/ui/gui/handler/boolean.py index 56404d7..c87bf83 100644 --- a/hooke/ui/gui/handler/boolean.py +++ b/hooke/ui/gui/handler/boolean.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . import wx diff --git a/hooke/ui/gui/handler/selection.py b/hooke/ui/gui/handler/selection.py index 89ef426..ee0e5e0 100644 --- a/hooke/ui/gui/handler/selection.py +++ b/hooke/ui/gui/handler/selection.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Define :class:`SelectionHandler` to handle :class:`~hooke.interaction.SelectionRequest`\s. diff --git a/hooke/ui/gui/handler/string.py b/hooke/ui/gui/handler/string.py index fe2d7e3..4147026 100644 --- a/hooke/ui/gui/handler/string.py +++ b/hooke/ui/gui/handler/string.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Define :class:`StringHandler` to handle :class:`~hooke.interaction.StringRequest`\s. diff --git a/hooke/ui/gui/menu.py b/hooke/ui/gui/menu.py index 068e32e..d918a94 100644 --- a/hooke/ui/gui/menu.py +++ b/hooke/ui/gui/menu.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Menu bar for Hooke. """ diff --git a/hooke/ui/gui/navbar.py b/hooke/ui/gui/navbar.py index 535f0f6..64e5895 100644 --- a/hooke/ui/gui/navbar.py +++ b/hooke/ui/gui/navbar.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Navigation bar for Hooke. """ diff --git a/hooke/ui/gui/panel/__init__.py b/hooke/ui/gui/panel/__init__.py index cbb4aee..a1be1eb 100644 --- a/hooke/ui/gui/panel/__init__.py +++ b/hooke/ui/gui/panel/__init__.py @@ -1,47 +1,64 @@ -# Copyright - -"""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`. -""" +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""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`. +""" diff --git a/hooke/ui/gui/panel/commands.py b/hooke/ui/gui/panel/commands.py index f46f098..5d27cc0 100644 --- a/hooke/ui/gui/panel/commands.py +++ b/hooke/ui/gui/panel/commands.py @@ -1,191 +1,191 @@ -#!/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() +#!/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() diff --git a/hooke/ui/gui/panel/note.py b/hooke/ui/gui/panel/note.py index 30de470..741c6df 100644 --- a/hooke/ui/gui/panel/note.py +++ b/hooke/ui/gui/panel/note.py @@ -1,38 +1,55 @@ -# Copyright - -"""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) +# Copyright (C) 2010 Rolf Schmidt +# W. Trevor King +# +# 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 +# . + +"""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) diff --git a/hooke/ui/gui/panel/notebook.py b/hooke/ui/gui/panel/notebook.py index 43b8bb7..e00a801 100644 --- a/hooke/ui/gui/panel/notebook.py +++ b/hooke/ui/gui/panel/notebook.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Notebook panel for Hooke. """ diff --git a/hooke/ui/gui/panel/output.py b/hooke/ui/gui/panel/output.py index d3b66b5..5edb72f 100644 --- a/hooke/ui/gui/panel/output.py +++ b/hooke/ui/gui/panel/output.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Scrolling text buffer panel for Hooke. """ diff --git a/hooke/ui/gui/panel/playlist.py b/hooke/ui/gui/panel/playlist.py index 8518473..f5c840f 100644 --- a/hooke/ui/gui/panel/playlist.py +++ b/hooke/ui/gui/panel/playlist.py @@ -1,347 +1,364 @@ -# Copyright - -"""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 +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""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 diff --git a/hooke/ui/gui/panel/plot.py b/hooke/ui/gui/panel/plot.py index 3652579..65f0542 100644 --- a/hooke/ui/gui/panel/plot.py +++ b/hooke/ui/gui/panel/plot.py @@ -1,270 +1,288 @@ -# Copyright - -"""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() +# Copyright (C) 2010 Massimo Sandal +# Rolf Schmidt +# W. Trevor King +# +# 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 +# . + +"""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() diff --git a/hooke/ui/gui/panel/propertyeditor-propgrid.py b/hooke/ui/gui/panel/propertyeditor-propgrid.py index edf0968..9686e4b 100644 --- a/hooke/ui/gui/panel/propertyeditor-propgrid.py +++ b/hooke/ui/gui/panel/propertyeditor-propgrid.py @@ -1,500 +1,517 @@ -# Copyright - -"""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 +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""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 diff --git a/hooke/ui/gui/panel/propertyeditor.py b/hooke/ui/gui/panel/propertyeditor.py index ab2518d..559ba42 100644 --- a/hooke/ui/gui/panel/propertyeditor.py +++ b/hooke/ui/gui/panel/propertyeditor.py @@ -1,277 +1,294 @@ -# Copyright - -"""Property editor panel for Hooke. - -wxPropertyGrid is `included in wxPython >= 2.9.1 `_. 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) +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""Property editor panel for Hooke. + +wxPropertyGrid is `included in wxPython >= 2.9.1 `_. 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) diff --git a/hooke/ui/gui/panel/results.py b/hooke/ui/gui/panel/results.py index 1588dbd..35568b6 100644 --- a/hooke/ui/gui/panel/results.py +++ b/hooke/ui/gui/panel/results.py @@ -1,77 +1,95 @@ -# Copyright - -"""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 +# Copyright (C) 2010 Massimo Sandal +# Rolf Schmidt +# W. Trevor King +# +# 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 +# . + +"""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 diff --git a/hooke/ui/gui/panel/selection.py b/hooke/ui/gui/panel/selection.py index a42ecb3..13da12e 100644 --- a/hooke/ui/gui/panel/selection.py +++ b/hooke/ui/gui/panel/selection.py @@ -1,97 +1,114 @@ -# Copyright - -"""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) +# Copyright (C) 2010 Massimo Sandal +# W. Trevor King +# +# 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 +# . + +"""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) diff --git a/hooke/ui/gui/panel/welcome.py b/hooke/ui/gui/panel/welcome.py index 5b5a5ae..6e9047c 100644 --- a/hooke/ui/gui/panel/welcome.py +++ b/hooke/ui/gui/panel/welcome.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Welcome panel for Hooke. """ diff --git a/hooke/ui/gui/playlist.py b/hooke/ui/gui/playlist.py index 9b7af3f..530049f 100644 --- a/hooke/ui/gui/playlist.py +++ b/hooke/ui/gui/playlist.py @@ -1,175 +1,175 @@ -#!/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: - - - - - ''' - 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 +#!/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: + + + + + ''' + 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 diff --git a/hooke/ui/gui/plotcommands.py b/hooke/ui/gui/plotcommands.py index ea256a7..ae17f2e 100644 --- a/hooke/ui/gui/plotcommands.py +++ b/hooke/ui/gui/plotcommands.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 diff --git a/hooke/ui/gui/point_request.py b/hooke/ui/gui/point_request.py index c8999ea..ef17dce 100644 --- a/hooke/ui/gui/point_request.py +++ b/hooke/ui/gui/point_request.py @@ -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 diff --git a/hooke/ui/gui/results.py b/hooke/ui/gui/results.py index ca458bd..01f859d 100644 --- a/hooke/ui/gui/results.py +++ b/hooke/ui/gui/results.py @@ -1,189 +1,189 @@ -#!/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 +#!/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 diff --git a/hooke/ui/gui/statusbar.py b/hooke/ui/gui/statusbar.py index 17de966..758a412 100644 --- a/hooke/ui/gui/statusbar.py +++ b/hooke/ui/gui/statusbar.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Status bar for Hooke. """ diff --git a/hooke/util/__init__.py b/hooke/util/__init__.py index 01e5db8..ffef0a8 100644 --- a/hooke/util/__init__.py +++ b/hooke/util/__init__.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 diff --git a/hooke/util/calculus.py b/hooke/util/calculus.py index ca60ce0..e75a371 100644 --- a/hooke/util/calculus.py +++ b/hooke/util/calculus.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 diff --git a/hooke/util/callback.py b/hooke/util/callback.py index ec7895c..3a8f927 100644 --- a/hooke/util/callback.py +++ b/hooke/util/callback.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Define the `@callback` decorator. diff --git a/hooke/util/caller.py b/hooke/util/caller.py index e2e592d..b41b024 100644 --- a/hooke/util/caller.py +++ b/hooke/util/caller.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . """Define :func:`caller_name`. diff --git a/hooke/util/encoding.py b/hooke/util/encoding.py index 41c98a4..acbd7a6 100644 --- a/hooke/util/encoding.py +++ b/hooke/util/encoding.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 diff --git a/hooke/util/fft.py b/hooke/util/fft.py index 04fab7d..4b13b31 100644 --- a/hooke/util/fft.py +++ b/hooke/util/fft.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 diff --git a/hooke/util/fit.py b/hooke/util/fit.py index da120d7..8b519e4 100644 --- a/hooke/util/fit.py +++ b/hooke/util/fit.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 diff --git a/hooke/util/graph.py b/hooke/util/graph.py index 1556e37..d374c2a 100644 --- a/hooke/util/graph.py +++ b/hooke/util/graph.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 diff --git a/hooke/util/peak.py b/hooke/util/peak.py index 137763b..ef16891 100644 --- a/hooke/util/peak.py +++ b/hooke/util/peak.py @@ -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 diff --git a/hooke/util/pluggable.py b/hooke/util/pluggable.py index ad2d85b..1379c82 100644 --- a/hooke/util/pluggable.py +++ b/hooke/util/pluggable.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 diff --git a/hooke/util/si.py b/hooke/util/si.py index ebb3c19..6108b9f 100644 --- a/hooke/util/si.py +++ b/hooke/util/si.py @@ -1,163 +1,181 @@ -# Copyright - -"""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() +# Copyright (C) 2010 Massimo Sandal +# Rolf Schmidt +# W. Trevor King +# +# 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 +# . + +"""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() diff --git a/hooke/util/util.py b/hooke/util/util.py index 49b0a21..3f20ec9 100644 --- a/hooke/util/util.py +++ b/hooke/util/util.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2010 W. Trevor King +# +# 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 +# . class Closing (object): diff --git a/setup.py b/setup.py index 6e1d3d0..47d6a33 100644 --- 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 diff --git a/test/__init__.py b/test/__init__.py index 9fc0fd6..b1a0962 100644 --- a/test/__init__.py +++ b/test/__init__.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 diff --git a/test/curve_info.py b/test/curve_info.py index 21b39af..9ab0ce6 100644 --- a/test/curve_info.py +++ b/test/curve_info.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 diff --git a/test/flat_filter.py b/test/flat_filter.py index c8cc84e..d16c9b5 100644 --- a/test/flat_filter.py +++ b/test/flat_filter.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 diff --git a/test/hemingway_driver.py b/test/hemingway_driver.py index d43d2c3..374af16 100644 --- a/test/hemingway_driver.py +++ b/test/hemingway_driver.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 diff --git a/test/jpk_driver.py b/test/jpk_driver.py index d35934a..2515273 100644 --- a/test/jpk_driver.py +++ b/test/jpk_driver.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 diff --git a/test/load_playlist.py b/test/load_playlist.py index 18d8295..1bce4f3 100644 --- a/test/load_playlist.py +++ b/test/load_playlist.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 diff --git a/test/mfp3d_driver.py b/test/mfp3d_driver.py index 06b8adb..ba9a3b8 100644 --- a/test/mfp3d_driver.py +++ b/test/mfp3d_driver.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 diff --git a/test/picoforce_driver.py b/test/picoforce_driver.py index a40ade7..ffe823c 100644 --- a/test/picoforce_driver.py +++ b/test/picoforce_driver.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 diff --git a/test/unicode_output.py b/test/unicode_output.py index 39ed7b0..293b80c 100644 --- a/test/unicode_output.py +++ b/test/unicode_output.py @@ -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 diff --git a/test/wtk_driver.py b/test/wtk_driver.py index bf2af11..0953a3a 100644 --- a/test/wtk_driver.py +++ b/test/wtk_driver.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 diff --git a/update_copyright.py b/update_copyright.py index 631adee..a2dd252 100755 --- a/update_copyright.py +++ b/update_copyright.py @@ -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 -- 2.26.2