예제 #1
0
def add_image_panel(window, group):
    """ Creates a themed ImagePanel for the specified group and parent window.
    """
    from image_panel import ImagePanel

    image_panel = ImagePanel(theme=group.group_theme, text=group.label)
    panel = image_panel.create_control(window)

    return (image_panel, panel.GetSizer())
예제 #2
0
    def _get_open_page ( self ):
        """ Returns the 'open' form of the notebook page.
        """
        result = ImagePanel( theme             = self.open_theme,
                             text              = self.name,
                             controller        = self,
                             default_alignment = 'center',
                             state             = 'open' )
        result.create_control( self.notebook.control )

        return result
예제 #3
0
    def _get_open_page(self):
        """ Returns the 'open' form of the notebook page.
        """
        result = ImagePanel(theme=self.open_theme,
                            text=self.name,
                            controller=self,
                            default_alignment='center',
                            state='open')
        result.create_control(self.notebook.control)

        return result
예제 #4
0
    def config(self):
        """Configures the application at start-up.

        Controllers are responsible for initializing the application
        and creating all of the other objects. This method does just
        that. It loads the currently selected image file, creates an
        ImageArray for that file, creates an ImageProcessor to handle
        the array, and connects the ImageProcessor to the two
        ImagePanel objects."""
        # Load the image into an array
        image_array = ImageArray.LoadFile(self.source)
        # Create the processor for the given ImageArray
        self.image_processor = ImageProcessor(image_array)
        # Set up the display panels
        self.original_image_panel = ImagePanel(self.original_image, self.image_processor.original)
        self.current_image_panel = ImagePanel(self.current_image, self.image_processor.current)
예제 #5
0
    def config(self):
        """Configures the application at start-up.

        Controllers are responsible for initializing the application
        and creating all of the other objects. This method does just
        that. It loads the currently selected image file, creates an
        ImageArray for that file, creates an ImageProcessor to handle
        the array, and connects the ImageProcessor to the two
        ImagePanel objects."""
        # Load the image into an array
        image_array = ImageArray.LoadFile(self.source)
        # Create the processor for the given ImageArray
        self.image_processor = ImageProcessor(image_array)
        # Set up the display panels
        self.original_image_panel = ImagePanel(self.original_image,
                                               self.image_processor.original)
        self.current_image_panel = ImagePanel(self.current_image,
                                              self.image_processor.current)
