OSDN Git Service

color history popup (unpolished, but works)
authorMartin Renold <martinxyz@gmx.ch>
Mon, 20 Apr 2009 10:31:14 +0000 (10:31 +0000)
committerMartin Renold <martinxyz@gmx.ch>
Mon, 20 Apr 2009 10:31:14 +0000 (10:31 +0000)
This replaces the "invert color" feature (hotkey x) which was only
useful for black and white anyway.

svn://svn.gna.org/svn/mypaint/trunk@315

gui/colorselectionwindow.py
gui/drawwindow.py
gui/historypopup.py [new file with mode: 0644]
lib/brush.py
lib/document.py

index 18a48ed..ef7af0e 100644 (file)
@@ -127,7 +127,7 @@ class ColorSelectorPopup(gtk.Window):
         self.app.kbm.add_window(self)
 
         #self.set_title('Color')
-        self.connect('delete-event', self.app.hide_window_cb)
+        #self.connect('delete-event', self.app.hide_window_cb)
 
         self.image = image = gtk.Image()
         self.add(image)
index 576c6d7..e4bd8c7 100644 (file)
@@ -20,7 +20,7 @@ from glob import glob
 import gtk
 from gtk import gdk, keysyms
 
-import tileddrawwidget, colorselectionwindow, stategroup, keyboard
+import tileddrawwidget, colorselectionwindow, historypopup, stategroup, keyboard
 from lib import document, helpers
 
 class Window(gtk.Window):
@@ -41,12 +41,13 @@ class Window(gtk.Window):
         vbox = gtk.VBox()
         self.add(vbox)
 
+        self.doc = document.Document()
+        self.doc.set_brush(self.app.brush)
+
         self.create_ui()
         self.menubar = self.ui.get_widget('/Menubar')
         vbox.pack_start(self.menubar, expand=False)
 
-        self.doc = document.Document()
-        self.doc.set_brush(self.app.brush)
         self.tdw = tileddrawwidget.TiledDrawWidget(self.doc)
         vbox.pack_start(self.tdw)
 
@@ -149,7 +150,7 @@ class Window(gtk.Window):
               <separator/>
               <menuitem action='Eraser'/>
               <separator/>
-              <menuitem action='InvertColor'/>
+              <menuitem action='ColorHistoryPopup'/>
               <menuitem action='PickColor'/>
               <menuitem action='ChangeColorPopup'/>
               <menuitem action='ColorWheelPopup'/>
@@ -202,7 +203,7 @@ class Window(gtk.Window):
             ('PasteLayer',         None, 'Paste Layer from Clipboard', '<control>V', None, self.paste_cb),
 
             ('BrushMenu',    None, 'Brush'),
-            ('InvertColor',  None, 'Invert Color', 'x', None, self.invert_color_cb),
+            ('ColorHistoryPopup',  None, 'Previous Color', 'x', None, self.popup_cb),
             ('Brighter',     None, 'Brighter', None, None, self.brighter_cb),
             ('Darker',       None, 'Darker', None, None, self.darker_cb),
             ('Bigger',       None, 'Bigger', 'f', None, self.brush_bigger_cb),
@@ -304,16 +305,20 @@ class Window(gtk.Window):
         p2s = sg.create_popup_state
         changer = p2s(colorselectionwindow.ChangeColorPopup(self.app))
         wheel = p2s(colorselectionwindow.ColorWheelPopup(self.app))
+        hist = p2s(historypopup.HistoryPopup(self.app, self.doc))
 
         self.popup_states = {
             'ChangeColorPopup': changer,
             'ColorWheelPopup': wheel,
+            'ColorHistoryPopup': hist,
             }
         changer.next_state = wheel
         wheel.next_state = changer
         changer.autoleave_timeout = None
         wheel.autoleave_timeout = None
-
+        hist.autoleave_timeout = None
+        hist.autoleave_timeout = 0.600
+        self.history_popup_state = hist
 
     def toggleWindow_cb(self, action):
         s = action.get_name()
