Beispiel #1
0
class CommandStack:
    def __init__(self, goghdoc):
        self.packed_command_list = []
        self.current_index = -1;
        self.goghdoc = goghdoc
        self.observer = Observer()
        self.changes_since_save = 0

    def add(self, command):
        if(command.is_trivial()):
            return
        del self.packed_command_list[self.current_index+1:]
        self.packed_command_list.append(self.pack_command(command))
        self.current_index+=1
        self.changes_since_save+=1
        command.execute()
        if len(self.packed_command_list)>max_stack_len:
            self.current_index -= len(self.packed_command_list)-max_stack_len
            del self.packed_command_list[:-max_stack_len]
        self.observer.notify_all()

    def undo(self):
        if self.current_index<0:
            return
        command = self.unpack_command(self.packed_command_list[self.current_index])
        command.undo()
        self.current_index-=1
        self.changes_since_save-=1
        self.observer.notify_all()

    def redo(self):
        if self.current_index+1>=len(self.packed_command_list):
            return
        command = self.unpack_command(self.packed_command_list[self.current_index+1])
        command.redo()
        self.current_index+=1
        self.changes_since_save+=1
        self.observer.notify_all()

    def pack_command(self, command):
        return zlib.compress(pickle.dumps(command, 2))

    def unpack_command(self, packed_command):
        command = pickle.loads(zlib.decompress(packed_command))
        command.goghdoc = self.goghdoc
        return command

    def clear(self):
        self.packed_command_list = []
        self.current_index = -1;
