#!/usr/bin/python
# python file encoding: utf-8

#
# This program is Copyright © 2008 Ryan Lortie <desrt@desrt.ca>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of version 3 of the GNU General Public License.
#

import gobject
import cairo
import gtk

# pygobject has set_application_name, but not get_?
def get_application_name ():
  return gtk.AboutDialog ().get_program_name ()

shapes = """
 .::    ::.              ::::::::::
 :xx:  :xx:              :xxxxxxxx:
 :xxx::xxx:              :xxxxxxxx:
  :xxxxxx:               :x::::::x:
   :xxxx:                :x:    :x:
   :xxxx:                :x:    :x:
  :xxxxxx:   ::::::::::  :x:    :x:
 :xxx::xxx:  :xxxxxxxx:  :x::::::x:
 :xx:  :xx:  :xxxxxxxx:  :xxxxxxxx:
 .::    ::.  ::::::::::  ::::::::::
"""

def get_shapes_images ():
  mapping = { ' ': '\x00\x00\x00\x00', '\n': '\x00\x00\x00\x00',
              ':': '\x00\x00\x00\xff', '.': '\x00\x00\x00\x80',
              'x': '\xff\xff\xff\xff' }

  data = ''.join (mapping[x] for x in shapes[1:])
  pixbuf = gtk.gdk.pixbuf_new_from_data (data, gtk.gdk.COLORSPACE_RGB,
                                         True, 8, 36, 10, 144)

  for x in [12, 24, 0]:
    image = gtk.Image ()
    image.set_from_pixbuf (pixbuf.subpixbuf (x, 0, 12, 10))

    yield image

class FrameWindow (gtk.Window):
  __gtype_name__ = 'FrameWindow'

  def __init__ (self):
    gtk.Window.__init__ (self)

    self.set_decorated (False)
    self.add_events (gtk.gdk.BUTTON_PRESS_MASK |
                     gtk.gdk.ENTER_NOTIFY_MASK |
                     gtk.gdk.LEAVE_NOTIFY_MASK |
                     gtk.gdk.POINTER_MOTION_MASK)
    self.connect ('button-press-event', self.on_button_press_event)
    self.cook_title_area ()
    self.cook_status_area ()
    self.window_border = 5
    self.connect ('notify::title', self.update_title)
    self.connect ('notify::icon-name', self.update_icon)
    self.update_title (self, None)
    self.set_colormap (self.get_screen ().get_rgba_colormap ())

  def min_button_clicked (self, btn):
    self.iconify ()

  def max_button_clicked (self, btn):
    self.is_max = not self.is_max

    if self.is_max:
      self.maximize ()
    else:
      self.unmaximize ()

  def close_button_clicked (self, btn):
    self.destroy ()

  def cook_title_area (self):
    self.title_area = gtk.HBox ()
    self.icon = gtk.Image ()
    self.icon.set_pixel_size (48)
    self.title_label = gtk.Label ()
    self.title_label.set_alignment (0, 0)
    self.buttonbox = gtk.HBox ()
    self.btnalign = gtk.Alignment ()

    self.title_area.pack_start (self.icon, False)
    self.title_area.pack_start (self.title_label, True, True, 10)
    self.title_area.pack_end (self.btnalign, False)
    self.btnalign.add (self.buttonbox)

    buttons = []
    for image in get_shapes_images ():
      button = gtk.Button ()
      button.set_focus_on_click (False)
      button.set_relief (gtk.RELIEF_NONE)
      button.add (image)
      self.buttonbox.add (button)
      buttons.append (button)

    self.is_max = False
    buttons[0].connect ('clicked', self.min_button_clicked)
    buttons[1].connect ('clicked', self.max_button_clicked)
    buttons[2].connect ('clicked', self.close_button_clicked)

    self.title_area.show_all ()
    self.title_area.set_parent (self)

  def cook_status_area (self):
    self.status_area = gtk.Statusbar ()
    self.status_area.show_all ()
    self.status_area.set_parent (self)

  def update_title (self, self2, pspec):
    title = self.get_title ()
    if not title:
      title = "(untitled)"

    application = get_application_name ()
    self.title_label.set_markup ('<b><big><span foreground="white">' +
                                   gobject.markup_escape_text (title) +
                                 '</span></big></b>\n'
                                 '<i><span foreground="#8af">' +
                                   gobject.markup_escape_text (application) +
                                 '</span></i>')

  def update_icon (self, self2, pspec):
    name = self.get_icon_name ()
    self.icon.set_from_icon_name (name, 32)

  def classify (self, x, y, size):
    if self.allocation.width < size * 2 and x > self.allocation.width / 2:
      x = size * 2

    if self.allocation.height < size * 2 and y > self.allocation.height / 2:
      y = size * 2

    if 0 <= x and x < size:
      x = -1
    elif x >= self.allocation.width - size:
      x = 1
    else:
      x = 0

    if 0 <= y and y < size:
      y = -1
    elif y >= self.allocation.height - size:
      y = 1
    else:
      y = 0

    if x < 0 and y < 0:
      return (gtk.gdk.WINDOW_EDGE_NORTH_WEST, gtk.gdk.TOP_LEFT_CORNER)
    elif x < 0 and y == 0:
      return (gtk.gdk.WINDOW_EDGE_WEST, gtk.gdk.LEFT_SIDE)
    elif x < 0:
      return (gtk.gdk.WINDOW_EDGE_SOUTH_WEST, gtk.gdk.BOTTOM_LEFT_CORNER)

    elif x == 0 and y < 0:
      return (gtk.gdk.WINDOW_EDGE_NORTH, gtk.gdk.TOP_SIDE)
    elif x == 0 and y == 0:
      return (None, None)
    elif x == 0:
      return (gtk.gdk.WINDOW_EDGE_SOUTH, gtk.gdk.BOTTOM_SIDE)

    elif y < 0:
      return (gtk.gdk.WINDOW_EDGE_NORTH_EAST, gtk.gdk.TOP_RIGHT_CORNER)
    elif y == 0:
      return (gtk.gdk.WINDOW_EDGE_EAST, gtk.gdk.RIGHT_SIDE)
    else:
      return (gtk.gdk.WINDOW_EDGE_SOUTH_EAST, gtk.gdk.BOTTOM_RIGHT_CORNER)

  def update_cursor (self, event):
    (edge, cursor) = self.classify (event.x, event.y, 10)
    if cursor != None:
      self.window.set_cursor (gtk.gdk.Cursor (cursor))
    else:
      self.window.set_cursor (None)

  def do_enter_notify_event (self, event):
    self.update_cursor (event)

  def do_motion_notify_event (self, event):
    self.update_cursor (event)

  def do_leave_notify_event (self, event):
    self.window.set_cursor (None)

  def do_map (self):
    gtk.Window.do_map (self)
    self.title_area.map ()
    self.status_area.map ()

  def do_realize (self):
    gtk.Window.do_realize (self)
    self.title_area.realize ()
    self.status_area.realize ()

  def do_expose_event (self, event):
    cr = self.window.cairo_create ()
    cr.set_operator (cairo.OPERATOR_SOURCE)
    cr.set_source_rgba (0, 0, 0, 0)
    cr.paint ()

    cr.set_source_rgba (0.3, 0.4, 0.7, 0.9)
    cr.rectangle ((2, 2,
                   self.allocation.width - 4,
                   self.allocation.height - 4))
    cr.fill ()

    self.style.paint_shadow (self.window, self.state, gtk.SHADOW_OUT,
        event.area, self, '',
        0, 0, self.allocation.width, self.allocation.height)
    self.propagate_expose (self.title_area, event)
    self.propagate_expose (self.status_area, event)

    if self.child:
      self.propagate_expose (self.child, event)

  def do_forall (self, all, func, data):
    if all:
      func (self.title_area, data)

    if self.child:
      func (self.child, data)

  def do_size_request (self, requisition):
    (x1, y1) = self.title_area.size_request ()
    self.height_for_title = y1

    (x2, y2) = self.status_area.size_request ()
    self.height_for_status = y2

    if self.child:
      (x3, y3) = self.child.size_request ()
    else:
      (x3, y3) = (0, 0)

    requisition.width = max (x1, x2, x3) + self.window_border * 2
    requisition.height = y1 + y2 + y3 + self.window_border * 2

  def do_size_allocate (self, allocation):
    width = allocation.width - self.window_border * 2
    height = allocation.height - self.window_border * 2

    if self.height_for_title > height:
      self.height_for_title = height
    height = height - self.height_for_title

    if self.height_for_status > height:
      self.height_for_status = height
    height = height - self.height_for_status

    self.title_area.size_allocate ((self.window_border,
      self.window_border, width, self.height_for_title))
    self.status_area.size_allocate ((self.window_border,
      self.window_border + height + self.height_for_title, width,
        self.height_for_status))

    if self.child:
      self.child.size_allocate ((self.window_border,
        self.window_border + self.height_for_title, width, height))

  def on_button_press_event (self, self2, event):
    if event.button == 1:
      (edge, cursor) = self.classify (event.x, event.y, 10)

      if edge != None:
        self.begin_resize_drag (edge, event.button,
                                int (event.x_root), int (event.y_root),
                                event.time)
      else:
        self.begin_move_drag (event.button,
                              int (event.x_root), int (event.y_root),
                              event.time)

    if event.button == 2:
      (edge, cursor) = self.classify (event.x, event.y, 10000)

      if edge != None:
        self.begin_resize_drag (edge,
                                event.button,
                                int (event.x_root), int (event.y_root),
                                event.time)