@@ -469,7 +474,7 @@ class Window(gtk.Window):
     def button_press_cb(self, win, event):
         #print event.device, event.button
         if event.button == 2:
-            # check whether we are painting (accidental
+            # check whether we are painting (accidental)
             pressure = event.get_axis(gdk.AXIS_PRESSURE)
             if (event.state & gdk.BUTTON1_MASK) or pressure:
                 # do not allow dragging while painting (often happens accidentally)
@@ -480,9 +485,8 @@ class Window(gtk.Window):
             # pick color "standard"; TODO: show color picking cursor?
             if event.state & gdk.CONTROL_MASK:
                 self.pick_color_cb(None)
-        # too slow to be useful:
-        #elif event.button == 3:
-        #    self.tdw.start_drag(self.dragfunc_rotate)
+        elif event.button == 3:
+            self.history_popup_state.activate()
 
     def button_release_cb(self, win, event):
         #print event.device, event.button
@@ -582,10 +586,6 @@ class Window(gtk.Window):
     def toggle_layers_above_cb(self, action):
         self.tdw.toggle_show_layers_above()
 
-    def invert_color_cb(self, action):
-        self.end_eraser_mode()
-        self.app.brush.invert_color()
-        
     def pick_color_cb(self, action):
         self.end_eraser_mode()
         size = int(self.app.brush.get_actual_radius() * math.sqrt(math.pi))