Beispiel #2
0
class BrushManager:
    def __init__(self):
        self.brush_types = {'pen': BrushType.Pen, 'eraser': BrushType.Eraser, 'smudge': BrushType.Smudge }#, 'bucket': BrushType.Bucket}
        self.brush_type_names = inverse_dictionary(self.brush_types)
        self.init_brush_list()
        self.current_pen_data = self.default_pen_data
        self.current_eraser_data = self.default_eraser_data
        self.active_brush_data = self.current_pen_data
        self.init_brush_menu()
        self.select_menu_item_for_active_brush_data()
        self.eraser_mode = False
        self.brush_selection_observer = Observer()

    def construct_brush_from_node(self, brush_node):
        brush = BrushData(brush_node.getAttribute("name"))
        for child_node in brush_node.childNodes:
            if child_node.localName=='width':
                brush.min_width = int(child_node.getAttribute("min"))
                brush.max_width = int(child_node.getAttribute("max"))
            if child_node.localName=='opacity':
                brush.min_opacity = float(child_node.getAttribute("min"))
                brush.max_opacity = float(child_node.getAttribute("max"))
            if child_node.localName=='step':
                brush.step = int(child_node.getAttribute("value"))
            if child_node.localName=='smudge-amount':
                brush.smudge_amount = float(child_node.getAttribute("value"))
            if child_node.localName=='type':
                brush.brush_type = self.brush_types[child_node.childNodes[0].nodeValue.strip()]
            if child_node.localName=='default-eraser':
                self.default_eraser_data = brush
            if child_node.localName=='default-pen':
                self.default_pen_data = brush
        brush.populate_originals()
        return brush

    def construct_node_from_brush(self, brush, xmldoc):
        brush_node = xmldoc.createElement('brush')
        brush_node.setAttribute('name', brush.name)
        brush_node.appendChild(xmldoc.createElement('width'))
        brush_node.lastChild.setAttribute('min', str(int(brush.min_width)))
        brush_node.lastChild.setAttribute('max', str(int(brush.max_width)))
        if (brush.min_opacity!=0 or brush.max_opacity!=1) and brush.brush_type != BrushType.Smudge:
            brush_node.appendChild(xmldoc.createElement('opacity'))
            brush_node.lastChild.setAttribute('min', str(brush.min_opacity))
            brush_node.lastChild.setAttribute('max', str(brush.max_opacity))
        if brush.step != 1:
            brush_node.appendChild(xmldoc.createElement('step'))
            brush_node.lastChild.setAttribute('value', str(int(brush.step)))
        if brush.brush_type == BrushType.Smudge:
            brush_node.appendChild(xmldoc.createElement('smudge-amount'))
            brush_node.lastChild.setAttribute('value', str(brush.smudge_amount))
        brush_node.appendChild(xmldoc.createElement('type'))
        brush_node.lastChild.appendChild(xmldoc.createTextNode(self.brush_type_names[brush.brush_type]))
        if self.default_pen_data == brush :
            brush_node.appendChild(xmldoc.createElement('default-pen'))
        if self.default_eraser_data == brush :
            brush_node.appendChild(xmldoc.createElement('default-eraser'))
        return brush_node

    def init_brush_list(self):
        original_doc, custom_doc = load_original_brush_list_xmldoc(), load_custom_brush_list_xmldoc()
        self.brush_groups = []
        self.load_brushes_from_document(original_doc, True)
        if custom_doc:
            self.load_brushes_from_document(custom_doc, False)

    def load_brushes_from_document(self, doc, is_original):
        for group_node in doc.getElementsByTagName("brushgroup"):
            group_name = group_node.getAttribute("name")
            group = find_item(self.brush_groups, lambda g : g.name == group_name)
            if not group:
                group = BrushGroup(group_node.getAttribute("name"))
                self.brush_groups.append(group)
            for brush_node in group_node.getElementsByTagName("brush"):
                brush = self.construct_brush_from_node(brush_node)
                brush.is_original = is_original
                group.brushes.append(brush)


    def save_brush_list(self):
        doc = xml.dom.minidom.Document()
        root_node = doc.createElement('brushes')
        doc.appendChild(root_node)
        for group in self.brush_groups:
            group_node = doc.createElement('brushgroup')
            group_node.setAttribute('name', group.name)
            for brush in group.brushes:
                if not brush.is_original:
                    brush_node = self.construct_node_from_brush(brush, doc)
                    group_node.appendChild(brush_node)
            root_node.appendChild(group_node)
        save_brush_list_xmldoc(doc)

    def init_brush_menu(self):
        self.brush_menu = gtk.Menu()
        self.brush_menu.connect("selection-done", self.selection_done)
        self.items_for_brushes = {}
        self.menu_item_group = None
        for brush_group in self.brush_groups:
            for brush_data in brush_group.brushes:
                item = gtk.RadioMenuItem(self.menu_item_group, brush_data.name)
                self.items_for_brushes[brush_data] = item
                if not self.menu_item_group:
                    self.menu_item_group = item
                self.brush_menu.append(item)
        self.brush_menu.append(gtk.SeparatorMenuItem())
        self.brush_menu.show_all()

    def selection_done(self, widget, data=None):
        for brush_data, item in self.items_for_brushes.iteritems():
            if item.get_active():
                self.active_brush_data = brush_data
                self.brush_selection_observer.notify_all()
                self.assign_current_brushes()
                self.select_menu_item_for_active_brush_data()
                return

    def select_eraser(self):
        self.eraser_mode = True
        if self.active_brush_data!=self.current_eraser_data :
            self.current_pen_data = self.active_brush_data
            self.active_brush_data = self.current_eraser_data
            self.brush_selection_observer.notify_all()
            self.select_menu_item_for_active_brush_data()


    def unselect_eraser(self):
        self.eraser_mode = False
        if self.active_brush_data!=self.current_pen_data :
            self.current_eraser_data = self.active_brush_data
            self.active_brush_data = self.current_pen_data
            self.brush_selection_observer.notify_all()
            self.select_menu_item_for_active_brush_data()

    def select_menu_item_for_active_brush_data(self):
        item = self.items_for_brushes[self.active_brush_data]
        self.brush_menu.select_item(item)
        item.set_active(True)

    def assign_current_brushes(self):
        if self.eraser_mode :
            self.current_eraser_data = self.active_brush_data
        else :
            self.current_pen_data = self.active_brush_data


    def on_brush_name_changed(self, brush_data) :
        self.items_for_brushes[brush_data].child.set_text(brush_data.name)

    def group_for_brush(self, brush_data):
        return find_item(self.brush_groups, lambda g : brush_data in g.brushes)

    def add_brush(self, brush_group, brush_data):
        brush_group.brushes.append(brush_data)
        new_item = gtk.RadioMenuItem(self.menu_item_group, brush_data.name)
        self.items_for_brushes[brush_data] = new_item
        self.brush_menu.append(new_item)
        self.brush_menu.show_all()

    def remove_brush(self, brush_data):
        brush_group = self.group_for_brush(brush_data)
        brush_group.brushes.remove(brush_data)
        self.brush_menu.remove(self.items_for_brushes[brush_data])
        del self.items_for_brushes[brush_data]
        self.brush_menu.show_all()

    def select_brush(self, brush_data):
        if self.active_brush_data!=brush_data :
            self.active_brush_data = brush_data
            self.brush_selection_observer.notify_all()
            self.assign_current_brushes()
            self.select_menu_item_for_active_brush_data()
