Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS20
-rw-r--r--TurtleArt/sprites.py9
-rw-r--r--TurtleArt/taconstants.py2
-rw-r--r--TurtleArt/talogo.py32
-rw-r--r--TurtleArt/tawindow.py45
-rw-r--r--TurtleArtActivity.py159
-rw-r--r--images/dupstack.svg69
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py13
-rwxr-xr-xturtleblocks.py82
9 files changed, 259 insertions, 172 deletions
diff --git a/NEWS b/NEWS
index 5556108..b02eb3b 100644
--- a/NEWS
+++ b/NEWS
@@ -1,15 +1,21 @@
194
ENHANCEMENTS:
-* Save/restore palette settings
-* Add confirmation when emptying trash in GNOME version
+* Save/restore palette settings in GConf
+* Add confirmation when emptying trash in GNONE version (Alan Aguiar)
+* Add load/save plugin dialog to GNOME version (Daniel Francis)
+* Add busy cursor when projects are being prepared to run
+* Reworked Duplicate Blocks warning graphic to reflect current block shapes
+* New translations
BUG FIX:
-* Fix shade block dock settings (Alan Aguiar)
-* Fix bug in empty trash (Alan Aguiar)
-* Set active turtle after clean in exported Python code
-* Execute until body once before entering loop in exported Python code
-* Fix problem with WeDo plugin (Alan Aguiar)
+* Fixed shade block dock settings (Alan Aguiar)
+* Fixed bug in empty trash (Alan Aguiar)
+* Set active turtle after 'clean' in exported Python code
+* Execute 'until' body once before entering loop in exported Python code
+* Fixed problem with running_blocks flag not being cleared (cause of spurious
+ "hit stop button" messages.
+* Fixed problem with errant text entry widget on run.
193
diff --git a/TurtleArt/sprites.py b/TurtleArt/sprites.py
index 7483c12..3f5d7df 100644
--- a/TurtleArt/sprites.py
+++ b/TurtleArt/sprites.py
@@ -106,7 +106,7 @@ class Sprites:
def length_of_list(self):
''' How many sprites are there? '''
- return(len(self.list))
+ return len(self.list)
def append_to_list(self, spr):
''' Append a new sprite to the end of the list. '''
@@ -122,9 +122,7 @@ class Sprites:
self.list.insert(i, spr)
def find_in_list(self, spr):
- if spr in self.list:
- return True
- return False
+ return (spr in self.list)
def remove_from_list(self, spr):
''' Remove a sprite from the list. '''
@@ -258,7 +256,8 @@ class Sprite:
if layer is not None:
self.layer = layer
for i in range(self._sprites.length_of_list()):
- if self.layer < self._sprites.get_sprite(i).layer:
+ spr = self._sprites.get_sprite(i)
+ if spr is not None and self.layer < spr.layer:
self._sprites.insert_in_list(self, i)
self.inval()
return
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
index 4cedb34..78cb9e1 100644
--- a/TurtleArt/taconstants.py
+++ b/TurtleArt/taconstants.py
@@ -287,7 +287,7 @@ OVERLAY_SHAPES = ['Cartesian', 'Cartesian_labeled', 'polar', 'metric']
STATUS_SHAPES = ['status', 'info', 'nostack', 'dupstack', 'noinput',
'emptyheap', 'emptybox', 'nomedia', 'nocode', 'overflowerror',
'negroot', 'syntaxerror', 'nofile', 'nojournal', 'zerodivide',
- 'notanumber', 'incompatible', 'help', 'print']
+ 'notanumber', 'incompatible', 'help', 'print', 'noconnection']
# Emulate Sugar toolbar when running from outside of Sugar
TOOLBAR_SHAPES = ['hideshowoff', 'eraseron', 'run-fastoff',
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index ba76085..67aa32c 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -29,6 +29,8 @@ from operator import isNumberType
import os
from os.path import exists as os_path_exists
from UserDict import UserDict
+import urllib2
+import tempfile
try:
from sugar.graphics import style
@@ -1060,6 +1062,36 @@ class LogoCode:
gobject.idle_add(self.tw.send_event, event)
os.remove(tmp_file)
+ def get_from_url(self, url):
+ """ Get contents of URL as text or tempfile to image """
+ if "://" not in url: # no protocol
+ url = "http://" + url # assume HTTP
+ try:
+ req = urllib2.urlopen(url)
+ except urllib2.HTTPError, e:
+ debug_output("Couldn't open %s: %s" % (url, e),
+ self.tw.running_sugar)
+ raise logoerror(url + ' [%d]' % (e.code))
+ except urllib2.URLError, e:
+ if hasattr(e, 'code'):
+ debug_output("Couldn't open %s: %s" % (url, e),
+ self.tw.running_sugar)
+ raise logoerror(url + ' [%d]' % (e.code))
+ else: # elif hasattr(e, 'reason'):
+ debug_output("Couldn't reach server: %s" % (e),
+ self.tw.running_sugar)
+ raise logoerror('#noconnection')
+
+ if req.info().getheader("Content-Type")[0:5] == "image":
+ # it can't be deleted immediately, or else we won't ever access it
+ tmp = tempfile.NamedTemporaryFile(delete=False)
+ tmp.write(req.read()) # prepare for writing
+ tmp.flush() # actually write it
+ obj = Media('media', value=tmp.name)
+ return obj
+ else:
+ return req.read()
+
def showlist(self, objects):
""" Display list of media objects """
x = (self.tw.turtles.get_active_turtle().get_xy()[0] /
diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py
index 5b9a1c6..2aa8f89 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -1662,9 +1662,20 @@ before making changes to your program'))
name = blk.name
# You can only have one instance of some blocks
if blk.name in ['start', 'hat1', 'hat2']:
- if len(self.block_list.get_similar_blocks(
- 'block', blk.name)) > 0:
+ blk_list = self.block_list.get_similar_blocks(
+ 'block', blk.name)
+ if len(blk_list) > 0:
self.showlabel('dupstack')
+ if blk.name == 'start':
+ # Recenter the screen and move the start
+ # stack to the center of the screen
+ if self.running_sugar:
+ self.activity.recenter()
+ dx = 200 - blk_list[0].spr.get_xy()[0]
+ dy = 200 - blk_list[0].spr.get_xy()[1]
+ drag_group = find_group(blk_list[0])
+ for dblk in drag_group:
+ dblk.spr.move_relative((dx, dy))
return True
# We need to check to see if there is already a
# similarly default named stack
@@ -2176,7 +2187,7 @@ before making changes to your program'))
for gblk in group:
if gblk.name == 'sandwichclampcollapsed':
restore_clamp(gblk)
- self.resize_parent_clamps(gblk)
+ self._resize_parent_clamps(gblk)
for gblk in group:
gblk.rescale(self.block_scale)
@@ -3217,6 +3228,13 @@ before making changes to your program'))
if len(self.block_list.get_similar_blocks('block', 'forever')) > 0:
debug_output('WARNING: Projects with forever blocks \
may not terminate.', False)
+ else:
+ self._hide_text_entry()
+ self.parent.get_window().set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.WATCH))
+ gobject.idle_add(self.__run_stack, blk)
+
+ def __run_stack(self, blk):
if self.status_spr is not None:
self.status_spr.hide()
self._autohide_shape = True
@@ -3229,12 +3247,16 @@ before making changes to your program'))
self.start_plugins() # Let the plugins know we are running.
top = find_top_block(blk)
code = self.lc.generate_code(top, self.just_blocks())
+ if self.interactive_mode:
+ self.parent.get_window().set_cursor(
+ gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.lc.run_blocks(code)
if self.interactive_mode:
gobject.idle_add(self.lc.doevalstep)
else:
while self.lc.doevalstep():
pass
+ self.running_blocks = False
def _snap_to_dock(self):
''' Snap a block (selected_block) to the dock of another block
@@ -3810,11 +3832,8 @@ before making changes to your program'))
self._snap_to_dock()
self.drag_group = None
- def _test_number(self):
- ''' Make sure a 'number' block contains a number. '''
+ def _hide_text_entry(self):
if hasattr(self, '_text_entry'):
- bounds = self._text_buffer.get_bounds()
- text = self._text_buffer.get_text(bounds[0], bounds[1])
if self._focus_out_id is not None:
self._text_entry.disconnect(self._focus_out_id)
self._focus_out_id = None
@@ -3822,6 +3841,13 @@ before making changes to your program'))
self._text_buffer.disconnect(self._insert_text_id)
self._insert_text_id = None
self._text_entry.hide()
+
+ def _test_number(self):
+ ''' Make sure a 'number' block contains a number. '''
+ if hasattr(self, '_text_entry'):
+ bounds = self._text_buffer.get_bounds()
+ text = self._text_buffer.get_text(bounds[0], bounds[1])
+ self._hide_text_entry()
else:
text = self.selected_blk.spr.labels[0]
self._number_check(text)
@@ -3868,12 +3894,9 @@ before making changes to your program'))
def _test_string(self):
if hasattr(self, '_text_entry'):
- if self._focus_out_id is not None:
- self._text_entry.disconnect(self._focus_out_id)
- self._focus_out_id = None
bounds = self._text_buffer.get_bounds()
text = self._text_buffer.get_text(bounds[0], bounds[1])
- self._text_entry.hide()
+ self._hide_text_entry()
else:
text = self.selected_blk.spr.labels[0]
self.selected_blk.spr.set_label(text.replace('\12', RETURN))
diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py
index f15650e..f6b94ca 100644
--- a/TurtleArtActivity.py
+++ b/TurtleArtActivity.py
@@ -63,6 +63,8 @@ except ImportError:
from gettext import gettext as _
+from TurtleArt.taplugin import (load_a_plugin, cancel_plugin_install,
+ complete_plugin_install)
from TurtleArt.tapalette import (palette_names, help_strings, help_palettes,
help_windows, default_values)
from TurtleArt.taconstants import (BLOCK_SCALE, XO1, XO15, XO175, XO4,
@@ -95,6 +97,8 @@ class TurtleArtActivity(activity.Activity):
self.tw = None
self.init_complete = False
+ self.bundle_path = activity.get_bundle_path()
+
self.error_list = []
self.palette_buttons = []
@@ -327,9 +331,9 @@ class TurtleArtActivity(activity.Activity):
def do_load_python_cb(self, button):
''' Load Python code from the Journal. '''
- self.load_python.set_icon('python-saveon')
+ self.load_python.set_icon('pippy-openon')
self.tw.load_python_code_from_file(fname=None, add_new_block=True)
- gobject.timeout_add(250, self.load_python.set_icon, 'python-saveoff')
+ gobject.timeout_add(250, self.load_python.set_icon, 'pippy-openoff')
def do_save_as_image_cb(self, button):
''' Save the canvas to the Journal. '''
@@ -886,10 +890,7 @@ class TurtleArtActivity(activity.Activity):
help_palettes['activity-toolbar'].show()
add_paragraph(help_box, _('Share selected blocks'), icon='shareon')
- if gtk.gdk.screen_width() < 1024:
- add_paragraph(help_box, _('Save/Load'), icon='save-load')
- else:
- add_section(help_box, _('Save/Load'), icon='turtleoff')
+ add_paragraph(help_box, _('Save/Load'), icon='save-load')
add_paragraph(help_box, _('Save as image'), icon='image-saveoff')
add_paragraph(help_box, _('Save as Logo'), icon='logo-saveoff')
add_paragraph(help_box, _('Save as Python'), icon='python-saveoff')
@@ -899,7 +900,7 @@ class TurtleArtActivity(activity.Activity):
if activity.get_bundle_path()[0:len(home)] == home:
add_paragraph(help_box, _('Load plugin'), icon='pluginoff')
add_paragraph(help_box, _('Load Python block'),
- icon='python-saveoff')
+ icon='pippy-openoff')
help_box = gtk.VBox()
help_box.set_homogeneous(False)
@@ -1067,10 +1068,10 @@ class TurtleArtActivity(activity.Activity):
self._share_cb, toolbar)
if self.has_toolbarbox:
self._add_separator(toolbar, expand=False, visible=True)
- save_load_button = self._add_button(
- 'save-load', _('Save/Load'), self._save_load_palette_cb,
+ save_button = self._add_button(
+ 'save', _('Save'), self._save_load_palette_cb,
toolbar)
- self._palette = save_load_button.get_palette()
+ self._save_palette = save_button.get_palette()
button_box = gtk.VBox()
self.save_as_image, label = self._add_button_and_label(
'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
@@ -1086,6 +1087,14 @@ class TurtleArtActivity(activity.Activity):
'filesaveoff', _('Save snapshot'), self.do_keep_cb,
None, button_box)
+ load_button = self._add_button(
+ 'load', _('Load'), self._save_load_palette_cb,
+ toolbar)
+ button_box.show_all()
+ self._save_palette.set_content(button_box)
+
+ self._load_palette = load_button.get_palette()
+ button_box = gtk.VBox()
# When screen is in portrait mode, the buttons don't fit
# on the main toolbar, so put them here.
self.samples_button2, self.samples_label2 = \
@@ -1107,10 +1116,10 @@ class TurtleArtActivity(activity.Activity):
'pluginoff', _('Load plugin'),
self.do_load_ta_plugin_cb, None, button_box)
self.load_python, label = self._add_button_and_label(
- 'python-saveoff', _('Load Python block'),
+ 'pippy-openoff', _('Load Python block'),
self.do_load_python_cb, None, button_box)
button_box.show_all()
- self._palette.set_content(button_box)
+ self._load_palette.set_content(button_box)
else:
self.save_as_image = self._add_button(
'image-saveoff', _('Save as image'), self.do_save_as_image_cb,
@@ -1125,7 +1134,7 @@ class TurtleArtActivity(activity.Activity):
self.keep_button = self._add_button(
'filesaveoff', _('Save snapshot'), self.do_keep_cb, toolbar)
self.load_ta_project = self._add_button(
- 'load-from-journal', _('Load project'),
+ 'load-from-journal', _('Add project'),
self.do_load_ta_project_cb, toolbar)
# Only enable plugin loading if installed in $HOME
if activity.get_bundle_path()[0:len(home)] == home:
@@ -1133,17 +1142,16 @@ class TurtleArtActivity(activity.Activity):
'pluginoff', _('Load plugin'),
self.do_load_ta_plugin_cb, toolbar)
self.load_python = self._add_button(
- 'python-saveoff', _('Load Python block'),
+ 'pippy-openoff', _('Load Python block'),
self.do_load_python_cb, toolbar)
def _save_load_palette_cb(self, button):
- if self._palette:
- if not self._palette.is_up():
- self._palette.popup(immediate=True,
- state=self._palette.SECONDARY)
+ palette = button.get_palette()
+ if palette:
+ if not palette.is_up():
+ palette.popup(immediate=True, state=palette.SECONDARY)
else:
- self._palette.popdown(immediate=True)
- return
+ palette.popdown(immediate=True)
def _make_palette_buttons(self, toolbar, palette_button=False):
''' Creates the palette and block buttons for both toolbar types'''
@@ -1337,109 +1345,6 @@ class TurtleArtActivity(activity.Activity):
self.metadata['error_list'] = data_to_string(errors)
_logger.debug('Wrote to file: %s' % (file_path))
- def _load_a_plugin(self, tmp_dir):
- ''' Load a plugin from the Journal and initialize it '''
- plugin_path = os.path.join(tmp_dir, 'plugin.info')
- _logger.debug(plugin_path)
- file_info = ConfigParser.ConfigParser()
- if len(file_info.read(plugin_path)) == 0:
- _logger.debug('Required file plugin.info could not be found.')
- self.tw.showlabel('status',
- label=_('Plugin could not be installed.'))
- elif not file_info.has_option('Plugin', 'name'):
- _logger.debug('Required open name not found in \
-Plugin section of plugin.info file.')
- self.tw.showlabel(
- 'status', label=_('Plugin could not be installed.'))
- else:
- plugin_name = file_info.get('Plugin', 'name')
- _logger.debug('Plugin name: %s' % (plugin_name))
- tmp_path = os.path.join(tmp_dir, plugin_name)
- plugin_path = os.path.join(activity.get_bundle_path(), 'plugins')
- if os.path.exists(os.path.join(plugin_path, plugin_name)):
- self._reload_plugin_alert(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
- else:
- self._complete_plugin_install(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
-
- def _complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info):
- ''' We complete the installation directly or from ConfirmationAlert '''
- status = subprocess.call(['cp', '-r', tmp_path, plugin_path + '/'])
- if status == 0:
- # Save the plugin.info file in the plugin directory
- subprocess.call(['cp', os.path.join(tmp_dir, 'plugin.info'),
- os.path.join(plugin_path, plugin_name) + '/'])
- _logger.debug('Plugin installed successfully.')
- if self.has_toolbarbox:
- palette_name_list = []
- if file_info.has_option('Plugin', 'palette'):
- palette_name_list = file_info.get(
- 'Plugin', 'palette').split(',')
- create_palette = []
- for palette_name in palette_name_list:
- if not palette_name.strip() in palette_names:
- create_palette.append(True)
- else:
- create_palette.append(False)
- _logger.debug('Initializing plugin...')
- self.tw.init_plugin(plugin_name)
- self.tw.turtleart_plugins[-1].setup()
- self.tw.load_media_shapes()
- for i, palette_name in enumerate(palette_name_list):
- if create_palette[i]:
- _logger.debug('Creating plugin palette %s (%d)' %
- (palette_name.strip(), i))
- j = len(self.palette_buttons)
- self.palette_buttons.append(
- self._radio_button_factory(
- palette_name.strip() + 'off',
- self._palette_toolbar,
- self.do_palette_buttons_cb,
- j - 1,
- help_strings[palette_name.strip()],
- self.palette_buttons[0]))
- self._overflow_buttons.append(
- self._add_button(
- palette_name.strip() + 'off',
- None,
- self.do_palette_buttons_cb,
- None,
- arg=j - 1))
- self._overflow_box.pack_start(
- self._overflow_buttons[j - 1])
- self.tw.palettes.insert(j - 1, [])
- self.tw.palette_sprs.insert(j - 1, [None, None])
- else:
- _logger.debug('Palette already exists... \
-skipping insert')
- # We need to change the index associated with the
- # Trash Palette Button.
- j = len(palette_names)
- pidx = palette_names.index(palette_name.strip())
- self.palette_buttons[pidx].connect(
- 'clicked', self.do_palette_buttons_cb, j - 1)
- self._overflow_buttons[pidx].connect(
- 'clicked', self.do_palette_buttons_cb, j - 1)
- _logger.debug('reinitializing palette toolbar')
- self._setup_palette_toolbar()
- else:
- self.tw.showlabel('status',
- label=_('Please restart Turtle Art \
-in order to use the plugin.'))
- else:
- self.tw.showlabel(
- 'status', label=_('Plugin could not be installed.'))
- status = subprocess.call(['rm', '-r', tmp_path])
- if status != 0:
- _logger.debug('Problems cleaning up tmp_path.')
- shutil.rmtree(tmp_dir)
-
- def _cancel_plugin_install(self, tmp_dir):
- ''' If we cancel, just cleanup '''
- shutil.rmtree(tmp_dir)
-
def _reload_plugin_alert(self, tmp_dir, tmp_path, plugin_path, plugin_name,
file_info):
''' We warn the user if the plugin was previously loaded '''
@@ -1453,12 +1358,12 @@ in order to use the plugin.'))
if response_id is gtk.RESPONSE_OK:
_logger.debug('continue to install')
self.remove_alert(alert)
- self._complete_plugin_install(tmp_dir, tmp_path, plugin_path,
- plugin_name, file_info)
+ complete_plugin_install(self, tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info)
elif response_id is gtk.RESPONSE_CANCEL:
_logger.debug('cancel install')
self.remove_alert(alert)
- self._cancel_plugin_install(tmp_dir)
+ cancel_plugin_install(self, tmp_dir)
alert.connect('response', _reload_plugin_alert_response_cb, self,
tmp_dir, tmp_path, plugin_path, plugin_name, file_info)
@@ -1501,7 +1406,7 @@ in order to use the plugin.'))
gobject.idle_add(self._project_loader, turtle_code)
else:
_logger.debug('load a plugin from %s' % (tmp_dir))
- self._load_a_plugin(tmp_dir)
+ load_a_plugin(self, tmp_dir)
self.restore_cursor()
except:
_logger.debug('Could not extract files from %s.' %
diff --git a/images/dupstack.svg b/images/dupstack.svg
index 6a0f3fd..f3eacd9 100644
--- a/images/dupstack.svg
+++ b/images/dupstack.svg
@@ -47,21 +47,6 @@
style="font-size:12px">X</tspan>
</text>
</g>
- <path
- d="m 222.97818,18.999999 0,0 -26,15.000001 0,0 0,3 -13.50001,0 0,-3 c 0,0 -26,-15.000001 -26,-15.000001 0,0 33.00001,-17.9999992 33.00001,-17.9999992 0,0 32.5,17.9999992 32.5,17.9999992 z"
- id="path2490-6"
- style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- <text
- x="6.1187973"
- y="4.1250014"
- id="text2474"
- style="font-size:12px;font-style:normal;font-weight:normal;line-height:125%;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
- <tspan
- x="176.1188"
- y="32.125"
- id="tspan2476"
- style="font-size:48px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">x</tspan>
- </text>
<g
transform="translate(6,0)"
id="g2478">
@@ -91,8 +76,54 @@
id="tspan2488"
style="font-size:24px">!</tspan>
</text>
- <path
- d="m 148.5,19 0,0 -26,15 0,0 0,3 -13.5,0 0,-3 c 0,0 -26,-15 -26,-15 0,0 33,-18 33,-18 0,0 32.5,18 32.5,18 z"
- id="path2490"
- style="fill:#ffe000;fill-opacity:1;fill-rule:nonzero;stroke:#c0a000;stroke-width:1.33340001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <g
+ transform="matrix(1.44,0,0,1.44,70,1)"
+ id="g3061"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-opacity:1">
+ <path
+ d="m 0.5,14.5 0,-4 a 4,4 90 0 1 4,-4 l 4,0 5,-6 5,6 30,0 4,0 a 4,4 90 0 1 4,4 l 0,4 0,4 a 4,4 90 0 1 -4,4 l -4,0 -30,0 -1,0 0,2 -8,0 0,-2 -1,0 -4,0 a 4,4 90 0 1 -4,-4 l 0,-4 z"
+ id="path3063"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-width:1;stroke-linecap:round;stroke-opacity:1" />
+ </g>
+ <g
+ transform="matrix(1.44,0,0,1.44,160,1)"
+ id="g3061-8"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-opacity:1">
+ <path
+ d="m 0.5,14.5 0,-4 a 4,4 90 0 1 4,-4 l 4,0 5,-6 5,6 30,0 4,0 a 4,4 90 0 1 4,4 l 0,4 0,4 a 4,4 90 0 1 -4,4 l -4,0 -30,0 -1,0 0,2 -8,0 0,-2 -1,0 -4,0 a 4,4 90 0 1 -4,-4 l 0,-4 z"
+ id="path3063-7"
+ style="fill:#ffe000;fill-opacity:1;stroke:#c0a000;stroke-width:1;stroke-linecap:round;stroke-opacity:1" />
+ </g>
+ <text
+ x="-15.714066"
+ y="-38.59026"
+ transform="scale(1.0155331,-0.98470448)"
+ id="text2474"
+ style="font-size:13.9273119px;font-style:normal;font-weight:normal;line-height:125%;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans">
+ <tspan
+ x="181.58951"
+ y="-6.0931983"
+ id="tspan2476"
+ style="font-size:55.70924759px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans">x</tspan>
+ </text>
+ <text
+ x="103.1943"
+ y="29.748047"
+ id="text2999"
+ xml:space="preserve"
+ style="font-size:288px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="103.1943"
+ y="29.748047"
+ id="tspan3001"
+ style="font-size:24px">1</tspan></text>
+ <text
+ x="193.72749"
+ y="29.90625"
+ id="text2999-5"
+ xml:space="preserve"
+ style="font-size:288px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><tspan
+ x="193.72749"
+ y="29.90625"
+ id="tspan3001-1"
+ style="font-size:24px">2</tspan></text>
</svg>
diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
index df0d1ff..3192021 100644
--- a/plugins/turtle_blocks_extras/turtle_blocks_extras.py
+++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
@@ -862,6 +862,19 @@ module found in the Journal'))
special_name=_('top'),
help_string=_('top of a collapsed stack'))
+ palette.add_block('getfromurl',
+ style='number-style-1arg',
+ #TRANS: URL is universal resource locator
+ label=_('URL'),
+ default=\
+'http://wiki.sugarlabs.org/images/2/2c/Logo_alt_3.svg',
+ prim_name='getfromurl',
+ help_string=\
+_('gets a text string or an image from a URL'))
+ self.tw.lc.def_prim('getfromurl', 1,
+ Primitive(self.tw.lc.get_from_url,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
+
def _portfolio_palette(self):
palette = make_palette('portfolio',
diff --git a/turtleblocks.py b/turtleblocks.py
index 7e3aba4..051d447 100755
--- a/turtleblocks.py
+++ b/turtleblocks.py
@@ -36,6 +36,9 @@ import cStringIO
import errno
import ConfigParser
import gconf
+import tarfile
+import tempfile
+import subprocess
try:
# Try to use XDG Base Directory standard for config files.
@@ -53,12 +56,15 @@ from gettext import gettext as _
from TurtleArt.taconstants import (OVERLAY_LAYER, DEFAULT_TURTLE_COLORS,
TAB_LAYER, SUFFIX)
-from TurtleArt.tautils import (data_from_string, get_save_name)
+from TurtleArt.tautils import (data_from_string, get_load_name,
+ get_path, get_save_name)
from TurtleArt.tapalette import default_values
from TurtleArt.tawindow import TurtleArtWindow
from TurtleArt.taexportlogo import save_logo
from TurtleArt.taexportpython import save_python
from TurtleArt.taprimitive import PyExportError
+from TurtleArt.taplugin import (load_a_plugin, cancel_plugin_install,
+ complete_plugin_install)
from util.menubuilder import MenuBuilder
@@ -89,6 +95,7 @@ class TurtleMain():
self.summary = file_activity_info.get('Activity', 'summary')
self.website = file_activity_info.get('Activity', 'website')
self.icon_name = file_activity_info.get('Activity', 'icon')
+ self.bundle_path = self._abspath
path = os.path.abspath('./locale/')
gettext.bindtextdomain(bundle_id, path)
gettext.textdomain(bundle_id)
@@ -107,6 +114,7 @@ class TurtleMain():
self._gnome_plugins = []
self._selected_sample = None
self._sample_window = None
+ self.has_toolbarbox = False
if self._output_png:
# Outputing to file, so no need for a canvas
@@ -347,6 +355,18 @@ return %s(self)" % (p, P, P)
self.vbox.set_size_request(rect[2], rect[3])
self.menu_height = self.menu_bar.size_request()[1]
+ def restore_cursor(self):
+ ''' No longer copying or sharing, so restore standard cursor. '''
+ self.tw.copying_blocks = False
+ self.tw.sharing_blocks = False
+ self.tw.saving_blocks = False
+ self.tw.deleting_blocks = False
+ if hasattr(self, 'get_window'):
+ if hasattr(self.get_window(), 'get_cursor'):
+ self.get_window().set_cursor(self._old_cursor)
+ else:
+ self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+
def _setup_gtk(self):
''' Set up a scrolled window in which to run Turtle Blocks. '''
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
@@ -403,7 +423,9 @@ return %s(self)" % (p, P, P)
MenuBuilder.make_menu_item(menu, _('Show sample projects'),
self._create_store)
MenuBuilder.make_menu_item(menu, _('Open'), self._do_open_cb)
- MenuBuilder.make_menu_item(menu, _('Load project'), self._do_load_cb)
+ MenuBuilder.make_menu_item(menu, _('Add project'), self._do_load_cb)
+ MenuBuilder.make_menu_item(menu, _('Load plugin'),
+ self._do_load_plugin_cb)
MenuBuilder.make_menu_item(menu, _('Save'), self._do_save_cb)
MenuBuilder.make_menu_item(menu, _('Save as'), self._do_save_as_cb)
MenuBuilder.make_menu_item(menu, _('Save as image'),
@@ -518,6 +540,27 @@ Would you like to save before quitting?'))
dlg.destroy()
return resp
+ def _reload_plugin_alert(self, tmp_dir, tmp_path, plugin_path, plugin_name,
+ file_info):
+ print "Already installed"
+ title = _('Plugin %s already installed') % plugin_name
+ msg = _('Do you want to reinstall %s?') % plugin_name
+ dlg = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO,
+ buttons=gtk.BUTTONS_YES_NO,
+ message_format=title)
+ dlg.format_secondary_text(msg)
+ dlg.set_title(title)
+ dlg.set_property('skip-taskbar-hint', False)
+
+ resp = dlg.run()
+ dlg.destroy()
+
+ if resp is gtk.RESPONSE_OK:
+ complete_plugin_install(tmp_dir, tmp_path, plugin_path,
+ plugin_name, file_info)
+ elif resp is gtk.RESPONSE_CANCEL:
+ cancel_plugin_install(tmp_dir)
+
def _do_new_cb(self, widget):
''' Callback for new project. '''
self.tw.new_project()
@@ -531,6 +574,41 @@ Would you like to save before quitting?'))
''' Callback for load project (add to current project). '''
self.tw.load_file_from_chooser(False)
+ def _do_load_plugin_cb(self, widget):
+ self.tw.load_save_folder = self._get_execution_dir()
+ file_path, loaddir = get_load_name('.tar.gz', self.tw.load_save_folder)
+ if file_path is None:
+ return
+ try:
+ # Copy to tmp file since some systems had trouble
+ # with gunzip directly from datastore
+ datapath = get_path(None, 'instance')
+ if not os.path.exists(datapath):
+ os.makedirs(datapath)
+ tmpfile = os.path.join(datapath, 'tmpfile.tar.gz')
+ subprocess.call(['cp', file_path, tmpfile])
+ status = subprocess.call(['gunzip', tmpfile])
+ if status == 0:
+ tar_fd = tarfile.open(tmpfile[:-3], 'r')
+ else:
+ tar_fd = tarfile.open(tmpfile, 'r')
+ except:
+ tar_fd = tarfile.open(file_path, 'r')
+
+ tmp_dir = tempfile.mkdtemp()
+
+ try:
+ tar_fd.extractall(tmp_dir)
+ load_a_plugin(self, tmp_dir)
+ self.restore_cursor()
+ except:
+ self.restore_cursor()
+ finally:
+ tar_fd.close()
+ # Remove tmpfile.tar
+ subprocess.call(['rm',
+ os.path.join(datapath, 'tmpfile.tar')])
+
def _do_save_cb(self, widget):
''' Callback for save project. '''
self.tw.save_file(self._ta_file)