class TextFileView (gtk.TextView):
  def __init__ (self, filename):
    buffer = gtk.TextBuffer ()

    self.file = open (filename, 'r+')
    buffer.set_text (self.file.read ())
    buffer.set_modified (False)

    gtk.TextView.__init__ (self, buffer)

    buffer.connect ('changed', self.buffer_changed)
    self.update_id = None
    self.buffer = buffer

  def save (self):
    print 'save'
    if self.update_id:
      # yay.  non-atomic update.  hope nothing bad happens :)
      self.file.seek (0)
      self.file.truncate ()
      self.file.write (self.buffer.get_text (self.buffer.get_start_iter (),
                                             self.buffer.get_end_iter ()))
      self.file.flush ()

    self.update_id = None
    return False

  def buffer_changed (self, buffer):
    if self.update_id:
      gobject.source_remove (self.update_id)
    self.update_id = gobject.timeout_add (2000, self.save)

class Document (FrameWindow):
  __gtype_name__ = 'Document'

  def __init__ (self, filename):
    FrameWindow.__init__ (self)

    self.set_icon_name ('accessories-text-editor')

    scrolled = gtk.ScrolledWindow ()
    scrolled.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    self.set_default_size (400, 300)
    self.view = TextFileView (filename)
    scrolled.add (self.view)
    self.add (scrolled)

    self.set_title (gobject.filename_display_basename (filename))
    self.show_all ()

  def do_destroy (self):
    self.view.save ()
    exit ()

import sys

if len (sys.argv) != 2:
  print 'give a document name to edit'
  print 'WARNING: do not use this with important files :)'
  exit ()

gobject.set_application_name ('icanhasedit')
d = Document (sys.argv[1])
gtk.main ()
