#!/usr/bin/env python #Copyright (c) 2007-8, Playful Invention Company #Copyright (c) 2008-10, Walter Bender #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. import pygtk pygtk.require('2.0') import gtk import getopt import sys import os import os.path import cStringIO import errno try: import pycurl import xmlrpclib _UPLOAD_AVAILABLE = True except ImportError, e: print "Import Error: %s. Project upload is disabled." % (e) _UPLOAD_AVAILABLE = False try: # Try to use XDG Base Directory standard for config files import xdg.BaseDirectory CONFIG_HOME = os.path.join(xdg.BaseDirectory.xdg_config_home, 'turtleart') except ImportError, e: # Default to `.config` per the spec. CONFIG_HOME = os.path.expanduser(os.path.join('~', '.config', 'turtleart')) argv = sys.argv[:] # Workaround for import behavior of gst in tagplay sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h' from gettext import gettext as _ from TurtleArt.taconstants import OVERLAY_LAYER from TurtleArt.tautils import data_to_string, data_from_string, get_save_name from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo _HELP_MSG = 'turtleart.py: ' + _('usage is') + """ \tturtleart.py \tturtleart.py project.ta \tturtleart.py --output_png project.ta \tturtleart.py -o project""" _MAX_FILE_SIZE = 950000 _INSTALL_PATH = '/usr/share/turtleart' _ALTERNATE_INSTALL_PATH = '/usr/local/share/turtleart' _ICON_SUBPATH = 'images/turtle.png' _UPLOAD_SERVER = 'http://turtleartsite.appspot.com' def mkdir_p(path): '''Create a directory in a fashion similar to `mkdir -p`''' try: os.makedirs(path) except OSError as exc: if exc.errno == errno.EEXIST: pass else: raise def _make_sub_menu(menu, name): """ add a new submenu to the toolbar """ sub_menu = gtk.MenuItem(name) sub_menu.show() sub_menu.set_submenu(menu) return sub_menu def _make_menu_item(menu, tooltip, callback, arg=None): """ add a new item to the submenu """ menu_items = gtk.MenuItem(tooltip) menu.append(menu_items) if arg is None: menu_items.connect('activate', callback) else: menu_items.connect('activate', callback, arg) menu_items.show() def makepath(path): """ Make a path if it doesn't previously exist """ from os import makedirs from os.path import normpath, dirname, exists dpath = normpath(dirname(path)) if not exists(dpath): makedirs(dpath) class TurtleMain(): """ Launch Turtle Art from outside of Sugar """ def __init__(self): """ Parse command-line options and initialize class """ # If we are invoked to start a project from Gnome, we should make # sure our current directory is TA's source dir. os.chdir(os.path.dirname(__file__)) self.ta_file = None self.output_png = False self.uploading = False # Parse command line try: opts, args = getopt.getopt(argv[1:], 'ho', ['help', 'output_png']) except getopt.GetoptError, err: print str(err) print _HELP_MSG sys.exit(2) for o, a in opts: if o in ('-h', '--help'): print _HELP_MSG sys.exit() if o in ('-o', '--output_png'): self.output_png = True else: assert False, _('No option action:') + ' ' + o if args: self.ta_file = args[0] if len(args) > 1 or self.output_png and self.ta_file is None: print _HELP_MSG sys.exit() if self.ta_file is not None: if not self.ta_file.endswith(('.ta')): self.ta_file += '.ta' if not os.path.exists(self.ta_file): assert False, ('%s: %s' % (self.ta_file, _('File not found'))) self.i = 0 self.scale = 2.0 self.tw = None # make sure Sugar paths are present tapath = os.path.join(os.environ['HOME'], '.sugar', 'default', 'org.laptop.TurtleArtActivity') map (makepath, (os.path.join(tapath, 'data/'), os.path.join(tapath, 'instance/'))) if self.output_png: pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, gtk.gdk.screen_width(), gtk.gdk.screen_height()) canvas, mask = pixbuf.render_pixmap_and_mask() else: win = gtk.Window(gtk.WINDOW_TOPLEVEL) try: data_file = open(os.path.join(CONFIG_HOME, 'turtleartrc'), 'r') except IOError: # Opening the config file failed # We'll assume it needs to be created try: mkdir_p(CONFIG_HOME) data_file = open(os.path.join(CONFIG_HOME, 'turtleartrc'), 'a+') except IOError, e: # We can't write to the configuration file, use # a faux file that will persist for the length of # the session. print _('Configuration directory not writable: %s') % (e) data_file = cStringIO.StringIO() data_file.write(str(50) + '\n') data_file.write(str(50) + '\n') data_file.write(str(800) + '\n') data_file.write(str(550) + '\n') data_file.seek(0) self.x = int(data_file.readline()) self.y = int(data_file.readline()) self.width = int(data_file.readline()) self.height = int(data_file.readline()) win.set_default_size(self.width, self.height) win.move(self.x, self.y) win.maximize() win.set_title(_('Turtle Art')) if os.path.exists(os.path.join(_INSTALL_PATH, _ICON_SUBPATH)): win.set_icon_from_file(os.path.join(_INSTALL_PATH, _ICON_SUBPATH)) else: try: win.set_icon_from_file(_ICON_SUBPATH) except IOError: pass win.connect('delete_event', self._quit_ta) menu = gtk.Menu() _make_menu_item(menu, _('New'), self._do_new_cb) _make_menu_item(menu, _('Open'), self._do_open_cb) _make_menu_item(menu, _('Save'), self._do_save_cb) _make_menu_item(menu, _('Save As'), self._do_save_as_cb) _make_menu_item(menu, _('Save as image'), self._do_save_picture_cb) _make_menu_item(menu, _('Save as HTML'), self._do_save_html_cb) _make_menu_item(menu, _('Save as Logo'), self._do_save_logo_cb) if _UPLOAD_AVAILABLE: _make_menu_item(menu, _('Upload to Web'), self._do_upload_to_web) _make_menu_item(menu, _('Quit'), self.destroy) activity_menu = _make_sub_menu(menu, _('File')) menu = gtk.Menu() _make_menu_item(menu, _('Cartesian coordinates'), self._do_cartesian_cb) _make_menu_item(menu, _('Polar coordinates'), self._do_polar_cb) _make_menu_item(menu, _('Rescale coordinates'), self._do_rescale_cb) _make_menu_item(menu, _('Grow blocks'), self._do_resize_cb, 1.5) _make_menu_item(menu, _('Shrink blocks'), self._do_resize_cb, 0.667) _make_menu_item(menu, _('Reset block size'), self._do_resize_cb, -1) view_menu = _make_sub_menu(menu, _('View')) menu = gtk.Menu() _make_menu_item(menu, _('Copy'), self._do_copy_cb) _make_menu_item(menu, _('Paste'), self._do_paste_cb) edit_menu = _make_sub_menu(menu, _('Edit')) menu = gtk.Menu() _make_menu_item(menu, _('Show palette'), self._do_palette_cb) _make_menu_item(menu, _('Hide palette'), self._do_hide_palette_cb) _make_menu_item(menu, _('Show/hide blocks'), self._do_hideshow_cb) tool_menu = _make_sub_menu(menu, _('Tools')) menu = gtk.Menu() _make_menu_item(menu, _('Clean'), self._do_eraser_cb) _make_menu_item(menu, _('Run'), self._do_run_cb) _make_menu_item(menu, _('Step'), self._do_step_cb) _make_menu_item(menu, _('Debug'), self._do_trace_cb) _make_menu_item(menu, _('Stop'), self._do_stop_cb) turtle_menu = _make_sub_menu(menu, _('Turtle')) vbox = gtk.VBox(False, 0) win.add(vbox) vbox.show() menu_bar = gtk.MenuBar() vbox.pack_start(menu_bar, False, False, 2) menu_bar.show() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.show() canvas = gtk.DrawingArea() width = gtk.gdk.screen_width() * 2 height = gtk.gdk.screen_height() * 2 canvas.set_size_request(width, height) sw.add_with_viewport(canvas) canvas.show() vbox.pack_end(sw, True, True) menu_bar.append(activity_menu) menu_bar.append(edit_menu) menu_bar.append(view_menu) menu_bar.append(tool_menu) menu_bar.append(turtle_menu) win.show_all() if os.path.exists(_INSTALL_PATH): self.tw = TurtleArtWindow(canvas, _INSTALL_PATH) elif os.path.exists(_ALTERNATE_INSTALL_PATH): self.tw = TurtleArtWindow(canvas, _ALTERNATE_INSTALL_PATH) else: self.tw = TurtleArtWindow(canvas, os.path.abspath('.')) self.tw.save_folder = os.path.expanduser('~') if not self.output_png: win.connect('configure_event', self.tw.update_overlay_position) self.tw.win = win if self.ta_file is None: self.tw.load_start() else: print self.ta_file self.tw.load_start(self.ta_file) self.tw.lc.trace = 0 self.tw.run_button(0) gtk.main() else: self.tw.load_start(self.ta_file) self.tw.lc.trace = 0 self.tw.run_button(0) self.tw.save_as_image(self.ta_file, canvas) def _quit_ta(self, widget=None, e=None): """ Save changes on exit """ project_empty = self.tw.is_project_empty() if not project_empty: if self.tw.is_new_project(): self._show_save_dialog(True) else: if self.tw.project_has_changes(): self._show_save_dialog(False) gtk.main_quit() def _show_save_dialog(self, new_project=True): """ Dialog for save project """ dlg = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL, message_format=\ _('You have unsaved work. Would you like to save before quitting?')) dlg.set_title(_('Save project?')) dlg.set_property('skip-taskbar-hint', False) resp = dlg.run() dlg.destroy() if resp == gtk.RESPONSE_OK: if new_project: self._save_as() else: self._save_changes() def _do_new_cb(self, widget): """ Callback for new project. """ self.tw.new_project() self.tw.load_start() def _do_open_cb(self, widget): """ Callback for open project. """ self.tw.load_file(True) def _do_save_cb(self, widget): """ Callback for save project. """ self.tw.save_file() def _do_save_as_cb(self, widget): """ Callback for save-as project. """ self._save_as() def _save_as(self): """ Save as is called from callback and quit """ self.tw.save_file_name = None self.tw.save_file() def _save_changes(self): """ Save changes to current project """ self.tw.save_file_name = None self.tw.save_file(self.tw._loaded_project) def _do_save_picture_cb(self, widget): """ Callback for save canvas. """ self.tw.save_as_image() def _do_save_html_cb(self, widget): """ Callback for save project to HTML. """ html = save_html(self, self.tw, False) if len(html) == 0: return save_type = '.html' if len(self.tw.saved_pictures) > 0: if self.tw.saved_pictures[0].endswith(('.svg')): save_type = '.xml' filename, self.tw.load_save_folder = get_save_name(save_type, self.tw.load_save_folder, 'portfolio') f = file(filename, 'w') f.write(html) f.close() self.tw.saved_pictures = [] def _do_save_logo_cb(self, widget): """ Callback for save project to Logo. """ logocode = save_logo(self.tw) if len(logocode) == 0: return save_type = '.lg' filename, self.tw.load_save_folder = get_save_name(save_type, self.tw.load_save_folder, 'logosession') f = file(filename, 'w') f.write(logocode) f.close() def _do_resize_cb(self, widget, factor): """ Callback to resize blocks. """ if factor == -1: self.tw.block_scale = 2.0 else: self.tw.block_scale *= factor self.tw.resize_blocks() def _do_cartesian_cb(self, button): """ Callback to display/hide Cartesian coordinate overlay. """ if self.tw.cartesian is True: if self.tw.coord_scale == 1: self.tw.overlay_shapes['Cartesian_labeled'].hide() else: self.tw.overlay_shapes['Cartesian'].hide() self.tw.cartesian = False else: if self.tw.coord_scale == 1: self.tw.overlay_shapes['Cartesian_labeled'].set_layer( OVERLAY_LAYER) else: self.tw.overlay_shapes['Cartesian'].set_layer(OVERLAY_LAYER) self.tw.cartesian = True def _do_polar_cb(self, button): """ Callback to display/hide Polar coordinate overlay. """ if self.tw.polar is True: self.tw.overlay_shapes['polar'].hide() self.tw.polar = False else: self.tw.overlay_shapes['polar'].set_layer(OVERLAY_LAYER) self.tw.polar = True def _do_rescale_cb(self, button): """ Callback to rescale coordinate space. """ if self.tw.coord_scale == 1: self.tw.coord_scale = self.tw.height / 200 self.tw.eraser_button() if self.tw.cartesian is True: self.tw.overlay_shapes['Cartesian_labeled'].hide() self.tw.overlay_shapes['Cartesian'].set_layer(OVERLAY_LAYER) else: self.tw.coord_scale = 1 self.tw.eraser_button() if self.tw.cartesian is True: self.tw.overlay_shapes['Cartesian'].hide() self.tw.overlay_shapes['Cartesian_labeled'].set_layer( OVERLAY_LAYER) def _do_palette_cb(self, widget): """ Callback to show/hide palette of blocks. """ self.tw.show_palette(self.i) self.i += 1 if self.i == len(self.tw.palettes): self.i = 0 def _do_hide_palette_cb(self, widget): """ Hide the palette of blocks. """ self.tw.hide_palette() def _do_hideshow_cb(self, widget): """ Hide/show the blocks. """ self.tw.hideshow_button() def _do_eraser_cb(self, widget): """ Callback for eraser button. """ self.tw.eraser_button() return def _do_run_cb(self, widget): """ Callback for run button (rabbit). """ self.tw.lc.trace = 0 self.tw.run_button(0) return def _do_step_cb(self, widget): """ Callback for step button (turtle). """ self.tw.lc.trace = 0 self.tw.run_button(3) return def _do_trace_cb(self, widget): """ Callback for debug button (bug). """ self.tw.lc.trace = 1 self.tw.run_button(6) return def _do_stop_cb(self, widget): """ Callback for stop button. """ self.tw.lc.trace = 0 self.tw.stop_button() return def _do_copy_cb(self, button): """ Callback for copy button. """ clipBoard = gtk.Clipboard() data = self.tw.assemble_data_to_save(False, False) if data is not []: text = data_to_string(data) clipBoard.set_text(text) def _do_paste_cb(self, button): """ Callback for paste button. """ clipBoard = gtk.Clipboard() text = clipBoard.wait_for_text() if text is not None: if self.tw.selected_blk is not None and\ self.tw.selected_blk.name == 'string': for i in text: self.tw.process_alphanumeric_input(i, -1) self.tw.selected_blk.resize() else: self.tw.process_data(data_from_string(text), self.tw.paste_offset) self.tw.paste_offset += 20 def _window_event(self, event, data): """ Callback for resize event. """ data_file = open('.turtleartrc', 'w') data_file.write(str(data.x) + '\n') data_file.write(str(data.y) + '\n') data_file.write(str(data.width) + '\n') data_file.write(str(data.height) + '\n') def destroy(self, event, data=None): """ Callback for destroy event. """ gtk.main_quit() def _do_upload_to_web(self, widget): """ Create dialog for uploading current project to the Web """ if not self.uploading: self.uploading = True self.pop_up = gtk.Window() self.pop_up.set_default_size(600, 400) self.pop_up.connect('delete_event', self._stop_uploading) table = gtk.Table(8, 1, False) self.pop_up.add(table) login_label = gtk.Label(_('You must have an account at \ http://turtleartsite.sugarlabs.org to upload your project.')) table.attach(login_label, 0, 1, 0, 1) self.login_message = gtk.Label('') table.attach(self.login_message, 0, 1, 1, 2) self.Hbox1 = gtk.HBox() table.attach(self.Hbox1, 0, 1, 2, 3, xpadding=5, ypadding=3) self.username_entry = gtk.Entry() username_label = gtk.Label(_('Username:') + ' ') username_label.set_size_request(150, 25) username_label.set_alignment(1.0, 0.5) self.username_entry.set_size_request(450, 25) self.Hbox1.add(username_label) self.Hbox1.add(self.username_entry) self.Hbox2 = gtk.HBox() table.attach(self.Hbox2, 0, 1, 3, 4, xpadding=5, ypadding=3) self.password_entry = gtk.Entry() password_label = gtk.Label(_('Password:') + ' ') self.password_entry.set_visibility(False) password_label.set_size_request(150, 25) password_label.set_alignment(1.0, 0.5) self.password_entry.set_size_request(450, 25) self.Hbox2.add(password_label) self.Hbox2.add(self.password_entry) self.Hbox3 = gtk.HBox() table.attach(self.Hbox3, 0, 1, 4, 5, xpadding=5, ypadding=3) self.title_entry = gtk.Entry() title_label = gtk.Label(_('Title:') + ' ') title_label.set_size_request(150, 25) title_label.set_alignment(1.0, 0.5) self.title_entry.set_size_request(450, 25) self.Hbox3.add(title_label) self.Hbox3.add(self.title_entry) self.Hbox4 = gtk.HBox() table.attach(self.Hbox4, 0, 1, 5, 6, xpadding=5, ypadding=3) self.description_entry = gtk.TextView() description_label = gtk.Label(_('Description:') + ' ') description_label.set_size_request(150, 25) description_label.set_alignment(1.0, 0.5) self.description_entry.set_wrap_mode(gtk.WRAP_WORD) self.description_entry.set_size_request(450, 50) self.Hbox4.add(description_label) self.Hbox4.add(self.description_entry) self.Hbox5 = gtk.HBox() table.attach(self.Hbox5, 0, 1, 6, 7, xpadding=5, ypadding=3) self.submit_button = gtk.Button(_('Submit to Web')) self.submit_button.set_size_request(300, 25) self.submit_button.connect('pressed', self._do_remote_logon) self.Hbox5.add(self.submit_button) self.cancel_button = gtk.Button(_('Cancel')) self.cancel_button.set_size_request(300, 25) self.cancel_button.connect('pressed', self._stop_uploading) self.Hbox5.add(self.cancel_button) self.pop_up.show_all() def _stop_uploading(self, widget, event=None): """ Hide the popup when the upload is complte """ self.uploading = False self.pop_up.hide() def _do_remote_logon(self, widget): """ Log into the upload server """ username = self.username_entry.get_text() password = self.password_entry.get_text() server=xmlrpclib.ServerProxy(_UPLOAD_SERVER + '/call/xmlrpc') logged_in = server.login_remote(username,password) if logged_in: upload_key = logged_in self._do_submit_to_web(upload_key) else: self.login_message.set_text(_('Login failed')) def _do_submit_to_web(self, key): """ Submit project to the server """ title = self.title_entry.get_text() description = self.description_entry.get_buffer().get_text( *self.description_entry.get_buffer().get_bounds()) tafile, imagefile = self.tw.save_for_upload(title) # Set a maximum file size for image to be uploaded. if int(os.path.getsize(imagefile)) > _MAX_FILE_SIZE: import Image while int(os.path.getsize(imagefile)) > _MAX_FILE_SIZE: big_file = Image.open(imagefile) smaller_file = big_file.resize(int(0.9 * big_file.size[0]), int(0.9 * big_file.size[1]), Image.ANTIALIAS) smaller_file.save(imagefile, quality = 100) c = pycurl.Curl() c.setopt(c.POST, 1) c.setopt(c.FOLLOWLOCATION, 1) c.setopt(c.URL, _UPLOAD_SERVER + '/upload') c.setopt(c.HTTPHEADER, ["Expect:"]) c.setopt(c.HTTPPOST, [('file', (c.FORM_FILE, tafile)), ('newimage', (c.FORM_FILE, imagefile)), ('small_image', (c.FORM_FILE, imagefile)), ('title', title), ('description', description), ('upload_key',key), ('_formname', 'image_create')]) c.perform() error_code = c.getinfo(c.HTTP_CODE) c.close os.remove(imagefile) os.remove(tafile) if error_code == 400: self.login_message.set_text(_('Failed to upload!')) else: self.pop_up.hide() self.uploading = False if __name__ == "__main__": TurtleMain()