Exemplo n.º 1
0
class PaintingWindow(yutani.Window):

    base_width = 600
    base_height = 600

    def __init__(self, decorator, path):
        super(PaintingWindow,
              self).__init__(self.base_width + decorator.width(),
                             self.base_height + decorator.height(),
                             title=app_name,
                             icon="applications-painting",
                             doublebuffer=True)
        self.move(100, 100)
        self.x = 100
        self.y = 100
        self.decorator = decorator
        self.picker = None
        self.last_color = (0, 0, 0)
        self.modifiers = None
        self.checkpattern = self.checkerboard(24)

        def about_window(action):
            AboutAppletWindow(self.decorator, f"About {app_name}",
                              "/usr/share/icons/48/applications-painting.png",
                              _description, "applications-painting")

        def help_browser(action):
            subprocess.Popen(["help-browser.py", "painting.trt"])

        def close_picker():
            self.last_color = self.picker.color
            self.picker = None

        def open_file(action):
            OpenFileDialog(self.decorator,
                           "Open...",
                           glob="*.png",
                           callback=self.load_buffer,
                           window=self)

        def new_surface(action):
            # TODO: prompt for size
            if self.buf:
                self.buf.destroy()
            self.new_buffer(*action)
            self.draw()

        def new_prompt(action):
            def input_callback(input_window):
                width = int(input_window.tr.text)
                input_window.close()

                def second_callback(input_window):
                    height = int(input_window.tr.text)
                    input_window.close()
                    new_surface((width, height))

                TextInputWindow(self.decorator,
                                "Height?",
                                "new",
                                text="500",
                                callback=second_callback,
                                window=self)

            TextInputWindow(self.decorator,
                            "Width?",
                            "new",
                            text="500",
                            callback=input_callback,
                            window=self)

        def save_file(action):
            self.modified = False
            path = '/tmp/painting.png'
            self.set_title(f'{os.path.basename(path)} - {app_name}', self.icon)
            self.surface.write_to_png(path)

        def select_color(action):
            if self.picker:
                return
            else:
                self.picker = ColorPickerWindow(self.decorator, close_picker)
                self.picker.draw()

        def clear_everything(action):
            self.draw_ctx.save()
            self.draw_ctx.set_operator(cairo.OPERATOR_SOURCE)
            self.draw_ctx.rectangle(0, 0, self.surface.get_width(),
                                    self.surface.get_height())
            self.draw_ctx.set_source_rgba(0, 0, 0, 0)
            self.draw_ctx.fill()
            self.draw_ctx.restore()

        menus = [
            ("File", [
                MenuEntrySubmenu("New...", [
                    MenuEntryAction("500×500", "new", new_surface, (500, 500)),
                    MenuEntryAction("800×600", "new", new_surface, (800, 600)),
                    MenuEntryAction("Custom...", "new", new_prompt, None),
                ],
                                 icon="new"),
                MenuEntryAction("Open", "open", open_file, None),
                MenuEntryAction("Save", "save", save_file, None),
                MenuEntryDivider(),
                MenuEntryAction("Exit", "exit", self.exit_app, None),
            ]),
            ("Tools", [
                MenuEntryAction("Color", None, select_color, None),
                MenuEntryAction("Clear Everything", None, clear_everything,
                                None),
            ]),
            ("Help", [
                MenuEntryAction("Contents", "help", help_browser, None),
                MenuEntryDivider(),
                MenuEntryAction(f"About {app_name}", "star", about_window,
                                None),
            ]),
        ]

        self.menubar = MenuBarWidget(self, menus)

        self.menus = {}
        self.hovered_menu = None

        if not path:
            self.new_buffer(500, 500)
        else:
            self.load_buffer(path)

        self.hilighted = None
        self.was_drawing = False
        self.line_width = 2.0
        self.curs_x = None
        self.curs_y = None
        self.moving = False
        self.scale = 1.0
        self.modified = False

    def actually_exit(self):
        menus = [x for x in self.menus.values()]
        for x in menus:
            x.definitely_close()
        self.close()
        sys.exit(0)

    def exit_app(self, action=None):
        if self.modified:
            DialogWindow(
                self.decorator,
                app_name,
                "You have unsaved changes.\nAre you sure you want to quit?",
                callback=self.actually_exit,
                window=self)
        else:
            self.actually_exit()

    def load_buffer(self, path):
        self.set_title(f'{os.path.basename(path)} - {app_name}', self.icon)
        s = cairo.ImageSurface.create_from_png(path)

        self.init_buffer(s.get_width(), s.get_height())
        self.draw_ctx.save()
        self.draw_ctx.set_operator(cairo.OPERATOR_SOURCE)
        self.draw_ctx.rectangle(0, 0, self.surface.get_width(),
                                self.surface.get_height())
        self.draw_ctx.set_source_rgba(0, 0, 0, 0)
        self.draw_ctx.fill()
        self.draw_ctx.restore()
        self.draw_ctx.set_source_surface(s, 0, 0)
        self.draw_ctx.paint()

    def init_buffer(self, w, h):
        self.modified = False
        self.buf = yutani.GraphicsBuffer(w, h)
        self.surface = self.buf.get_cairo_surface()
        self.draw_ctx = cairo.Context(self.surface)
        self.offset_x = int(
            (self.width - self.decorator.width() - self.buf.width) / 2)
        self.offset_y = int((self.height - self.decorator.height() -
                             self.buf.height - self.menubar.height) / 2)

    def new_buffer(self, w, h):
        self.init_buffer(w, h)
        self.set_title(f'Untitled - {app_name}', self.icon)
        self.draw_ctx.rectangle(0, 0, self.surface.get_width(),
                                self.surface.get_height())
        self.draw_ctx.set_source_rgb(1, 1, 1)
        self.draw_ctx.fill()

    def color(self):
        if self.picker:
            return self.picker.color
        else:
            return self.last_color

    def checkerboard(self, size):
        s = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
        c = cairo.Context(s)
        c.set_source_rgb(128 / 255, 128 / 255, 128 / 255)
        c.paint()
        c.set_source_rgb(200 / 255, 200 / 255, 200 / 255)
        c.rectangle(size / 2, 0, size / 2, size / 2)
        c.rectangle(0, size / 2, size / 2, size / 2)
        c.fill()
        return s

    def draw(self, decor=True, menu=True, clips=None):
        surface = self.get_cairo_surface()

        WIDTH, HEIGHT = self.width - self.decorator.width(
        ), self.height - self.decorator.height()

        ctx = cairo.Context(surface)
        ctx.translate(self.decorator.left_width(), self.decorator.top_height())

        if clips:
            for clip in clips:
                x, y, a, b = clip
                width = a - x
                height = b - y
                ctx.rectangle(x, y, width, height)
            ctx.clip()

        ctx.save()
        ctx.set_source_surface(self.checkpattern, 0, 0)
        ctx.get_source().set_filter(cairo.FILTER_NEAREST)
        ctx.get_source().set_extend(cairo.EXTEND_REPEAT)
        ctx.rectangle(0, self.menubar.height, WIDTH,
                      HEIGHT - self.menubar.height)
        ctx.fill()
        ctx.restore()

        ctx.save()
        ctx.translate(0, self.menubar.height)
        ctx.rectangle(0, 0, WIDTH, HEIGHT - self.menubar.height)
        ctx.clip()

        ctx.save()
        ctx.scale(self.scale, self.scale)
        ctx.set_source_surface(self.surface, self.offset_x / self.scale,
                               self.offset_y / self.scale)
        ctx.get_source().set_filter(cairo.FILTER_FAST)
        ctx.paint()

        if not self.curs_x is None:
            if self.scale < 1.0:
                ctx.set_line_width(0.5 / self.scale)
            else:
                ctx.set_line_width(0.5)
            ctx.arc(self.curs_x / self.scale, self.curs_y / self.scale,
                    self.line_width / 2, 0, 2 * math.pi)
            ctx.set_source_rgba(0, 0, 0, 0.7)
            ctx.stroke()
            ctx.arc(self.curs_x / self.scale, self.curs_y / self.scale,
                    self.line_width / 2 - 0.5, 0, 2 * math.pi)
            ctx.set_source_rgba(1, 1, 1, 0.7)
            ctx.stroke()

        ctx.restore()
        ctx.restore()

        # For debugging clip regions.
        #if clips:
        #    for clip in clips:
        #        x,y,a,b = clip
        #        width = a - x
        #        height = b - y
        #        ctx.rectangle(x,y,width,height)
        #        ctx.set_source_rgba(1.0,0,0,0.4)
        #        ctx.paint()

        if menu:
            self.menubar.draw(ctx, 0, 0, WIDTH)

        if decor:
            self.decorator.render(self)

        self.flip()

    def finish_resize(self, msg):
        """Accept a resize."""
        if msg.width < 120 or msg.height < 120:
            self.resize_offer(max(msg.width, 120), max(msg.height, 120))
            return
        self.resize_accept(msg.width, msg.height)
        self.reinit()
        self.offset_x = int(
            (self.width - self.decorator.width() - self.buf.width) / 2)
        self.offset_y = int((self.height - self.decorator.height() -
                             self.buf.height - self.menubar.height) / 2)
        self.draw()
        self.resize_done()
        self.flip()

    def get_color(self, x, y):
        c = self.buf.get_value(x, y)
        a = (c >> 24) & 0xFF
        r = (c >> 16) & 0xFF
        g = (c >> 8) & 0xFF
        b = (c) & 0xFF
        return (r, g, b)

    def mouse_event(self, msg):
        if d.handle_event(msg) == yutani.Decor.EVENT_CLOSE:
            self.exit_app()
        x, y = msg.new_x - self.decorator.left_width(
        ), msg.new_y - self.decorator.top_height()
        w, h = self.width - self.decorator.width(
        ), self.height - self.decorator.height()

        if not self.was_drawing:
            if x >= 0 and x < w and y >= 0 and y < self.menubar.height:
                self.menubar.mouse_event(msg, x, y)
                return

            if x < 0 or x >= w or y < 0 or y >= h:
                return

            if x >= 0 and x < w and y >= self.menubar.height and y < h:
                if msg.buttons & yutani.MouseButton.BUTTON_RIGHT:
                    if self.picker:
                        _x, _y = x - self.offset_x, y - self.menubar.height - self.offset_y
                        if _x >= 0 and _x < self.surface.get_width(
                        ) and _y >= 0 and _y < self.surface.get_height():
                            self.picker.set_color(*self.get_color(_x, _y))
                    if not self.menus:
                        pass  # No context menu at the moment.
                        #menu_entries = [
                        #    MenuEntryAction("Up",None,self.go_up,None),
                        #]
                        #menu = MenuWindow(menu_entries,(self.x+msg.new_x,self.y+msg.new_y),root=self)
                    return

            if y < 0: return

        decor = False
        regions = []

        if not self.modifiers:
            if msg.buttons & yutani.MouseButton.SCROLL_UP:
                self.line_width *= 1.2
            elif msg.buttons & yutani.MouseButton.SCROLL_DOWN:
                self.line_width /= 1.2
        elif self.modifiers & yutani.Modifier.MOD_LEFT_CTRL:
            if msg.buttons & yutani.MouseButton.SCROLL_UP:
                self.scale += 0.1
                regions.append((0, 0, w, h))
            elif msg.buttons & yutani.MouseButton.SCROLL_DOWN:
                self.scale -= 0.1
                if self.scale < 0.1:
                    self.scale = 0.1
                regions.append((0, 0, w, h))

        if not (msg.buttons & yutani.MouseButton.BUTTON_LEFT):
            self.was_drawing = False

        if (msg.buttons & yutani.MouseButton.BUTTON_MIDDLE):
            if not self.moving:
                self.initial = msg.new_x, msg.new_y
                self.initial_off = self.offset_x, self.offset_y
                self.moving = True
            regions.append((self.offset_x, self.offset_y + self.menubar.height,
                            self.offset_x + int(self.buf.width * self.scale),
                            self.offset_y + self.menubar.height +
                            int(self.buf.height * self.scale)))
            self.offset_x = self.initial_off[0] + msg.new_x - self.initial[0]
            self.offset_y = self.initial_off[1] + msg.new_y - self.initial[1]
            regions.append((self.offset_x, self.offset_y + self.menubar.height,
                            self.offset_x + int(self.buf.width * self.scale),
                            self.offset_y + self.menubar.height +
                            int(self.buf.height * self.scale)))
        else:
            self.moving = False

        cur_x_new = msg.new_x - self.decorator.left_width()
        cur_y_new = msg.new_y - self.decorator.top_height()

        cur_x_old = msg.old_x - self.decorator.left_width()
        cur_y_old = msg.old_y - self.decorator.top_height()

        if (msg.command == yutani.MouseEvent.DRAG
                or msg.command == yutani.MouseEvent.DOWN
            ) and msg.buttons & yutani.MouseButton.BUTTON_LEFT:
            self.was_drawing = True
            self.draw_ctx.set_line_cap(cairo.LINE_CAP_ROUND)
            self.draw_ctx.set_line_join(cairo.LINE_JOIN_ROUND)
            self.draw_ctx.set_source_rgb(*self.color())
            self.draw_ctx.set_line_width(self.line_width)

            x_1 = 0.5 + (cur_x_new - self.offset_x) / self.scale
            y_1 = 0.5 + (cur_y_new - self.offset_y -
                         self.menubar.height) / self.scale
            if msg.command == yutani.MouseEvent.DOWN:
                x_0 = x_1
                y_0 = y_1
                regions.append((
                    int(cur_x_new - self.line_width * self.scale),
                    int(cur_y_new - self.line_width * self.scale),
                    int(cur_x_new + self.line_width * self.scale),
                    int(cur_y_new + self.line_width * self.scale),
                ))
            else:
                x_0 = 0.5 + (cur_x_old - self.offset_x) / self.scale
                y_0 = 0.5 + (cur_y_old - self.offset_y -
                             self.menubar.height) / self.scale
                regions.append((
                    int(
                        min(cur_x_new, cur_x_old) -
                        self.line_width * self.scale),
                    int(
                        min(cur_y_new, cur_y_old) -
                        self.line_width * self.scale),
                    int(
                        max(cur_x_new, cur_x_old) +
                        self.line_width * self.scale),
                    int(
                        max(cur_y_new, cur_y_old) +
                        self.line_width * self.scale),
                ))

            self.draw_ctx.move_to(x_0, y_0)
            self.draw_ctx.line_to(x_1, y_1)
            self.draw_ctx.stroke()
            if not self.modified:
                self.modified = True
                self.set_title("*" + self.title, self.icon)
                decor = True
        else:
            regions.append((
                int(cur_x_new - self.line_width * self.scale),
                int(cur_y_new - self.line_width * self.scale),
                int(cur_x_new + self.line_width * self.scale),
                int(cur_y_new + self.line_width * self.scale),
            ))

        if self.curs_x:
            regions.append((
                int(self.curs_x - self.line_width * self.scale - 1.0),
                int(self.curs_y - self.line_width * self.scale +
                    self.menubar.height - 1.0),
                int(self.curs_x + self.line_width * self.scale + 1.0),
                int(self.curs_y + self.line_width * self.scale +
                    self.menubar.height + 1.0),
            ))
        self.curs_x = 0.5 + msg.new_x - self.decorator.left_width()
        self.curs_y = 0.5 + msg.new_y - self.decorator.top_height(
        ) - self.menubar.height
        self.draw(menu=False, decor=decor, clips=regions)

    def keyboard_event(self, msg):
        self.modifiers = msg.event.modifiers
        if msg.event.action != yutani.KeyAction.ACTION_DOWN:
            return  # Ignore anything that isn't a key down.
        if msg.event.key == b"q":
            self.exit_app()
