#!/usr/bin/python3
import gi, os
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf
import cairo

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Paint')
        self.set_default_size(800, 600)
        self.tool = 'pencil'
        self.color = (0.0, 0.0, 0.0)
        self.brush = 3.0
        self.drawing = False
        self.last_x = self.last_y = 0
        self.start_x = self.start_y = 0
        self.surface = None
        self.backup = None
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
        vbox.set_margin_top(2); vbox.set_margin_bottom(2)
        vbox.set_margin_start(2); vbox.set_margin_end(2)
        self.add(vbox)
        toolbar = Gtk.Box(spacing=4)
        tools = [('pencil','✏ Pencil'),('line','/ Line'),('rect','▭ Rect'),
                 ('circle','◯ Circle'),('eraser','◻ Eraser')]
        self.tool_btns = {}
        for t, lbl in tools:
            btn = Gtk.RadioButton.new_with_label(None if not self.tool_btns else list(self.tool_btns.values())[0], lbl)
            btn.connect('toggled', self.set_tool, t)
            if not self.tool_btns: btn.set_active(True)
            toolbar.pack_start(btn, False, False, 0)
            self.tool_btns[t] = btn
        toolbar.add(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL))
        self.color_btn = Gtk.ColorButton()
        rgba = Gdk.RGBA(0,0,0,1); self.color_btn.set_rgba(rgba)
        self.color_btn.connect('color-set', self.set_color)
        toolbar.pack_start(self.color_btn, False, False, 0)
        toolbar.add(Gtk.Label(label='Size:'))
        self.size_spin = Gtk.SpinButton.new_with_range(1, 50, 1)
        self.size_spin.set_value(3)
        self.size_spin.connect('value-changed', lambda s: setattr(self, 'brush', s.get_value()))
        toolbar.pack_start(self.size_spin, False, False, 0)
        toolbar.add(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL))
        undo_btn = Gtk.Button(label='↩ Undo')
        undo_btn.connect('clicked', self.undo)
        toolbar.pack_start(undo_btn, False, False, 0)
        clear_btn = Gtk.Button(label='🗑 Clear')
        clear_btn.connect('clicked', self.clear)
        toolbar.pack_start(clear_btn, False, False, 0)
        save_btn = Gtk.Button(label='💾 Save PNG')
        save_btn.connect('clicked', self.save)
        toolbar.pack_start(save_btn, False, False, 0)
        open_btn = Gtk.Button(label='📂 Open')
        open_btn.connect('clicked', self.open_img)
        toolbar.pack_start(open_btn, False, False, 0)
        vbox.pack_start(toolbar, False, False, 0)
        self.da = Gtk.DrawingArea()
        self.da.connect('draw', self.draw)
        self.da.connect('button-press-event', self.press)
        self.da.connect('button-release-event', self.release)
        self.da.connect('motion-notify-event', self.motion)
        self.da.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
                           Gdk.EventMask.BUTTON_RELEASE_MASK |
                           Gdk.EventMask.POINTER_MOTION_MASK)
        vbox.pack_start(self.da, True, True, 0)
    def set_tool(self, btn, t):
        if btn.get_active(): self.tool = t
    def set_color(self, btn):
        rgba = btn.get_rgba()
        self.color = (rgba.red, rgba.green, rgba.blue)
    def ensure_surface(self):
        if not self.surface:
            alloc = self.da.get_allocation()
            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, max(alloc.width,10), max(alloc.height,10))
            cr = cairo.Context(self.surface); cr.set_source_rgb(1,1,1); cr.paint()
    def press(self, w, e):
        self.ensure_surface()
        self.drawing = True
        self.start_x = self.last_x = e.x
        self.start_y = self.last_y = e.y
        # save backup for shape tools
        if self.tool in ('line','rect','circle') and self.surface:
            self.backup = self.surface.copy()
    def release(self, w, e):
        if not self.drawing: return
        self.drawing = False
        if self.tool == 'line':
            self._draw_line(self.surface, self.start_x, self.start_y, e.x, e.y)
            self.da.queue_draw()
        elif self.tool == 'rect':
            self._draw_rect(self.surface, self.start_x, self.start_y, e.x, e.y)
            self.da.queue_draw()
        elif self.tool == 'circle':
            self._draw_circle(self.surface, self.start_x, self.start_y, e.x, e.y)
            self.da.queue_draw()
    def motion(self, w, e):
        if not self.drawing or not self.surface: return
        if self.tool == 'pencil':
            cr = cairo.Context(self.surface)
            cr.set_source_rgb(*self.color); cr.set_line_width(self.brush)
            cr.set_line_cap(cairo.LINE_CAP_ROUND)
            cr.move_to(self.last_x, self.last_y); cr.line_to(e.x, e.y); cr.stroke()
        elif self.tool == 'eraser':
            cr = cairo.Context(self.surface)
            cr.set_source_rgb(1,1,1); cr.set_line_width(self.brush*3)
            cr.set_line_cap(cairo.LINE_CAP_ROUND)
            cr.move_to(self.last_x, self.last_y); cr.line_to(e.x, e.y); cr.stroke()
        elif self.tool in ('line','rect','circle') and self.backup:
            # restore backup, draw preview
            cr = cairo.Context(self.surface)
            cr.set_source_surface(self.backup, 0, 0); cr.paint()
            if self.tool == 'line': self._draw_line(self.surface, self.start_x, self.start_y, e.x, e.y)
            elif self.tool == 'rect': self._draw_rect(self.surface, self.start_x, self.start_y, e.x, e.y)
            elif self.tool == 'circle': self._draw_circle(self.surface, self.start_x, self.start_y, e.x, e.y)
        self.last_x = e.x; self.last_y = e.y
        self.da.queue_draw()
    def _draw_line(self, surf, x1,y1,x2,y2):
        cr = cairo.Context(surf)
        cr.set_source_rgb(*self.color); cr.set_line_width(self.brush)
        cr.move_to(x1,y1); cr.line_to(x2,y2); cr.stroke()
    def _draw_rect(self, surf, x1,y1,x2,y2):
        cr = cairo.Context(surf)
        cr.set_source_rgb(*self.color); cr.set_line_width(self.brush)
        cr.rectangle(min(x1,x2),min(y1,y2),abs(x2-x1),abs(y2-y1)); cr.stroke()
    def _draw_circle(self, surf, x1,y1,x2,y2):
        import math
        cr = cairo.Context(surf)
        cr.set_source_rgb(*self.color); cr.set_line_width(self.brush)
        cx,cy = (x1+x2)/2, (y1+y2)/2
        rx,ry = abs(x2-x1)/2, abs(y2-y1)/2
        cr.save(); cr.translate(cx,cy); cr.scale(rx if rx else 1, ry if ry else 1)
        cr.arc(0,0,1,0,math.pi*2); cr.restore(); cr.stroke()
    def draw(self, w, cr):
        self.ensure_surface()
        cr.set_source_surface(self.surface, 0, 0); cr.paint()
    def clear(self, *_):
        if self.surface:
            cr = cairo.Context(self.surface); cr.set_source_rgb(1,1,1); cr.paint()
            self.da.queue_draw()
    def undo(self, *_):
        if self.backup:
            cr = cairo.Context(self.surface)
            cr.set_source_surface(self.backup, 0, 0); cr.paint()
            self.backup = None; self.da.queue_draw()
    def save(self, *_):
        dlg = Gtk.FileChooserDialog(title='Save PNG', parent=self,
            action=Gtk.FileChooserAction.SAVE)
        dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                        Gtk.STOCK_SAVE, Gtk.ResponseType.OK)
        dlg.set_current_name('drawing.png')
        if dlg.run() == Gtk.ResponseType.OK and self.surface:
            self.surface.write_to_png(dlg.get_filename())
        dlg.destroy()
    def open_img(self, *_):
        dlg = Gtk.FileChooserDialog(title='Open Image', parent=self,
            action=Gtk.FileChooserAction.OPEN)
        dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                        Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
        if dlg.run() == Gtk.ResponseType.OK:
            path = dlg.get_filename()
            try:
                pb = GdkPixbuf.Pixbuf.new_from_file(path)
                alloc = self.da.get_allocation()
                self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, alloc.width, alloc.height)
                cr = cairo.Context(self.surface); cr.set_source_rgb(1,1,1); cr.paint()
                Gdk.cairo_set_source_pixbuf(cr, pb, 0, 0); cr.paint()
                self.da.queue_draw()
            except Exception as e:
                pass
        dlg.destroy()

win = App()
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()
