#!/usr/bin/python

__version__ = "1.0.9-1"

__license__ = """
Copyright (c) 2005-2006,
  Bill McCloskey    <bill.mccloskey@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The names of the contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""

import pygtk
pygtk.require('2.0')
import gtk
import gtk.gdk
import gobject

try:
    import hildon
    has_hildon = True
except:
    has_hildon = False

try:
    import osso
    has_osso = True
except:
    has_osso = False

try:
    import gnomeprint
    import gnomeprint.ui
    has_print = True
except:
    has_print = False

import pango
import sys
import time
import os, os.path
import md5
import pickle
import ConfigParser

HOME_PATH = os.sep+'usr'+os.sep+'share'+os.sep+'xword'
if os.path.exists('data'+os.sep+'Left.xpm'): HOME_PATH = 'data'

if has_hildon:
    try:
        gtk.rc_parse(HOME_PATH + os.sep + 'xword.gtkrc')
    except:
        pass

MIN_BOX_SIZE = 12 
MAX_BOX_SIZE = 40

ACROSS = 0
DOWN = 1

NO_ERROR = 0
MISTAKE = 1
FIXED_MISTAKE = 2
CHEAT = 3

CHK_LTR_BTN = '  Check  \n  Letter'
CHK_WRD_BTN = '  Check  \n   Word'
SOL_LTR_BTN = '  Solve  \n  Letter'
SOL_WRD_BTN = '  Solve  \n   Word'

layouts = [
    ('Only Puzzle', 'puzzle'),
    ('Right Side', ('H', 'puzzle', 550, ('V', 'across', 250, 'down'))),
    ('Left Side', ('H', ('V', 'across', 250, 'down'), 200, 'puzzle')),
    ('Left and Right', ('H', ('H', 'across', 175, 'puzzle'), 725, 'down')),
    ('Top', ('V', ('H', 'across', 450, 'down'), 200, 'puzzle')),
    ('Bottom', ('V', 'puzzle', 400, ('H', 'across', 450, 'down'))),
    ('Top and Bottom', ('V', 'across', 150, ('V', 'puzzle', 300, 'down')))
    ]

def time_str(t):
    total = int(t)
    secs = total % 60
    mins = (total / 60) % 60
    hrs = (total / 3600)
    return "%d:%02d:%02d" % (hrs, mins, secs)

class BinaryFile:
    def __init__(self, filename=None):
        f = file(filename, 'rb')
        self.data = list(f.read())
        f.close()
        self.index = 0

    def save(self, filename):
        f = file(filename, 'wb+')
        f.write(''.join(self.data))
        f.close()

    def seek(self, pos):
        self.index = pos

    def write_char(self, c):
        self.data[self.index] = c
        self.index += 1

    def read_char(self):
        c = self.data[self.index]
        self.index += 1
        return c

    def read_chars(self, n):
        c = []
        for i in range(n):
            c.append(self.read_char())
        return c

    def read_byte(self):
        return ord(self.read_char())

    def read_short(self):
        return self.read_byte() + 256 * self.read_byte()

    def read_string(self):
        if self.index == len(self.data): return ''
        s = ''
        c = self.read_char()
        while ord(c) is not 0 and self.index < len(self.data):
            s += c
            c = self.read_char()

        result = s
        ellipsis_char = 133
        result = result.replace(chr(ellipsis_char), '...')
        result = unicode(result, 'iso_8859-1')
        return result

    def hashcode(self):
        m = md5.new()
        m.update(''.join(self.data))
        return m.hexdigest()

class PersistentPuzzle:
    def __init__(self):
        self.responses = {}
        self.errors = {}
        self.clock = 0
        self.notes = ''

    def get_size(self, m):
        width = 0
        height = 0
        for (x, y) in m.keys():
            if x > width: width = x
            if y > height: height = y
        width += 1
        height += 1

        return (width, height)

    def to_binary(self):
        (width, height) = self.get_size(self.responses)
        bin1 = [' ']*width*height
        bin2 = [' ']*width*height

        for ((x, y), r) in self.responses.items():
            index = y * width + x
            bin1[index] = self.responses[x, y]
            if bin1[index] == '': bin1[index] = chr(0)

        for ((x, y), r) in self.errors.items():
            index = y * width + x
            bin2[index] = chr(self.errors[x, y])

        bin = ''.join(bin1 + bin2)
        return '%d %d %d %s %s%c' % (width, height, int(self.clock), bin, self.notes, chr(0))

    def get_int(self, s, pos):
        pos0 = pos
        while pos < len(s) and s[pos].isdigit(): pos += 1
        return (int(s[pos0:pos]), pos)

    def get_string(self, s, pos):
        str = ''
        while pos < len(s) and ord(s[pos]) is not 0:
            str += s[pos]
            pos += 1
        return (str, pos)

    def from_binary(self, bin):
        pos = 0
        (width, pos) = self.get_int(bin, pos)
        pos += 1
        (height, pos) = self.get_int(bin, pos)
        pos += 1
        (self.clock, pos) = self.get_int(bin, pos)
        pos += 1

        count = width*height
        bin1 = bin[pos:pos+count]
        pos += count
        bin2 = bin[pos:pos+count]
        pos += count

        pos += 1
        (self.notes, pos) = self.get_string(bin, pos)

        self.responses = {}
        self.errors = {}

        i = 0
        for y in range(height):
            for x in range(width):
                if bin1[i] == chr(0): self.responses[x, y] = ''
                else: self.responses[x, y] = bin1[i]
                self.errors[x, y] = ord(bin2[i])
                i += 1

class Puzzle:
    def __init__(self, filename):
        self.load_file(filename)

    def load_file(self, filename):
        f = BinaryFile(filename)
        self.f = f

        cksum = f.read_short()
        file_magic = f.read_string()
        assert(file_magic == "ACROSS&DOWN")

        cksum_cib = f.read_short()
        magic_10 = f.read_chars(4)
        magic_14 = f.read_chars(4)
        # most puzzles are v 1.2, cryptic puzzles are 1.3
        puz_version = f.read_string()

        f.seek(0x2c)
        self.width = f.read_byte()
        self.height = f.read_byte()
        self.clue_count = f.read_short()

        f.seek(0x34)
        self.answers = {}
        self.errors = {}
        for y in range(self.height):
            for x in range(self.width):
                self.answers[x, y] = f.read_char()
                self.errors[x, y] = NO_ERROR

        self.responses = {}
        for y in range(self.height):
            for x in range(self.width):
                c = f.read_char()
                if c == '-': c = ''
                self.responses[x, y] = c

        def massage(s):
            # skips unprintable characters
            snew = ''
            for c in s:
                if ord(c) >= ord(' ') and ord(c) <= ord('~'): snew += c
            return snew

        self.title = massage(f.read_string())
        self.author = massage(f.read_string())
        self.copyright = massage(f.read_string())

        self.clues = []
        for n in range(self.clue_count):
            self.clues.append(f.read_string())

        self.all_clues = self.clues[:]

        self.notes = f.read_string()

        self.setup()

    def setup(self):
        self.across_clues = {}
        self.down_clues = {}
        self.across_map = {}
        self.down_map = {}
        self.number_map = {}
        self.number_rev_map = {}
        self.mode_maps = [self.across_map, self.down_map]
        self.mode_clues = [self.across_clues, self.down_clues]
        self.is_across = {}
        self.is_down = {}
        number = 1
        used_number = False
        for y in range(self.height):
            for x in range(self.width):
                is_fresh_x = self.is_black(x-1, y)
                is_fresh_y = self.is_black(x, y-1)

                if not self.is_black(x, y):
                    if is_fresh_x:
                        if self.is_black(x+1, y):
                            self.across_map[x, y] = 0
                        else:
                            self.across_map[x, y] = number
                            self.across_clues[number] = self.clues.pop(0)
                            used_number = True
                    else: self.across_map[x, y] = self.across_map[x-1, y]

                    if is_fresh_y:
                        if self.is_black(x, y+1): # see April 30, 2006 puzzle
                            self.down_map[x, y] = 0
                        else:
                            self.down_map[x, y] = number
                            self.down_clues[number] = self.clues.pop(0)
                            used_number = True
                    else: self.down_map[x, y] = self.down_map[x, y-1]

                    if used_number:
                        self.is_across[number] = is_fresh_x
                        self.is_down[number] = is_fresh_y
                        self.number_map[number] = (x, y)
                        self.number_rev_map[x, y] = number
                        number += 1
                        used_number = False
                else:
                    self.across_map[x, y] = 0
                    self.down_map[x, y] = 0
        self.max_number = number-1

    def hashcode(self):
        (width, height) = (self.width, self.height)

        data = [' ']*width*height
        for ((x, y), r) in self.responses.items():
            index = y * width + x
            if r == '.': data[index] = '1'
            else: data[index] = '0'

        s1 = ''.join(data)
        s2 = ';'.join(self.all_clues)

        m = md5.new()
        m.update(s1 + s2)
        return m.hexdigest()

    def save(self, fname):
        f = self.f
        f.seek(0x34 + self.width * self.height)
        for y in range(self.height):
            for x in range(self.width):
                c = self.responses[x, y]
                if c == '': c = '-'
                f.write_char(c)
        f.save(fname)

    def is_black(self, x, y):
        return self.responses.get((x, y), '.') == '.'

    def clue(self, x, y, mode):
        if mode is ACROSS: return self.across_clues[self.across_map[x, y]]
        if mode is DOWN: return self.down_clues[self.down_map[x, y]]

    def number(self, x, y, mode):
        return self.mode_maps[mode][x, y]

    def next_cell(self, x, y, mode, incr, skip_black):
        (xo, yo) = (x, y)
        while True:
            if mode is ACROSS:
                if x+incr < 0 or x+incr >= self.width: return ((x, y), True)
                x += incr
            else:
                if y+incr < 0 or y+incr >= self.height: return ((x, y), True)
                y += incr

            if not skip_black or not self.is_black(x, y): break
            (xo, yo) = (x, y)

        if self.is_black(x, y): return ((xo, yo), True)
        else: return ((x, y), False)

    def find_blank_cell_recursive(self, x, y, mode, incr):
        if self.responses[x, y] == '' or self.errors[x, y] == MISTAKE:
            return (x, y)
        else:
            ((x, y), hit) = self.next_cell(x, y, mode, incr, False)
            if hit: return None
            else: return self.find_blank_cell_recursive(x, y, mode, incr)

    def find_blank_cell(self, x, y, mode, incr):
        r = self.find_blank_cell_recursive(x, y, mode, incr)
        if r == None: return (x, y)
        else: return r

    def is_cell_correct(self, x, y):
        return self.responses[x, y] == self.answers[x, y]

    def is_puzzle_correct(self):
        for x in range(self.width):
            for y in range(self.height):
                if not self.is_black(x, y) and not self.is_cell_correct(x, y):
                    return False
        return True

    def incr_number(self, x, y, mode, incr):
        n = self.mode_maps[mode][x, y]
        while True:
            n += incr
            if not self.number_map.has_key(n): return 0
            if mode == ACROSS and self.is_across[n]: break
            if mode == DOWN and self.is_down[n]: break
        return n

    def final_number(self, mode):
        n = self.max_number
        while True:
            if mode == ACROSS and self.is_across[n]: break
            if mode == DOWN and self.is_down[n]: break
            n -= 1
        return n

class PrintFont:
    def __init__(self, family, style, size):
        self.face = gnomeprint.font_face_find_from_family_and_style(family,
                                                                    style)
        self.font = gnomeprint.font_find_closest(self.face.get_name(), size)
        self.size = size

    def measure_text(self, s):
        w = 0.0
        for c in s:
            glyph = self.face.lookup_default(ord(c))
            w += self.face.get_glyph_width(glyph) * 0.001 *self.font.get_size()
        return w

class ClueArea:
    def __init__(self, puzzle, font_size, col_height_fun):
        self.puzzle = puzzle

        self.clue_font = PrintFont('Serif', 'Regular', font_size)
        self.label_font = PrintFont('Serif', 'Bold', font_size)

        spacer = 'This is the width of a column'
        self.col_width = self.clue_font.measure_text(spacer)
        self.num_width = self.clue_font.measure_text('100. ')
        self.text_width = (self.col_width - self.num_width) * 0.9

        self.col_height_fun = col_height_fun
        self.col_num = 0
        
        self.y = self.col_height_fun(0, self.col_width) - self.clue_font.size
        self.x = self.num_width

        self.items = []
        self.group_start = None
        self.setup()

    def add_item(self, x, y, font, text):
        self.items = self.items + [(x, y, font, text)]

    def nextcol(self):
        self.col_num += 1
        self.x += self.col_width

        x = self.x - self.num_width
        h = self.col_height_fun(x, x + self.col_width)
        self.y = h - self.clue_font.size

    def open_group(self):
        self.group_start = self.items
        if self.y < 0: self.nextcol()

    def close_group(self):
        if self.y < 0:
            self.items = self.group_start
            return False
        else:
            return True

    def draw(self, gpc, x0, y0):
        for (x, y, font, text) in self.items:
            gpc.setfont(font.font)
            gpc.moveto(x + x0, y + y0)
            gpc.show(text)

    def add_wrapped_text(self, width, font, text):
        words = text.split(' ')
        lines = []
        while len(words) > 0:
            w = 0.0
            line = []
            while len(words) > 0 and w < width:
                if len(line) > 0:
                    w += font.measure_text(' ')
                word = words.pop(0)
                line.append(word)
                w += font.measure_text(word)

            if w >= width and len(line) == 1:
                i = 0
                w = 0.0
                word = line[0]
                while True:
                    w += font.measure_text(word[i])
                    if w > width: break
                    i += 1
                line = [word[:i]]
                words = [word[i:]] + words
            elif w >= width:
                words = [line.pop()] + words
            lines.append(line)

        for line in lines:
            s = ' '.join(line)
            self.add_item(self.x, self.y, font, s)
            self.y -= font.size

    def add_space(self, pct):
        self.y -= self.clue_font.size * pct

    def add_label(self, label):
        start = self.x
        stop = self.x + self.text_width
        w = self.label_font.measure_text(label)
        x0 = start + (stop - start - w)/2

        self.add_item(x0, self.y, self.label_font, label)
        self.y -= self.label_font.size

    def add_column(self, name, mode):
        first = True
        for n in range(1, self.puzzle.max_number+1):
            m = self.puzzle.mode_clues[mode]
            if m.has_key(n):
                clue = m[n]
                num = '%d. ' % n
                nw = self.clue_font.measure_text(num)

                while True:
                    self.open_group()
                    if first:
                        self.add_label(name)
                        self.add_space(1.0)
                        
                    self.add_item(self.x - nw, self.y, self.clue_font, num)
                    self.add_wrapped_text(self.text_width,self.clue_font, clue)

                    if self.close_group(): break

                self.add_space(0.5)
                if first: first = False

    def setup(self):
        self.add_column('Across', ACROSS)
        self.add_space(1.0)
        self.add_column('Down', DOWN)

    def width(self):
        return (self.col_num + 1) * self.col_width

class PuzzlePrinter:
    def __init__(self, puzzle):
        self.puzzle = puzzle

    def draw_banner(self, r):
        (left, bottom, right, top) = r

        h = top - bottom
        size = int(h * 0.7)
        font = PrintFont('Serif', 'Regular', size)

        self.gpc.setfont(font.font)
        width = font.measure_text(self.puzzle.title)
        x0 = left + (right - left - width)/2
        y0 = top - size
        self.gpc.moveto(x0, y0)
        self.gpc.show(self.puzzle.title)

    def draw_box(self, x, y, r):
        (left, bottom, right, top) = r
        gpc = self.gpc

        gpc.rect_stroked(left, bottom, (right-left), (top-bottom))
        if self.puzzle.is_black(x, y):
            gpc.rect_filled(left, bottom, (right-left), (top-bottom))

        if self.puzzle.number_rev_map.has_key((x, y)):
            gpc.setfont(self.num_font.font)
            n = self.puzzle.number_rev_map[x, y]
            gpc.moveto(left + self.box_size*0.05, top - self.box_size*0.35)
            gpc.show(str(n))

        gpc.setfont(self.let_font.font)
        w = self.let_font.measure_text(self.puzzle.responses[x, y])
        x0 = left + (right - left - w)/2
        gpc.moveto(x0, bottom + self.box_size*0.2)
        gpc.show(self.puzzle.responses[x, y])

        if self.puzzle.errors[x, y] != NO_ERROR:
            gpc.moveto(right - self.box_size*0.3, top)
            gpc.lineto(right, top)
            gpc.lineto(right, top - self.box_size*0.3)
            gpc.fill()

    def min_puzzle_size(self, r):
        puzzle = self.puzzle
        (left, bottom, right, top) = r

        self.banner_size = 18

        bw = (right - left)/float(puzzle.width)
        bh = (top - bottom - self.banner_size)/float(puzzle.height)
        box_size = int(min(bw, bh))
        self.box_size = box_size

        w = box_size * puzzle.width
        h = box_size * puzzle.height
        return (w, h + self.banner_size)

    def draw_puzzle(self, r):
        puzzle = self.puzzle
        box_size = self.box_size
        (left, bottom, right, top) = r

        w = box_size * puzzle.width
        h = box_size * puzzle.height

        banner_box = (left, top - self.banner_size, right, top)
        self.draw_banner(banner_box)

        left += ((right - left) - w)/2
        top -= self.banner_size

        self.num_font = PrintFont('Sans', 'Regular', box_size * 0.3)
        self.let_font = PrintFont('Sans', 'Regular', box_size * 0.6)

        for y in range(puzzle.height):
            for x in range(puzzle.width):
                r = (left + x*box_size,
                     top - (y+1)*box_size,
                     left + (x+1)*box_size,
                     top - y*box_size)
                self.draw_box(x, y, r)

    def draw_clues(self, r, coltop):
        (left, bottom, right, top) = r

        maxw = right - left

        def coltoprel(x0, x1):
            return coltop(x0 + left, x1 + left) - bottom

        size = 12
        while True:
            area = ClueArea(self.puzzle, size, coltoprel)
            w = area.width()
            if w <= maxw: break
            size -= 1

        area.draw(self.gpc, left, bottom)
        return area.col_width

    def units(self, length):
        i = 0
        while i < len(length) and (length[i].isdigit() or length[i] == '.'):
            i += 1
        num = length[:i].strip()
        units = length[i:].strip()

        if units == '': return float(num)

        u = gnomeprint.unit_get_by_abbreviation(units)
        if u == None:
            print 'Bad unit:', length
            return 0.0
        return float(num) * u.unittobase
        
    def draw(self, config):
        w = self.units(config.get(gnomeprint.KEY_PAPER_WIDTH))
        h = self.units(config.get(gnomeprint.KEY_PAPER_HEIGHT))
        
        left = self.units(config.get(gnomeprint.KEY_PAGE_MARGIN_LEFT))
        top = self.units(config.get(gnomeprint.KEY_PAGE_MARGIN_TOP))
        right = self.units(config.get(gnomeprint.KEY_PAGE_MARGIN_RIGHT))
        bottom = self.units(config.get(gnomeprint.KEY_PAGE_MARGIN_BOTTOM))

        if config.get(gnomeprint.KEY_PAGE_ORIENTATION) == 'R90':
            (w, h) = (h, w)
            (left, bottom, right, top) = (bottom, left, top, right)

        right = w - right
        top = h - top
        banner_size = 14

        self.gpc.beginpage("1")

        #self.gpc.rect_stroked(left, bottom, right-left, top-bottom)

        if h > w:
            mid = (top + bottom) / 2
            r = (left, mid, right, top)
        else:
            mid = (left + right)/2
            r = (left, bottom, mid, top)

        (w, h) = self.min_puzzle_size(r)
        h += 0.05 * h

        def coltop(x0, x1):
            if ((x0 >= left and x0 <= left+w) or (x1 >= left and x1 <= left+w)
                or (x0 <= left and x1 >= left+w)):
                return top - h
            else:
                return top
            
        fullr = (left, bottom, right, top)
        col_width = self.draw_clues(fullr, coltop)

        w = int((w+col_width-1)/col_width) * col_width
        r = (r[0], r[1], left+w, r[3])
        self.draw_puzzle(r)

        self.gpc.showpage()

    def do_preview(self, config, dialog):
        job = gnomeprint.Job(config)
        self.gpc = job.get_context()
        job.close()
        self.draw(config)
        w = gnomeprint.ui.JobPreview(job, 'Print Preview')
        w.set_property('allow-grow', 1)
        w.set_property('allow-shrink', 1)
        w.set_transient_for(dialog)
        w.show_all()

    def do_print(self, dialog, res, job):
        config = job.get_config()

        if res == gnomeprint.ui.DIALOG_RESPONSE_CANCEL:
            dialog.destroy()
        elif res == gnomeprint.ui.DIALOG_RESPONSE_PREVIEW:
            self.do_preview(config, dialog)
        elif res == gnomeprint.ui.DIALOG_RESPONSE_PRINT:
            dialog.destroy()
            self.gpc = job.get_context()
            self.draw(config)
            job.close()
            job.print_()

    def print_file(self, win):
        job = gnomeprint.Job(gnomeprint.config_default())
        dialog = gnomeprint.ui.Dialog(job, "Print...", 0)
        dialog.connect('response', self.do_print, job)
        dialog.set_transient_for(win)
        dialog.show()

class PuzzleWidget:
    def __init__(self, puzzle, control, min_box_size=20):
        self.puzzle = puzzle
        self.control = control
        self.min_box_size = min_box_size
        
        self.area = gtk.DrawingArea()
        self.pango = self.area.create_pango_layout('')
        self.area.connect('expose-event', self.expose_event)
        self.area.connect('configure-event', self.configure_event)
        self.area.set_flags(gtk.CAN_FOCUS)

        self.sw = gtk.ScrolledWindow()
        self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        viewport = gtk.Viewport()
        viewport.add(self.area)
        viewport.set_shadow_type(gtk.SHADOW_NONE)
        self.sw.add(viewport)

        self.widget = self.sw
        self.set_puzzle(puzzle, control)

    def set_puzzle(self, puzzle, control):
        self.puzzle = puzzle
        self.control = control

        if puzzle:
            width = puzzle.width * self.min_box_size
            height = puzzle.height * self.min_box_size
            self.area.set_size_request(width, height)
        else:
            self.box_size = self.min_box_size

        self.area.queue_draw_area(0, 0, 32768, 32768)

    def configure_event(self, area, event):
        width, height = event.width, event.height

        if self.puzzle:
            bw = width / self.puzzle.width
            bh = height / self.puzzle.height
            self.box_size = min(bw, bh)
            
            self.width = self.box_size * self.puzzle.width
            self.height = self.box_size * self.puzzle.height
            
            self.x = (width - self.width) / 2
            self.y = (height - self.height) / 2
        else:
            self.width = width
            self.height = height
            self.x = 0
            self.y = 0

    def expose_event(self, area, event):
        if self.puzzle: self.draw_puzzle()
        else: self.draw_empty()

    def draw_empty(self):
        pass

    def draw_puzzle(self):
        view = self.area.window
        cm = view.get_colormap()
        self.white = cm.alloc_color('white')
        self.black = cm.alloc_color('black')
        self.red = cm.alloc_color('red')
        self.gray = cm.alloc_color('LightGray')

        num_size = int(self.box_size * 0.25)
        let_size = int(self.box_size * 0.45)
        self.num_font = pango.FontDescription('Sans %d' % num_size)
        self.let_font = pango.FontDescription('Sans %d' % let_size)

        self.gc = view.new_gc(foreground = self.white, background = self.white)
        view.draw_rectangle(self.gc, True, self.x, self.y,
                            self.width, self.height)

        self.gc.set_foreground(self.black)
        view.draw_rectangle(self.gc, False, self.x, self.y,
                            self.width, self.height)

        for y in range(self.puzzle.height):
            for x in range(self.puzzle.width):
                self.draw_box(x, y)
        
        return True

    def draw_triangle(self, x0, y0, color, filled):
        view = self.area.window

        self.gc.set_foreground(color)
        length = int(self.box_size * 0.3)
        view.draw_polygon(self.gc, filled,
                          [(x0 + self.box_size - length, y0),
                           (x0 + self.box_size, y0),
                           (x0 + self.box_size, y0 + length)])
        self.gc.set_foreground(self.black)

    def draw_box_data(self, x0, y0, n, letter, error):
        view = self.area.window

        self.pango.set_font_description(self.num_font)
        self.pango.set_text(n)
        view.draw_layout(self.gc, int(x0 + self.box_size*0.08), y0, self.pango)

        self.pango.set_font_description(self.let_font)
        self.pango.set_text(letter)
        (w, h) = self.pango.get_pixel_size()
        x1 = int(x0 + (self.box_size - w) / 2)
        y1 = int(y0 + self.box_size * 0.3)
        view.draw_layout(self.gc, x1, y1, self.pango)

        if error == MISTAKE:
            view.draw_line(self.gc, x0, y0,
                           x0 + self.box_size, y0 + self.box_size)
            view.draw_line(self.gc, x0, y0 + self.box_size,
                           x0 + self.box_size, y0)
        elif error == FIXED_MISTAKE:
            self.draw_triangle(x0, y0, self.black, True)
        elif error == CHEAT:
            self.draw_triangle(x0, y0, self.red, True)
            self.draw_triangle(x0, y0, self.black, False)

    def draw_box(self, x, y):
        view = self.area.window

        x0 = self.x + x*self.box_size
        y0 = self.y + y*self.box_size

        if self.control.is_main_selection(x, y): color = self.red
        elif self.control.is_selected(x, y): color = self.gray
        elif self.puzzle.is_black(x, y): color = self.black
        else: color = self.white

        self.gc.set_foreground(color)
        view.draw_rectangle(self.gc, True, x0, y0,
                            self.box_size, self.box_size)

        self.gc.set_foreground(self.black)
        view.draw_rectangle(self.gc, False, x0, y0,
                            self.box_size, self.box_size)

        letter = self.puzzle.responses[x, y]
        error = self.puzzle.errors[x, y]
        
        if self.puzzle.number_rev_map.has_key((x, y)):
            n = str(self.puzzle.number_rev_map[x, y])
        else:
            n = ''

        self.draw_box_data(x0, y0, n, letter, error)

    def translate_position(self, x, y):
        x -= self.x
        y -= self.y
        return (int(x / self.box_size), int(y / self.box_size))

    def update(self, x, y):
        x0 = self.x + x*self.box_size
        y0 = self.y + y*self.box_size
        self.area.queue_draw_area(x0, y0, self.box_size, self.box_size)

class ClueWidget:
    def __init__(self, control):
        self.control = control

        width = 0
        height = 22

        self.area = gtk.DrawingArea()
        self.pango = self.area.create_pango_layout('')
        self.area.set_size_request(width, height)
        self.area.connect('expose-event', self.expose_event)
        self.area.connect('configure-event', self.configure_event)

        self.widget = self.area

    def set_controller(self, control):
        self.control = control
        self.update()

    def configure_event(self, area, event):
        self.width, self.height = event.width, event.height
        self.pango.set_width(self.width * pango.SCALE)
        
    def expose_event(self, area, event):
        view = self.area.window
        cm = view.get_colormap()
        self.black = cm.alloc_color('black')
        self.gc = view.new_gc(foreground = self.black)
        
        size = 14
        while True:
            font = pango.FontDescription('Sans %d' % size)
            self.pango.set_font_description(font)
            self.pango.set_text(self.control.get_selected_word())
            w, h = self.pango.get_pixel_size()

            if h <= self.height: break
            size -= 1

        x = (self.width - w) / 2
        y = (self.height - h) / 2
        view.draw_layout(self.gc, x, y, self.pango)

    def update(self):
        self.area.queue_draw_area(0, 0, self.width, self.height)

class PuzzleController:
    def __init__(self, puzzle):
        self.puzzle = puzzle

        self.handlers = []
        self.selection = []

        self.mode = ACROSS
        (x, y) = (0, 0)
        if puzzle.is_black(x, y):
            ((x, y), _) = puzzle.next_cell(0, 0, ACROSS, 1, True)
        self.move_to(x, y)

    def connect(self, ev, handler):
        self.handlers.append((ev, handler))

    def do_update(self, signal_ev, *args):
        for (ev, h) in self.handlers:
            if ev == signal_ev: h(*args)

    def signal(self):
        self.move_to(self.x, self.y)

    def get_selection(self):
        x, y, mode = self.x, self.y, self.mode

        sel = []
        if mode is ACROSS:
            index = x
            while not self.puzzle.is_black(index, y):
                sel.append((index, y))
                index -= 1
            index = x+1
            while not self.puzzle.is_black(index, y):
                sel.append((index, y))
                index += 1
        else:
            index = y
            while not self.puzzle.is_black(x, index):
                sel.append((x, index))
                index -= 1
            index = y+1
            while not self.puzzle.is_black(x, index):
                sel.append((x, index))
                index += 1
        return sel

    def switch_mode(self):
        if self.mode is ACROSS:
            if not self.puzzle.down_map[self.x, self.y]:
                return
        if self.mode is DOWN:
            if not self.puzzle.across_map[self.x, self.y]:
                return

        self.mode = 1-self.mode

        old_sel = self.selection
        self.selection = self.get_selection()

        for (x, y) in old_sel + self.selection:
            self.do_update('box-update', x, y)

        self.do_update('title-update')

    def move_to(self, x, y):
        if not self.puzzle.is_black(x, y):
            self.x = x
            self.y = y

            if self.mode is ACROSS:
                if not self.puzzle.across_map[self.x, self.y]:
                    self.switch_mode()
            if self.mode is DOWN:
                if not self.puzzle.down_map[self.x, self.y]:
                    self.switch_mode()

            old_sel = self.selection
            self.selection = self.get_selection()

            for (xp, yp) in old_sel + self.selection:
                self.do_update('box-update', xp, yp)

            self.do_update('title-update')
            self.do_update('across-update', self.puzzle.number(x, y, ACROSS))
            self.do_update('down-update', self.puzzle.number(x, y, DOWN))

    def select_word(self, mode, n):
        if mode <> self.mode: self.switch_mode()
        (x, y) = self.puzzle.number_map[n]
        (x, y) = self.puzzle.find_blank_cell(x, y, mode, 1)
        self.move_to(x, y)

    def set_letter(self, letter):
        self.puzzle.responses[self.x, self.y] = letter
        if self.puzzle.errors[self.x, self.y] == MISTAKE:
            self.puzzle.errors[self.x, self.y] = FIXED_MISTAKE
            
        self.do_update('box-update', self.x, self.y)

        if self.puzzle.is_puzzle_correct():
            self.do_update('puzzle-finished')

    def erase_letter(self):
        self.set_letter('')

    def move(self, dir, amt, skip_black=True):
        if self.mode == dir:
            ((x, y), _) = self.puzzle.next_cell(self.x, self.y,
                                                self.mode, amt, skip_black)
            self.move_to(x, y)
        else:
            self.switch_mode()

    def back_space(self):
        if self.puzzle.responses[self.x, self.y] == '':
            self.move(self.mode, -1, False)
            self.erase_letter()
        else:
            self.erase_letter()

    def next_word(self, incr):
        n = self.puzzle.incr_number(self.x, self.y, self.mode, incr)
        if n == 0:
            self.switch_mode()
            if incr == 1: n = 1
            else: n = self.puzzle.final_number(self.mode)
        (x, y) = self.puzzle.number_map[n]
        (x, y) = self.puzzle.find_blank_cell(x, y, self.mode, 1)
        self.move_to(x, y)

    def input_char(self, skip_filled, c):
        c = c.upper()
        self.set_letter(c)
        ((x, y), hit) = self.puzzle.next_cell(self.x, self.y,
                                              self.mode, 1, False)
        if skip_filled:
            (x, y) = self.puzzle.find_blank_cell(x, y, self.mode, 1)

        self.move_to(x, y)

    def check_letter(self):
        correct = True
        if not self.puzzle.is_cell_correct(self.x,self.y):
            if self.puzzle.responses[self.x,self.y] <> '':
                self.puzzle.errors[self.x,self.y] = MISTAKE
                correct = False
                self.do_update('box-update', self.x, self.y)
        self.do_update('check-letter-result', correct)

    def check_word(self):
        correct = True
        for (x, y) in self.selection:
            if not self.puzzle.is_cell_correct(x, y):
                if self.puzzle.responses[x, y] <> '':
                    self.puzzle.errors[x, y] = MISTAKE
                    correct = False
                    self.do_update('box-update', x, y)
        self.do_update('check-word-result', correct)

    def check_puzzle(self):
        correct = True
        for (x, y) in self.puzzle.responses.keys():
            if not self.puzzle.is_cell_correct(x, y):
                if self.puzzle.responses[x, y] <> '':
                    self.puzzle.errors[x, y] = MISTAKE
                    correct = False
                    self.do_update('box-update', x, y)
        self.do_update('check-puzzle-result', correct)

    def solve_letter(self):
        if not self.puzzle.is_cell_correct(self.x, self.y):
            self.puzzle.errors[self.x, self.y] = CHEAT
            self.puzzle.responses[self.x, self.y] = self.puzzle.answers[self.x, self.y]
            self.do_update('box-update', self.x, self.y)
        if self.puzzle.is_puzzle_correct():
            self.do_update('puzzle-finished')

    def solve_word(self):
        for (x, y) in self.selection:
            if not self.puzzle.is_cell_correct(x, y):
                self.puzzle.errors[x, y] = CHEAT
                self.puzzle.responses[x, y] = self.puzzle.answers[x, y]
                self.do_update('box-update', x, y)
        if self.puzzle.is_puzzle_correct():
            self.do_update('puzzle-finished')

    def solve_puzzle(self):
        for (x, y) in self.puzzle.responses.keys():
            if not self.puzzle.is_cell_correct(x, y):
                self.puzzle.errors[x, y] = CHEAT
                self.puzzle.responses[x, y] = self.puzzle.answers[x, y]
                self.do_update('box-update', x, y)
        if self.puzzle.is_puzzle_correct():
            self.do_update('puzzle-finished')

    def clear_letter(self):
        self.puzzle.errors[self.x, self.y] = NO_ERROR
        self.puzzle.responses[self.x, self.y] = ''
        self.do_update('box-update', self.x, self.y)

    def clear_word(self):
        for (x, y) in self.selection:
            self.puzzle.errors[x, y] = NO_ERROR
            self.puzzle.responses[x, y] = ''
            self.do_update('box-update', x, y)

    def is_selected(self, x, y):
        return ((x, y) in self.selection)

    def is_main_selection(self, x, y):
        return (x == self.x and y == self.y)

    def get_selected_word(self):
        return self.puzzle.clue(self.x, self.y, self.mode)

    def get_clues(self, mode):
        clues = []
        m = self.puzzle.mode_clues[mode]
        for n in range(1, self.puzzle.max_number+1):
            if m.has_key(n): clues.append((n, m[n]))
        return clues

class DummyController:
    def __init__(self):
        self.x = 0
        self.y = 0

    def connect(self, ev, handler):
        pass

    def signal(self):
        pass

    def switch_mode(self):
        pass

    def move_to(self, x, y):
        pass

    def select_word(self, mode, n):
        pass

    def set_letter(self, letter):
        pass

    def erase_letter(self):
        pass

    def move(self, dir, amt):
        pass

    def back_space(self):
        pass

    def next_word(self, incr):
        pass

    def input_char(self, skip_filled, c):
        pass

    def check_letter(self):
        pass

    def check_word(self):
        pass

    def check_puzzle(self):
        pass

    def solve_letter(self):
        pass

    def solve_word(self):
        pass

    def solve_puzzle(self):
        pass

    def clear_letter(self):
        pass

    def clear_word(self):
        pass

    def is_selected(self, x, y):
        return False

    def is_main_selection(self, x, y):
        return False

    def get_selected_word(self):
        return 'Welcome. Please open a puzzle.'

    def get_clues(self, mode):
        return []


class RecentList:
    def __init__(self):
        self.max_entries = 5
        self.recent_list = []

    def add(self,newentry):
        self.delete(newentry)
        self.recent_list.insert(0,newentry)
        if len(self.recent_list) > self.max_entries:
            del self.recent_list[-1]

    def delete(self,oldentry):
        if oldentry in self.recent_list:
            try:
                self.recent_list.remove(oldentry)
            except ValueError:
                pass

    def set(self, newlist=[]):
        self.recent_list = newlist


class PuzzleWindow:
    def __init__(self, puzzle):
        self.clock_time = 0.0
        self.clock_start = time.time()
        self.clock_running = False

        self.win = None
        self.set_puzzle(puzzle)

        if self.puzzle: self.control = PuzzleController(self.puzzle)
        else: self.control = DummyController()

        self.recent_menu = gtk.Menu()
        self.recent = RecentList()

        self.skip_filled = False
        self.layout = 0
        self.checks_position = 0
        self.keyboard_position = 0
        self.window_size = (800, 480)
        self.maximized = False
        self.fullscreen = False
        self.positions = layouts[self.layout][1]
        self.default_loc = None
        self.last_file = None
        self.min_box_size = 20

        title = 'Crossword Puzzle'
        if self.puzzle: title = 'Crossword Puzzle - %s' % self.puzzle.title
        
        if has_hildon:
            win = hildon.Window()
        else:
            win = gtk.Window()
        self.handler = win.connect('destroy', lambda w: self.exit())
        win.set_title(title)
        win.connect('size-allocate', self.resize_window)
        win.connect('window-state-event', self.state_event)
        self.win = win

        self.read_config()

        win.resize(self.window_size[0], self.window_size[1])
        if self.maximized: win.maximize()

        self.cur_layout = None
        self.letterbarV = None
        self.letterbarH = None

        menubox = gtk.VBox()
        self.menubar = self.create_menubar()
        menubox.pack_start(self.menubar, False, False, 0)
        self.update_recent_menu()

        self.check_popup = self.create_check_popup()
        self.solve_popup = self.create_solve_popup()

        win.add(menubox)

        self.box = gtk.HBox()
        menubox.pack_start(self.box, True, True, 0)

        self.toolbox = [gtk.HBox(),gtk.HBox(),gtk.VBox(),gtk.VBox()] # L,R,T,B

        self.vbox2 = gtk.VBox()
        self.box.pack_start(self.toolbox[0],False,False,0)
        self.box.pack_start(self.vbox2,True,True,0)
        self.box.pack_start(self.toolbox[1],False,False,0)

        self.clock_buttons = list()
        self.clock_buttons_handler_ids = list()
        self.toolbar = self.create_toolbar()
        self.letterbarV = self.create_letterbar(13,3,
            ['A','B','C','D','E','F','G','H','I','J','K','L','M',
             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
             'BackSpace','ISO_Left_Tab','Up','Tab','Left','Return',
             'Right','F12','Down','Escape','clock'])
        self.letterbarH = self.create_letterbar(3,13,
            ['Q','W','E','R','T','Y','U','I','O','P','ISO_Left_Tab','Up','Tab',
             'A','S','D','F','G','H','J','K','L','BackSpace','Left','Return','Right',
             'Z','X','C','V','B','N','M','clock','F12','Down','Escape'])

        self.create_widgets()
        self.setup_controller()

        self.vbox = gtk.VBox()
        self.vbox2.pack_start(self.toolbox[2],False,False,0)
        self.vbox2.pack_start(self.vbox,True,True, 0)
        self.vbox2.pack_start(self.toolbox[3],False,False,0)

        self.cur_layout = self.generate_layout(self.positions)
        self.vbox.pack_start(self.cur_layout, True, True, 0)

        self.set_toolbox_layout(self.checks_position,self.keyboard_position)

        gobject.timeout_add(500, self.idle_event)
        win.connect('key-press-event', self.key_event)

        if not self.puzzle: self.enable_controls(False)

        if has_hildon:
            menu = gtk.Menu()
            for child in self.menubar.get_children():
                child.reparent(menu)
            self.win.set_menu(menu)
            self.menubar.destroy()
        else:
            self.status_bar = gtk.Statusbar()
            menubox.pack_start(self.status_bar, False, False, 0)

        win.show_all()

        self.control.signal()
        self.puzzle_widget.area.grab_focus()

        if self.last_file and os.path.exists(self.last_file): 
            self.do_open_file(self.last_file)
        else:
            self.clock_running = False

        self.activate_clock(self.clock_running)

        if self.fullscreen:
            self.win.fullscreen()

    def enable_controls(self, enabled):
        def enable(w): w.set_property('sensitive', enabled)

        enable(self.menu_items['save'])
        enable(self.menu_items['close'])
        enable(self.menu_items['delete'])
        enable(self.menu_items['check'])
        enable(self.menu_items['solve'])
        enable(self.menu_items['clear'])
        enable(self.menu_items['properties'])
        enable(self.menu_items['notepad'])
        if has_print:
            enable(self.menu_items['print'])

        enable(self.toolbar_items[CHK_LTR_BTN])
        enable(self.toolbar_items[CHK_WRD_BTN])
        enable(self.toolbar_items[SOL_LTR_BTN])
        enable(self.toolbar_items[SOL_WRD_BTN])
        for index in range(len(self.clock_buttons)):
            enable(self.clock_buttons[index])

        for child in self.letterbarV.get_children():
            enable(child)
        for child in self.letterbarH.get_children():
            enable(child)

    def setup_controller(self):
        self.control.connect('puzzle-finished', self.puzzle_finished)
        self.control.connect('box-update', self.puzzle_widget.update)
        self.control.connect('title-update', self.clue_widget.update)
        self.control.connect('across-update', self.across_update)
        self.control.connect('down-update', self.down_update)
        self.control.connect('check-letter-result', self.check_result)
        self.control.connect('check-word-result', self.check_result)
        self.control.connect('check-puzzle-result', self.check_result)

    def do_open_file(self, fname, start_over = False):
        if start_over: 
            self.activate_clock(False)
        
        if self.puzzle: self.write_puzzle()

        self.clock_start = time.time()

        try:
            self.set_puzzle(Puzzle(fname), start_over)
        except:
            self.notify("%s could not be loaded" % fname)
            self.activate_clock(False)
            return
        self.last_file = fname
        self.recent.add(fname)
        self.update_recent_menu()
        self.control = PuzzleController(self.puzzle)
        self.setup_controller()
        self.clue_widget.set_controller(self.control)
        self.puzzle_widget.set_puzzle(self.puzzle, self.control)

        self.load_list(ACROSS)
        self.load_list(DOWN)
        self.enable_controls(True)
        self.notify("Opened puzzle '" + self.puzzle.title + "'")

        self.puzzle_widget.widget.hide_all()
        self.puzzle_widget.widget.show_all()

        self.idle_event()

    def do_save_file(self, fname):
        self.default_loc = os.path.dirname(fname)
        self.puzzle.save(fname)

    def get_puzzle_file(self, puzzle):
        dir = os.path.expanduser('~' + os.sep + '.crossword_puzzles')
        try: os.mkdir(dir)
        except OSError: pass

        return dir + os.sep + puzzle.hashcode()

    def clear_puzzle(self):
        if self.last_file: 
            if self.ask("Clear the current puzzle?"):
                self.do_open_file(self.last_file, True)

    def load_puzzle(self, fname, f):
        pp = PersistentPuzzle()
        try:
            pp.from_binary(f.read())
            
            self.puzzle.responses = pp.responses
            self.puzzle.errors = pp.errors
            if len(pp.notes) > 0:
                self.puzzle.notes = pp.notes
            self.clock_time = pp.clock
        except:
            self.notify('The saved puzzle is corrupted. It will not be used.')
            os.remove(fname)

        f.close()

    def set_puzzle(self, puzzle, start_over = False):
        self.clock_time = 0.0

        self.puzzle = puzzle
        if not self.puzzle: return
        
        fname = self.get_puzzle_file(puzzle)

        try: f = file(fname, 'r')
        except IOError: return

        if not start_over:
            self.load_puzzle(fname, f)

    def write_puzzle(self):
        if not self.puzzle: return
        
        pp = PersistentPuzzle()
        pp.responses = self.puzzle.responses
        pp.errors = self.puzzle.errors

        if self.clock_running:
            self.clock_time += (time.time() - self.clock_start)
        pp.clock = self.clock_time

        pp.notes = self.puzzle.notes

        fname = self.get_puzzle_file(self.puzzle)
        f = file(fname, 'w+')
        f.write(pp.to_binary())
        f.close()

    def exit(self):
        self.write_puzzle()
        self.write_config()
        gtk.main_quit()

    def notify(self, msg):
        if has_hildon:
            hildon.hildon_banner_show_information(self.win, None, msg)
        else:
            self.status_bar.push(self.status_bar.get_context_id('stat'), msg)

    def ask(self, msg, opts=(gtk.STOCK_OK, gtk.STOCK_CANCEL)):
        if has_hildon:
            dialog = hildon.Note("confirmation", (self.win, msg))
            dialog.set_button_texts(opts[0], opts[1])
        else:
            dialog = gtk.MessageDialog(parent=self.win,
                                       flags=gtk.DIALOG_MODAL,
                                       type=gtk.MESSAGE_QUESTION,
                                       message_format=msg)
            dialog.add_buttons(opts[0], gtk.RESPONSE_OK, opts[1], gtk.RESPONSE_CANCEL)

        dialog.set_default_response(gtk.RESPONSE_CANCEL)

        dialog.show()
        r = dialog.run()
        dialog.destroy()

        return (r == gtk.RESPONSE_OK)

    def show_properties(self):
        show_props = gtk.Dialog('Properties', self.win)
        show_props.set_has_separator(False)
        show_props.set_resizable(False)
        vbox_left = gtk.VBox()
        title = gtk.Label('Title:')
        title.set_alignment(1, 1)
        author = gtk.Label('Author:')
        author.set_alignment(1, 1)
        copyright = gtk.Label('Copyright:')
        copyright.set_alignment(1, 1)
        vbox_left.pack_start(title, False, False, 1)
        vbox_left.pack_start(author, False, False, 1)
        vbox_left.pack_start(copyright, False, False, 1)
        vbox_right = gtk.VBox()
        title2 = gtk.Label(self.puzzle.title)
        author2 = gtk.Label(self.puzzle.author)
        copyright2 = gtk.Label(self.puzzle.copyright)
        vbox_right.pack_start(title2, False, False, 1)
        vbox_right.pack_start(author2, False, False, 1)
        vbox_right.pack_start(copyright2, False, False, 1)
        hbox = gtk.HBox()
        hbox.pack_start(vbox_left, False, False, 2)
        hbox.pack_start(vbox_right, False, False, 2)
        show_props.vbox.pack_start(hbox, False, False, 15)
        show_props.vbox.show_all()
        close_button = show_props.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
        close_button.grab_focus()
        show_props.run()
        show_props.destroy()

    def show_notepad(self):
        notepad = gtk.Dialog('Notepad', self.win)
        notepad.set_has_separator(False)
        notepad.set_border_width(2)
        notepad.set_resizable(True)
        textbuf = gtk.TextBuffer()
        textbuf.set_text(self.puzzle.notes)
        textview = gtk.TextView(textbuf)
        textview.set_left_margin(4)
        textview.set_right_margin(4)
        scrollwin = gtk.ScrolledWindow()
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
        scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scrollwin.add(textview)
        notepad.vbox.pack_start(scrollwin, True, True, 0)
        notepad.vbox.show_all()
        close_button = notepad.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
        close_button.grab_focus()
        notepad.set_size_request(650, 350)
        notepad.run()
        (start, end) = textbuf.get_bounds()
        self.puzzle.notes = textbuf.get_slice(start, end, True)
        notepad.destroy()

    def show_about(self):
        # Help > About
        self.about_dialog = gtk.AboutDialog()
        try:
            self.about_dialog.set_transient_for(self.win)
            self.about_dialog.set_modal(True)
        except:
            pass
        self.about_dialog.set_name('Xword')
        self.about_dialog.set_version(__version__)
        self.about_dialog.set_comments('The Crossword puzzle player.')
        self.about_dialog.set_license(__license__)
        self.about_dialog.set_authors(
            ['Bill McCloskey <bill.mccloskey@gmail.com>\n' +
             'Maemo Port: Bradley Bell <bradleyb@u.washington.edu>\n' +
             'and Terrence Fleury <terrencegf@gmail.com>'])
#        gtk.about_dialog_set_url_hook(self.show_website, "http://xword.garage.maemo.org")
        self.about_dialog.set_website("http://xword.garage.maemo.org/")
        self.about_dialog.set_website_label("xword.garage.maemo.org")
        try:
            icon_pixbuf = gtk.gdk.pixbuf_new_from_file(HOME_PATH+os.sep+
                'xword-logo2.png')
            self.about_dialog.set_logo(icon_pixbuf)
        except:
            pass
        self.about_dialog.connect('response', self.close_about)
        self.about_dialog.connect('delete_event', self.close_about)
        self.about_dialog.show_all()

    def close_about(self, event, data=None):
        self.about_dialog.hide()
        return True

    def prompt_min_box_size(self):
        dialog = gtk.Dialog(None, self.win,
                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                     (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                      gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        box = gtk.HBox();
        dialog.vbox.pack_start(box)
        label = gtk.Label("Minimum Box Size:");
        box.pack_start(label)
        adjust = gtk.Adjustment(self.min_box_size,
            MIN_BOX_SIZE,MAX_BOX_SIZE,2,0,0)
        spin = gtk.SpinButton(adjust)
        box.pack_start(spin)
        dialog.show_all();
        r = dialog.run()
        if r == gtk.RESPONSE_ACCEPT:
            spinval = spin.get_value_as_int()
            if spinval != self.min_box_size:
                self.min_box_size = spinval
                self.puzzle_widget.min_box_size = spinval
                self.puzzle_widget.set_puzzle(self.puzzle, self.control)
        dialog.destroy()

    def create_widgets(self):
        self.widgets = {}

        vbox = gtk.VBox()
        
        clue = ClueWidget(self.control)
        vbox.pack_start(clue.widget, False, False, 0)
        self.clue_widget = clue

        puzzle = PuzzleWidget(self.puzzle, self.control, self.min_box_size)
        puzzle.area.connect('key-press-event', self.puzzle_key_event)
        vbox.pack_start(puzzle.widget, True, True, 0)
        self.puzzle_widget = puzzle

        self.widgets['puzzle'] = vbox

        puzzle.widget.connect('button-press-event', self.button_event, puzzle)

        self.tree_paths = {}
        self.trees = {}

        self.widgets['across'] = self.create_list(ACROSS)
        self.widgets['down'] = self.create_list(DOWN)
        self.load_list(ACROSS)
        self.load_list(DOWN)
            
    def generate_layout(self, layout):
        if type(layout) == str:
            return self.widgets[layout]
        else:
            if layout[0] == 'H': w = gtk.HPaned()
            elif layout[0] == 'V': w = gtk.VPaned()
            
            w.add1(self.generate_layout(layout[1]))
            w.add2(self.generate_layout(layout[3]))
            w.set_position(layout[2])
            w.show()
            
            return w

    def set_layout(self, index):
        if not self.cur_layout: return

        for w in self.widgets.values():
            p = w.get_parent()
            if p: p.remove(w)

        p = self.cur_layout.get_parent()
        if p: p.remove(self.cur_layout)
        
        self.cur_layout = None
        self.layout = index
        self.positions = layouts[index][1]
        self.cur_layout = self.generate_layout(self.positions)
        self.vbox.pack_start(self.cur_layout, True, True, 0)

        self.win.show_all()
        self.puzzle_widget.area.grab_focus()

    def get_layout(self, widget):
        kind = widget.get_name()
        if kind == 'GtkHPaned':
            children = widget.get_children()
            return ('H',
                    self.get_layout(children[0]),
                    widget.get_position(),
                    self.get_layout(children[1]))
        elif kind == 'GtkVPaned':
            children = widget.get_children()
            return ('V',
                    self.get_layout(children[0]),
                    widget.get_position(),
                    self.get_layout(children[1]))
        else:
            for (name, w) in self.widgets.items():
                if w is widget: return name

    def set_toolbox_layout(self,checks_position,keyboard_position):
        if not self.letterbarV: return

        self.checks_position = checks_position
        self.keyboard_position = keyboard_position

        for i in range(0,4):
            for child in self.toolbox[i].get_children():
                self.toolbox[i].remove(child)

        if self.checks_position > 0:
            if self.checks_position == 1 or self.checks_position == 2:
                self.toolbar.set_orientation(gtk.ORIENTATION_VERTICAL)
            elif self.checks_position == 3 or self.checks_position == 4:
                self.toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL)
            self.toolbox[self.checks_position-1].pack_start(self.toolbar,
                False,False,0)

        if self.keyboard_position == 1 or self.keyboard_position == 2:
            self.toolbox[self.keyboard_position-1].pack_start(self.letterbarV,
                False,False,0)
        elif self.keyboard_position == 3 or self.keyboard_position == 4:
            self.toolbox[self.keyboard_position-1].pack_start(self.letterbarH,
                False,False,0)

        self.win.show_all()

    def state_event(self, w, event):
        state = int(event.new_window_state)
        self.maximized = (state & gtk.gdk.WINDOW_STATE_MAXIMIZED) <> 0
        self.fullscreen = (state & gtk.gdk.WINDOW_STATE_FULLSCREEN) <> 0

    def resize_window(self, widget, allocation):
        if not self.maximized:
            self.window_size = self.win.get_size()

    def update_recent_menu(self):
        for child in self.recent_menu.get_children():
            self.recent_menu.remove(child)
        if len(self.recent.recent_list) == 0:
            self.menu_items['recent'].set_property('sensitive', False)
        else:
            self.menu_items['recent'].set_property('sensitive', True)
            for item in self.recent.recent_list:
                m = gtk.MenuItem(item.split(os.sep)[-1])
                m.connect('activate', self.menu_selected, 'open_recent', item)
                self.recent_menu.append(m)
        self.recent_menu.show_all()


    def create_check_popup(self):
        menu = gtk.Menu()

        item = gtk.ImageMenuItem('Check Letter')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'check_letter.png'))
        item.connect('activate', self.menu_selected, 'check_letter')
        menu.append(item)

        item = gtk.ImageMenuItem('Check Word')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'check_word.png'))
        item.connect('activate', self.menu_selected, 'check_word')
        menu.append(item)

        item = gtk.ImageMenuItem('Check Puzzle')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'check_puzzle.png'))
        item.connect('activate', self.menu_selected, 'check_puzzle')
        menu.append(item)

        menu.show_all()
        return menu


    def create_solve_popup(self):
        menu = gtk.Menu()

        item = gtk.ImageMenuItem('Solve Letter')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'solve_letter.png'))
        item.connect('activate', self.menu_selected, 'solve_letter')
        menu.append(item)

        item = gtk.ImageMenuItem('Solve Word')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'solve_word.png'))
        item.connect('activate', self.menu_selected, 'solve_word')
        menu.append(item)

        item = gtk.ImageMenuItem('Solve Puzzle')
        item.set_image(self.load_icon(HOME_PATH+os.sep+'solve_puzzle.png'))
        item.connect('activate', self.menu_selected, 'solve_puzzle')
        menu.append(item)

        menu.show_all()
        return menu


    def create_menubar(self):
        accel = gtk.AccelGroup()

        self.menu_items = {}

        def create_item(args, action, key, klass, active):
            item = klass(**args)
            if active: item.set_active(True)
            item.connect('activate', self.menu_selected, action)
            if not has_hildon and key:
                item.add_accelerator('activate', accel, ord(key),
                                     gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            return item

        def create_menu_item(label, action, key=None, klass=gtk.MenuItem,
                             active=False):
            return create_item({ 'label': label }, action, key, klass, active)

        def create_radio_item(label, action, group, active):
            return create_item({ 'label': label, 'group': group },
                               action, None, gtk.RadioMenuItem, active)

        def append(menu, name, item):
            self.menu_items[name] = item
            menu.append(item)

        menubar = gtk.MenuBar()

        file_menu = gtk.MenuItem('_File')
        menubar.append(file_menu)

        menu = gtk.Menu()
        file_menu.set_submenu(menu)

        append(menu,'open',create_menu_item('Open...','open_file','O'))
        item = create_menu_item('Open Recent',0)
        item.set_submenu(self.recent_menu)
        append(menu,'recent',item)
        append(menu,'save',create_menu_item('Save As...','save_file','S'))
        append(menu,'close',create_menu_item('Close','close_file','C'))
        append(menu,'delete',create_menu_item('Delete','delete_file','D'))
        if has_print:
            append(menu,'print',create_menu_item('Print...','print_file','P'))
        append(menu,'',gtk.SeparatorMenuItem())
        append(menu,'quit',create_menu_item('Quit','menu_quit','Q'))

        puzzle_menu = gtk.MenuItem('Puzzle')
        menubar.append(puzzle_menu)

        menu=gtk.Menu()
        puzzle_menu.set_submenu(menu)

        item = create_menu_item('Check',0)
        append(menu,'check',item)
        menu1 = gtk.Menu()
        item.set_submenu(menu1)
        append(menu1,'check_letter',create_menu_item('Letter','check_letter'))
        append(menu1,'check_word',create_menu_item('Word','check_word'))
        append(menu1,'check_puzzle',create_menu_item('Puzzle','check_puzzle'))

        item = create_menu_item('Solve',0)
        append(menu,'solve',item)
        menu1 = gtk.Menu()
        item.set_submenu(menu1)
        append(menu1,'solve_letter',create_menu_item('Letter','solve_letter'))
        append(menu1,'solve_word',create_menu_item('Word','solve_word'))
        append(menu1,'solve_puzzle',create_menu_item('Puzzle','solve_puzzle'))

        item = create_menu_item('Clear',0)
        append(menu,'clear',item)
        menu1 = gtk.Menu()
        item.set_submenu(menu1)
        append(menu1,'clear_letter',create_menu_item('Letter','clear_letter'))
        append(menu1,'clear_word',create_menu_item('Word','clear_word'))
        append(menu1,'clear_puzzle',create_menu_item('Puzzle','clear_puzzle'))

        append(menu,'properties',create_menu_item('Properties...','properties'))
        append(menu,'notepad',create_menu_item('Notepad...','notepad'))

        prefs_menu = gtk.MenuItem('Preferences')
        menubar.append(prefs_menu)
            
        menu = gtk.Menu()
        prefs_menu.set_submenu(menu)
            
        append(menu,'skip-filled',
               create_menu_item('Skip Filled','skip_filled',None,
                                gtk.CheckMenuItem,self.skip_filled))

        append(menu,'min_box_size',
            create_menu_item('Minimum Box Size...','min_box_size'))

        item = create_menu_item('Solution Buttons', 0)
        append(menu, 'checks', item)
        menu1 = gtk.Menu()
        item.set_submenu(menu1)
        g = None
        i = -1
        for side in ['Off', 'Left', 'Right', 'Top', 'Bottom']:
            item = create_radio_item(side, i-20, g,
                -(i+1)==self.checks_position)
            menu1.append(item)
            if not g: item.set_active(True)
            g = item
            i -= 1

        item = create_menu_item('Onscreen Keyboard', 0)
        append(menu, 'keyboard', item)
        menu1 = gtk.Menu()
        item.set_submenu(menu1)
        g = None
        i = -1
        for side in ['Off', 'Left', 'Right', 'Top', 'Bottom']:
            item = create_radio_item(side, i-10, g,
                -(i+1)==self.keyboard_position)
            menu1.append(item)
            if not g: item.set_active(True)
            g = item
            i -= 1

        item = create_menu_item('Word List Layout',0)
        append(menu,'layout',item)
        
        menu = gtk.Menu()
        item.set_submenu(menu)
        
        g = None
        i = -1
        for (name, layout) in layouts:
            item = create_radio_item(name, i, g, -(i+1) == self.layout)
            menu.append(item)
            if not g: item.set_active(True)
            g = item
            i -= 1

        help_menu = gtk.MenuItem('Help')
        menubar.append(help_menu)

        menu = gtk.Menu()
        help_menu.set_submenu(menu)

        append(menu,'about',create_menu_item('About','about'))

        self.win.add_accel_group(accel)
        return menubar

    def menu_selected(self, item, action, extra=None):
        if action == 'menu_quit':
            self.exit()
        elif action == 'skip_filled':
            self.skip_filled = not self.skip_filled
        elif action == 'open_file':
            self.open_file()
        elif action == 'open_recent':
            self.open_recent(extra)
        elif action == 'save_file':
            self.save_file()
        elif action == 'close_file':
            self.close_file()
        elif action == 'delete_file':
            self.delete_file()
        elif action == 'print_file':
            self.print_file()
        elif action == 'check_letter':
            self.control.check_letter()
        elif action == 'check_word':
            self.control.check_word()
        elif action == 'check_puzzle':
            self.control.check_puzzle()
        elif action == 'solve_letter':
            self.control.solve_letter()
        elif action == 'solve_word':
            self.control.solve_word()
        elif action == 'solve_puzzle':
            if self.ask("Show puzzle solution?"):
                self.control.solve_puzzle()
        elif action == 'clear_letter':
            self.control.clear_letter()
        elif action == 'clear_word':
            self.control.clear_word()
        elif action == 'clear_puzzle':
            self.clear_puzzle()
        elif action == 'notepad':
            self.show_notepad()
        elif action == 'properties':
            self.show_properties()
        elif action == 'about':
            self.show_about()
        elif action == 'min_box_size':
            self.prompt_min_box_size()
        elif action < -20:
            checks_position = -(action+21)
            if checks_position <> self.checks_position:
                self.set_toolbox_layout(checks_position,self.keyboard_position)
        elif action < -10:
            keyboard_position = -(action+11)
            if keyboard_position <> self.keyboard_position:
                self.set_toolbox_layout(self.checks_position,keyboard_position)
        elif action < 0:
            layout = -(action+1)
            if layout <> self.layout: self.set_layout(layout)

    def load_icon(self,icon):
        img = gtk.Image()
        ext = icon[-4:]
        if ext == '.png' or ext == '.xpm':
            img.set_from_file(icon)
        else:
            img.set_from_stock(icon, gtk.ICON_SIZE_SMALL_TOOLBAR)
        return img

    def create_toolbar_button_item(self, label, icon, tooltip, is_toggle=False):
        img = None
        if icon: img = self.load_icon(icon)

        if is_toggle:
            item = gtk.ToggleToolButton()
            item.set_label(label)
            item.set_icon_widget(img)
        else:
            item = gtk.ToolButton(img, label)

        handler_id = item.connect('clicked', self.toolbar_event, label)
        return item, handler_id

    def create_toolbar_item(self, label, icon, tooltip, is_toggle=False):
        (item,handler_id) = self.create_toolbar_button_item(label,icon,
                                                            tooltip,is_toggle)
        self.toolbar.insert(item, -1)
        self.toolbar_items[label] = item
        return item

    def create_separator_toolitem(self):
        item = gtk.SeparatorToolItem()
        item.set_draw(False)
        item.set_expand(True)
        self.toolbar.insert(item, -1)

    def create_clock_button(self):
        (b,handler_id) = self.create_toolbar_button_item('', 
            HOME_PATH+os.sep+'clock.png', 'Enable or disable the clock',True)
        b.set_is_important(True)
        self.clock_buttons.append(b)
        self.clock_buttons_handler_ids.append(handler_id)
        self.idle_event()

        return len(self.clock_buttons) - 1

    def create_toolbar(self):
        self.toolbar_items = {}
        
        toolbar = gtk.Toolbar()
        toolbar.set_style(gtk.TOOLBAR_BOTH)
        self.toolbar = toolbar

        self.create_toolbar_item(CHK_LTR_BTN, 
            HOME_PATH+os.sep+'check_letter.png', 'Check a letter')
        self.create_toolbar_item(CHK_WRD_BTN, 
            HOME_PATH+os.sep+'check_word.png', 'Check a word')
        self.create_toolbar_item(SOL_LTR_BTN, 
            HOME_PATH+os.sep+'solve_letter.png', 'Cheat to get one letter')
        self.create_toolbar_item(SOL_WRD_BTN, 
            HOME_PATH+os.sep+'solve_word.png', 'Cheat to get a word')
        self.create_separator_toolitem()
        pos = self.create_clock_button()
        self.toolbar.insert(self.clock_buttons[pos], -1)

        return toolbar

    def create_letterbar(self,rows,cols,labels):
        scrolled = gtk.ScrolledWindow()
        if (rows > cols):
            scrolled.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
        else:
            scrolled.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_NEVER)
        table = gtk.Table(rows, cols, False)
        viewport = gtk.Viewport()
        viewport.add(table)
        viewport.set_shadow_type(gtk.SHADOW_NONE)
        scrolled.add(viewport)

        row = 0
        col = 0
        for label in labels:
            if label == 'clock':
                toolbar = gtk.Toolbar()
                toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
                pos = self.create_clock_button()
                toolbar.insert(self.clock_buttons[pos],-1)
                table.attach(toolbar, col, col+3, row, row+1)
                col += 3
                if (col >= cols):
                    col = 0
                    row += 1
                continue
            elif len(label) == 1:
                button = gtk.ToolButton(None, label)
            else:
                button = gtk.ToolButton(self.load_icon(
                         HOME_PATH + os.sep + label + '.xpm'), None)

            if has_hildon:
                if label == 'Escape':
                    button.tap_and_hold_setup(self.check_popup)
                elif label == 'F12':
                    button.tap_and_hold_setup(self.solve_popup)

            button.child.connect('button-release-event',
                self.letterbar_event,label)
            table.attach(button, col, col+1, row, row+1)

            col += 1
            if (col >= cols):
                col = 0
                row += 1
            
        return scrolled

    def create_list(self, mode):
        if mode == ACROSS: label = 'Across'
        else: label = 'Down'

        tree = gtk.TreeView()
        column = gtk.TreeViewColumn(label, gtk.CellRendererText(), text=1)
        tree.append_column(column)
        tree.connect('row-activated', self.select_changed, mode)
        tree.set_property('can-focus', False)

        scroll = gtk.ScrolledWindow()
        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll.add(tree)

        self.trees[mode] = tree
        
        return scroll

    def load_list(self, mode):
        self.tree_paths[mode] = {}
        store = gtk.ListStore(int, str)
        i = 0
        for (n, clue) in self.control.get_clues(mode):
            self.tree_paths[mode][n] = i
            store.append((n, '%d. %s' % (n, clue)))
            i += 1

        self.trees[mode].set_model(store)

    def select_changed(self, tree, path, column, mode):
        store = tree.get_model()
        n = store.get_value(store.get_iter(path), 0)
        self.control.select_word(mode, n)
        
    def across_update(self, an):
        if self.tree_paths.has_key(ACROSS):
            selection = self.trees[ACROSS].get_selection()
            if an:
                selection.select_path(self.tree_paths[ACROSS][an])
                self.trees[ACROSS].scroll_to_cell(self.tree_paths[ACROSS][an])
            else:
                selection.unselect_all()

    def down_update(self, dn):
        if self.tree_paths.has_key(DOWN):
            selection = self.trees[DOWN].get_selection()
            if dn:
                selection.select_path(self.tree_paths[DOWN][dn])
                self.trees[DOWN].scroll_to_cell(self.tree_paths[DOWN][dn])
            else:
                selection.unselect_all()

    def idle_event(self):
        t = time.time()
        if self.clock_running:
            total = int(self.clock_time + (t - self.clock_start))
        else:
            total = int(self.clock_time)
        s = time_str(total)
        for index in range(len(self.clock_buttons)):
            sold = self.clock_buttons[index].get_label()
            if sold <> s: 
                try:
                    self.clock_buttons[index].set_label(s)
                except:
                    pass

        return True

    def activate_clock(self, active=True):
        if self.clock_running != active:
            self.clock_running = active
            if self.clock_running:
                self.clock_start = time.time()
            else:
                self.clock_time += (time.time() - self.clock_start)
            for index in range(len(self.clock_buttons)):
                self.clock_buttons[index].handler_block(
                    self.clock_buttons_handler_ids[index])
                self.clock_buttons[index].set_active(active)
                self.clock_buttons[index].handler_unblock(
                    self.clock_buttons_handler_ids[index])

    def toolbar_event(self, widget, event):
        if event == 'Quit':
            self.exit()
        elif event == 'Open':
            self.open_file()
        elif event == CHK_LTR_BTN:
            self.control.check_letter()
        elif event == CHK_WRD_BTN:
            self.control.check_word()
        elif event == SOL_LTR_BTN:
            self.control.solve_letter()
        elif event == SOL_WRD_BTN:
            self.control.solve_word()
        else: # it must be the clock
            self.activate_clock(not self.clock_running)

    def letterbar_event(self, widget, event, label):
        keypress = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
        keypress.keyval = int(gtk.gdk.keyval_from_name(label))
        if len(label) is 1 and label.isalpha():
            self.puzzle_key_event('',keypress)
        else:
            if event.button == 3 and (label == 'F12' or label == 'Escape'):
                if label == 'F12':
                   self.solve_popup.popup(None,None,None,
                       event.button,event.time)
                else:
                   self.check_popup.popup(None,None,None,
                       event.button,event.time)
            else:
                self.key_event('',keypress)

    def button_event(self, widget, event, puzzle):
        if event.type is gtk.gdk.BUTTON_PRESS:
            (x, y) = puzzle.translate_position(
                         event.x+puzzle.widget.get_hadjustment().get_value(), 
                         event.y+puzzle.widget.get_vadjustment().get_value())
            if x == self.control.x and y == self.control.y:
                self.control.switch_mode()
            else:
                if event.button is 3: self.control.switch_mode()
                self.control.move_to(x, y)

    def key_event(self, item, event):
        name = gtk.gdk.keyval_name(event.keyval)
        
        c = self.control

        if name == 'Right': c.move(ACROSS, 1)
        elif name == 'Left': c.move(ACROSS, -1)
        elif name == 'Up': c.move(DOWN, -1)
        elif name == 'Down': c.move(DOWN, 1)
        elif name == 'BackSpace': c.back_space()
        elif name == 'Return': c.switch_mode()
        elif name == 'Tab': c.next_word(1)
        elif name == 'ISO_Left_Tab': c.next_word(-1)
        elif name == 'F6':
            if self.fullscreen:
                self.win.unfullscreen()
            else:
                self.win.fullscreen()
        elif name == 'F7': c.next_word(1)
        elif name == 'F8': c.next_word(-1)
        elif name == 'Escape': c.check_word()
        elif name == 'F12': c.solve_word()
        else: return False

        return True

    def puzzle_key_event(self, item, event):
        name = gtk.gdk.keyval_name(event.keyval)
        c = self.control
        if len(name) is 1 and name.isalpha():
            c.input_char(self.skip_filled, name)
            return True
        else:
            return False

    def puzzle_finished(self):
        self.notify('You have solved the puzzle!')
        self.activate_clock(False)

    def check_result(self, correct):
        if correct: msg = 'No mistakes found.'
        else: msg = 'Incorrect.'

        self.notify(msg)

    def open_file(self):
        def open_cb(w, open_dlg):
            self.do_open_file(open_dlg.get_filename())
            open_dlg.destroy()
        
        if has_hildon:
            dlg = hildon.FileChooserDialog(self.win,
                                           gtk.FILE_CHOOSER_ACTION_OPEN)
        else:
            dlg = gtk.FileChooserDialog("Open...",
                                        None,
                                        gtk.FILE_CHOOSER_ACTION_OPEN,
                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                         gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dlg.set_default_response(gtk.RESPONSE_OK)
        if self.default_loc: dlg.set_current_folder(self.default_loc)

        response = dlg.run()
        if response == gtk.RESPONSE_OK:
            open_cb(None, dlg)
        else:
            dlg.destroy()

    def save_file(self):
        def save_cb(w, save_dlg):
            self.do_save_file(save_dlg.get_filename())
            save_dlg.destroy()
        
        if has_hildon:
            dlg = hildon.FileChooserDialog(self.win,
                                           gtk.FILE_CHOOSER_ACTION_SAVE)
        else:
            dlg = gtk.FileChooserDialog("Save As...",
                                        None,
                                        gtk.FILE_CHOOSER_ACTION_SAVE,
                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                         gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        dlg.set_default_response(gtk.RESPONSE_OK)
        if self.default_loc: dlg.set_current_folder(self.default_loc)

        response = dlg.run()
        if response == gtk.RESPONSE_OK:
            save_cb(None, dlg)
        else:
            dlg.destroy()

    def close_file(self):
        if self.puzzle: self.write_puzzle()
        self.activate_clock(False)
        self.clock_start = time.time()
        self.clock_time = 0.0
        self.puzzle = None
        self.last_file = ''
        self.control = DummyController()
        self.setup_controller()
        self.clue_widget.set_controller(self.control)
        self.puzzle_widget.set_puzzle(self.puzzle, self.control)
        self.load_list(ACROSS)
        self.load_list(DOWN)
        self.enable_controls(False)
        self.puzzle_widget.widget.hide_all()
        self.puzzle_widget.widget.show_all()
        self.idle_event()

    def delete_file(self):
        if self.last_file: 
            if self.ask('Permanently delete the current file?'):
                if os.path.exists(self.last_file):
                    try:
                        binary_puzzle = self.get_puzzle_file(self.puzzle)
                        os.remove(self.last_file)
                        self.recent.delete(self.last_file)
                        self.update_recent_menu()
                        self.close_file()
                        os.remove(binary_puzzle)
                    except:
                        pass
        
    def open_recent(self, filename):
        if filename != None:
            if os.path.exists(filename):
                self.do_open_file(filename)
            else:
                self.notify('File not found.')
                self.recent.delete(filename)
                self.update_recent_menu()

    def print_file(self):
        if has_print:
            pr = PuzzlePrinter(self.puzzle)
            pr.print_file(self.win)
        else:
            self.notify('Printing libraries are not installed. Please'
                        + ' install the Python wrapper for gnomeprint.')

    def read_config(self):
        c = ConfigParser.ConfigParser()
        c.read(os.path.expanduser('~' + os.sep + '.crossword.cfg'))
        if c.has_section('options'):
            if c.has_option('options', 'skip_filled'):
                self.skip_filled = c.getboolean('options', 'skip_filled')
            if c.has_option('options', 'clock_running'):
                self.clock_running = c.getboolean('options', 'clock_running')
            if c.has_option('options', 'fullscreen'):
                self.fullscreen = c.getboolean('options', 'fullscreen')
            if c.has_option('options', 'layout'):
                self.layout = c.getint('options', 'layout')
            if c.has_option('options', 'checks_position'):
                self.checks_position = c.getint('options', 'checks_position')
            if c.has_option('options', 'keyboard_position'):
                self.keyboard_position = c.getint('options','keyboard_position')
            if c.has_option('options', 'positions'):
                self.positions = eval(c.get('options', 'positions'))
            if c.has_option('options', 'window_size'):
                self.window_size = eval(c.get('options', 'window_size'))
            if c.has_option('options', 'maximized'):
                self.maximized = eval(c.get('options', 'maximized'))
            if c.has_option('options', 'default_loc'):
                self.default_loc = eval(c.get('options', 'default_loc'))
            if c.has_option('options', 'last_file'):
                self.last_file = eval(c.get('options', 'last_file'))
            if c.has_option('options', 'min_box_size'):
                self.min_box_size = c.getint('options', 'min_box_size')
            if c.has_option('options', 'recent_list'):
                self.recent.set(eval(c.get('options', 'recent_list')));

    def write_config(self):
        c = ConfigParser.ConfigParser()
        c.add_section('options')
        c.set('options', 'skip_filled', self.skip_filled)
        c.set('options', 'clock_running', self.clock_running)
        c.set('options', 'fullscreen', self.fullscreen)
        c.set('options', 'layout', self.layout)
        c.set('options', 'checks_position', self.checks_position)
        c.set('options', 'keyboard_position', self.keyboard_position)
        c.set('options', 'positions', repr(self.get_layout(self.cur_layout)))
        c.set('options', 'window_size', repr(self.window_size))
        c.set('options', 'maximized', repr(self.maximized))
        c.set('options', 'default_loc', repr(self.default_loc))
        c.set('options', 'last_file', repr(self.last_file))
        c.set('options', 'min_box_size', self.min_box_size)
        c.set('options', 'recent_list', repr(self.recent.recent_list))
        c.write(file(os.path.expanduser('~' + os.sep + '.crossword.cfg'), 'w'))


def osso_callback(interface, method, arguments, user_data):
    if method == 'mime_open':
        w.win.present()
        filename = arguments[0]
        if filename.startswith('file://'):
            filename = filename[7:]
        if os.path.exists(filename):
            w.do_open_file(filename)

if __name__ == '__main__':
    if has_osso:
        osso_c = osso.Context("xword", __version__, False)
        osso_rpc = osso.Rpc(osso_c)
        osso_rpc.set_rpc_callback("org.maemo.xword", "/org/maemo/xword",
                                  "org.maemo.xword", osso_callback, osso_c)

    p = None
    if len(sys.argv) == 2 and os.path.exists(sys.argv[1]):
        try:
            p = Puzzle(sys.argv[1])
        except:
            print "%s could not be loaded" % sys.argv[1]
            p = None

    w = PuzzleWindow(p)
    gtk.main()