Exemplo n.º 2
0
 def select_color(action):
     if self.picker:
         return
     else:
         self.picker = ColorPickerWindow(self.decorator, close_picker)
         self.picker.draw()
Exemplo n.º 3
0
 def select_color(action):
     if self.picker:
         return
     else:
         self.picker = ColorPickerWindow(self.decorator, close_picker)
         self.picker.draw()
Exemplo n.º 4
0
class PaintingWindow(yutani.Window):

    base_width = 600
    base_height = 600

    def __init__(self, decorator, path):
        super(PaintingWindow, self).__init__(self.base_width + decorator.width(), self.base_height + decorator.height(), title=app_name, icon="applications-painting", doublebuffer=True)
        self.move(100,100)
        self.x = 100
        self.y = 100
        self.decorator = decorator
        self.picker = None
        self.last_color = (0,0,0)
        self.modifiers = None
        self.checkpattern = self.checkerboard(24)

        def about_window(action):
            AboutAppletWindow(self.decorator,f"About {app_name}","/usr/share/icons/48/applications-painting.png",_description,"applications-painting")

        def help_browser(action):
            subprocess.Popen(["help-browser.py","painting.trt"])

        def close_picker():
            self.last_color = self.picker.color
            self.picker = None

        def open_file(action):
            OpenFileDialog(self.decorator,"Open...",glob="*.png",callback=self.load_buffer,window=self)

        def new_surface(action):
            # TODO: prompt for size
            if self.buf:
                self.buf.destroy()
            self.new_buffer(*action)
            self.draw()

        def new_prompt(action):
            def input_callback(input_window):
                width = int(input_window.tr.text)
                input_window.close()
                def second_callback(input_window):
                    height = int(input_window.tr.text)
                    input_window.close()
                    new_surface((width,height))

                TextInputWindow(self.decorator,"Height?","new",text="500",callback=second_callback,window=self)
            TextInputWindow(self.decorator,"Width?","new",text="500",callback=input_callback,window=self)

        def save_file(action):
            self.modified = False
            path = '/tmp/painting.png'
            self.set_title(f'{os.path.basename(path)} - {app_name}',self.icon)
            self.surface.write_to_png(path)

        def select_color(action):
            if self.picker:
                return
            else:
                self.picker = ColorPickerWindow(self.decorator, close_picker)
                self.picker.draw()

        def clear_everything(action):
            self.draw_ctx.save()
            self.draw_ctx.set_operator(cairo.OPERATOR_SOURCE)
            self.draw_ctx.rectangle(0,0,self.surface.get_width(),self.surface.get_height())
            self.draw_ctx.set_source_rgba(0,0,0,0)
            self.draw_ctx.fill()
            self.draw_ctx.restore()

        menus = [
            ("File", [
                MenuEntrySubmenu("New...",[
                    MenuEntryAction("500×500","new",new_surface,(500,500)),
                    MenuEntryAction("800×600","new",new_surface,(800,600)),
                    MenuEntryAction("Custom...","new",new_prompt,None),
                ],icon="new"),
                MenuEntryAction("Open","open",open_file,None),
                MenuEntryAction("Save","save",save_file,None),
                MenuEntryDivider(),
                MenuEntryAction("Exit","exit",self.exit_app,None),
            ]),
            ("Tools", [
                MenuEntryAction("Color",None,select_color,None),
                MenuEntryAction("Clear Everything",None,clear_everything,None),
            ]),
            ("Help", [
                MenuEntryAction("Contents","help",help_browser,None),
                MenuEntryDivider(),
                MenuEntryAction(f"About {app_name}","star",about_window,None),
            ]),
        ]

        self.menubar = MenuBarWidget(self,menus)

        self.menus = {}
        self.hovered_menu = None

        if not path:
            self.new_buffer(500,500)
        else:
            self.load_buffer(path)

        self.hilighted = None
        self.was_drawing = False
        self.line_width = 2.0
        self.curs_x = None
        self.curs_y = None
        self.moving = False
        self.scale = 1.0
        self.modified = False

    def actually_exit(self):
        menus = [x for x in self.menus.values()]
        for x in menus:
            x.definitely_close()
        self.close()
        sys.exit(0)

    def exit_app(self, action=None):
        if self.modified:
            DialogWindow(self.decorator,app_name,"You have unsaved changes.\nAre you sure you want to quit?",callback=self.actually_exit,window=self)
        else:
            self.actually_exit()


    def load_buffer(self,path):
        self.set_title(f'{os.path.basename(path)} - {app_name}',self.icon)
        s = cairo.ImageSurface.create_from_png(path)

        self.init_buffer(s.get_width(),s.get_height())
        self.draw_ctx.save()
        self.draw_ctx.set_operator(cairo.OPERATOR_SOURCE)
        self.draw_ctx.rectangle(0,0,self.surface.get_width(),self.surface.get_height())
        self.draw_ctx.set_source_rgba(0,0,0,0)
        self.draw_ctx.fill()
        self.draw_ctx.restore()
        self.draw_ctx.set_source_surface(s,0,0)
        self.draw_ctx.paint()

    def init_buffer(self,w,h):
        self.modified = False
        self.buf = yutani.GraphicsBuffer(w,h)
        self.surface = self.buf.get_cairo_surface()
        self.draw_ctx = cairo.Context(self.surface)
        self.offset_x = int((self.width-self.decorator.width()-self.buf.width)/2)
        self.offset_y = int((self.height-self.decorator.height()-self.buf.height-self.menubar.height)/2)

    def new_buffer(self,w,h):
        self.init_buffer(w,h)
        self.set_title(f'Untitled - {app_name}', self.icon)
        self.draw_ctx.rectangle(0,0,self.surface.get_width(),self.surface.get_height())
        self.draw_ctx.set_source_rgb(1,1,1)
        self.draw_ctx.fill()

    def color(self):
        if self.picker:
            return self.picker.color
        else:
            return self.last_color

    def checkerboard(self,size):
        s = cairo.ImageSurface(cairo.FORMAT_ARGB32,size,size)
        c = cairo.Context(s)
        c.set_source_rgb(128/255,128/255,128/255)
        c.paint()
        c.set_source_rgb(200/255,200/255,200/255)
        c.rectangle(size/2,0,size/2,size/2)
        c.rectangle(0,size/2,size/2,size/2)
        c.fill()
        return s

    def draw(self,decor=True,menu=True,clips=None):
        surface = self.get_cairo_surface()

        WIDTH, HEIGHT = self.width - self.decorator.width(), self.height - self.decorator.height()

        ctx = cairo.Context(surface)
        ctx.translate(self.decorator.left_width(), self.decorator.top_height())

        if clips:
            for clip in clips:
                x,y,a,b = clip
                width = a - x
                height = b - y
                ctx.rectangle(x,y,width,height)
            ctx.clip()

        ctx.save()
        ctx.set_source_surface(self.checkpattern,0,0)
        ctx.get_source().set_filter(cairo.FILTER_NEAREST)
        ctx.get_source().set_extend(cairo.EXTEND_REPEAT)
        ctx.rectangle(0,self.menubar.height,WIDTH,HEIGHT-self.menubar.height)
        ctx.fill()
        ctx.restore()

        ctx.save()
        ctx.translate(0,self.menubar.height)
        ctx.rectangle(0,0,WIDTH,HEIGHT-self.menubar.height)
        ctx.clip()


        ctx.save()
        ctx.scale(self.scale,self.scale)
        ctx.set_source_surface(self.surface,self.offset_x/self.scale,self.offset_y/self.scale)
        ctx.get_source().set_filter(cairo.FILTER_FAST)
        ctx.paint()

        if not self.curs_x is None:
            if self.scale < 1.0:
                ctx.set_line_width(0.5/self.scale)
            else:
                ctx.set_line_width(0.5)
            ctx.arc(self.curs_x/self.scale,self.curs_y/self.scale,self.line_width/2,0,2*math.pi)
            ctx.set_source_rgba(0,0,0,0.7)
            ctx.stroke()
            ctx.arc(self.curs_x/self.scale,self.curs_y/self.scale,self.line_width/2-0.5,0,2*math.pi)
            ctx.set_source_rgba(1,1,1,0.7)
            ctx.stroke()

        ctx.restore()
        ctx.restore()

        # For debugging clip regions.
        #if clips:
        #    for clip in clips:
        #        x,y,a,b = clip
        #        width = a - x
        #        height = b - y
        #        ctx.rectangle(x,y,width,height)
        #        ctx.set_source_rgba(1.0,0,0,0.4)
        #        ctx.paint()

        if menu:
            self.menubar.draw(ctx,0,0,WIDTH)

        if decor:
            self.decorator.render(self)

        self.flip()

    def finish_resize(self, msg):
        """Accept a resize."""
        if msg.width < 120 or msg.height < 120:
            self.resize_offer(max(msg.width,120),max(msg.height,120))
            return
        self.resize_accept(msg.width, msg.height)
        self.reinit()
        self.offset_x = int((self.width-self.decorator.width()-self.buf.width)/2)
        self.offset_y = int((self.height-self.decorator.height()-self.buf.height-self.menubar.height)/2)
        self.draw()
        self.resize_done()
        self.flip()

    def get_color(self,x,y):
        c = self.buf.get_value(x,y)
        a = (c >> 24) & 0xFF
        r = (c >> 16) & 0xFF
        g = (c >> 8)  & 0xFF
        b = (c) & 0xFF
        return (r,g,b)

    def mouse_event(self, msg):
        if d.handle_event(msg) == yutani.Decor.EVENT_CLOSE:
            self.exit_app()
        x,y = msg.new_x - self.decorator.left_width(), msg.new_y - self.decorator.top_height()
        w,h = self.width - self.decorator.width(), self.height - self.decorator.height()

        if not self.was_drawing:
            if x >= 0 and x < w and y >= 0 and y < self.menubar.height:
                self.menubar.mouse_event(msg, x, y)
                return

            if x < 0 or x >= w or y < 0 or y >= h:
                return

            if x >= 0 and x < w and y >= self.menubar.height and y < h:
                if msg.buttons & yutani.MouseButton.BUTTON_RIGHT:
                    if self.picker:
                        _x,_y = x-self.offset_x,y-self.menubar.height-self.offset_y
                        if _x >= 0 and _x < self.surface.get_width() and _y >= 0 and _y < self.surface.get_height():
                            self.picker.set_color(*self.get_color(_x,_y))
                    if not self.menus:
                        pass # No context menu at the moment.
                        #menu_entries = [
                        #    MenuEntryAction("Up",None,self.go_up,None),
                        #]
                        #menu = MenuWindow(menu_entries,(self.x+msg.new_x,self.y+msg.new_y),root=self)
                    return

            if y < 0: return

        decor = False
        regions = []

        if not self.modifiers:
            if msg.buttons & yutani.MouseButton.SCROLL_UP:
                self.line_width *= 1.2
            elif msg.buttons & yutani.MouseButton.SCROLL_DOWN:
                self.line_width /= 1.2
        elif self.modifiers & yutani.Modifier.MOD_LEFT_CTRL:
            if msg.buttons & yutani.MouseButton.SCROLL_UP:
                self.scale += 0.1
                regions.append((0,0,w,h))
            elif msg.buttons & yutani.MouseButton.SCROLL_DOWN:
                self.scale -= 0.1
                if self.scale < 0.1:
                    self.scale = 0.1
                regions.append((0,0,w,h))

        if not (msg.buttons & yutani.MouseButton.BUTTON_LEFT):
            self.was_drawing = False

        if (msg.buttons & yutani.MouseButton.BUTTON_MIDDLE):
            if not self.moving:
                self.initial = msg.new_x, msg.new_y
                self.initial_off = self.offset_x, self.offset_y
                self.moving = True
            regions.append(
                (
                    self.offset_x,
                    self.offset_y+self.menubar.height,
                    self.offset_x+int(self.buf.width*self.scale),
                    self.offset_y+self.menubar.height+int(self.buf.height*self.scale)
                )
            )
            self.offset_x = self.initial_off[0] + msg.new_x - self.initial[0]
            self.offset_y = self.initial_off[1] + msg.new_y - self.initial[1]
            regions.append(
                (
                    self.offset_x,
                    self.offset_y+self.menubar.height,
                    self.offset_x+int(self.buf.width*self.scale),
                    self.offset_y+self.menubar.height+int(self.buf.height*self.scale)
                )
            )
        else:
            self.moving = False


        cur_x_new = msg.new_x - self.decorator.left_width()
        cur_y_new = msg.new_y - self.decorator.top_height()

        cur_x_old = msg.old_x - self.decorator.left_width()
        cur_y_old = msg.old_y - self.decorator.top_height()

        if (msg.command == yutani.MouseEvent.DRAG or msg.command == yutani.MouseEvent.DOWN) and msg.buttons & yutani.MouseButton.BUTTON_LEFT:
            self.was_drawing = True
            self.draw_ctx.set_line_cap(cairo.LINE_CAP_ROUND)
            self.draw_ctx.set_line_join(cairo.LINE_JOIN_ROUND)
            self.draw_ctx.set_source_rgb(*self.color())
            self.draw_ctx.set_line_width(self.line_width)

            x_1 = 0.5 + (cur_x_new - self.offset_x) / self.scale
            y_1 = 0.5 + (cur_y_new - self.offset_y - self.menubar.height) / self.scale
            if msg.command == yutani.MouseEvent.DOWN:
                x_0 = x_1
                y_0 = y_1
                regions.append(
                    (
                        int(cur_x_new - self.line_width * self.scale),
                        int(cur_y_new - self.line_width * self.scale),
                        int(cur_x_new + self.line_width * self.scale),
                        int(cur_y_new + self.line_width * self.scale),
                    )
                )
            else:
                x_0 = 0.5 + (cur_x_old - self.offset_x) / self.scale
                y_0 = 0.5 + (cur_y_old - self.offset_y - self.menubar.height) / self.scale
                regions.append(
                    (
                        int(min(cur_x_new,cur_x_old) - self.line_width * self.scale),
                        int(min(cur_y_new,cur_y_old) - self.line_width * self.scale),
                        int(max(cur_x_new,cur_x_old) + self.line_width * self.scale),
                        int(max(cur_y_new,cur_y_old) + self.line_width * self.scale),
                    )
                )

            self.draw_ctx.move_to(x_0,y_0)
            self.draw_ctx.line_to(x_1,y_1)
            self.draw_ctx.stroke()
            if not self.modified:
                self.modified = True
                self.set_title("*" + self.title, self.icon)
                decor = True
        else:
            regions.append(
                (
                    int(cur_x_new - self.line_width * self.scale),
                    int(cur_y_new - self.line_width * self.scale),
                    int(cur_x_new + self.line_width * self.scale),
                    int(cur_y_new + self.line_width * self.scale),
                )
            )


        if self.curs_x:
            regions.append(
                (
                    int(self.curs_x - self.line_width * self.scale - 1.0),
                    int(self.curs_y - self.line_width * self.scale + self.menubar.height - 1.0),
                    int(self.curs_x + self.line_width * self.scale + 1.0),
                    int(self.curs_y + self.line_width * self.scale + self.menubar.height + 1.0),
                )
            )
        self.curs_x = 0.5+msg.new_x - self.decorator.left_width()
        self.curs_y = 0.5+msg.new_y - self.decorator.top_height() - self.menubar.height
        self.draw(menu=False,decor=decor,clips=regions)

    def keyboard_event(self, msg):
        self.modifiers = msg.event.modifiers
        if msg.event.action != yutani.KeyAction.ACTION_DOWN:
            return # Ignore anything that isn't a key down.
        if msg.event.key == b"q":
            self.exit_app()