Baby Language Lab Scripts
A collection of data processing tools.
 All Classes Namespaces Files Functions Variables Pages
ui_utils.py
Go to the documentation of this file.
1 ## @package utils.ui_utils
2 
3 from gi.repository import Gtk as gtk, Pango
4 from gi.repository import GObject as gobject
5 from gi.repository import GdkPixbuf as pixbuf
6 
7 import os
8 import re
9 
10 from datetime import datetime
11 from dateutil import tz
12 from db.bll_database import DBConstants, BLLDatabase
13 from utils.backend_utils import BackendUtils
14 from utils.enum import Enum
15 
16 ## A collection of static methods and constants for common UI-related tasks.
17 # This class should never be instantiated.
18 class UIUtils(object):
19  #stores the last directory accessed by the user from a filechooser dialog (see the show_file_dialog() and show_file_dialog_with_checks() methods)
20  last_filechooser_location = None
21 
22  #These static members store gtk.FileFilter constants created by the _get_constants method() of the ui_utils module. They are populated when this file is processed by the Python iterpreter.
23  WAV_FILE_FILTER = None
24  TRS_FILE_FILTER = None
25  ALL_FILE_FILTER = None
26  CSV_FILE_FILTER = None
27  TRS_CSV_FILE_FILTER = None
28 
29  #this is a date-time format string (of the type accepted by the Python datetime.strptime() method - see Python docs) that corresponds to the format in which timestamps come out of the database.
30  DB_DT_OUTPUT_FMT = '%Y-%m-%d %H:%M:%S'
31  #this is a date-time format string that corresponds to the format in which timestamps should be shown in the UI. Timestamps coming out of the database are converted to this format by get_db_timestamp_str().
32  DT_DISPLAY_FMT = '%b %d, %Y %I:%M %p'
33 
34  #The UI contains lots of buttons that perform common actions. This is a lookup structure (an Enum) that maps actions to (the file path to) descriptive icons. You can use it to create buttons with icons on them.
35  #See the create_button() method for more info. The '%s' placeholder will be filled with the name of the directory corresponding to the desired size of the icon - this is done in create_button().
36  BUTTON_ICONS = Enum.from_dict({
37  'CREATE': 'icons/open_icon_library-standard/icons/png/%s/actions/document-new-8.png',
38  'OPEN': 'icons/open_icon_library-standard/icons/png/%s/actions/document-open-8.png',
39  'ADD': 'icons/open_icon_library-standard/icons/png/%s/actions/list-add-5.png',
40  'EDIT': 'icons/open_icon_library-standard/icons/png/%s/actions/edit.png',
41  'REMOVE': 'icons/open_icon_library-standard/icons/png/%s/actions/list-remove-5.png',
42  'DELETE': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-delete-3.png',
43  'RUN': 'icons/open_icon_library-standard/icons/png/%s/actions/system-run-2.png',
44  'CLOSE': 'icons/open_icon_library-standard/icons/png/%s/actions/application-exit-4.png',
45  'EXIT': 'icons/open_icon_library-standard/icons/png/%s/actions/application-exit.png',
46  'EXPAND': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-add-3.png',
47  'COLLAPSE': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-remove-2.png',
48  'REFRESH': 'icons/open_icon_library-standard/icons/png/%s/actions/view-refresh-6.png',
49  'PLAY': 'icons/open_icon_library-standard/icons/png/%s/apps/exaile-2.png',
50  'FLAG': 'icons/open_icon_library-standard/icons/png/%s/actions/flag.png',
51  'EXPORT': 'icons/open_icon_library-standard/icons/png/%s/actions/document-export.png',
52  'SPLIT': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-copy-9.png',
53  'MERGE': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-text-frame-update.png',
54  'CLEAR': 'icons/open_icon_library-standard/icons/png/%s/actions/edit-clear-2.png',
55  'FILTER': 'icons/open_icon_library-standard/icons/png/%s/actions/filter.png',
56  'PRAAT': 'icons/open_icon_library-standard/icons/png/%s/apps/praat.png',
57  'SAVE': 'icons/open_icon_library-standard/icons/png/%s/actions/document-save-as-6.png',
58  'UPDATE': 'icons/open_icon_library-standard/icons/png/%s/apps/system-software-update-4.png',
59  'VOLUME_OFF': 'icons/open_icon_library-standard/icons/png/%s/status/volume-0.png',
60  'VOLUME_ON': 'icons/open_icon_library-standard/icons/png/%s/status/audio-volume-high-5.png',
61  })
62 
63  #This enum allows you to select the size of the image you want for your button (see create_button()). The values correspond to the names of subdirectories in the 'bll_app/icons/png/' directory.
64  #The selected value is interpolated into the value from the BUTTON_ICONS enum (above) within the create_button() method.
65  BUTTON_ICON_SIZES = Enum.from_dict({
66  'PX8': '8x8',
67  'PX16': '16x16',
68  'PX22': '22x22',
69  'PX24': '24x24',
70  'PX32': '32x32',
71  'PX48': '48x48',
72  'PX64': '64x64',
73  'PX128': '128x128',
74  })
75 
76  ## Sets some common GTK properties that all of the apps use. This only needs to be called once at appliation startup.
77  # @param app_icon_filename (string) path to an image file. This icons will be used as the default application icon (appears in the top left-hand corner of all windows)
78  @staticmethod
79  def setup_gtk(app_icon_filename):
80  #allow images to appear on buttons
81  gtk.Settings.get_default().set_long_property('gtk-button-images', True, 'main')
82  #set default program icon
83  gtk.Window.set_default_icon_list([pixbuf.Pixbuf.new_from_file(app_icon_filename)])
84 
85  ## Sets the font size for a given widget
86  # @param widget (Gtk Container) the widget whose font size you'd like to set
87  # @param pt_size (int, possibly float too?) the font size (pt)
88  # @param bold (boolean=False) Set to True to make font bold
89  @staticmethod
90  def set_font_size(widget, pt_size, bold=False):
91  font = widget.get_style_context().get_font(gtk.StateFlags.NORMAL)
92  font.set_size(pt_size * Pango.SCALE)
93  if bold:
94  font.set_weight(Pango.Weight.BOLD)
95  widget.override_font(font)
96 
97  ## Constructs the full path to an icon, given a value from the BUTTON_ICONS enum, and a value from the BUTTON_ICON_SIZES enum.
98  # @param img_path (string) this must be a value from the BUTTON_ICONS enum
99  # @param icon_size_dir (string=BUTTON_ICON_SIZES.PX32) this must be a value from the BUTTON_ICON_SIZES enum. Size defaults to 32x32 pixels.
100  # @returns (string) the full path to the icon file
101  @staticmethod
102  def get_icon_path(img_path, icon_size_dir=BUTTON_ICON_SIZES.PX32):
103  return img_path % (icon_size_dir)
104 
105  ## Creates a gtk.Button with an icon on it. You can create predefined types of buttons (corresponding to common actions) of any given size by passing in parameters from this class' button enums.
106  # Note: in order for this to work, you must first tell gtk to allow images on button - this is done in the setup_gtk() method, so you should call that first.
107  # @param label (string) the text to display on the button.
108  # @param img_path (string) a value from the BUTTON_ICONS enum (indicates the button action)
109  # @param icon_size_dir (string=BUTTON_ICON_SIZES.PX32) a value from the BUTTON_ICON_SIZES enum, indicating the size of the icon that will be placed on the button. Default is 32x32 pixels.
110  # @returns (gtk.Button) a gtk.Button object with the specified text and image.
111  @staticmethod
112  def create_button(label, img_path, icon_size_dir=BUTTON_ICON_SIZES.PX32): #use BUTTON_ICONS enum for 2nd param, BUTTON_ICON_SIZES enum for 3rd param
113  button = gtk.Button(label)
114  img = gtk.Image()
115  full_path = UIUtils.get_icon_path(img_path, icon_size_dir)
116  img.set_from_file(full_path)
117  button.set_image(img)
118 
119  return button
120 
121  ## Displays a popup confirmation dialog with yes/no buttons. This method will block until one of the buttons is clicked, at which point it will return a boolean value (indicating which button was clicked) and the dialog will auto-destruct.
122  # @param msg (string) the confirmation message to display (should be a yes/no question so the buttons make sense)
123  # @returns (boolean) True if "yes" was clicked, False otherwise.
124  @staticmethod
126  dialog = gtk.MessageDialog(buttons=gtk.ButtonsType.YES_NO,
127  type=gtk.MessageType.INFO,
128  message_format=msg)
129  response = dialog.run()
130  dialog.destroy()
131 
132  return response == gtk.ResponseType.YES
133 
134  ## Displays a popup dialog (with an ok button) that tells the user to select a row (presumably in some type of UI list).
135  # This method blocks until the user clicks ok.
136  # @param alt_msg (string=None) alternate text to display in place of the default. The default is 'Please select a row.'
137  @staticmethod
138  def show_no_sel_dialog(alt_msg=None):
139  default_msg = 'Please select a row.'
140  UIUtils.show_message_dialog(default_msg or alt_msg)
141 
142  ## Displays a popup dialog (with an ok button) that tells the user to make sure all options in a form have been filled out.
143  # This method blocks until the user clicks ok.
144  # @param alt_msg (string=None) alternate text to display in place of the default. The default is 'Please make sure that all options have been filled out.'
145  @staticmethod
146  def show_empty_form_dialog(alt_msg=None):
147  default_msg = 'Please make sure that all options have been filled out.'
148  UIUtils.show_message_dialog(default_msg or alt_msg)
149 
150  ## Displays a popup dialog (with an ok button) that presents a textual message.
151  # This method blocks until the user clicks ok.
152  # @param message (string) the text to display in the dialog box.
153  # @param dialog_type (int) one of the gtk message type constants (see gtk docs for the MessageDialog class), indicating the type of icon to display in the dialog. This icon indicates whether the
154  # dialog is an info/question/warning/error message. Possible values are gtk.MESSAGE_INFO, gtk.MESSAGE_WARNING, gtk.MESSAGE_QUESTION, or gtk.MESSAGE_ERROR.
155  @staticmethod
156  def show_message_dialog(message, dialog_type=gtk.MessageType.INFO):
157  dialog = gtk.MessageDialog(buttons=gtk.ButtonsType.OK,
158  message_format=message,
159  type=dialog_type)
160 
161  #this call blocks until the OK button is clicked
162  dialog.run()
163  dialog.destroy()
164 
165  ## Creates a set of gtk.SpinButton inputs to allow the user to input a time in the format hh:mm:ss.
166  # These entries are packing into a horizontal gtk.HBox container, which is fitted with a label.
167  # Here's what the container looks like when it's displayed in the UI:
168  # <img src="../images/time_spinners.png">
169  # @param label (string=None) an optional label to pack at the left side of the container
170  # @param hours (int=0) value to default the hours spinbutton to
171  # @param mins (int=0) value to default the minutes spinbutton to
172  # @param secs (int=0) value to default the seconds spinbutton to
173  # @returns (tuple) returns a tuple of 4 items: (hbox_container, hours_spinbutton, mins_spinbutton, secs_spinbutton). The spinbuttons are already packed into the hbox container. They are returned individually as well just for convenience
174  # (so the caller can easily hook up signals without having to extract them from the container first).
175  @staticmethod
176  def get_time_spinners(label=None, hours=0, mins=0, secs=0):
177  entry_box = gtk.HBox()
178  entry_box.pack_start(gtk.Alignment(xalign=0.25, yalign=0), False, False, 0)
179 
180  if label:
181  entry_box.pack_start(label, False, False, 0)
182 
183  hours_adj = gtk.Adjustment(value=0, lower=0, upper=1000, step_incr=1, page_incr=5) #note: upper is a required param - set it to something that won't be exceeded
184  hours_spinner = gtk.SpinButton()
185  hours_spinner.set_adjustment(hours_adj)
186  hours_spinner.set_value(hours)
187  entry_box.pack_start(hours_spinner, False, False, 0)
188  entry_box.pack_start(gtk.Label(':'), False, False, 0)
189 
190  mins_adj = gtk.Adjustment(value=0, lower=0, upper=59, step_incr=1, page_incr=5)
191  mins_spinner = gtk.SpinButton()
192  mins_spinner.set_adjustment(mins_adj)
193  mins_spinner.set_value(mins)
194  entry_box.pack_start(mins_spinner, False, False, 0)
195  entry_box.pack_start(gtk.Label(':'), False, False, 0)
196 
197  secs_adj = gtk.Adjustment(value=0, lower=0, upper=59, step_incr=1, page_incr=5)
198  secs_spinner = gtk.SpinButton()
199  secs_spinner.set_adjustment(secs_adj)
200  secs_spinner.set_value(secs)
201  entry_box.pack_start(secs_spinner, False, False, 0)
202  entry_box.pack_start(gtk.Alignment(xalign=0.25, yalign=0), False, False, 0)
203 
204  return entry_box, hours_spinner, mins_spinner, secs_spinner
205 
206  ## Returns a string containing the current date and time.
207  # @returns (string) the current timestamp, formatted according to the UIUtils.DT_DISPLAY_FMT pattern.
208  @staticmethod
210  return datetime.now().strftime(UIUtils.DT_DISPLAY_FMT)
211 
212  ## Accepts a string representing a datetime that was retrieved from the database, and converts it into a format suitable for display in the UI.
213  # @param timestamp (string) a timestamp string (retrieved from the DB) in the format UIUtils.DB_DT_OUTPUT_FMT
214  # @returns (string) a timestamp string in the format UIUtils.DT_DISPLAY_FMT
215  @staticmethod
216  def get_db_timestamp_str(timestamp):
217  return datetime.strptime(timestamp, UIUtils.DB_DT_OUTPUT_FMT).strftime(UIUtils.DT_DISPLAY_FMT)
218 
219  @staticmethod
220  def utc_to_local_str(utc_timestamp):
221  from_zone = tz.tzutc()
222  to_zone = tz.tzlocal()
223 
224  # utc = datetime.utcnow()
225  utc = datetime.strptime(utc_timestamp, UIUtils.DB_DT_OUTPUT_FMT)
226 
227  # Tell the datetime object that it's in UTC time zone since
228  # datetime objects are 'naive' by default
229  utc = utc.replace(tzinfo=from_zone)
230 
231  # Convert time zone
232  local = utc.astimezone(to_zone)
233 
234  return local.strftime(UIUtils.DT_DISPLAY_FMT)
235 
236  ## Constructs a gtk.FileFilter object for a set of file extensions.
237  # @param title (string) the title for this filer. This title is displayed in the dropdown combobox of file types in filechooser dialogs
238  # @param patterns (list) a list of strings, where each is a shell-expandible file extension (like '*.wav' or '*.trs')
239  # @return (gtk.FileFilter) a gtk.FileFilter object that can be passed to a filechooser dialog
240  @staticmethod
241  def build_file_filter(title, patterns):
242  file_filter = gtk.FileFilter()
243  file_filter.set_name(title)
244  map(file_filter.add_pattern, patterns)
245 
246  return file_filter
247 
248  ## Builds a simple combo box. Each entry has a title and a value.
249  # The title is displayed, the value is hidden to the user. Here's what it looks like:
250  # <img src="../images/simple_combo.png">
251  # By default, the first option is selected. Note: You can make this a row with a label that is an empty string (and value None) if you want it to appear as though nothing is selected by default (as in the above image).
252  # @param types (tuple) a 2-tuple of type functions (e.g. int, float, str). The
253  # first element is the type of the label, and the second is the type of the value.
254  # In most cases, the type of the label should be string, so you should pass the the str function as the first element.
255  # Alternatively, you can also use the type constants from the gobject module (gobject.TYPE_STRING, gobject.TYPE_FLOAT, etc.).
256  # @param labels (list) a list of strings - these will be the options made available for the user to select.
257  # @param vals (list) a list of anything - the length must be equal to the length of the labels param. These are the values that
258  # the labels will be given in the combobox model (assigned in the same order as the elements in the labels list appear).
259  # These values can be retrieved when you query the combobox selection state.
260  # @returns (gtk.ComboBox) a gtk.ComboBox object that can be used in the UI
261  @staticmethod
262  def build_simple_combo(types, labels, vals):
263  model = gtk.ListStore(*types)
264  map(model.append, zip(labels, vals))
265 
266  combobox = gtk.ComboBox(model=model)
267  renderer = gtk.CellRendererText()
268  combobox.pack_start(renderer, True, False, 0)
269  combobox.add_attribute(renderer, 'text', 0)
270  combobox.set_active(0)
271 
272  return combobox
273 
274  ## Builds a simple gtk.ListStore. This is the data store backing for a combobox or treeview.
275  # @param labels (list) list of strings that will be displayed in the list of treeview
276  # @param vals (list) list of objects, must be same length as labels list. These values are assigned to the rows/options in the widget, and
277  # can be retrieved when the widget's selection state is queried.
278  # @param val_type (function pointer) this is function pointer corresponding to a type (like str, int, float, etc.) or it is a type constant from the gobject module (eg. gobject.TYPE_STRING, gobject.TYPE_FLOAT, etc.).
279  # It indicates the type of the elements in the vals array.
280  # @returns (gtk.ListStore) a liststore object for use in the creation of a combobox or treeview (or potentially other types of widgets).
281  @staticmethod
282  def build_simple_liststore(labels, vals, val_type):
283  list_store = gtk.ListStore(gobject.TYPE_STRING,
284  val_type)
285 
286  for i in range(len(labels)):
287  list_store.append([labels[i], vals[i]])
288 
289  return list_store
290 
291  ## Builds a treeview (a grid with selectable rows) with a single column. The user may select multiple rows at once.
292  # The treeview has a hidden 'ID' column appended to it - you can use this to track the indices of the selected rows (or you can use the vals parameter).
293  # See the gtk docs for more info on treeviews. Here is what the treeview looks like:
294  # <img src="../images/multiselect_treeview.png">
295  # @param labels (list) list of strings. These are the values to display in the rows (one element per row).
296  # @param vals (list) list of anything. These values will be assigned to the rows in the same order as the labels list (therefore it must be of the same length). You can retrieve the values of selected rows when
297  # querying the treeview widget's selection state.
298  # @param val_type (function pointer) this is function pointer corresponding to a type (like str, int, float, etc.) or it is a type constant from the gobject module (eg. gobject.TYPE_STRING, gobject.TYPE_FLOAT, etc.).
299  # It indicates the type of the elements in the vals array.
300  # @param header_text (string) text to display at the top of the single column.
301  # @returns (gtk.TreeView) a gtk.TreeView object that's set up and ready to embed in the UI
302  @staticmethod
303  def build_multiselect_treeview(labels, vals, val_type, header_text):
304  list_store = UIUtils.build_simple_liststore(labels, vals, val_type)
305  treeview = gtk.TreeView(list_store)
306  treeview.get_selection().set_mode(gtk.SelectionMode.MULTIPLE)
307 
308  col = gtk.TreeViewColumn(header_text, gtk.CellRendererText(), text=0)
309  treeview.append_column(col)
310  col.set_sizing(gtk.TreeViewColumnSizing.AUTOSIZE)
311 
312  col = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=1)
313  col.set_visible(False)
314  col.set_sizing(gtk.TreeViewColumnSizing.AUTOSIZE)
315  treeview.append_column(col)
316 
317  return treeview
318 
319  ## Builds a gtk.ListStore that contains a group of common combo options stored in the DBConstants.COMBO_OPTIONS enum (these ultimately come from the database).
320  # For example, you could create a liststore of all LENA speaker codes, then display them in a treeview - this is what was done to create the screenshot below:
321  # <img src="../images/multiselect_treeview.png">
322  # You could just as easily use this liststore to create a combobox, or something else.
323  # @param group_id (int) a value from the DBConstants.COMBO_GROUPS enum, indicating which group of options to insert into the liststore. Combo options are grouped according to their type.
324  # For example, passing a group_id of DBConstants.COMBO_GROUPS.SPEAKER_CODES would create a liststore like the one used in the screenshot shown above.
325  # @param include_empty_option (boolean=True) if True, an option with an empty string label will be pre-pended to the liststore. This can be used as a "nothing-selected" option.
326  # @returns (gtk.ListStore) a liststore object that contains all of the options from the specified group. This object can be used as a data-store backing for other widgets.
327  @staticmethod
328  def build_options_liststore(group_id, include_empty_option=True):
329  list_store = gtk.ListStore(gobject.TYPE_STRING,
330  gobject.TYPE_INT)
331 
332  group_keys_enum = DBConstants.COMBO_OPTIONS[group_id]
333  for i in range(int(not include_empty_option), len(group_keys_enum)):
334  group_option_key = group_keys_enum[i]
335  option = DBConstants.COMBOS[group_id][group_option_key]
336  if not option.hidden:
337  list_store.append([option.disp_desc, option.db_id])
338 
339  return list_store
340 
341  ## Computes the minimum number of pixels (width) needed to show a treeview column with the specified title text.
342  # This is an approximation that was emperically determined, and uses magic numbers. It is not guarenteed to work, and
343  # should probably be removed. But you can try it if you feel frustrated.
344  # @param col_name (string) the text that will be displayed in the column header
345  # @returns (int) an approximation of the number of pixel you will need to applocate for the width of the treeview column, in order for the whole col_name header string to be displayed.
346  @staticmethod
348  return 90 + (len(col_name) / 15) * 50
349 
350  ## Builds a gtk.ComboBox widget that contains a group of common combo options stored in the DBConstants.COMBO_OPTIONS enum (these ultimately come from the database).
351  # For example, you could create a combobox that allows you to select one of the LENA speaker codes.
352  # @param group_id (int) a value from the DBConstants.COMBO_GROUPS enum, indicating which group of options to insert into the liststore.
353  # @param active_index (int=0) the row index of the entry to auto-select. If include_empty_option is True, and this is zero, then the empty entry will be selected.
354  # @param include_empty_option (boolean=True) if True, an option with an empty string label will be pre-pended to the list of options. This can be used as a "nothing-selected" option.
355  # @returns (gtk.ComboBox) a combobox widget containing all of the options for the specified group.
356  @staticmethod
357  def build_options_combo(group_id, active_index=0, include_empty_option=True):
358  list_store = UIUtils.build_options_liststore(group_id, include_empty_option)
359 
360  combobox = gtk.ComboBox(model=list_store)
361  renderer = gtk.CellRendererText()
362  combobox.pack_start(renderer, True, False, 0)
363  combobox.add_attribute(renderer, 'text', 0)
364 
365  combobox.set_active(active_index)
366 
367  return combobox
368 
369  ## Builds a gtk.ComboBox containing options for "common regexs" (COMBO_GROUPS.COMMON_REGEXS).
370  # When the selected option changes, a text entry box is populated with the regex string.
371  # (This is just a wrapper around the build_options_combo() method for common regexs, to add the ability to connect
372  # the 'changed' signal.)
373  # @param entry (gtk.Entry) a text entry box that will be populated with a regular expression string when the selection changes
374  # @returns (gtk.ComboBox) a combobox with the 'changed' signal set up
375  @staticmethod
377  combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.COMMON_REGEXS)
378  combo.connect('changed', UIUtils._fill_entry, entry)
379 
380  return combo
381 
382  ## This is an internal helper method for build_regex_helper_combo(). It populates a text entry with a regex string corresponding to the
383  # selected option in the combobox. It is called every time the combobox selection changes.
384  # @param combo (gtk.ComboBox) this is a combobox created with the options for the DBConstants.COMBO_GROUPS.COMMON_REGEXS group
385  # @param entry (gtk.Entry) a text entry to populate with the regex string for the selected combobox option
386  @staticmethod
387  def _fill_entry(combo, entry):
388  opt_id = combo.get_model()[combo.get_active()][1]
389  db = BLLDatabase()
390  # Ideally, the regex info should come out of an enum in the DBConstants class (such as DBConstants.COMMON_REGEXS).
391  # However, we only have the combobox option id of the selected option, and this isn't enough to figure out which enum value we need.
392  # To solve this problem, I've reverted to a database query here, but this should really be fixed in the future...we need something other
393  # than the option id to identify the selected combo option.
394  rows = db.select('common_regexs', 'regex'.split(), 'combo_option_id=?', [opt_id])
395  if rows and rows[0]:
396  regex = rows[0][0]
397  highlight_start = regex.find('<')
398  highlight_end = regex.find('>')
399 
400  entry.set_text(regex.replace('<', '<').replace('>', '>'))
401 
402  entry.grab_focus()
403  if highlight_start > -1 and highlight_end > -1:
404  entry.select_region(highlight_start, highlight_end)
405 
406  db.close()
407 
408  ## This method opens an "open file" dialog, and populates a text entry widget with the resulting path.
409  # It's useful to hook up to a "browse" button in the UI. This method blocks until the user clicks ok.
410  # @param title (string) text to display in the titlebar of the "open file" dialog
411  # @param entry (gtk.Entry) an entry widget to populate with the path name after the user clicks ok in the "open file" dialog
412  # @param filters (list=[]) list of gtk.FileFilter objects. A dropdown list of file types displaying these is shown in the "open file" dialog.
413  # You can use the constants in this class to build a list (UIUtils.ALL_FILE_FILTER, UIUtils.WAV_FILE_FILTER, UIUtils.TRS_FILE_FILTER, etc.)
414  # @param auto_fill_entry (gtk.Entry=None) an optional gtk entry. It is possible to automatically locate another file with the same name as the one the user selects, but a different file extension.
415  # This is useful, for example, when the user is locating a trs file, and you want to automatically locate the corresponding wav file. If this param is not None, an automatic search will be done
416  # for the corresponding wav file and, if it is found, the entry will be populated with the path. The search encompasses the current directory (the one the user's selected file is in) and the parent directory.
417  # @param auto_fill_file_extension (string='wav') an optional file extension string, used together with auto_fill_entry. This dictates the type of file extension that the search described in
418  # the auto_fill_entry parameter documentation.
419  @staticmethod
420  def browse_file(title, entry, filters=[], auto_fill_entry=None, auto_fill_file_extension='wav'):
421  filename = UIUtils.open_file(title, filters, save_last_location=True)
422  if filename:
423  entry.set_text(filename)
424  search_name = filename[:-3] + auto_fill_file_extension
425  #try to automatically find a corresponding wav file in the current or parent directories (in that order)
426  #if one is found, and auto_fill_entry is not None, then the gtk.Entry pointed to be auto_fill_entry will be populated with the path of the found wav file
427  try:
428  if auto_fill_entry and auto_fill_entry.get_text() == '':
429  if os.path.exists(search_name):
430  auto_fill_entry.set_text(search_name)
431  else:
432  parent_dir = os.path.abspath( os.path.dirname(filename) + os.path.sep + os.path.pardir) #find absolute name of directory 'filename_dir/../'
433  search_name = parent_dir + os.path.sep + os.path.basename(search_name) #search for file in parent directory
434  if os.path.exists(search_name):
435  auto_fill_entry.set_text(search_name)
436  except Exception as e:
437  print 'Unable to find matching %s file: %s' % (auto_fill_file_extension, e)
438 
439  ## This method opens an "open folder" dialog, and populates a text entry widget with the resulting path.
440  # It's useful to hook up to a "browse" button in the UI. This method blocks until the user clicks ok.
441  # @param title (string) text to display in the titlebar of the "open folder" dialog
442  # @param entry (gtk.Entry) an entry widget to populate with the path name after the user clicks ok in the "open folder" dialog
443  # @param filters (list=[]) list of gtk.FileFilter objects. This specifies the types of files (via their extensions) that will be visible in the "open folder" dialog.
444  # Since the dialog doesn't let you select files (only folders), any visible files will appear "greyed out", and this param really just affects with the user can see, not what they can do.
445  # In general, it is probably most useful to either leave this empty or set it to UIUtils.ALL_FILE_FILTER.
446  @staticmethod
447  def browse_folder(title, entry, filters=[]):
448  foldername = UIUtils.open_folder(title, filters, save_last_location=True)
449  if foldername:
450  entry.set_text(foldername)
451 
452  ## Shows an "open file" dialog, returning a user-selected filename (if any). This method blocks until the user clicks ok.
453  # @param title (string='Open File') string to display in the dialog box titlebar
454  # @param filters (list=[]) list of gtk.FileFilter objects for the dialog to use. A combobox is displayed listing these filters (generally file extensions). Only files matching the pattern will be viewable in the browse pane.
455  # If no filters are specified, all files will be visible.
456  # @param save_last_location (boolean=False) if True, the directory containing the selected file will be saved (to UIUtils.last_filechoose_location), and the next "open" or "save" dialog will use it as the current directory
457  # @param cur_location (string=None) if this is non-None, it specifies the path to the folder to use as the current directory - this is the directory initially displayed when the dialog pops up
458  # @returns (string) the path to the user-selected file. If the user did not select a file, and instead clicked the cancel button or closed the dialog, this method will return None.
459  @staticmethod
460  def open_file(title='Open File', filters=[], save_last_location=False, cur_location=None):
461  if not filters:
462  filters = [UIUtils.ALL_FILE_FILTER]
463  filename, open_now = UIUtils.show_file_dialog(title, filters, gtk.FileChooserAction.OPEN, gtk.STOCK_OPEN, save_last_location=save_last_location, cur_location=cur_location)
464 
465  return filename
466 
467  ## Shows an "open folder" dialog, returning a user-selected foldername (if any). This method blocks until the user clicks ok.
468  # @param title (string='Open Folder') string to display in the dialog box titlebar
469  # @param filters (list=[]) list of gtk.FileFilter objects. This specifies the types of files (via their extensions) that will be visible in the "open folder" dialog.
470  # Since the dialog doesn't let you select files (only folders), any visible files will appear "greyed out", and this param really just affects with the user can see, not what they can do.
471  # In general, it is probably most useful to either leave this empty or set it to UIUtils.ALL_FILE_FILTER.
472  # @param save_last_location (boolean=False) if True, the directory containing the selected file will be saved (to UIUtils.last_filechoose_location), and the next "open" dialog will use it as the current directory
473  # @param cur_location (string=None) if this is non-None, it specifies the path to the folder to use as the current directory - this is the directory initially displayed when the dialog pops up
474  # @returns (string) the path to the user-selected folder. If the user did not select a folder, and instead clicked the cancel button or closed the dialog, this method will return None.
475  @staticmethod
476  def open_folder(title='Select Folder', filters=[], save_last_location=False, cur_location=None):
477  if not filters:
478  filters = [UIUtils.ALL_FILE_FILTER]
479  foldername, open_now = UIUtils.show_file_dialog(title, filters, gtk.FileChooserAction.SELECT_FOLDER, gtk.STOCK_OPEN, save_last_location=save_last_location, cur_location=cur_location)
480 
481  return foldername
482 
483  ## Shows a "Save file" dialog, returning the path to a user-selected location. This method blocks until the user clicks ok.
484  # @param title (string="Save File") text to display in the dialog box titlebar
485  # @param filters (list=[]) list of gtk.FileFilter objects. This specifies the types of files (via their extensions) that will be visible in the "save" dialog.
486  # @param open_now_opt (boolean=False) if True, a checkbox will be displayed along the bottom of the save dialog box that allows the use to open the saved file as soon as it is finished being written to disk (opened in Excel).
487  # @param save_last_location (boolean=False) if True, the directory containing the selected file will be saved (to UIUtils.last_filechoose_location), and the next "open" or "save" dialog will use it as the current directory
488  # @param cur_location (string=None) a path to a folder to set the dialog to show when it opens. If None, this will display whatever directory GTK feels like.
489  # @returns (string) the save path. If the user did not enter one, and instead clicked the cancel button or closed the dialog, this method will return None.
490  @staticmethod
491  def save_file(title='Save File', filters=[], open_now_opt=False, save_last_location=False, cur_location=None):
492  if not filters:
493  filters = [UIUtils.ALL_FILE_FILTER]
494  return UIUtils.show_file_dialog(title, filters, gtk.FileChooserAction.SAVE, gtk.STOCK_SAVE, open_now_opt, save_last_location=save_last_location, cur_location=cur_location)
495 
496  ## Shows a dialog box with a message, a single text entry, and a set of buttons (e.g. ok/cancel). This allows the user to enter a string value. This method blocks until the user clicks ok.
497  # Here is what the dialog looks like, (the red message at the bottom is shown only if the user clicks ok after entering invalid text/no text).
498  # <img src="../images/entry_dialog.png">
499  # @param msg (string) message to display above the entry. This is generally some instructions about what to type in the entry.
500  # @param entry_title (string) This text will be displayed directly to the left of the entry. Usually it is some text with a colon that acts as a title for the entry.
501  # @param default_text (string='') The entry will be prepopulated with this text when the dialog box opens.
502  # @param validate_regex (string=r'^.+$') a regular expression that will be used to validate the text in the entry when the user clicks ok. If the regex does not match the text, invalid_msg will be displayed and the dialog will remain open.
503  # The default value for this parameter is a regex that matches any text except the empty string (this just makes sure the user enters something).
504  # @param invalid_msg (string='Please enter a value') this text will be shown below the dialog, in red, if the user clicks ok and the text they entered does not match validate_regex. In this case, the dialog will not close.
505  # @returns (string) The string that the user typed into the entry, or None if they clicked the cancel button or closed the dialog.
506  @staticmethod
507  def show_entry_dialog(msg, entry_title, default_text='', validate_regex=r'^.+$', invalid_msg='Please enter a value.'):
508  dialog = gtk.MessageDialog(buttons=gtk.ButtonsType.OK_CANCEL,
509  message_format=msg,
510  type=gtk.MessageType.QUESTION)
511 
512  message_area = dialog.get_message_area()
513 
514  vbox = gtk.VBox()
515 
516  entry_label = gtk.Label(entry_title)
517  entry = gtk.Entry()
518  entry.set_text(default_text)
519  invalid_label = gtk.Label('')
520 
521  hbox = gtk.HBox()
522  hbox.pack_start(entry_label, False, False, 0)
523  hbox.pack_start(entry, False, False, 0)
524  vbox.pack_start(hbox, False, False, 0)
525  vbox.pack_start(invalid_label, False, False, 0)
526 
527  message_area.pack_end(vbox, False, False, 0)
528  vbox.show_all()
529 
530  done = False
531  text = None
532  while text == None and dialog.run() == gtk.ResponseType.OK:
533  entry_input = entry.get_text()
534  if re.match(validate_regex, entry_input):
535  text = entry_input
536  else:
537  invalid_label.set_markup('<span foreground="red">%s</span>' % (invalid_msg))
538 
539  dialog.destroy()
540 
541  return text
542 
543  ## This method shows a a file dialog box (for "open" or "save") that (in addition to its regular buttons and controls) contains one or more checkboxes that the user can toggle.
544  # The checkboxes' states are returned, along with the user-specified path string (corresponding to the filesystem location they chose). This method blocks until the user clicks ok.
545  # @param title (string) text to display in the dialog box titlebar
546  # @param filters (list=[]) list of gtk.FileFilter objects. This specifies the types of files (via their extensions) that will be visible in the dialog.
547  # @param action (int) This is a gtk filechooser action constant (one of gtk.FILE_CHOOSER_ACTION_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER or gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER),
548  # indicating the type of dialog to display.
549  # @param confirm_button_stock (int) a gtk stock icon constant (e.g. gtk.STOCK_OK, gtk.STOCK_OPEN), indicating what type of text and icon to display on the "ok" button. For a complete list of stock constants, see
550  # <a href="http://www.pygtk.org/docs/pygtk/gtk-stock-items.html">the PyGTK docs here</a>.
551  # @param checkbuttons (list) a list of gtk.CheckButton objects (i.e. checkboxes) to display in the dialog. See return value description for how to obtain their state when the ok button is pressed.
552  # @param save_last_location (boolean=False) if True, the directory containing the selected file will be saved (to UIUtils.last_filechoose_location), and the next "open" or "save" dialog will use it as the current directory
553  # @returns (string, list) a 2-tuple. The first element is the path the user has selected using the dialog controls (for example, the path to the file to open). The second element is a list of boolean values. Each value corresponds to one checkbutton.
554  # A value of True indicates that the checkbox was checked when the user clicked ok - False indicates it was unchecked.
555  @staticmethod
556  def show_file_dialog_with_checks(title, filters, action, confirm_button_stock, checkbuttons, save_last_location=False):
557  filename = None
558  check_results = []
559 
560  dialog = gtk.FileChooserDialog(title=title,
561  action=action,
562  buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, confirm_button_stock, gtk.ResponseType.OK))
563  dialog.set_default_response(gtk.ResponseType.OK)
564  if save_last_location and UIUtils.last_filechooser_location:
565  dialog.set_current_folder(UIUtils.last_filechooser_location)
566 
567  for cur_filter in filters:
568  dialog.add_filter(cur_filter)
569 
570  if checkbuttons:
571  content_area = dialog.get_content_area()
572  vbox = gtk.VBox()
573  for button in checkbuttons:
574  align = gtk.Alignment(xalign=1.0, yalign=1.0)
575  align.add(button)
576  vbox.pack_start(align, False, False, 0)
577 
578  content_area.pack_end(vbox, False, False, 0)
579  vbox.show_all()
580 
581  response = dialog.run()
582  if response == gtk.ResponseType.OK:
583  filename = dialog.get_filename()
584  for button in checkbuttons:
585  check_results.append(button.get_active())
586 
587  dialog.destroy()
588 
589  if save_last_location and filename:
590  UIUtils.last_filechooser_location = os.path.dirname(filename)
591 
592  return filename, check_results
593 
594  ## This method shows a a file dialog box (for "open" or "save") with controls that allow the user to select a file. This method blocks until the user clicks ok.
595  # @param title (string) text to display in the dialog box titlebar
596  # @param filters (list=[]) list of gtk.FileFilter objects. This specifies the types of files (via their extensions) that will be visible in the dialog.
597  # @param action (int) This is a gtk filechooser action constant (one of gtk.FILE_CHOOSER_ACTION_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER or gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER),
598  # indicating the type of dialog to display.
599  # @param confirm_button_stock (int) a gtk stock icon constant (e.g. gtk.STOCK_OK, gtk.STOCK_OPEN), indicating what type of text and icon to display on the "ok" button. For a complete list of stock constants, see
600  # <a href="http://www.pygtk.org/docs/pygtk/gtk-stock-items.html">the PyGTK docs here</a>.
601  # @param open_now_opt (boolean=False) if True, a checkbox will be displayed in the dialog with the title "Open Immediately". Its value (True=checked, False=unchecked) is returned along with the path when this method returns. Typically this is used
602  # in save dialogs to allow the user to indicate that they would like to open a file immediately after it is saved to disk.
603  # @param save_last_location (boolean=False) if True, the directory containing the selected file will be saved (to UIUtils.last_filechoose_location), and the next "open" or "save" dialog will use it as the current directory
604  # @param cur_location (string=None) a path to a folder to set the dialog to show when it opens. If None, this will display whatever directory GTK feels like.
605  # @returns (string, list) a 2-tuple. The first element is the path the user has selected using the dialog controls (for example, the path to the file to open). The second element is a boolean value indicating the status of the "open now" checkbox (if present).
606  # A value of True indicates that the checkbox was checked when the user clicked ok - False indicates it was unchecked (or that the checkbox is not being shown).
607  @staticmethod
608  def show_file_dialog(title, filters, action, confirm_button_stock, open_now_opt=False, save_last_location=False, cur_location=None):
609  filename = None
610  open_now_checkbox = None
611  open_now = False
612 
613  dialog = gtk.FileChooserDialog(title=title,
614  action=action,
615  buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, confirm_button_stock, gtk.ResponseType.OK))
616  dialog.set_default_response(gtk.ResponseType.OK)
617  if cur_location:
618  dialog.set_current_folder(cur_location)
619  elif save_last_location and UIUtils.last_filechooser_location:
620  dialog.set_current_folder(UIUtils.last_filechooser_location)
621 
622  map(lambda f: dialog.add_filter(f), filters)
623 
624  if open_now_opt:
625  #splice in the 'open immediately checkbox'
626  content_area = dialog.get_content_area()
627  open_now_checkbox = gtk.CheckButton('Open Immediately')
628  open_now_checkbox.set_active(True)
629  align = gtk.Alignment(xalign=1.0, yalign=1.0)
630  align.add(open_now_checkbox)
631  content_area.pack_end(align, False, False, 0)
632  open_now_checkbox.show()
633  align.show()
634 
635  response = dialog.run()
636  if response == gtk.ResponseType.OK:
637  filename = dialog.get_filename()
638  if open_now_opt:
639  open_now = open_now_checkbox.get_active()
640 
641  dialog.destroy()
642 
643  if save_last_location and filename:
644  UIUtils.last_filechooser_location = os.path.dirname(filename)
645 
646  return filename, open_now
647 
648 ## This function is executed (by the call directly below it in the code) when the Python iterpreter reads this file for the first time.
649 # It populates all of the static constants in the UIUtils class. We cannot do this at class creation time (or in the class constructor)
650 # because it involves calling static UIUtils methods, and UIUtils has not yet been defined in either of those cases.
652  #Create some static common filter objects that can be passed to the dialog box methods in the UIUtils class.
653  #These are provided only for convenience. Calling code is free to create filter for other types of files/folders.
654  UIUtils.WAV_FILE_FILTER = UIUtils.build_file_filter('WAV Files', ['*.wav'])
655  UIUtils.TRS_FILE_FILTER = UIUtils.build_file_filter('TRS Files', ['*.trs'])
656  UIUtils.ALL_FILE_FILTER = UIUtils.build_file_filter('All Files', ['*'])
657  UIUtils.CSV_FILE_FILTER = UIUtils.build_file_filter('CSV Files', ['*.csv'])
658  UIUtils.TRS_CSV_FILE_FILTER = UIUtils.build_file_filter('TRS/CSV Files', ['*.trs', '*.csv'])
659