Skip to content

Commit 683ff06

Browse files
author
aglavic
committed
Automatic dialog creation working.
1 parent a28f682 commit 683ff06

File tree

8 files changed

+249
-15
lines changed

8 files changed

+249
-15
lines changed

.settings/org.eclipse.core.resources.prefs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ encoding//plot_script/parallel.py=utf-8
5858
encoding//plot_script/peakfinder.py=utf8
5959
encoding//plot_script/plotpy_info.py=utf-8
6060
encoding//plot_script/plotting_debug.py=utf-8
61+
encoding//plot_script/plugins/__init__.py=utf-8
6162
encoding//plot_script/read_data/__init__.py=utf-8
6263
encoding//plot_script/read_data/circle.py=utf-8
6364
encoding//plot_script/read_data/dns.py=utf-8
@@ -89,6 +90,8 @@ encoding//plot_script/sessions/squid.py=utf-8
8990
encoding//plot_script/sessions/templates.py=utf-8
9091
encoding//plot_script/sessions/treff.py=utf-8
9192
encoding//sphinx/conf.py=utf-8
93+
encoding//tests/datatype.py=utf8
94+
encoding//tests/main_gui.py=utf8
9295
encoding/import_debug.py=utf-8
9396
encoding/plot.py=utf-8
9497
encoding/setup.py=utf-8

plot.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
##---add_python_path_here---## # Place holder to add installation directory to python path for non superuser installation.
88

99
import plot_script
10+
import sys
11+
import os
1012

1113
if __name__=='__main__':
12-
plot_script._run()
14+
if '--profile' in sys.argv:
15+
# code profiling run
16+
sys.argv.remove('--profile')
17+
import cProfile
18+
cProfile.run('plot_script._run()', os.path.join(os.path.split(__file__)[0], 'plot.py.profile'))
19+
else:
20+
plot_script._run()