예제 #6
0
class Main(BoxLayout):
    """Instance is a controller for the primary application.

    This controller manages all of the buttons and text fields of
    the application. It instantiates ImageProcessor (the student
    defined class), and uses that sub-controller to process images.

    The View for this controller is defined in imager.kv."""
    # These fields are 'hooks' to connect to the imager.kv file
    # They work sort of like @properties, but are different
    source = StringProperty('samples/goldhill.jpg')
    original_image = ObjectProperty(None)
    current_image = ObjectProperty(None)
    grayscale = ObjectProperty(None)
    hidden_text = ObjectProperty(None)
    text_input = ObjectProperty(None)
    image_processor = ObjectProperty(None)
    notifier = ObjectProperty(None)

    # Hidden fields not needed by imager.kv
    _operand = None # current executing option
    _op_args = None # arguments for the executing option

    def config(self):
        """Configures the application at start-up.

        Controllers are responsible for initializing the application
        and creating all of the other objects. This method does just
        that. It loads the currently selected image file, creates an
        ImageArray for that file, creates an ImageProcessor to handle
        the array, and connects the ImageProcessor to the two
        ImagePanel objects."""
        # Load the image into an array
        image_array = ImageArray.LoadFile(self.source)
        # Create the processor for the given ImageArray
        self.image_processor = ImageProcessor(image_array)
        # Set up the display panels
        self.original_image_panel = ImagePanel(self.original_image, self.image_processor.original)
        self.current_image_panel = ImagePanel(self.current_image, self.image_processor.current)

    def error(self, msg):
        """Report an error to the user

            :param msg: the error message
            **Precondition**: a string

        The error message will take up most of the Window, and last until
        the user dismisses it."""
        assert type(msg) == str, `msg`+' is not a string'
        content = ErrorDialog(label=msg, ok=self._dismiss_popup)
        self._popup = Popup(title='Error', content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def do(self, trans, *args):
        """Perform a transformation on the image.

            :param trans: transformation method in ImageProcessor
            **Precondition** : a reference to a method or function, not a string for its name

            :param args: list of arguments for `transform`
            **Precondition**: a list or tuple with valid argument values

        This method does not enforce its preconditions. Use with care."""
        if not self._operand is None:
            return

        # Say PROCESSING...
        self.notifier.color = [1,1,1,1]
        self._operand = trans
        self._op_args = args
        # Process the transform on the next clock cycle.
        Clock.schedule_once(self._do_async)

    def _do_async(self,dt):
        """Perform the active image transform.

        Hidden method that allows us to spread a transformation over
        two clock cycles.  This allows us to print a progress message
        on the screen."""
        # Perform the transformation
        if len(self._op_args) == 0:
            self._operand()
        else:
            self._operand(self._op_args[0])

        # Remove the status message and redisplay
        self.notifier.color = [0,0,0,0]
        self.current_image_panel.display(self.image_processor.current)
        self._operand = None
        self.op_args = None

    def hide(self):
        """Stores the hidden message in the image via steganography.

        Calls the method from image_processor. Displays a pop-up
        if the method fails (i.e. returns False).  Otherwise, message is
        now stored in the image."""
        text = str(self.hidden_text.text)
        result = self.image_processor.hide(text)
        if not result:
            self.error('Nothing was hidden')

    def reveal(self):
        """Reveal the hidden message in the image.

        Calls the method from image_processor. Displays a pop-up
        if there is no message.  Otherwise, places message in
        the text input box."""
        self.hidden_text.text = ''
        text = self.image_processor.reveal()
        if text is None:
            self.error('No hidden message, apparently')
        else:
            self.hidden_text.text = '<message revealed:> ' + text

    def _dismiss_popup(self):
        """Used to dismiss the currently active pop-up"""
        self._popup.dismiss()

    def load(self):
        """Open a dialog to load an image file."""
        content = LoadDialog(load=self._load_helper, cancel=self._dismiss_popup)
        self._popup = Popup(title="Load image", content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def _load_helper(self, path, filename):
        """Callback function for load. Called when user selects a file.

        This method loads the image file and redisplays the ImagePanels.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if (len(filename) == 0):
            return
        self.source = str(os.path.join(path, filename[0]))
        self.config()
        self.original_image_panel.display()
        self.current_image_panel.display()

    def save(self):
        """Save the image in the current ImageArray to a file."""
        content = SaveDialog(save=self._check_png, cancel=self._dismiss_popup)
        self._popup = Popup(title="Save image", content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def _check_png(self, path, filename):
        """Make sure we are saving in .png format.

        If user uses another extension, or no extension at all, force
        the file to be a .png

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if filename.lower().endswith('.png'):
            self._save_png(filename)
        else:
            i = filename.rfind('.')
            if i != -1: filename = filename[:i] # strip old extension
            filename += '.png'
            msg = 'File will be saved as\n{}\nin .png format. Proceed?'
            self._file_warning(msg.format(filename), filename, self._save_png)

    def _save_png(self, filename):
        """Check whether file exists before saving.

        Saves the file if does not exist or user confirms.

        Hidden method used only internally.  No preconditions except png suffix enforced."""
        assert filename.lower().endswith('.png')
        self._dismiss_popup()
        if os.path.isfile(filename):
            msg = 'File\n{}\nexists. Overwrite?'
            self._file_warning(msg.format(filename), filename, self._force_save)
        else:
            self._force_save(filename)

    def _force_save(self, filename):
        """Forceably saves the specified file, without user confirmation.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        # prepare image for saving
        im = self.image_processor.current.image
        # Direct file descriptor save broken on Windows
        # with open(filename, 'w') as f:
        try:
            im.save(filename, 'PNG')
        except:
            self.error('Cannot save image file: ' + filename)
        #f.close

    def _file_warning(self, msg, filename, ok):
        """Alerts the user of an issue when trying to load or save a file

        Hidden method used only internally.  No preconditions enforced."""
        content = WarningDialog(label=msg, data=filename, ok=ok, cancel=self._dismiss_popup)
        self._popup = Popup(title='Warning', content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def loadText(self):
        """Open a dialog to load a text file.

        Hidden method to try loading large messages into the text
        field.  Used for grading purposed on hide/reveal, as the
        clipboard does not work on all OSs"""
        content = LoadDialog(load=self._load_text_helper, cancel=self._dismiss_popup)
        content.filechooser.filters = ['*.txt','*.py']
        self._popup = Popup(title="Load image", content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def _load_text_helper(self, path, filename):
        """Callback function for _load_text. Called when user selects a file.

        This method loads the text file and puts it in the text input box.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if (len(filename) == 0):
            return
        filename = str(os.path.join(path, filename[0]))
        instream = open(filename)
        self.hidden_text.text = instream.read()
예제 #7
0
    def add_items(self, content, panel, sizer):
        """ Adds a list of Item objects to the panel.
        """
        # Get local references to various objects we need:
        ui = self.ui
        info = ui.info
        handler = ui.handler

        group = self.group
        show_left = group.show_left
        padding = group.padding
        col = -1
        col_incr = 1
        self.label_flags = 0
        show_labels = False
        for item in content:
            show_labels |= item.show_label
        if (not self.is_horizontal) and (show_labels or (group.columns > 1)):
            # For a vertical list of Items with labels or multiple columns, use
            # a 'FlexGridSizer':
            self.label_pad = 0
            cols = group.columns
            if show_labels:
                cols *= 2
                col_incr = 2
            flags = wx.TOP | wx.BOTTOM
            border_size = 1
            item_sizer = wx.FlexGridSizer(0, cols, 0, 5)
            if show_left:
                self.label_flags = wx.ALIGN_RIGHT
                if show_labels:
                    for i in range(1, cols, 2):
                        item_sizer.AddGrowableCol(i)
        else:
            # Otherwise, the current sizer will work as is:
            self.label_pad = 4
            cols = 1
            flags = wx.ALL
            border_size = 1
            item_sizer = sizer

        # Process each Item in the list:
        for item in content:

            # Get the item theme (if any):
            theme = item.item_theme

            # Get the name in order to determine its type:
            name = item.name

            # Check if is a label:
            if name == "":
                label = item.label
                if label != "":
                    # Update the column counter:
                    col += col_incr

                    # If we are building a multi-column layout with labels,
                    # just add space in the next column:
                    if (cols > 1) and show_labels:
                        item_sizer.Add((1, 1))

                    if theme is not None:
                        from image_text import ImageText

                        label = ImageText(panel, theme, label)
                        item_sizer.Add(label, 0, wx.EXPAND)
                    elif item.style == "simple":
                        # Add a simple text label:
                        label = wx.StaticText(panel, -1, label, style=wx.ALIGN_LEFT)
                        item_sizer.Add(label, 0, wx.EXPAND)
                    else:
                        # Add the label to the sizer:
                        label = heading_text(panel, text=label).control
                        item_sizer.Add(label, 0, wx.TOP | wx.BOTTOM | wx.EXPAND, 3)

                    if item.emphasized:
                        self._add_emphasis(label)

                # Continue on to the next Item in the list:
                continue

            # Update the column counter:
            col += col_incr

            # Check if it is a separator:
            if name == "_":
                for i in range(cols):
                    if self.is_horizontal:
                        # Add a vertical separator:
                        line = wx.StaticLine(panel, -1, style=wx.LI_VERTICAL)
                        item_sizer.Add(line, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 2)
                    else:
                        # Add a horizontal separator:
                        line = wx.StaticLine(panel, -1, style=wx.LI_HORIZONTAL)
                        item_sizer.Add(line, 0, wx.TOP | wx.BOTTOM | wx.EXPAND, 2)
                    self._set_owner(line, item)
                # Continue on to the next Item in the list:
                continue

            # Convert a blank to a 5 pixel spacer:
            if name == " ":
                name = "5"

            # Check if it is a spacer:
            if all_digits.match(name):

                # If so, add the appropriate amount of space to the sizer:
                n = int(name)
                if self.is_horizontal:
                    item_sizer.Add((n, 1))
                else:
                    spacer = (1, n)
                    item_sizer.Add(spacer)
                    if show_labels:
                        item_sizer.Add(spacer)

                # Continue on to the next Item in the list:
                continue

            # Otherwise, it must be a trait Item:
            object = eval(item.object_, globals(), ui.context)
            trait = object.base_trait(name)
            desc = trait.desc or ""
            label = None

            # If we are displaying labels on the left, add the label to the
            # user interface:
            if show_left:
                if item.show_label:
                    label = self.create_label(item, ui, desc, panel, item_sizer, border=group.show_border)
                elif (cols > 1) and show_labels:
                    label = self.dummy_label(panel, item_sizer)

            # Get the editor factory associated with the Item:
            editor_factory = item.editor
            if editor_factory is None:
                editor_factory = trait.get_editor()

                # If still no editor factory found, use a default text editor:
                if editor_factory is None:
                    from text_editor import ToolkitEditorFactory

                    editor_factory = ToolkitEditorFactory()

                # If the item has formatting traits set them in the editor
                # factory:
                if item.format_func is not None:
                    editor_factory.format_func = item.format_func

                if item.format_str != "":
                    editor_factory.format_str = item.format_str

                # If the item has an invalid state extended trait name, set it
                # in the editor factory:
                if item.invalid != "":
                    editor_factory.invalid = item.invalid

            # Set up the background image (if used):
            item_panel = panel
            if theme is not None:
                from image_panel import ImagePanel

                text = ""
                if item.show_label:
                    text = item.get_label(ui)
                image_panel = ImagePanel(theme=theme, text=text)
                item_panel = image_panel.create_control(panel)

            # Create the requested type of editor from the editor factory:
            factory_method = getattr(editor_factory, item.style + "_editor")
            editor = factory_method(ui, object, name, item.tooltip, item_panel).set(item=item, object_name=item.object)

            # Tell editor to actually build the editing widget:
            editor.prepare(item_panel)

            # Set the initial 'enabled' state of the editor from the factory:
            editor.enabled = editor_factory.enabled

            # Add emphasis to the editor control if requested:
            if item.emphasized:
                self._add_emphasis(editor.control)

            # Give the editor focus if it requested it:
            if item.has_focus:
                editor.control.SetFocus()

            # Adjust the maximum border size based on the editor's settings:
            border_size = min(border_size, editor.border_size)

            # Set up the reference to the correct 'control' to use in the
            # following section, depending upon whether we have wrapped an
            # ImagePanel around the editor control or not:
            control = editor.control
            if theme is None:
                width, height = control.GetSizeTuple()
            else:
                item_panel.GetSizer().Add(control, 1, wx.EXPAND)
                control = item_panel
                width, height = image_panel.adjusted_size

            # Set the correct size on the control, as specified by the user:
            scrollable = editor.scrollable
            item_width = item.width
            item_height = item.height
            growable = 0
            if (item_width != -1.0) or (item_height != -1.0):
                if (0.0 < item_width <= 1.0) and self.is_horizontal:
                    growable = int(1000.0 * item_width)
                    item_width = -1

                item_width = int(item_width)
                if item_width < -1:
                    item_width = -item_width
                elif item_width != -1:
                    item_width = max(item_width, width)

                if (0.0 < item_height <= 1.0) and (not self.is_horizontal):
                    growable = int(1000.0 * item_height)
                    item_height = -1

                item_height = int(item_height)
                if item_height < -1:
                    item_height = -item_height
                elif item_height != -1:
                    item_height = max(item_height, height)

                control.SetMinSize(wx.Size(item_width, item_height))

            # Bind the item to the control and all of its children:
            self._set_owner(control, item)

            # Bind the editor into the UIInfo object name space so it can be
            # referred to by a Handler while the user interface is active:
            id = item.id or name
            info.bind(id, editor, item.id)

            # Also, add the editors to the list of editors used to construct
            # the user interface:
            ui._editors.append(editor)

            # If the handler wants to be notified when the editor is created,
            # add it to the list of methods to be called when the UI is
            # complete:
            defined = getattr(handler, id + "_defined", None)
            if defined is not None:
                ui.add_defined(defined)

            # If the editor is conditionally visible, add the visibility
            # 'expression' and the editor to the UI object's list of monitored
            # objects:
            if item.visible_when != "":
                ui.add_visible(item.visible_when, editor)

            # If the editor is conditionally enabled, add the enabling
            # 'expression' and the editor to the UI object's list of monitored
            # objects:
            if item.enabled_when != "":
                ui.add_enabled(item.enabled_when, editor)

            # Add the created editor control to the sizer with the appropriate
            # layout flags and values:
            ui._scrollable |= scrollable
            item_resizable = (item.resizable is True) or ((item.resizable is Undefined) and scrollable)
            if item_resizable:
                growable = growable or 500
                self.resizable = True
            elif item.springy:
                growable = growable or 500

            # The following is a hack to allow 'readonly' text fields to
            # work correctly (wx has a bug that setting wx.EXPAND on a
            # StaticText control seems to cause the text to be aligned higher
            # than it would be otherwise, causing it to misalign with its
            # label).
            layout_style = editor.layout_style
            if not show_labels:
                layout_style |= wx.EXPAND

            item_sizer.Add(
                control,
                growable,
                flags | layout_style | wx.ALIGN_CENTER_VERTICAL,
                max(0, border_size + padding + item.padding),
            )

            # If we are displaying labels on the right, add the label to the
            # user interface:
            if not show_left:
                if item.show_label:
                    label = self.create_label(item, ui, desc, panel, item_sizer, "", wx.RIGHT)
                elif (cols > 1) and show_labels:
                    label = self.dummy_label(panel, item_sizer)

            # If the Item is resizable, and we are using a multi-column grid:
            if item_resizable and (cols > 1):
                # Mark the entire row as growable:
                item_sizer.AddGrowableRow(col / cols)

            # Save the reference to the label control (if any) in the editor:
            editor.label_control = label

        # If we created a grid sizer, add it to the original sizer:
        if item_sizer is not sizer:
            growable = 0
            if self.resizable:
                growable = 1

            sizer.Add(item_sizer, growable, wx.EXPAND | wx.ALL, 2)
예제 #8
0
class Main(BoxLayout):
    """Instance is a controller for the primary application.

    This controller manages all of the buttons and text fields of
    the application. It instantiates ImageProcessor (the student
    defined class), and uses that sub-controller to process images.

    The View for this controller is defined in imager.kv."""
    # These fields are 'hooks' to connect to the imager.kv file
    # They work sort of like @properties, but are different
    source = StringProperty('samples/goldhill.jpg')
    original_image = ObjectProperty(None)
    current_image = ObjectProperty(None)
    grayscale = ObjectProperty(None)
    hidden_text = ObjectProperty(None)
    text_input = ObjectProperty(None)
    image_processor = ObjectProperty(None)
    notifier = ObjectProperty(None)

    # Hidden fields not needed by imager.kv
    _operand = None  # current executing option
    _op_args = None  # arguments for the executing option

    def config(self):
        """Configures the application at start-up.

        Controllers are responsible for initializing the application
        and creating all of the other objects. This method does just
        that. It loads the currently selected image file, creates an
        ImageArray for that file, creates an ImageProcessor to handle
        the array, and connects the ImageProcessor to the two
        ImagePanel objects."""
        # Load the image into an array
        image_array = ImageArray.LoadFile(self.source)
        # Create the processor for the given ImageArray
        self.image_processor = ImageProcessor(image_array)
        # Set up the display panels
        self.original_image_panel = ImagePanel(self.original_image,
                                               self.image_processor.original)
        self.current_image_panel = ImagePanel(self.current_image,
                                              self.image_processor.current)

    def error(self, msg):
        """Report an error to the user

            :param msg: the error message
            **Precondition**: a string

        The error message will take up most of the Window, and last until
        the user dismisses it."""
        assert type(msg) == str, ` msg ` + ' is not a string'
        content = ErrorDialog(label=msg, ok=self._dismiss_popup)
        self._popup = Popup(title='Error',
                            content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def do(self, trans, *args):
        """Perform a transformation on the image.

            :param trans: transformation method in ImageProcessor
            **Precondition** : a reference to a method or function, not a string for its name

            :param args: list of arguments for `transform`
            **Precondition**: a list or tuple with valid argument values

        This method does not enforce its preconditions. Use with care."""
        if not self._operand is None:
            return

        # Say PROCESSING...
        self.notifier.color = [1, 1, 1, 1]
        self._operand = trans
        self._op_args = args
        # Process the transform on the next clock cycle.
        Clock.schedule_once(self._do_async)

    def _do_async(self, dt):
        """Perform the active image transform.

        Hidden method that allows us to spread a transformation over
        two clock cycles.  This allows us to print a progress message
        on the screen."""
        # Perform the transformation
        if len(self._op_args) == 0:
            self._operand()
        else:
            self._operand(self._op_args[0])

        # Remove the status message and redisplay
        self.notifier.color = [0, 0, 0, 0]
        self.current_image_panel.display(self.image_processor.current)
        self._operand = None
        self.op_args = None

    def hide(self):
        """Stores the hidden message in the image via steganography.

        Calls the method from image_processor. Displays a pop-up
        if the method fails (i.e. returns False).  Otherwise, message is
        now stored in the image."""
        text = str(self.hidden_text.text)
        result = self.image_processor.hide(text)
        if not result:
            self.error('Nothing was hidden')

    def reveal(self):
        """Reveal the hidden message in the image.

        Calls the method from image_processor. Displays a pop-up
        if there is no message.  Otherwise, places message in
        the text input box."""
        self.hidden_text.text = ''
        text = self.image_processor.reveal()
        if text is None:
            self.error('No hidden message, apparently')
        else:
            self.hidden_text.text = '<message revealed:> ' + text

    def _dismiss_popup(self):
        """Used to dismiss the currently active pop-up"""
        self._popup.dismiss()

    def load(self):
        """Open a dialog to load an image file."""
        content = LoadDialog(load=self._load_helper,
                             cancel=self._dismiss_popup)
        self._popup = Popup(title="Load image",
                            content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def _load_helper(self, path, filename):
        """Callback function for load. Called when user selects a file.

        This method loads the image file and redisplays the ImagePanels.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if (len(filename) == 0):
            return
        self.source = str(os.path.join(path, filename[0]))
        self.config()
        self.original_image_panel.display()
        self.current_image_panel.display()

    def save(self):
        """Save the image in the current ImageArray to a file."""
        content = SaveDialog(save=self._check_png, cancel=self._dismiss_popup)
        self._popup = Popup(title="Save image",
                            content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def _check_png(self, path, filename):
        """Make sure we are saving in .png format.

        If user uses another extension, or no extension at all, force
        the file to be a .png

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if filename.lower().endswith('.png'):
            self._save_png(filename)
        else:
            i = filename.rfind('.')
            if i != -1: filename = filename[:i]  # strip old extension
            filename += '.png'
            msg = 'File will be saved as\n{}\nin .png format. Proceed?'
            self._file_warning(msg.format(filename), filename, self._save_png)

    def _save_png(self, filename):
        """Check whether file exists before saving.

        Saves the file if does not exist or user confirms.

        Hidden method used only internally.  No preconditions except png suffix enforced."""
        assert filename.lower().endswith('.png')
        self._dismiss_popup()
        if os.path.isfile(filename):
            msg = 'File\n{}\nexists. Overwrite?'
            self._file_warning(msg.format(filename), filename,
                               self._force_save)
        else:
            self._force_save(filename)

    def _force_save(self, filename):
        """Forceably saves the specified file, without user confirmation.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        # prepare image for saving
        im = self.image_processor.current.image
        # Direct file descriptor save broken on Windows
        # with open(filename, 'w') as f:
        try:
            im.save(filename, 'PNG')
        except:
            self.error('Cannot save image file: ' + filename)
        #f.close

    def _file_warning(self, msg, filename, ok):
        """Alerts the user of an issue when trying to load or save a file

        Hidden method used only internally.  No preconditions enforced."""
        content = WarningDialog(label=msg,
                                data=filename,
                                ok=ok,
                                cancel=self._dismiss_popup)
        self._popup = Popup(title='Warning',
                            content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def loadText(self):
        """Open a dialog to load a text file.

        Hidden method to try loading large messages into the text
        field.  Used for grading purposed on hide/reveal, as the
        clipboard does not work on all OSs"""
        content = LoadDialog(load=self._load_text_helper,
                             cancel=self._dismiss_popup)
        content.filechooser.filters = ['*.txt', '*.py']
        self._popup = Popup(title="Load image",
                            content=content,
                            size_hint=(0.9, 0.9))
        self._popup.open()

    def _load_text_helper(self, path, filename):
        """Callback function for _load_text. Called when user selects a file.

        This method loads the text file and puts it in the text input box.

        Hidden method used only internally.  No preconditions enforced."""
        self._dismiss_popup()
        if (len(filename) == 0):
            return
        filename = str(os.path.join(path, filename[0]))
        instream = open(filename)
        self.hidden_text.text = instream.read()