From a4d0e58038a85d657d157738a4cedf9c1ba3fdcd Mon Sep 17 00:00:00 2001 From: Wade Brainerd Date: Mon, 07 Dec 2009 03:10:28 +0000 Subject: Update TODO list. Add Undo button. Fix some minor bugs, slight code reorganization. --- diff --git a/TODO b/TODO index da01c18..a732ad7 100644 --- a/TODO +++ b/TODO @@ -1,28 +1,15 @@ Colors! -- Save colors to letter keys using Ctrl+Letter. -- Finish zooming and panning. -- Speed up intro animation. -- Sort out idle vs paint priority issue. ++ Add MIME type, link to gallery site. ++ Help dropdown - text based is fine. ++ Publish tutorials on DailyMotion ++ New toolbars. ++ Reimplement reference image as a way to set the initial canvas. + Toolbar repaint issue. + Get tutorial paintings from Scott Nash. + Improve opacity and size scrollbar behavior: click to set. Maybe a custom widget? -+ Implement pickup color tool. -+ Add FPS counter. -+ Optimize framerate. -+ Improve videopaint - + Use Nirav's pygame extension. - + Calibration button. -+ Test Journal save & resume. -+ Fix cursor drawing. -+ Implement Copy & Paste. ++ Add icons above and below opacity and size scrollbars. ++ Make the color picker come up instantly. + Implement joypad key bindings. -+ Implement stylus support using evtest. -+ Speed up painting performance of large brushes. -+ Single stroke quick undo. -+ Button to adjust playback speed. -- Implement Wacom support. -- Fullscreen button. ++ Combo to adjust playback speed. + Confirm image clear. -- Ignore painting while playing back. Make them pause first! -+ Copy should create a new 1x gdk.Image. diff --git a/colors.py b/colors.py index eb39a69..043b8a6 100755 --- a/colors.py +++ b/colors.py @@ -472,10 +472,10 @@ class Colors(activity.Activity, ExportedGObject): self.zoomoutbtn = toolbutton.ToolButton('zoom-out') self.zoomoutbtn.set_tooltip(_("Zoom Out")) self.zoomoutbtn.connect('clicked', self.on_zoom_out) - self.zoomoutbtn.props.accelerator = 'X' + self.zoomoutbtn.props.accelerator = 'Down' self.zoominbtn = toolbutton.ToolButton('zoom-in') self.zoominbtn.set_tooltip(_("Zoom In")) - self.zoominbtn.props.accelerator = 'Z' + self.zoominbtn.props.accelerator = 'Up' self.zoominbtn.connect('clicked', self.on_zoom_in) self.centerbtn = toolbutton.ToolButton('zoom-original') self.centerbtn.set_tooltip(_("Center Image")) @@ -491,6 +491,11 @@ class Colors(activity.Activity, ExportedGObject): self.editsep.set_expand(True) self.editsep.set_draw(False) + self.undobtn = toolbutton.ToolButton('edit-undo') + self.undobtn.set_tooltip(_("Undo")) + self.undobtn.connect('clicked', self.on_undo) + self.undobtn.props.accelerator = 'Z' + self.copybtn = toolbutton.ToolButton('edit-copy') self.copybtn.set_tooltip(_("Copy")) self.copybtn.connect('clicked', self.on_copy) @@ -545,6 +550,7 @@ class Colors(activity.Activity, ExportedGObject): paintbox.insert(self.centerbtn, -1) paintbox.insert(self.fullscreenbtn, -1) paintbox.insert(self.editsep, -1) + paintbox.insert(self.undobtn, -1) paintbox.insert(self.copybtn, -1) #paintbox.insert(self.refsep, -1) #paintbox.insert(self.takerefbtn, -1) @@ -564,15 +570,15 @@ class Colors(activity.Activity, ExportedGObject): self.pausebtn.set_tooltip(_("Pause Playback")) self.pausebtn.connect('clicked', self.on_pause) - self.backonebtn = toolbutton.ToolButton('media-seek-backward') - self.backonebtn.set_tooltip(_("Back One Stroke")) - self.backonebtn.connect('clicked', self.on_back_one) - self.backonebtn.props.accelerator = 'Left' + #self.backonebtn = toolbutton.ToolButton('media-seek-backward') + #self.backonebtn.set_tooltip(_("Back One Stroke")) + #self.backonebtn.connect('clicked', self.on_back_one) + #self.backonebtn.props.accelerator = 'Left' - self.forwardonebtn = toolbutton.ToolButton('media-seek-forward') - self.forwardonebtn.set_tooltip(_("Forward One Stroke")) - self.forwardonebtn.connect('clicked', self.on_forward_one) - self.forwardonebtn.props.accelerator = 'Right' + #self.forwardonebtn = toolbutton.ToolButton('media-seek-forward') + #self.forwardonebtn.set_tooltip(_("Forward One Stroke")) + #self.forwardonebtn.connect('clicked', self.on_forward_one) + #self.forwardonebtn.props.accelerator = 'Right' # Position bar self.playbackpossep = gtk.SeparatorToolItem() @@ -598,12 +604,10 @@ class Colors(activity.Activity, ExportedGObject): playbox = gtk.Toolbar() playbox.insert(self.startbtn, -1) playbox.insert(self.pausebtn, -1) - playbox.insert(self.backonebtn, -1) - playbox.insert(self.forwardonebtn, -1) - playbox.insert(self.playbackpossep, -1) playbox.insert(self.beginbtn, -1) - playbox.insert(self.playbackpositem, -1) playbox.insert(self.endbtn, -1) + playbox.insert(self.playbackpossep, -1) + playbox.insert(self.playbackpositem, -1) # Sample files to learn from. Reads the list from an INDEX file in the data folder. samplebox = gtk.Toolbar() @@ -856,6 +860,7 @@ class Colors(activity.Activity, ExportedGObject): if self.connected: # Broadcast drawing commands that were generated by this user since the last call to this function. if self.draw_command_sent < self.easel.get_num_commands(): + # TODO: Always prepend the current brush here. buf = self.easel.send_drw_commands(self.draw_command_sent, self.easel.get_num_commands()-self.draw_command_sent) self.BroadcastDrawCommands(buf.get_bytes(), buf.ncommands) @@ -884,7 +889,8 @@ class Colors(activity.Activity, ExportedGObject): self.beginbtn.set_sensitive(False) self.endbtn.set_sensitive(False) self.playbackposbar.set_sensitive(False) - + self.undobtn.set_sensitive(False) + # Cannot activate sample drawings. for s in self.samplebtns: s.set_sensitive(False) @@ -1253,7 +1259,7 @@ class Colors(activity.Activity, ExportedGObject): Called to skip the playback position, for example when fast forwarding or rewinding or dragging the scrollbar.""" self.playbackposbar.ignore_change += 1 - #log.debug("play_to %d easel_pos=%d", int(self.playbackpos.get_value()/100.0*self.easel.playback_length()), self.easel.playback_pos()) + log.debug("play_to %d easel_pos=%d", to, self.easel.playback_pos()) total_left = 0 @@ -1298,13 +1304,16 @@ class Colors(activity.Activity, ExportedGObject): # This is currently disabled as it leads to the playback position passing the requested position, which # can cause infinite "Working..." loops when the mouse is held down. # Leaving it out can lead to inaccurate playback, when the play-to position is mid-stroke. - #self.easel.playback_finish_stroke() + self.easel.playback_finish_stroke() # Update the progress bar. if total_left > 0: f = 1.0-float(to - self.easel.playback_pos())/total_left self.progress.progress.set_fraction(f) + if self.easel.playback_pos() >= to: + break + # Comment this in to watch the painting and slow things down. #self.flush_entire_canvas() @@ -1380,6 +1389,7 @@ class Colors(activity.Activity, ExportedGObject): if self.mode == Colors.MODE_INTRO: # Load and play intro movie. It was created on a DS at 60hz, so we need to speed it up drastically to # make it watchable. + self.clear_undo() self.easel.clear() self.easel.load(str(activity.get_bundle_path() + "/data/intro.drw")) self.easel.set_playback_speed(8) @@ -1429,6 +1439,7 @@ class Colors(activity.Activity, ExportedGObject): def leave_mode (self): if self.mode == Colors.MODE_INTRO: self.easel.stop_playback() + self.clear_undo() self.easel.clear() self.easel.reset_brush() self.flush_entire_canvas() @@ -1437,6 +1448,7 @@ class Colors(activity.Activity, ExportedGObject): self.easel.stop_playback() if self.mode == Colors.MODE_CANVAS: + # Finish any in-progress stroke. self.end_draw() if self.mode == Colors.MODE_PICK: @@ -1475,6 +1487,7 @@ class Colors(activity.Activity, ExportedGObject): self.easel.update_playback() if not self.easel.playback_done(): self.flush_dirty_canvas() + # Update the progress bar. progress_percent = int(100*float(self.easel.playback)/(self.easel.playback_length()+1)) if self.easel.playing and progress_percent != self.playbackpos.get_value(): @@ -1485,6 +1498,7 @@ class Colors(activity.Activity, ExportedGObject): # Painting during playback mode allows you start where the painter left off. # But only when playback is not active. if not self.easel.playing and (self.cur_buttons & Colors.BUTTON_TOUCH): + self.clear_undo() self.easel.truncate_at_playback() self.set_mode(Colors.MODE_CANVAS) return @@ -1496,6 +1510,11 @@ class Colors(activity.Activity, ExportedGObject): # Update drawing. if self.cur_buttons & Colors.BUTTON_TOUCH: + # At the beginning of the stroke, the shared buffer is used to + # save the Undo position (when in single user mode). + if not self.easel.stroke: + self.save_undo() + if self.mx != self.lastmx or self.my != self.lastmy or self.videopaint_enabled: self.draw(Pos(self.mx, self.my)) self.flush_dirty_canvas() @@ -1760,6 +1779,7 @@ class Colors(activity.Activity, ExportedGObject): if response_id is gtk.RESPONSE_OK: if self.mode != Colors.MODE_CANVAS: self.set_mode(Colors.MODE_CANVAS) + self.clear_undo() self.easel.clear() self.flush_entire_canvas() @@ -1802,6 +1822,7 @@ class Colors(activity.Activity, ExportedGObject): def on_sample (self, button): self.set_mode(Colors.MODE_PLAYBACK) + self.clear_undo() self.easel.clear() self.easel.load(str(button.filename)) self.easel.set_playback_speed(8) @@ -1823,6 +1844,7 @@ class Colors(activity.Activity, ExportedGObject): def read_file(self, file_path): log.debug("Loading from journal %s", file_path) self.set_mode(Colors.MODE_CANVAS) + self.clear_undo() self.easel.clear() self.easel.load(str(file_path.encode())) self.easel.start_playback() @@ -1830,6 +1852,7 @@ class Colors(activity.Activity, ExportedGObject): self.playbackpos.set_value(100) self.set_mode(Colors.MODE_CANVAS) log.debug("Played back %d commands", self.easel.playback_length()) + self.save_undo() def write_file(self, file_path): log.debug("Saving to journal %s", file_path) @@ -1847,6 +1870,50 @@ class Colors(activity.Activity, ExportedGObject): pbuf.save(filename, "png") #----------------------------------------------------------------------------------------------------------------- + # Undo + # Only available in non-shared mode. Undo saves a single stroke in the shared buffer + # for instant undo. Further undos require a replay of the drawing, which may take + # awhile. + # Redo not implement yet but shouldn't be hard to do. + + def on_undo(self, button): + # Cannot undo when progress window is up. + if self.playbackposbar.ignore_change > 0: + return + + if not self.connected and len(self.undo_buffer) > 0: + to = self.undo_buffer.pop() + print "undoing from %d to %d" % (self.easel.get_num_commands(), to) + if self.undo_image_valid: + # TODO: Add an API for truncating at arbitrary points when the C library is next changed. + self.easel.playback = to + self.easel.restore_shared_image() + else: + self.play_to(to) + self.easel.truncate_at_playback() + self.flush_entire_canvas() + self.undo_image_valid = False + self.update_undo() + + def save_undo(self): + if not self.connected: + to = max(0, self.easel.get_num_commands()-1) + print "saving undo at ", to + self.undo_buffer.append(to) + self.easel.save_shared_image() + self.undo_image_valid = True + self.update_undo() + + def clear_undo(self): + self.undo_buffer = [] + self.undo_image_valid = False + self.update_undo() + + def update_undo(self): + if not self.connected: + self.undobtn.set_sensitive(len(self.undo_buffer) > 0) + + #----------------------------------------------------------------------------------------------------------------- # Clipboard integration (ported from Oficina) def on_copy(self, button): -- cgit v0.9.1