plot_script/gtkgui/autodialogs.py

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ def _analyze_function(self, function):
6666
defaults and types.
6767
'''
6868
argspec=inspect.getargspec(function)
69-
if argspec.args[0]!='dataset' or len(argspec.args)!=(1+len(argspec.defaults)):
69+
skip_items=len(argspec.args)-len(argspec.defaults)
70+
if skip_items==0:
7071
raise ValueError, "function does not have the right form"
7172
parameters=[]
7273
doc_lines=function.__doc__.splitlines()
7374
doc_lines=map(str.strip, doc_lines)
74-
for arg, default in zip(argspec.args[1:], argspec.defaults):
75+
for arg, default in zip(argspec.args[skip_items:], argspec.defaults):
7576
item={'arg':arg, 'name': arg, 'description': '',
7677
'type': type(default), 'default': default,
7778
'numrange': (-1e30, 1e30)}
@@ -134,3 +135,115 @@ def get_result(self):
134135
elif item['type'] is list:
135136
result[item['arg']]=entry.list_link
136137
return result
138+
139+
class FunctionHandler(object):
140+
'''
141+
Class to store and process functions to be used with AutoDialog creation.
142+
The class is not instantiated, calling it will add a function to as
143+
the FunctionHandler.add_function method does.
144+
145+
Functions are added using:
146+
FunctionHandler.add_function(function, menu_entry=None,
147+
description=None, shortcut=None)
148+
'''
149+
# for easier reading of the code, the convention using cls for classmethods
150+
# is replaced by using self instead
151+
registered_functions=[]
152+
main_gui=None
153+
154+
def __new__(self, function, menu_entry=None, description=None, shortcut=None):
155+
return self.add_function(function, menu_entry, description, shortcut)
156+
157+
@classmethod
158+
def add_function(self, function, menu_entry=None, description=None, shortcut=None):
159+
'''
160+
Add a function to the list of available objects.
161+
'''
162+
if menu_entry is None:
163+
# if no menu entry is supplied, use the functions name
164+
# replacing underscores with spaces and using the first letter
165+
# in upper case
166+
fname=function.__name__
167+
menu_entry=fname[0].upper()+fname[1:].replace('_', ' ')+'...'
168+
if not self.check_function(function):
169+
raise ValueError, 'function does not have the right format'
170+
if function.__doc__ is None:
171+
function.__doc__=''
172+
self.registered_functions.append((function, menu_entry, description, shortcut))
173+
174+
@classmethod
175+
def check_function(self, function):
176+
'''
177+
Check the validity of the function argument specifications.
178+
'''
179+
argspec=inspect.getargspec(function)
180+
args=argspec.args
181+
if args[0]=='dataset':
182+
if len(args)!=(1+len(argspec.defaults)):
183+
return False
184+
return True
185+
elif args[0]=='datasets':
186+
if args[1]!='d_index' or len(args)!=(2+len(argspec.defaults)):
187+
return False
188+
return True
189+
else:
190+
return False
191+
192+
@classmethod
193+
def get_menu_string(self):
194+
if len(self.registered_functions)==0:
195+
return ''
196+
else:
197+
output=''' </menu>
198+
<menu action='UserAddedMenu'>
199+
'''
200+
for function, ignore, ignore, ignore in self.registered_functions:
201+
output+=''' <menuitem action='%s'/>
202+
'''%function.__name__.replace('_', '')
203+
return output
204+
205+
206+
@classmethod
207+
def get_menu_actions(self):
208+
if len(self.registered_functions)==0:
209+
return ()
210+
else:
211+
output=[("UserAddedMenu", None, "User-Func.", None, None, None), ]
212+
for function, menu_entry, ignore, shortcut in self.registered_functions:
213+
output.append(
214+
(function.__name__.replace('_', ''), None,
215+
menu_entry, shortcut,
216+
None, self.call_function)
217+
)
218+
return tuple(output)
219+
220+
@classmethod
221+
def call_function(self, action, main_window):
222+
'''
223+
Open AutoDialog and run the function with the given parameters.
224+
'''
225+
action_name=action.get_name()
226+
action_names=[item[0].__name__.replace('_', '') for item in self.registered_functions]
227+
action_index=action_names.index(action_name)
228+
function, menu_entry, description, ignore=self.registered_functions[action_index]
229+
dialog=AutoDialog(function, description_text=description, title=menu_entry)
230+
result=dialog.run()
231+
if result:
232+
datasets=main_window.active_session.active_file_data
233+
d_index=main_window.index_mess
234+
argspec=inspect.getargspec(function)
235+
if argspec.args[0]=='dataset':
236+
result=function(datasets[d_index], **dialog.get_result())
237+
else:
238+
result=function(datasets, d_index, **dialog.get_result())
239+
if result is None:
240+
main_window.rebuild_menus()
241+
main_window.replot()
242+
else:
243+
datasets.append(result)
244+
main_window.index_mess=len(datasets)-1
245+
main_window.rebuild_menus()
246+
main_window.replot()
247+
dialog.destroy()
248+
249+

plot_script/gtkgui/main_window.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,9 @@ def main_quit(self, action=None, store_config=True):
579579
# exit persistent gnuplot instances
580580
persistent_plot_instances=measurement_data_plotting.persistent_plot_instances
581581
for p in persistent_plot_instances:
582-
p.stdin.write('quit\n')
583-
p.stdin.flush()
582+
if p.stdin.is_open():
583+
p.stdin.write('quit\n')
584+
p.stdin.flush()
584585
p.communicate()
585586
# save settings to ini file
586587
if store_config:

plot_script/gtkgui/main_window_actions.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def add_multiplot(self, action):
366366
self.do_add_multiplot(self.index_mess)
367367
elif action.get_name()=='AddMultiplot':
368368
self.multiplot.sort_add()
369-
369+
370370
def toggle_multiplot_copymode(self, action):
371371
'''
372372
Toggle between copy and non-copy mode in multiplot.
@@ -552,7 +552,7 @@ def action_history(self, action):
552552

553553
def open_tabdialog(self, action=None):
554554
dia=NotebookDialog(self, title='Plot.py Notebook')
555-
dia.set_default_size(600,400)
555+
dia.set_default_size(600, 400)
556556
dia.show()
557557

558558
def open_ipy_console(self, action=None, commands=[], show_greetings=True):
@@ -575,6 +575,7 @@ def open_ipy_console(self, action=None, commands=[], show_greetings=True):
575575
scipy=None
576576
from glob import glob
577577
from plot_script.fit_data import new_function
578+
from plot_script.gtkgui.autodialogs import FunctionHandler
578579