Beispiel #3
0
class GoghView:
    def __init__(self, goghdoc, drawable):
        self.x_visible, self.y_visible, self.w_visible, self.h_visible = 0, 0, 0, 0
        self.viewpixbuf = None
        self.goghdoc = goghdoc
        self.drawable = drawable
        self.zoom_factor = 1.0
        self.size_observer = Observer()
        self.image_observer = Observer()
        self.goghdoc.size_observer.add_callback(self.on_resize)
        self.goghdoc.pixbuf_observer.add_callback(self.refresh_area)
        self.gc = self.drawable.new_gc()
        self.top_level_gc = self.drawable.new_gc(function=gtk.gdk.INVERT)
        self.show_cursor = False
        self.xcur, self.ycur, self.brush_min_width, self.brush_max_width = 0, 0, 0, 0
        self.xcur_old, self.ycur_old = None, None


    def get_size(self):
        return int(ceil(self.goghdoc.width*self.zoom_factor)), int(ceil(self.goghdoc.height*self.zoom_factor))


    def zoom_in(self):
        self.set_zoom(self.zoom_factor*1.5)

    def zoom_out(self):
        self.set_zoom(self.zoom_factor/1.5)

    def zoom_normal(self):
        self.set_zoom(1.0)

    def set_zoom(self, zoom_factor):
        self.zoom_factor = zoom_factor
        if abs(self.zoom_factor-round(self.zoom_factor))<0.01:
            self.zoom_factor = round(self.zoom_factor)
        self.size_observer.notify_all()


    def scale_with_clipping(self, x, y, w, h):
        if x<0 :
            w,x = w-x, 0
        if x+w>self.viewpixbuf.get_width() :
            w = self.viewpixbuf.get_width()-x
        if y<0 :
            h, y = h-y, 0
        if y+h>self.viewpixbuf.get_height() :
            h = self.viewpixbuf.get_height()-y
        if w<=0 or h<=0 :
            return
        self.goghdoc.composite.scale(self.viewpixbuf, x, y, w, h, -self.x_visible, -self.y_visible, self.zoom_factor, self.zoom_factor, gtk.gdk.INTERP_TILES)

    def update_view_pixbuf(self, x, y, w, h):
        xv, yv, wv, hv = self.to_view_rect(x, y, w, h)
        self.scale_with_clipping(xv-self.x_visible, yv-self.y_visible, wv, hv)

    def reset_view_pixbuf(self):
        if self.viewpixbuf :
            if (self.viewpixbuf.get_width(), self.viewpixbuf.get_height()) != (self.w_visible +1, self.h_visible+1):
                del self.viewpixbuf
                self.viewpixbuf = create_pixbuf(self.w_visible +1, self.h_visible+1)
        else :
            self.viewpixbuf = create_pixbuf(self.w_visible +1, self.h_visible+1)
        self.scale_with_clipping(0, 0, self.w_visible +1, self.h_visible+1)

    def refresh_area(self, area_rect=None):
        if not area_rect:
            self.image_observer.notify_all(gtk.gdk.Rectangle(0, 0, self.w_visible, self.h_visible), False)
            return
        xv, yv, wv, hv = self.to_view_rect(area_rect.x, area_rect.y, area_rect.width, area_rect.height)
        self.scale_with_clipping(xv-self.x_visible, yv-self.y_visible, wv, hv)
        self.image_observer.notify_all(gtk.gdk.Rectangle(xv, yv, wv, hv), False)

    def on_resize(self):
        self.reset_view_pixbuf()
        self.size_observer.notify_all()


    def to_model(self, x, y):
        return x/self.zoom_factor, y/self.zoom_factor

    def to_view(self, x, y):
        return x*self.zoom_factor, y*self.zoom_factor

    def redraw_image_fragment_for_model_coord(self, x, y, w, h):
        xv, yv, wv, hv = self.to_view_rect(x, y, w, h)
        if self.zoom_factor==1:
            self.draw_pixbuf_with_clipping(self.goghdoc.composite, xv, yv, xv, yv, wv, hv)
        else:
            xv, yv, wv, hv = xv-1, yv-1, wv+2, hv+2
            self.draw_pixbuf_with_clipping(self.viewpixbuf, xv-self.x_visible, yv-self.y_visible, xv, yv, wv, hv)
        self.draw_top_level_items(xv, yv, wv, hv)

    def draw_pixbuf_with_clipping(self, pixbuf, src_x, src_y, dest_x, dest_y, w, h):
        w_ofs, h_ofs = min(src_x, 0), min(src_y, 0)
        w_cutoff, h_cutoff = max(src_x+w-pixbuf.get_width(), 0), max(src_y+h-pixbuf.get_height(), 0)
        if w<=w_ofs+w_cutoff or h<=h_ofs+h_cutoff:
            return
        self.drawable.draw_pixbuf(self.gc, pixbuf, src_x-w_ofs, src_y-h_ofs, dest_x-w_ofs, dest_y-h_ofs, w-w_ofs-w_cutoff, h-h_ofs-h_cutoff)

    def redraw_image_for_cursor(self):
        max_size = max(self.brush_min_width, self.brush_max_width)+1
        d = max_size//2
        model_rect = None
        if self.xcur_old is not None and self.ycur_old is not None:
            model_rect = rect_union(model_rect, rect_from_float_list([self.xcur_old-d, self.ycur_old-d-1, max_size+2, max_size+2]))
        if self.show_cursor:
            model_rect = rect_union(model_rect, rect_from_float_list([self.xcur-d-1, self.ycur-d-1, max_size+2, max_size+2]))
        if model_rect:
            model_rect = model_rect.intersect(gtk.gdk.Rectangle(0, 0, self.goghdoc.width, self.goghdoc.height))
            self.redraw_image_fragment_for_model_coord(model_rect.x, model_rect.y, model_rect.width, model_rect.height)
        self.xcur_old, self.ycur_old = self.xcur, self.ycur


    def draw_top_level_items(self, xv, yv, wv, hv):
        self.top_level_gc.set_clip_rectangle(gtk.gdk.Rectangle(xv, yv, wv, hv))
        if self.show_cursor:
            xc, yc = [int(round(t)) for t in self.to_view(self.xcur, self.ycur)]
            w1, w2 = [int(ceil(w*self.zoom_factor)) | 1 for w in (self.brush_min_width, self.brush_max_width)]
            self.drawable.draw_arc(self.top_level_gc, False, xc-w1//2, yc-w1//2, w1, w1, 0, 360*64)
            if w1 != w2:
                self.drawable.draw_arc(self.top_level_gc, False, xc-w2//2, yc-w2//2, w2, w2, 0, 360*64)

    def to_view_rect(self, x, y, w, h):
        xv1, yv1 = [int(floor(t)) for t in self.to_view(x, y)]
        xv2, yv2 = [int(ceil(t)) for t in self.to_view(x+w, y+h)]
        return  xv1, yv1, xv2-xv1, yv2-yv1

    def reposition(self, x, y, w, h):
        if (self.x_visible, self.y_visible, self.w_visible, self.h_visible) == (x, y, w, h):
           return
        self.x_visible, self.y_visible, self.w_visible, self.h_visible = [int(k) for k in (x, y, w, h)]

    def redraw_image(self):
        self.reset_view_pixbuf()
        self.drawable.draw_pixbuf(self.drawable.new_gc(), self.viewpixbuf, 0, 0, self.x_visible, self.y_visible, -1, -1)
        self.draw_top_level_items(self.x_visible, self.y_visible, self.w_visible, self.h_visible)

    def set_cursor(self, x, y, brush_data):
        self.xcur, self.ycur, self.brush_min_width, self.brush_max_width = x, y, brush_data.min_width, brush_data.max_width
        self.show_cursor = True

    def set_no_cursor(self):
        self.show_cursor = False