diff --git a/gui/historypopup.py b/gui/historypopup.py
new file mode 100644 (file)
index 0000000..2a45cba
--- /dev/null
@@ -0,0 +1,130 @@
+import math
+import gtk
+gdk = gtk.gdk
+
+import random, colorsys
+import numpy
+
+"""
+Worklist (planning/prototyping)
+- short keypress switches between two colors (==> still possible to do black/white the old way)
+- long keypress allows to move the mouse over another color
+  - the mouseover color is selected when the key is released
+- (unsure) pressing the key twice (without painting in-between) cycles through the colors
+  - the problem is that you don't see the colors
+  ==> different concept:
+    - short keypress opens the color ring with cursor centered on hole (ring stays open)
+    - the ring disappears as soon as you touch it (you can just continue painting)
+    - pressing the key again cycles the ring
+- recent colors should be saved with painting
+
+Observation:
+- it seems quite unnatural that you can have a /shorter/ popup duration by pressing the key /longer/
+  ==> you rather want a /minimum/ duration
+"""
+
+num_colors = 6
+
+class HistoryPopup(gtk.Window):
+    outside_popup_timeout = 0
+    def __init__(self, app, doc):
+        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+        self.set_gravity(gdk.GRAVITY_CENTER)
+        self.set_position(gtk.WIN_POS_MOUSE)
+
+        self.app = app
+        self.app.kbm.add_window(self)
+
+       self.set_events(gdk.BUTTON_PRESS_MASK |
+                        gdk.BUTTON_RELEASE_MASK |
+                        gdk.ENTER_NOTIFY |
+                        gdk.LEAVE_NOTIFY
+                        )
+        self.connect("button-release-event", self.button_release_cb)
+        self.connect("button-press-event", self.button_press_cb)
+        self.connect("expose_event", self.expose_cb)
+
+        self.set_size_request(300, 50)
+
+        self.selection = None
+
+        self.colorhist = [(random.random(), random.random(), random.random()) for i in range(num_colors)]
+
+        self.doc = doc
+        doc.stroke_observers.append(self.stroke_finished_cb)
+
+    def enter(self):
+        def hsv_equal(a, b):
+            # hack required because we somewhere have an rgb<-->hsv conversion roundtrip
+            a_ = numpy.array(colorsys.hsv_to_rgb(*a))
+            b_ = numpy.array(colorsys.hsv_to_rgb(*b))
+            return ((a_ - b_)**2).sum() < (3*1.0/256)**2
+
+        # finish pending stroke, if any (causes stroke_finished_cb to get called)
+        self.doc.split_stroke()
+        if self.selection is None:
+            self.selection = num_colors - 1
+            color = self.app.brush.get_color_hsv()
+            if hsv_equal(self.colorhist[self.selection], color):
+                self.selection -= 1
+        else:
+            self.selection = (self.selection - 1) % num_colors
+
+        self.app.brush.set_color_hsv(self.colorhist[self.selection])
+        self.show_all()
+        self.window.set_cursor(gdk.Cursor(gdk.CROSSHAIR))
+    
+    def leave(self):
+        self.hide()
+
+    def button_press_cb(self, widget, event):
+        pass
+
+    def button_release_cb(self, widget, event):
+        pass
+
+    def stroke_finished_cb(self, stroke, brush):
+        print 'stroke finished', stroke.total_painting_time, 'seconds'
+        self.selection = None
+        if not brush.is_eraser():
+            color = brush.get_color_hsv()
+            if color in self.colorhist:
+                self.colorhist.remove(color)
+            self.colorhist = (self.colorhist + [color])[-num_colors:]
+
+    def expose_cb(self, widget, event):
+        cr = self.window.cairo_create()
+        aloc = self.get_allocation()
+        #pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, True, 8, size, size)
+
+        # translate to center
+        cx = aloc.x + aloc.width / 2
+        cy = aloc.y + aloc.height / 2
+        r = aloc.height/2.0 - 2.0
+        #cr.translate(aloc.width-1 -r-1.0, cy)
+        cr.translate(0+r+1.0, cy)
+
+        cr.set_source_rgb(1.0, 1.0, 1.0)
+        cr.paint()
+
+        for i, c in enumerate(self.colorhist):
+            cr.arc(0, 0, r, 0, 2 * math.pi)
+
+            cr.set_source_rgb(*colorsys.hsv_to_rgb(*c))
+        
+            if i == self.selection:
+                cr.fill_preserve()
+                cr.set_source_rgb(0, 0, 0)
+                cr.stroke()
+            else:
+                cr.fill()
+                #cr.set_source_rgb(0.5, 0, 0)
+                #cr.stroke()
+
+            cr.translate(+2*r+2.0, 0)
+
+        #pixmap, mask = pixbuf.render_pixmap_and_mask()
+        #self.image.set_from_pixmap(pixmap, mask)
+        #self.shape_combine_mask(mask,0,0)
+
+        return True
index 6909f5e..cbbe2f6 100644 (file)
@@ -265,13 +265,8 @@ class Brush_Lowlevel(mypaintlib.Brush):
         hsv = [clamp(x, 0.0, 1.0) for x in hsv]
         return colorsys.hsv_to_rgb(*hsv)
 
-    def invert_color(self):
-        rgb = self.get_color_rgb()
-        rgb = [1-x for x in rgb]
-        self.set_color_rgb(rgb)
-
     def is_eraser(self):
-        return self.setting_by_cname('eraser').base_value > 0.5
+        return self.setting_by_cname('eraser').base_value > 0.9
 
 class Brush(Brush_Lowlevel):
     def __init__(self, app):
index 887b21b..e04a3e8 100644 (file)
@@ -63,8 +63,8 @@ class Document():
         self.brush = brush.Brush_Lowlevel()
         self.stroke = None
         self.canvas_observers = []
-        self.layer_observers = []
-
+        self.layer_observers = []  # callback arguments: x, y, w, h
+        self.stroke_observers = [] # callback arguments: stroke, brush (brush is a temporary read-only convenience object)
         self.clear(True)
 
     def clear(self, init=False):
@@ -99,6 +99,8 @@ class Document():
             self.command_stack.do(command.Stroke(self, self.stroke, before, after))
             self.snapshot_before_stroke = after
             self.unsaved_painting_time += self.stroke.total_painting_time
+            for f in self.stroke_observers:
+                f(self.stroke, self.brush)
         self.stroke = None
 
     def select_layer(self, idx):