579580
if getattr(self, 'active_ipython', False):
580581
# if there is already an ipython console, show it and exit
@@ -607,15 +608,13 @@ def open_ipy_console(self, action=None, commands=[], show_greetings=True):
607608
608609
Functions:
609610
replot \tFunction to replot the dataset
610-
dataset \tFunction to get the active MeasurementData object
611-
getxyz/\tReturn 3/all PhysicalProperty instances of the x,y and z columns
612-
getall\tfrom the active dataset
611+
dataset \tGet the active MeasurementData object (see attributes x,y,z,data)
613612
newxyz/ \tCreate a new plot with changed columns, takes three/several lists or
614613
newall\tarrays as input. For line plots the last parameter is 'None'.
615-
mapdata \tApply a function to all datasets in the active file data
616-
mapall \tApply a function to all datasets from all files
614+
mapdata \tApply a function to all datasets in the active file data (see mapall)
617615
newfit \tAdd a function to the fit dialog functions, should be defined as
618616
\tither f(p,x) of f(p,x,y) for 2d or 3d datasets respectively.
617+
newmenu \tAdd a menu entry which opens a dialog for parameter entry (see newmenu?)
619618
makefit \tClass which holds all fittable functions as properties. To fit
620619
\te.g. a linear regression to the current dataset use:
621620
\tmakefit.Linear_Regression([0.5, 2]) (-> parameters after fit)
@@ -626,7 +625,7 @@ def open_ipy_console(self, action=None, commands=[], show_greetings=True):
626625
\tThe data for the selected file is in session.active_file_data
627626
plot_gui \tThe window object with all window related funcitons
628627
macros \tDictionary containing all functions which can be run as 'macros'
629-
menus \tAccess all GUI menus as properties of this object, e.g. "menus.File.Open_File()".
628+
menus \tAccess all GUI menus as properties of this object.
630629
action_history \tList of macros activated through the GUI (key, (parameters)).
631630
Modules:
632631
np \tNumpy
@@ -727,6 +726,38 @@ def mapall(function):
727726
for dataset in datasets:
728727
output[key].append(function(dataset))
729728
return output
729+
def newmenu(function, menu_entry=None, description=None, shortcut=None):
730+
'''
731+
Create an entry in the "User-Func." menu which opens a parameter
732+
dialog and after that calls the function. The dialog is created
733+
directly from function inspection so the function needs to have
734+
one of the following two forms:
735+
736+
my_function(dataset, param1=12., param2=23, param3='abc')
737+
my_function(datasets, d_index, param1=12., param2=23, param3='abc')
738+
739+
Where the naming convention of dataset/datasets/d_index is fixed
740+
while the parameters can be named as desired. Parameters must be
741+
ither int, float, str or list of only one type of these.
742+
The docstring of the function can be used to further change the
743+
dialog appearance of the parameters by supplieng lines of type:
744+
745+
:param param1: [lower_bound:upper_bound] - param item name - description
746+
or
747+
:param param1: param item name - description
748+
or
749+
:param param1: description
750+
751+
If this is not supplied the dialog will use the parameter names and leave
752+
empty descriptions.
753+
754+
The optional arguments to newmenu can define the name of the menu item,
755+
a descriptive text at the top of the dialog and a keystroke shortcut
756+
as "<control>U".
757+
'''
758+
FunctionHandler.add_function(function, menu_entry, description, shortcut)
759+
self.rebuild_menus()
760+
730761
# add variables to ipython namespace
731762
ipview.updateNamespace({
732763
'session': self.active_session,
@@ -750,6 +781,7 @@ def mapall(function):
750781
'menus': MenuWrapper(self.menu_bar),
751782
'newfit': new_function,
752783
'makefit': FitWrapper(self, self.active_session),
784+
'newmenu': newmenu,
753785
})
754786
# add common mathematic functions to the namespace
755787
math_functions=['exp', 'log', 'log10', 'pi',

plot_script/gtkgui/main_window_ui.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import gobject
99
from main_window_actions import apihelp
1010
from plot_script.config import gui as gui_config
11+
from autodialogs import FunctionHandler
1112

1213

1314
__author__="Artur Glavic"
@@ -332,6 +333,8 @@ def build_menu(self):
332333
<menu action='PluginMenu'>
333334
'''+plugin_menu
334335
self.session_added_items=self.session_added_items+(("PluginMenu", None, "Plugins", None, None, None),)
336+
output+=FunctionHandler.get_menu_string()
337+
self.session_added_items=self.session_added_items+FunctionHandler.get_menu_actions()
335338
output+='''
336339
</menu>
337340
</menubar>
@@ -791,7 +794,7 @@ def replace_icon(self, item, imgfile):
791794
height=buf.get_height()
792795
scale=min(size/width, size/height)
793796
buf=buf.scale_simple(int(width*scale), int(height*scale), gtk.gdk.INTERP_BILINEAR)
794-
797+
795798
for i in range(2):
796799
toolbutton=self.UIManager.get_widget('/ui/ToolBar%i/'%(i+1)+item)
797800
if toolbutton is not None:

tests/datatype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
'''
55

66
from plot_script.measurement_data_structure import PhysicalProperty
7-
from numpy import *
7+
from numpy import array, ndarray, arange, float32, sqrt, zeros_like, ones_like, pi, sin
88
import unittest
99

1010
class TestPhysicalProperty(unittest.TestCase):

tests/main_gui.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#-*- coding: utf8 -*-
2+
'''
3+
Testing the main window.
4+
'''
5+
6+
import unittest
7+
import os
8+
import sys
9+
import gtk
10+
from plot_script.sessions.circle import CircleSession
11+
from plot_script.gtkgui.main_window import ApplicationMainWindow
12+
13+
class TestMainWindow(unittest.TestCase):
14+
'''
15+
Check the behavior of the base data holding array type.
16+
'''
17+
18+
@classmethod
19+
def setUpClass(self):
20+
self.session=CircleSession(['/home/glavic/inplan_2plane_radial_scans.spec'])
21+
self.gui=ApplicationMainWindow(self.session)
22+
23+
@classmethod
24+
def tearDownClass(self):
25+
self.gui.main_quit()
26+
while gtk.events_pending():
27+
gtk.main_iteration_do(block=False)
28+
29+
def setUp(self):
30+
while gtk.events_pending():
31+
gtk.main_iteration_do(block=False)
32+
33+
def tearDown(self):
34+
while gtk.events_pending():
35+
gtk.main_iteration_do(block=False)
36+
37+
def test_1show(self):
38+
self.assertTrue(self.gui.get_visible(), 'GUI is not shown')
39+
self.assertEqual(self.session, self.gui.active_session, 'Session not associated')
40+
41+
def test_2image_created(self):
42+
image_file=self.session.TEMP_DIR+'plot_temp.png'
43+
self.assertTrue(os.path.exists(image_file), 'Plot image not created')
44+
45+
def test_3persistent(self):
46+
self.gui.plot_persistent()
47+
48+
def test_4ipython(self):
49+
self.gui.open_ipy_console()
50+
self.assertNotEqual(False, getattr(self.gui, 'active_ipython', False),
51+
'IPythonView not created')
52+
53+
def test_5ipython_close(self):
54+
self.gui.active_ipython.destroy()
55+
self.assertNotEqual(False, getattr(self.gui, 'ipython_user_namespace', False),
56+
'Namspace not saved')
57+
self.assertNotEqual(False, getattr(self.gui, 'ipython_user_history', False),
58+
'History not saved')
59+
60+
def test_6opow(self):
61+
self.gui.open_plot_options_window(None)
62+
63+
def test_7cpow(self):
64+
self.gui.open_plot_options_window(None)
65+
66+
def test_8slpp(self):
67+
self.gui.show_last_plot_params(None)
68+
69+
70+
if __name__=='__main__':
71+
#unittest.main()
72+
loader=unittest.TestLoader()
73+
suite=loader.loadTestsFromTestCase(TestMainWindow)
74+
unittest.TextTestRunner(verbosity=2).run(suite)

0 commit comments

Comments
 (0)