# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This module provides GUI for touch device firmware test using GTK.""" import os import re import shutil import gobject import gtk import gtk.gdk import pango import tempfile import common_util import firmware_utils import test_conf as conf from firmware_constants import TFK TITLE = "Touch Firmware Test" class BaseFrame(object): """A simple base frame class.""" def __init__(self, label=None, size=None, aspect=False): # Create a regular/aspect frame self.frame = gtk.AspectFrame() if aspect else gtk.Frame() self.frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) self.size = size if label: self.frame.set_label(label) self.frame.set_label_align(0.0, 0.0) frame_label = self.frame.get_label_widget() markup_str = '%s' frame_label.set_markup(markup_str % ('black', label)) if size: width, height = size self.frame.set_size_request(width, height) if aspect: self.frame.set(ratio=(float(width) / height)) class PromptFrame(BaseFrame): """A simple frame widget to display the prompt. It consists of: - A frame - a label showing the gesture name - a label showing the prompt - a label showing the keyboard interactions """ def __init__(self, label=None, size=None): super(PromptFrame, self).__init__(label, size) # Create a vertical packing box. self.vbox = gtk.VBox(False, 0) self.frame.add(self.vbox) # Create a label to show the gesture name self.label_gesture = gtk.Label('Gesture Name') self.label_gesture.set_justify(gtk.JUSTIFY_LEFT) self.vbox.pack_start(self.label_gesture, True, True, 0) # Expand the lable to be wider and wrap the line if necessary. if self.size: _, label_height = self.label_gesture.get_size_request() width, _ = self.size label_width = int(width * 0.9) self.label_gesture.set_size_request(label_width, label_height) self.label_gesture.set_line_wrap(True) # Pack a horizontal separator self.vbox.pack_start(gtk.HSeparator(), True, True, 0) # Create a label to show the prompt self.label_prompt = gtk.Label('Prompt') self.label_prompt.set_justify(gtk.JUSTIFY_CENTER) self.vbox.pack_start(self.label_prompt, True, True, 0) # Create a label to show the choice self.label_choice = gtk.Label('') self.label_choice.set_justify(gtk.JUSTIFY_LEFT) self.vbox.pack_start(self.label_choice, True, True, 0) # Show all widgets added to this frame self.frame.show_all() def set_gesture_name(self, string, color='blue'): """Set the gesture name in label_gesture.""" markup_str = ' %s ' self.label_gesture.set_markup(markup_str % (color, string)) def set_prompt(self, string, color='black'): """Set the prompt in label_prompt.""" markup_str = ' %s ' self.label_prompt.set_markup(markup_str % (color, string)) def set_choice(self, string): """Set the choice in label_choice.""" self.label_choice.set_text(string) class ResultFrame(BaseFrame): """A simple frame widget to display the test result. It consists of: - A frame - a scrolled window - a label showing the test result """ SCROLL_STEP = 100.0 def __init__(self, label=None, size=None): super(ResultFrame, self).__init__(label, size) # Create a scrolled window widget self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.frame.add(self.scrolled_window) # Create a vertical packing box. self.vbox = gtk.VBox(False, 0) self.scrolled_window.add_with_viewport(self.vbox) # Create a label to show the gesture name self.result = gtk.Label() self.vbox.pack_start(self.result , False, False, 0) # Show all widgets added to this frame self.frame.show_all() # Get the vertical and horizontal adjustments self.vadj = self.scrolled_window.get_vadjustment() self.hadj = self.scrolled_window.get_hadjustment() self._scroll_func_dict = {TFK.UP: self._scroll_up, TFK.DOWN: self._scroll_down, TFK.LEFT: self._scroll_left, TFK.RIGHT: self._scroll_right} def _calc_result_font_size(self): """Calculate the font size so that it does not overflow.""" label_width_in_px, _ = self.size font_size = int(float(label_width_in_px) / conf.num_chars_per_row * pango.SCALE) return font_size def set_result(self, text, color='black'): """Set the text in the result label.""" mod_text = re.sub('<', '<', text) mod_text = re.sub('>', '>', mod_text) markup_str = ' %s ' font_size = self._calc_result_font_size() self.result.set_markup(markup_str % (color, font_size, mod_text)) def _calc_inc_value(self, adj): """Calculate new increased value of the specified adjustement object.""" value = adj.get_value() new_value = min(value + self.SCROLL_STEP, adj.upper - adj.page_size) return new_value def _calc_dec_value(self, adj): """Calculate new decreased value of the specified adjustement object.""" value = adj.get_value() new_value = max(value - self.SCROLL_STEP, adj.lower) return new_value def _scroll_down(self): """Scroll the scrolled_window down.""" self.vadj.set_value(self._calc_inc_value(self.vadj)) def _scroll_up(self): """Scroll the scrolled_window up.""" self.vadj.set_value(self._calc_dec_value(self.vadj)) def _scroll_right(self): """Scroll the scrolled_window to the right.""" self.hadj.set_value(self._calc_inc_value(self.hadj)) def _scroll_left(self): """Scroll the scrolled_window to the left.""" self.hadj.set_value(self._calc_dec_value(self.hadj)) def scroll(self, choice): """Scroll the result frame using the choice key.""" scroll_method = self._scroll_func_dict.get(choice) if scroll_method: scroll_method() else: print 'Warning: the key choice "%s" is not legal!' % choice class ImageFrame(BaseFrame): """A simple frame widget to display the mtplot window. It consists of: - An aspect frame - an image widget showing mtplot """ def __init__(self, label=None, size=None): super(ImageFrame, self).__init__(label, size, aspect=True) # Use a fixed widget to display the image. self.fixed = gtk.Fixed() self.frame.add(self.fixed) # Create an image widget. self.image = gtk.Image() self.fixed.put(self.image, 0, 0) # Show all widgets added to this frame self.frame.show_all() def set_from_file(self, filename): """Set the image file.""" self.image.set_from_file(filename) self.frame.show_all() class FirmwareWindow(object): """A simple window class to display the touch firmware test window.""" def __init__(self, size=None, prompt_size=None, result_size=None, image_size=None): # Setup gtk environment correctly. self._setup_gtk_environment() # Create a new window self.win = gtk.Window(gtk.WINDOW_TOPLEVEL) if size: self.win_size = size self.win.resize(*size) self.win.set_title(TITLE) self.win.set_border_width(0) # Create the prompt frame self.prompt_frame = PromptFrame(TITLE, prompt_size) # Create the result frame self.result_frame = ResultFrame("Test results:", size=result_size) # Create the image frame for mtplot self.image_frame = ImageFrame(size=image_size) # Handle layout below self.box0 = gtk.VBox(False, 0) self.box1 = gtk.HBox(False, 0) # Arrange the layout about box0 self.win.add(self.box0) self.box0.pack_start(self.prompt_frame.frame, True, True, 0) self.box0.pack_start(self.box1, True, True, 0) # Arrange the layout about box1 self.box1.pack_start(self.image_frame.frame, True, True, 0) self.box1.pack_start(self.result_frame.frame, True, True, 0) # Capture keyboard events. self.win.add_events(gtk.gdk.KEY_PRESS_MASK | gtk.gdk.KEY_RELEASE_MASK) # Set a handler for delete_event that immediately exits GTK. self.win.connect("delete_event", self.delete_event) # Show all widgets. self.win.show_all() def _setup_gtk_environment(self): """Set up the gtk environment correctly.""" def _warning(msg=None): print 'Warning: fail to setup gtk environment.' if msg: print '\t' + msg print '\tImage files would not be shown properly.' print '\tIt does not affect the test results though.' def _make_symlink(path, symlink): """Remove the symlink if exists. Create a new symlink to point to the given path. """ if os.path.islink(symlink): os.remove(symlink) os.symlink(real_gtk_dir, self.gtk_symlink) self.new_symlink = True self.gtk_symlink = None self.tmp = tempfile.mkdtemp() self.moved_flag = False self.original_gtk_realpath = None self.new_symlink = False # Get LoaderDir: # The output of gdk-pixbuf-query-loaders looks like: # # GdkPixbuf Image Loader Modules file # Automatically generated file, do not edit # Created by gdk-pixbuf-query-loaders from gtk+-2.20.1 # # LoaderDir = /usr/lib64/gtk-2.0/2.10.0/loaders loader_dir_str = common_util.simple_system_output( 'gdk-pixbuf-query-loaders | grep LoaderDir') result = re.search('(/.*?)/(gtk-.*?)/', loader_dir_str) if result: prefix = result.group(1) self.gtk_version = result.group(2) else: _warning('Cannot derive gtk version from LoaderDir.') return # Verify the existence of the loaders file. gdk_pixbuf_loaders = ('/usr/local/etc/%s/gdk-pixbuf.loaders' % self.gtk_version) if not os.path.isfile(gdk_pixbuf_loaders): msg = 'The loaders file "%s" does not exist.' % gdk_pixbuf_loaders _warning(msg) return # Setup the environment variable for GdkPixbuf Image Loader Modules file # so that gtk library could find it. os.environ['GDK_PIXBUF_MODULE_FILE'] = gdk_pixbuf_loaders # In the loaders file, it specifies the paths of various # sharable objects (.so) which are used to load images of corresponding # image formats. For example, for png loader, the path looks like # # "/usr/lib64/gtk-2.0/2.10.0/loaders/libpixbufloader-png.so" # "png" 5 "gtk20" "The PNG image format" "LGPL" # "image/png" "" # "png" "" # "\211PNG\r\n\032\n" "" 100 # # However, the real path for the .so file is under # "/usr/local/lib64/..." # Hence, we would like to make a temporary symlink so that # gtk library could find the .so file correctly. self.gtk_symlink = os.path.join(prefix, self.gtk_version) prefix_list = prefix.split('/') prefix_list.insert(prefix_list.index('usr') + 1, 'local') real_gtk_dir = os.path.join('/', *(prefix_list + [self.gtk_version])) # Make sure that the directory of .so files does exist. if not os.path.isdir(real_gtk_dir): msg = 'The directory of gtk image loaders "%s" does not exist.' _warning(msg % real_gtk_dir) return # Take care of an existing symlink. if os.path.islink(self.gtk_symlink): # If the symlink does not point to the correct path, # save the real path of the symlink and re-create the symlink. if not os.path.samefile(self.gtk_symlink, real_gtk_dir): self.original_gtk_realpath = os.path.realpath(self.gtk_symlink) _make_symlink(real_gtk_dir, self.gtk_symlink) # Take care of an existing directory. elif os.path.isdir(self.gtk_symlink): # Move the directory only if it is not what we expect. if not os.path.samefile(self.gtk_symlink, real_gtk_dir): shutil.move(self.gtk_symlink, self.tmp) self.moved_flag = True _make_symlink(real_gtk_dir, self.gtk_symlink) # Take care of an existing file. # Such a file is not supposed to exist here. Move it anyway. elif os.path.isfile(self.gtk_symlink): shutil.move(self.gtk_symlink, self.tmp) self.moved_flag = True _make_symlink(real_gtk_dir, self.gtk_symlink) # Just create the temporary symlink since there is nothing here. else: _make_symlink(real_gtk_dir, self.gtk_symlink) def close(self): """Cleanup by restoring any symlink, file, or directory if necessary.""" # Remove the symlink that the test created. if self.new_symlink: os.remove(self.gtk_symlink) # Restore the original symlink. if self.original_gtk_realpath: os.symlink(self.original_gtk_realpath, self.gtk_symlink) # Restore the original file or directory. elif self.moved_flag: tmp_gtk_path = os.path.join(self.tmp, self.gtk_version) if (os.path.isdir(tmp_gtk_path) or os.path.isfile(tmp_gtk_path)): shutil.move(tmp_gtk_path, os.path.dirname(self.gtk_symlink)) self.moved_flag = False shutil.rmtree(self.tmp) def register_callback(self, event, callback): """Register a callback function for an event.""" self.win.connect(event, callback) def register_timeout_add(self, callback, timeout): """Register a callback function for gobject.timeout_add.""" return gobject.timeout_add(timeout, callback) def register_io_add_watch(self, callback, fd, data=None, condition=gobject.IO_IN): """Register a callback function for gobject.io_add_watch.""" if data: return gobject.io_add_watch(fd, condition, callback, data) else: return gobject.io_add_watch(fd, condition, callback) def create_key_press_event(self, keyval): """Create a key_press_event.""" event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # Assign current time to the event event.time = 0 event.keyval = keyval self.win.emit('key_press_event', event) def remove_event_source(self, tag): """Remove the registered callback.""" gobject.source_remove(tag) def delete_event(self, widget, event, data=None): """A handler to exit the window.""" self.stop() return False def set_gesture_name(self, string, color='blue'): """A helper method to set gesture name.""" self.prompt_frame.set_gesture_name(string, color) def set_prompt(self, string, color='black'): """A helper method to set the prompt.""" self.prompt_frame.set_prompt(string, color) def set_choice(self, string): """A helper method to set the choice.""" self.prompt_frame.set_choice(string) def set_result(self, text): """A helper method to set the text in the result.""" self.result_frame.set_result(text) def set_image(self, filename): """Set an image in the image frame.""" self.image_frame.set_from_file(filename) def scroll(self, choice): """Scroll the result frame using the choice key.""" self.result_frame.scroll(choice) def stop(self): """Quit the window.""" self.close() gtk.main_quit() def main(self): """Main function of the window.""" try: gtk.main() except KeyboardInterrupt: self.close()