#!/usr/bin/env python # # Copyright (C) 2014 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import wx from wx.lib.pubsub import setuparg1 from wx.lib.pubsub import pub from wx.lib.wordwrap import wordwrap import CanvasModel import CanvasView ICON_FILE = "res/ao2.ico" IMAGE_WIDTH = 101 IMAGE_HEIGHT = 30 INTERNAL_RADIUS = 2 class CanvasFrame(wx.Frame): def __init__(self, *args, **kwargs): base_image = kwargs.pop('base_image', None) wx.Frame.__init__(self, *args, **kwargs) # Set up a sizer BEFORE every other action, in order to prevent any # weird side effects; for instance self.SetToolBar() seems to resize # child windows... vsizer = wx.BoxSizer(orient=wx.VERTICAL) self.SetSizer(vsizer) # Instantiate the Model and set up the view self.model = CanvasModel.Canvas(IMAGE_WIDTH, IMAGE_HEIGHT, INTERNAL_RADIUS) self.view = CanvasView.CanvasView(self, model=self.model, base_image=base_image) vsizer.Add(self.view, 0, wx.SHAPED) icon = wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO) self.SetIcon(icon) # Set up the menu bar menu_bar = self._BuildMenu() self.SetMenuBar(menu_bar) # Tool bar tool_bar = self._BuildTools() self.SetToolBar(tool_bar) # Status bar status_bar = wx.StatusBar(self) status_bar.SetWindowStyle(status_bar.GetWindowStyle() ^ wx.ST_SIZEGRIP) status_bar.SetFieldsCount(3) self.SetStatusBar(status_bar) # View callbacks pub.subscribe(self.UpdateStatusBar, "NEW PIXEL") pub.subscribe(self.UpdateView, "NEW PIXEL") # Controller Methods self.view.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.view.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.view.Bind(wx.EVT_MOTION, self.OnMouseMotion) # other Events self.Bind(wx.EVT_CLOSE, self.OnQuit) # The frame gets resized to fit all its elements self.GetSizer().Fit(self) # and centered on screen self.Center(wx.BOTH | wx.CENTER_ON_SCREEN) def _BuildTools(self): tool_bar = wx.ToolBar(self, style=wx.TB_HORZ_LAYOUT|wx.TB_TEXT) color_picker_label = wx.StaticText(tool_bar, label=" Color picker ") tool_bar.AddControl(color_picker_label) self.color = wx.WHITE ID_COLOR_PICKER = wx.NewId() color_picker = wx.ColourPickerCtrl(tool_bar, ID_COLOR_PICKER, self.color, size=wx.Size(32,32), name="Color Picker") tool_bar.AddControl(color_picker) wx.EVT_COLOURPICKER_CHANGED(self, ID_COLOR_PICKER, self.OnPickColor) tool_bar.AddSeparator() ID_SHOW_GRID = wx.NewId() show_grid_checkbox = wx.CheckBox(tool_bar, ID_SHOW_GRID, label="Show grid", style=wx.ALIGN_RIGHT) show_grid_checkbox.SetValue(self.view.draw_grid) tool_bar.AddControl(show_grid_checkbox) wx.EVT_CHECKBOX(tool_bar, ID_SHOW_GRID, self.OnShowGrid) tool_bar.Realize() return tool_bar def _BuildMenu(self): menu_bar = wx.MenuBar() # File menu file_menu = wx.Menu() menu_bar.Append(file_menu, '&File') ID_NEW_BITMAP = wx.ID_NEW file_menu.Append(ID_NEW_BITMAP, 'New Bitmap', 'Start a new bitmap') wx.EVT_MENU(self, ID_NEW_BITMAP, self.OnNewBitmap) ID_LOAD_BITMAP = wx.ID_OPEN file_menu.Append(ID_LOAD_BITMAP, 'Load Bitmap', 'Load a bitmap') wx.EVT_MENU(self, ID_LOAD_BITMAP, self.OnLoadBitmap) ID_SAVE_BITMAP = wx.ID_SAVE file_menu.Append(ID_SAVE_BITMAP, 'Save Bitmap', 'Save a bitmap') wx.EVT_MENU(self, ID_SAVE_BITMAP, self.OnSaveBitmap) file_menu.AppendSeparator() # Export sub-menu export_menu = wx.Menu() file_menu.AppendMenu(wx.ID_ANY, 'E&xport', export_menu) ID_EXPORT_ANIMATION = wx.NewId() export_menu.Append(ID_EXPORT_ANIMATION, 'Export animation', 'Export as animation') wx.EVT_MENU(self, ID_EXPORT_ANIMATION, self.OnExportAnimation) ID_EXPORT_SNAPSHOT = wx.NewId() export_menu.Append(ID_EXPORT_SNAPSHOT, 'Export snapshot', 'Export a snapshot of the current canvas') wx.EVT_MENU(self, ID_EXPORT_SNAPSHOT, self.OnExportSnapshot) # Last item of file_menu ID_EXIT_MENUITEM = wx.ID_EXIT file_menu.Append(ID_EXIT_MENUITEM, 'E&xit\tAlt-X', 'Exit the program') wx.EVT_MENU(self, ID_EXIT_MENUITEM, self.OnQuit) # Help menu help_menu = wx.Menu() menu_bar.Append(help_menu, '&Help') ID_HELP_MENUITEM = wx.ID_HELP help_menu.Append(ID_HELP_MENUITEM, 'About\tAlt-A', 'Show Informations') wx.EVT_MENU(self, ID_HELP_MENUITEM, self.ShowAboutDialog) return menu_bar def addPixel(self, event): x, y = event.GetLogicalPosition(self.view.dc) self.SetStatusText("Last Click at %-3d,%-3d" % (x, y), 0) r, theta = CanvasView.cartesian2polar(x, y, self.view.offset_angle) self.model.setPixelColor(r, theta, self.color) def OnNewBitmap(self, event): if self.ShowConfirmationDialog() == wx.ID_YES: self.model.Reset() self.view.drawAllPixels() self.view.Refresh() def OnLoadBitmap(self, event): dialog = wx.FileDialog(self, "Load bitmap", "", "", "PNG files (*.png)|*.png", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) ret = dialog.ShowModal() file_path = dialog.GetPath() dialog.Destroy() if ret == wx.ID_CANCEL: return if self.view.loadImage(file_path): self.view.drawAllPixels() self.view.Refresh() else: self.ShowErrorDialog("Image is not %dx%d" % (self.model.width, self.model.height)) def OnSaveBitmap(self, event): dialog = wx.FileDialog(self, "Save bitmap", "", "", "PNG files (*.png)|*.png", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) ret = dialog.ShowModal() file_path = dialog.GetPath() dialog.Destroy() if ret == wx.ID_CANCEL: return bitmap = wx.BitmapFromBuffer(self.model.width, self.model.height, self.model.pixels_array) bitmap.SaveFile(file_path, wx.BITMAP_TYPE_PNG) def OnExportAnimation(self, event): dialog = wx.FileDialog(self, "Save animation", "", "animation.h", "C header files (*.h)|*.h", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) ret = dialog.ShowModal() file_path = dialog.GetPath() dialog.Destroy() if ret == wx.ID_CANCEL: return self.model.saveAsAnimation(file_path) def OnExportSnapshot(self, event): dialog = wx.FileDialog(self, "Take snapwhot", "", "snapshot.png", "PNG files (*.png)|*.png", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) ret = dialog.ShowModal() file_path = dialog.GetPath() dialog.Destroy() if ret == wx.ID_CANCEL: return self.view.pixels_buffer.SaveFile(file_path, wx.BITMAP_TYPE_PNG) def OnQuit(self, event): if self.ShowConfirmationDialog("Exit the program?") == wx.ID_YES: self.Destroy() def OnPickColor(self, event): self.color = event.Colour.asTuple() def OnShowGrid(self, event): self.view.draw_grid = event.Checked() self.view.Refresh() def OnLeftDown(self, event): self.addPixel(event) self.view.CaptureMouse() def OnLeftUp(self, event): self.view.ReleaseMouse() def OnMouseMotion(self, event): if event.Dragging() and event.LeftIsDown(): self.addPixel(event) def UpdateStatusBar(self, event): if self.model.last_pixel: x, y = self.model.last_pixel r, theta = self.model.toPolar(x, y) self.SetStatusText("r: %-4.1f theta: %-4.1f" % (r, theta), 1) self.SetStatusText("x: %-2d y: %-2d" % (x, y), 2) def UpdateView(self, event): if self.model.last_pixel: self.view.drawPixel(self.model.last_pixel) self.view.Refresh() def ShowConfirmationDialog(self, message=None): if not message: message = "With this operation you can loose your data.\n\n" message += "Are you really sure you want to proceed?" dialog = wx.MessageDialog(self, message, "Warning!", style=wx.YES_NO | wx.ICON_QUESTION) ret = dialog.ShowModal() dialog.Destroy() return ret def ShowErrorDialog(self, message): dialog = wx.MessageDialog(self, message, "Error!", style=wx.OK | wx.ICON_ERROR) ret = dialog.ShowModal() dialog.Destroy() def ShowAboutDialog(self, event): info = wx.AboutDialogInfo() info.Name = "PoPiPaint - Polar Pixel Painter" info.Copyright = "(C) 2014 Antonio Ospite" text = "A prototype program for the JMPrope project," text += "the programmable jump rope with LEDs." info.Description = wordwrap(text, 350, wx.ClientDC(self)) info.WebSite = ("http://ao2.it", "http://ao2.it") info.Developers = ["Antonio Ospite"] info.License = "GNU/GPLv3" wx.AboutBox(info)