From 1cbc7b2a88db47101cdd22aa32519946c877997c Mon Sep 17 00:00:00 2001 From: Gonzalo Odiard Date: Fri, 08 Apr 2011 20:32:45 +0000 Subject: Implement a custom widget to show in the toolbar the brush shape, color and size --- diff --git a/Area.py b/Area.py index 74b8fe4..2e979a8 100644 --- a/Area.py +++ b/Area.py @@ -69,7 +69,7 @@ import os import tempfile import math import pango -from fill import * +from fill import fill from Desenho import Desenho from urlparse import urlparse @@ -139,7 +139,7 @@ class Area(gtk.DrawingArea): ## All values migth be None, execept in 'name' key. self.tool = { 'name': 'pencil', - 'line size': 2, + 'line size': 4, 'fill color': None, 'stroke color': None, 'line shape': 'circle', @@ -853,7 +853,6 @@ class Area(gtk.DrawingArea): logging.debug('Area._set_fill_color(self, color)') self.gc.set_foreground(color) - self.janela.textview.modify_text(gtk.STATE_NORMAL, color) def set_stroke_color(self, color): """Set stroke color. @@ -868,6 +867,7 @@ class Area(gtk.DrawingArea): self.gc_line.set_line_attributes(1, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND) self.gc_brush.set_foreground(color) + self.janela.textview.modify_text(gtk.STATE_NORMAL, color) def grayscale(self, widget): """Apply grayscale effect. diff --git a/Desenho.py b/Desenho.py index 9b4c771..4f8f196 100644 --- a/Desenho.py +++ b/Desenho.py @@ -587,7 +587,7 @@ class Desenho: layout = widget.janela.textview.create_pango_layout(text) - widget.pixmap.draw_layout(widget.gc, + widget.pixmap.draw_layout(widget.gc_brush, widget.oldx, widget.oldy, layout) widget.pixmap_temp.draw_layout(widget.gc, widget.oldx, widget.oldy, layout) diff --git a/toolbox.py b/toolbox.py index 384cb15..dafb4e7 100644 --- a/toolbox.py +++ b/toolbox.py @@ -73,6 +73,7 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.toggletoolbutton import ToggleToolButton from sugar.graphics.objectchooser import ObjectChooser +from widgets import ButtonStrokeColor from sugar.graphics.colorbutton import ColorToolButton from sugar.graphics import style @@ -131,6 +132,14 @@ class DrawToolbarBox(ToolbarBox): stop = StopButton(self._activity) self.toolbar.insert(stop, -1) + # TODO: workaround + # the BrushButton does not starts + brush_button = tools_builder._stroke_color.color_button + brush_button.set_brush_shape(self._activity.area.tool['line shape']) + brush_button.set_brush_size(self._activity.area.tool['line size']) + if self._activity.area.tool['stroke color'] is not None: + brush_button.set_color(self._activity.area.tool['stroke color']) + ##Make the Edit Toolbar class DrawEditToolbar(EditToolbar): @@ -212,117 +221,6 @@ class DrawToolButton(RadioToolButton): self.set_tooltip(tooltip) -class ButtonStrokeColor(ColorToolButton): - """Class to manage the Stroke Color of a Button""" - - def __init__(self, activity): - ColorToolButton.__init__(self) - self._activity = activity - self.properties = self._activity.area.tool - self.connect('notify::color', self._color_button_cb) - - def _color_button_cb(self, widget, pspec): - color = self.get_color() - self.set_stroke_color(color) - - def alloc_color(self, color): - colormap = self._activity.area.get_colormap() - return colormap.alloc_color(color.red, color.green, color.blue) - - def set_stroke_color(self, color): - new_color = self.alloc_color(color) - self._activity.area.set_stroke_color(new_color) - self.properties['stroke color'] = new_color - - def create_palette(self): - self._palette = self.get_child().create_palette() - - color_palette_hbox = self._palette._picker_hbox - - content_box = gtk.VBox() - # We can set size when using either - - size_spinbutton = gtk.SpinButton() - - # This is where we set restrictions for size: - # Initial value, minimum value, maximum value, step - adj = gtk.Adjustment(self.properties['line size'], 1.0, 100.0, 1.0) - size_spinbutton.set_adjustment(adj) - - size_spinbutton.set_numeric(True) - - label = gtk.Label(_('Size: ')) - - hbox = gtk.HBox() - content_box.pack_start(hbox) - - hbox.pack_start(label) - hbox.pack_start(size_spinbutton) - - size_spinbutton.connect('value-changed', self._on_value_changed) - - # User is able to choose Shapes for 'Brush' and 'Eraser' - - # Changing to gtk.RadioButton - item1 = gtk.RadioButton(None, _('Circle')) - item1.set_active(True) - - image1 = gtk.Image() - pixbuf1 = gtk.gdk.pixbuf_new_from_file_at_size( - './icons/tool-shape-ellipse.svg', - style.SMALL_ICON_SIZE, - style.SMALL_ICON_SIZE) - image1.set_from_pixbuf(pixbuf1) - item1.set_image(image1) - - item2 = gtk.RadioButton(item1, _('Square')) - - image2 = gtk.Image() - pixbuf2 = gtk.gdk.pixbuf_new_from_file_at_size( - './icons/tool-shape-rectangle.svg', - style.SMALL_ICON_SIZE, - style.SMALL_ICON_SIZE) - image2.set_from_pixbuf(pixbuf2) - item2.set_image(image2) - - item1.connect('toggled', self._on_toggled, self.properties, 'circle') - item2.connect('toggled', self._on_toggled, self.properties, 'square') - - label = gtk.Label(_('Shape')) - - content_box.pack_start(label) - content_box.pack_start(item1) - content_box.pack_start(item2) - - # Creating a CheckButton - keep_aspect_checkbutton = gtk.CheckButton(_('Keep aspect')) - ratio = self._activity.area.keep_aspect_ratio - keep_aspect_checkbutton.set_active(ratio) - keep_aspect_checkbutton.connect('toggled', - self._keep_aspect_checkbutton_toggled) - content_box.pack_start(keep_aspect_checkbutton) - - color_palette_hbox.pack_start(gtk.VSeparator(), - padding=style.DEFAULT_SPACING) - color_palette_hbox.pack_start(content_box) - color_palette_hbox.show_all() - - return self._palette - - def _keep_aspect_checkbutton_toggled(self, checkbutton): - logging.debug('Keep aspect is Active: %s', checkbutton.get_active()) - self._activity.area.keep_aspect_ratio = checkbutton.get_active() - - def _on_value_changed(self, spinbutton): - size = spinbutton.get_value_as_int() - self.properties['line size'] = size - self._activity.area.set_tool(self.properties) - - def _on_toggled(self, radiobutton, tool, shape): - if radiobutton.get_active(): - self.properties['line shape'] = shape - - class ToolsToolbarBuilder(): #Tool default definitions @@ -339,8 +237,9 @@ class ToolsToolbarBuilder(): self.properties = self._activity.area.tool self._stroke_color = ButtonStrokeColor(activity) - self._stroke_color.set_icon_name('icon-stroke') - self._stroke_color.set_title(_('Stroke Color')) + #self._stroke_color.set_icon_name('icon-stroke') + self._stroke_color.set_title(_('Brush properties')) + self._stroke_color.connect('notify::color', self._color_button_cb) item = gtk.ToolItem() item.add(self._stroke_color) toolbar.insert(item, -1) @@ -349,13 +248,15 @@ class ToolsToolbarBuilder(): separator.set_draw(True) toolbar.insert(separator, -1) + """ self._tool_pencil = DrawToolButton('tool-pencil', activity.tool_group, _('Pencil')) toolbar.insert(self._tool_pencil, -1) - activity.tool_group = self._tool_pencil + """ self._tool_brush = DrawToolButton('tool-brush', activity.tool_group, _('Brush')) + activity.tool_group = self._tool_brush toolbar.insert(self._tool_brush, -1) self._tool_eraser = DrawToolButton('tool-eraser', @@ -377,8 +278,8 @@ class ToolsToolbarBuilder(): # New connect method # Using dictionnaries to control tool's properties - self._tool_pencil.connect('clicked', self.set_tool, - self._TOOL_PENCIL_NAME) + #self._tool_pencil.connect('clicked', self.set_tool, + # self._TOOL_PENCIL_NAME) self._tool_brush.connect('clicked', self.set_tool, self._TOOL_BRUSH_NAME) self._tool_eraser.connect('clicked', self.set_tool, @@ -400,6 +301,13 @@ class ToolsToolbarBuilder(): self.properties['name'] = tool_name self._activity.area.set_tool(self.properties) + def _color_button_cb(self, widget, pspec): + logging.error('ToolsToolbarBuilder._color_button_cb') + + new_color = widget.alloc_color(widget.get_color()) + self._activity.area.set_stroke_color(new_color) + self.properties['stroke color'] = new_color + class ButtonFillColor(ColorToolButton): """Class to manage the Fill Color of a Button""" @@ -508,7 +416,7 @@ class ShapesToolbar(gtk.Toolbar): self._fill_color = ButtonFillColor(activity) self._fill_color.set_icon_name('icon-fill') - self._fill_color.set_title(_('Fill Color')) + self._fill_color.set_title(_('Shapes properties')) item = gtk.ToolItem() item.add(self._fill_color) self.insert(item, -1) @@ -599,22 +507,17 @@ class ShapesToolbar(gtk.Toolbar): ##Make the Text Toolbar class TextToolbar(gtk.Toolbar): - _ACTION_TEXT = {'name': 'text', - 'line size': None, - 'fill color': None, - 'stroke color': None, - 'line shape': None, - 'fill': True, - 'vertices': None} + _ACTION_TEXT_NAME = 'text' def __init__(self, activity): gtk.Toolbar.__init__(self) self._activity = activity + self.properties = self._activity.area.tool self._text = DrawToolButton('text', activity.tool_group, _('Type')) self.insert(self._text, -1) - self._text.connect('clicked', self.set_tool, self._ACTION_TEXT) + self._text.connect('clicked', self.set_tool, self._ACTION_TEXT_NAME) separator = gtk.SeparatorToolItem() separator.set_draw(True) @@ -630,10 +533,12 @@ class TextToolbar(gtk.Toolbar): self._italic.show() self._italic.connect('clicked', self.__italic_bt_cb) + """ self._text_color = ButtonFillColor(activity) item = gtk.ToolItem() item.add(self._text_color) self.insert(item, -1) + """ separator = gtk.SeparatorToolItem() separator.set_draw(True) @@ -713,11 +618,9 @@ class TextToolbar(gtk.Toolbar): return None return model[active][0] - def set_tool(self, widget, tool): - #FIXME: this callback must change as others buttons get enabled - new_color = self._text_color.get_color() - tool['fill color'] = self._text_color.alloc_color(new_color) - self._activity.area.set_tool(tool) + def set_tool(self, widget, tool_name): + self.properties['name'] = tool_name + self._activity.area.set_tool(self.properties) ##Make the Images Toolbar diff --git a/widgets.py b/widgets.py new file mode 100644 index 0000000..832649b --- /dev/null +++ b/widgets.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- + +from gettext import gettext as _ + +import gtk +import gobject + +from sugar.graphics import style +from sugar.graphics.palette import ToolInvoker +from sugar.graphics.colorbutton import _ColorButton + + +class BrushButton(_ColorButton): + """This is a ColorButton but show the color, the size and the shape + of the brush. + Instead of a color selector dialog it will pop up a Sugar palette. + As a preview an DrawingArea is used, to use the same methods to + draw than in the main window. + """ + + __gtype_name__ = 'BrushButton' + + def __init__(self, **kwargs): + self._title = _('Choose brush properties') + self._color = gtk.gdk.Color(0, 0, 0) + self._has_palette = True + self._has_invoker = True + self._palette = None + self._accept_drag = True + self._brush_size = 2 + self._brush_shape = 'circle' + self._preview = gtk.DrawingArea() + self._preview.set_size_request(style.STANDARD_ICON_SIZE, + style.STANDARD_ICON_SIZE) + + gobject.GObject.__init__(self, **kwargs) + self._preview.set_events(gtk.gdk.BUTTON_PRESS_MASK) + + self._preview.connect('button_press_event', self.__mouse_down_cb) + self._preview.connect("expose_event", self.expose) + self.set_image(self._preview) + + self._gc = None + + if self._has_palette and self._has_invoker: + self._invoker = WidgetInvoker(self) + # FIXME: This is a hack. + self._invoker.has_rectangle_gap = lambda: False + self._invoker.palette = self._palette + + def _setup(self): + if self.get_window() is not None: + self._preview.fill_color = '' + self._preview.show() + self.pixmap = gtk.gdk.Pixmap(self.get_window(), + style.STANDARD_ICON_SIZE, + style.STANDARD_ICON_SIZE, 24) + self._gc = self.get_window().new_gc() + self.show_all() + + def get_brush_size(self): + return self._brush_size + + def set_brush_size(self, brush_size): + self._brush_size = brush_size + self._preview.queue_draw() + + brush_size = gobject.property(type=int, getter=get_brush_size, + setter=set_brush_size) + + def get_brush_shape(self): + return self._brush_shape + + def set_brush_shape(self, brush_shape): + self._brush_shape = brush_shape + self._preview.queue_draw() + + brush_shape = gobject.property(type=str, getter=get_brush_shape, + setter=set_brush_shape) + + def set_color(self, color): + # receive gtk.gdk.Color + self._color = color + self._preview.queue_draw() + + def expose(self, widget, event): + if self._gc is None: + self._setup() + + if self.get_window() is not None: + center = style.STANDARD_ICON_SIZE / 2 + self.pixmap.draw_rectangle(self._preview.get_style().white_gc, + True, 0, 0, style.STANDARD_ICON_SIZE, style.STANDARD_ICON_SIZE) + self._gc.set_foreground(self._color) + + if(self._brush_shape == 'circle'): + self.pixmap.draw_arc(self._gc, True, + center - self._brush_size / 2, + center - self._brush_size / 2, + self._brush_size, self._brush_size, 0, 360 * 64) + if(self._brush_shape == 'square'): + self.pixmap.draw_rectangle(self._gc, True, + center - self._brush_size / 2, + center - self._brush_size / 2, + self._brush_size, self._brush_size) + + area = event.area + widget.window.draw_drawable(self._gc, self.pixmap, + area[0], area[1], area[0], area[1], area[2], area[3]) + return False + + def do_style_set(self, previous_style): + pass + + def set_icon_name(self, icon_name): + pass + + def get_icon_name(self): + pass + + def set_icon_size(self, icon_size): + pass + + def get_icon_size(self): + pass + + def __mouse_down_cb(self, event): + if self._palette: + if not self._palette.is_up(): + self._palette.popup(immediate=True, + state=self._palette.SECONDARY) + else: + self._palette.popdown(immediate=True) + return True + + +class ButtonStrokeColor(gtk.ToolItem): + """Class to manage the Stroke Color of a Button""" + + __gtype_name__ = 'BrushColorToolButton' + __gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + tuple())} + + def __init__(self, activity, **kwargs): + self._activity = activity + self.properties = self._activity.area.tool + self._accelerator = None + self._tooltip = None + self._palette_invoker = ToolInvoker() + self._palette = None + + gobject.GObject.__init__(self, **kwargs) + + # The gtk.ToolButton has already added a normal button. + # Replace it with a ColorButton + self.color_button = BrushButton(has_invoker=False) + self.add(self.color_button) + self.color_button.set_brush_size(2) + self.color_button.set_brush_shape('circle') + + # The following is so that the behaviour on the toolbar is correct. + self.color_button.set_relief(gtk.RELIEF_NONE) + + self._palette_invoker.attach_tool(self) + + # This widget just proxies the following properties to the colorbutton + self.color_button.connect('notify::color', self.__notify_change) + self.color_button.connect('notify::icon-name', self.__notify_change) + self.color_button.connect('notify::icon-size', self.__notify_change) + self.color_button.connect('notify::title', self.__notify_change) + self.color_button.connect('can-activate-accel', + self.__button_can_activate_accel_cb) + + def __button_can_activate_accel_cb(self, button, signal_id): + # Accept activation via accelerators regardless of this widget's state + return True + + def __notify_change(self, widget, pspec): + new_color = self.alloc_color(self.get_color()) + self.color_button.set_color(new_color) + self.notify(pspec.name) + + def _color_button_cb(self, widget, pspec): + color = self.get_color() + self.set_stroke_color(color) + + def alloc_color(self, color): + colormap = self._activity.area.get_colormap() + return colormap.alloc_color(color.red, color.green, color.blue) + + def create_palette(self): + self._palette = self.get_child().create_palette() + + color_palette_hbox = self._palette._picker_hbox + content_box = gtk.VBox() + size_spinbutton = gtk.SpinButton() + # This is where we set restrictions for size: + # Initial value, minimum value, maximum value, step + adj = gtk.Adjustment(self.properties['line size'], 1.0, 100.0, 1.0) + size_spinbutton.set_adjustment(adj) + size_spinbutton.set_numeric(True) + + label = gtk.Label(_('Size: ')) + hbox = gtk.HBox() + content_box.pack_start(hbox) + + hbox.pack_start(label) + hbox.pack_start(size_spinbutton) + size_spinbutton.connect('value-changed', self._on_value_changed) + + # User is able to choose Shapes for 'Brush' and 'Eraser' + item1 = gtk.RadioButton(None, _('Circle')) + item1.set_active(True) + image1 = gtk.Image() + pixbuf1 = gtk.gdk.pixbuf_new_from_file_at_size( + './icons/tool-shape-ellipse.svg', + style.SMALL_ICON_SIZE, + style.SMALL_ICON_SIZE) + image1.set_from_pixbuf(pixbuf1) + item1.set_image(image1) + + item2 = gtk.RadioButton(item1, _('Square')) + image2 = gtk.Image() + pixbuf2 = gtk.gdk.pixbuf_new_from_file_at_size( + './icons/tool-shape-rectangle.svg', + style.SMALL_ICON_SIZE, + style.SMALL_ICON_SIZE) + image2.set_from_pixbuf(pixbuf2) + item2.set_image(image2) + + item1.connect('toggled', self._on_toggled, self.properties, 'circle') + item2.connect('toggled', self._on_toggled, self.properties, 'square') + + label = gtk.Label(_('Shape')) + + content_box.pack_start(label) + content_box.pack_start(item1) + content_box.pack_start(item2) + + keep_aspect_checkbutton = gtk.CheckButton(_('Keep aspect')) + ratio = self._activity.area.keep_aspect_ratio + keep_aspect_checkbutton.set_active(ratio) + keep_aspect_checkbutton.connect('toggled', + self._keep_aspect_checkbutton_toggled) + content_box.pack_start(keep_aspect_checkbutton) + + color_palette_hbox.pack_start(gtk.VSeparator(), + padding=style.DEFAULT_SPACING) + color_palette_hbox.pack_start(content_box) + color_palette_hbox.show_all() + return self._palette + + def _keep_aspect_checkbutton_toggled(self, checkbutton): + self._activity.area.keep_aspect_ratio = checkbutton.get_active() + + def _on_value_changed(self, spinbutton): + size = spinbutton.get_value_as_int() + self.properties['line size'] = size + self._activity.area.set_tool(self.properties) + self.color_button.set_brush_size(self.properties['line size']) + + def _on_toggled(self, radiobutton, tool, shape): + if radiobutton.get_active(): + self.properties['line shape'] = shape + self.color_button.set_brush_shape(shape) + self.color_button.set_brush_size(self.properties['line size']) + + def get_palette_invoker(self): + return self._palette_invoker + + def set_palette_invoker(self, palette_invoker): + self._palette_invoker.detach() + self._palette_invoker = palette_invoker + + palette_invoker = gobject.property( + type=object, setter=set_palette_invoker, getter=get_palette_invoker) + + def set_color(self, color): + self.color_button.set_color(color) + + def get_color(self): + return self.get_child().props.color + + color = gobject.property(type=object, getter=get_color, setter=set_color) + + def set_title(self, title): + self.get_child().props.title = title + + def get_title(self): + return self.get_child().props.title + + title = gobject.property(type=str, getter=get_title, setter=set_title) + + def do_expose_event(self, event): + child = self.get_child() + allocation = self.get_allocation() + if self._palette and self._palette.is_up(): + invoker = self._palette.props.invoker + invoker.draw_rectangle(event, self._palette) + elif child.state == gtk.STATE_PRELIGHT: + child.style.paint_box(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_NONE, event.area, + child, 'toolbutton-prelight', + allocation.x, allocation.y, + allocation.width, allocation.height) + + gtk.ToolButton.do_expose_event(self, event) -- cgit v0.9.1