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;
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()
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