Home | History | Annotate | Download | only in firmware_TouchMTB
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """This module provides GUI for touch device firmware test using GTK."""
      6 
      7 import os
      8 import re
      9 import shutil
     10 
     11 import gobject
     12 import gtk
     13 import gtk.gdk
     14 import pango
     15 import tempfile
     16 
     17 import common_util
     18 import firmware_utils
     19 import test_conf as conf
     20 
     21 from firmware_constants import TFK
     22 
     23 
     24 TITLE = "Touch Firmware Test"
     25 
     26 
     27 class BaseFrame(object):
     28     """A simple base frame class."""
     29     def __init__(self, label=None, size=None, aspect=False):
     30         # Create a regular/aspect frame
     31         self.frame = gtk.AspectFrame() if aspect else gtk.Frame()
     32         self.frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
     33         self.size = size
     34         if label:
     35             self.frame.set_label(label)
     36             self.frame.set_label_align(0.0, 0.0)
     37             frame_label = self.frame.get_label_widget()
     38             markup_str = '<span foreground="%s" size="x-large">%s</span>'
     39             frame_label.set_markup(markup_str % ('black', label))
     40         if size:
     41             width, height = size
     42             self.frame.set_size_request(width, height)
     43             if aspect:
     44                 self.frame.set(ratio=(float(width) / height))
     45 
     46 
     47 class PromptFrame(BaseFrame):
     48     """A simple frame widget to display the prompt.
     49 
     50     It consists of:
     51       - A frame
     52       - a label showing the gesture name
     53       - a label showing the prompt
     54       - a label showing the keyboard interactions
     55     """
     56 
     57     def __init__(self, label=None, size=None):
     58         super(PromptFrame, self).__init__(label, size)
     59 
     60         # Create a vertical packing box.
     61         self.vbox = gtk.VBox(False, 0)
     62         self.frame.add(self.vbox)
     63 
     64         # Create a label to show the gesture name
     65         self.label_gesture = gtk.Label('Gesture Name')
     66         self.label_gesture.set_justify(gtk.JUSTIFY_LEFT)
     67         self.vbox.pack_start(self.label_gesture, True, True, 0)
     68         # Expand the lable to be wider and wrap the line if necessary.
     69         if self.size:
     70             _, label_height = self.label_gesture.get_size_request()
     71             width, _ = self.size
     72             label_width = int(width * 0.9)
     73             self.label_gesture.set_size_request(label_width, label_height)
     74         self.label_gesture.set_line_wrap(True)
     75 
     76         # Pack a horizontal separator
     77         self.vbox.pack_start(gtk.HSeparator(), True, True, 0)
     78 
     79         # Create a label to show the prompt
     80         self.label_prompt = gtk.Label('Prompt')
     81         self.label_prompt.set_justify(gtk.JUSTIFY_CENTER)
     82         self.vbox.pack_start(self.label_prompt, True, True, 0)
     83 
     84         # Create a label to show the choice
     85         self.label_choice = gtk.Label('')
     86         self.label_choice.set_justify(gtk.JUSTIFY_LEFT)
     87         self.vbox.pack_start(self.label_choice, True, True, 0)
     88 
     89         # Show all widgets added to this frame
     90         self.frame.show_all()
     91 
     92     def set_gesture_name(self, string, color='blue'):
     93         """Set the gesture name in label_gesture."""
     94         markup_str = '<b><span foreground="%s" size="xx-large"> %s </span></b>'
     95         self.label_gesture.set_markup(markup_str % (color, string))
     96 
     97     def set_prompt(self, string, color='black'):
     98         """Set the prompt in label_prompt."""
     99         markup_str = '<span foreground="%s" size="x-large"> %s </span>'
    100         self.label_prompt.set_markup(markup_str % (color, string))
    101 
    102     def set_choice(self, string):
    103         """Set the choice in label_choice."""
    104         self.label_choice.set_text(string)
    105 
    106 
    107 class ResultFrame(BaseFrame):
    108     """A simple frame widget to display the test result.
    109 
    110     It consists of:
    111       - A frame
    112       - a scrolled window
    113       - a label showing the test result
    114     """
    115     SCROLL_STEP = 100.0
    116 
    117     def __init__(self, label=None, size=None):
    118         super(ResultFrame, self).__init__(label, size)
    119 
    120         # Create a scrolled window widget
    121         self.scrolled_window = gtk.ScrolledWindow()
    122         self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC,
    123                                         gtk.POLICY_AUTOMATIC)
    124         self.frame.add(self.scrolled_window)
    125 
    126         # Create a vertical packing box.
    127         self.vbox = gtk.VBox(False, 0)
    128         self.scrolled_window.add_with_viewport(self.vbox)
    129 
    130         # Create a label to show the gesture name
    131         self.result = gtk.Label()
    132         self.vbox.pack_start(self.result , False, False, 0)
    133 
    134         # Show all widgets added to this frame
    135         self.frame.show_all()
    136 
    137         # Get the vertical and horizontal adjustments
    138         self.vadj = self.scrolled_window.get_vadjustment()
    139         self.hadj = self.scrolled_window.get_hadjustment()
    140 
    141         self._scroll_func_dict = {TFK.UP: self._scroll_up,
    142                                   TFK.DOWN: self._scroll_down,
    143                                   TFK.LEFT: self._scroll_left,
    144                                   TFK.RIGHT: self._scroll_right}
    145 
    146     def _calc_result_font_size(self):
    147         """Calculate the font size so that it does not overflow."""
    148         label_width_in_px, _ = self.size
    149         font_size = int(float(label_width_in_px) / conf.num_chars_per_row *
    150                         pango.SCALE)
    151         return font_size
    152 
    153     def set_result(self, text, color='black'):
    154         """Set the text in the result label."""
    155         mod_text = re.sub('<', '&lt;', text)
    156         mod_text = re.sub('>', '&gt;', mod_text)
    157         markup_str = '<b><span foreground="%s" size="%d"> %s </span></b>'
    158         font_size = self._calc_result_font_size()
    159         self.result.set_markup(markup_str % (color, font_size, mod_text))
    160 
    161     def _calc_inc_value(self, adj):
    162         """Calculate new increased value of the specified adjustement object."""
    163         value = adj.get_value()
    164         new_value = min(value + self.SCROLL_STEP, adj.upper - adj.page_size)
    165         return new_value
    166 
    167     def _calc_dec_value(self, adj):
    168         """Calculate new decreased value of the specified adjustement object."""
    169         value = adj.get_value()
    170         new_value = max(value - self.SCROLL_STEP, adj.lower)
    171         return new_value
    172 
    173     def _scroll_down(self):
    174         """Scroll the scrolled_window down."""
    175         self.vadj.set_value(self._calc_inc_value(self.vadj))
    176 
    177     def _scroll_up(self):
    178         """Scroll the scrolled_window up."""
    179         self.vadj.set_value(self._calc_dec_value(self.vadj))
    180 
    181     def _scroll_right(self):
    182         """Scroll the scrolled_window to the right."""
    183         self.hadj.set_value(self._calc_inc_value(self.hadj))
    184 
    185     def _scroll_left(self):
    186         """Scroll the scrolled_window to the left."""
    187         self.hadj.set_value(self._calc_dec_value(self.hadj))
    188 
    189     def scroll(self, choice):
    190         """Scroll the result frame using the choice key."""
    191         scroll_method = self._scroll_func_dict.get(choice)
    192         if scroll_method:
    193             scroll_method()
    194         else:
    195             print 'Warning: the key choice "%s" is not legal!' % choice
    196 
    197 
    198 class ImageFrame(BaseFrame):
    199     """A simple frame widget to display the mtplot window.
    200 
    201     It consists of:
    202       - An aspect frame
    203       - an image widget showing mtplot
    204     """
    205 
    206     def __init__(self, label=None, size=None):
    207         super(ImageFrame, self).__init__(label, size, aspect=True)
    208 
    209         # Use a fixed widget to display the image.
    210         self.fixed = gtk.Fixed()
    211         self.frame.add(self.fixed)
    212 
    213         # Create an image widget.
    214         self.image = gtk.Image()
    215         self.fixed.put(self.image, 0, 0)
    216 
    217         # Show all widgets added to this frame
    218         self.frame.show_all()
    219 
    220     def set_from_file(self, filename):
    221         """Set the image file."""
    222         self.image.set_from_file(filename)
    223         self.frame.show_all()
    224 
    225 
    226 class FirmwareWindow(object):
    227     """A simple window class to display the touch firmware test window."""
    228 
    229     def __init__(self, size=None, prompt_size=None, result_size=None,
    230                  image_size=None):
    231         # Setup gtk environment correctly.
    232         self._setup_gtk_environment()
    233 
    234         # Create a new window
    235         self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    236         if size:
    237             self.win_size = size
    238             self.win.resize(*size)
    239         self.win.set_title(TITLE)
    240         self.win.set_border_width(0)
    241 
    242         # Create the prompt frame
    243         self.prompt_frame = PromptFrame(TITLE, prompt_size)
    244 
    245         # Create the result frame
    246         self.result_frame = ResultFrame("Test results:", size=result_size)
    247 
    248         # Create the image frame for mtplot
    249         self.image_frame = ImageFrame(size=image_size)
    250 
    251         # Handle layout below
    252         self.box0 = gtk.VBox(False, 0)
    253         self.box1 = gtk.HBox(False, 0)
    254         # Arrange the layout about box0
    255         self.win.add(self.box0)
    256         self.box0.pack_start(self.prompt_frame.frame, True, True, 0)
    257         self.box0.pack_start(self.box1, True, True, 0)
    258         # Arrange the layout about box1
    259         self.box1.pack_start(self.image_frame.frame, True, True, 0)
    260         self.box1.pack_start(self.result_frame.frame, True, True, 0)
    261 
    262         # Capture keyboard events.
    263         self.win.add_events(gtk.gdk.KEY_PRESS_MASK | gtk.gdk.KEY_RELEASE_MASK)
    264 
    265         # Set a handler for delete_event that immediately exits GTK.
    266         self.win.connect("delete_event", self.delete_event)
    267 
    268         # Show all widgets.
    269         self.win.show_all()
    270 
    271     def _setup_gtk_environment(self):
    272         """Set up the gtk environment correctly."""
    273 
    274         def _warning(msg=None):
    275             print 'Warning: fail to setup gtk environment.'
    276             if msg:
    277                 print '\t' + msg
    278             print '\tImage files would not be shown properly.'
    279             print '\tIt does not affect the test results though.'
    280 
    281         def _make_symlink(path, symlink):
    282             """Remove the symlink if exists. Create a new symlink to point to
    283             the given path.
    284             """
    285             if os.path.islink(symlink):
    286                 os.remove(symlink)
    287             os.symlink(real_gtk_dir, self.gtk_symlink)
    288             self.new_symlink = True
    289 
    290         self.gtk_symlink = None
    291         self.tmp = tempfile.mkdtemp()
    292         self.moved_flag = False
    293         self.original_gtk_realpath = None
    294         self.new_symlink = False
    295 
    296         # Get LoaderDir:
    297         # The output of gdk-pixbuf-query-loaders looks like:
    298         #
    299         #   GdkPixbuf Image Loader Modules file
    300         #   Automatically generated file, do not edit
    301         #   Created by gdk-pixbuf-query-loaders from gtk+-2.20.1
    302         #
    303         #   LoaderDir = /usr/lib64/gtk-2.0/2.10.0/loaders
    304         loader_dir_str = common_util.simple_system_output(
    305                 'gdk-pixbuf-query-loaders | grep LoaderDir')
    306         result = re.search('(/.*?)/(gtk-.*?)/', loader_dir_str)
    307         if result:
    308             prefix = result.group(1)
    309             self.gtk_version = result.group(2)
    310         else:
    311             _warning('Cannot derive gtk version from LoaderDir.')
    312             return
    313 
    314         # Verify the existence of the loaders file.
    315         gdk_pixbuf_loaders = ('/usr/local/etc/%s/gdk-pixbuf.loaders' %
    316                               self.gtk_version)
    317         if not os.path.isfile(gdk_pixbuf_loaders):
    318             msg = 'The loaders file "%s" does not exist.' % gdk_pixbuf_loaders
    319             _warning(msg)
    320             return
    321 
    322         # Setup the environment variable for GdkPixbuf Image Loader Modules file
    323         # so that gtk library could find it.
    324         os.environ['GDK_PIXBUF_MODULE_FILE'] = gdk_pixbuf_loaders
    325 
    326         # In the loaders file, it specifies the paths of various
    327         # sharable objects (.so) which are used to load images of corresponding
    328         # image formats. For example, for png loader, the path looks like
    329         #
    330         # "/usr/lib64/gtk-2.0/2.10.0/loaders/libpixbufloader-png.so"
    331         # "png" 5 "gtk20" "The PNG image format" "LGPL"
    332         # "image/png" ""
    333         # "png" ""
    334         # "\211PNG\r\n\032\n" "" 100
    335         #
    336         # However, the real path for the .so file is under
    337         # "/usr/local/lib64/..."
    338         # Hence, we would like to make a temporary symlink so that
    339         # gtk library could find the .so file correctly.
    340         self.gtk_symlink = os.path.join(prefix, self.gtk_version)
    341         prefix_list = prefix.split('/')
    342         prefix_list.insert(prefix_list.index('usr') + 1, 'local')
    343         real_gtk_dir = os.path.join('/', *(prefix_list + [self.gtk_version]))
    344 
    345         # Make sure that the directory of .so files does exist.
    346         if not os.path.isdir(real_gtk_dir):
    347             msg = 'The directory of gtk image loaders "%s" does not exist.'
    348             _warning(msg % real_gtk_dir)
    349             return
    350 
    351         # Take care of an existing symlink.
    352         if os.path.islink(self.gtk_symlink):
    353             # If the symlink does not point to the correct path,
    354             # save the real path of the symlink and re-create the symlink.
    355             if not os.path.samefile(self.gtk_symlink, real_gtk_dir):
    356                 self.original_gtk_realpath = os.path.realpath(self.gtk_symlink)
    357                 _make_symlink(real_gtk_dir, self.gtk_symlink)
    358 
    359         # Take care of an existing directory.
    360         elif os.path.isdir(self.gtk_symlink):
    361             # Move the directory only if it is not what we expect.
    362             if not os.path.samefile(self.gtk_symlink, real_gtk_dir):
    363                 shutil.move(self.gtk_symlink, self.tmp)
    364                 self.moved_flag = True
    365                 _make_symlink(real_gtk_dir, self.gtk_symlink)
    366 
    367         # Take care of an existing file.
    368         # Such a file is not supposed to exist here. Move it anyway.
    369         elif os.path.isfile(self.gtk_symlink):
    370             shutil.move(self.gtk_symlink, self.tmp)
    371             self.moved_flag = True
    372             _make_symlink(real_gtk_dir, self.gtk_symlink)
    373 
    374         # Just create the temporary symlink since there is nothing here.
    375         else:
    376             _make_symlink(real_gtk_dir, self.gtk_symlink)
    377 
    378     def close(self):
    379         """Cleanup by restoring any symlink, file, or directory if necessary."""
    380         # Remove the symlink that the test created.
    381         if self.new_symlink:
    382             os.remove(self.gtk_symlink)
    383 
    384         # Restore the original symlink.
    385         if self.original_gtk_realpath:
    386             os.symlink(self.original_gtk_realpath, self.gtk_symlink)
    387         # Restore the original file or directory.
    388         elif self.moved_flag:
    389             tmp_gtk_path = os.path.join(self.tmp, self.gtk_version)
    390             if (os.path.isdir(tmp_gtk_path) or os.path.isfile(tmp_gtk_path)):
    391                 shutil.move(tmp_gtk_path, os.path.dirname(self.gtk_symlink))
    392                 self.moved_flag = False
    393                 shutil.rmtree(self.tmp)
    394 
    395     def register_callback(self, event, callback):
    396         """Register a callback function for an event."""
    397         self.win.connect(event, callback)
    398 
    399     def register_timeout_add(self, callback, timeout):
    400         """Register a callback function for gobject.timeout_add."""
    401         return gobject.timeout_add(timeout, callback)
    402 
    403     def register_io_add_watch(self, callback, fd, data=None,
    404                               condition=gobject.IO_IN):
    405         """Register a callback function for gobject.io_add_watch."""
    406         if data:
    407             return gobject.io_add_watch(fd, condition, callback, data)
    408         else:
    409             return gobject.io_add_watch(fd, condition, callback)
    410 
    411     def create_key_press_event(self, keyval):
    412         """Create a key_press_event."""
    413         event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
    414         # Assign current time to the event
    415         event.time = 0
    416         event.keyval = keyval
    417         self.win.emit('key_press_event', event)
    418 
    419     def remove_event_source(self, tag):
    420         """Remove the registered callback."""
    421         gobject.source_remove(tag)
    422 
    423     def delete_event(self, widget, event, data=None):
    424         """A handler to exit the window."""
    425         self.stop()
    426         return False
    427 
    428     def set_gesture_name(self, string, color='blue'):
    429         """A helper method to set gesture name."""
    430         self.prompt_frame.set_gesture_name(string, color)
    431 
    432     def set_prompt(self, string, color='black'):
    433         """A helper method to set the prompt."""
    434         self.prompt_frame.set_prompt(string, color)
    435 
    436     def set_choice(self, string):
    437         """A helper method to set the choice."""
    438         self.prompt_frame.set_choice(string)
    439 
    440     def set_result(self, text):
    441         """A helper method to set the text in the result."""
    442         self.result_frame.set_result(text)
    443 
    444     def set_image(self, filename):
    445         """Set an image in the image frame."""
    446         self.image_frame.set_from_file(filename)
    447 
    448     def scroll(self, choice):
    449         """Scroll the result frame using the choice key."""
    450         self.result_frame.scroll(choice)
    451 
    452     def stop(self):
    453         """Quit the window."""
    454         self.close()
    455         gtk.main_quit()
    456 
    457     def main(self):
    458         """Main function of the window."""
    459         try:
    460             gtk.main()
    461         except KeyboardInterrupt:
    462             self.close()
    463