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())
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
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
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)
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()
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)
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()