-[drivers]\r
-csvdriver = boolean(default = False)\r
-hemingclamp = boolean(default = False)\r
-jpk = boolean(default = False)\r
-mcs = boolean(default = False)\r
-mfp1d = boolean(default = True)\r
-mfp1dexport = boolean(default = True)\r
-mfp3d = boolean(default = True)\r
-picoforce = boolean(default = True)\r
-picoforcealt = boolean(default = False)\r
-tutorialdriver = boolean(default = False)\r
-\r
-[folders]\r
-filterindex = integer(default = 0)\r
-filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
-\r
-[main]\r
-height = integer(default = 500)\r
-left = integer(default = 50)\r
-top = integer(default = 50)\r
-width = integer(default = 700)\r
-\r
-[perspectives]\r
-active = string(default = Default)\r
-\r
-[plugins]\r
-autopeak = boolean(default = True)\r
-export = boolean(default = True)\r
-fit = boolean(default = True)\r
-flatfilts = boolean(default = True)\r
-generalvclamp = boolean(default = True)\r
-playlist = boolean(default = True)\r
-plot = boolean(default = True)\r
-procplots = boolean(default = True)\r
-results = boolean(default = True)\r
-\r
-[splashscreen]\r
-#duration in milliseconds\r
-duration = integer(default = 1000)\r
-show = boolean(default = True)\r
+[drivers]
+csvdriver = boolean(default = False)
+hemingclamp = boolean(default = False)
+jpk = boolean(default = False)
+mcs = boolean(default = False)
+mfp1d = boolean(default = True)
+mfp1dexport = boolean(default = True)
+mfp3d = boolean(default = True)
+picoforce = boolean(default = True)
+picoforcealt = boolean(default = False)
+tutorialdriver = boolean(default = False)
+
+[folders]
+filterindex = integer(default = 0)
+filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+[main]
+height = integer(default = 500)
+left = integer(default = 50)
+top = integer(default = 50)
+width = integer(default = 700)
+
+[perspectives]
+active = string(default = Default)
+
+[plugins]
+autopeak = boolean(default = True)
+export = boolean(default = True)
+fit = boolean(default = True)
+flatfilts = boolean(default = True)
+generalvclamp = boolean(default = True)
+playlist = boolean(default = True)
+plot = boolean(default = True)
+procplots = boolean(default = True)
+results = boolean(default = True)
+
+[splashscreen]
+#duration in milliseconds
+duration = integer(default = 1000)
+show = boolean(default = True)
-#prefix with '#' to add a comment\r
-\r
-[command]\r
-command = autopeak\r
-plugin = autopeak\r
-\r
-#this section defines which drivers have to be loaded by Hooke\r
-[drivers]\r
-csvdriver = False\r
-hemingclamp = False\r
-jpk = False\r
-mcs = False\r
-mfp1d = True\r
-mfp3d = True\r
-mfp1dexport = True\r
-picoforce = True\r
-picoforcealt = False\r
-tutorialdriver = False\r
-\r
-[folders]\r
-filterindex = 0\r
-filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')\r
-\r
-#this section defines the window size and position\r
-[main]\r
-height = 500\r
-left = 20\r
-top = 20\r
-width = 600\r
-\r
-[perspectives]\r
-active = Default\r
-default = Default\r
-\r
-#this section defines which plugins have to be loaded by Hooke\r
-[plugins]\r
-autopeak = True\r
-export = True\r
-fit = True\r
-flatfilts = True\r
-generalvclamp = True\r
-multidistance = True\r
-playlist = True\r
-plot = True\r
-procplots = True\r
-results = True\r
-\r
-[splashscreen]\r
-#duration in milliseconds\r
-duration = 1000\r
-show = True\r
+#prefix with '#' to add a comment
+
+[command]
+command = autopeak
+plugin = autopeak
+
+#this section defines which drivers have to be loaded by Hooke
+[drivers]
+csvdriver = False
+hemingclamp = False
+jpk = False
+mcs = False
+mfp1d = True
+mfp3d = True
+mfp1dexport = True
+picoforce = True
+picoforcealt = False
+tutorialdriver = False
+
+[folders]
+filterindex = 0
+filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*')
+
+#this section defines the window size and position
+[main]
+height = 500
+left = 20
+top = 20
+width = 600
+
+[perspectives]
+active = Default
+default = Default
+
+#this section defines which plugins have to be loaded by Hooke
+[plugins]
+autopeak = True
+export = True
+fit = True
+flatfilts = True
+generalvclamp = True
+multidistance = True
+playlist = True
+plot = True
+procplots = True
+results = True
+
+[splashscreen]
+#duration in milliseconds
+duration = 1000
+show = True
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Dynamically patch :mod:`xml.dom.minidom`'s attribute value escaping.
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright
+# Copyright (C) 2008-2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Simple driver to read general comma-separated values in Hooke
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright
+# Copyright (C) 2009-2010 Allen Chen
+# Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Driver for mcs fluorescence files.
"""
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright\r
-\r
-"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.\r
-\r
-"""\r
-\r
-WX_GOOD=['2.8']\r
-\r
-import wxversion\r
-wxversion.select(WX_GOOD)\r
-\r
-import copy\r
-import logging\r
-import os\r
-import os.path\r
-import platform\r
-import shutil\r
-import time\r
-\r
-import wx.html\r
-import wx.aui as aui\r
-import wx.lib.evtmgr as evtmgr\r
-# wxPropertyGrid is included in wxPython >= 2.9.1, see\r
-# http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
-# until then, we'll avoid it because of the *nix build problems.\r
-#import wx.propgrid as wxpg\r
-\r
-from ...command import CommandExit, Exit, Success, Failure, Command, Argument\r
-from ...config import Setting\r
-from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
-from ...ui import UserInterface, CommandMessage\r
-from .dialog.selection import Selection as SelectionDialog\r
-from .dialog.save_file import select_save_file\r
-from . import menu as menu\r
-from . import navbar as navbar\r
-from . import panel as panel\r
-from .panel.propertyeditor import prop_from_argument, prop_from_setting\r
-from . import statusbar as statusbar\r
-\r
-\r
-class HookeFrame (wx.Frame):\r
- """The main Hooke-interface window. \r
- """\r
- def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):\r
- super(HookeFrame, self).__init__(*args, **kwargs)\r
- self.log = logging.getLogger('hooke')\r
- self.gui = gui\r
- self.commands = commands\r
- self.inqueue = inqueue\r
- self.outqueue = outqueue\r
- self._perspectives = {} # {name: perspective_str}\r
- self._c = {}\r
-\r
- self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))\r
-\r
- # setup frame manager\r
- self._c['manager'] = aui.AuiManager()\r
- self._c['manager'].SetManagedWindow(self)\r
-\r
- # set the gradient and drag styles\r
- self._c['manager'].GetArtProvider().SetMetric(\r
- aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)\r
- self._c['manager'].SetFlags(\r
- self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)\r
-\r
- # Min size for the frame itself isn't completely done. See\r
- # the end of FrameManager::Update() for the test code. For\r
- # now, just hard code a frame minimum size.\r
- #self.SetMinSize(wx.Size(500, 500))\r
-\r
- self._setup_panels()\r
- self._setup_toolbars()\r
- self._c['manager'].Update() # commit pending changes\r
-\r
- # Create the menubar after the panes so that the default\r
- # perspective is created with all panes open\r
- panels = [p for p in self._c.values() if isinstance(p, panel.Panel)]\r
- self._c['menu bar'] = menu.HookeMenuBar(\r
- parent=self,\r
- panels=panels,\r
- callbacks={\r
- 'close': self._on_close,\r
- 'about': self._on_about,\r
- 'view_panel': self._on_panel_visibility,\r
- 'save_perspective': self._on_save_perspective,\r
- 'delete_perspective': self._on_delete_perspective,\r
- 'select_perspective': self._on_select_perspective,\r
- })\r
- self.SetMenuBar(self._c['menu bar'])\r
-\r
- self._c['status bar'] = statusbar.StatusBar(\r
- parent=self,\r
- style=wx.ST_SIZEGRIP)\r
- self.SetStatusBar(self._c['status bar'])\r
-\r
- self._setup_perspectives()\r
- self._bind_events()\r
-\r
- self.execute_command(\r
- command=self._command_by_name('load playlist'),\r
- args={'input':'test/data/vclamp_picoforce/playlist'},\r
- )\r
- return # TODO: cleanup\r
- self.playlists = self._c['playlist'].Playlists\r
- self._displayed_plot = None\r
- #load default list, if possible\r
- self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists'))\r
-\r
-\r
- # GUI maintenance\r
-\r
- def _setup_panels(self):\r
- client_size = self.GetClientSize()\r
- for p,style in [\r
-# ('folders', wx.GenericDirCtrl(\r
-# parent=self,\r
-# dir=self.gui.config['folders-workdir'],\r
-# size=(200, 250),\r
-# style=wx.DIRCTRL_SHOW_FILTERS,\r
-# filter=self.gui.config['folders-filters'],\r
-# defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert\r
- (panel.PANELS['playlist'](\r
- callbacks={\r
- 'delete_playlist':self._on_user_delete_playlist,\r
- '_delete_playlist':self._on_delete_playlist,\r
- 'delete_curve':self._on_user_delete_curve,\r
- '_delete_curve':self._on_delete_curve,\r
- '_on_set_selected_playlist':self._on_set_selected_playlist,\r
- '_on_set_selected_curve':self._on_set_selected_curve,\r
- },\r
- parent=self,\r
- style=wx.WANTS_CHARS|wx.NO_BORDER,\r
- # WANTS_CHARS so the panel doesn't eat the Return key.\r
-# size=(160, 200),\r
- ), 'left'),\r
- (panel.PANELS['note'](\r
- callbacks = {\r
- '_on_update':self._on_update_note,\r
- },\r
- parent=self,\r
- style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-# size=(160, 200),\r
- ), 'left'),\r
-# ('notebook', Notebook(\r
-# parent=self,\r
-# pos=wx.Point(client_size.x, client_size.y),\r
-# size=wx.Size(430, 200),\r
-# style=aui.AUI_NB_DEFAULT_STYLE\r
-# | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
- (panel.PANELS['commands'](\r
- commands=self.commands,\r
- selected=self.gui.config['selected command'],\r
- callbacks={\r
- 'execute': self.execute_command,\r
- 'select_plugin': self.select_plugin,\r
- 'select_command': self.select_command,\r
-# 'selection_changed': self.panelProperties.select(self, method, command), #SelectedTreeItem = selected_item,\r
- },\r
- parent=self,\r
- style=wx.WANTS_CHARS|wx.NO_BORDER,\r
- # WANTS_CHARS so the panel doesn't eat the Return key.\r
-# size=(160, 200),\r
- ), 'right'),\r
- (panel.PANELS['propertyeditor'](\r
- callbacks={},\r
- parent=self,\r
- style=wx.WANTS_CHARS,\r
- # WANTS_CHARS so the panel doesn't eat the Return key.\r
- ), 'center'),\r
-# ('assistant', wx.TextCtrl(\r
-# parent=self,\r
-# pos=wx.Point(0, 0),\r
-# size=wx.Size(150, 90),\r
-# style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
- (panel.PANELS['plot'](\r
- callbacks={\r
- },\r
- parent=self,\r
- style=wx.WANTS_CHARS|wx.NO_BORDER,\r
- # WANTS_CHARS so the panel doesn't eat the Return key.\r
-# size=(160, 200),\r
- ), 'center'),\r
- (panel.PANELS['output'](\r
- parent=self,\r
- pos=wx.Point(0, 0),\r
- size=wx.Size(150, 90),\r
- style=wx.TE_READONLY|wx.NO_BORDER|wx.TE_MULTILINE),\r
- 'bottom'),\r
-# ('results', panel.results.Results(self), 'bottom'),\r
- ]:\r
- self._add_panel(p, style)\r
- #self._c['assistant'].SetEditable(False)\r
-\r
- def _add_panel(self, panel, style):\r
- self._c[panel.name] = panel\r
- m_name = panel.managed_name\r
- info = aui.AuiPaneInfo().Name(m_name).Caption(m_name)\r
- info.PaneBorder(False).CloseButton(True).MaximizeButton(False)\r
- if style == 'top':\r
- info.Top()\r
- elif style == 'center':\r
- info.CenterPane()\r
- elif style == 'left':\r
- info.Left()\r
- elif style == 'right':\r
- info.Right()\r
- else:\r
- assert style == 'bottom', style\r
- info.Bottom()\r
- self._c['manager'].AddPane(panel, info)\r
-\r
- def _setup_toolbars(self):\r
- self._c['navigation bar'] = navbar.NavBar(\r
- callbacks={\r
- 'next': self._next_curve,\r
- 'previous': self._previous_curve,\r
- },\r
- parent=self,\r
- style=wx.TB_FLAT | wx.TB_NODIVIDER)\r
- self._c['manager'].AddPane(\r
- self._c['navigation bar'],\r
- aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'\r
- ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False\r
- ).RightDockable(False))\r
-\r
- def _bind_events(self):\r
- # TODO: figure out if we can use the eventManager for menu\r
- # ranges and events of 'self' without raising an assertion\r
- # fail error.\r
- self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)\r
- self.Bind(wx.EVT_SIZE, self._on_size)\r
- self.Bind(wx.EVT_CLOSE, self._on_close)\r
- self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
- self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)\r
-\r
- return # TODO: cleanup\r
- treeCtrl = self._c['folders'].GetTreeCtrl()\r
- treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)\r
- \r
- #property editor\r
- self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
- #results panel\r
- self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
-\r
- def _on_about(self, *args):\r
- dialog = wx.MessageDialog(\r
- parent=self,\r
- message=self.gui._splash_text(extra_info={\r
- 'get-details':'click "Help -> License"'},\r
- wrap=False),\r
- caption='About Hooke',\r
- style=wx.OK|wx.ICON_INFORMATION)\r
- dialog.ShowModal()\r
- dialog.Destroy()\r
-\r
- def _on_close(self, *args):\r
- self.log.info('closing GUI framework')\r
- # apply changes\r
- self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
- self.gui.config['main left'] = str(self.GetPosition()[0])\r
- self.gui.config['main top'] = str(self.GetPosition()[1])\r
- self.gui.config['main width'] = str(self.GetSize().GetWidth())\r
- # push changes back to Hooke.config?\r
- self._c['manager'].UnInit()\r
- del self._c['manager']\r
- self.Destroy()\r
-\r
-\r
-\r
- # Panel utility functions\r
-\r
- def _file_name(self, name):\r
- """Cleanup names according to configured preferences.\r
- """\r
- if self.gui.config['hide extensions'] == 'True': # HACK: config should decode\r
- name,ext = os.path.splitext(name)\r
- return name\r
-\r
-\r
-\r
- # Command handling\r
-\r
- def _command_by_name(self, name):\r
- cs = [c for c in self.commands if c.name == name]\r
- if len(cs) == 0:\r
- raise KeyError(name)\r
- elif len(cs) > 1:\r
- raise Exception('Multiple commands named "%s"' % name)\r
- return cs[0]\r
-\r
- def execute_command(self, _class=None, method=None,\r
- command=None, args=None):\r
- if args == None:\r
- args = {}\r
- if ('property editor' in self._c\r
- and self.gui.config['selected command'] == command):\r
- arg_names = [arg.name for arg in command.arguments]\r
- for name,value in self._c['property editor'].get_values().items():\r
- if name in arg_names:\r
- args[name] = value\r
- self.log.debug('executing %s with %s' % (command.name, args))\r
- self.inqueue.put(CommandMessage(command, args))\r
- results = []\r
- while True:\r
- msg = self.outqueue.get()\r
- results.append(msg)\r
- if isinstance(msg, Exit):\r
- self._on_close()\r
- break\r
- elif isinstance(msg, CommandExit):\r
- # TODO: display command complete\r
- break\r
- elif isinstance(msg, ReloadUserInterfaceConfig):\r
- self.gui.reload_config(msg.config)\r
- continue\r
- elif isinstance(msg, Request):\r
- h = handler.HANDLERS[msg.type]\r
- h.run(self, msg) # TODO: pause for response?\r
- continue\r
- pp = getattr(\r
- self, '_postprocess_%s' % command.name.replace(' ', '_'),\r
- self._postprocess_text)\r
- pp(command=command, args=args, results=results)\r
- return results\r
-\r
- def _handle_request(self, msg):\r
- """Repeatedly try to get a response to `msg`.\r
- """\r
- if prompt == None:\r
- raise NotImplementedError('_%s_request_prompt' % msg.type)\r
- prompt_string = prompt(msg)\r
- parser = getattr(self, '_%s_request_parser' % msg.type, None)\r
- if parser == None:\r
- raise NotImplementedError('_%s_request_parser' % msg.type)\r
- error = None\r
- while True:\r
- if error != None:\r
- self.cmd.stdout.write(''.join([\r
- error.__class__.__name__, ': ', str(error), '\n']))\r
- self.cmd.stdout.write(prompt_string)\r
- value = parser(msg, self.cmd.stdin.readline())\r
- try:\r
- response = msg.response(value)\r
- break\r
- except ValueError, error:\r
- continue\r
- self.inqueue.put(response)\r
-\r
-\r
-\r
- # Command-specific postprocessing\r
-\r
- def _postprocess_text(self, command, args={}, results=[]):\r
- """Print the string representation of the results to the Results window.\r
-\r
- This is similar to :class:`~hooke.ui.commandline.DoCommand`'s\r
- approach, except that :class:`~hooke.ui.commandline.DoCommand`\r
- doesn't print some internally handled messages\r
- (e.g. :class:`~hooke.interaction.ReloadUserInterfaceConfig`).\r
- """\r
- for result in results:\r
- if isinstance(result, CommandExit):\r
- self._c['output'].write(result.__class__.__name__+'\n')\r
- self._c['output'].write(str(result).rstrip()+'\n')\r
-\r
- def _postprocess_load_playlist(self, command, args={}, results=None):\r
- """Update `self` to show the playlist.\r
- """\r
- if not isinstance(results[-1], Success):\r
- self._postprocess_text(command, results=results)\r
- return\r
- assert len(results) == 2, results\r
- playlist = results[0]\r
- self._c['playlist']._c['tree'].add_playlist(playlist)\r
-\r
- def _postprocess_get_playlist(self, command, args={}, results=[]):\r
- if not isinstance(results[-1], Success):\r
- self._postprocess_text(command, results=results)\r
- return\r
- assert len(results) == 2, results\r
- playlist = results[0]\r
- self._c['playlist']._c['tree'].update_playlist(playlist)\r
-\r
- def _postprocess_get_curve(self, command, args={}, results=[]):\r
- """Update `self` to show the curve.\r
- """\r
- if not isinstance(results[-1], Success):\r
- self._postprocess_text(command, results=results)\r
- return\r
- assert len(results) == 2, results\r
- curve = results[0]\r
- if args.get('curve', None) == None:\r
- # the command defaults to the current curve of the current playlist\r
- results = self.execute_command(\r
- command=self._command_by_name('get playlist'))\r
- playlist = results[0]\r
- else:\r
- raise NotImplementedError()\r
- if 'note' in self._c:\r
- print sorted(curve.info.keys())\r
- self._c['note'].set_text(curve.info['note'])\r
- if 'playlist' in self._c:\r
- self._c['playlist']._c['tree'].set_selected_curve(\r
- playlist, curve)\r
- if 'plot' in self._c:\r
- self._c['plot'].set_curve(curve, config=self.gui.config)\r
-\r
- def _postprocess_next_curve(self, command, args={}, results=[]):\r
- """No-op. Only call 'next curve' via `self._next_curve()`.\r
- """\r
- pass\r
-\r
- def _postprocess_previous_curve(self, command, args={}, results=[]):\r
- """No-op. Only call 'previous curve' via `self._previous_curve()`.\r
- """\r
- pass\r
-\r
- def _postprocess_zero_block_surface_contact_point(\r
- self, command, args={}, results=[]):\r
- """Update the curve, since the available columns may have changed.\r
- """\r
- if isinstance(results[-1], Success):\r
- self.execute_command(\r
- command=self._command_by_name('get curve'))\r
- \r
- def _postprocess_add_block_force_array(\r
- self, command, args={}, results=[]):\r
- """Update the curve, since the available columns may have changed.\r
- """\r
- if isinstance(results[-1], Success):\r
- self.execute_command(\r
- command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
- # TODO: cruft\r
-\r
- def _GetActiveFileIndex(self):\r
- lib.playlist.Playlist = self.GetActivePlaylist()\r
- #get the selected item from the tree\r
- selected_item = self._c['playlist']._c['tree'].GetSelection()\r
- #test if a playlist or a curve was double-clicked\r
- if self._c['playlist']._c['tree'].ItemHasChildren(selected_item):\r
- return -1\r
- else:\r
- count = 0\r
- selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
- while selected_item.IsOk():\r
- count += 1\r
- selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)\r
- return count\r
-\r
- def _GetPlaylistTab(self, name):\r
- for index, page in enumerate(self._c['notebook']._tabs._pages):\r
- if page.caption == name:\r
- return index\r
- return -1\r
-\r
- def select_plugin(self, _class=None, method=None, plugin=None):\r
- pass\r
-\r
- def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
- if files:\r
- playlist = lib.playlist.Playlist(self, self.drivers)\r
- for item in files:\r
- playlist.add_curve(item)\r
- if playlist.count > 0:\r
- playlist.name = self._GetUniquePlaylistName(name)\r
- playlist.reset()\r
- self.AddTayliss(playlist)\r
-\r
- def AppliesPlotmanipulator(self, name):\r
- '''\r
- Returns True if the plotmanipulator 'name' is applied, False otherwise\r
- name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
- '''\r
- return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
-\r
- def ApplyPlotmanipulators(self, plot, plot_file):\r
- '''\r
- Apply all active plotmanipulators.\r
- '''\r
- if plot is not None and plot_file is not None:\r
- manipulated_plot = copy.deepcopy(plot)\r
- for plotmanipulator in self.plotmanipulators:\r
- if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
- manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
- return manipulated_plot\r
-\r
- def GetActiveFigure(self):\r
- playlist_name = self.GetActivePlaylistName()\r
- figure = self.playlists[playlist_name].figure\r
- if figure is not None:\r
- return figure\r
- return None\r
-\r
- def GetActiveFile(self):\r
- playlist = self.GetActivePlaylist()\r
- if playlist is not None:\r
- return playlist.get_active_file()\r
- return None\r
-\r
- def GetActivePlot(self):\r
- playlist = self.GetActivePlaylist()\r
- if playlist is not None:\r
- return playlist.get_active_file().plot\r
- return None\r
-\r
- def GetDisplayedPlot(self):\r
- plot = copy.deepcopy(self.displayed_plot)\r
- #plot.curves = []\r
- #plot.curves = copy.deepcopy(plot.curves)\r
- return plot\r
-\r
- def GetDisplayedPlotCorrected(self):\r
- plot = copy.deepcopy(self.displayed_plot)\r
- plot.curves = []\r
- plot.curves = copy.deepcopy(plot.corrected_curves)\r
- return plot\r
-\r
- def GetDisplayedPlotRaw(self):\r
- plot = copy.deepcopy(self.displayed_plot)\r
- plot.curves = []\r
- plot.curves = copy.deepcopy(plot.raw_curves)\r
- return plot\r
-\r
- def GetDockArt(self):\r
- return self._c['manager'].GetArtProvider()\r
-\r
- def GetPlotmanipulator(self, name):\r
- '''\r
- Returns a plot manipulator function from its name\r
- '''\r
- for plotmanipulator in self.plotmanipulators:\r
- if plotmanipulator.name == name:\r
- return plotmanipulator\r
- return None\r
-\r
- def HasPlotmanipulator(self, name):\r
- '''\r
- returns True if the plotmanipulator 'name' is loaded, False otherwise\r
- '''\r
- for plotmanipulator in self.plotmanipulators:\r
- if plotmanipulator.command == name:\r
- return True\r
- return False\r
-\r
-\r
- def _on_dir_ctrl_left_double_click(self, event):\r
- file_path = self.panelFolders.GetPath()\r
- if os.path.isfile(file_path):\r
- if file_path.endswith('.hkp'):\r
- self.do_loadlist(file_path)\r
- event.Skip()\r
-\r
- def _on_erase_background(self, event):\r
- event.Skip()\r
-\r
- def _on_notebook_page_close(self, event):\r
- ctrl = event.GetEventObject()\r
- playlist_name = ctrl.GetPageText(ctrl._curpage)\r
- self.DeleteFromPlaylists(playlist_name)\r
-\r
- def OnPaneClose(self, event):\r
- event.Skip()\r
-\r
- def OnPropGridChanged (self, event):\r
- prop = event.GetProperty()\r
- if prop:\r
- item_section = self.panelProperties.SelectedTreeItem\r
- item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)\r
- plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)\r
- config = self.gui.config[plugin]\r
- property_section = self._c['commands']._c['tree'].GetItemText(item_section)\r
- property_key = prop.GetName()\r
- property_value = prop.GetDisplayedString()\r
-\r
- config[property_section][property_key]['value'] = property_value\r
-\r
- def OnResultsCheck(self, index, flag):\r
- results = self.GetActivePlot().results\r
- if results.has_key(self.results_str):\r
- results[self.results_str].results[index].visible = flag\r
- results[self.results_str].update()\r
- self.UpdatePlot()\r
-\r
-\r
- def _on_size(self, event):\r
- event.Skip()\r
-\r
- def UpdatePlaylistsTreeSelection(self):\r
- playlist = self.GetActivePlaylist()\r
- if playlist is not None:\r
- if playlist.index >= 0:\r
- self._c['status bar'].set_playlist(playlist)\r
- self.UpdateNote()\r
- self.UpdatePlot()\r
-\r
- def _on_curve_select(self, playlist, curve):\r
- #create the plot tab and add playlist to the dictionary\r
- plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
- notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)\r
- #tab_index = self._c['notebook'].GetSelection()\r
- playlist.figure = plotPanel.get_figure()\r
- self.playlists[playlist.name] = playlist\r
- #self.playlists[playlist.name] = [playlist, figure]\r
- self._c['status bar'].set_playlist(playlist)\r
- self.UpdateNote()\r
- self.UpdatePlot()\r
-\r
-\r
- def _on_playlist_left_doubleclick(self):\r
- index = self._c['notebook'].GetSelection()\r
- current_playlist = self._c['notebook'].GetPageText(index)\r
- if current_playlist != playlist_name:\r
- index = self._GetPlaylistTab(playlist_name)\r
- self._c['notebook'].SetSelection(index)\r
- self._c['status bar'].set_playlist(playlist)\r
- self.UpdateNote()\r
- self.UpdatePlot()\r
-\r
- def _on_playlist_delete(self, playlist):\r
- notebook = self.Parent.plotNotebook\r
- index = self.Parent._GetPlaylistTab(playlist.name)\r
- notebook.SetSelection(index)\r
- notebook.DeletePage(notebook.GetSelection())\r
- self.Parent.DeleteFromPlaylists(playlist_name)\r
-\r
-\r
-\r
- # Command panel interface\r
-\r
- def select_command(self, _class, method, command):\r
- #self.select_plugin(plugin=command.plugin)\r
- if 'assistant' in self._c:\r
- self._c['assitant'].ChangeValue(command.help)\r
- self._c['property editor'].clear()\r
- for argument in command.arguments:\r
- if argument.name == 'help':\r
- continue\r
-\r
- results = self.execute_command(\r
- command=self._command_by_name('playlists'))\r
- if not isinstance(results[-1], Success):\r
- self._postprocess_text(command, results=results)\r
- playlists = []\r
- else:\r
- playlists = results[0]\r
-\r
- results = self.execute_command(\r
- command=self._command_by_name('playlist curves'))\r
- if not isinstance(results[-1], Success):\r
- self._postprocess_text(command, results=results)\r
- curves = []\r
- else:\r
- curves = results[0]\r
-\r
- p = prop_from_argument(\r
- argument, curves=curves, playlists=playlists)\r
- if p == None:\r
- continue # property intentionally not handled (yet)\r
- self._c['property editor'].append_property(p)\r
-\r
- self.gui.config['selected command'] = command # TODO: push to engine\r
-\r
-\r
-\r
- # Note panel interface\r
-\r
- def _on_update_note(self, _class, method, text):\r
- """Sets the note for the active curve.\r
- """\r
- # TODO: note list interface in NotePanel.\r
- self.execute_command(\r
- command=self._command_by_name('set note'),\r
- args={'note':text})\r
-\r
-\r
-\r
- # Playlist panel interface\r
-\r
- def _on_user_delete_playlist(self, _class, method, playlist):\r
- pass\r
-\r
- def _on_delete_playlist(self, _class, method, playlist):\r
- if hasattr(playlist, 'path') and playlist.path != None:\r
- os.remove(playlist.path)\r
-\r
- def _on_user_delete_curve(self, _class, method, playlist, curve):\r
- pass\r
-\r
- def _on_delete_curve(self, _class, method, playlist, curve):\r
- os.remove(curve.path)\r
-\r
- def _on_set_selected_playlist(self, _class, method, playlist):\r
- """TODO: playlists plugin with `jump to playlist`.\r
- """\r
- pass\r
-\r
- def _on_set_selected_curve(self, _class, method, playlist, curve):\r
- """Call the `jump to curve` command.\r
-\r
- TODO: playlists plugin.\r
- """\r
- # TODO: jump to playlist, get playlist\r
- index = playlist.index(curve)\r
- results = self.execute_command(\r
- command=self._command_by_name('jump to curve'),\r
- args={'index':index})\r
- if not isinstance(results[-1], Success):\r
- return\r
- #results = self.execute_command(\r
- # command=self._command_by_name('get playlist'))\r
- #if not isinstance(results[-1], Success):\r
- # return\r
- self.execute_command(\r
- command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
- # Navbar interface\r
-\r
- def _next_curve(self, *args):\r
- """Call the `next curve` command.\r
- """\r
- results = self.execute_command(\r
- command=self._command_by_name('next curve'))\r
- if isinstance(results[-1], Success):\r
- self.execute_command(\r
- command=self._command_by_name('get curve'))\r
-\r
- def _previous_curve(self, *args):\r
- """Call the `previous curve` command.\r
- """\r
- results = self.execute_command(\r
- command=self._command_by_name('previous curve'))\r
- if isinstance(results[-1], Success):\r
- self.execute_command(\r
- command=self._command_by_name('get curve'))\r
-\r
-\r
-\r
- # Panel display handling\r
-\r
- def _on_panel_visibility(self, _class, method, panel_name, visible):\r
- pane = self._c['manager'].GetPane(panel_name)\r
- pane.Show(visible)\r
- #if we don't do the following, the Folders pane does not resize properly on hide/show\r
- if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
- #folders_size = pane.GetSize()\r
- self.panelFolders.Fit()\r
- self._c['manager'].Update()\r
-\r
- def _setup_perspectives(self):\r
- """Add perspectives to menubar and _perspectives.\r
- """\r
- self._perspectives = {\r
- 'Default': self._c['manager'].SavePerspective(),\r
- }\r
- path = self.gui.config['perspective path']\r
- if os.path.isdir(path):\r
- files = sorted(os.listdir(path))\r
- for fname in files:\r
- name, extension = os.path.splitext(fname)\r
- if extension != self.gui.config['perspective extension']:\r
- continue\r
- fpath = os.path.join(path, fname)\r
- if not os.path.isfile(fpath):\r
- continue\r
- perspective = None\r
- with open(fpath, 'rU') as f:\r
- perspective = f.readline()\r
- if perspective:\r
- self._perspectives[name] = perspective\r
-\r
- selected_perspective = self.gui.config['active perspective']\r
- if not self._perspectives.has_key(selected_perspective):\r
- self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke\r
-\r
- self._restore_perspective(selected_perspective, force=True)\r
- self._update_perspective_menu()\r
-\r
- def _update_perspective_menu(self):\r
- self._c['menu bar']._c['perspective'].update(\r
- sorted(self._perspectives.keys()),\r
- self.gui.config['active perspective'])\r
-\r
- def _save_perspective(self, perspective, perspective_dir, name,\r
- extension=None):\r
- path = os.path.join(perspective_dir, name)\r
- if extension != None:\r
- path += extension\r
- if not os.path.isdir(perspective_dir):\r
- os.makedirs(perspective_dir)\r
- with open(path, 'w') as f:\r
- f.write(perspective)\r
- self._perspectives[name] = perspective\r
- self._restore_perspective(name)\r
- self._update_perspective_menu()\r
-\r
- def _delete_perspectives(self, perspective_dir, names,\r
- extension=None):\r
- self.log.debug('remove perspectives %s from %s'\r
- % (names, perspective_dir))\r
- for name in names:\r
- path = os.path.join(perspective_dir, name)\r
- if extension != None:\r
- path += extension\r
- os.remove(path)\r
- del(self._perspectives[name])\r
- self._update_perspective_menu()\r
- if self.gui.config['active perspective'] in names:\r
- self._restore_perspective('Default')\r
- # TODO: does this bug still apply?\r
- # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
- # http://trac.wxwidgets.org/ticket/3258 \r
- # ) that makes the radio item indicator in the menu disappear.\r
- # The code should be fine once this issue is fixed.\r
-\r
- def _restore_perspective(self, name, force=False):\r
- if name != self.gui.config['active perspective'] or force == True:\r
- self.log.debug('restore perspective %s' % name)\r
- self.gui.config['active perspective'] = name # TODO: push to engine's Hooke\r
- self._c['manager'].LoadPerspective(self._perspectives[name])\r
- self._c['manager'].Update()\r
- for pane in self._c['manager'].GetAllPanes():\r
- view = self._c['menu bar']._c['view']\r
- if pane.name in view._c.keys():\r
- view._c[pane.name].Check(pane.window.IsShown())\r
-\r
- def _on_save_perspective(self, *args):\r
- perspective = self._c['manager'].SavePerspective()\r
- name = self.gui.config['active perspective']\r
- if name == 'Default':\r
- name = 'New perspective'\r
- name = select_save_file(\r
- directory=self.gui.config['perspective path'],\r
- name=name,\r
- extension=self.gui.config['perspective extension'],\r
- parent=self,\r
- message='Enter a name for the new perspective:',\r
- caption='Save perspective')\r
- if name == None:\r
- return\r
- self._save_perspective(\r
- perspective, self.gui.config['perspective path'], name=name,\r
- extension=self.gui.config['perspective extension'])\r
-\r
- def _on_delete_perspective(self, *args, **kwargs):\r
- options = sorted([p for p in self._perspectives.keys()\r
- if p != 'Default'])\r
- dialog = SelectionDialog(\r
- options=options,\r
- message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
- button_id=wx.ID_DELETE,\r
- selection_style='multiple',\r
- parent=self,\r
- title='Delete perspective(s)',\r
- style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
- dialog.CenterOnScreen()\r
- dialog.ShowModal()\r
- names = [options[i] for i in dialog.selected]\r
- dialog.Destroy()\r
- self._delete_perspectives(\r
- self.gui.config['perspective path'], names=names,\r
- extension=self.gui.config['perspective extension'])\r
-\r
- def _on_select_perspective(self, _class, method, name):\r
- self._restore_perspective(name)\r
-\r
-\r
-\r
-class HookeApp (wx.App):\r
- """A :class:`wx.App` wrapper around :class:`HookeFrame`.\r
-\r
- Tosses up a splash screen and then loads :class:`HookeFrame` in\r
- its own window.\r
- """\r
- def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):\r
- self.gui = gui\r
- self.commands = commands\r
- self.inqueue = inqueue\r
- self.outqueue = outqueue\r
- super(HookeApp, self).__init__(*args, **kwargs)\r
-\r
- def OnInit(self):\r
- self.SetAppName('Hooke')\r
- self.SetVendorName('')\r
- self._setup_splash_screen()\r
-\r
- height = int(self.gui.config['main height']) # HACK: config should convert\r
- width = int(self.gui.config['main width'])\r
- top = int(self.gui.config['main top'])\r
- left = int(self.gui.config['main left'])\r
-\r
- # Sometimes, the ini file gets confused and sets 'left' and\r
- # 'top' to large negative numbers. Here we catch and fix\r
- # this. Keep small negative numbers, the user might want\r
- # those.\r
- if left < -width:\r
- left = 0\r
- if top < -height:\r
- top = 0\r
-\r
- self._c = {\r
- 'frame': HookeFrame(\r
- self.gui, self.commands, self.inqueue, self.outqueue,\r
- parent=None, title='Hooke',\r
- pos=(left, top), size=(width, height),\r
- style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),\r
- }\r
- self._c['frame'].Show(True)\r
- self.SetTopWindow(self._c['frame'])\r
- return True\r
-\r
- def _setup_splash_screen(self):\r
- if self.gui.config['show splash screen'] == 'True': # HACK: config should decode\r
- path = self.gui.config['splash screen image']\r
- if os.path.isfile(path):\r
- duration = int(self.gui.config['splash screen duration']) # HACK: config should decode types\r
- wx.SplashScreen(\r
- bitmap=wx.Image(path).ConvertToBitmap(),\r
- splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,\r
- milliseconds=duration,\r
- parent=None)\r
- wx.Yield()\r
- # For some reason splashDuration and sleep do not\r
- # correspond to each other at least not on Windows.\r
- # Maybe it's because duration is in milliseconds and\r
- # sleep in seconds. Thus we need to increase the\r
- # sleep time a bit. A factor of 1.2 seems to work.\r
- sleepFactor = 1.2\r
- time.sleep(sleepFactor * duration / 1000)\r
-\r
-\r
-class GUI (UserInterface):\r
- """wxWindows graphical user interface.\r
- """\r
- def __init__(self):\r
- super(GUI, self).__init__(name='gui')\r
-\r
- def default_settings(self):\r
- """Return a list of :class:`hooke.config.Setting`\s for any\r
- configurable UI settings.\r
-\r
- The suggested section setting is::\r
-\r
- Setting(section=self.setting_section, help=self.__doc__)\r
- """\r
- return [\r
- Setting(section=self.setting_section, help=self.__doc__),\r
- Setting(section=self.setting_section, option='icon image',\r
- value=os.path.join('doc', 'img', 'microscope.ico'),\r
- help='Path to the hooke icon image.'),\r
- Setting(section=self.setting_section, option='show splash screen',\r
- value=True,\r
- help='Enable/disable the splash screen'),\r
- Setting(section=self.setting_section, option='splash screen image',\r
- value=os.path.join('doc', 'img', 'hooke.jpg'),\r
- help='Path to the Hooke splash screen image.'),\r
- Setting(section=self.setting_section, option='splash screen duration',\r
- value=1000,\r
- help='Duration of the splash screen in milliseconds.'),\r
- Setting(section=self.setting_section, option='perspective path',\r
- value=os.path.join('resources', 'gui', 'perspective'),\r
- help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.\r
- Setting(section=self.setting_section, option='perspective extension',\r
- value='.txt',\r
- help='Extension for perspective files.'),\r
- Setting(section=self.setting_section, option='hide extensions',\r
- value=False,\r
- help='Hide file extensions when displaying names.'),\r
- Setting(section=self.setting_section, option='plot legend',\r
- value=True,\r
- help='Enable/disable the plot legend.'),\r
- Setting(section=self.setting_section, option='plot SI format',\r
- value='True',\r
- help='Enable/disable SI plot axes numbering.'),\r
- Setting(section=self.setting_section, option='plot decimals',\r
- value=2,\r
- help='Number of decimal places to show if "plot SI format" is enabled.'),\r
- Setting(section=self.setting_section, option='folders-workdir',\r
- value='.',\r
- help='This should probably go...'),\r
- Setting(section=self.setting_section, option='folders-filters',\r
- value='.',\r
- help='This should probably go...'),\r
- Setting(section=self.setting_section, option='active perspective',\r
- value='Default',\r
- help='Name of active perspective file (or "Default").'),\r
- Setting(section=self.setting_section, option='folders-filter-index',\r
- value='0',\r
- help='This should probably go...'),\r
- Setting(section=self.setting_section, option='main height',\r
- value=450,\r
- help='Height of main window in pixels.'),\r
- Setting(section=self.setting_section, option='main width',\r
- value=800,\r
- help='Width of main window in pixels.'),\r
- Setting(section=self.setting_section, option='main top',\r
- value=0,\r
- help='Pixels from screen top to top of main window.'),\r
- Setting(section=self.setting_section, option='main left',\r
- value=0,\r
- help='Pixels from screen left to left of main window.'), \r
- Setting(section=self.setting_section, option='selected command',\r
- value='load playlist',\r
- help='Name of the initially selected command.'),\r
- ]\r
-\r
- def _app(self, commands, ui_to_command_queue, command_to_ui_queue):\r
- redirect = True\r
- if __debug__:\r
- redirect=False\r
- app = HookeApp(gui=self,\r
- commands=commands,\r
- inqueue=ui_to_command_queue,\r
- outqueue=command_to_ui_queue,\r
- redirect=redirect)\r
- return app\r
-\r
- def run(self, commands, ui_to_command_queue, command_to_ui_queue):\r
- app = self._app(commands, ui_to_command_queue, command_to_ui_queue)\r
- app.MainLoop()\r
+# Copyright (C) 2008-2010 Fabrizio Benedetti
+# Massimo Sandal <devicerandom@gmail.com>
+# Rolf Schmidt <rschmidt@alcor.concordia.ca>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.
+
+"""
+
+WX_GOOD=['2.8']
+
+import wxversion
+wxversion.select(WX_GOOD)
+
+import copy
+import logging
+import os
+import os.path
+import platform
+import shutil
+import time
+
+import wx.html
+import wx.aui as aui
+import wx.lib.evtmgr as evtmgr
+# wxPropertyGrid is included in wxPython >= 2.9.1, see
+# http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
+# until then, we'll avoid it because of the *nix build problems.
+#import wx.propgrid as wxpg
+
+from ...command import CommandExit, Exit, Success, Failure, Command, Argument
+from ...config import Setting
+from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig
+from ...ui import UserInterface, CommandMessage
+from .dialog.selection import Selection as SelectionDialog
+from .dialog.save_file import select_save_file
+from . import menu as menu
+from . import navbar as navbar
+from . import panel as panel
+from .panel.propertyeditor import prop_from_argument, prop_from_setting
+from . import statusbar as statusbar
+
+
+class HookeFrame (wx.Frame):
+ """The main Hooke-interface window.
+ """
+ def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
+ super(HookeFrame, self).__init__(*args, **kwargs)
+ self.log = logging.getLogger('hooke')
+ self.gui = gui
+ self.commands = commands
+ self.inqueue = inqueue
+ self.outqueue = outqueue
+ self._perspectives = {} # {name: perspective_str}
+ self._c = {}
+
+ self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))
+
+ # setup frame manager
+ self._c['manager'] = aui.AuiManager()
+ self._c['manager'].SetManagedWindow(self)
+
+ # set the gradient and drag styles
+ self._c['manager'].GetArtProvider().SetMetric(
+ aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)
+ self._c['manager'].SetFlags(
+ self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)
+
+ # Min size for the frame itself isn't completely done. See
+ # the end of FrameManager::Update() for the test code. For
+ # now, just hard code a frame minimum size.
+ #self.SetMinSize(wx.Size(500, 500))
+
+ self._setup_panels()
+ self._setup_toolbars()
+ self._c['manager'].Update() # commit pending changes
+
+ # Create the menubar after the panes so that the default
+ # perspective is created with all panes open
+ panels = [p for p in self._c.values() if isinstance(p, panel.Panel)]
+ self._c['menu bar'] = menu.HookeMenuBar(
+ parent=self,
+ panels=panels,
+ callbacks={
+ 'close': self._on_close,
+ 'about': self._on_about,
+ 'view_panel': self._on_panel_visibility,
+ 'save_perspective': self._on_save_perspective,
+ 'delete_perspective': self._on_delete_perspective,
+ 'select_perspective': self._on_select_perspective,
+ })
+ self.SetMenuBar(self._c['menu bar'])
+
+ self._c['status bar'] = statusbar.StatusBar(
+ parent=self,
+ style=wx.ST_SIZEGRIP)
+ self.SetStatusBar(self._c['status bar'])
+
+ self._setup_perspectives()
+ self._bind_events()
+
+ self.execute_command(
+ command=self._command_by_name('load playlist'),
+ args={'input':'test/data/vclamp_picoforce/playlist'},
+ )
+ return # TODO: cleanup
+ self.playlists = self._c['playlist'].Playlists
+ self._displayed_plot = None
+ #load default list, if possible
+ self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists'))
+
+
+ # GUI maintenance
+
+ def _setup_panels(self):
+ client_size = self.GetClientSize()
+ for p,style in [
+# ('folders', wx.GenericDirCtrl(
+# parent=self,
+# dir=self.gui.config['folders-workdir'],
+# size=(200, 250),
+# style=wx.DIRCTRL_SHOW_FILTERS,
+# filter=self.gui.config['folders-filters'],
+# defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert
+ (panel.PANELS['playlist'](
+ callbacks={
+ 'delete_playlist':self._on_user_delete_playlist,
+ '_delete_playlist':self._on_delete_playlist,
+ 'delete_curve':self._on_user_delete_curve,
+ '_delete_curve':self._on_delete_curve,
+ '_on_set_selected_playlist':self._on_set_selected_playlist,
+ '_on_set_selected_curve':self._on_set_selected_curve,
+ },
+ parent=self,
+ style=wx.WANTS_CHARS|wx.NO_BORDER,
+ # WANTS_CHARS so the panel doesn't eat the Return key.
+# size=(160, 200),
+ ), 'left'),
+ (panel.PANELS['note'](
+ callbacks = {
+ '_on_update':self._on_update_note,
+ },
+ parent=self,
+ style=wx.WANTS_CHARS|wx.NO_BORDER,
+# size=(160, 200),
+ ), 'left'),
+# ('notebook', Notebook(
+# parent=self,
+# pos=wx.Point(client_size.x, client_size.y),
+# size=wx.Size(430, 200),
+# style=aui.AUI_NB_DEFAULT_STYLE
+# | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),
+ (panel.PANELS['commands'](
+ commands=self.commands,
+ selected=self.gui.config['selected command'],
+ callbacks={
+ 'execute': self.execute_command,
+ 'select_plugin': self.select_plugin,
+ 'select_command': self.select_command,
+# 'selection_changed': self.panelProperties.select(self, method, command), #SelectedTreeItem = selected_item,
+ },
+ parent=self,
+ style=wx.WANTS_CHARS|wx.NO_BORDER,
+ # WANTS_CHARS so the panel doesn't eat the Return key.
+# size=(160, 200),
+ ), 'right'),
+ (panel.PANELS['propertyeditor'](
+ callbacks={},
+ parent=self,
+ style=wx.WANTS_CHARS,
+ # WANTS_CHARS so the panel doesn't eat the Return key.
+ ), 'center'),
+# ('assistant', wx.TextCtrl(
+# parent=self,
+# pos=wx.Point(0, 0),
+# size=wx.Size(150, 90),
+# style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),
+ (panel.PANELS['plot'](
+ callbacks={
+ },
+ parent=self,
+ style=wx.WANTS_CHARS|wx.NO_BORDER,
+ # WANTS_CHARS so the panel doesn't eat the Return key.
+# size=(160, 200),
+ ), 'center'),
+ (panel.PANELS['output'](
+ parent=self,
+ pos=wx.Point(0, 0),
+ size=wx.Size(150, 90),
+ style=wx.TE_READONLY|wx.NO_BORDER|wx.TE_MULTILINE),
+ 'bottom'),
+# ('results', panel.results.Results(self), 'bottom'),
+ ]:
+ self._add_panel(p, style)
+ #self._c['assistant'].SetEditable(False)
+
+ def _add_panel(self, panel, style):
+ self._c[panel.name] = panel
+ m_name = panel.managed_name
+ info = aui.AuiPaneInfo().Name(m_name).Caption(m_name)
+ info.PaneBorder(False).CloseButton(True).MaximizeButton(False)
+ if style == 'top':
+ info.Top()
+ elif style == 'center':
+ info.CenterPane()
+ elif style == 'left':
+ info.Left()
+ elif style == 'right':
+ info.Right()
+ else:
+ assert style == 'bottom', style
+ info.Bottom()
+ self._c['manager'].AddPane(panel, info)
+
+ def _setup_toolbars(self):
+ self._c['navigation bar'] = navbar.NavBar(
+ callbacks={
+ 'next': self._next_curve,
+ 'previous': self._previous_curve,
+ },
+ parent=self,
+ style=wx.TB_FLAT | wx.TB_NODIVIDER)
+ self._c['manager'].AddPane(
+ self._c['navigation bar'],
+ aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'
+ ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False
+ ).RightDockable(False))
+
+ def _bind_events(self):
+ # TODO: figure out if we can use the eventManager for menu
+ # ranges and events of 'self' without raising an assertion
+ # fail error.
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)
+ self.Bind(wx.EVT_SIZE, self._on_size)
+ self.Bind(wx.EVT_CLOSE, self._on_close)
+ self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)
+ self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)
+
+ return # TODO: cleanup
+ treeCtrl = self._c['folders'].GetTreeCtrl()
+ treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)
+
+ #property editor
+ self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
+ #results panel
+ self.panelResults.results_list.OnCheckItem = self.OnResultsCheck
+
+ def _on_about(self, *args):
+ dialog = wx.MessageDialog(
+ parent=self,
+ message=self.gui._splash_text(extra_info={
+ 'get-details':'click "Help -> License"'},
+ wrap=False),
+ caption='About Hooke',
+ style=wx.OK|wx.ICON_INFORMATION)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+ def _on_close(self, *args):
+ self.log.info('closing GUI framework')
+ # apply changes
+ self.gui.config['main height'] = str(self.GetSize().GetHeight())
+ self.gui.config['main left'] = str(self.GetPosition()[0])
+ self.gui.config['main top'] = str(self.GetPosition()[1])
+ self.gui.config['main width'] = str(self.GetSize().GetWidth())
+ # push changes back to Hooke.config?
+ self._c['manager'].UnInit()
+ del self._c['manager']
+ self.Destroy()
+
+
+
+ # Panel utility functions
+
+ def _file_name(self, name):
+ """Cleanup names according to configured preferences.
+ """
+ if self.gui.config['hide extensions'] == 'True': # HACK: config should decode
+ name,ext = os.path.splitext(name)
+ return name
+
+
+
+ # Command handling
+
+ def _command_by_name(self, name):
+ cs = [c for c in self.commands if c.name == name]
+ if len(cs) == 0:
+ raise KeyError(name)
+ elif len(cs) > 1:
+ raise Exception('Multiple commands named "%s"' % name)
+ return cs[0]
+
+ def execute_command(self, _class=None, method=None,
+ command=None, args=None):
+ if args == None:
+ args = {}
+ if ('property editor' in self._c
+ and self.gui.config['selected command'] == command):
+ arg_names = [arg.name for arg in command.arguments]
+ for name,value in self._c['property editor'].get_values().items():
+ if name in arg_names:
+ args[name] = value
+ self.log.debug('executing %s with %s' % (command.name, args))
+ self.inqueue.put(CommandMessage(command, args))
+ results = []
+ while True:
+ msg = self.outqueue.get()
+ results.append(msg)
+ if isinstance(msg, Exit):
+ self._on_close()
+ break
+ elif isinstance(msg, CommandExit):
+ # TODO: display command complete
+ break
+ elif isinstance(msg, ReloadUserInterfaceConfig):
+ self.gui.reload_config(msg.config)
+ continue
+ elif isinstance(msg, Request):
+ h = handler.HANDLERS[msg.type]
+ h.run(self, msg) # TODO: pause for response?
+ continue
+ pp = getattr(
+ self, '_postprocess_%s' % command.name.replace(' ', '_'),
+ self._postprocess_text)
+ pp(command=command, args=args, results=results)
+ return results
+
+ def _handle_request(self, msg):
+ """Repeatedly try to get a response to `msg`.
+ """
+ if prompt == None:
+ raise NotImplementedError('_%s_request_prompt' % msg.type)
+ prompt_string = prompt(msg)
+ parser = getattr(self, '_%s_request_parser' % msg.type, None)
+ if parser == None:
+ raise NotImplementedError('_%s_request_parser' % msg.type)
+ error = None
+ while True:
+ if error != None:
+ self.cmd.stdout.write(''.join([
+ error.__class__.__name__, ': ', str(error), '\n']))
+ self.cmd.stdout.write(prompt_string)
+ value = parser(msg, self.cmd.stdin.readline())
+ try:
+ response = msg.response(value)
+ break
+ except ValueError, error:
+ continue
+ self.inqueue.put(response)
+
+
+
+ # Command-specific postprocessing
+
+ def _postprocess_text(self, command, args={}, results=[]):
+ """Print the string representation of the results to the Results window.
+
+ This is similar to :class:`~hooke.ui.commandline.DoCommand`'s
+ approach, except that :class:`~hooke.ui.commandline.DoCommand`
+ doesn't print some internally handled messages
+ (e.g. :class:`~hooke.interaction.ReloadUserInterfaceConfig`).
+ """
+ for result in results:
+ if isinstance(result, CommandExit):
+ self._c['output'].write(result.__class__.__name__+'\n')
+ self._c['output'].write(str(result).rstrip()+'\n')
+
+ def _postprocess_load_playlist(self, command, args={}, results=None):
+ """Update `self` to show the playlist.
+ """
+ if not isinstance(results[-1], Success):
+ self._postprocess_text(command, results=results)
+ return
+ assert len(results) == 2, results
+ playlist = results[0]
+ self._c['playlist']._c['tree'].add_playlist(playlist)
+
+ def _postprocess_get_playlist(self, command, args={}, results=[]):
+ if not isinstance(results[-1], Success):
+ self._postprocess_text(command, results=results)
+ return
+ assert len(results) == 2, results
+ playlist = results[0]
+ self._c['playlist']._c['tree'].update_playlist(playlist)
+
+ def _postprocess_get_curve(self, command, args={}, results=[]):
+ """Update `self` to show the curve.
+ """
+ if not isinstance(results[-1], Success):
+ self._postprocess_text(command, results=results)
+ return
+ assert len(results) == 2, results
+ curve = results[0]
+ if args.get('curve', None) == None:
+ # the command defaults to the current curve of the current playlist
+ results = self.execute_command(
+ command=self._command_by_name('get playlist'))
+ playlist = results[0]
+ else:
+ raise NotImplementedError()
+ if 'note' in self._c:
+ print sorted(curve.info.keys())
+ self._c['note'].set_text(curve.info['note'])
+ if 'playlist' in self._c:
+ self._c['playlist']._c['tree'].set_selected_curve(
+ playlist, curve)
+ if 'plot' in self._c:
+ self._c['plot'].set_curve(curve, config=self.gui.config)
+
+ def _postprocess_next_curve(self, command, args={}, results=[]):
+ """No-op. Only call 'next curve' via `self._next_curve()`.
+ """
+ pass
+
+ def _postprocess_previous_curve(self, command, args={}, results=[]):
+ """No-op. Only call 'previous curve' via `self._previous_curve()`.
+ """
+ pass
+
+ def _postprocess_zero_block_surface_contact_point(
+ self, command, args={}, results=[]):
+ """Update the curve, since the available columns may have changed.
+ """
+ if isinstance(results[-1], Success):
+ self.execute_command(
+ command=self._command_by_name('get curve'))
+
+ def _postprocess_add_block_force_array(
+ self, command, args={}, results=[]):
+ """Update the curve, since the available columns may have changed.
+ """
+ if isinstance(results[-1], Success):
+ self.execute_command(
+ command=self._command_by_name('get curve'))
+
+
+
+ # TODO: cruft
+
+ def _GetActiveFileIndex(self):
+ lib.playlist.Playlist = self.GetActivePlaylist()
+ #get the selected item from the tree
+ selected_item = self._c['playlist']._c['tree'].GetSelection()
+ #test if a playlist or a curve was double-clicked
+ if self._c['playlist']._c['tree'].ItemHasChildren(selected_item):
+ return -1
+ else:
+ count = 0
+ selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)
+ while selected_item.IsOk():
+ count += 1
+ selected_item = self._c['playlist']._c['tree'].GetPrevSibling(selected_item)
+ return count
+
+ def _GetPlaylistTab(self, name):
+ for index, page in enumerate(self._c['notebook']._tabs._pages):
+ if page.caption == name:
+ return index
+ return -1
+
+ def select_plugin(self, _class=None, method=None, plugin=None):
+ pass
+
+ def AddPlaylistFromFiles(self, files=[], name='Untitled'):
+ if files:
+ playlist = lib.playlist.Playlist(self, self.drivers)
+ for item in files:
+ playlist.add_curve(item)
+ if playlist.count > 0:
+ playlist.name = self._GetUniquePlaylistName(name)
+ playlist.reset()
+ self.AddTayliss(playlist)
+
+ def AppliesPlotmanipulator(self, name):
+ '''
+ Returns True if the plotmanipulator 'name' is applied, False otherwise
+ name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')
+ '''
+ return self.GetBoolFromConfig('core', 'plotmanipulators', name)
+
+ def ApplyPlotmanipulators(self, plot, plot_file):
+ '''
+ Apply all active plotmanipulators.
+ '''
+ if plot is not None and plot_file is not None:
+ manipulated_plot = copy.deepcopy(plot)
+ for plotmanipulator in self.plotmanipulators:
+ if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):
+ manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)
+ return manipulated_plot
+
+ def GetActiveFigure(self):
+ playlist_name = self.GetActivePlaylistName()
+ figure = self.playlists[playlist_name].figure
+ if figure is not None:
+ return figure
+ return None
+
+ def GetActiveFile(self):
+ playlist = self.GetActivePlaylist()
+ if playlist is not None:
+ return playlist.get_active_file()
+ return None
+
+ def GetActivePlot(self):
+ playlist = self.GetActivePlaylist()
+ if playlist is not None:
+ return playlist.get_active_file().plot
+ return None
+
+ def GetDisplayedPlot(self):
+ plot = copy.deepcopy(self.displayed_plot)
+ #plot.curves = []
+ #plot.curves = copy.deepcopy(plot.curves)
+ return plot
+
+ def GetDisplayedPlotCorrected(self):
+ plot = copy.deepcopy(self.displayed_plot)
+ plot.curves = []
+ plot.curves = copy.deepcopy(plot.corrected_curves)
+ return plot
+
+ def GetDisplayedPlotRaw(self):
+ plot = copy.deepcopy(self.displayed_plot)
+ plot.curves = []
+ plot.curves = copy.deepcopy(plot.raw_curves)
+ return plot
+
+ def GetDockArt(self):
+ return self._c['manager'].GetArtProvider()
+
+ def GetPlotmanipulator(self, name):
+ '''
+ Returns a plot manipulator function from its name
+ '''
+ for plotmanipulator in self.plotmanipulators:
+ if plotmanipulator.name == name:
+ return plotmanipulator
+ return None
+
+ def HasPlotmanipulator(self, name):
+ '''
+ returns True if the plotmanipulator 'name' is loaded, False otherwise
+ '''
+ for plotmanipulator in self.plotmanipulators:
+ if plotmanipulator.command == name:
+ return True
+ return False
+
+
+ def _on_dir_ctrl_left_double_click(self, event):
+ file_path = self.panelFolders.GetPath()
+ if os.path.isfile(file_path):
+ if file_path.endswith('.hkp'):
+ self.do_loadlist(file_path)
+ event.Skip()
+
+ def _on_erase_background(self, event):
+ event.Skip()
+
+ def _on_notebook_page_close(self, event):
+ ctrl = event.GetEventObject()
+ playlist_name = ctrl.GetPageText(ctrl._curpage)
+ self.DeleteFromPlaylists(playlist_name)
+
+ def OnPaneClose(self, event):
+ event.Skip()
+
+ def OnPropGridChanged (self, event):
+ prop = event.GetProperty()
+ if prop:
+ item_section = self.panelProperties.SelectedTreeItem
+ item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)
+ plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)
+ config = self.gui.config[plugin]
+ property_section = self._c['commands']._c['tree'].GetItemText(item_section)
+ property_key = prop.GetName()
+ property_value = prop.GetDisplayedString()
+
+ config[property_section][property_key]['value'] = property_value
+
+ def OnResultsCheck(self, index, flag):
+ results = self.GetActivePlot().results
+ if results.has_key(self.results_str):
+ results[self.results_str].results[index].visible = flag
+ results[self.results_str].update()
+ self.UpdatePlot()
+
+
+ def _on_size(self, event):
+ event.Skip()
+
+ def UpdatePlaylistsTreeSelection(self):
+ playlist = self.GetActivePlaylist()
+ if playlist is not None:
+ if playlist.index >= 0:
+ self._c['status bar'].set_playlist(playlist)
+ self.UpdateNote()
+ self.UpdatePlot()
+
+ def _on_curve_select(self, playlist, curve):
+ #create the plot tab and add playlist to the dictionary
+ plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))
+ notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)
+ #tab_index = self._c['notebook'].GetSelection()
+ playlist.figure = plotPanel.get_figure()
+ self.playlists[playlist.name] = playlist
+ #self.playlists[playlist.name] = [playlist, figure]
+ self._c['status bar'].set_playlist(playlist)
+ self.UpdateNote()
+ self.UpdatePlot()
+
+
+ def _on_playlist_left_doubleclick(self):
+ index = self._c['notebook'].GetSelection()
+ current_playlist = self._c['notebook'].GetPageText(index)
+ if current_playlist != playlist_name:
+ index = self._GetPlaylistTab(playlist_name)
+ self._c['notebook'].SetSelection(index)
+ self._c['status bar'].set_playlist(playlist)
+ self.UpdateNote()
+ self.UpdatePlot()
+
+ def _on_playlist_delete(self, playlist):
+ notebook = self.Parent.plotNotebook
+ index = self.Parent._GetPlaylistTab(playlist.name)
+ notebook.SetSelection(index)
+ notebook.DeletePage(notebook.GetSelection())
+ self.Parent.DeleteFromPlaylists(playlist_name)
+
+
+
+ # Command panel interface
+
+ def select_command(self, _class, method, command):
+ #self.select_plugin(plugin=command.plugin)
+ if 'assistant' in self._c:
+ self._c['assitant'].ChangeValue(command.help)
+ self._c['property editor'].clear()
+ for argument in command.arguments:
+ if argument.name == 'help':
+ continue
+
+ results = self.execute_command(
+ command=self._command_by_name('playlists'))
+ if not isinstance(results[-1], Success):
+ self._postprocess_text(command, results=results)
+ playlists = []
+ else:
+ playlists = results[0]
+
+ results = self.execute_command(
+ command=self._command_by_name('playlist curves'))
+ if not isinstance(results[-1], Success):
+ self._postprocess_text(command, results=results)
+ curves = []
+ else:
+ curves = results[0]
+
+ p = prop_from_argument(
+ argument, curves=curves, playlists=playlists)
+ if p == None:
+ continue # property intentionally not handled (yet)
+ self._c['property editor'].append_property(p)
+
+ self.gui.config['selected command'] = command # TODO: push to engine
+
+
+
+ # Note panel interface
+
+ def _on_update_note(self, _class, method, text):
+ """Sets the note for the active curve.
+ """
+ # TODO: note list interface in NotePanel.
+ self.execute_command(
+ command=self._command_by_name('set note'),
+ args={'note':text})
+
+
+
+ # Playlist panel interface
+
+ def _on_user_delete_playlist(self, _class, method, playlist):
+ pass
+
+ def _on_delete_playlist(self, _class, method, playlist):
+ if hasattr(playlist, 'path') and playlist.path != None:
+ os.remove(playlist.path)
+
+ def _on_user_delete_curve(self, _class, method, playlist, curve):
+ pass
+
+ def _on_delete_curve(self, _class, method, playlist, curve):
+ os.remove(curve.path)
+
+ def _on_set_selected_playlist(self, _class, method, playlist):
+ """TODO: playlists plugin with `jump to playlist`.
+ """
+ pass
+
+ def _on_set_selected_curve(self, _class, method, playlist, curve):
+ """Call the `jump to curve` command.
+
+ TODO: playlists plugin.
+ """
+ # TODO: jump to playlist, get playlist
+ index = playlist.index(curve)
+ results = self.execute_command(
+ command=self._command_by_name('jump to curve'),
+ args={'index':index})
+ if not isinstance(results[-1], Success):
+ return
+ #results = self.execute_command(
+ # command=self._command_by_name('get playlist'))
+ #if not isinstance(results[-1], Success):
+ # return
+ self.execute_command(
+ command=self._command_by_name('get curve'))
+
+
+
+ # Navbar interface
+
+ def _next_curve(self, *args):
+ """Call the `next curve` command.
+ """
+ results = self.execute_command(
+ command=self._command_by_name('next curve'))
+ if isinstance(results[-1], Success):
+ self.execute_command(
+ command=self._command_by_name('get curve'))
+
+ def _previous_curve(self, *args):
+ """Call the `previous curve` command.
+ """
+ results = self.execute_command(
+ command=self._command_by_name('previous curve'))
+ if isinstance(results[-1], Success):
+ self.execute_command(
+ command=self._command_by_name('get curve'))
+
+
+
+ # Panel display handling
+
+ def _on_panel_visibility(self, _class, method, panel_name, visible):
+ pane = self._c['manager'].GetPane(panel_name)
+ pane.Show(visible)
+ #if we don't do the following, the Folders pane does not resize properly on hide/show
+ if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():
+ #folders_size = pane.GetSize()
+ self.panelFolders.Fit()
+ self._c['manager'].Update()
+
+ def _setup_perspectives(self):
+ """Add perspectives to menubar and _perspectives.
+ """
+ self._perspectives = {
+ 'Default': self._c['manager'].SavePerspective(),
+ }
+ path = self.gui.config['perspective path']
+ if os.path.isdir(path):
+ files = sorted(os.listdir(path))
+ for fname in files:
+ name, extension = os.path.splitext(fname)
+ if extension != self.gui.config['perspective extension']:
+ continue
+ fpath = os.path.join(path, fname)
+ if not os.path.isfile(fpath):
+ continue
+ perspective = None
+ with open(fpath, 'rU') as f:
+ perspective = f.readline()
+ if perspective:
+ self._perspectives[name] = perspective
+
+ selected_perspective = self.gui.config['active perspective']
+ if not self._perspectives.has_key(selected_perspective):
+ self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke
+
+ self._restore_perspective(selected_perspective, force=True)
+ self._update_perspective_menu()
+
+ def _update_perspective_menu(self):
+ self._c['menu bar']._c['perspective'].update(
+ sorted(self._perspectives.keys()),
+ self.gui.config['active perspective'])
+
+ def _save_perspective(self, perspective, perspective_dir, name,
+ extension=None):
+ path = os.path.join(perspective_dir, name)
+ if extension != None:
+ path += extension
+ if not os.path.isdir(perspective_dir):
+ os.makedirs(perspective_dir)
+ with open(path, 'w') as f:
+ f.write(perspective)
+ self._perspectives[name] = perspective
+ self._restore_perspective(name)
+ self._update_perspective_menu()
+
+ def _delete_perspectives(self, perspective_dir, names,
+ extension=None):
+ self.log.debug('remove perspectives %s from %s'
+ % (names, perspective_dir))
+ for name in names:
+ path = os.path.join(perspective_dir, name)
+ if extension != None:
+ path += extension
+ os.remove(path)
+ del(self._perspectives[name])
+ self._update_perspective_menu()
+ if self.gui.config['active perspective'] in names:
+ self._restore_perspective('Default')
+ # TODO: does this bug still apply?
+ # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258
+ # http://trac.wxwidgets.org/ticket/3258
+ # ) that makes the radio item indicator in the menu disappear.
+ # The code should be fine once this issue is fixed.
+
+ def _restore_perspective(self, name, force=False):
+ if name != self.gui.config['active perspective'] or force == True:
+ self.log.debug('restore perspective %s' % name)
+ self.gui.config['active perspective'] = name # TODO: push to engine's Hooke
+ self._c['manager'].LoadPerspective(self._perspectives[name])
+ self._c['manager'].Update()
+ for pane in self._c['manager'].GetAllPanes():
+ view = self._c['menu bar']._c['view']
+ if pane.name in view._c.keys():
+ view._c[pane.name].Check(pane.window.IsShown())
+
+ def _on_save_perspective(self, *args):
+ perspective = self._c['manager'].SavePerspective()
+ name = self.gui.config['active perspective']
+ if name == 'Default':
+ name = 'New perspective'
+ name = select_save_file(
+ directory=self.gui.config['perspective path'],
+ name=name,
+ extension=self.gui.config['perspective extension'],
+ parent=self,
+ message='Enter a name for the new perspective:',
+ caption='Save perspective')
+ if name == None:
+ return
+ self._save_perspective(
+ perspective, self.gui.config['perspective path'], name=name,
+ extension=self.gui.config['perspective extension'])
+
+ def _on_delete_perspective(self, *args, **kwargs):
+ options = sorted([p for p in self._perspectives.keys()
+ if p != 'Default'])
+ dialog = SelectionDialog(
+ options=options,
+ message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",
+ button_id=wx.ID_DELETE,
+ selection_style='multiple',
+ parent=self,
+ title='Delete perspective(s)',
+ style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
+ dialog.CenterOnScreen()
+ dialog.ShowModal()
+ names = [options[i] for i in dialog.selected]
+ dialog.Destroy()
+ self._delete_perspectives(
+ self.gui.config['perspective path'], names=names,
+ extension=self.gui.config['perspective extension'])
+
+ def _on_select_perspective(self, _class, method, name):
+ self._restore_perspective(name)
+
+
+
+class HookeApp (wx.App):
+ """A :class:`wx.App` wrapper around :class:`HookeFrame`.
+
+ Tosses up a splash screen and then loads :class:`HookeFrame` in
+ its own window.
+ """
+ def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
+ self.gui = gui
+ self.commands = commands
+ self.inqueue = inqueue
+ self.outqueue = outqueue
+ super(HookeApp, self).__init__(*args, **kwargs)
+
+ def OnInit(self):
+ self.SetAppName('Hooke')
+ self.SetVendorName('')
+ self._setup_splash_screen()
+
+ height = int(self.gui.config['main height']) # HACK: config should convert
+ width = int(self.gui.config['main width'])
+ top = int(self.gui.config['main top'])
+ left = int(self.gui.config['main left'])
+
+ # Sometimes, the ini file gets confused and sets 'left' and
+ # 'top' to large negative numbers. Here we catch and fix
+ # this. Keep small negative numbers, the user might want
+ # those.
+ if left < -width:
+ left = 0
+ if top < -height:
+ top = 0
+
+ self._c = {
+ 'frame': HookeFrame(
+ self.gui, self.commands, self.inqueue, self.outqueue,
+ parent=None, title='Hooke',
+ pos=(left, top), size=(width, height),
+ style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),
+ }
+ self._c['frame'].Show(True)
+ self.SetTopWindow(self._c['frame'])
+ return True
+
+ def _setup_splash_screen(self):
+ if self.gui.config['show splash screen'] == 'True': # HACK: config should decode
+ path = self.gui.config['splash screen image']
+ if os.path.isfile(path):
+ duration = int(self.gui.config['splash screen duration']) # HACK: config should decode types
+ wx.SplashScreen(
+ bitmap=wx.Image(path).ConvertToBitmap(),
+ splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
+ milliseconds=duration,
+ parent=None)
+ wx.Yield()
+ # For some reason splashDuration and sleep do not
+ # correspond to each other at least not on Windows.
+ # Maybe it's because duration is in milliseconds and
+ # sleep in seconds. Thus we need to increase the
+ # sleep time a bit. A factor of 1.2 seems to work.
+ sleepFactor = 1.2
+ time.sleep(sleepFactor * duration / 1000)
+
+
+class GUI (UserInterface):
+ """wxWindows graphical user interface.
+ """
+ def __init__(self):
+ super(GUI, self).__init__(name='gui')
+
+ def default_settings(self):
+ """Return a list of :class:`hooke.config.Setting`\s for any
+ configurable UI settings.
+
+ The suggested section setting is::
+
+ Setting(section=self.setting_section, help=self.__doc__)
+ """
+ return [
+ Setting(section=self.setting_section, help=self.__doc__),
+ Setting(section=self.setting_section, option='icon image',
+ value=os.path.join('doc', 'img', 'microscope.ico'),
+ help='Path to the hooke icon image.'),
+ Setting(section=self.setting_section, option='show splash screen',
+ value=True,
+ help='Enable/disable the splash screen'),
+ Setting(section=self.setting_section, option='splash screen image',
+ value=os.path.join('doc', 'img', 'hooke.jpg'),
+ help='Path to the Hooke splash screen image.'),
+ Setting(section=self.setting_section, option='splash screen duration',
+ value=1000,
+ help='Duration of the splash screen in milliseconds.'),
+ Setting(section=self.setting_section, option='perspective path',
+ value=os.path.join('resources', 'gui', 'perspective'),
+ help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.
+ Setting(section=self.setting_section, option='perspective extension',
+ value='.txt',
+ help='Extension for perspective files.'),
+ Setting(section=self.setting_section, option='hide extensions',
+ value=False,
+ help='Hide file extensions when displaying names.'),
+ Setting(section=self.setting_section, option='plot legend',
+ value=True,
+ help='Enable/disable the plot legend.'),
+ Setting(section=self.setting_section, option='plot SI format',
+ value='True',
+ help='Enable/disable SI plot axes numbering.'),
+ Setting(section=self.setting_section, option='plot decimals',
+ value=2,
+ help='Number of decimal places to show if "plot SI format" is enabled.'),
+ Setting(section=self.setting_section, option='folders-workdir',
+ value='.',
+ help='This should probably go...'),
+ Setting(section=self.setting_section, option='folders-filters',
+ value='.',
+ help='This should probably go...'),
+ Setting(section=self.setting_section, option='active perspective',
+ value='Default',
+ help='Name of active perspective file (or "Default").'),
+ Setting(section=self.setting_section, option='folders-filter-index',
+ value='0',
+ help='This should probably go...'),
+ Setting(section=self.setting_section, option='main height',
+ value=450,
+ help='Height of main window in pixels.'),
+ Setting(section=self.setting_section, option='main width',
+ value=800,
+ help='Width of main window in pixels.'),
+ Setting(section=self.setting_section, option='main top',
+ value=0,
+ help='Pixels from screen top to top of main window.'),
+ Setting(section=self.setting_section, option='main left',
+ value=0,
+ help='Pixels from screen left to left of main window.'),
+ Setting(section=self.setting_section, option='selected command',
+ value='load playlist',
+ help='Name of the initially selected command.'),
+ ]
+
+ def _app(self, commands, ui_to_command_queue, command_to_ui_queue):
+ redirect = True
+ if __debug__:
+ redirect=False
+ app = HookeApp(gui=self,
+ commands=commands,
+ inqueue=ui_to_command_queue,
+ outqueue=command_to_ui_queue,
+ redirect=redirect)
+ return app
+
+ def run(self, commands, ui_to_command_queue, command_to_ui_queue):
+ app = self._app(commands, ui_to_command_queue, command_to_ui_queue)
+ app.MainLoop()
-#!/usr/bin/env python\r
-\r
-'''\r
-clickedpoint.py\r
-\r
-ClickedPoint class for Hooke.\r
-\r
-Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
-\r
-This program is released under the GNU General Public License version 2.\r
-'''\r
-\r
-from scipy import arange\r
-\r
-class ClickedPoint(object):\r
- '''\r
- This class defines what a clicked point on the curve plot is.\r
- '''\r
- def __init__(self):\r
-\r
- self.is_marker = None #boolean ; decides if it is a marker\r
- self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused)\r
- self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph\r
- self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point\r
- self.index = None #integer ; the index of the clicked point with respect to the vector selected\r
- self.dest = None #0 or 1 ; 0=top plot 1=bottom plot\r
-\r
- def find_graph_coords(self, xvector, yvector):\r
- '''\r
- Given a clicked point on the plot, finds the nearest point in the dataset (in X) that\r
- corresponds to the clicked point.\r
- '''\r
- dists = []\r
- for index in arange(1, len(xvector), 1):\r
- dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2))\r
-\r
- self.index=dists.index(min(dists))\r
- self.graph_coords=(xvector[self.index], yvector[self.index])\r
+#!/usr/bin/env python
+
+'''
+clickedpoint.py
+
+ClickedPoint class for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from scipy import arange
+
+class ClickedPoint(object):
+ '''
+ This class defines what a clicked point on the curve plot is.
+ '''
+ def __init__(self):
+
+ self.is_marker = None #boolean ; decides if it is a marker
+ self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused)
+ self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph
+ self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
+ self.index = None #integer ; the index of the clicked point with respect to the vector selected
+ self.dest = None #0 or 1 ; 0=top plot 1=bottom plot
+
+ def find_graph_coords(self, xvector, yvector):
+ '''
+ Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
+ corresponds to the clicked point.
+ '''
+ dists = []
+ for index in arange(1, len(xvector), 1):
+ dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2))
+
+ self.index=dists.index(min(dists))
+ self.graph_coords=(xvector[self.index], yvector[self.index])
-# Copyright\r
-\r
-"""A collection of useful popup dialogs for user interaction.\r
-"""\r
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""A collection of useful popup dialogs for user interaction.
+"""
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
import wx
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Define :func:`select_save_file`
"""
-# Copyright\r
-\r
-"""Selection dialog.\r
-"""\r
-\r
-from os import remove\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-\r
-\r
-class Selection (wx.Dialog):\r
- """A selection dialog box.\r
-\r
- Lists options and two buttons. The first button is setup by the\r
- caller. The second button cancels the dialog.\r
-\r
- The button appearance can be specified by selecting one of the\r
- `standard wx IDs`_.\r
-\r
- .. _standard wx IDs:\r
- http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid\r
- """\r
- def __init__(self, options, message, button_id, callbacks=None,\r
- default=None, selection_style='single', *args, **kwargs):\r
- super(Selection, self).__init__(*args, **kwargs)\r
-\r
- self._options = options\r
- if callbacks == None:\r
- callbacks = {}\r
- self._callbacks = callbacks\r
- self._selection_style = selection_style\r
-\r
- self._c = {\r
- 'text': wx.StaticText(\r
- parent=self, label=message, style=wx.ALIGN_CENTRE),\r
- 'button': wx.Button(parent=self, id=button_id),\r
- 'cancel': wx.Button(self, wx.ID_CANCEL),\r
- }\r
- size = wx.Size(175, 200)\r
- if selection_style == 'single':\r
- self._c['listbox'] = wx.ListBox(\r
- parent=self, size=size, choices=options)\r
- if default != None:\r
- self._c['listbox'].SetSelection(default)\r
- else:\r
- assert selection_style == 'multiple', selection_style\r
- self._c['listbox'] = wx.CheckListBox(\r
- parent=self, size=size, choices=options)\r
- if default != None:\r
- self._c['listbox'].Check(default)\r
- self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])\r
- self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])\r
-\r
- b = wx.BoxSizer(wx.HORIZONTAL)\r
- self._add(b, 'button')\r
- self._add(b, 'cancel')\r
- v = wx.BoxSizer(wx.VERTICAL)\r
- self._add(v, 'text')\r
- self._add(v, 'listbox')\r
- self._add(v, wx.StaticLine(\r
- parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),\r
- flag=wx.GROW)\r
- self._add(v, b)\r
- self.SetSizer(v)\r
- v.Fit(self)\r
-\r
- def _add(self, sizer, item,\r
- flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
- border=5):\r
- kwargs = {'flag':flag, 'border':border}\r
- if isinstance(item, types.StringTypes):\r
- item = self._c[item]\r
- kwargs['item'] = item # window\r
- sizer.Add(**kwargs)\r
-\r
- @callback\r
- def cancel(self, event):\r
- """Close the dialog.\r
- """\r
- self.EndModal(wx.ID_CANCEL)\r
-\r
- def button(self, event):\r
- """Call ._button_callback() and close the dialog.\r
- """\r
- if self._selection_style == 'single':\r
- selected = self._c['listbox'].GetSelection()\r
- else:\r
- assert self._selection_style == 'multiple', self._selection_style\r
- selected = self._c['listbox'].GetChecked()\r
- self.selected = selected\r
- in_callback(self, options=self._options, selected=selected)\r
- self.EndModal(wx.ID_CLOSE)\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Selection dialog.
+"""
+
+from os import remove
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+
+
+class Selection (wx.Dialog):
+ """A selection dialog box.
+
+ Lists options and two buttons. The first button is setup by the
+ caller. The second button cancels the dialog.
+
+ The button appearance can be specified by selecting one of the
+ `standard wx IDs`_.
+
+ .. _standard wx IDs:
+ http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid
+ """
+ def __init__(self, options, message, button_id, callbacks=None,
+ default=None, selection_style='single', *args, **kwargs):
+ super(Selection, self).__init__(*args, **kwargs)
+
+ self._options = options
+ if callbacks == None:
+ callbacks = {}
+ self._callbacks = callbacks
+ self._selection_style = selection_style
+
+ self._c = {
+ 'text': wx.StaticText(
+ parent=self, label=message, style=wx.ALIGN_CENTRE),
+ 'button': wx.Button(parent=self, id=button_id),
+ 'cancel': wx.Button(self, wx.ID_CANCEL),
+ }
+ size = wx.Size(175, 200)
+ if selection_style == 'single':
+ self._c['listbox'] = wx.ListBox(
+ parent=self, size=size, choices=options)
+ if default != None:
+ self._c['listbox'].SetSelection(default)
+ else:
+ assert selection_style == 'multiple', selection_style
+ self._c['listbox'] = wx.CheckListBox(
+ parent=self, size=size, choices=options)
+ if default != None:
+ self._c['listbox'].Check(default)
+ self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])
+ self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])
+
+ b = wx.BoxSizer(wx.HORIZONTAL)
+ self._add(b, 'button')
+ self._add(b, 'cancel')
+ v = wx.BoxSizer(wx.VERTICAL)
+ self._add(v, 'text')
+ self._add(v, 'listbox')
+ self._add(v, wx.StaticLine(
+ parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),
+ flag=wx.GROW)
+ self._add(v, b)
+ self.SetSizer(v)
+ v.Fit(self)
+
+ def _add(self, sizer, item,
+ flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+ border=5):
+ kwargs = {'flag':flag, 'border':border}
+ if isinstance(item, types.StringTypes):
+ item = self._c[item]
+ kwargs['item'] = item # window
+ sizer.Add(**kwargs)
+
+ @callback
+ def cancel(self, event):
+ """Close the dialog.
+ """
+ self.EndModal(wx.ID_CANCEL)
+
+ def button(self, event):
+ """Call ._button_callback() and close the dialog.
+ """
+ if self._selection_style == 'single':
+ selected = self._c['listbox'].GetSelection()
+ else:
+ assert self._selection_style == 'multiple', self._selection_style
+ selected = self._c['listbox'].GetChecked()
+ self.selected = selected
+ in_callback(self, options=self._options, selected=selected)
+ self.EndModal(wx.ID_CLOSE)
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright\r
-\r
-from ....util.pluggable import IsSubclass, construct_graph\r
-\r
-\r
-HANDLER_MODULES = [\r
- 'boolean',\r
- 'float',\r
-# 'int'\r
-# 'point',\r
- 'selection',\r
- 'string'\r
- ]\r
-"""List of handler modules. TODO: autodiscovery\r
-"""\r
-\r
-class Handler (object):\r
- """Base class for :class:`~hooke.interaction.Request` handlers.\r
- \r
- :attr:`name` identifies the request type and should match the\r
- module name.\r
- """\r
- def __init__(self, name):\r
- self.name = name\r
-\r
- def run(self, hooke_frame, msg):\r
- raise NotImplemented\r
-\r
- def _cancel(self, *args, **kwargs):\r
- # TODO: somehow abort the running command\r
-\r
-\r
-HANDLERS = construct_odict(\r
- this_modname=__name__,\r
- submodnames=USER_INTERFACE_MODULES,\r
- class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))\r
-""":class:`hooke.compat.odict.odict` of :class:`Handler`\r
-instances keyed by `.name`.\r
-"""\r
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+from ....util.pluggable import IsSubclass, construct_graph
+
+
+HANDLER_MODULES = [
+ 'boolean',
+ 'float',
+# 'int'
+# 'point',
+ 'selection',
+ 'string'
+ ]
+"""List of handler modules. TODO: autodiscovery
+"""
+
+class Handler (object):
+ """Base class for :class:`~hooke.interaction.Request` handlers.
+
+ :attr:`name` identifies the request type and should match the
+ module name.
+ """
+ def __init__(self, name):
+ self.name = name
+
+ def run(self, hooke_frame, msg):
+ raise NotImplemented
+
+ def _cancel(self, *args, **kwargs):
+ # TODO: somehow abort the running command
+
+
+HANDLERS = construct_odict(
+ this_modname=__name__,
+ submodnames=USER_INTERFACE_MODULES,
+ class_selector=IsSubclass(UserInterface, blacklist=[UserInterface]))
+""":class:`hooke.compat.odict.odict` of :class:`Handler`
+instances keyed by `.name`.
+"""
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
import wx
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Define :class:`SelectionHandler` to handle
:class:`~hooke.interaction.SelectionRequest`\s.
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Define :class:`StringHandler` to handle
:class:`~hooke.interaction.StringRequest`\s.
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Menu bar for Hooke.
"""
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Navigation bar for Hooke.
"""
-# Copyright\r
-\r
-"""The `panel` module provides optional submodules that add GUI panels.\r
-"""\r
-\r
-from ....util.pluggable import IsSubclass, construct_odict\r
-\r
-\r
-PANEL_MODULES = [\r
- 'commands',\r
- 'note',\r
-# 'notebook',\r
- 'output',\r
- 'playlist',\r
- 'plot',\r
- 'propertyeditor',\r
-# 'results',\r
-# 'selection',\r
-# 'welcome',\r
- ]\r
-"""List of panel modules. TODO: autodiscovery\r
-"""\r
-\r
-class Panel (object):\r
- """Base class for Hooke GUI panels.\r
- \r
- :attr:`name` identifies the request type and should match the\r
- module name.\r
- """\r
- def __init__(self, name=None, callbacks=None, **kwargs):\r
- super(Panel, self).__init__(**kwargs)\r
- self.name = name\r
- self.managed_name = name.capitalize()\r
- self._hooke_frame = kwargs.get('parent', None)\r
- if callbacks == None:\r
- callbacks = {}\r
- self._callbacks = callbacks\r
-\r
-\r
-PANELS = construct_odict(\r
- this_modname=__name__,\r
- submodnames=PANEL_MODULES,\r
- class_selector=IsSubclass(Panel, blacklist=[Panel]),\r
- instantiate=False)\r
-""":class:`hooke.compat.odict.odict` of :class:`Panel`\r
-instances keyed by `.name`.\r
-"""\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""The `panel` module provides optional submodules that add GUI panels.
+"""
+
+from ....util.pluggable import IsSubclass, construct_odict
+
+
+PANEL_MODULES = [
+ 'commands',
+ 'note',
+# 'notebook',
+ 'output',
+ 'playlist',
+ 'plot',
+ 'propertyeditor',
+# 'results',
+# 'selection',
+# 'welcome',
+ ]
+"""List of panel modules. TODO: autodiscovery
+"""
+
+class Panel (object):
+ """Base class for Hooke GUI panels.
+
+ :attr:`name` identifies the request type and should match the
+ module name.
+ """
+ def __init__(self, name=None, callbacks=None, **kwargs):
+ super(Panel, self).__init__(**kwargs)
+ self.name = name
+ self.managed_name = name.capitalize()
+ self._hooke_frame = kwargs.get('parent', None)
+ if callbacks == None:
+ callbacks = {}
+ self._callbacks = callbacks
+
+
+PANELS = construct_odict(
+ this_modname=__name__,
+ submodnames=PANEL_MODULES,
+ class_selector=IsSubclass(Panel, blacklist=[Panel]),
+ instantiate=False)
+""":class:`hooke.compat.odict.odict` of :class:`Panel`
+instances keyed by `.name`.
+"""
-#!/usr/bin/env python\r
-\r
-"""Commands and settings panel for Hooke.\r
-\r
-This panel handles command generation of\r
-:class:`hooke.ui.CommandMessage`\s for all of the commands that don't\r
-have panels of their own. Actually it can generate\r
-:class:`hooke.ui.CommandMessage`\s for those as well, but the\r
-command-specific panel will probably have a nicer interface.\r
-\r
-# TODO: command arguments.\r
-"""\r
-\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class Tree (wx.TreeCtrl):\r
- """The main widget of :class:`CommandsPanel`.\r
-\r
- `callbacks` is shared with the parent :class:`Commands`.\r
- """\r
- def __init__(self, commands, selected, callbacks, *args, **kwargs):\r
- super(Tree, self).__init__(*args, **kwargs)\r
- imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
- imglist.Add(wx.ArtProvider.GetBitmap(\r
- wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
- imglist.Add(wx.ArtProvider.GetBitmap(\r
- wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
- self.AssignImageList(imglist)\r
- self.image = {\r
- 'root': 0,\r
- 'plugin': 0,\r
- 'command': 1,\r
- }\r
- self._c = {\r
- 'root': self.AddRoot(\r
- text='Commands and Settings', image=self.image['root']),\r
- }\r
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)\r
- self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)\r
- self.Bind(wx.EVT_MOTION, self._on_motion)\r
-\r
- self._callbacks = callbacks\r
- self._setup_commands(commands, selected)\r
- self._last_tooltip = None\r
-\r
- def _setup_commands(self, commands, selected):\r
- self._plugins = {} # {name: hooke.plugin.Plugin()}\r
- self._commands = {} # {name: hooke.command.Command()}\r
-\r
- # In both of the following dicts, command names are\r
- # (plugin.name, command.name) to avoid cross-plugin\r
- # collisions. See ._is_command().\r
- self._id_for_name = {} # {name: id}\r
- self._name_for_id = {} # {id: name}\r
-\r
- selected = None\r
- plugins = sorted(set([c.plugin for c in commands]),\r
- key=lambda p:p.name)\r
- for plugin in plugins:\r
- self._plugins[plugin.name] = plugin\r
- _id = self.AppendItem(\r
- parent=self._c['root'],\r
- text=plugin.name,\r
- image=self.image['plugin'])\r
- self._id_for_name[plugin.name] = _id\r
- self._name_for_id[_id] = plugin.name\r
- for command in sorted(commands, key=lambda c:c.name):\r
- name = (command.plugin.name, command.name)\r
- self._commands[name] = command\r
- _id = self.AppendItem(\r
- parent=self._id_for_name[command.plugin.name],\r
- text=command.name,\r
- image=self.image['command'])\r
- self._id_for_name[name] = _id\r
- self._name_for_id[_id] = name\r
- if command.name == selected:\r
- selected = _id\r
-\r
- #for plugin in self._plugins.values():\r
- # self.Expand(self._id_for_name[plugin.name])\r
- # make sure the selected command/plugin is visible in the tree\r
- if selected is not None:\r
- self.SelectItem(selected, True)\r
- self.EnsureVisible(selected)\r
-\r
- def _is_command(self, name): # name from ._id_for_name / ._name_for_id\r
- """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.\r
- """\r
- # Plugin names are strings, Command names are tuples.\r
- # See ._setup_commands().\r
- return not isinstance(name, types.StringTypes)\r
-\r
- def _canonical_id(self, _id):\r
- """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
-\r
- For some reason, `.GetSelection()`, etc. return items that\r
- hash differently than the original `.AppendItem()`-returned\r
- IDs. This means that `._name_for_id[self.GetSelection()]`\r
- will raise `KeyError`, even if there is an id `X` in\r
- `._name_for_id` for which `X == self.GetSelection()` will\r
- return `True`. This method "canonicalizes" IDs so that the\r
- hashing is consistent.\r
- """\r
- for c_id in self._name_for_id.keys():\r
- if c_id == _id:\r
- return c_id\r
- raise KeyError(_id)\r
-\r
- def _on_selection_changed(self, event):\r
- active_id = event.GetItem()\r
- selected_id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(selected_id)]\r
- if self._is_command(name):\r
- self.select_command(self._commands[name])\r
- else:\r
- self.select_plugin(self._plugins[name])\r
-\r
- def _on_execute(self, event):\r
- self.execute()\r
-\r
- def _on_motion(self, event):\r
- """Enable tooltips.\r
- """\r
- hit_id,hit_flags = self.HitTest(event.GetPosition())\r
- try:\r
- hit_id = self._canonical_id(hit_id)\r
- except KeyError:\r
- hit_id = None\r
- if hit_id == None:\r
- msg = ''\r
- else:\r
- name = self._name_for_id[hit_id]\r
- if self._is_command(name):\r
- msg = self._commands[name].help()\r
- else:\r
- msg = '' # self._plugins[name].help() TODO: Plugin.help method\r
- if msg != self._last_tooltip:\r
- self._last_tooltip = msg\r
- event.GetEventObject().SetToolTipString(msg)\r
-\r
- def select_plugin(self, plugin):\r
- in_callback(self, plugin)\r
-\r
- def select_command(self, command):\r
- in_callback(self, command)\r
-\r
- def execute(self):\r
- _id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
- if self._is_command(name):\r
- command = self._commands[name]\r
- in_callback(self, command)\r
-\r
-\r
-class CommandsPanel (Panel, wx.Panel):\r
- """UI for selecting from available commands.\r
-\r
- `callbacks` is shared with the underlying :class:`Tree`.\r
- """\r
- def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):\r
- super(CommandsPanel, self).__init__(\r
- name='commands', callbacks=callbacks, **kwargs)\r
- self._c = {\r
- 'tree': Tree(\r
- commands=commands,\r
- selected=selected,\r
- callbacks=callbacks,\r
- parent=self,\r
- pos=wx.Point(0, 0),\r
- size=wx.Size(160, 250),\r
- style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),\r
- 'execute': wx.Button(self, label='Execute'),\r
- }\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self._c['execute'], 0, wx.EXPAND)\r
- sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
- # Put 'tree' second because its min size may be large enough\r
- # to push the button out of view.\r
- self.SetSizer(sizer)\r
- sizer.Fit(self)\r
-\r
- self.Bind(wx.EVT_BUTTON, self._on_execute_button)\r
-\r
- def _on_execute_button(self, event):\r
- self._c['tree'].execute()\r
+#!/usr/bin/env python
+
+"""Commands and settings panel for Hooke.
+
+This panel handles command generation of
+:class:`hooke.ui.CommandMessage`\s for all of the commands that don't
+have panels of their own. Actually it can generate
+:class:`hooke.ui.CommandMessage`\s for those as well, but the
+command-specific panel will probably have a nicer interface.
+
+# TODO: command arguments.
+"""
+
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class Tree (wx.TreeCtrl):
+ """The main widget of :class:`CommandsPanel`.
+
+ `callbacks` is shared with the parent :class:`Commands`.
+ """
+ def __init__(self, commands, selected, callbacks, *args, **kwargs):
+ super(Tree, self).__init__(*args, **kwargs)
+ imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16)))
+ self.AssignImageList(imglist)
+ self.image = {
+ 'root': 0,
+ 'plugin': 0,
+ 'command': 1,
+ }
+ self._c = {
+ 'root': self.AddRoot(
+ text='Commands and Settings', image=self.image['root']),
+ }
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_selection_changed)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._on_execute)
+ self.Bind(wx.EVT_MOTION, self._on_motion)
+
+ self._callbacks = callbacks
+ self._setup_commands(commands, selected)
+ self._last_tooltip = None
+
+ def _setup_commands(self, commands, selected):
+ self._plugins = {} # {name: hooke.plugin.Plugin()}
+ self._commands = {} # {name: hooke.command.Command()}
+
+ # In both of the following dicts, command names are
+ # (plugin.name, command.name) to avoid cross-plugin
+ # collisions. See ._is_command().
+ self._id_for_name = {} # {name: id}
+ self._name_for_id = {} # {id: name}
+
+ selected = None
+ plugins = sorted(set([c.plugin for c in commands]),
+ key=lambda p:p.name)
+ for plugin in plugins:
+ self._plugins[plugin.name] = plugin
+ _id = self.AppendItem(
+ parent=self._c['root'],
+ text=plugin.name,
+ image=self.image['plugin'])
+ self._id_for_name[plugin.name] = _id
+ self._name_for_id[_id] = plugin.name
+ for command in sorted(commands, key=lambda c:c.name):
+ name = (command.plugin.name, command.name)
+ self._commands[name] = command
+ _id = self.AppendItem(
+ parent=self._id_for_name[command.plugin.name],
+ text=command.name,
+ image=self.image['command'])
+ self._id_for_name[name] = _id
+ self._name_for_id[_id] = name
+ if command.name == selected:
+ selected = _id
+
+ #for plugin in self._plugins.values():
+ # self.Expand(self._id_for_name[plugin.name])
+ # make sure the selected command/plugin is visible in the tree
+ if selected is not None:
+ self.SelectItem(selected, True)
+ self.EnsureVisible(selected)
+
+ def _is_command(self, name): # name from ._id_for_name / ._name_for_id
+ """Return `True` if `name` corresponds to a :class:`hooke.command.Command`.
+ """
+ # Plugin names are strings, Command names are tuples.
+ # See ._setup_commands().
+ return not isinstance(name, types.StringTypes)
+
+ def _canonical_id(self, _id):
+ """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
+
+ For some reason, `.GetSelection()`, etc. return items that
+ hash differently than the original `.AppendItem()`-returned
+ IDs. This means that `._name_for_id[self.GetSelection()]`
+ will raise `KeyError`, even if there is an id `X` in
+ `._name_for_id` for which `X == self.GetSelection()` will
+ return `True`. This method "canonicalizes" IDs so that the
+ hashing is consistent.
+ """
+ for c_id in self._name_for_id.keys():
+ if c_id == _id:
+ return c_id
+ raise KeyError(_id)
+
+ def _on_selection_changed(self, event):
+ active_id = event.GetItem()
+ selected_id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(selected_id)]
+ if self._is_command(name):
+ self.select_command(self._commands[name])
+ else:
+ self.select_plugin(self._plugins[name])
+
+ def _on_execute(self, event):
+ self.execute()
+
+ def _on_motion(self, event):
+ """Enable tooltips.
+ """
+ hit_id,hit_flags = self.HitTest(event.GetPosition())
+ try:
+ hit_id = self._canonical_id(hit_id)
+ except KeyError:
+ hit_id = None
+ if hit_id == None:
+ msg = ''
+ else:
+ name = self._name_for_id[hit_id]
+ if self._is_command(name):
+ msg = self._commands[name].help()
+ else:
+ msg = '' # self._plugins[name].help() TODO: Plugin.help method
+ if msg != self._last_tooltip:
+ self._last_tooltip = msg
+ event.GetEventObject().SetToolTipString(msg)
+
+ def select_plugin(self, plugin):
+ in_callback(self, plugin)
+
+ def select_command(self, command):
+ in_callback(self, command)
+
+ def execute(self):
+ _id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(_id)]
+ if self._is_command(name):
+ command = self._commands[name]
+ in_callback(self, command)
+
+
+class CommandsPanel (Panel, wx.Panel):
+ """UI for selecting from available commands.
+
+ `callbacks` is shared with the underlying :class:`Tree`.
+ """
+ def __init__(self, callbacks=None, commands=None, selected=None, **kwargs):
+ super(CommandsPanel, self).__init__(
+ name='commands', callbacks=callbacks, **kwargs)
+ self._c = {
+ 'tree': Tree(
+ commands=commands,
+ selected=selected,
+ callbacks=callbacks,
+ parent=self,
+ pos=wx.Point(0, 0),
+ size=wx.Size(160, 250),
+ style=wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT),
+ 'execute': wx.Button(self, label='Execute'),
+ }
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self._c['execute'], 0, wx.EXPAND)
+ sizer.Add(self._c['tree'], 1, wx.EXPAND)
+ # Put 'tree' second because its min size may be large enough
+ # to push the button out of view.
+ self.SetSizer(sizer)
+ sizer.Fit(self)
+
+ self.Bind(wx.EVT_BUTTON, self._on_execute_button)
+
+ def _on_execute_button(self, event):
+ self._c['tree'].execute()
-# Copyright\r
-\r
-"""Note panel for Hooke.\r
-"""\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class NotePanel (Panel, wx.Panel):\r
- def __init__(self, callbacks=None, **kwargs):\r
- super(NotePanel, self).__init__(\r
- name='note', callbacks=callbacks, **kwargs)\r
-\r
- self._c = {\r
- 'editor': wx.TextCtrl(\r
- parent=self,\r
- style=wx.TE_MULTILINE),\r
- 'update': wx.Button(\r
- parent=self,\r
- label='Update note'),\r
- }\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self._c['editor'], 1, wx.EXPAND)\r
- sizer.Add(self._c['update'], 0, wx.EXPAND)\r
- self.SetSizer(sizer)\r
- self.SetAutoLayout(True)\r
-\r
- self.Bind(wx.EVT_BUTTON, self._on_update)\r
-\r
- def set_text(self, text):\r
- self._c['editor'].SetValue(text)\r
-\r
- def _on_update(self, event):\r
- text = self._c['editor'].GetValue()\r
- in_callback(self, text)\r
+# Copyright (C) 2010 Rolf Schmidt <rschmidt@alcor.concordia.ca>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Note panel for Hooke.
+"""
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class NotePanel (Panel, wx.Panel):
+ def __init__(self, callbacks=None, **kwargs):
+ super(NotePanel, self).__init__(
+ name='note', callbacks=callbacks, **kwargs)
+
+ self._c = {
+ 'editor': wx.TextCtrl(
+ parent=self,
+ style=wx.TE_MULTILINE),
+ 'update': wx.Button(
+ parent=self,
+ label='Update note'),
+ }
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self._c['editor'], 1, wx.EXPAND)
+ sizer.Add(self._c['update'], 0, wx.EXPAND)
+ self.SetSizer(sizer)
+ self.SetAutoLayout(True)
+
+ self.Bind(wx.EVT_BUTTON, self._on_update)
+
+ def set_text(self, text):
+ self._c['editor'].SetValue(text)
+
+ def _on_update(self, event):
+ text = self._c['editor'].GetValue()
+ in_callback(self, text)
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Notebook panel for Hooke.
"""
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Scrolling text buffer panel for Hooke.
"""
-# Copyright\r
-\r
-"""Playlist panel for Hooke.\r
-\r
-Provides a nice GUI interface to the\r
-:class:`~hooke.plugin.playlist.PlaylistPlugin`.\r
-"""\r
-\r
-import logging\r
-import types\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from . import Panel\r
-\r
-\r
-class Menu (wx.Menu):\r
- """Popup menu for selecting playlist :class:`Tree` actions.\r
- """\r
- def __init__(self, on_delete, *args, **kwargs):\r
- super(Menu, self).__init__(*args, **kwargs)\r
- self._c = {\r
- 'delete': self.Append(id=wx.ID_ANY, text='Delete'),\r
- }\r
- self.Bind(wx.EVT_MENU, on_delete)\r
-\r
-\r
-class Tree (wx.TreeCtrl):\r
- """:class:`wx.TreeCtrl` subclass handling playlist and curve selection.\r
- """\r
- def __init__(self, *args, **kwargs):\r
- self.log = logging.getLogger('hooke')\r
- self._panel = kwargs['parent']\r
- self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()\r
- super(Tree, self).__init__(*args, **kwargs)\r
- imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
- imglist.Add(wx.ArtProvider.GetBitmap(\r
- wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))\r
- imglist.Add(wx.ArtProvider.GetBitmap(\r
- wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)))\r
- self.AssignImageList(imglist)\r
- self.image = {\r
- 'root': 0,\r
- 'playlist': 0,\r
- 'curve': 1,\r
- }\r
- self._c = {\r
- 'menu': Menu(self._on_delete),\r
- 'root': self.AddRoot(text='Playlists', image=self.image['root'])\r
- }\r
- self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)\r
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
-\r
- self._setup_playlists()\r
-\r
- def _setup_playlists(self):\r
- self._playlists = {} # {name: hooke.playlist.Playlist()}\r
-\r
- # In both of the following dicts, curve names are\r
- # (playlist.name, curve.name) to avoid cross-playlist\r
- # collisions. See ._is_curve().\r
- self._id_for_name = {} # {name: id}\r
- self._name_for_id = {} # {id: name}\r
-\r
- def _is_curve(self, name): # name from ._id_for_name / ._name_for_id\r
- """Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.\r
- """\r
- # Playlist names are strings, Curve names are tuples.\r
- # See ._setup_playlists().\r
- return not isinstance(name, types.StringTypes)\r
-\r
- def _canonical_id(self, _id):\r
- """Return a canonical form of `_id` suitable for accessing `._name_for_id`.\r
-\r
- For some reason, `.GetSelection()`, etc. return items that\r
- hash differently than the original `.AppendItem()`-returned\r
- IDs. This means that `._name_for_id[self.GetSelection()]`\r
- will raise `KeyError`, even if there is an id `X` in\r
- `._name_for_id` for which `X == self.GetSelection()` will\r
- return `True`. This method "canonicalizes" IDs so that the\r
- hashing is consistent.\r
- """\r
- for c_id in self._name_for_id.keys():\r
- if c_id == _id:\r
- return c_id\r
- raise KeyError(_id)\r
-\r
-\r
- # Context menu\r
-\r
- def _on_context_menu(self, event):\r
- """Launch a popup :class:`Menu` with per-playlist/curve activities.\r
- """\r
- hit_id,hit_flags = self.HitTest(event.GetPosition())\r
- if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
- self._hit_id = self._canonical_id(hit_id) # store for callbacks\r
- menu = Menu(self._on_delete)\r
- self.PopupMenu(menu, event.GetPosition())\r
- menu.Destroy()\r
-\r
- # Add\r
- # add_* called directly by HookeFrame\r
- # _add_* called on every addition\r
-\r
- def add_playlist(self, playlist):\r
- """Add a :class:`hooke.playlist.Playlist` to the tree.\r
-\r
- Calls :meth:`_add_playlist` and triggers a callback.\r
- """\r
- self._add_playlist(playlist)\r
- in_callback(self, playlist)\r
-\r
- def _add_playlist(self, playlist):\r
- """Add a class:`hooke.playlist.Playlist` to the tree.\r
-\r
- No callback triggered.\r
- """\r
- if playlist.name not in self._playlists:\r
- pass\r
- else:\r
- raise ValueError('duplicate playlist: %s' % playlist.name)\r
- self._playlists[playlist.name] = playlist\r
- p_id = self.AppendItem(\r
- parent=self._c['root'],\r
- text=self._panel._hooke_frame._file_name(playlist.name),\r
- image=self.image['playlist'])\r
- self._id_for_name[playlist.name] = p_id\r
- self._name_for_id[p_id] = playlist.name\r
- for curve in playlist:\r
- self._add_curve(playlist.name, curve)\r
-\r
- def add_curve(self, playlist_name, curve):\r
- """Add a :class:`hooke.curve.Curve` to a curently loaded playlist.\r
-\r
- Calls :meth:`_add_curve` and triggers a callback.\r
- """\r
- self._add_curve(playlist_name, curve)\r
- playlist = self._playlists[playlist_name]\r
- in_callback(self, playlist, curve)\r
-\r
- def _add_curve(self, playlist_name, curve):\r
- """Add a class:`hooke.curve.Curve` to the tree.\r
-\r
- No callback triggered.\r
- """\r
- p = self._playlists[playlist_name]\r
- if curve not in p:\r
- p.append(curve)\r
- c_id = self.AppendItem(\r
- parent=self._id_for_name[playlist_name],\r
- text=self._panel._hooke_frame._file_name(curve.name),\r
- image=self.image['curve'])\r
- self._id_for_name[(p.name, curve.name)] = c_id\r
- self._name_for_id[c_id] = (p.name, curve.name)\r
-\r
- @callback\r
- def generate_new_playlist(self):\r
- pass # TODO\r
-\r
- def _GetUniquePlaylistName(self, name): # TODO\r
- playlist_name = name\r
- count = 1\r
- while playlist_name in self.playlists:\r
- playlist_name = ''.join([name, str(count)])\r
- count += 1\r
- return playlist_name\r
-\r
- # Delete\r
- # delete_* called by _on_delete handler (user click) or HookeFrame\r
- # _delete_* called on every deletion\r
-\r
- def _on_delete(self, event):\r
- """Handler for :class:`Menu`'s `Delete` button.\r
-\r
- Determines the clicked item and calls the appropriate\r
- `.delete_*()` method on it.\r
- """\r
- #if hasattr(self, '_hit_id'): # called via ._c['menu']\r
- _id = self._hit_id\r
- del(self._hit_id)\r
- name = self._name_for_id[_id]\r
- if self._is_curve(name):\r
- self.delete_curve(playlist_name=name[0], name=name[1])\r
- else:\r
- self.delete_playlist(name)\r
-\r
- def delete_playlist(self, name):\r
- """Delete a :class:`hooke.playlist.Playlist` by name.\r
-\r
- Called by the :meth:`_on_delete` handler.\r
-\r
- Removes the playlist and its curves from the tree, then calls\r
- :meth:`_delete_playlist`.\r
- """\r
- _id = self._id_for_name[name]\r
- self.Delete(_id)\r
- playlist = self._playlists[name]\r
- self._delete_playlist(playlist)\r
- in_callback(self, playlist)\r
-\r
- def _delete_playlist(self, playlist):\r
- """Adjust name/id caches for the playlist and its curves.\r
-\r
- Called on *every* playlist deletion.\r
- """\r
- self._playlists.pop(playlist.name)\r
- _id = self._id_for_name.pop(playlist.name)\r
- del(self._name_for_id[_id])\r
- for curve in playlist:\r
- self._delete_curve(playlist, curve)\r
- in_callback(self, playlist)\r
-\r
- def delete_curve(self, playlist_name, name):\r
- """Delete a :class:`hooke.curve.Curve` by name.\r
-\r
- Called by the :meth:`_on_delete` handler.\r
-\r
- Removes the curve from the tree, then calls\r
- :meth:`_delete_curve`.\r
- """\r
- _id = self._id_for_name[(playlist_name, name)]\r
- self.Delete(_id)\r
- playlist = self._playlists[playlist_name]\r
- curve = None\r
- for i,c in enumerate(playlist):\r
- if c.name == name:\r
- curve = c\r
- break\r
- self._delete_curve(playlist, curve)\r
- in_callback(self, playlist, curve)\r
-\r
- def _delete_curve(self, playlist, curve):\r
- """Adjust name/id caches.\r
-\r
- Called on _every_ curve deletion.\r
- """\r
- _id = self._id_for_name.pop((playlist.name, curve.name))\r
- del(self._name_for_id[_id])\r
- in_callback(self, playlist, curve)\r
-\r
- # Get selection\r
-\r
- def get_selected_playlist(self):\r
- """Return the selected :class:`hooke.playlist.Playlist`.\r
- """\r
- _id = self.GetSelection()\r
- try:\r
- _id = self._canonical_id(_id)\r
- except KeyError: # no playlist selected\r
- return None\r
- name = self._name_for_id[_id]\r
- if self._is_curve(name):\r
- name = name[0]\r
- return self._playlists[name]\r
-\r
- def get_selected_curve(self):\r
- """Return the selected :class:`hooke.curve.Curve`.\r
- """\r
- _id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
- if self._is_curve(name):\r
- p_name,c_name = name\r
- playlist = self._playlists[p_name]\r
- c = playlist.current()\r
- assert c.name == c_name, '%s != %s' % (c.name, c_name)\r
- else:\r
- playlist = self._playlists[name]\r
- return playlist.current()\r
-\r
- # Set selection (via user interaction with this panel)\r
- #\r
- # These are hooks for HookeFrame callbacks which will send\r
- # the results back via 'get curve' calling 'set_selected_curve'.\r
-\r
- def _on_select(self, event):\r
- """Select the clicked-on curve/playlist.\r
- """\r
- _id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
- if self._is_curve(name):\r
- p_name,c_name = name\r
- self._on_set_selected_curve(p_name, c_name)\r
- else:\r
- self._on_set_selected_playlist(name)\r
-\r
- def _on_set_selected_playlist(self, name):\r
- in_callback(self, self._playlists[name])\r
-\r
- def _on_set_selected_curve(self, playlist_name, name):\r
- playlist = self._playlists[playlist_name]\r
- curve = None\r
- for i,c in enumerate(playlist):\r
- if c.name == name:\r
- curve = c\r
- break\r
- if curve == None:\r
- raise ValueError(name)\r
- in_callback(self, playlist, curve)\r
- \r
- # Set selection (from the HookeFrame)\r
-\r
- def set_selected_curve(self, playlist, curve):\r
- """Make the curve the playlist's current curve.\r
- """\r
- self.log.debug('playlist tree expanding %s' % playlist.name)\r
- self.Expand(self._id_for_name[playlist.name])\r
- self.Unbind(wx.EVT_TREE_SEL_CHANGED)\r
- self.log.debug('playlist tree selecting %s' % curve.name)\r
- self.SelectItem(self._id_for_name[(playlist.name, curve.name)])\r
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
-\r
- def update_playlist(self, playlist):\r
- """Absorb changed `._index`, etc.\r
- """\r
- self._playlists[playlist.name] = playlist\r
-\r
-\r
-class Playlist (Panel, wx.Panel):\r
- """:class:`wx.Panel` subclass wrapper for :class:`Tree`.\r
- """\r
- def __init__(self, callbacks=None, **kwargs):\r
- # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
- super(Playlist, self).__init__(\r
- name='playlist', callbacks=callbacks, **kwargs)\r
- self._c = {\r
- 'tree': Tree(\r
- parent=self,\r
- size=wx.Size(160, 250),\r
- style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),\r
- }\r
-\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self._c['tree'], 1, wx.EXPAND)\r
- self.SetSizer(sizer)\r
- sizer.Fit(self)\r
-\r
- # Expose all Tree's public curve/playlist methods directly.\r
- # Following DRY and the LoD.\r
- for attribute_name in dir(self._c['tree']):\r
- if (attribute_name.startswith('_')\r
- or 'playlist' not in attribute_name\r
- or 'curve' not in attribute_name):\r
- continue # not an attribute we're interested in\r
- attr = getattr(self._c['tree'], attribute_name)\r
- if hasattr(attr, '__call__'): # attr is a function / method\r
- setattr(self, attribute_name, attr) # expose it\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Playlist panel for Hooke.
+
+Provides a nice GUI interface to the
+:class:`~hooke.plugin.playlist.PlaylistPlugin`.
+"""
+
+import logging
+import types
+
+import wx
+
+from ....util.callback import callback, in_callback
+from . import Panel
+
+
+class Menu (wx.Menu):
+ """Popup menu for selecting playlist :class:`Tree` actions.
+ """
+ def __init__(self, on_delete, *args, **kwargs):
+ super(Menu, self).__init__(*args, **kwargs)
+ self._c = {
+ 'delete': self.Append(id=wx.ID_ANY, text='Delete'),
+ }
+ self.Bind(wx.EVT_MENU, on_delete)
+
+
+class Tree (wx.TreeCtrl):
+ """:class:`wx.TreeCtrl` subclass handling playlist and curve selection.
+ """
+ def __init__(self, *args, **kwargs):
+ self.log = logging.getLogger('hooke')
+ self._panel = kwargs['parent']
+ self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()
+ super(Tree, self).__init__(*args, **kwargs)
+ imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16)))
+ imglist.Add(wx.ArtProvider.GetBitmap(
+ wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)))
+ self.AssignImageList(imglist)
+ self.image = {
+ 'root': 0,
+ 'playlist': 0,
+ 'curve': 1,
+ }
+ self._c = {
+ 'menu': Menu(self._on_delete),
+ 'root': self.AddRoot(text='Playlists', image=self.image['root'])
+ }
+ self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)
+
+ self._setup_playlists()
+
+ def _setup_playlists(self):
+ self._playlists = {} # {name: hooke.playlist.Playlist()}
+
+ # In both of the following dicts, curve names are
+ # (playlist.name, curve.name) to avoid cross-playlist
+ # collisions. See ._is_curve().
+ self._id_for_name = {} # {name: id}
+ self._name_for_id = {} # {id: name}
+
+ def _is_curve(self, name): # name from ._id_for_name / ._name_for_id
+ """Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.
+ """
+ # Playlist names are strings, Curve names are tuples.
+ # See ._setup_playlists().
+ return not isinstance(name, types.StringTypes)
+
+ def _canonical_id(self, _id):
+ """Return a canonical form of `_id` suitable for accessing `._name_for_id`.
+
+ For some reason, `.GetSelection()`, etc. return items that
+ hash differently than the original `.AppendItem()`-returned
+ IDs. This means that `._name_for_id[self.GetSelection()]`
+ will raise `KeyError`, even if there is an id `X` in
+ `._name_for_id` for which `X == self.GetSelection()` will
+ return `True`. This method "canonicalizes" IDs so that the
+ hashing is consistent.
+ """
+ for c_id in self._name_for_id.keys():
+ if c_id == _id:
+ return c_id
+ raise KeyError(_id)
+
+
+ # Context menu
+
+ def _on_context_menu(self, event):
+ """Launch a popup :class:`Menu` with per-playlist/curve activities.
+ """
+ hit_id,hit_flags = self.HitTest(event.GetPosition())
+ if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:
+ self._hit_id = self._canonical_id(hit_id) # store for callbacks
+ menu = Menu(self._on_delete)
+ self.PopupMenu(menu, event.GetPosition())
+ menu.Destroy()
+
+ # Add
+ # add_* called directly by HookeFrame
+ # _add_* called on every addition
+
+ def add_playlist(self, playlist):
+ """Add a :class:`hooke.playlist.Playlist` to the tree.
+
+ Calls :meth:`_add_playlist` and triggers a callback.
+ """
+ self._add_playlist(playlist)
+ in_callback(self, playlist)
+
+ def _add_playlist(self, playlist):
+ """Add a class:`hooke.playlist.Playlist` to the tree.
+
+ No callback triggered.
+ """
+ if playlist.name not in self._playlists:
+ pass
+ else:
+ raise ValueError('duplicate playlist: %s' % playlist.name)
+ self._playlists[playlist.name] = playlist
+ p_id = self.AppendItem(
+ parent=self._c['root'],
+ text=self._panel._hooke_frame._file_name(playlist.name),
+ image=self.image['playlist'])
+ self._id_for_name[playlist.name] = p_id
+ self._name_for_id[p_id] = playlist.name
+ for curve in playlist:
+ self._add_curve(playlist.name, curve)
+
+ def add_curve(self, playlist_name, curve):
+ """Add a :class:`hooke.curve.Curve` to a curently loaded playlist.
+
+ Calls :meth:`_add_curve` and triggers a callback.
+ """
+ self._add_curve(playlist_name, curve)
+ playlist = self._playlists[playlist_name]
+ in_callback(self, playlist, curve)
+
+ def _add_curve(self, playlist_name, curve):
+ """Add a class:`hooke.curve.Curve` to the tree.
+
+ No callback triggered.
+ """
+ p = self._playlists[playlist_name]
+ if curve not in p:
+ p.append(curve)
+ c_id = self.AppendItem(
+ parent=self._id_for_name[playlist_name],
+ text=self._panel._hooke_frame._file_name(curve.name),
+ image=self.image['curve'])
+ self._id_for_name[(p.name, curve.name)] = c_id
+ self._name_for_id[c_id] = (p.name, curve.name)
+
+ @callback
+ def generate_new_playlist(self):
+ pass # TODO
+
+ def _GetUniquePlaylistName(self, name): # TODO
+ playlist_name = name
+ count = 1
+ while playlist_name in self.playlists:
+ playlist_name = ''.join([name, str(count)])
+ count += 1
+ return playlist_name
+
+ # Delete
+ # delete_* called by _on_delete handler (user click) or HookeFrame
+ # _delete_* called on every deletion
+
+ def _on_delete(self, event):
+ """Handler for :class:`Menu`'s `Delete` button.
+
+ Determines the clicked item and calls the appropriate
+ `.delete_*()` method on it.
+ """
+ #if hasattr(self, '_hit_id'): # called via ._c['menu']
+ _id = self._hit_id
+ del(self._hit_id)
+ name = self._name_for_id[_id]
+ if self._is_curve(name):
+ self.delete_curve(playlist_name=name[0], name=name[1])
+ else:
+ self.delete_playlist(name)
+
+ def delete_playlist(self, name):
+ """Delete a :class:`hooke.playlist.Playlist` by name.
+
+ Called by the :meth:`_on_delete` handler.
+
+ Removes the playlist and its curves from the tree, then calls
+ :meth:`_delete_playlist`.
+ """
+ _id = self._id_for_name[name]
+ self.Delete(_id)
+ playlist = self._playlists[name]
+ self._delete_playlist(playlist)
+ in_callback(self, playlist)
+
+ def _delete_playlist(self, playlist):
+ """Adjust name/id caches for the playlist and its curves.
+
+ Called on *every* playlist deletion.
+ """
+ self._playlists.pop(playlist.name)
+ _id = self._id_for_name.pop(playlist.name)
+ del(self._name_for_id[_id])
+ for curve in playlist:
+ self._delete_curve(playlist, curve)
+ in_callback(self, playlist)
+
+ def delete_curve(self, playlist_name, name):
+ """Delete a :class:`hooke.curve.Curve` by name.
+
+ Called by the :meth:`_on_delete` handler.
+
+ Removes the curve from the tree, then calls
+ :meth:`_delete_curve`.
+ """
+ _id = self._id_for_name[(playlist_name, name)]
+ self.Delete(_id)
+ playlist = self._playlists[playlist_name]
+ curve = None
+ for i,c in enumerate(playlist):
+ if c.name == name:
+ curve = c
+ break
+ self._delete_curve(playlist, curve)
+ in_callback(self, playlist, curve)
+
+ def _delete_curve(self, playlist, curve):
+ """Adjust name/id caches.
+
+ Called on _every_ curve deletion.
+ """
+ _id = self._id_for_name.pop((playlist.name, curve.name))
+ del(self._name_for_id[_id])
+ in_callback(self, playlist, curve)
+
+ # Get selection
+
+ def get_selected_playlist(self):
+ """Return the selected :class:`hooke.playlist.Playlist`.
+ """
+ _id = self.GetSelection()
+ try:
+ _id = self._canonical_id(_id)
+ except KeyError: # no playlist selected
+ return None
+ name = self._name_for_id[_id]
+ if self._is_curve(name):
+ name = name[0]
+ return self._playlists[name]
+
+ def get_selected_curve(self):
+ """Return the selected :class:`hooke.curve.Curve`.
+ """
+ _id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(_id)]
+ if self._is_curve(name):
+ p_name,c_name = name
+ playlist = self._playlists[p_name]
+ c = playlist.current()
+ assert c.name == c_name, '%s != %s' % (c.name, c_name)
+ else:
+ playlist = self._playlists[name]
+ return playlist.current()
+
+ # Set selection (via user interaction with this panel)
+ #
+ # These are hooks for HookeFrame callbacks which will send
+ # the results back via 'get curve' calling 'set_selected_curve'.
+
+ def _on_select(self, event):
+ """Select the clicked-on curve/playlist.
+ """
+ _id = self.GetSelection()
+ name = self._name_for_id[self._canonical_id(_id)]
+ if self._is_curve(name):
+ p_name,c_name = name
+ self._on_set_selected_curve(p_name, c_name)
+ else:
+ self._on_set_selected_playlist(name)
+
+ def _on_set_selected_playlist(self, name):
+ in_callback(self, self._playlists[name])
+
+ def _on_set_selected_curve(self, playlist_name, name):
+ playlist = self._playlists[playlist_name]
+ curve = None
+ for i,c in enumerate(playlist):
+ if c.name == name:
+ curve = c
+ break
+ if curve == None:
+ raise ValueError(name)
+ in_callback(self, playlist, curve)
+
+ # Set selection (from the HookeFrame)
+
+ def set_selected_curve(self, playlist, curve):
+ """Make the curve the playlist's current curve.
+ """
+ self.log.debug('playlist tree expanding %s' % playlist.name)
+ self.Expand(self._id_for_name[playlist.name])
+ self.Unbind(wx.EVT_TREE_SEL_CHANGED)
+ self.log.debug('playlist tree selecting %s' % curve.name)
+ self.SelectItem(self._id_for_name[(playlist.name, curve.name)])
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)
+
+ def update_playlist(self, playlist):
+ """Absorb changed `._index`, etc.
+ """
+ self._playlists[playlist.name] = playlist
+
+
+class Playlist (Panel, wx.Panel):
+ """:class:`wx.Panel` subclass wrapper for :class:`Tree`.
+ """
+ def __init__(self, callbacks=None, **kwargs):
+ # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
+ super(Playlist, self).__init__(
+ name='playlist', callbacks=callbacks, **kwargs)
+ self._c = {
+ 'tree': Tree(
+ parent=self,
+ size=wx.Size(160, 250),
+ style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),
+ }
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self._c['tree'], 1, wx.EXPAND)
+ self.SetSizer(sizer)
+ sizer.Fit(self)
+
+ # Expose all Tree's public curve/playlist methods directly.
+ # Following DRY and the LoD.
+ for attribute_name in dir(self._c['tree']):
+ if (attribute_name.startswith('_')
+ or 'playlist' not in attribute_name
+ or 'curve' not in attribute_name):
+ continue # not an attribute we're interested in
+ attr = getattr(self._c['tree'], attribute_name)
+ if hasattr(attr, '__call__'): # attr is a function / method
+ setattr(self, attribute_name, attr) # expose it
-# Copyright\r
-\r
-"""Plot panel for Hooke.\r
-\r
-Notes\r
------\r
-Originally based on `this example`_.\r
-\r
-.. _this example:\r
- http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html\r
-"""\r
-\r
-import matplotlib\r
-matplotlib.use('WXAgg') # use wxpython with antigrain (agg) rendering\r
-from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
-from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar\r
-from matplotlib.figure import Figure\r
-from matplotlib.ticker import Formatter, ScalarFormatter\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-from ....util.si import ppSI, split_data_label\r
-from . import Panel\r
-\r
-\r
-class HookeFormatter (Formatter):\r
- """:class:`matplotlib.ticker.Formatter` using SI prefixes.\r
- """\r
- def __init__(self, unit='', decimals=2):\r
- self.decimals = decimals\r
- self.unit = unit\r
-\r
- def __call__(self, x, pos=None):\r
- """Return the format for tick val `x` at position `pos`.\r
- """\r
- if x == 0:\r
- return '0'\r
- return ppSI(value=x, unit=self.unit, decimals=self.decimals)\r
-\r
-\r
-class HookeScalarFormatter (ScalarFormatter):\r
- """:class:`matplotlib.ticker.ScalarFormatter` using only multiples\r
- of three in the mantissa.\r
-\r
- A fixed number of decimals can be displayed with the optional\r
- parameter `decimals` . If `decimals` is `None` (default), the number\r
- of decimals is defined from the current ticks.\r
- """\r
- def __init__(self, decimals=None, **kwargs):\r
- # Can't use super() because ScalarFormatter is an old-style class :(.\r
- ScalarFormatter.__init__(self, **kwargs)\r
- self._decimals = decimals\r
-\r
- def _set_orderOfMagnitude(self, *args, **kwargs):\r
- """Sets the order of magnitude.""" \r
- # Can't use super() because ScalarFormatter is an old-style class :(.\r
- ScalarFormatter._set_orderOfMagnitude(self, *args, **kwargs)\r
- self.orderOfMagnitude -= self.orderOfMagnitude % 3\r
-\r
- def _set_format(self, *args, **kwargs):\r
- """Sets the format string to format all ticklabels."""\r
- # Can't use super() because ScalarFormatter is an old-style class :(.\r
- ScalarFormatter._set_format(self, *args, **kwargs)\r
- if self._decimals is None or self._decimals < 0:\r
- locs = (np.asarray(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15\r
- sigfigs = [len(str('%1.8f'% loc).split('.')[1].rstrip('0')) \\r
- for loc in locs]\r
- sigfigs.sort()\r
- decimals = sigfigs[-1]\r
- else:\r
- decimals = self._decimals\r
- self.format = '%1.' + str(decimals) + 'f'\r
- if self._usetex:\r
- self.format = '$%s$' % self.format\r
- elif self._useMathText:\r
- self.format = '$\mathdefault{%s}$' % self.format\r
-\r
-\r
-class PlotPanel (Panel, wx.Panel):\r
- """UI for graphical curve display.\r
- """\r
- def __init__(self, callbacks=None, **kwargs):\r
- self.display_coordinates = False\r
- self.style = 'line'\r
- self._curve = None\r
- self._x_column = None\r
- self._y_column = None\r
- super(PlotPanel, self).__init__(\r
- name='plot', callbacks=callbacks, **kwargs)\r
- self._c = {}\r
- self._c['figure'] = Figure()\r
- self._c['canvas'] = FigureCanvas(\r
- parent=self, id=wx.ID_ANY, figure=self._c['figure'])\r
-\r
- self._set_color(wx.NamedColor('WHITE'))\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
- sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)\r
- self._setup_toolbar(sizer=sizer) # comment out to remove plot toolbar.\r
- self.SetSizer(sizer)\r
- self.Fit()\r
-\r
- self.Bind(wx.EVT_SIZE, self._on_size) \r
- self._c['figure'].canvas.mpl_connect(\r
- 'button_press_event', self._on_click)\r
- self._c['figure'].canvas.mpl_connect(\r
- 'axes_enter_event', self._on_enter_axes)\r
- self._c['figure'].canvas.mpl_connect(\r
- 'axes_leave_event', self._on_leave_axes)\r
- self._c['figure'].canvas.mpl_connect(\r
- 'motion_notify_event', self._on_mouse_move)\r
-\r
- def _setup_toolbar(self, sizer):\r
- self._c['toolbar'] = NavToolbar(self._c['canvas'])\r
- self._c['x column'] = wx.Choice(\r
- parent=self._c['toolbar'], choices=[])\r
- self._c['x column'].SetToolTip(wx.ToolTip('x column'))\r
- self._c['toolbar'].AddControl(self._c['x column'])\r
- self._c['x column'].Bind(wx.EVT_CHOICE, self._on_x_column)\r
- self._c['y column'] = wx.Choice(\r
- parent=self._c['toolbar'], choices=[])\r
- self._c['y column'].SetToolTip(wx.ToolTip('y column'))\r
- self._c['toolbar'].AddControl(self._c['y column'])\r
- self._c['y column'].Bind(wx.EVT_CHOICE, self._on_y_column)\r
-\r
- self._c['toolbar'].Realize() # call after putting items in the toolbar\r
- if wx.Platform == '__WXMAC__':\r
- # Mac platform (OSX 10.3, MacPython) does not seem to cope with\r
- # having a toolbar in a sizer. This work-around gets the buttons\r
- # back, but at the expense of having the toolbar at the top\r
- self.SetToolBar(self._c['toolbar'])\r
- elif wx.Platform == '__WXMSW__':\r
- # On Windows platform, default window size is incorrect, so set\r
- # toolbar width to figure width.\r
- tw, th = toolbar.GetSizeTuple()\r
- fw, fh = self._c['canvas'].GetSizeTuple()\r
- # By adding toolbar in sizer, we are able to put it at the bottom\r
- # of the frame - so appearance is closer to GTK version.\r
- # As noted above, doesn't work for Mac.\r
- self._c['toolbar'].SetSize(wx.Size(fw, th))\r
- sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)\r
- else:\r
- sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)\r
- self._c['toolbar'].update() # update the axes menu on the toolbar\r
-\r
- def _set_color(self, rgbtuple=None):\r
- """Set both figure and canvas colors to `rgbtuple`.\r
- """\r
- if rgbtuple == None:\r
- rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()\r
- col = [c/255.0 for c in rgbtuple]\r
- self._c['figure'].set_facecolor(col)\r
- self._c['figure'].set_edgecolor(col)\r
- self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))\r
-\r
- #def SetStatusText(self, text, field=1):\r
- # self.Parent.Parent.statusbar.SetStatusText(text, field)\r
-\r
- def _on_size(self, event):\r
- event.Skip()\r
- wx.CallAfter(self._resize_canvas)\r
-\r
- def _on_click(self, event):\r
- #self.SetStatusText(str(event.xdata))\r
- #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)\r
- pass\r
-\r
- def _on_enter_axes(self, event):\r
- self.display_coordinates = True\r
-\r
- def _on_leave_axes(self, event):\r
- self.display_coordinates = False\r
- #self.SetStatusText('')\r
-\r
- def _on_mouse_move(self, event):\r
- if 'toolbar' in self._c:\r
- if event.guiEvent.m_shiftDown:\r
- self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)\r
- else:\r
- self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)\r
- if self.display_coordinates:\r
- coordinateString = ''.join(\r
- ['x: ', str(event.xdata), ' y: ', str(event.ydata)])\r
- #TODO: pretty format\r
- #self.SetStatusText(coordinateString)\r
-\r
- def _on_x_column(self, event):\r
- self._x_column = self._c['x column'].GetStringSelection()\r
- self.update()\r
-\r
- def _on_y_column(self, event):\r
- self._y_column = self._c['y column'].GetStringSelection()\r
- self.update()\r
-\r
- def _resize_canvas(self):\r
- w,h = self.GetClientSize()\r
- if 'toolbar' in self._c:\r
- tw,th = self._c['toolbar'].GetSizeTuple()\r
- else:\r
- th = 0\r
- dpi = float(self._c['figure'].get_dpi())\r
- self._c['figure'].set_figwidth(w/dpi)\r
- self._c['figure'].set_figheight((h-th)/dpi)\r
- self._c['canvas'].draw()\r
- self.Refresh()\r
-\r
- def OnPaint(self, event):\r
- print 'painting'\r
- super(PlotPanel, self).OnPaint(event)\r
- self._c['canvas'].draw()\r
-\r
- def set_curve(self, curve, config=None):\r
- self._curve = curve\r
- columns = set()\r
- for data in curve.data:\r
- columns = columns.union(set(data.info['columns']))\r
- self._columns = sorted(columns)\r
- if self._x_column not in self._columns:\r
- self._x_column = self._columns[0]\r
- if self._y_column not in self._columns:\r
- self._y_column = self._columns[-1]\r
- if 'x column' in self._c:\r
- for i in range(self._c['x column'].GetCount()):\r
- self._c['x column'].Delete(0)\r
- self._c['x column'].AppendItems(self._columns)\r
- self._c['x column'].SetStringSelection(self._x_column)\r
- if 'y column' in self._c:\r
- for i in range(self._c['y column'].GetCount()):\r
- self._c['y column'].Delete(0)\r
- self._c['y column'].AppendItems(self._columns)\r
- self._c['y column'].SetStringSelection(self._y_column)\r
- self.update(config=config)\r
-\r
- def update(self, config=None):\r
- if config == None:\r
- config = self._config # use the last cached value\r
- else:\r
- self._config = config # cache for later refreshes\r
- self._c['figure'].clear()\r
- self._c['figure'].suptitle(\r
- self._hooke_frame._file_name(self._curve.name),\r
- fontsize=12)\r
- axes = self._c['figure'].add_subplot(1, 1, 1)\r
-\r
- if config['plot SI format'] == 'True': # TODO: config should convert\r
- d = int(config['plot decimals']) # TODO: config should convert\r
- x_n, x_unit = split_data_label(self._x_column)\r
- y_n, y_unit = split_data_label(self._y_column)\r
- fx = HookeFormatter(decimals=d, unit=x_unit)\r
- axes.xaxis.set_major_formatter(fx)\r
- fy = HookeFormatter(decimals=d, unit=y_unit)\r
- axes.yaxis.set_major_formatter(fy)\r
- axes.set_xlabel(x_n)\r
- axes.set_ylabel(y_n)\r
- else:\r
- axes.set_xlabel(self._x_column)\r
- axes.set_ylabel(self._y_column)\r
-\r
- self._c['figure'].hold(True)\r
- for i,data in enumerate(self._curve.data):\r
- try:\r
- x_col = data.info['columns'].index(self._x_column)\r
- y_col = data.info['columns'].index(self._y_column)\r
- except ValueError:\r
- continue # data is missing a required column\r
- axes.plot(data[:,x_col], data[:,y_col],\r
- '.',\r
- label=data.info['name'])\r
- if config['plot legend'] == 'True': # HACK: config should convert\r
- axes.legend(loc='best')\r
- self._c['canvas'].draw()\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# Rolf Schmidt <rschmidt@alcor.concordia.ca>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Plot panel for Hooke.
+
+Notes
+-----
+Originally based on `this example`_.
+
+.. _this example:
+ http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_wx2.html
+"""
+
+import matplotlib
+matplotlib.use('WXAgg') # use wxpython with antigrain (agg) rendering
+from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
+from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar
+from matplotlib.figure import Figure
+from matplotlib.ticker import Formatter, ScalarFormatter
+import wx
+
+from ....util.callback import callback, in_callback
+from ....util.si import ppSI, split_data_label
+from . import Panel
+
+
+class HookeFormatter (Formatter):
+ """:class:`matplotlib.ticker.Formatter` using SI prefixes.
+ """
+ def __init__(self, unit='', decimals=2):
+ self.decimals = decimals
+ self.unit = unit
+
+ def __call__(self, x, pos=None):
+ """Return the format for tick val `x` at position `pos`.
+ """
+ if x == 0:
+ return '0'
+ return ppSI(value=x, unit=self.unit, decimals=self.decimals)
+
+
+class HookeScalarFormatter (ScalarFormatter):
+ """:class:`matplotlib.ticker.ScalarFormatter` using only multiples
+ of three in the mantissa.
+
+ A fixed number of decimals can be displayed with the optional
+ parameter `decimals` . If `decimals` is `None` (default), the number
+ of decimals is defined from the current ticks.
+ """
+ def __init__(self, decimals=None, **kwargs):
+ # Can't use super() because ScalarFormatter is an old-style class :(.
+ ScalarFormatter.__init__(self, **kwargs)
+ self._decimals = decimals
+
+ def _set_orderOfMagnitude(self, *args, **kwargs):
+ """Sets the order of magnitude."""
+ # Can't use super() because ScalarFormatter is an old-style class :(.
+ ScalarFormatter._set_orderOfMagnitude(self, *args, **kwargs)
+ self.orderOfMagnitude -= self.orderOfMagnitude % 3
+
+ def _set_format(self, *args, **kwargs):
+ """Sets the format string to format all ticklabels."""
+ # Can't use super() because ScalarFormatter is an old-style class :(.
+ ScalarFormatter._set_format(self, *args, **kwargs)
+ if self._decimals is None or self._decimals < 0:
+ locs = (np.asarray(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15
+ sigfigs = [len(str('%1.8f'% loc).split('.')[1].rstrip('0')) \
+ for loc in locs]
+ sigfigs.sort()
+ decimals = sigfigs[-1]
+ else:
+ decimals = self._decimals
+ self.format = '%1.' + str(decimals) + 'f'
+ if self._usetex:
+ self.format = '$%s$' % self.format
+ elif self._useMathText:
+ self.format = '$\mathdefault{%s}$' % self.format
+
+
+class PlotPanel (Panel, wx.Panel):
+ """UI for graphical curve display.
+ """
+ def __init__(self, callbacks=None, **kwargs):
+ self.display_coordinates = False
+ self.style = 'line'
+ self._curve = None
+ self._x_column = None
+ self._y_column = None
+ super(PlotPanel, self).__init__(
+ name='plot', callbacks=callbacks, **kwargs)
+ self._c = {}
+ self._c['figure'] = Figure()
+ self._c['canvas'] = FigureCanvas(
+ parent=self, id=wx.ID_ANY, figure=self._c['figure'])
+
+ self._set_color(wx.NamedColor('WHITE'))
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self._c['canvas'], 1, wx.LEFT | wx.TOP | wx.GROW)
+ self._setup_toolbar(sizer=sizer) # comment out to remove plot toolbar.
+ self.SetSizer(sizer)
+ self.Fit()
+
+ self.Bind(wx.EVT_SIZE, self._on_size)
+ self._c['figure'].canvas.mpl_connect(
+ 'button_press_event', self._on_click)
+ self._c['figure'].canvas.mpl_connect(
+ 'axes_enter_event', self._on_enter_axes)
+ self._c['figure'].canvas.mpl_connect(
+ 'axes_leave_event', self._on_leave_axes)
+ self._c['figure'].canvas.mpl_connect(
+ 'motion_notify_event', self._on_mouse_move)
+
+ def _setup_toolbar(self, sizer):
+ self._c['toolbar'] = NavToolbar(self._c['canvas'])
+ self._c['x column'] = wx.Choice(
+ parent=self._c['toolbar'], choices=[])
+ self._c['x column'].SetToolTip(wx.ToolTip('x column'))
+ self._c['toolbar'].AddControl(self._c['x column'])
+ self._c['x column'].Bind(wx.EVT_CHOICE, self._on_x_column)
+ self._c['y column'] = wx.Choice(
+ parent=self._c['toolbar'], choices=[])
+ self._c['y column'].SetToolTip(wx.ToolTip('y column'))
+ self._c['toolbar'].AddControl(self._c['y column'])
+ self._c['y column'].Bind(wx.EVT_CHOICE, self._on_y_column)
+
+ self._c['toolbar'].Realize() # call after putting items in the toolbar
+ if wx.Platform == '__WXMAC__':
+ # Mac platform (OSX 10.3, MacPython) does not seem to cope with
+ # having a toolbar in a sizer. This work-around gets the buttons
+ # back, but at the expense of having the toolbar at the top
+ self.SetToolBar(self._c['toolbar'])
+ elif wx.Platform == '__WXMSW__':
+ # On Windows platform, default window size is incorrect, so set
+ # toolbar width to figure width.
+ tw, th = toolbar.GetSizeTuple()
+ fw, fh = self._c['canvas'].GetSizeTuple()
+ # By adding toolbar in sizer, we are able to put it at the bottom
+ # of the frame - so appearance is closer to GTK version.
+ # As noted above, doesn't work for Mac.
+ self._c['toolbar'].SetSize(wx.Size(fw, th))
+ sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)
+ else:
+ sizer.Add(self._c['toolbar'], 0 , wx.LEFT | wx.EXPAND)
+ self._c['toolbar'].update() # update the axes menu on the toolbar
+
+ def _set_color(self, rgbtuple=None):
+ """Set both figure and canvas colors to `rgbtuple`.
+ """
+ if rgbtuple == None:
+ rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
+ col = [c/255.0 for c in rgbtuple]
+ self._c['figure'].set_facecolor(col)
+ self._c['figure'].set_edgecolor(col)
+ self._c['canvas'].SetBackgroundColour(wx.Colour(*rgbtuple))
+
+ #def SetStatusText(self, text, field=1):
+ # self.Parent.Parent.statusbar.SetStatusText(text, field)
+
+ def _on_size(self, event):
+ event.Skip()
+ wx.CallAfter(self._resize_canvas)
+
+ def _on_click(self, event):
+ #self.SetStatusText(str(event.xdata))
+ #print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(event.button, event.x, event.y, event.xdata, event.ydata)
+ pass
+
+ def _on_enter_axes(self, event):
+ self.display_coordinates = True
+
+ def _on_leave_axes(self, event):
+ self.display_coordinates = False
+ #self.SetStatusText('')
+
+ def _on_mouse_move(self, event):
+ if 'toolbar' in self._c:
+ if event.guiEvent.m_shiftDown:
+ self._c['toolbar'].set_cursor(wx.CURSOR_RIGHT_ARROW)
+ else:
+ self._c['toolbar'].set_cursor(wx.CURSOR_ARROW)
+ if self.display_coordinates:
+ coordinateString = ''.join(
+ ['x: ', str(event.xdata), ' y: ', str(event.ydata)])
+ #TODO: pretty format
+ #self.SetStatusText(coordinateString)
+
+ def _on_x_column(self, event):
+ self._x_column = self._c['x column'].GetStringSelection()
+ self.update()
+
+ def _on_y_column(self, event):
+ self._y_column = self._c['y column'].GetStringSelection()
+ self.update()
+
+ def _resize_canvas(self):
+ w,h = self.GetClientSize()
+ if 'toolbar' in self._c:
+ tw,th = self._c['toolbar'].GetSizeTuple()
+ else:
+ th = 0
+ dpi = float(self._c['figure'].get_dpi())
+ self._c['figure'].set_figwidth(w/dpi)
+ self._c['figure'].set_figheight((h-th)/dpi)
+ self._c['canvas'].draw()
+ self.Refresh()
+
+ def OnPaint(self, event):
+ print 'painting'
+ super(PlotPanel, self).OnPaint(event)
+ self._c['canvas'].draw()
+
+ def set_curve(self, curve, config=None):
+ self._curve = curve
+ columns = set()
+ for data in curve.data:
+ columns = columns.union(set(data.info['columns']))
+ self._columns = sorted(columns)
+ if self._x_column not in self._columns:
+ self._x_column = self._columns[0]
+ if self._y_column not in self._columns:
+ self._y_column = self._columns[-1]
+ if 'x column' in self._c:
+ for i in range(self._c['x column'].GetCount()):
+ self._c['x column'].Delete(0)
+ self._c['x column'].AppendItems(self._columns)
+ self._c['x column'].SetStringSelection(self._x_column)
+ if 'y column' in self._c:
+ for i in range(self._c['y column'].GetCount()):
+ self._c['y column'].Delete(0)
+ self._c['y column'].AppendItems(self._columns)
+ self._c['y column'].SetStringSelection(self._y_column)
+ self.update(config=config)
+
+ def update(self, config=None):
+ if config == None:
+ config = self._config # use the last cached value
+ else:
+ self._config = config # cache for later refreshes
+ self._c['figure'].clear()
+ self._c['figure'].suptitle(
+ self._hooke_frame._file_name(self._curve.name),
+ fontsize=12)
+ axes = self._c['figure'].add_subplot(1, 1, 1)
+
+ if config['plot SI format'] == 'True': # TODO: config should convert
+ d = int(config['plot decimals']) # TODO: config should convert
+ x_n, x_unit = split_data_label(self._x_column)
+ y_n, y_unit = split_data_label(self._y_column)
+ fx = HookeFormatter(decimals=d, unit=x_unit)
+ axes.xaxis.set_major_formatter(fx)
+ fy = HookeFormatter(decimals=d, unit=y_unit)
+ axes.yaxis.set_major_formatter(fy)
+ axes.set_xlabel(x_n)
+ axes.set_ylabel(y_n)
+ else:
+ axes.set_xlabel(self._x_column)
+ axes.set_ylabel(self._y_column)
+
+ self._c['figure'].hold(True)
+ for i,data in enumerate(self._curve.data):
+ try:
+ x_col = data.info['columns'].index(self._x_column)
+ y_col = data.info['columns'].index(self._y_column)
+ except ValueError:
+ continue # data is missing a required column
+ axes.plot(data[:,x_col], data[:,y_col],
+ '.',
+ label=data.info['name'])
+ if config['plot legend'] == 'True': # HACK: config should convert
+ axes.legend(loc='best')
+ self._c['canvas'].draw()
-# Copyright\r
-\r
-"""Property editor panel for Hooke.\r
-"""\r
-\r
-import sys\r
-import os.path\r
-\r
-import wx\r
-import wx.propgrid as wxpg\r
-\r
-# There are many comments and code fragments in here from the demo app.\r
-# They should come in handy to expand the functionality in the future.\r
-\r
-class Display (object):\r
- property_descriptor = []\r
- def __init__(self):\r
- pass\r
-\r
-class ValueObject (object):\r
- def __init__(self):\r
- pass\r
-\r
-\r
-class IntProperty2 (wxpg.PyProperty):\r
- """This is a simple re-implementation of wxIntProperty.\r
- """\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):\r
- wxpg.PyProperty.__init__(self, label, name)\r
- self.SetValue(value)\r
-\r
- def GetClassName(self):\r
- return "IntProperty2"\r
-\r
- def GetEditor(self):\r
- return "TextCtrl"\r
-\r
- def GetValueAsString(self, flags):\r
- return str(self.GetValue())\r
-\r
- def PyStringToValue(self, s, flags):\r
- try:\r
- v = int(s)\r
- if self.GetValue() != v:\r
- return v\r
- except TypeError:\r
- if flags & wxpg.PG_REPORT_ERROR:\r
- wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")\r
- return False\r
-\r
- def PyIntToValue(self, v, flags):\r
- if (self.GetValue() != v):\r
- return v\r
-\r
-\r
-class PyFilesProperty(wxpg.PyArrayStringProperty):\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):\r
- wxpg.PyArrayStringProperty.__init__(self, label, name, value)\r
- self.SetValue(value)\r
-\r
- def OnSetValue(self, v):\r
- self.value = v\r
- self.display = ', '.join(self.value)\r
-\r
- def GetValueAsString(self, argFlags):\r
- return self.display\r
-\r
- def PyStringToValue(self, s, flags):\r
- return [a.strip() for a in s.split(',')]\r
-\r
- def OnEvent(self, propgrid, ctrl, event):\r
- if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:\r
- # Show dialog to select a string, call DoSetValue and\r
- # return True, if value changed.\r
- return True\r
-\r
- return False\r
-\r
-\r
-class PyObjectPropertyValue:\r
- """\\r
- Value type of our sample PyObjectProperty. We keep a simple dash-delimited\r
- list of string given as argument to constructor.\r
- """\r
- def __init__(self, s=None):\r
- try:\r
- self.ls = [a.strip() for a in s.split('-')]\r
- except:\r
- self.ls = []\r
-\r
- def __repr__(self):\r
- return ' - '.join(self.ls)\r
-\r
-\r
-class PyObjectProperty(wxpg.PyProperty):\r
- """\\r
- Another simple example. This time our value is a PyObject (NOTE: we can't\r
- return an arbitrary python object in DoGetValue. It cannot be a simple\r
- type such as int, bool, double, or string, nor an array or wxObject based.\r
- Dictionary, None, or any user-specified Python object is allowed).\r
- """\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):\r
- wxpg.PyProperty.__init__(self, label, name)\r
- self.SetValue(value)\r
-\r
- def GetClassName(self):\r
- return self.__class__.__name__\r
-\r
- def GetEditor(self):\r
- return "TextCtrl"\r
-\r
- def GetValueAsString(self, flags):\r
- return repr(self.GetValue())\r
-\r
- def PyStringToValue(self, s, flags):\r
- return PyObjectPropertyValue(s)\r
-\r
-\r
-class ShapeProperty(wxpg.PyEnumProperty):\r
- """\\r
- Demonstrates use of OnCustomPaint method.\r
- """\r
- def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):\r
- wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)\r
-\r
- def OnMeasureImage(self, index):\r
- return wxpg.DEFAULT_IMAGE_SIZE\r
-\r
- def OnCustomPaint(self, dc, rect, paint_data):\r
- """\\r
- paint_data.m_choiceItem is -1 if we are painting the control,\r
- in which case we need to get the drawn item using DoGetValue.\r
- """\r
- item = paint_data.m_choiceItem\r
- if item == -1:\r
- item = self.DoGetValue()\r
-\r
- dc.SetPen(wx.Pen(wx.BLACK))\r
- dc.SetBrush(wx.Brush(wx.BLACK))\r
-\r
- if item == 0:\r
- dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)\r
- elif item == 1:\r
- half_width = rect.width / 2\r
- dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)\r
- elif item == 2:\r
- dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)\r
-\r
-\r
-class LargeImagePickerCtrl(wx.Window):\r
- """\\r
- Control created and used by LargeImageEditor.\r
- """\r
- def __init__(self):\r
- pre = wx.PreWindow()\r
- self.PostCreate(pre)\r
-\r
- def Create(self, parent, id_, pos, size, style = 0):\r
- wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)\r
- img_spc = size[1]\r
- self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)\r
- self.SetBackgroundColour(wx.WHITE)\r
- self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)\r
- self.property = None\r
- self.bmp = None\r
- self.Bind(wx.EVT_PAINT, self.OnPaint)\r
-\r
- def OnPaint(self, event):\r
- dc = wx.BufferedPaintDC(self)\r
-\r
- whiteBrush = wx.Brush(wx.WHITE)\r
- dc.SetBackground(whiteBrush)\r
- dc.Clear()\r
-\r
- bmp = self.bmp\r
- if bmp:\r
- dc.DrawBitmap(bmp, 2, 2)\r
- else:\r
- dc.SetPen(wx.Pen(wx.BLACK))\r
- dc.SetBrush(whiteBrush)\r
- dc.DrawRectangle(2, 2, 64, 64)\r
-\r
- def RefreshThumbnail(self):\r
- """\\r
- We use here very simple image scaling code.\r
- """\r
- if not self.property:\r
- self.bmp = None\r
- return\r
-\r
- path = self.property.DoGetValue()\r
-\r
- if not os.path.isfile(path):\r
- self.bmp = None\r
- return\r
-\r
- image = wx.Image(path)\r
- image.Rescale(64, 64)\r
- self.bmp = wx.BitmapFromImage(image)\r
-\r
- def SetProperty(self, property):\r
- self.property = property\r
- self.tc.SetValue(property.GetDisplayedString())\r
- self.RefreshThumbnail()\r
-\r
- def SetValue(self, s):\r
- self.RefreshThumbnail()\r
- self.tc.SetValue(s)\r
-\r
- def GetLastPosition(self):\r
- return self.tc.GetLastPosition()\r
-\r
-\r
-class LargeImageEditor(wxpg.PyEditor):\r
- """\\r
- Double-height text-editor with image in front.\r
- """\r
- def __init__(self):\r
- wxpg.PyEditor.__init__(self)\r
-\r
- def CreateControls(self, propgrid, property, pos, sz):\r
- try:\r
- h = 64 + 6\r
- x = propgrid.GetSplitterPosition()\r
- x2 = propgrid.GetClientSize().x\r
- bw = propgrid.GetRowHeight()\r
- lipc = LargeImagePickerCtrl()\r
- if sys.platform == 'win32':\r
- lipc.Hide()\r
- lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))\r
- lipc.SetProperty(property)\r
- # Hmmm.. how to have two-stage creation without subclassing?\r
- #btn = wx.PreButton()\r
- #pre = wx.PreWindow()\r
- #self.PostCreate(pre)\r
- #if sys.platform == 'win32':\r
- # btn.Hide()\r
- #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
- btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)\r
- return (lipc, btn)\r
- except:\r
- import traceback\r
- print traceback.print_exc()\r
-\r
- def UpdateControl(self, property, ctrl):\r
- ctrl.SetValue(property.GetDisplayedString())\r
-\r
- def DrawValue(self, dc, property, rect):\r
- if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
- dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );\r
-\r
- def OnEvent(self, propgrid, ctrl, event):\r
- if not ctrl:\r
- return False\r
-\r
- evtType = event.GetEventType()\r
-\r
- if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:\r
- if propgrid.IsEditorsValueModified():\r
- return True\r
-\r
- elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:\r
- if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \\r
- ctrl.GetLastPosition() > 0:\r
-\r
- # We must check this since an 'empty' text event\r
- # may be triggered when creating the property.\r
- PG_FL_IN_SELECT_PROPERTY = 0x00100000\r
- if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):\r
- event.Skip();\r
- event.SetId(propgrid.GetId());\r
-\r
- propgrid.EditorsValueWasModified();\r
-\r
- return False\r
-\r
-\r
- def CopyValueFromControl(self, property, ctrl):\r
- tc = ctrl.tc\r
- res = property.SetValueFromString(tc.GetValue(),0)\r
- # Changing unspecified always causes event (returning\r
- # true here should be enough to trigger it).\r
- if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):\r
- res = True\r
-\r
- return res\r
-\r
- def SetValueToUnspecified(self, ctrl):\r
- ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));\r
-\r
- def SetControlStringValue(self, ctrl, txt):\r
- ctrl.SetValue(txt)\r
-\r
- def OnFocus(self, property, ctrl):\r
- ctrl.tc.SetSelection(-1,-1)\r
- ctrl.tc.SetFocus()\r
-\r
-\r
-class PropertyEditor(wx.Panel):\r
-\r
- def __init__(self, parent):\r
- # Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
- wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))\r
-\r
- sizer = wx.BoxSizer(wx.VERTICAL)\r
-\r
- self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)\r
-\r
- # Show help as tooltips\r
- self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)\r
-\r
- #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)\r
- #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)\r
- #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)\r
-\r
- # Needed by custom image editor\r
- wx.InitAllImageHandlers()\r
-\r
- #\r
- # Let's create a simple custom editor\r
- #\r
- # NOTE: Editor must be registered *before* adding a property that uses it.\r
- self.pg.RegisterEditor(LargeImageEditor)\r
-\r
- '''\r
- #\r
- # Add properties\r
- #\r
-\r
- pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )\r
- pg.Append( wxpg.StringProperty("String",value="Some Text") )\r
- pg.Append( wxpg.IntProperty("Int",value=100) )\r
- pg.Append( wxpg.FloatProperty("Float",value=100.0) )\r
- pg.Append( wxpg.BoolProperty("Bool",value=True) )\r
- pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )\r
- pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)\r
-\r
- pg.Append( wxpg.PropertyCategory("2 - More Properties") )\r
- pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )\r
- pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )\r
- pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )\r
- pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )\r
-\r
- pg.Append( wxpg.EnumProperty("Enum","Enum",\r
- ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],\r
- [10,11,12],0) )\r
- pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )\r
-\r
- pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )\r
- pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )\r
- pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )\r
- pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )\r
- pg.Append( wxpg.SystemColourProperty("SystemColour") )\r
- pg.Append( wxpg.ImageFileProperty("ImageFile") )\r
- pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )\r
-\r
- pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )\r
- pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )\r
- pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )\r
- pg.Append( wxpg.FontDataProperty("FontData") )\r
- pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )\r
- pg.SetPropertyEditor("IntWithSpin","SpinCtrl")\r
- pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )\r
- pg.SetPropertyHelpString( "String", "String Property help string!" )\r
- pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )\r
-\r
- pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )\r
- pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )\r
- pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )\r
-\r
- pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )\r
- pg.Append( IntProperty2("IntProperty2", value=1024) )\r
-\r
- pg.Append( ShapeProperty("ShapeProperty", value=0) )\r
- pg.Append( PyObjectProperty("PyObjectProperty") )\r
-\r
- pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )\r
- pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")\r
-\r
-\r
- pg.SetPropertyClientData( "Point", 1234 )\r
- if pg.GetPropertyClientData( "Point" ) != 1234:\r
- raise ValueError("Set/GetPropertyClientData() failed")\r
-\r
- # Test setting unicode string\r
- pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")\r
-\r
- #\r
- # Test some code that *should* fail (but not crash)\r
- #try:\r
- #a_ = pg.GetPropertyValue( "NotARealProperty" )\r
- #pg.EnableProperty( "NotAtAllRealProperty", False )\r
- #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )\r
- #except:\r
- #pass\r
- #raise\r
-\r
- '''\r
- sizer.Add(self.pg, 1, wx.EXPAND)\r
- self.SetSizer(sizer)\r
- sizer.SetSizeHints(self)\r
-\r
- self.SelectedTreeItem = None\r
-\r
- def GetPropertyValues(self):\r
- return self.pg.GetPropertyValues()\r
-\r
- def Initialize(self, properties):\r
- pg = self.pg\r
- pg.Clear()\r
-\r
- if properties:\r
- for element in properties:\r
- if element[1]['type'] == 'arraystring':\r
- elements = element[1]['elements']\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- #retrieve individual strings\r
- property_value = split(property_value, ' ')\r
- #remove " delimiters\r
- values = [value.strip('"') for value in property_value]\r
- pg.Append(wxpg.ArrayStringProperty(element[0], value=values))\r
-\r
- if element[1]['type'] == 'boolean':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_bool('value')\r
- else:\r
- property_value = element[1].as_bool('default')\r
- property_control = wxpg.BoolProperty(element[0], value=property_value)\r
- pg.Append(property_control)\r
- pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)\r
-\r
- #if element[0] == 'category':\r
- #pg.Append(wxpg.PropertyCategory(element[1]))\r
-\r
- if element[1]['type'] == 'color':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- property_value = eval(property_value)\r
- pg.Append(wxpg.ColourProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'enum':\r
- elements = element[1]['elements']\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))\r
-\r
- if element[1]['type'] == 'filename':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.FileProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'float':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_float('value')\r
- else:\r
- property_value = element[1].as_float('default')\r
- property_control = wxpg.FloatProperty(element[0], value=property_value)\r
- pg.Append(property_control)\r
-\r
- if element[1]['type'] == 'folder':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.DirProperty(element[0], value=property_value))\r
-\r
- if element[1]['type'] == 'integer':\r
- if 'value' in element[1]:\r
- property_value = element[1].as_int('value')\r
- else:\r
- property_value = element[1].as_int('default')\r
- property_control = wxpg.IntProperty(element[0], value=property_value)\r
- if 'maximum' in element[1]:\r
- property_control.SetAttribute('Max', element[1].as_int('maximum'))\r
- if 'minimum' in element[1]:\r
- property_control.SetAttribute('Min', element[1].as_int('minimum'))\r
- property_control.SetAttribute('Wrap', True)\r
- pg.Append(property_control)\r
- pg.SetPropertyEditor(element[0], 'SpinCtrl')\r
-\r
- if element[1]['type'] == 'string':\r
- if 'value' in element[1]:\r
- property_value = element[1]['value']\r
- else:\r
- property_value = element[1]['default']\r
- pg.Append(wxpg.StringProperty(element[0], value=property_value))\r
-\r
- pg.Refresh()\r
-\r
- def OnReserved(self, event):\r
- pass\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Property editor panel for Hooke.
+"""
+
+import sys
+import os.path
+
+import wx
+import wx.propgrid as wxpg
+
+# There are many comments and code fragments in here from the demo app.
+# They should come in handy to expand the functionality in the future.
+
+class Display (object):
+ property_descriptor = []
+ def __init__(self):
+ pass
+
+class ValueObject (object):
+ def __init__(self):
+ pass
+
+
+class IntProperty2 (wxpg.PyProperty):
+ """This is a simple re-implementation of wxIntProperty.
+ """
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0):
+ wxpg.PyProperty.__init__(self, label, name)
+ self.SetValue(value)
+
+ def GetClassName(self):
+ return "IntProperty2"
+
+ def GetEditor(self):
+ return "TextCtrl"
+
+ def GetValueAsString(self, flags):
+ return str(self.GetValue())
+
+ def PyStringToValue(self, s, flags):
+ try:
+ v = int(s)
+ if self.GetValue() != v:
+ return v
+ except TypeError:
+ if flags & wxpg.PG_REPORT_ERROR:
+ wx.MessageBox("Cannot convert '%s' into a number."%s, "Error")
+ return False
+
+ def PyIntToValue(self, v, flags):
+ if (self.GetValue() != v):
+ return v
+
+
+class PyFilesProperty(wxpg.PyArrayStringProperty):
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]):
+ wxpg.PyArrayStringProperty.__init__(self, label, name, value)
+ self.SetValue(value)
+
+ def OnSetValue(self, v):
+ self.value = v
+ self.display = ', '.join(self.value)
+
+ def GetValueAsString(self, argFlags):
+ return self.display
+
+ def PyStringToValue(self, s, flags):
+ return [a.strip() for a in s.split(',')]
+
+ def OnEvent(self, propgrid, ctrl, event):
+ if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED:
+ # Show dialog to select a string, call DoSetValue and
+ # return True, if value changed.
+ return True
+
+ return False
+
+
+class PyObjectPropertyValue:
+ """\
+ Value type of our sample PyObjectProperty. We keep a simple dash-delimited
+ list of string given as argument to constructor.
+ """
+ def __init__(self, s=None):
+ try:
+ self.ls = [a.strip() for a in s.split('-')]
+ except:
+ self.ls = []
+
+ def __repr__(self):
+ return ' - '.join(self.ls)
+
+
+class PyObjectProperty(wxpg.PyProperty):
+ """\
+ Another simple example. This time our value is a PyObject (NOTE: we can't
+ return an arbitrary python object in DoGetValue. It cannot be a simple
+ type such as int, bool, double, or string, nor an array or wxObject based.
+ Dictionary, None, or any user-specified Python object is allowed).
+ """
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None):
+ wxpg.PyProperty.__init__(self, label, name)
+ self.SetValue(value)
+
+ def GetClassName(self):
+ return self.__class__.__name__
+
+ def GetEditor(self):
+ return "TextCtrl"
+
+ def GetValueAsString(self, flags):
+ return repr(self.GetValue())
+
+ def PyStringToValue(self, s, flags):
+ return PyObjectPropertyValue(s)
+
+
+class ShapeProperty(wxpg.PyEnumProperty):
+ """\
+ Demonstrates use of OnCustomPaint method.
+ """
+ def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1):
+ wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value)
+
+ def OnMeasureImage(self, index):
+ return wxpg.DEFAULT_IMAGE_SIZE
+
+ def OnCustomPaint(self, dc, rect, paint_data):
+ """\
+ paint_data.m_choiceItem is -1 if we are painting the control,
+ in which case we need to get the drawn item using DoGetValue.
+ """
+ item = paint_data.m_choiceItem
+ if item == -1:
+ item = self.DoGetValue()
+
+ dc.SetPen(wx.Pen(wx.BLACK))
+ dc.SetBrush(wx.Brush(wx.BLACK))
+
+ if item == 0:
+ dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height)
+ elif item == 1:
+ half_width = rect.width / 2
+ dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3)
+ elif item == 2:
+ dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
+
+
+class LargeImagePickerCtrl(wx.Window):
+ """\
+ Control created and used by LargeImageEditor.
+ """
+ def __init__(self):
+ pre = wx.PreWindow()
+ self.PostCreate(pre)
+
+ def Create(self, parent, id_, pos, size, style = 0):
+ wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE)
+ img_spc = size[1]
+ self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE)
+ self.SetBackgroundColour(wx.WHITE)
+ self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+ self.property = None
+ self.bmp = None
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+
+ def OnPaint(self, event):
+ dc = wx.BufferedPaintDC(self)
+
+ whiteBrush = wx.Brush(wx.WHITE)
+ dc.SetBackground(whiteBrush)
+ dc.Clear()
+
+ bmp = self.bmp
+ if bmp:
+ dc.DrawBitmap(bmp, 2, 2)
+ else:
+ dc.SetPen(wx.Pen(wx.BLACK))
+ dc.SetBrush(whiteBrush)
+ dc.DrawRectangle(2, 2, 64, 64)
+
+ def RefreshThumbnail(self):
+ """\
+ We use here very simple image scaling code.
+ """
+ if not self.property:
+ self.bmp = None
+ return
+
+ path = self.property.DoGetValue()
+
+ if not os.path.isfile(path):
+ self.bmp = None
+ return
+
+ image = wx.Image(path)
+ image.Rescale(64, 64)
+ self.bmp = wx.BitmapFromImage(image)
+
+ def SetProperty(self, property):
+ self.property = property
+ self.tc.SetValue(property.GetDisplayedString())
+ self.RefreshThumbnail()
+
+ def SetValue(self, s):
+ self.RefreshThumbnail()
+ self.tc.SetValue(s)
+
+ def GetLastPosition(self):
+ return self.tc.GetLastPosition()
+
+
+class LargeImageEditor(wxpg.PyEditor):
+ """\
+ Double-height text-editor with image in front.
+ """
+ def __init__(self):
+ wxpg.PyEditor.__init__(self)
+
+ def CreateControls(self, propgrid, property, pos, sz):
+ try:
+ h = 64 + 6
+ x = propgrid.GetSplitterPosition()
+ x2 = propgrid.GetClientSize().x
+ bw = propgrid.GetRowHeight()
+ lipc = LargeImagePickerCtrl()
+ if sys.platform == 'win32':
+ lipc.Hide()
+ lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h))
+ lipc.SetProperty(property)
+ # Hmmm.. how to have two-stage creation without subclassing?
+ #btn = wx.PreButton()
+ #pre = wx.PreWindow()
+ #self.PostCreate(pre)
+ #if sys.platform == 'win32':
+ # btn.Hide()
+ #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)
+ btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS)
+ return (lipc, btn)
+ except:
+ import traceback
+ print traceback.print_exc()
+
+ def UpdateControl(self, property, ctrl):
+ ctrl.SetValue(property.GetDisplayedString())
+
+ def DrawValue(self, dc, property, rect):
+ if not (property.GetFlags() & wxpg.PG_PROP_AUTO_UNSPECIFIED):
+ dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y );
+
+ def OnEvent(self, propgrid, ctrl, event):
+ if not ctrl:
+ return False
+
+ evtType = event.GetEventType()
+
+ if evtType == wx.wxEVT_COMMAND_TEXT_ENTER:
+ if propgrid.IsEditorsValueModified():
+ return True
+
+ elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED:
+ if not property.HasFlag(wxpg.PG_PROP_AUTO_UNSPECIFIED) or not ctrl or \
+ ctrl.GetLastPosition() > 0:
+
+ # We must check this since an 'empty' text event
+ # may be triggered when creating the property.
+ PG_FL_IN_SELECT_PROPERTY = 0x00100000
+ if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY):
+ event.Skip();
+ event.SetId(propgrid.GetId());
+
+ propgrid.EditorsValueWasModified();
+
+ return False
+
+
+ def CopyValueFromControl(self, property, ctrl):
+ tc = ctrl.tc
+ res = property.SetValueFromString(tc.GetValue(),0)
+ # Changing unspecified always causes event (returning
+ # true here should be enough to trigger it).
+ if not res and property.IsFlagSet(wxpg.PG_PROP_AUTO_UNSPECIFIED):
+ res = True
+
+ return res
+
+ def SetValueToUnspecified(self, ctrl):
+ ctrl.tc.Remove(0,len(ctrl.tc.GetValue()));
+
+ def SetControlStringValue(self, ctrl, txt):
+ ctrl.SetValue(txt)
+
+ def OnFocus(self, property, ctrl):
+ ctrl.tc.SetSelection(-1,-1)
+ ctrl.tc.SetFocus()
+
+
+class PropertyEditor(wx.Panel):
+
+ def __init__(self, parent):
+ # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
+ wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200))
+
+ sizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT)
+
+ # Show help as tooltips
+ self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
+
+ #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange)
+ #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect)
+ #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick)
+
+ # Needed by custom image editor
+ wx.InitAllImageHandlers()
+
+ #
+ # Let's create a simple custom editor
+ #
+ # NOTE: Editor must be registered *before* adding a property that uses it.
+ self.pg.RegisterEditor(LargeImageEditor)
+
+ '''
+ #
+ # Add properties
+ #
+
+ pg.Append( wxpg.PropertyCategory("1 - Basic Properties") )
+ pg.Append( wxpg.StringProperty("String",value="Some Text") )
+ pg.Append( wxpg.IntProperty("Int",value=100) )
+ pg.Append( wxpg.FloatProperty("Float",value=100.0) )
+ pg.Append( wxpg.BoolProperty("Bool",value=True) )
+ pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) )
+ pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True)
+
+ pg.Append( wxpg.PropertyCategory("2 - More Properties") )
+ pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") )
+ pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") )
+ pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") )
+ pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) )
+
+ pg.Append( wxpg.EnumProperty("Enum","Enum",
+ ['wxPython Rules','wxPython Rocks','wxPython Is The Best'],
+ [10,11,12],0) )
+ pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") )
+
+ pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") )
+ pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) )
+ pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) )
+ pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) )
+ pg.Append( wxpg.SystemColourProperty("SystemColour") )
+ pg.Append( wxpg.ImageFileProperty("ImageFile") )
+ pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) )
+
+ pg.Append( wxpg.PropertyCategory("4 - Additional Properties") )
+ pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) )
+ pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) )
+ pg.Append( wxpg.FontDataProperty("FontData") )
+ pg.Append( wxpg.IntProperty("IntWithSpin",value=256) )
+ pg.SetPropertyEditor("IntWithSpin","SpinCtrl")
+ pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) )
+ pg.SetPropertyHelpString( "String", "String Property help string!" )
+ pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" )
+
+ pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 )
+ pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" )
+ pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY )
+
+ pg.Append( wxpg.PropertyCategory("5 - Custom Properties") )
+ pg.Append( IntProperty2("IntProperty2", value=1024) )
+
+ pg.Append( ShapeProperty("ShapeProperty", value=0) )
+ pg.Append( PyObjectProperty("PyObjectProperty") )
+
+ pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") )
+ pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor")
+
+
+ pg.SetPropertyClientData( "Point", 1234 )
+ if pg.GetPropertyClientData( "Point" ) != 1234:
+ raise ValueError("Set/GetPropertyClientData() failed")
+
+ # Test setting unicode string
+ pg.GetPropertyByName("String").SetValue(u"Some Unicode Text")
+
+ #
+ # Test some code that *should* fail (but not crash)
+ #try:
+ #a_ = pg.GetPropertyValue( "NotARealProperty" )
+ #pg.EnableProperty( "NotAtAllRealProperty", False )
+ #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" )
+ #except:
+ #pass
+ #raise
+
+ '''
+ sizer.Add(self.pg, 1, wx.EXPAND)
+ self.SetSizer(sizer)
+ sizer.SetSizeHints(self)
+
+ self.SelectedTreeItem = None
+
+ def GetPropertyValues(self):
+ return self.pg.GetPropertyValues()
+
+ def Initialize(self, properties):
+ pg = self.pg
+ pg.Clear()
+
+ if properties:
+ for element in properties:
+ if element[1]['type'] == 'arraystring':
+ elements = element[1]['elements']
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ #retrieve individual strings
+ property_value = split(property_value, ' ')
+ #remove " delimiters
+ values = [value.strip('"') for value in property_value]
+ pg.Append(wxpg.ArrayStringProperty(element[0], value=values))
+
+ if element[1]['type'] == 'boolean':
+ if 'value' in element[1]:
+ property_value = element[1].as_bool('value')
+ else:
+ property_value = element[1].as_bool('default')
+ property_control = wxpg.BoolProperty(element[0], value=property_value)
+ pg.Append(property_control)
+ pg.SetPropertyAttribute(element[0], 'UseCheckbox', True)
+
+ #if element[0] == 'category':
+ #pg.Append(wxpg.PropertyCategory(element[1]))
+
+ if element[1]['type'] == 'color':
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ property_value = eval(property_value)
+ pg.Append(wxpg.ColourProperty(element[0], value=property_value))
+
+ if element[1]['type'] == 'enum':
+ elements = element[1]['elements']
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ pg.Append(wxpg.EnumProperty(element[0], element[0], elements, [], elements.index(property_value)))
+
+ if element[1]['type'] == 'filename':
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ pg.Append(wxpg.FileProperty(element[0], value=property_value))
+
+ if element[1]['type'] == 'float':
+ if 'value' in element[1]:
+ property_value = element[1].as_float('value')
+ else:
+ property_value = element[1].as_float('default')
+ property_control = wxpg.FloatProperty(element[0], value=property_value)
+ pg.Append(property_control)
+
+ if element[1]['type'] == 'folder':
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ pg.Append(wxpg.DirProperty(element[0], value=property_value))
+
+ if element[1]['type'] == 'integer':
+ if 'value' in element[1]:
+ property_value = element[1].as_int('value')
+ else:
+ property_value = element[1].as_int('default')
+ property_control = wxpg.IntProperty(element[0], value=property_value)
+ if 'maximum' in element[1]:
+ property_control.SetAttribute('Max', element[1].as_int('maximum'))
+ if 'minimum' in element[1]:
+ property_control.SetAttribute('Min', element[1].as_int('minimum'))
+ property_control.SetAttribute('Wrap', True)
+ pg.Append(property_control)
+ pg.SetPropertyEditor(element[0], 'SpinCtrl')
+
+ if element[1]['type'] == 'string':
+ if 'value' in element[1]:
+ property_value = element[1]['value']
+ else:
+ property_value = element[1]['default']
+ pg.Append(wxpg.StringProperty(element[0], value=property_value))
+
+ pg.Refresh()
+
+ def OnReserved(self, event):
+ pass
-# Copyright\r
-\r
-"""Property editor panel for Hooke.\r
-\r
-wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_. Until\r
-then, we'll avoid it because of the *nix build problems.\r
-\r
-This module hacks together a workaround to be used until 2.9.1 is\r
-widely installed (or at least released ;).\r
-\r
-.. _included:\r
- http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
-"""\r
-\r
-import wx.grid\r
-\r
-from . import Panel\r
-\r
-\r
-def prop_from_argument(argument, curves=None, playlists=None):\r
- """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.\r
- """\r
- type = argument.type\r
- if type in ['driver']: # intentionally not handled (yet)\r
- return None\r
- if argument.count != 1:\r
- raise NotImplementedError(argument)\r
- kwargs = {\r
- 'label':argument.name,\r
- 'default':argument.default,\r
- 'help':argument.help(),\r
- }\r
- # type consolidation\r
- if type == 'file':\r
- type = 'path'\r
- # type handling\r
- if type in ['string', 'bool', 'int', 'float', 'path']:\r
- _class = globals()['%sProperty' % type.capitalize()]\r
- return _class(**kwargs)\r
- elif type in ['curve', 'playlist']:\r
- if type == 'curve':\r
- choices = curves # extracted from the current playlist\r
- else:\r
- choices = playlists\r
- return ChoiceProperty(choices=choices, **kwargs)\r
- raise NotImplementedError(argument.type)\r
-\r
-def prop_from_setting(setting):\r
- """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.\r
- """ \r
- raise NotImplementedError()\r
-\r
-\r
-class Property (object):\r
- def __init__(self, type, label, default, help=None):\r
- self.type = type\r
- self.label = label\r
- self.default = default\r
- self.help = help\r
-\r
- def get_editor(self):\r
- """Return a suitable grid editor.\r
- """\r
- raise NotImplementedError()\r
-\r
- def get_renderer(self):\r
- """Return a suitable grid renderer.\r
-\r
- Returns `None` if no special renderer is required.\r
- """\r
- return None\r
-\r
- def string_for_value(self, value):\r
- """Return a string representation of `value` for loading the table.\r
- """\r
- return str(value)\r
-\r
- def value_for_string(self, string):\r
- """Return the value represented by `string`.\r
- """\r
- return string\r
-\r
-\r
-class StringProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0\r
- super(StringProperty, self).__init__(type='string', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellTextEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellStringRenderer()\r
-\r
-\r
-class BoolProperty (Property):\r
- """A boolean property.\r
-\r
- Notes\r
- -----\r
- Unfortunately, changing a boolean property takes two clicks:\r
-\r
- 1) create the editor\r
- 2) change the value\r
-\r
- There are `ways around this`_, but it's not pretty.\r
-\r
- .. _ways around this:\r
- http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click\r
- """\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = True\r
- super(BoolProperty, self).__init__(type='bool', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellBoolEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellBoolRenderer()\r
-\r
- def string_for_value(self, value):\r
- if value == True:\r
- return '1'\r
- return ''\r
-\r
- def value_for_string(self, string):\r
- return string == '1'\r
-\r
-\r
-class IntProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0\r
- super(IntProperty, self).__init__(type='int', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellNumberEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellNumberRenderer()\r
-\r
- def value_for_string(self, string):\r
- return int(string)\r
-\r
-\r
-class FloatProperty (Property):\r
- def __init__(self, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' not in kwargs:\r
- kwargs['default'] = 0.0\r
- super(FloatProperty, self).__init__(type='float', **kwargs)\r
-\r
- def get_editor(self):\r
- return wx.grid.GridCellFloatEditor()\r
-\r
- def get_renderer(self):\r
- return wx.grid.GridCellFloatRenderer()\r
-\r
- def value_for_string(self, string):\r
- return float(string)\r
-\r
-\r
-class ChoiceProperty (Property):\r
- def __init__(self, choices, **kwargs):\r
- assert 'type' not in kwargs, kwargs\r
- if 'default' in kwargs:\r
- if kwargs['default'] not in choices:\r
- choices.insert(0, kwargs['default'])\r
- else:\r
- kwargs['default'] = choices[0]\r
- super(ChoiceProperty, self).__init__(type='choice', **kwargs)\r
- self._choices = choices\r
-\r
- def get_editor(self):\r
- choices = [self.string_for_value(c) for c in self._choices]\r
- return wx.grid.GridCellChoiceEditor(choices=choices)\r
-\r
- def get_renderer(self):\r
- return None\r
- #return wx.grid.GridCellChoiceRenderer()\r
-\r
- def string_for_value(self, value):\r
- if hasattr(value, 'name'):\r
- return value.name\r
- return str(value)\r
-\r
- def value_for_string(self, string):\r
- for choice in self._choices:\r
- if self.string_for_value(choice) == string:\r
- return choice\r
- raise ValueError(string)\r
-\r
-\r
-class PathProperty (StringProperty):\r
- """Simple file or path property.\r
-\r
- Currently there isn't a fancy file-picker popup. Perhaps in the\r
- future.\r
- """\r
- def __init__(self, **kwargs):\r
- super(PathProperty, self).__init__(**kwargs)\r
- self.type = 'path'\r
-\r
-\r
-class PropertyPanel(Panel, wx.grid.Grid):\r
- """UI to view/set config values and command argsuments.\r
- """\r
- def __init__(self, callbacks=None, **kwargs):\r
- super(PropertyPanel, self).__init__(\r
- name='property editor', callbacks=callbacks, **kwargs)\r
- self._properties = []\r
-\r
- self.CreateGrid(numRows=0, numCols=1)\r
- self.SetColLabelValue(0, 'value')\r
-\r
- self._last_tooltip = None\r
- self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_motion)\r
-\r
- def _on_motion(self, event):\r
- """Enable tooltips.\r
- """\r
- x,y = self.CalcUnscrolledPosition(event.GetPosition())\r
- col,row = self.XYToCell(x, y)\r
- if col == -1 or row == -1:\r
- msg = ''\r
- else:\r
- msg = self._properties[row].help or ''\r
- if msg != self._last_tooltip:\r
- self._last_tooltip = msg\r
- event.GetEventObject().SetToolTipString(msg)\r
-\r
- def append_property(self, property):\r
- if len([p for p in self._properties if p.label == property.label]) > 0:\r
- raise ValueError(property) # property.label collision\r
- self._properties.append(property)\r
- row = len(self._properties) - 1\r
- self.AppendRows(numRows=1)\r
- self.SetRowLabelValue(row, property.label)\r
- self.SetCellEditor(row=row, col=0, editor=property.get_editor())\r
- r = property.get_renderer()\r
- if r != None:\r
- self.SetCellRenderer(row=row, col=0, renderer=r)\r
- self.set_property(property.label, property.default)\r
-\r
- def remove_property(self, label):\r
- row,property = self._property_by_label(label)\r
- self._properties.pop(row)\r
- self.DeleteRows(pos=row)\r
-\r
- def clear(self):\r
- while(len(self._properties) > 0):\r
- self.remove_property(self._properties[-1].label)\r
-\r
- def set_property(self, label, value):\r
- row,property = self._property_by_label(label)\r
- self.SetCellValue(row=row, col=0, s=property.string_for_value(value))\r
-\r
- def get_property(self, label):\r
- row,property = self._property_by_label(label)\r
- string = self.GetCellValue(row=row, col=0)\r
- return property.value_for_string(string)\r
-\r
- def get_values(self):\r
- return dict([(p.label, self.get_property(p.label))\r
- for p in self._properties])\r
-\r
- def _property_by_label(self, label):\r
- props = [(i,p) for i,p in enumerate(self._properties)\r
- if p.label == label]\r
- assert len(props) == 1, props\r
- row,property = props[0]\r
- return (row, property)\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Property editor panel for Hooke.
+
+wxPropertyGrid is `included in wxPython >= 2.9.1 <included>`_. Until
+then, we'll avoid it because of the *nix build problems.
+
+This module hacks together a workaround to be used until 2.9.1 is
+widely installed (or at least released ;).
+
+.. _included:
+ http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download
+"""
+
+import wx.grid
+
+from . import Panel
+
+
+def prop_from_argument(argument, curves=None, playlists=None):
+ """Convert a :class:`~hooke.command.Argument` to a :class:`Property`.
+ """
+ type = argument.type
+ if type in ['driver']: # intentionally not handled (yet)
+ return None
+ if argument.count != 1:
+ raise NotImplementedError(argument)
+ kwargs = {
+ 'label':argument.name,
+ 'default':argument.default,
+ 'help':argument.help(),
+ }
+ # type consolidation
+ if type == 'file':
+ type = 'path'
+ # type handling
+ if type in ['string', 'bool', 'int', 'float', 'path']:
+ _class = globals()['%sProperty' % type.capitalize()]
+ return _class(**kwargs)
+ elif type in ['curve', 'playlist']:
+ if type == 'curve':
+ choices = curves # extracted from the current playlist
+ else:
+ choices = playlists
+ return ChoiceProperty(choices=choices, **kwargs)
+ raise NotImplementedError(argument.type)
+
+def prop_from_setting(setting):
+ """Convert a :class:`~hooke.config.Setting` to a :class:`Property`.
+ """
+ raise NotImplementedError()
+
+
+class Property (object):
+ def __init__(self, type, label, default, help=None):
+ self.type = type
+ self.label = label
+ self.default = default
+ self.help = help
+
+ def get_editor(self):
+ """Return a suitable grid editor.
+ """
+ raise NotImplementedError()
+
+ def get_renderer(self):
+ """Return a suitable grid renderer.
+
+ Returns `None` if no special renderer is required.
+ """
+ return None
+
+ def string_for_value(self, value):
+ """Return a string representation of `value` for loading the table.
+ """
+ return str(value)
+
+ def value_for_string(self, string):
+ """Return the value represented by `string`.
+ """
+ return string
+
+
+class StringProperty (Property):
+ def __init__(self, **kwargs):
+ assert 'type' not in kwargs, kwargs
+ if 'default' not in kwargs:
+ kwargs['default'] = 0
+ super(StringProperty, self).__init__(type='string', **kwargs)
+
+ def get_editor(self):
+ return wx.grid.GridCellTextEditor()
+
+ def get_renderer(self):
+ return wx.grid.GridCellStringRenderer()
+
+
+class BoolProperty (Property):
+ """A boolean property.
+
+ Notes
+ -----
+ Unfortunately, changing a boolean property takes two clicks:
+
+ 1) create the editor
+ 2) change the value
+
+ There are `ways around this`_, but it's not pretty.
+
+ .. _ways around this:
+ http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click
+ """
+ def __init__(self, **kwargs):
+ assert 'type' not in kwargs, kwargs
+ if 'default' not in kwargs:
+ kwargs['default'] = True
+ super(BoolProperty, self).__init__(type='bool', **kwargs)
+
+ def get_editor(self):
+ return wx.grid.GridCellBoolEditor()
+
+ def get_renderer(self):
+ return wx.grid.GridCellBoolRenderer()
+
+ def string_for_value(self, value):
+ if value == True:
+ return '1'
+ return ''
+
+ def value_for_string(self, string):
+ return string == '1'
+
+
+class IntProperty (Property):
+ def __init__(self, **kwargs):
+ assert 'type' not in kwargs, kwargs
+ if 'default' not in kwargs:
+ kwargs['default'] = 0
+ super(IntProperty, self).__init__(type='int', **kwargs)
+
+ def get_editor(self):
+ return wx.grid.GridCellNumberEditor()
+
+ def get_renderer(self):
+ return wx.grid.GridCellNumberRenderer()
+
+ def value_for_string(self, string):
+ return int(string)
+
+
+class FloatProperty (Property):
+ def __init__(self, **kwargs):
+ assert 'type' not in kwargs, kwargs
+ if 'default' not in kwargs:
+ kwargs['default'] = 0.0
+ super(FloatProperty, self).__init__(type='float', **kwargs)
+
+ def get_editor(self):
+ return wx.grid.GridCellFloatEditor()
+
+ def get_renderer(self):
+ return wx.grid.GridCellFloatRenderer()
+
+ def value_for_string(self, string):
+ return float(string)
+
+
+class ChoiceProperty (Property):
+ def __init__(self, choices, **kwargs):
+ assert 'type' not in kwargs, kwargs
+ if 'default' in kwargs:
+ if kwargs['default'] not in choices:
+ choices.insert(0, kwargs['default'])
+ else:
+ kwargs['default'] = choices[0]
+ super(ChoiceProperty, self).__init__(type='choice', **kwargs)
+ self._choices = choices
+
+ def get_editor(self):
+ choices = [self.string_for_value(c) for c in self._choices]
+ return wx.grid.GridCellChoiceEditor(choices=choices)
+
+ def get_renderer(self):
+ return None
+ #return wx.grid.GridCellChoiceRenderer()
+
+ def string_for_value(self, value):
+ if hasattr(value, 'name'):
+ return value.name
+ return str(value)
+
+ def value_for_string(self, string):
+ for choice in self._choices:
+ if self.string_for_value(choice) == string:
+ return choice
+ raise ValueError(string)
+
+
+class PathProperty (StringProperty):
+ """Simple file or path property.
+
+ Currently there isn't a fancy file-picker popup. Perhaps in the
+ future.
+ """
+ def __init__(self, **kwargs):
+ super(PathProperty, self).__init__(**kwargs)
+ self.type = 'path'
+
+
+class PropertyPanel(Panel, wx.grid.Grid):
+ """UI to view/set config values and command argsuments.
+ """
+ def __init__(self, callbacks=None, **kwargs):
+ super(PropertyPanel, self).__init__(
+ name='property editor', callbacks=callbacks, **kwargs)
+ self._properties = []
+
+ self.CreateGrid(numRows=0, numCols=1)
+ self.SetColLabelValue(0, 'value')
+
+ self._last_tooltip = None
+ self.GetGridWindow().Bind(wx.EVT_MOTION, self._on_motion)
+
+ def _on_motion(self, event):
+ """Enable tooltips.
+ """
+ x,y = self.CalcUnscrolledPosition(event.GetPosition())
+ col,row = self.XYToCell(x, y)
+ if col == -1 or row == -1:
+ msg = ''
+ else:
+ msg = self._properties[row].help or ''
+ if msg != self._last_tooltip:
+ self._last_tooltip = msg
+ event.GetEventObject().SetToolTipString(msg)
+
+ def append_property(self, property):
+ if len([p for p in self._properties if p.label == property.label]) > 0:
+ raise ValueError(property) # property.label collision
+ self._properties.append(property)
+ row = len(self._properties) - 1
+ self.AppendRows(numRows=1)
+ self.SetRowLabelValue(row, property.label)
+ self.SetCellEditor(row=row, col=0, editor=property.get_editor())
+ r = property.get_renderer()
+ if r != None:
+ self.SetCellRenderer(row=row, col=0, renderer=r)
+ self.set_property(property.label, property.default)
+
+ def remove_property(self, label):
+ row,property = self._property_by_label(label)
+ self._properties.pop(row)
+ self.DeleteRows(pos=row)
+
+ def clear(self):
+ while(len(self._properties) > 0):
+ self.remove_property(self._properties[-1].label)
+
+ def set_property(self, label, value):
+ row,property = self._property_by_label(label)
+ self.SetCellValue(row=row, col=0, s=property.string_for_value(value))
+
+ def get_property(self, label):
+ row,property = self._property_by_label(label)
+ string = self.GetCellValue(row=row, col=0)
+ return property.value_for_string(string)
+
+ def get_values(self):
+ return dict([(p.label, self.get_property(p.label))
+ for p in self._properties])
+
+ def _property_by_label(self, label):
+ props = [(i,p) for i,p in enumerate(self._properties)
+ if p.label == label]
+ assert len(props) == 1, props
+ row,property = props[0]
+ return (row, property)
-# Copyright\r
-\r
-"""Fitting results panel for Hooke.\r
-"""\r
-\r
-import sys\r
-\r
-import wx\r
-from wx.lib.mixins.listctrl import CheckListCtrlMixin\r
-\r
-class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):\r
- def __init__(self, parent):\r
- wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)\r
- CheckListCtrlMixin.__init__(self)\r
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)\r
-\r
- def OnItemActivated(self, evt):\r
- self.ToggleItem(evt.m_itemIndex)\r
-\r
-\r
-class Results(wx.Panel):\r
- def __init__(self, parent):\r
- wx.Panel.__init__(self, parent, -1)\r
- self.results_list = CheckListCtrl(self)\r
- sizer = wx.BoxSizer()\r
- sizer.Add(self.results_list, 1, wx.EXPAND)\r
- self.SetSizer(sizer)\r
- self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list)\r
- self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list)\r
-\r
- def _GetWidthInPixels(self, text):\r
- #TODO:\r
- #Returns the width of a string in pixels\r
- #Unfortunately, it does not work terribly well (although it should).\r
- #Thus, we have to add a bit afterwards.\r
- #Annoys the heck out of me (illysam).\r
- font = self.results_list.GetFont()\r
- dc = wx.WindowDC(self.results_list)\r
- dc.SetFont(font)\r
- width, height = dc.GetTextExtent(text)\r
- return width\r
-\r
- def ClearResults(self):\r
- self.results_list.ClearAll()\r
-\r
- def DisplayResults(self, results):\r
- self.ClearResults()\r
- header = results.get_header_as_list()\r
- self.results_list.InsertColumn(0, 'Show')\r
- for index, column in enumerate(header):\r
- self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT)\r
-\r
- for result in results.results:\r
- done = False\r
- for index, column in enumerate(results.columns):\r
- value_str = results.get_pretty_value(column, result.result[column])\r
- if not done:\r
- index_col = self.results_list.InsertStringItem(sys.maxint, '')\r
- done = True\r
- column_width = len(self.results_list.GetColumn(index + 1).GetText())\r
- value_str = value_str.center(column_width)\r
- self.results_list.SetStringItem(index_col, index + 1, value_str)\r
-\r
- for index, result in enumerate(results.results):\r
- if result.visible:\r
- #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates)\r
- self.results_list.SetItemImage(index, 1)\r
- for index in range(self.results_list.GetColumnCount()):\r
- column_text = self.results_list.GetColumn(index).GetText()\r
- column_width = self._GetWidthInPixels(column_text)\r
- self.results_list.SetColumnWidth(index, column_width + 15)\r
-\r
- def OnItemSelected(self, evt):\r
- pass\r
-\r
- def OnItemDeselected(self, evt):\r
- pass\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# Rolf Schmidt <rschmidt@alcor.concordia.ca>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Fitting results panel for Hooke.
+"""
+
+import sys
+
+import wx
+from wx.lib.mixins.listctrl import CheckListCtrlMixin
+
+class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):
+ def __init__(self, parent):
+ wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
+ CheckListCtrlMixin.__init__(self)
+ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
+
+ def OnItemActivated(self, evt):
+ self.ToggleItem(evt.m_itemIndex)
+
+
+class Results(wx.Panel):
+ def __init__(self, parent):
+ wx.Panel.__init__(self, parent, -1)
+ self.results_list = CheckListCtrl(self)
+ sizer = wx.BoxSizer()
+ sizer.Add(self.results_list, 1, wx.EXPAND)
+ self.SetSizer(sizer)
+ self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list)
+ self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list)
+
+ def _GetWidthInPixels(self, text):
+ #TODO:
+ #Returns the width of a string in pixels
+ #Unfortunately, it does not work terribly well (although it should).
+ #Thus, we have to add a bit afterwards.
+ #Annoys the heck out of me (illysam).
+ font = self.results_list.GetFont()
+ dc = wx.WindowDC(self.results_list)
+ dc.SetFont(font)
+ width, height = dc.GetTextExtent(text)
+ return width
+
+ def ClearResults(self):
+ self.results_list.ClearAll()
+
+ def DisplayResults(self, results):
+ self.ClearResults()
+ header = results.get_header_as_list()
+ self.results_list.InsertColumn(0, 'Show')
+ for index, column in enumerate(header):
+ self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT)
+
+ for result in results.results:
+ done = False
+ for index, column in enumerate(results.columns):
+ value_str = results.get_pretty_value(column, result.result[column])
+ if not done:
+ index_col = self.results_list.InsertStringItem(sys.maxint, '')
+ done = True
+ column_width = len(self.results_list.GetColumn(index + 1).GetText())
+ value_str = value_str.center(column_width)
+ self.results_list.SetStringItem(index_col, index + 1, value_str)
+
+ for index, result in enumerate(results.results):
+ if result.visible:
+ #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates)
+ self.results_list.SetItemImage(index, 1)
+ for index in range(self.results_list.GetColumnCount()):
+ column_text = self.results_list.GetColumn(index).GetText()
+ column_width = self._GetWidthInPixels(column_text)
+ self.results_list.SetColumnWidth(index, column_width + 15)
+
+ def OnItemSelected(self, evt):
+ pass
+
+ def OnItemDeselected(self, evt):
+ pass
-# Copyright\r
-\r
-"""Selection dialog.\r
-"""\r
-\r
-from os import remove\r
-\r
-import wx\r
-\r
-from ....util.callback import callback, in_callback\r
-\r
-\r
-class SelectionDialog (wx.Dialog):\r
- """A selection dialog box.\r
-\r
- Lists options and two buttons. The first button is setup by the\r
- caller. The second button cancels the dialog.\r
-\r
- The button appearance can be specified by selecting one of the\r
- `standard wx IDs`_.\r
-\r
- .. _standard wx IDs:\r
- http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid\r
- """\r
- def __init__(self, options, message, button_id, callbacks,\r
- default=None, selection_style='single', *args, **kwargs):\r
- super(Selection, self).__init__(*args, **kwargs)\r
-\r
- self._options = options\r
- self._callbacks = callbacks\r
- self._selection_style = selection_style\r
-\r
- self._c = {\r
- 'text': wx.StaticText(\r
- parent=self, label=message, style=wx.ALIGN_CENTRE),\r
- 'button': wx.Button(parent=self, id=button_id),\r
- 'cancel': wx.Button(self, wx.ID_CANCEL),\r
- }\r
- size = wx.Size(175, 200)\r
- if selection_style == 'single':\r
- self._c['listbox'] = wx.ListBox(\r
- parent=self, size=size, list=options)\r
- if default != None:\r
- self._c['listbox'].SetSelection(default)\r
- else:\r
- assert selection_style == 'multiple', selection_style\r
- self._c['listbox'] = wx.CheckListBox(\r
- parent=self, size=size, list=options)\r
- if default != None:\r
- self._c['listbox'].Check(default)\r
- self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])\r
- self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])\r
-\r
- border_width = 5\r
-\r
- b = wx.BoxSizer(wx.HORIZONTAL)\r
- b.Add(window=self._c['button'],\r
- flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
- border=border_width)\r
- b.Add(window=self._c['cancel'],\r
- flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
- border=border_width)\r
-\r
- v = wx.BoxSizer(wx.VERTICAL)\r
- v.Add(window=self._c['text'],\r
- flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
- border=border_width)\r
- v.Add(window=self._c['listbox'],\r
- proportion=1,\r
- flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,\r
- border=border_width)\r
- v.Add(window=wx.StaticLine(\r
- parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),\r
- flag=wx.GROW,\r
- border=border_width)\r
- v.Add(window=b,\r
- flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,\r
- border=border_width)\r
- self.SetSizer(v)\r
- v.Fit(self)\r
-\r
- @callback\r
- def cancel(self, event):\r
- """Close the dialog.\r
- """\r
- self.EndModal(wx.ID_CANCEL)\r
-\r
- def button(self, event):\r
- """Call ._button_callback() and close the dialog.\r
- """\r
- if self._selection_style == 'single':\r
- selected = self._c['listbox'].GetSelection()\r
- else:\r
- assert self._selection_style == 'multiple', self._selection_style\r
- selected = self._c['listbox'].GetChecked())\r
- in_callback(self, options=self._options, selected=selected)\r
- self.EndModal(wx.ID_CLOSE)\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Selection dialog.
+"""
+
+from os import remove
+
+import wx
+
+from ....util.callback import callback, in_callback
+
+
+class SelectionDialog (wx.Dialog):
+ """A selection dialog box.
+
+ Lists options and two buttons. The first button is setup by the
+ caller. The second button cancels the dialog.
+
+ The button appearance can be specified by selecting one of the
+ `standard wx IDs`_.
+
+ .. _standard wx IDs:
+ http://docs.wxwidgets.org/stable/wx_stdevtid.html#stdevtid
+ """
+ def __init__(self, options, message, button_id, callbacks,
+ default=None, selection_style='single', *args, **kwargs):
+ super(Selection, self).__init__(*args, **kwargs)
+
+ self._options = options
+ self._callbacks = callbacks
+ self._selection_style = selection_style
+
+ self._c = {
+ 'text': wx.StaticText(
+ parent=self, label=message, style=wx.ALIGN_CENTRE),
+ 'button': wx.Button(parent=self, id=button_id),
+ 'cancel': wx.Button(self, wx.ID_CANCEL),
+ }
+ size = wx.Size(175, 200)
+ if selection_style == 'single':
+ self._c['listbox'] = wx.ListBox(
+ parent=self, size=size, list=options)
+ if default != None:
+ self._c['listbox'].SetSelection(default)
+ else:
+ assert selection_style == 'multiple', selection_style
+ self._c['listbox'] = wx.CheckListBox(
+ parent=self, size=size, list=options)
+ if default != None:
+ self._c['listbox'].Check(default)
+ self.Bind(wx.EVT_BUTTON, self.button, self._c['button'])
+ self.Bind(wx.EVT_BUTTON, self.cancel, self._c['cancel'])
+
+ border_width = 5
+
+ b = wx.BoxSizer(wx.HORIZONTAL)
+ b.Add(window=self._c['button'],
+ flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+ border=border_width)
+ b.Add(window=self._c['cancel'],
+ flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+ border=border_width)
+
+ v = wx.BoxSizer(wx.VERTICAL)
+ v.Add(window=self._c['text'],
+ flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+ border=border_width)
+ v.Add(window=self._c['listbox'],
+ proportion=1,
+ flag=wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+ border=border_width)
+ v.Add(window=wx.StaticLine(
+ parent=self, size=(20,-1), style=wx.LI_HORIZONTAL),
+ flag=wx.GROW,
+ border=border_width)
+ v.Add(window=b,
+ flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,
+ border=border_width)
+ self.SetSizer(v)
+ v.Fit(self)
+
+ @callback
+ def cancel(self, event):
+ """Close the dialog.
+ """
+ self.EndModal(wx.ID_CANCEL)
+
+ def button(self, event):
+ """Call ._button_callback() and close the dialog.
+ """
+ if self._selection_style == 'single':
+ selected = self._c['listbox'].GetSelection()
+ else:
+ assert self._selection_style == 'multiple', self._selection_style
+ selected = self._c['listbox'].GetChecked())
+ in_callback(self, options=self._options, selected=selected)
+ self.EndModal(wx.ID_CLOSE)
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Welcome panel for Hooke.
"""
-#!/usr/bin/env python\r
-\r
-'''\r
-playlist.py\r
-\r
-Playlist class for Hooke.\r
-\r
-Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
-\r
-This program is released under the GNU General Public License version 2.\r
-'''\r
-\r
-import os.path\r
-import xml.dom.minidom\r
-\r
-import lib.libhooke\r
-import lib.file\r
-\r
-class Playlist(object):\r
-\r
- def __init__(self, filename=None):\r
- self._saved = False\r
- self.count = 0\r
- self.figure = None\r
- self.files = []\r
- self.generics_dict = {}\r
- self.hidden_attributes = ['curve', 'data', 'driver', 'fits', 'name', 'plot', 'plots']\r
- self.index = -1\r
- self.name = None\r
- self.path = None\r
- self.plot_panel = None\r
- self.plot_tab = None\r
- if filename is None:\r
- self.filename = None\r
- else:\r
- self.load(filename)\r
-\r
- def add_file(self, filename):\r
- if os.path.isfile(filename):\r
- file_to_add = lib.file.File(filename)\r
- self.files.append(file_to_add)\r
- self._saved = False\r
- self.count = len(self.files)\r
-\r
- def delete_file(self, name):\r
- for index, item in enumerate(self.files):\r
- if item.name == name:\r
- del self.files[index]\r
- self.index = index\r
- self.count = len(self.files)\r
- if self.index > self.count - 1:\r
- self.index = 0\r
-\r
- def filter_curves(self, curves_to_keep=[]):\r
- playlist = Playlist()\r
- for curve_index in curves_to_keep:\r
- playlist.files.append(self.files[curve_index])\r
- playlist.count = len(playlist.files)\r
- playlist.index = 0\r
- return playlist\r
-\r
- def get_active_file(self):\r
- return self.files[self.index]\r
-\r
- #def get_active_plot(self):\r
- ##TODO: is this the only active (or default?) plot?\r
- #return self.files[self.index].plots[0]\r
-\r
- def load(self, filename):\r
- '''\r
- Loads a playlist file\r
- '''\r
- self.filename = filename\r
- self.path, self.name = os.path.split(filename)\r
- playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename)\r
- self.xml = xml.dom.minidom.parseString(playlist_file)\r
- #TODO: rename 'element' to 'curve' or something in hkp file\r
- #TODO: rename 'path' to 'filename'\r
- #TODO: switch from attributes to nodes, it's cleaner XML in my eyes\r
-\r
- element_list = self.xml.getElementsByTagName('element')\r
- #populate playlist with files\r
- for index, element in enumerate(element_list):\r
- #rebuild a data structure from the xml attributes\r
- #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path'\r
- if element.hasAttribute('path'):\r
- #path, name = os.path.split(element.getAttribute('path'))\r
- #path = path.split(os.sep)\r
- #filename = lib.libhooke.get_file_path(name, path)\r
- filename = element.getAttribute('path')\r
- if element.hasAttribute('filename'):\r
- #path, name = os.path.split(element.getAttribute('filename'))\r
- #path = path.split(os.sep)\r
- #filename = lib.libhooke.get_file_path(name, path)\r
- filename = element.getAttribute('filename')\r
- if os.path.isfile(filename):\r
- data_file = lib.file.File(filename)\r
- if element.hasAttribute('note'):\r
- data_file.note = element.getAttribute('note')\r
- self.files.append(data_file)\r
- self.count = len(self.files)\r
- if self.count > 0:\r
- #populate generics\r
- genericsDict = {}\r
- generics_list = self.xml.getElementsByTagName('generics')\r
- if generics_list:\r
- for attribute in generics_list[0].attributes.keys():\r
- genericsDict[attribute] = generics_list[0].getAttribute(attribute)\r
- if genericsDict.has_key('pointer'):\r
- index = int(genericsDict['pointer'])\r
- if index >= 0 and index < self.count:\r
- self.index = index\r
- else:\r
- index = 0\r
- self._saved = True\r
-\r
- def next(self):\r
- self.index += 1\r
- if self.index > self.count - 1:\r
- self.index = 0\r
-\r
- def previous(self):\r
- self.index -= 1\r
- if self.index < 0:\r
- self.index = self.count - 1\r
-\r
- def reset(self):\r
- if self.count > 0:\r
- self.index = 0\r
- else:\r
- self.index = -1\r
-\r
- def save(self, filename):\r
- '''\r
- Saves a playlist from a list of files.\r
- A playlist is an XML document with the following syntax:\r
- <playlist>\r
- <element path="/my/file/path/"/ attribute="attribute">\r
- <element path="...">\r
- </playlist>\r
- '''\r
- try:\r
- output_file = file(filename, 'w')\r
- except IOError:\r
- self.AppendToOutput('Cannot save playlist. Wrong path or filename')\r
- return\r
- #create the output playlist, a simple XML document\r
- implementation = xml.dom.minidom.getDOMImplementation()\r
- #create the document DOM object and the root element\r
- self.xml = implementation.createDocument(None, 'playlist', None)\r
- root = self.xml.documentElement\r
-\r
- #save generics variables\r
- playlist_generics = self.xml.createElement('generics')\r
- root.appendChild(playlist_generics)\r
- self.generics_dict['pointer'] = self.index\r
- for key in self.generics_dict.keys():\r
- #for key in generics.keys():\r
- self.xml.createAttribute(key)\r
- playlist_generics.setAttribute(key, str(self.generics_dict[key]))\r
-\r
- #save files and their attributes\r
- for item in self.files:\r
- #playlist_element=newdoc.createElement("file")\r
- playlist_element = self.xml.createElement('element')\r
- root.appendChild(playlist_element)\r
- for key in item.__dict__:\r
- if not (key in self.hidden_attributes):\r
- self.xml.createAttribute(key)\r
- playlist_element.setAttribute(key, str(item.__dict__[key]))\r
- self._saved = False\r
-\r
- self.xml.writexml(output_file, indent='\n')\r
- output_file.close()\r
- self._saved = True\r
+#!/usr/bin/env python
+
+'''
+playlist.py
+
+Playlist class for Hooke.
+
+Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+import os.path
+import xml.dom.minidom
+
+import lib.libhooke
+import lib.file
+
+class Playlist(object):
+
+ def __init__(self, filename=None):
+ self._saved = False
+ self.count = 0
+ self.figure = None
+ self.files = []
+ self.generics_dict = {}
+ self.hidden_attributes = ['curve', 'data', 'driver', 'fits', 'name', 'plot', 'plots']
+ self.index = -1
+ self.name = None
+ self.path = None
+ self.plot_panel = None
+ self.plot_tab = None
+ if filename is None:
+ self.filename = None
+ else:
+ self.load(filename)
+
+ def add_file(self, filename):
+ if os.path.isfile(filename):
+ file_to_add = lib.file.File(filename)
+ self.files.append(file_to_add)
+ self._saved = False
+ self.count = len(self.files)
+
+ def delete_file(self, name):
+ for index, item in enumerate(self.files):
+ if item.name == name:
+ del self.files[index]
+ self.index = index
+ self.count = len(self.files)
+ if self.index > self.count - 1:
+ self.index = 0
+
+ def filter_curves(self, curves_to_keep=[]):
+ playlist = Playlist()
+ for curve_index in curves_to_keep:
+ playlist.files.append(self.files[curve_index])
+ playlist.count = len(playlist.files)
+ playlist.index = 0
+ return playlist
+
+ def get_active_file(self):
+ return self.files[self.index]
+
+ #def get_active_plot(self):
+ ##TODO: is this the only active (or default?) plot?
+ #return self.files[self.index].plots[0]
+
+ def load(self, filename):
+ '''
+ Loads a playlist file
+ '''
+ self.filename = filename
+ self.path, self.name = os.path.split(filename)
+ playlist_file = lib.libhooke.delete_empty_lines_from_xmlfile(filename)
+ self.xml = xml.dom.minidom.parseString(playlist_file)
+ #TODO: rename 'element' to 'curve' or something in hkp file
+ #TODO: rename 'path' to 'filename'
+ #TODO: switch from attributes to nodes, it's cleaner XML in my eyes
+
+ element_list = self.xml.getElementsByTagName('element')
+ #populate playlist with files
+ for index, element in enumerate(element_list):
+ #rebuild a data structure from the xml attributes
+ #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path'
+ if element.hasAttribute('path'):
+ #path, name = os.path.split(element.getAttribute('path'))
+ #path = path.split(os.sep)
+ #filename = lib.libhooke.get_file_path(name, path)
+ filename = element.getAttribute('path')
+ if element.hasAttribute('filename'):
+ #path, name = os.path.split(element.getAttribute('filename'))
+ #path = path.split(os.sep)
+ #filename = lib.libhooke.get_file_path(name, path)
+ filename = element.getAttribute('filename')
+ if os.path.isfile(filename):
+ data_file = lib.file.File(filename)
+ if element.hasAttribute('note'):
+ data_file.note = element.getAttribute('note')
+ self.files.append(data_file)
+ self.count = len(self.files)
+ if self.count > 0:
+ #populate generics
+ genericsDict = {}
+ generics_list = self.xml.getElementsByTagName('generics')
+ if generics_list:
+ for attribute in generics_list[0].attributes.keys():
+ genericsDict[attribute] = generics_list[0].getAttribute(attribute)
+ if genericsDict.has_key('pointer'):
+ index = int(genericsDict['pointer'])
+ if index >= 0 and index < self.count:
+ self.index = index
+ else:
+ index = 0
+ self._saved = True
+
+ def next(self):
+ self.index += 1
+ if self.index > self.count - 1:
+ self.index = 0
+
+ def previous(self):
+ self.index -= 1
+ if self.index < 0:
+ self.index = self.count - 1
+
+ def reset(self):
+ if self.count > 0:
+ self.index = 0
+ else:
+ self.index = -1
+
+ def save(self, filename):
+ '''
+ Saves a playlist from a list of files.
+ A playlist is an XML document with the following syntax:
+ <playlist>
+ <element path="/my/file/path/"/ attribute="attribute">
+ <element path="...">
+ </playlist>
+ '''
+ try:
+ output_file = file(filename, 'w')
+ except IOError:
+ self.AppendToOutput('Cannot save playlist. Wrong path or filename')
+ return
+ #create the output playlist, a simple XML document
+ implementation = xml.dom.minidom.getDOMImplementation()
+ #create the document DOM object and the root element
+ self.xml = implementation.createDocument(None, 'playlist', None)
+ root = self.xml.documentElement
+
+ #save generics variables
+ playlist_generics = self.xml.createElement('generics')
+ root.appendChild(playlist_generics)
+ self.generics_dict['pointer'] = self.index
+ for key in self.generics_dict.keys():
+ #for key in generics.keys():
+ self.xml.createAttribute(key)
+ playlist_generics.setAttribute(key, str(self.generics_dict[key]))
+
+ #save files and their attributes
+ for item in self.files:
+ #playlist_element=newdoc.createElement("file")
+ playlist_element = self.xml.createElement('element')
+ root.appendChild(playlist_element)
+ for key in item.__dict__:
+ if not (key in self.hidden_attributes):
+ self.xml.createAttribute(key)
+ playlist_element.setAttribute(key, str(item.__dict__[key]))
+ self._saved = False
+
+ self.xml.writexml(output_file, indent='\n')
+ output_file.close()
+ self._saved = True
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-#!/usr/bin/env python\r
-\r
-'''\r
-results.py\r
-\r
-Result and Results classes for Hooke.\r
-\r
-Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)\r
-\r
-This program is released under the GNU General Public License version 2.\r
-'''\r
-\r
-from numpy import nan\r
-\r
-import prettyformat\r
-import lib.curve\r
-\r
-DEFAULT_COLOR = 'green'\r
-DEFAULT_DECIMAL = 2\r
-DEFAULT_STYLE = 'scatter'\r
-\r
-class Result(lib.curve.Curve):\r
- def __init__(self):\r
- lib.curve.Curve.__init__(self)\r
- self.color = DEFAULT_COLOR\r
- self.result = {}\r
- self.style = DEFAULT_STYLE\r
-\r
-class Results(object):\r
- def __init__(self):\r
- self.columns = []\r
- self.decimals = {}\r
- self.has_multipliers = False\r
- self.multipliers = {}\r
- self.results = []\r
- self.separator='\t'\r
- self.units = {}\r
-\r
- def get_pretty_value(self, column, value):\r
- if self.has_multipliers and self.has_results():\r
- multiplier = self.multipliers[column]\r
- decimals = self.decimals[column]\r
- return prettyformat.pretty_format(value, '', decimals, multiplier, True)\r
- return str(value)\r
-\r
- def has_results(self):\r
- return len(self.results) > 0\r
-\r
- def get_header_as_list(self):\r
- header_list = []\r
- if self.has_results():\r
- if not self.has_multipliers:\r
- self.set_multipliers()\r
- for column in self.columns:\r
- unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])\r
- header_str = ''.join([column, ' [', unit_str, ']'])\r
- header_list.append(header_str)\r
- return header_list\r
-\r
- def get_header_as_str(self, separator=None):\r
- if separator is None:\r
- separator = self.separator\r
- return separator.join(map(str, self.get_header_as_list()))\r
-\r
- def get_result_as_list(self, index=0):\r
- if index >= 0 and index < len(self.results):\r
- result_list = []\r
- if self.has_results():\r
- if not self.has_multipliers:\r
- self.set_multipliers()\r
- for column in self.columns:\r
- result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True)\r
- result_list.append(result_str)\r
- return result_list\r
- else:\r
- return None\r
-\r
- def get_result_as_string(self, index=0):\r
- results_list = self.get_result_as_list(index)\r
- if results_list is not None:\r
- return self.separator.join(map(str, results_list))\r
- else:\r
- return ''\r
-\r
- def set_decimal(self, column, decimal=DEFAULT_DECIMAL):\r
- if self.decimals.has_key(column):\r
- self.decimals[column] = decimal\r
-\r
- def set_decimals(self, decimals=DEFAULT_DECIMAL):\r
- if decimals < 0:\r
- #set default value if necessary\r
- decimals = DEFAULT_DECIMAL\r
- for column in self.columns:\r
- self.decimals[column] = decimals\r
-\r
- def set_multipliers(self, index=0):\r
- if self.has_results():\r
- for column in self.columns:\r
- #result will contain the results dictionary at 'index'\r
- result = self.results[index].result\r
- #in position 0 of the result we find the value\r
- self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
- self.has_multipliers = True\r
- else:\r
- self.has_multipliers = False\r
-\r
- def update(self):\r
- pass\r
-\r
-\r
-class ResultsFJC(Results):\r
- def __init__(self):\r
- Results.__init__(self)\r
- self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate']\r
- self.units['Contour length'] = 'm'\r
- self.units['sigma contour length'] = 'm'\r
- self.units['Kuhn length'] = 'm'\r
- self.units['sigma Kuhn length'] = 'm'\r
- self.units['Rupture force'] = 'N'\r
- self.units['Slope'] = 'N/m'\r
- self.units['Loading rate'] = 'N/s'\r
- self.set_decimals(2)\r
-\r
- def set_multipliers(self, index=0):\r
- if self.has_results():\r
- for column in self.columns:\r
- #result will contain the results dictionary at 'index'\r
- result = self.results[index].result\r
- #in position 0 of the result we find the value\r
- if column == 'sigma contour length':\r
- self.multipliers[column] = self.multipliers['Contour length']\r
- elif column == 'sigma Kuhn length':\r
- self.multipliers[column] = self.multipliers['Kuhn length']\r
- else:\r
- self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
- self.has_multipliers = True\r
- else:\r
- self.has_multipliers = False\r
-\r
-class ResultsMultiDistance(Results):\r
- def __init__(self):\r
- Results.__init__(self)\r
- self.columns = ['Distance']\r
- self.units['Distance'] = 'm'\r
- self.set_decimals(2)\r
-\r
- def update(self):\r
- if (self.results) > 0:\r
- for result in self.results:\r
- if result.visible:\r
- reference_peak = result.x\r
- break\r
-\r
- for result in self.results:\r
- if result.visible:\r
- result.result['Distance'] = reference_peak - result.x\r
- reference_peak = result.x\r
- else:\r
- result.result['Distance'] = nan\r
-\r
-\r
-class ResultsWLC(Results):\r
- def __init__(self):\r
- Results.__init__(self)\r
- self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Slope', 'Loading rate']\r
- self.units['Contour length'] = 'm'\r
- self.units['sigma contour length'] = 'm'\r
- self.units['Persistence length'] = 'm'\r
- self.units['sigma persistence length'] = 'm'\r
- self.units['Rupture force'] = 'N'\r
- self.units['Slope'] = 'N/m'\r
- self.units['Loading rate'] = 'N/s'\r
- self.set_decimals(2)\r
-\r
- def set_multipliers(self, index=0):\r
- if self.has_results():\r
- for column in self.columns:\r
- #result will contain the results dictionary at 'index'\r
- result = self.results[index].result\r
- #in position 0 of the result we find the value\r
- if column == 'sigma contour length':\r
- self.multipliers[column] = self.multipliers['Contour length']\r
- elif column == 'sigma persistence length':\r
- self.multipliers[column] = self.multipliers['Persistence length']\r
- else:\r
- self.multipliers[column] = prettyformat.get_multiplier(result[column])\r
- self.has_multipliers = True\r
- else:\r
- self.has_multipliers = False\r
+#!/usr/bin/env python
+
+'''
+results.py
+
+Result and Results classes for Hooke.
+
+Copyright 2009 by Dr. Rolf Schmidt (Concordia University, Canada)
+
+This program is released under the GNU General Public License version 2.
+'''
+
+from numpy import nan
+
+import prettyformat
+import lib.curve
+
+DEFAULT_COLOR = 'green'
+DEFAULT_DECIMAL = 2
+DEFAULT_STYLE = 'scatter'
+
+class Result(lib.curve.Curve):
+ def __init__(self):
+ lib.curve.Curve.__init__(self)
+ self.color = DEFAULT_COLOR
+ self.result = {}
+ self.style = DEFAULT_STYLE
+
+class Results(object):
+ def __init__(self):
+ self.columns = []
+ self.decimals = {}
+ self.has_multipliers = False
+ self.multipliers = {}
+ self.results = []
+ self.separator='\t'
+ self.units = {}
+
+ def get_pretty_value(self, column, value):
+ if self.has_multipliers and self.has_results():
+ multiplier = self.multipliers[column]
+ decimals = self.decimals[column]
+ return prettyformat.pretty_format(value, '', decimals, multiplier, True)
+ return str(value)
+
+ def has_results(self):
+ return len(self.results) > 0
+
+ def get_header_as_list(self):
+ header_list = []
+ if self.has_results():
+ if not self.has_multipliers:
+ self.set_multipliers()
+ for column in self.columns:
+ unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]])
+ header_str = ''.join([column, ' [', unit_str, ']'])
+ header_list.append(header_str)
+ return header_list
+
+ def get_header_as_str(self, separator=None):
+ if separator is None:
+ separator = self.separator
+ return separator.join(map(str, self.get_header_as_list()))
+
+ def get_result_as_list(self, index=0):
+ if index >= 0 and index < len(self.results):
+ result_list = []
+ if self.has_results():
+ if not self.has_multipliers:
+ self.set_multipliers()
+ for column in self.columns:
+ result_str = prettyformat.pretty_format(self.results[index].result[column], '', self.decimals[column], self.multipliers[column], True)
+ result_list.append(result_str)
+ return result_list
+ else:
+ return None
+
+ def get_result_as_string(self, index=0):
+ results_list = self.get_result_as_list(index)
+ if results_list is not None:
+ return self.separator.join(map(str, results_list))
+ else:
+ return ''
+
+ def set_decimal(self, column, decimal=DEFAULT_DECIMAL):
+ if self.decimals.has_key(column):
+ self.decimals[column] = decimal
+
+ def set_decimals(self, decimals=DEFAULT_DECIMAL):
+ if decimals < 0:
+ #set default value if necessary
+ decimals = DEFAULT_DECIMAL
+ for column in self.columns:
+ self.decimals[column] = decimals
+
+ def set_multipliers(self, index=0):
+ if self.has_results():
+ for column in self.columns:
+ #result will contain the results dictionary at 'index'
+ result = self.results[index].result
+ #in position 0 of the result we find the value
+ self.multipliers[column] = prettyformat.get_multiplier(result[column])
+ self.has_multipliers = True
+ else:
+ self.has_multipliers = False
+
+ def update(self):
+ pass
+
+
+class ResultsFJC(Results):
+ def __init__(self):
+ Results.__init__(self)
+ self.columns = ['Contour length', 'sigma contour length', 'Kuhn length', 'sigma Kuhn length', 'Rupture force', 'Slope', 'Loading rate']
+ self.units['Contour length'] = 'm'
+ self.units['sigma contour length'] = 'm'
+ self.units['Kuhn length'] = 'm'
+ self.units['sigma Kuhn length'] = 'm'
+ self.units['Rupture force'] = 'N'
+ self.units['Slope'] = 'N/m'
+ self.units['Loading rate'] = 'N/s'
+ self.set_decimals(2)
+
+ def set_multipliers(self, index=0):
+ if self.has_results():
+ for column in self.columns:
+ #result will contain the results dictionary at 'index'
+ result = self.results[index].result
+ #in position 0 of the result we find the value
+ if column == 'sigma contour length':
+ self.multipliers[column] = self.multipliers['Contour length']
+ elif column == 'sigma Kuhn length':
+ self.multipliers[column] = self.multipliers['Kuhn length']
+ else:
+ self.multipliers[column] = prettyformat.get_multiplier(result[column])
+ self.has_multipliers = True
+ else:
+ self.has_multipliers = False
+
+class ResultsMultiDistance(Results):
+ def __init__(self):
+ Results.__init__(self)
+ self.columns = ['Distance']
+ self.units['Distance'] = 'm'
+ self.set_decimals(2)
+
+ def update(self):
+ if (self.results) > 0:
+ for result in self.results:
+ if result.visible:
+ reference_peak = result.x
+ break
+
+ for result in self.results:
+ if result.visible:
+ result.result['Distance'] = reference_peak - result.x
+ reference_peak = result.x
+ else:
+ result.result['Distance'] = nan
+
+
+class ResultsWLC(Results):
+ def __init__(self):
+ Results.__init__(self)
+ self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Slope', 'Loading rate']
+ self.units['Contour length'] = 'm'
+ self.units['sigma contour length'] = 'm'
+ self.units['Persistence length'] = 'm'
+ self.units['sigma persistence length'] = 'm'
+ self.units['Rupture force'] = 'N'
+ self.units['Slope'] = 'N/m'
+ self.units['Loading rate'] = 'N/s'
+ self.set_decimals(2)
+
+ def set_multipliers(self, index=0):
+ if self.has_results():
+ for column in self.columns:
+ #result will contain the results dictionary at 'index'
+ result = self.results[index].result
+ #in position 0 of the result we find the value
+ if column == 'sigma contour length':
+ self.multipliers[column] = self.multipliers['Contour length']
+ elif column == 'sigma persistence length':
+ self.multipliers[column] = self.multipliers['Persistence length']
+ else:
+ self.multipliers[column] = prettyformat.get_multiplier(result[column])
+ self.has_multipliers = True
+ else:
+ self.has_multipliers = False
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Status bar for Hooke.
"""
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Define the `@callback` decorator.
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
"""Define :func:`caller_name`.
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
-# Copyright\r
-\r
-"""Define functions for handling numbers in SI notation.\r
-\r
-Notes\r
------\r
-To output a scale, choose any value on the axis and find the\r
-multiplier and prefix for it. Use those to format the rest of the\r
-scale. As values can span several orders of magnitude, you have\r
-to decide what units to use.\r
-\r
->>> xs = (985e-12, 1e-9, 112358e-12)\r
-\r
-Get the power from the first (or last, or middle, ...) value\r
-\r
->>> p = get_power(xs[0])\r
->>> for x in xs:\r
-... print ppSI(x, decimals=2, power=p)\r
-985.00 p\r
-1000.00 p\r
-112358.00 p\r
->>> print prefix_from_value(xs[0]) + 'N'\r
-pN\r
-"""\r
-\r
-import math\r
-from numpy import isnan\r
-import re\r
-\r
-\r
-PREFIX = {\r
- 24: 'Y',\r
- 21: 'Z',\r
- 18: 'E',\r
- 15: 'P',\r
- 12: 'T',\r
- 9: 'G',\r
- 6: 'M',\r
- 3: 'k',\r
- 0: '',\r
- -3: 'm',\r
- -6: u'\u00B5',\r
- -9: 'n',\r
- -12: 'p',\r
- -15: 'f',\r
- -18: 'a',\r
- -21: 'z',\r
- -24: 'y',\r
- }\r
-"""A dictionary of SI prefixes from 10**24 to 10**-24.\r
-\r
-Examples\r
---------\r
->>> PREFIX[0]\r
-''\r
->>> PREFIX[6]\r
-'M'\r
->>> PREFIX[-9]\r
-'n'\r
-"""\r
-\r
-_DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')\r
-"""Used by :func:`data_label_unit`.\r
-"""\r
-\r
-\r
-def ppSI(value, unit='', decimals=None, power=None, pad=False):\r
- """Pretty-print `value` in SI notation.\r
-\r
- The current implementation ignores `pad` if `decimals` is `None`.\r
-\r
- Examples\r
- --------\r
- >>> x = math.pi * 1e-8\r
- >>> print ppSI(x, 'N')\r
- 31.415927 nN\r
- >>> print ppSI(x, 'N', 3)\r
- 31.416 nN\r
- >>> print ppSI(x, 'N', 4, power=-12)\r
- 31415.9265 pN\r
- >>> print ppSI(x, 'N', 5, pad=True)\r
- 31.41593 nN\r
-\r
- If you want the decimal indented by six spaces with `decimal=2`,\r
- `pad` should be the sum of\r
-\r
- * 6 (places before the decimal point)\r
- * 1 (length of the decimal point)\r
- * 2 (places after the decimal point)\r
-\r
- >>> print ppSI(-x, 'N', 2, pad=(6+1+2))\r
- -31.42 nN\r
- """\r
- if value == 0:\r
- return '0'\r
- if isnan(value):\r
- return 'NaN'\r
-\r
- if power == None: # auto-detect power\r
- power = get_power(value)\r
-\r
- if decimals == None:\r
- format = lambda n: '%f' % n\r
- else:\r
- if pad == False: # no padding\r
- format = lambda n: '%.*f' % (decimals, n) \r
- else:\r
- if pad == True: # auto-generate pad\r
- # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.\r
- pad = 6 + decimals\r
- format = lambda n: '%*.*f' % (pad, decimals, n)\r
- return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)\r
-\r
-\r
-def get_power(value):\r
- """Return the SI power for which `0 <= |value|/10**pow < 1000`. \r
- \r
- Exampes\r
- -------\r
- >>> get_power(0)\r
- 0\r
- >>> get_power(123)\r
- 0\r
- >>> get_power(-123)\r
- 0\r
- >>> get_power(1e8)\r
- 6\r
- >>> get_power(1e-16)\r
- -18\r
- """\r
- if value != 0 and not isnan(value):\r
- # get log10(|value|)\r
- value_temp = math.floor(math.log10(math.fabs(value)))\r
- # reduce the log10 to a multiple of 3\r
- return int(value_temp - (value_temp % 3))\r
- else:\r
- return 0\r
-\r
-def prefix_from_value(value):\r
- """Determine the SI power of `value` and return its prefix.\r
-\r
- Examples\r
- --------\r
- >>> prefix_from_value(0)\r
- ''\r
- >>> prefix_from_value(1e10)\r
- 'G'\r
- """\r
- return PREFIX[get_power(value)]\r
-\r
-def split_data_label(label):\r
- """Split `curve.data[i].info['name']` labels into `(name, unit)`.\r
-\r
- Examples\r
- --------\r
- >>> split_data_label('z piezo (m)')\r
- ('z piezo', 'm')\r
- >>> split_data_label('deflection (N)')\r
- ('deflection', 'N')\r
- """\r
- m = _DATA_LABEL_REGEXP.match(label)\r
- assert m != None, label\r
- return m.groups()\r
+# Copyright (C) 2010 Massimo Sandal <devicerandom@gmail.com>
+# Rolf Schmidt <rschmidt@alcor.concordia.ca>
+# W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Define functions for handling numbers in SI notation.
+
+Notes
+-----
+To output a scale, choose any value on the axis and find the
+multiplier and prefix for it. Use those to format the rest of the
+scale. As values can span several orders of magnitude, you have
+to decide what units to use.
+
+>>> xs = (985e-12, 1e-9, 112358e-12)
+
+Get the power from the first (or last, or middle, ...) value
+
+>>> p = get_power(xs[0])
+>>> for x in xs:
+... print ppSI(x, decimals=2, power=p)
+985.00 p
+1000.00 p
+112358.00 p
+>>> print prefix_from_value(xs[0]) + 'N'
+pN
+"""
+
+import math
+from numpy import isnan
+import re
+
+
+PREFIX = {
+ 24: 'Y',
+ 21: 'Z',
+ 18: 'E',
+ 15: 'P',
+ 12: 'T',
+ 9: 'G',
+ 6: 'M',
+ 3: 'k',
+ 0: '',
+ -3: 'm',
+ -6: u'\u00B5',
+ -9: 'n',
+ -12: 'p',
+ -15: 'f',
+ -18: 'a',
+ -21: 'z',
+ -24: 'y',
+ }
+"""A dictionary of SI prefixes from 10**24 to 10**-24.
+
+Examples
+--------
+>>> PREFIX[0]
+''
+>>> PREFIX[6]
+'M'
+>>> PREFIX[-9]
+'n'
+"""
+
+_DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')
+"""Used by :func:`data_label_unit`.
+"""
+
+
+def ppSI(value, unit='', decimals=None, power=None, pad=False):
+ """Pretty-print `value` in SI notation.
+
+ The current implementation ignores `pad` if `decimals` is `None`.
+
+ Examples
+ --------
+ >>> x = math.pi * 1e-8
+ >>> print ppSI(x, 'N')
+ 31.415927 nN
+ >>> print ppSI(x, 'N', 3)
+ 31.416 nN
+ >>> print ppSI(x, 'N', 4, power=-12)
+ 31415.9265 pN
+ >>> print ppSI(x, 'N', 5, pad=True)
+ 31.41593 nN
+
+ If you want the decimal indented by six spaces with `decimal=2`,
+ `pad` should be the sum of
+
+ * 6 (places before the decimal point)
+ * 1 (length of the decimal point)
+ * 2 (places after the decimal point)
+
+ >>> print ppSI(-x, 'N', 2, pad=(6+1+2))
+ -31.42 nN
+ """
+ if value == 0:
+ return '0'
+ if isnan(value):
+ return 'NaN'
+
+ if power == None: # auto-detect power
+ power = get_power(value)
+
+ if decimals == None:
+ format = lambda n: '%f' % n
+ else:
+ if pad == False: # no padding
+ format = lambda n: '%.*f' % (decimals, n)
+ else:
+ if pad == True: # auto-generate pad
+ # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.
+ pad = 6 + decimals
+ format = lambda n: '%*.*f' % (pad, decimals, n)
+ return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)
+
+
+def get_power(value):
+ """Return the SI power for which `0 <= |value|/10**pow < 1000`.
+
+ Exampes
+ -------
+ >>> get_power(0)
+ 0
+ >>> get_power(123)
+ 0
+ >>> get_power(-123)
+ 0
+ >>> get_power(1e8)
+ 6
+ >>> get_power(1e-16)
+ -18
+ """
+ if value != 0 and not isnan(value):
+ # get log10(|value|)
+ value_temp = math.floor(math.log10(math.fabs(value)))
+ # reduce the log10 to a multiple of 3
+ return int(value_temp - (value_temp % 3))
+ else:
+ return 0
+
+def prefix_from_value(value):
+ """Determine the SI power of `value` and return its prefix.
+
+ Examples
+ --------
+ >>> prefix_from_value(0)
+ ''
+ >>> prefix_from_value(1e10)
+ 'G'
+ """
+ return PREFIX[get_power(value)]
+
+def split_data_label(label):
+ """Split `curve.data[i].info['name']` labels into `(name, unit)`.
+
+ Examples
+ --------
+ >>> split_data_label('z piezo (m)')
+ ('z piezo', 'm')
+ >>> split_data_label('deflection (N)')
+ ('deflection', 'N')
+ """
+ m = _DATA_LABEL_REGEXP.match(label)
+ assert m != None, label
+ return m.groups()
-# Copyright
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# This file is part of Hooke.
+#
+# Hooke is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Hooke is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Hooke. If not, see
+# <http://www.gnu.org/licenses/>.
class Closing (object):
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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
#
# This file is part of Hooke.
#
-# Hooke is free software: you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General 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