another distcheck fix
[debian/gnuradio] / grc / src / grc / gui / elements / Param.py
1 """
2 Copyright 2007 Free Software Foundation, Inc.
3 This file is part of GNU Radio
4
5 GNU Radio Companion is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 GNU Radio Companion is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18 """
19 ##@package grc.gui.elements.Param
20 #GTK objects for handling input and the signal block parameter class.
21
22 import Utils
23 from Element import Element
24 import pygtk
25 pygtk.require('2.0')
26 import gtk
27 import pango
28 import gobject
29 from grc.Constants import *
30 from os import path
31
32 ######################################################################################################
33 # gtk objects for handling input
34 ######################################################################################################
35
36 class InputParam(gtk.HBox):
37         """The base class for an input parameter inside the input parameters dialog."""
38
39         def __init__(self, param, _handle_changed):
40                 gtk.HBox.__init__(self)
41                 self.param = param
42                 self._handle_changed = _handle_changed
43                 self.label = gtk.Label('') #no label, markup is added by set_markup
44                 self.label.set_size_request(150, -1)
45                 self.pack_start(self.label, False)
46                 self.set_markup = lambda m: self.label.set_markup(m)
47                 self.tp = None
48
49 class EntryParam(InputParam):
50         """Provide an entry box for strings and numbers."""
51
52         def __init__(self, *args, **kwargs):
53                 InputParam.__init__(self, *args, **kwargs)
54                 self.entry = input = gtk.Entry()
55                 input.set_text(self.param.get_value())
56                 input.connect('changed', self._handle_changed)
57                 self.pack_start(input, True)
58                 self.get_text = input.get_text
59                 #tool tip
60                 self.tp = gtk.Tooltips()
61                 self.tp.set_tip(self.entry, '')
62                 self.tp.enable()
63
64 class FileParam(EntryParam):
65         """Provide an entry box for filename and a button to browse for a file."""
66
67         def __init__(self, *args, **kwargs):
68                 EntryParam.__init__(self, *args, **kwargs)
69                 input = gtk.Button('...')
70                 input.connect('clicked', self._handle_clicked)
71                 self.pack_start(input, False)
72
73         def _handle_clicked(self, widget=None):
74                 """
75                 If the button was clicked, open a file dialog in open/save format.
76                 Replace the text in the entry with the new filename from the file dialog.
77                 """
78                 file_path = self.param.is_valid() and self.param.evaluate() or ''
79                 #bad file paths will be redirected to default
80                 if not path.exists(path.dirname(file_path)): file_path = DEFAULT_FILE_PATH
81                 if self.param.get_type() == 'file_open':
82                         file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
83                                 gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
84                 elif self.param.get_type() == 'file_save':
85                         file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
86                                 gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
87                         file_dialog.set_do_overwrite_confirmation(True)
88                         file_dialog.set_current_name(path.basename(file_path)) #show the current filename
89                 file_dialog.set_current_folder(path.dirname(file_path)) #current directory
90                 file_dialog.set_select_multiple(False)
91                 file_dialog.set_local_only(True)
92                 if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
93                         file_path = file_dialog.get_filename() #get the file path
94                         self.entry.set_text(file_path)
95                         self._handle_changed()
96                 file_dialog.destroy() #destroy the dialog
97
98 class EnumParam(InputParam):
99         """Provide an entry box for Enum types with a drop down menu."""
100
101         def __init__(self, *args, **kwargs):
102                 InputParam.__init__(self, *args, **kwargs)
103                 input = gtk.ComboBox(gtk.ListStore(gobject.TYPE_STRING))
104                 cell = gtk.CellRendererText()
105                 input.pack_start(cell, True)
106                 input.add_attribute(cell, 'text', 0)
107                 for option in self.param.get_options(): input.append_text(option.get_name())
108                 input.set_active(int(self.param.get_option_keys().index(self.param.get_value())))
109                 input.connect("changed", self._handle_changed)
110                 self.pack_start(input, False)
111                 self.get_text = lambda: str(input.get_active()) #the get text parses the selected index to a string
112
113 ######################################################################################################
114 # A Flow Graph Parameter
115 ######################################################################################################
116
117 class Param(Element):
118         """The graphical parameter."""
119
120         def update(self):
121                 """!
122                 Called when an external change occurs.
123                 Update the graphical input by calling the change handler.
124                 """
125                 if hasattr(self, 'input'): self._handle_changed()
126
127         def get_input_object(self, callback=None):
128                 """!
129                 Get the graphical gtk class to represent this parameter.
130                 Create the input object with this data type and the handle changed method.
131                 @param callback a function of one argument(this param) to be called from the change handler
132                 @return gtk input object
133                 """
134                 self.callback = callback
135                 if self.is_enum(): input = EnumParam
136                 elif self.get_type() in ('file_open', 'file_save'): input = FileParam
137                 else: input = EntryParam
138                 self.input = input(self, self._handle_changed)
139                 return self.input
140
141         def _handle_changed(self, widget=None):
142                 """!
143                 When the input changes, write the inputs to the data type.
144                 Finish by calling the exteral callback.
145                 """
146                 value = self.input.get_text()
147                 if self.is_enum(): value = self.get_option_keys()[int(value)]
148                 self.set_value(value)
149                 #set the markup on the label, red for errors in corresponding data type.
150                 name = '<span font_desc="%s">%s</span>'%(PARAM_LABEL_FONT, Utils.xml_encode(self.get_name()))
151                 #special markups if param is involved in a callback
152                 if hasattr(self.get_parent(), 'get_callbacks') and \
153                         filter(lambda c: self.get_key() in c, self.get_parent()._callbacks):
154                         name = '<span underline="low">%s</span>'%name
155                 if not self.is_valid():
156                         self.input.set_markup('<span foreground="red">%s</span>'%name)
157                         tip = '- ' + '\n- '.join(self.get_error_messages())
158                 else:
159                         self.input.set_markup(name)
160                         tip = self.evaluate()
161                 #hide/show
162                 if self.get_hide() == 'all': self.input.hide_all()
163                 else: self.input.show_all()
164                 #set the tooltip
165                 if self.input.tp: self.input.tp.set_tip(self.input.entry, str(tip))
166                 #execute the external callback
167                 if self.callback: self.callback(self)
168
169         def get_markup(self):
170                 """!
171                 Create a markup to display the Param as a label on the SignalBlock.
172                 If the data type is an Enum type, use the cname of the Enum's current choice.
173                 Otherwise, use parsed the data type and use its string representation.
174                 If the data type is not valid, use a red foreground color.
175                 @return pango markup string
176                 """
177                 ###########################################################################
178                 # display logic for numbers
179                 ###########################################################################
180                 def float_to_str(var):
181                         if var-int(var) == 0: return '%d'%int(var)
182                         if var*10-int(var*10) == 0: return '%.1f'%var
183                         if var*100-int(var*100) == 0: return '%.2f'%var
184                         if var*1000-int(var*1000) == 0: return '%.3f'%var
185                         else: return '%.3g'%var
186                 def to_str(var):
187                         if isinstance(var, str): return var
188                         elif isinstance(var, complex):
189                                 if var.imag == var.real == 0: return '0' #value is zero
190                                 elif var.imag == 0: return '%s'%float_to_str(var.real) #value is real
191                                 elif var.real == 0: return '%sj'%float_to_str(var.imag) #value is imaginary
192                                 elif var.imag < 0: return '%s-%sj'%(float_to_str(var.real), float_to_str(var.imag*-1))
193                                 else: return '%s+%sj'%(float_to_str(var.real), float_to_str(var.imag))
194                         elif isinstance(var, float): return float_to_str(var)
195                         elif isinstance(var, int): return '%d'%var
196                         else: return str(var)
197                 ###########################################################################
198                 if self.is_valid():
199                         data = self.evaluate()
200                         t = self.get_type()
201                         if self.is_enum():
202                                 dt_str = self.get_option(self.get_value()).get_name()
203                         elif isinstance(data, (list, tuple, set)): #vector types
204                                 dt_str = ', '.join(map(to_str, data))
205                         else: dt_str = to_str(data)     #other types
206                         #truncate
207                         max_len = max(42 - len(self.get_name()), 3)
208                         if len(dt_str) > max_len:
209                                 dt_str = dt_str[:max_len-3] + '...'
210                         return '<b>%s:</b> %s'%(Utils.xml_encode(self.get_name()), Utils.xml_encode(dt_str))
211                 else: return '<span foreground="red"><b>%s:</b> error</span>'%Utils.xml_encode(self.get_name())
212
213         def get_layout(self):
214                 """!
215                 Create a layout based on the current markup.
216                 @return the pango layout
217                 """
218                 layout = gtk.DrawingArea().create_pango_layout('')
219                 layout.set_markup(self.get_markup())
220                 desc = pango.FontDescription(PARAM_FONT)
221                 layout.set_font_description(desc)
222                 return layout
223