xword-2.0.0~rc2/0000755000175000017500000000000011613174573012373 5ustar cdalecdalexword-2.0.0~rc2/TODO0000644000175000017500000000216511613113462013055 0ustar cdalecdaleBefore Release ============== # Fix printing (Windows bug, Linux bug, enlarge support) # Test fresh install, install over old version # Make sure keyboard shortcuts (including arrows & delete key) match Across Lite # Possible problem: Open puzzle in locked mode, reopen in unlocked, close, # then when you run xword it opens back in locked Next Release ============ # Add ability to scan multiple directories to the organizer # Use filters to limit the displayed puzzles in the organizer # Use IPC to manage multiple instances # Verify puzzle checksums # Handle Rebus squares # Organizer list of clock and filter/sort Future Releases =============== # Use a custom TreeModel implementation backed by an SQLite database http://www.johnstowers.co.nz/blog/index.php/2007/12/06/the-big-move/ http://docs.python.org/library/sqlite3.html # Use Glade http://www.micahcarrick.com/gtk-glade-tutorial-part-1.html # Translate all UI strings https://prioritiseapp.wordpress.com/2010/07/28/translating-a-pygtk-application/ # Create Windows installer # Migrate to PyGObject from PyGTK http://live.gnome.org/PyGObject/IntrospectionPorting xword-2.0.0~rc2/LICENSE0000644000175000017500000000307311613113462013371 0ustar cdalecdaleCopyright (c) 2009-2011, Cameron Dale Copyright (c) 2005-2009, Bill McCloskey 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. (See http://www.opensource.org/licenses/bsd-license.php) xword-2.0.0~rc2/xword/0000755000175000017500000000000011613174573013536 5ustar cdalecdalexword-2.0.0~rc2/xword/config.py0000755000175000017500000002163311613113462015353 0ustar cdalecdaleimport os.path import ConfigParser import csv import StringIO CONFIG_DIR = os.path.expanduser(os.path.join('~', '.xword')) CONFIG_FILE = os.path.join(CONFIG_DIR, 'crossword.cfg') CONFIG_RECENT_LIST = os.path.join(CONFIG_DIR, 'crossword-recent-list') CONFIG_RECENT_DIR = os.path.join(CONFIG_DIR, 'crossword-recent') CONFIG_PUZZLE_DIR = os.path.join(CONFIG_DIR, 'crossword_puzzles') 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'))) ] PHASH = 0 PTITLE = 1 MAX_RECENT = 5 class XwordConfig: def __init__(self): self.set_defaults() self.setup_config_dir() def set_defaults(self): self.skip_filled = False self.start_timer = False self.layout = 0 self.positions = LAYOUTS[self.layout][1] self.window_size = (900, 600) self.maximized = False self.organizer_window_size = (900, 600) self.organizer_maximized = False self.default_loc = None self.organizer_directories = [] def read_config(self): c = ConfigParser.ConfigParser() c.read(CONFIG_FILE) 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', 'start_timer'): self.start_timer = c.getboolean('options', 'start_timer') if c.has_option('options', 'layout'): self.layout = c.getint('options', 'layout') 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', 'organizer_window_size'): self.organizer_window_size = eval(c.get('options', 'organizer_window_size')) if c.has_option('options', 'organizer_maximized'): self.organizer_maximized = eval(c.get('options', 'organizer_maximized')) if c.has_option('options', 'default_loc'): self.default_loc = eval(c.get('options', 'default_loc')) if c.has_option('options', 'organizer_directories'): self.organizer_directories = [] dirs = c.get('options', 'organizer_directories') if len(dirs) > 0: parser = csv.reader([dirs]) dirslist = [] for row in parser: self.organizer_directories = row break def write_config(self): c = ConfigParser.ConfigParser() c.add_section('options') c.set('options', 'skip_filled', self.skip_filled) c.set('options', 'start_timer', self.start_timer) c.set('options', 'layout', self.layout) c.set('options', 'positions', repr(self.positions))#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', 'organizer_window_size', repr(self.organizer_window_size)) c.set('options', 'organizer_maximized', repr(self.organizer_maximized)) c.set('options', 'default_loc', repr(self.default_loc)) dirstring = StringIO.StringIO() if len(self.organizer_directories) > 0: writer = csv.writer(dirstring) writer.writerow(self.organizer_directories) c.set('options', 'organizer_directories', dirstring.getvalue().rstrip()) c.write(file(CONFIG_FILE, 'w')) def set_skip_filled(self, skip_filled): self.read_config() self.skip_filled = skip_filled self.write_config() def get_skip_filled(self): self.read_config() return self.skip_filled def set_start_timer(self, start_timer): self.read_config() self.start_timer = start_timer self.write_config() def get_start_timer(self): self.read_config() return self.start_timer def set_layout(self, layout): self.read_config() self.layout = layout self.write_config() def get_layout(self): self.read_config() return self.layout def set_positions(self, positions): self.read_config() self.positions = positions self.write_config() def get_positions(self): self.read_config() return self.positions def set_window_size(self, window_size): self.read_config() self.window_size = window_size self.write_config() def get_window_size(self): self.read_config() return self.window_size def set_maximized(self, maximized): self.read_config() self.maximized = maximized self.write_config() def get_maximized(self): self.read_config() return self.maximized def set_organizer_window_size(self, organizer_window_size): self.read_config() self.organizer_window_size = organizer_window_size self.write_config() def get_organizer_window_size(self): self.read_config() return self.organizer_window_size def set_organizer_maximized(self, organizer_maximized): self.read_config() self.organizer_maximized = organizer_maximized self.write_config() def get_organizer_maximized(self): self.read_config() return self.organizer_maximized def set_default_loc(self, default_loc): self.read_config() self.default_loc = default_loc self.write_config() def get_default_loc(self): self.read_config() return self.default_loc def set_organizer_directories(self, organizer_directories): self.read_config() self.organizer_directories = organizer_directories self.write_config() def get_organizer_directories(self): self.read_config() return self.organizer_directories def setup_config_dir(self): if not os.path.exists(CONFIG_DIR): def try_copy(oldname, fname): path1 = os.path.expanduser(os.path.join('~', oldname)) if os.path.exists(path1): try: os.system('cp -r %s %s' % (path1, fname)) except: pass def try_make(fname): try: os.mkdir(fname) except OSError: pass os.mkdir(CONFIG_DIR) try_copy('.crossword_puzzles', CONFIG_PUZZLE_DIR) try_make(CONFIG_PUZZLE_DIR) try_copy('.crossword.cfg', CONFIG_FILE) try_make(CONFIG_RECENT_DIR) def get_puzzle_file(self, puzzle): return os.path.join(CONFIG_PUZZLE_DIR, puzzle.hashcode()) def read_recent(self): try: data = eval(file(CONFIG_RECENT_LIST).read()) except: data = [] if type(data) == type([]): for x in data: if type(x) != type(()): data = [] if len(x) != 2: data = [] else: data = [] self.recent = data def recent_list(self): self.read_recent() # Returns a (title, hashcode) list return [ (d[PTITLE], d[PHASH]) for d in self.recent ] def remove_recent(self, hash): self.read_recent() self.recent = [ d for d in self.recent if d[PHASH] != hash ] try: os.remove(os.path.join(CONFIG_RECENT_DIR, hash)) except: pass self.write_recent() def add_recent(self, puzzle): hashcode = puzzle.hashcode() i = 0 for d in self.recent: if d[PHASH] == hashcode: self.recent.pop(i) self.recent = [d] + self.recent self.write_recent() return i += 1 if len(self.recent) >= MAX_RECENT: rmv = self.recent.pop() self.remove_recent(rmv[PHASH]) d = (hashcode, puzzle.title) self.recent = [d] + self.recent puzzle.save(os.path.join(CONFIG_RECENT_DIR, hashcode)) self.write_recent() def get_recent(self, hashcode): for d in self.recent: if d[PHASH] == hashcode: return os.path.join(CONFIG_RECENT_DIR, hashcode) def write_recent(self): f = file(CONFIG_RECENT_LIST, 'w') f.write(repr(self.recent)) f.close() xword-2.0.0~rc2/xword/main.py0000755000175000017500000010125111613170415015026 0ustar cdalecdaleimport puzzle import controller import grid import printing import config import __init__ import pygtk pygtk.require('2.0') import gtk import gtk.gdk import gobject import pango try: x = gtk.PrintOperation has_print = True except: has_print = False import sys import time import os.path import subprocess import wnck HOME_PATH = os.path.abspath(os.path.dirname(__file__)) stock_items = [ ('xw-check-word', 'pixmaps/crossword-check.png'), ('xw-check-puzzle', 'pixmaps/crossword-check-all.png'), ('xw-solve-word', 'pixmaps/crossword-solve.png'), ('xw-solve-puzzle', 'pixmaps/crossword-solve-all.png'), ('xw-notepad', 'pixmaps/crossword-notepad.png'), ('xw-clock', 'pixmaps/crossword-clock.png'), ] ACROSS = puzzle.ACROSS DOWN = puzzle.DOWN ui_description = ''' ''' 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 ClueWidget: def __init__(self, control): self.control = control width = 0 height = grid.MIN_BOX_SIZE 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 StatusBar: def __init__(self): self.frame = gtk.Frame() self.hbox = gtk.HBox() self.left_label = gtk.Label('Label') self.right_label = gtk.Label('Label') self.hbox.pack_start(self.left_label, True, True) self.hbox.pack_end(self.right_label, False, False, 20) self.frame.add(self.hbox) self.frame.set_shadow_type(gtk.SHADOW_NONE) self.left_label.set_ellipsize(pango.ELLIPSIZE_END) self.left_label.set_alignment(0.0, 0.0) def set_status(self, msg): self.left_label.set_text(msg) def set_right_label(self, label): self.right_label.set_text(label) class MainWindow: def __init__(self, fname): self.config = config.XwordConfig() self.clock_time = 0.0 self.clock_running = False self.puzzle = None self.control = controller.DummyController() self.skip_filled = self.config.get_skip_filled() self.start_timer = self.config.get_start_timer() self.layout = self.config.get_layout() self.maximized = self.config.get_maximized() win = gtk.Window() self.win = win self.handler = win.connect('destroy', lambda w: self.exit()) win.connect('size-allocate', self.resize_window) win.connect('window-state-event', self.state_event) window_size = self.config.get_window_size() win.resize(window_size[0], window_size[1]) if self.maximized: win.maximize() vbox = gtk.VBox() win.add(vbox) vbox = vbox self.cur_layout = None self.create_ui() vbox.pack_start(self.menubar, False, False, 0) vbox.pack_start(self.toolbar, False, False, 0) self.create_widgets() self.setup_controller() self.show_title() self.vbox = gtk.VBox() vbox.pack_start(self.vbox, True, True, 0) self.cur_layout = self.generate_layout(self.config.get_positions()) self.vbox.pack_start(self.cur_layout, True, True, 0) self.status_bar = StatusBar() vbox.pack_start(self.status_bar.frame, False, False, 0) self.timeout = gobject.timeout_add(100, self.idle_event) win.connect('key-press-event', self.key_event) self.enable_controls(False, False) win.show_all() if fname: self.do_open_file(fname) self.control.signal() self.puzzle_widget.area.grab_focus() def show_title(self): title = 'Xword Puzzle' data = '' locked = False if self.puzzle: title = 'Xword Puzzle - %s' % self.puzzle.title data = self.puzzle.title + ' ' + self.puzzle.author locked = self.puzzle.is_locked() self.win.set_title(title) def enable_controls(self, enabled, locked): def enable(a, x): action = self.actiongroup.get_action(a) action.set_sensitive(x) enable('Save', enabled) enable('PageSetup', enabled) enable('Print', enabled) enable('AboutPuzzle', enabled) enable('ShowNotepad', enabled and len(self.puzzle.notebook) > 0) enable('ClearWord', enabled) enable('ClearPuzzle', enabled) enable('CheckLetter', enabled and not locked) enable('CheckWord', enabled and not locked) enable('CheckPuzzle', enabled and not locked) enable('SolveLetter', enabled and not locked) enable('SolveWord', enabled and not locked) enable('SolvePuzzle', enabled and not locked) enable('Clock', enabled) def setup_controller(self): self.control.connect('puzzle-finished', self.puzzle_finished) self.control.connect('puzzle-filled', self.puzzle_filled) self.control.connect('letter-update', self.letter_update) self.control.connect('box-update', self.puzzle_widget.update) self.control.connect('all-update', self.puzzle_widget.update_all) self.control.connect('pos-update', self.puzzle_widget.pos_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-result', self.check_result) def open_recent(self, index): (title, hashcode) = self.config.recent_list()[index] fname = self.config.get_recent(hashcode) self.do_open_file(fname) def do_open_file(self, fname, ask=True): if self.puzzle: self.write_puzzle() if self.clock_running: self.activate_clock(False) self.set_puzzle(puzzle.Puzzle(fname), ask) self.control = controller.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.puzzle.is_locked()) self.config.add_recent(self.puzzle) self.update_recent_menu() self.idle_event() self.letter_update(0, 0) def do_save_file(self, fname): self.config.set_default_loc(os.path.dirname(fname)) self.puzzle.save(fname) def load_puzzle(self, fname, f): pp = puzzle.PersistentPuzzle() try: pp.from_binary(f.read()) self.puzzle.responses = pp.responses self.puzzle.errors = pp.errors self.clock_time = pp.clock if pp.clock_running: self.activate_clock(True) except: self.notify('The saved puzzle is corrupted. It will not be used.') os.remove(fname) f.close() def set_puzzle(self, puzzle, ask): self.clock_time = 0.0 self.puzzle = puzzle if not self.puzzle: return self.show_title() fname = self.config.get_puzzle_file(puzzle) try: f = file(fname, 'r') except IOError: f = None fresh = True if f: if ask: opts = ['Start Over', 'Continue'] msg = ('This puzzle has been opened before. Would you like to' + ' continue where you left off?') if self.ask(msg, opts) == 1: self.load_puzzle(fname, f) fresh = False else: self.load_puzzle(fname, f) fresh = False msg = ('Opened puzzle "%s" by %s, %s' % (puzzle.title, puzzle.author, puzzle.copyright)) self.status_bar.set_status(msg) if fresh and self.start_timer: self.activate_clock(True) if len(puzzle.notebook) > 0 and fresh: self.notify('This puzzle has a notepad attached:\n' + puzzle.notebook) def write_puzzle(self): if not self.puzzle: return if self.puzzle.is_empty(): fname = self.config.get_puzzle_file(self.puzzle) try: os.remove(fname) except: pass else: pp = puzzle.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.clock_running = self.clock_running fname = self.config.get_puzzle_file(self.puzzle) f = file(fname, 'w+') f.write(pp.to_binary()) f.close() def exit(self): self.config.set_positions(self.get_layout(self.cur_layout)) self.write_puzzle() gobject.source_remove(self.timeout) self.win.destroy() gtk.main_quit() def notify(self, msg, parent=None): if parent == None: parent = self.win dialog = gtk.MessageDialog(parent=parent, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=msg) dialog.connect("response", lambda dlg, resp: dlg.destroy()) dialog.show() def ask(self, msg, opts): dialog = gtk.MessageDialog(parent=self.win, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_QUESTION, message_format=msg) for (i, opt) in enumerate(opts): dialog.add_button(opt, i) dialog.set_default_response(i) dialog.show() r = dialog.run() dialog.destroy() return r def show_about_puzzle(self): msg = 'Title: ' + self.puzzle.title + '\n' msg += 'Author: ' + self.puzzle.author + '\n' msg += 'Copyright: ' + self.puzzle.copyright if self.puzzle.notebook != '': msg += '\n\nNotepad:\n' + self.puzzle.notebook mmsg = 'Title: ' + self.puzzle.title + '\n' mmsg += 'Author: ' + self.puzzle.author + '\n' mmsg += 'Copyright: ' + self.puzzle.copyright if self.puzzle.notebook != '': mmsg += '\n\nNotepad:\n' + self.puzzle.notebook dialog = gtk.MessageDialog(parent=self.win, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=msg) try: dialog.set_markup(mmsg) except: pass dialog.connect("response", lambda dlg, resp: dlg.destroy()) dialog.show() def show_keyboard_shortcuts(self): msg = ('arrow-key: Move around or change direction\n' 'tab: Next word\n' 'ctrl + tab: Previous word\n' 'control + arrow-key: Move around without changing direction\n' 'backspace: Back up and delete letter\n' 'delete: Clear the current letter\n' 'control + delete: Clear the current word\n' 'space: Clear letter and move to next space\n' 'control + backspace: Delete part of perpendicular word' ) dialog = gtk.MessageDialog(parent=self.win, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=msg) dialog.connect("response", lambda dlg, resp: dlg.destroy()) dialog.show() def show_about(self): dialog = gtk.AboutDialog() try: dialog.set_transient_for(self.win) dialog.set_modal(True) except: pass dialog.set_name('Xword') dialog.set_version(__init__.__version__) dialog.set_license(__init__.__license__) dialog.set_authors( ['Cameron Dale \n' + 'Bill McCloskey \n' + 'Maemo Port: Bradley Bell \n' + 'and Terrence Fleury ']) dialog.set_website('http://x-word.org') dialog.set_website_label('x-word.org') dialog.connect('response', lambda *args: dialog.destroy()) dialog.show() 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 = grid.GridWidget(self.puzzle, self.control) 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.config.set_layout(index) positions = config.LAYOUTS[index][1] self.cur_layout = self.generate_layout(positions) self.vbox.pack_start(self.cur_layout, True, True, 0) self.config.set_positions(self.get_layout(self.cur_layout)) 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 state_event(self, w, event): state = int(event.new_window_state) maximized = (state & gtk.gdk.WINDOW_STATE_MAXIMIZED) <> 0 if (self.maximized != maximized): self.maximized = maximized self.config.set_maximized(maximized) def resize_window(self, widget, allocation): if not self.maximized: self.config.set_window_size(self.win.get_size()) def update_recent_menu(self): recent = self.config.recent_list() for (i, (title, hashcode)) in enumerate(recent): action = self.actiongroup.get_action('Recent%d' % i) action.set_sensitive(True) action.set_property('label', title) def activate_clock(self, active): action = self.actiongroup.get_action('Clock') action.set_active(active) def is_clock_active(self): action = self.actiongroup.get_action('Clock') return action.get_active() def set_clock_time(self, t): action = self.actiongroup.get_action('Clock') action.set_property('label', time_str(t)) action.set_property('is-important', True) def create_ui(self): icons = gtk.IconFactory() for (stock_id, filename) in stock_items: path = os.path.join(HOME_PATH, filename) pixbuf = gtk.gdk.pixbuf_new_from_file(path) iconset = gtk.IconSet(pixbuf) icons.add(stock_id, iconset) icons.add_default() ui = gtk.UIManager() accelgroup = ui.get_accel_group() self.win.add_accel_group(accelgroup) actiongroup = gtk.ActionGroup('XwordActions') self.actiongroup = actiongroup def mk(action, stock_id, label=None, tooltip=None): return (action, stock_id, label, None, tooltip, self.action_callback) actiongroup.add_actions([ mk('MenuFile', None, '_File'), mk('Open', gtk.STOCK_OPEN, tooltip='Open another puzzle'), mk('MenuRecent', None, 'Open recent'), mk('Save', gtk.STOCK_SAVE, tooltip='Save this puzzle'), mk('PuzzleOrganizer', gtk.STOCK_DIRECTORY, 'Puzzle Organizer', 'Open the Xword Puzzle Organizer'), mk('AboutPuzzle', gtk.STOCK_INFO, 'About puzzle', 'About this puzzle'), mk('ShowNotepad', 'xw-notepad', 'Show notepad', 'Display the notepad attached to this puzzle'), mk('PageSetup', None, 'Page setup...'), mk('Print', gtk.STOCK_PRINT, tooltip='Print this puzzle'), mk('Close', gtk.STOCK_CLOSE), mk('Quit', gtk.STOCK_QUIT), mk('Recent0', None, 'No recent item'), mk('Recent1', None, 'No recent item'), mk('Recent2', None, 'No recent item'), mk('Recent3', None, 'No recent item'), mk('Recent4', None, 'No recent item'), mk('MenuEdit', None, 'Edit'), mk('ClearWord', None, 'Clear word'), mk('ClearPuzzle', None, 'Clear puzzle'), mk('MenuHints', None, 'Hints'), mk('CheckLetter', None, 'Check letter'), mk('CheckWord', 'xw-check-word', 'Check word', 'Check the current word'), mk('CheckPuzzle', 'xw-check-puzzle', 'Check puzzle', 'Check the entire puzzle'), mk('SolveLetter', None, 'Solve letter'), mk('SolveWord', 'xw-solve-word', 'Solve word', 'Solve the current word'), mk('SolvePuzzle', 'xw-solve-puzzle', 'Solve puzzle', 'Solve the entire puzzle'), mk('MenuPreferences', None, 'Preferences'), mk('MenuLayout', None, 'Layout'), mk('Lay0', None, ''), mk('Lay1', None, ''), mk('Lay2', None, ''), mk('Lay3', None, ''), mk('Lay4', None, ''), mk('Lay5', None, ''), mk('Lay6', None, ''), mk('MenuHelp', None, '_Help'), mk('Shortcuts', None, 'Keyboard shortcuts'), mk('About', None, 'About'), ]) def mktog(action, stock_id, label, active, tooltip=None): return (action, stock_id, label, None, tooltip, self.action_callback, active) actiongroup.add_toggle_actions([ mktog('SkipFilled', None, 'Skip filled squares', self.config.get_skip_filled()), mktog('StartTimer', None, 'Start timer automatically', self.start_timer), mktog('Clock', 'xw-clock', 'Clock', self.clock_running, 'Start/Stop the clock running'), ]) ui.insert_action_group(actiongroup, 0) ui.add_ui_from_string(ui_description) self.update_recent_menu() for (i, layout) in enumerate(config.LAYOUTS): action = self.actiongroup.get_action('Lay%d' % i) action.set_property('label', layout[0]) self.menubar = ui.get_widget('/Menubar') self.toolbar = ui.get_widget('/Toolbar') self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ) def select_layout(self, index): if index <> self.layout: self.set_layout(index) def action_callback(self, action): name = action.get_property('name') if name == 'Quit': self.exit() elif name == 'Close': self.exit() elif name == 'SkipFilled': self.config.set_skip_filled(not self.config.get_skip_filled()) elif name == 'StartTimer': self.start_timer = not self.start_timer elif name == 'Open': self.open_file() elif name == 'Save': self.save_file() elif name == 'PuzzleOrganizer': self.open_organizer() elif name == 'Print': self.print_puzzle() elif name == 'PageSetup': self.page_setup() elif name == 'About': self.show_about() elif name == 'AboutPuzzle': self.show_about_puzzle() elif name == 'ShowNotepad': self.notify('This puzzle has a notepad attached:\n' + self.puzzle.notebook) elif name == 'Shortcuts': self.show_keyboard_shortcuts() elif name.startswith('Recent'): index = int(name[len('Recent'):]) self.open_recent(index) elif name.startswith('Lay'): index = int(name[len('Lay'):]) self.select_layout(index) elif name == 'CheckLetter': self.control.check_letter() elif name == 'CheckWord': self.control.check_word() elif name == 'CheckPuzzle': self.control.check_puzzle() elif name == 'SolveLetter': self.control.solve_letter() elif name == 'SolveWord': self.control.solve_word() elif name == 'SolvePuzzle': self.control.solve_puzzle() elif name == 'ClearWord': self.control.clear_word() elif name == 'ClearPuzzle': self.control.clear_puzzle() elif name == 'Clock': self.clock_running = not self.clock_running if self.clock_running: self.clock_start = time.time() else: self.clock_time += (time.time() - self.clock_start) def create_list(self, mode): if mode == ACROSS: label = 'Across' else: label = 'Down' tree = gtk.TreeView() renderer = gtk.CellRendererText() renderer.set_property('wrap-width', 200) renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR) column = gtk.TreeViewColumn(label, renderer, text=1, strikethrough=2) 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, bool) i = 0 for (n, clue) in self.control.get_clues(mode): self.tree_paths[mode][n] = i store.append((n, '%d. %s' % (n, clue), self.control.is_word_filled(mode, n))) 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 letter_update(self, x, y): (empty, filled) = self.control.count_cells() total = empty+filled if total == 0: pct = 0.0 else: pct = float(filled)/total*100.0 self.status_bar.set_right_label('%d of %d squares filled in (%.0f%%)' % (filled, total, pct)) def update_mode(mode): if self.puzzle.is_mode_valid(x, y, mode): n = self.puzzle.number(x, y, mode) filled = self.control.is_word_filled(mode, n) store = self.trees[mode].get_model() it = store.get_iter(self.tree_paths[mode][n]) store.set_value(it, 2, filled) update_mode(ACROSS) update_mode(DOWN) def across_update(self, an): if self.tree_paths.has_key(ACROSS): selection = self.trees[ACROSS].get_selection() selection.select_path(self.tree_paths[ACROSS][an]) tree = self.trees[ACROSS] tree.scroll_to_cell(self.tree_paths[ACROSS][an]) def down_update(self, dn): if self.tree_paths.has_key(DOWN): selection = self.trees[DOWN].get_selection() selection.select_path(self.tree_paths[DOWN][dn]) tree = self.trees[DOWN] tree.scroll_to_cell(self.tree_paths[DOWN][dn]) 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) self.set_clock_time(total) self.control.idle_event() return True def button_event(self, widget, event, puzzle): if event.type is gtk.gdk.BUTTON_PRESS: x = event.x + puzzle.widget.get_hadjustment().get_value() y = event.y + puzzle.widget.get_vadjustment().get_value() (x, y) = puzzle.translate_position(x, y) mode = self.control.get_mode() if event.button is 3: mode = 1-mode self.control.change_position(x, y, mode) def key_event(self, item, event): name = gtk.gdk.keyval_name(event.keyval) c = self.control ctl = (event.state & gtk.gdk.CONTROL_MASK) != 0 if name == 'Right': c.move(ACROSS, 1, change_dir=not ctl) elif name == 'Left': c.move(ACROSS, -1, change_dir=not ctl) elif name == 'Up': c.move(DOWN, -1, change_dir=not ctl) elif name == 'Down': c.move(DOWN, 1, change_dir=not ctl) elif name == 'BackSpace' and ctl: c.kill_perpendicular() elif name == 'BackSpace' and not ctl: c.back_space() elif name == 'Delete' and ctl: c.clear_word() elif name == 'Delete' and not ctl: c.clear_letter() elif name == 'space': c.forward_space() elif name == 'Return' or name == 'Tab': c.next_word(1) elif name == 'ISO_Left_Tab': c.next_word(-1) else: return False return True def puzzle_key_event(self, item, event): if (event.state & ~gtk.gdk.LOCK_MASK) != 0: return False name = gtk.gdk.keyval_name(event.keyval) if name is None: return False if len(name) is 1 and name.isalpha(): if self.start_timer: self.activate_clock(True) self.control.input_char(self.skip_filled, name) return True else: return False def puzzle_finished(self): self.notify('You have solved the puzzle!') if self.clock_running: self.activate_clock(False) def puzzle_filled(self): self.notify('The puzzle is completely filled.') if self.clock_running: self.activate_clock(False) def check_result(self, correct): if correct: msg = 'No mistakes found' else: msg = 'Incorrect.' self.status_bar.set_status(msg) def open_file(self): 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) default_loc = self.config.get_default_loc() if default_loc: dlg.set_current_folder(default_loc) response = dlg.run() if response == gtk.RESPONSE_OK: fname = dlg.get_filename() dlg.destroy() self.do_open_file(fname) else: dlg.destroy() def save_file(self): 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) default_loc = self.config.get_default_loc() if default_loc: dlg.set_current_folder(default_loc) response = dlg.run() if response == gtk.RESPONSE_OK: self.do_save_file(dlg.get_filename()) dlg.destroy() def open_organizer(self): screen = wnck.screen_get_default() while gtk.events_pending(): gtk.main_iteration() found = False for window in screen.get_windows(): # select a window depending on WindowID, title, icon etc if window.get_name().startswith('Xword Organizer'): found = True window.activate(gtk.get_current_event_time()) if not found: p = subprocess.Popen([sys.argv[0]]) def page_setup(self): if has_print: pr = printing.PuzzlePrinter(self.puzzle) pr.do_page_setup(self.win) else: self.notify('Printing support is not available (need GTK 2.10+).') def print_puzzle(self): if has_print: pr = printing.PuzzlePrinter(self.puzzle) pr.print_puzzle(self.win) else: self.notify('Printing support is not available (need GTK 2.10+).') if __name__ == '__main__': if len(sys.argv) <> 2: fname = None else: fname = sys.argv[1] w = MainWindow(fname) gtk.main() xword-2.0.0~rc2/xword/model.py0000755000175000017500000003641711613113462015214 0ustar cdalecdaleimport puzzle import config import pygtk pygtk.require('2.0') import gtk import gobject import os.path import datetime DOW_SORT_ORDER = {'Mon': 0, 'Tue': 1, 'Wed': 2, 'Thu': 3, 'Fri': 4, 'Sat': 5, 'Sun': 6} INNER_COLUMNS = 14 INNER_HASHCODE = 0 INNER_TITLE = 1 INNER_AUTHOR = 2 INNER_COPYRIGHT = 3 INNER_HSIZE = 4 INNER_VSIZE = 5 INNER_SQUARES = 6 INNER_COMPLETE = 7 INNER_ERRORS = 8 INNER_CHEATS = 9 INNER_LOCATION = 10 INNER_DATE = 11 INNER_SOURCE = 12 INNER_TITLE2 = 13 MODEL_COLOUR = 0 MODEL_DATE = 1 MODEL_DOW = 2 MODEL_SOURCE = 3 MODEL_TITLE = 4 MODEL_AUTHOR = 5 MODEL_SIZE = 6 MODEL_COMPLETE = 7 MODEL_ERRORS = 8 MODEL_CHEATS = 9 MODEL_LOCATION = 10 HEADER_NYTIMES = 'NY Times, ' AUTHOR_THINKS = 'Michael Curl' COPYRIGHT_THINKS = 'Thinks.com' HEADER_THINKS = 'Daily Crossword :' #AUTHOR_CROS_SYNERGY = 'Bob Klahn' COPYRIGHT_CROS_SYNERGY = 'CrosSynergy' AUTHOR_MACNAMARA = 'Fred Piscop' COPYRIGHT_MACNAMARA = 'MacNamara\'s Band' SOURCE_NYTIMES = 'NY Times' SOURCE_THINKS = 'Thinks.com' SOURCE_CROS_SYNERGY = 'CrosSynergy' SOURCE_MACNAMARA = 'MacNamara\'s Band' def analyze_puzzle(p, f): if p.title.startswith(HEADER_NYTIMES): (date, title) = analyze_NYTimes_puzzle(p, f) source = SOURCE_NYTIMES elif p.copyright.find(COPYRIGHT_CROS_SYNERGY) >= 0: (date, title) = analyze_CrosSynergy_puzzle(p, f) source = SOURCE_CROS_SYNERGY elif p.copyright.find(AUTHOR_MACNAMARA) >= 0 or p.copyright.find(COPYRIGHT_MACNAMARA) >= 0: (date, title) = analyze_Macnamara_puzzle(p, f) source = SOURCE_MACNAMARA elif p.copyright.find(COPYRIGHT_THINKS) >= 0 or p.copyright.find(AUTHOR_THINKS) >= 0 or p.author.find(AUTHOR_THINKS) >= 0: (date, title) = analyze_Thinks_puzzle(p, f) source = SOURCE_THINKS else: date = None title = p.title.strip() source = p.copyright.strip() squares = 0 for ((x, y), a) in p.answers.items(): if a != '.': squares += 1 return (squares, date, title, source) def analyze_NYTimes_puzzle(p, f): title = ' '.join(p.title[len(HEADER_NYTIMES):].replace(u'\xa0', u' ').split(' ')[4:]) notePos = title.upper().find('NOTE:') if notePos >= 0: title = title[:notePos].strip() bracketStart = title.find('(') if bracketStart >= 0: title = title[:bracketStart].strip() dateStr = ' '.join(p.title[len(HEADER_NYTIMES):].replace(u'\xa0', u' ').split(' ')[:4]) date = datetime.datetime.strptime(dateStr, '%a, %b %d, %Y') return (date, title) def analyze_CrosSynergy_puzzle(p, f): title = p.title.replace(u'\xa0', u' ') dashPos = title.find('-') if dashPos >= 0: dateStr = title[:(dashPos-1)] title = title[(dashPos+1):].replace('"', ' ').strip() date = datetime.datetime.strptime(dateStr, '%B %d, %Y') return (date, title) def analyze_Macnamara_puzzle(p, f): title = p.title.replace(u'\xa0', u' ').strip() date = None return (date, title) def analyze_Thinks_puzzle(p, f): title = p.title.replace(u'\xa0', u' ').strip() date = None if title.startswith(HEADER_THINKS): dateStr = title[(len(HEADER_THINKS)+1):] date = datetime.datetime.strptime(dateStr, '%B %d') try: date = datetime.datetime.strptime(f, 'dc1-%Y-%m-%d.puz') except ValueError: pass return (date, title) class Model: def __init__(self, config): self.config = config self.model = None self.outer_model = None def create_model(self, update_func, done_func): self.model = None self.outer_model = None # Columns: hashcode, title, author, copyright, hsize, vsize, squares, complete, errors, cheats, location, date, source, title model = gtk.ListStore(str, str, str, str, int, int, int ,int, int, int, str, gobject.TYPE_PYOBJECT, str, str) modelHashes = {} scanTotal = float(len(self.config.recent_list()) + len(os.listdir(config.CONFIG_PUZZLE_DIR))) for dir in self.config.get_organizer_directories(): if (os.path.exists(dir)): scanTotal += float(len(os.listdir(dir))) scanned = 0 for dir in self.config.get_organizer_directories(): if (os.path.exists(dir)): for f in os.listdir(dir): scanned += 1 update_func(float(scanned) / scanTotal) while gtk.events_pending(): gtk.main_iteration() if done_func(): return fname = os.path.join(dir, f) if fname.endswith('.puz') and os.path.isfile(fname): p = puzzle.Puzzle(fname) hashcode = p.hashcode() (squares, date, title, source) = analyze_puzzle(p, f) if not date: date = datetime.datetime.fromtimestamp(os.path.getmtime(fname)) if hashcode in modelHashes: if model.iter_is_valid(modelHashes[hashcode]): model.set(modelHashes[hashcode], INNER_TITLE, p.title.replace(u'\xa0', u' ').strip(), INNER_AUTHOR, p.author.replace(u'\xa0', u' ').strip(), INNER_COPYRIGHT, p.copyright.replace(u'\xa0', u' ').strip(), INNER_LOCATION, fname, INNER_DATE, date, INNER_SOURCE, source, INNER_TITLE2, title) else: for row in model: if row[INNER_HASHCODE] == hashcode: row[INNER_TITLE] = p.title.replace(u'\xa0', u' ').strip() row[INNER_AUTHOR] = p.author.replace(u'\xa0', u' ').strip() row[INNER_COPYRIGHT] = p.copyright.replace(u'\xa0', u' ').strip() row[INNER_LOCATION] = fname row[INNER_DATE] = date row[INNER_SOURCE] = source row[INNER_TITLE2] = title modelHashes[hashcode] = row.iter break else: iter = model.append() model.set(iter, INNER_HASHCODE, hashcode, INNER_TITLE, p.title.replace(u'\xa0', u' ').strip(), INNER_AUTHOR, p.author.replace(u'\xa0', u' ').strip(), INNER_COPYRIGHT, p.copyright.replace(u'\xa0', u' ').strip(), INNER_HSIZE, p.width, INNER_VSIZE, p.height, INNER_SQUARES, squares, INNER_COMPLETE, 0, INNER_ERRORS, 0, INNER_CHEATS, 0, INNER_LOCATION, fname, INNER_DATE, date, INNER_SOURCE, source, INNER_TITLE2, title) modelHashes[hashcode] = iter for (title, hash) in self.config.recent_list(): scanned += 1 update_func(float(scanned) / scanTotal) while gtk.events_pending(): gtk.main_iteration() if done_func(): return fname = self.config.get_recent(hash) if fname and os.path.exists(fname): p = puzzle.Puzzle(fname) hashcode = p.hashcode() if hashcode not in modelHashes: (squares, date, title, source) = analyze_puzzle(p, '') if not date: date = datetime.datetime.fromtimestamp(os.path.getmtime(fname)) iter = model.append() model.set(iter, INNER_HASHCODE, hashcode, INNER_TITLE, p.title, INNER_AUTHOR, p.author, INNER_COPYRIGHT, p.copyright, INNER_HSIZE, p.width, INNER_VSIZE, p.height, INNER_SQUARES, squares, INNER_COMPLETE, 0, INNER_ERRORS, 0, INNER_CHEATS, 0, INNER_LOCATION, fname, INNER_DATE, date, INNER_SOURCE, source, INNER_TITLE2, title) modelHashes[hashcode] = iter for f in os.listdir(config.CONFIG_PUZZLE_DIR): scanned += 1 update_func(float(scanned) / scanTotal) while gtk.events_pending(): gtk.main_iteration() if done_func(): return fname = os.path.join(config.CONFIG_PUZZLE_DIR, f) if os.path.isfile(fname): pp = self.load_puzzle(fname) if pp: hsize = 0 vsize = 0 squares = 0 complete = 0 errors = 0 cheats = 0 for ((x, y), r) in pp.responses.items(): if x > hsize: hsize = x if y > vsize: vsize = y if r != '.': squares += 1 if r != '': complete += 1 for ((x, y), e) in pp.errors.items(): if e == puzzle.MISTAKE or e == puzzle.FIXED_MISTAKE: errors += 1 if e == puzzle.CHEAT: cheats += 1 if f in modelHashes: if model.iter_is_valid(modelHashes[f]): model.set(modelHashes[f], INNER_COMPLETE, complete, INNER_ERRORS, errors, INNER_CHEATS, cheats) else: for row in model: if row[INNER_HASHCODE] == f: row[INNER_COMPLETE] = complete row[INNER_ERRORS] = errors row[INNER_CHEATS] = cheats modelHashes[f] = row.iter break else: iter = model.append() model.set(iter, INNER_HASHCODE, f, INNER_TITLE, '', INNER_AUTHOR, '', INNER_COPYRIGHT, '', INNER_HSIZE, hsize+1, INNER_VSIZE, vsize+1, INNER_SQUARES, squares, INNER_COMPLETE, complete, INNER_ERRORS, errors, INNER_CHEATS, cheats, INNER_LOCATION, '', INNER_DATE, None, INNER_SOURCE, '', INNER_TITLE2, '') modelHashes[f] = iter # self.status_bar.set_right_label('Scanned %d crossword files' % len(modelHashes)) self.model = model def filter_model(self, model): def visible_func(model, iter, data=None): return not not model.get_value(iter, INNER_LOCATION) #return True def modify_func(model, iter, column, data=None): listmodel = model.get_model() row = listmodel.get(model.convert_iter_to_child_iter(iter), *range(INNER_COLUMNS)) if column == MODEL_COLOUR: if not row[INNER_LOCATION]: return 'red' elif row[INNER_SQUARES] == row[INNER_COMPLETE]: return 'green' elif row[INNER_COMPLETE] > 0: return 'blue' else: return 'black' if column == MODEL_DATE: if row[INNER_DATE]: return row[INNER_DATE].date().isoformat() else: return '' elif column == MODEL_DOW: if row[INNER_DATE]: return row[INNER_DATE].strftime('%a') else: return '' elif column == MODEL_SOURCE: return row[INNER_SOURCE] elif column == MODEL_TITLE: return row[INNER_TITLE2] elif column == MODEL_AUTHOR: return row[INNER_AUTHOR] elif column == MODEL_SIZE: return str(row[INNER_HSIZE]) + 'x' + str(row[INNER_VSIZE]) elif column == MODEL_COMPLETE: if row[INNER_SQUARES] > 0: return '%0.1f%%' % (100.0 * float(row[INNER_COMPLETE]) / float(row[INNER_SQUARES])) else: return '' elif column == MODEL_ERRORS: return row[INNER_ERRORS] elif column == MODEL_CHEATS: return row[INNER_CHEATS] elif column == MODEL_LOCATION: return row[INNER_LOCATION] modelFilter = model.filter_new() modelFilter.set_visible_func(visible_func) # Columns: colour, date, DOW, source, title, author, size, complete, errors, cheats, location modelFilter.set_modify_func((str, str, str, str, str, str, str, str, int, int, str), modify_func) return modelFilter def sort_model(self, model): def sort_func(model, iter1, iter2, column): item1 = model.get_value(iter1, column) item2 = model.get_value(iter2, column) if column == MODEL_DOW: if item1 in DOW_SORT_ORDER: item1 = DOW_SORT_ORDER[item1] if item2 in DOW_SORT_ORDER: item2 = DOW_SORT_ORDER[item2] elif column == MODEL_COMPLETE: if item1.endswith('%'): item1 = float(item1[:(len(item1)-1)]) if item2.endswith('%'): item2 = float(item2[:(len(item2)-1)]) if item1 < item2: return -1 elif item2 < item1: return 1 else: return 0 modelSort = gtk.TreeModelSort(model) modelSort.set_sort_func(MODEL_DOW, sort_func, MODEL_DOW) modelSort.set_sort_func(MODEL_COMPLETE, sort_func, MODEL_COMPLETE) return modelSort def get_model(self): if not self.model: return None modelFilter = self.filter_model(self.model) modelSort = self.sort_model(modelFilter) self.outer_model = modelSort return modelSort def get_location(self, path): if not self.outer_model: return None iterSort = self.outer_model.get_iter(path) return self.get_location_iter(iterSort) def get_location_iter(self, iterSort): if not self.outer_model: return None modelFilter = self.outer_model.get_model() iterFilter = self.outer_model.convert_iter_to_child_iter(None, iterSort) iter = modelFilter.convert_iter_to_child_iter(iterFilter) location = self.model.get_value(iter, INNER_LOCATION) return location def load_puzzle(self, fname): pp = None try: f = file(fname, 'r') except IOError: f = None if f: try: pp = puzzle.PersistentPuzzle() pp.from_binary(f.read()) except: print "Ignoring corrupt puzzle: " + fname finally: f.close() return pp xword-2.0.0~rc2/xword/puzzle.py0000755000175000017500000003031211613150143015426 0ustar cdalecdaleNO_ERROR = 0 MISTAKE = 1 FIXED_MISTAKE = 2 CHEAT = 3 ACROSS = 0 DOWN = 1 def make_hash(data): try: from hashlib import md5 m = md5() except: import md5 m = md5.new() m.update(data) return m.hexdigest() class BinaryFile: def __init__(self, filename=None): if type(filename) == type(''): f = file(filename, 'rb') else: f = filename 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 length(self): return len(self.data) def position(self): return self.index 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_byte(self): return ord(self.read_char()) def read_chars(self, count): r = '' for i in range(count): r += self.read_char() return r def read_bytes(self, count): r = [] for i in range(count): r.append(self.read_byte()) return r 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() return unicode(s, 'cp1252') # This is the Windows character set def hashcode(self): return make_hash(''.join(self.data)) class PersistentPuzzle: def __init__(self): self.responses = {} self.errors = {} self.clock = 0 self.clock_running = False 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) data = (width, height, int(self.clock), bin, int(self.clock_running)) return '%d %d %d %s %d' % data 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 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] try: pos += count + 1 # skip the space (self.clock_running, pos) = self.get_int(bin, pos) except ValueError: self.clock_running = False 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 f.seek(0x2c) self.width = f.read_byte() self.height = f.read_byte() f.seek(0x32) self.locked = (f.read_byte() == 0x04) 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): return s self.title = massage(f.read_string()) self.author = massage(f.read_string()) self.copyright = massage(f.read_string()) self.clues = [] def read_clue(): clue = massage(f.read_string()) self.clues.append(clue) return clue self.setup(read_clue) self.notebook = massage(f.read_string()) while f.position() < f.length(): code = f.read_chars(4) count = f.read_byte() + 256*f.read_byte() junk = f.read_bytes(2) data = f.read_bytes(count) zero = f.read_byte() self.process_section(code, data) def setup(self, read_clue): 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 = {} self.circles = {} number = 1 for y in range(self.height): for x in range(self.width): # NYTimes: April 30, 2006 is_fresh_x = (self.is_black(x-1, y) and not self.is_black(x+1, y)) is_fresh_y = (self.is_black(x, y-1) and not self.is_black(x, y+1)) if not self.is_black(x, y): if is_fresh_x: self.across_map[x, y] = number self.across_clues[number] = read_clue() else: if self.across_map.has_key((x-1, y)): self.across_map[x, y] = self.across_map[x-1, y] if is_fresh_y: self.down_map[x, y] = number self.down_clues[number] = read_clue() else: if self.down_map.has_key((x, y-1)): self.down_map[x, y] = self.down_map[x, y-1] if is_fresh_x or is_fresh_y: 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 #else: # self.across_map[x, y] = 0 # self.down_map[x, y] = 0 self.max_number = number-1 def process_section(self, code, data): if code == 'GEXT': index = 0 for y in range(self.height): for x in range(self.width): if data[index] == 0x80: self.circles[x, y] = True index += 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.clues) return make_hash(s1 + s2) 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_locked(self): return self.locked def is_black(self, x, y): return self.responses.get((x, y), '.') == '.' def is_circled(self, x, y): return self.circles.has_key((x, y)) def is_empty(self): for ((x, y), r) in self.responses.items(): if r != '.' and r != '': return False return True def is_word_filled(self, x, y, dir): if not self.is_mode_valid(x, y, dir): return False n = self.number(x, y, dir) (x, y) = self.number_map[n] hit = False while not hit: if self.responses[x, y] == '': return False ((x, y), hit) = self.next_cell(x, y, dir, 1, False) return True def clue(self, x, y, mode): assert self.is_mode_valid(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): assert self.is_mode_valid(x, y, mode) return self.mode_maps[mode][x, y] def cell_has_number(self, x, y): return self.number_rev_map.has_key((x, y)) def number_of_cell(self, x, y): return self.number_rev_map[x, y] def cell_of_number(self, number): return self.number_map[number] def is_mode_valid(self, x, y, mode): return self.mode_maps[mode].has_key((x, y)) def next_cell(self, x, y, mode, incr, skip_black): (x0, y0) = (x, y) while x >= 0 and x < self.width and y >= 0 and y < self.height: if mode is ACROSS: x += incr else: y += incr if not skip_black or not self.is_black(x, y): break if self.is_black(x, y): return ((x0, y0), 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: (x1, y1) = self.number_map[self.mode_maps[mode][x, y]] r = self.find_blank_cell_recursive(x1, y1, mode, incr) if r == None: return (x, y) else: return r 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 is_puzzle_filled(self): for x in range(self.width): for y in range(self.height): if not self.is_black(x, y) and self.responses[x, y] == '': return False return True def incr_number(self, x, y, mode, incr): assert self.is_mode_valid(x, y, mode) 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 initial_number(self, mode): n = 1 while True: if mode == ACROSS and self.is_across[n]: break if mode == DOWN and self.is_down[n]: break n += 1 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 def set_letter(self, x, y, c): self.responses[x, y] = c def get_letter(self, x, y): return self.responses[x, y] def get_answer(self, x, y): return self.answers[x, y] def get_error(self, x, y): return self.errors[x, y] def set_error(self, x, y, err): self.errors[x, y] = err def is_blank(self, x, y): return self.responses[x, y] == '' def get_cells(self): return self.responses.keys() xword-2.0.0~rc2/xword/organizer.py0000755000175000017500000003654611613132575016125 0ustar cdalecdaleimport puzzle import printing import model import config import __init__ import pygtk pygtk.require('2.0') import gtk import gtk.gdk import gobject import pango try: x = gtk.PrintOperation has_print = True except: has_print = False import sys import subprocess ui_description = ''' ''' class StatusBar: def __init__(self): self.frame = gtk.Frame() self.hbox = gtk.HBox() self.left_label = gtk.Label('Label') self.right_label = gtk.Label('Label') self.hbox.pack_start(self.left_label, True, True) self.hbox.pack_end(self.right_label, False, False, 20) self.frame.add(self.hbox) self.frame.set_shadow_type(gtk.SHADOW_NONE) self.left_label.set_ellipsize(pango.ELLIPSIZE_END) self.left_label.set_alignment(0.0, 0.0) def set_status(self, msg): self.left_label.set_text(msg) def set_right_label(self, label): self.right_label.set_text(label) class OrganizerWindow: def __init__(self): self.done = False gobject.idle_add(self.init) def init(self): self.config = config.XwordConfig() self.status_bar = StatusBar() window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.win = window def destroy(widget, data=None): self.done = True self.exit() handler = window.connect('destroy', destroy) if not self.config.get_organizer_directories(): opts = ['Skip', 'Configure'] msg = ('You have not configured a puzzle directory to scan for puzzle' + ' files. Would you like to configure it now?') if self.ask(msg, opts) == 1: self.choose_directory(False) pbar = self.create_progress_bar(window) self.model = model.Model(self.config) self.model.create_model(pbar.set_fraction, self.is_done) if self.done: return self.win.handler_disconnect(handler) self.win.destroy() self.organizer_maximized = self.config.get_organizer_maximized() win = gtk.Window() self.win = win self.handler = win.connect('destroy', destroy) win.connect('size-allocate', self.resize_window) win.connect('window-state-event', self.state_event) organizer_window_size = self.config.get_organizer_window_size() win.resize(organizer_window_size[0], organizer_window_size[1]) if self.organizer_maximized: win.maximize() mainbox = gtk.VBox() win.add(mainbox) mainbox = mainbox self.create_ui() mainbox.pack_start(self.menubar, False, False, 0) mainbox.pack_start(self.toolbar, False, False, 0) win.set_title('Xword Organizer') scroll = gtk.ScrolledWindow() mainbox.pack_start(scroll, True, True, 0) modelSort = self.model.get_model() modelSort.set_sort_column_id(model.MODEL_DATE, gtk.SORT_DESCENDING) tree = self.create_list_view(modelSort) scroll.add(tree) self.tree = tree mainbox.pack_start(self.status_bar.frame, False, False, 0) self.enable_controls(False) win.show_all() self.status_bar.set_status('Double-click a crossword to open it') tree.grab_focus() def create_progress_bar(self, window): window.set_position(gtk.WIN_POS_CENTER) window.set_resizable(True) window.set_title("Scanning Crossword Files") window.set_border_width(0) vbox = gtk.VBox(False, 5) vbox.set_border_width(10) window.add(vbox) vbox.show() # Create the ProgressBar pbar = gtk.ProgressBar() pbar.set_size_request(300, -1) vbox.pack_start(pbar, False, False, 5) pbar.show() separator = gtk.HSeparator() vbox.pack_start(separator, False, False, 0) separator.show() # Create a centering alignment object align = gtk.Alignment(0.5, 0.5, 0, 0) vbox.pack_start(align, False, False, 5) # Add a button to exit the program button = gtk.Button("Cancel") button.connect_object("clicked", gtk.Widget.destroy, window) align.add(button) align.show() # This makes it so the button is the default. button.set_flags(gtk.CAN_DEFAULT) # This grabs this button to be the default button. Simply hitting # the "Enter" key will cause this button to activate. button.grab_default() button.show() window.show() return pbar def is_done(self): return self.done def create_list_view(self, modelSort): tree = gtk.TreeView(modelSort) tree.set_headers_clickable(True) tree.set_rules_hint(True) def addColumn(name, columnId): cell = gtk.CellRendererText() column = gtk.TreeViewColumn(name, cell, text=columnId, foreground=model.MODEL_COLOUR) column.set_sort_column_id(columnId) tree.append_column(column) addColumn('Date', model.MODEL_DATE) addColumn('Weekday', model.MODEL_DOW) addColumn('Source', model.MODEL_SOURCE) addColumn('Title', model.MODEL_TITLE) addColumn('Author', model.MODEL_AUTHOR) addColumn('Size', model.MODEL_SIZE) addColumn('Complete', model.MODEL_COMPLETE) addColumn('Errors', model.MODEL_ERRORS) addColumn('Cheats', model.MODEL_CHEATS) addColumn('Location', model.MODEL_LOCATION) tree.connect('row-activated', self.row_activated) tree.connect('cursor-changed', self.cursor_changed) return tree def refresh_model(self): window = gtk.Window(gtk.WINDOW_TOPLEVEL) def destroy(widget, data=None): self.done = True handler = window.connect('destroy', destroy) pbar = self.create_progress_bar(window) self.done = False self.model.create_model(pbar.set_fraction, self.is_done) if not self.done: window.destroy() modelSort = self.model.get_model() modelSort.set_sort_column_id(model.MODEL_DATE, gtk.SORT_DESCENDING) self.tree.set_model(modelSort) def row_activated(self, treeview, path, view_column, data=None): location = self.model.get_location(path) self.launch_puzzle(location) def cursor_changed(self, treeview, data=None): selection = self.tree.get_selection() (model, iter) = selection.get_selected() if iter: self.enable_controls(True) def launch_puzzle(self, location): p = subprocess.Popen([sys.argv[0], location]) def enable_controls(self, enabled): def enable(a, x): action = self.actiongroup.get_action(a) action.set_sensitive(x) enable('PageSetup', enabled) enable('Print', enabled) def open_recent(self, index): (title, hashcode) = self.config.recent_list()[index] fname = self.config.get_recent(hashcode) self.launch_puzzle(fname) def exit(self): self.win.destroy() gtk.main_quit() def notify(self, msg, parent=None): if parent == None: parent = self.win dialog = gtk.MessageDialog(parent=parent, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=msg) dialog.connect("response", lambda dlg, resp: dlg.destroy()) dialog.show() def ask(self, msg, opts): dialog = gtk.MessageDialog(parent=self.win, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_QUESTION, message_format=msg) for (i, opt) in enumerate(opts): dialog.add_button(opt, i) dialog.set_default_response(i) dialog.show() r = dialog.run() dialog.destroy() return r def show_about(self): dialog = gtk.AboutDialog() try: dialog.set_transient_for(self.win) dialog.set_modal(True) except: pass dialog.set_name('Xword') dialog.set_version(__init__.__version__) dialog.set_license(__init__.__license__) dialog.set_authors( ['Cameron Dale \n' + 'Bill McCloskey \n' + 'Maemo Port: Bradley Bell \n' + 'and Terrence Fleury ']) dialog.set_website('http://x-word.org') dialog.set_website_label('x-word.org') dialog.connect('response', lambda *args: dialog.destroy()) dialog.show() def state_event(self, w, event): state = int(event.new_window_state) organizer_maximized = (state & gtk.gdk.WINDOW_STATE_MAXIMIZED) <> 0 if (self.organizer_maximized != organizer_maximized): self.organizer_maximized = organizer_maximized self.config.set_organizer_maximized(organizer_maximized) def resize_window(self, widget, allocation): if not self.organizer_maximized: self.config.set_organizer_window_size(self.win.get_size()) def update_recent_menu(self): recent = self.config.recent_list() for (i, (title, hashcode)) in enumerate(recent): action = self.actiongroup.get_action('Recent%d' % i) action.set_sensitive(True) action.set_property('label', title) def create_ui(self): ui = gtk.UIManager() accelgroup = ui.get_accel_group() self.win.add_accel_group(accelgroup) actiongroup = gtk.ActionGroup('XwordOrganizerActions') self.actiongroup = actiongroup def mk(action, stock_id, label=None, tooltip=None): return (action, stock_id, label, None, tooltip, self.action_callback) actiongroup.add_actions([ mk('MenuFile', None, '_File'), mk('Open', gtk.STOCK_OPEN, tooltip='Open a puzzle file'), mk('Refresh', gtk.STOCK_REFRESH, tooltip='Refresh the scanned crossword files'), mk('MenuRecent', None, 'Open recent'), mk('PageSetup', None, 'Page setup...'), mk('Print', gtk.STOCK_PRINT, tooltip='Print the selected puzzle'), mk('Close', gtk.STOCK_CLOSE), mk('Quit', gtk.STOCK_QUIT), mk('Recent0', None, 'No recent item'), mk('Recent1', None, 'No recent item'), mk('Recent2', None, 'No recent item'), mk('Recent3', None, 'No recent item'), mk('Recent4', None, 'No recent item'), mk('MenuPreferences', None, 'Preferences'), mk('ChooseDirectory', None, 'Puzzle Directory...'), mk('MenuHelp', None, '_Help'), mk('About', None, 'About'), ]) def mktog(action, stock_id, label, active): return (action, stock_id, label, None, None, self.action_callback, active) ui.insert_action_group(actiongroup, 0) ui.add_ui_from_string(ui_description) self.update_recent_menu() self.menubar = ui.get_widget('/Menubar') self.toolbar = ui.get_widget('/Toolbar') def action_callback(self, action): name = action.get_property('name') if name == 'Quit': self.exit() elif name == 'Close': self.exit() elif name == 'Open': self.open_file() elif name == 'Refresh': self.refresh_model() elif name == 'Print': self.print_puzzle() elif name == 'PageSetup': self.page_setup() elif name == 'About': self.show_about() elif name == 'ChooseDirectory': self.choose_directory() elif name.startswith('Recent'): index = int(name[len('Recent'):]) self.open_recent(index) def open_file(self): 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) default_loc = self.config.get_default_loc() if default_loc: dlg.set_current_folder(default_loc) response = dlg.run() if response == gtk.RESPONSE_OK: fname = dlg.get_filename() dlg.destroy() self.launch_puzzle(fname) else: dlg.destroy() def choose_directory(self, refresh=True): dlg = gtk.FileChooserDialog("Choose Puzzle Directory to Scan...", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dlg.set_default_response(gtk.RESPONSE_OK) default_loc = self.config.get_default_loc() if len(self.config.get_organizer_directories()) > 0: default_loc = self.config.get_organizer_directories()[0] if default_loc: dlg.set_current_folder(default_loc) response = dlg.run() if response == gtk.RESPONSE_OK: dir = dlg.get_filename() dlg.destroy() self.config.set_organizer_directories([dir]) if refresh: self.refresh_model() else: dlg.destroy() def page_setup(self): if has_print: selection = self.tree.get_selection() (model, iter) = selection.get_selected() location = self.model.get_location_iter(iter) puz = puzzle.Puzzle(location) pr = printing.PuzzlePrinter(puz) pr.do_page_setup(self.win) else: self.notify('Printing support is not available (need GTK 2.10+).') def print_puzzle(self): if has_print: selection = self.tree.get_selection() (model, iter) = selection.get_selected() location = self.model.get_location_iter(iter) puz = puzzle.Puzzle(location) pr = printing.PuzzlePrinter(puz) pr.print_puzzle(self.win) else: self.notify('Printing support is not available (need GTK 2.10+).') if __name__ == '__main__': w = OrganizerWindow() gtk.main() xword-2.0.0~rc2/xword/printing.py0000755000175000017500000003514311613150062015736 0ustar cdalecdaleimport gtk import pango import math import puzzle ACROSS = puzzle.ACROSS DOWN = puzzle.DOWN MAIN_FONT = 'Bitstream Charter 9' BOLD_FONT = 'Bitstream Charter bold 9' SPACER = 2 LABEL_SPACER = 15 MIN_HEIGHT = 50 def measure_text(layout, text): layout.set_text(text) return layout.get_size()[0] / pango.SCALE def font_size(layout): return layout.get_size()[1] / pango.SCALE def clue_area_width(context): clue_layout = context.create_pango_layout() clue_layout.set_font_description(pango.FontDescription(MAIN_FONT)) spacer = '1000 Polite agreement' return measure_text(clue_layout, spacer) class FontInfo: def __init__(self, context): self.context = context self.clue_layout = context.create_pango_layout() desc = pango.FontDescription(MAIN_FONT) self.clue_layout.set_font_description(desc) self.label_layout = context.create_pango_layout() desc = pango.FontDescription(BOLD_FONT) self.label_layout.set_font_description(desc) self.num_layout = context.create_pango_layout() desc = pango.FontDescription(BOLD_FONT) self.num_layout.set_font_description(desc) self.col_width = clue_area_width(context) self.num_width = measure_text(self.num_layout, '100 ') self.text_width = (self.col_width - self.num_width) * 1.0 self.clue_layout.set_wrap(pango.WRAP_WORD_CHAR) self.clue_layout.set_width(int(self.text_width * pango.SCALE)) class LabelItem: def __init__(self, info, label, clue_item, extra_space): self.info = info self.label = label self.clue_item = clue_item self.extra_space = 0 if extra_space: self.extra_space = LABEL_SPACER self.label_height = font_size(self.info.label_layout) def height(self): return (self.extra_space + self.label_height + SPACER + self.clue_item.height()) def draw(self, cr, x, y): cr.move_to(x + self.info.num_width, y + self.extra_space) self.info.label_layout.set_text(self.label) cr.show_layout(self.info.label_layout) self.clue_item.draw(cr, x, y + self.extra_space + self.label_height + SPACER) class ClueItem: def __init__(self, info, n, text): self.info = info self.n = n self.text = text self.info.clue_layout.set_text(self.text) self.clue_height = font_size(self.info.clue_layout) def height(self): return self.clue_height def draw(self, cr, x, y): self.info.num_layout.set_text(str(self.n) + ' ') w = measure_text(self.info.num_layout, str(self.n) + ' ') cr.move_to(x + self.info.num_width - w, y) cr.show_layout(self.info.num_layout) cr.move_to(x + self.info.num_width, y) self.info.clue_layout.set_text(self.text) cr.show_layout(self.info.clue_layout) class ClueLayout: def __init__(self, puzzle, context, col_heights): self.puzzle = puzzle self.info = FontInfo(context) self.col_heights = col_heights self.items = [] self.measure_mode('ACROSS', ACROSS, False) self.measure_mode('DOWN', DOWN, True) self.layout_items() def measure_mode(self, label, mode, extra_space): 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] item = ClueItem(self.info, n, clue) if first: item = LabelItem(self.info, label, item, extra_space) first = False self.items.append(item) def try_layout(self, heights): col = 0 v = 0 first = True columns = [[]] for (i, item) in enumerate(self.items): if not first: v += SPACER first = False height = item.height() if v+height <= heights[col]: columns[col].append((v, item)) v += height else: first = True v = 0 col += 1 if col >= len(heights): return (False, columns, self.items[i:]) columns.append([]) columns[col].append((v, item)) v += height return (True, columns, []) def layout_items(self): vspace = 0 for item in self.items: vspace += item.height() total_vspace = 0 for h in self.col_heights: total_vspace += h assert total_vspace >= vspace # FIXME ncols = len(self.col_heights) trunc = (total_vspace - vspace)/ncols * 1.2 incr = (total_vspace - vspace)/ncols * 0.025 while trunc > 0: heights = self.col_heights[:] for i in range(len(heights)): heights[i] -= trunc if heights[i] < MIN_HEIGHT: heights[i] = 0 (ok, columns, rest) = self.try_layout(heights) if ok: break trunc -= incr else: assert False # FIXME self.columns = columns self.trunc = trunc def num_columns(self): return len(self.columns) def draw_column(self, col, cr, x0, y0): for (y, item) in self.columns[col]: item.draw(cr, x0, y0+y) LEFT_TOP = 0 LEFT_BOT = 1 RIGHT_TOP = 2 RIGHT_BOT = 3 class PuzzlePrinter: settings = None page_setup = None place = None print_answers = False enlarged = False def __init__(self, puzzle): self.puzzle = puzzle def draw_banner(self, r): (left, top, right, bottom) = r h = bottom - top size = int(h * 0.7) layout = self.context.create_pango_layout() layout.set_text(self.puzzle.title) while size > 5: desc = pango.FontDescription('serif ' + str(size)) layout.set_font_description(desc) if measure_text(layout, self.puzzle.title) < right-left: break size -= 1 width = measure_text(layout, self.puzzle.title) x0 = left + (right - left - width)/2 y0 = top + (h - font_size(layout)) / 2 self.cr.move_to(x0, y0) layout.set_text(self.puzzle.title) self.cr.show_layout(layout) def draw_box(self, x, y, r): (left, top, right, bottom) = r cr = self.cr cr.rectangle(left, top, right-left, bottom-top) cr.stroke() if self.puzzle.is_black(x, y): cr.rectangle(left, top, right-left, bottom-top) cr.fill() if self.puzzle.is_circled(x, y): cr.arc((left+right)/2, (top+bottom)/2, self.box_size/2, 0, 2*math.pi) cr.stroke() if self.puzzle.cell_has_number(x, y): n = self.puzzle.number_of_cell(x, y) self.num_layout.set_text(str(n)) cr.move_to(left + self.box_size*0.05, top + self.box_size*0.05) cr.show_layout(self.num_layout) if PuzzlePrinter.print_answers: w = measure_text(self.let_layout, self.puzzle.responses[x, y]) self.let_layout.set_text(self.puzzle.responses[x, y]) h = font_size(self.let_layout) x0 = left + (right - left - w)/2 y0 = top + (bottom - top)*0.6 - h/2 cr.move_to(x0, y0) cr.show_layout(self.let_layout) def min_puzzle_size(self, w, h): puzzle = self.puzzle self.banner_size = 18 bw = w/float(puzzle.width) bh = (h - 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, top, right, bottom) = r w = box_size * puzzle.width h = box_size * puzzle.height # self.cr.rectangle(left, top, right-left, bottom-top) # self.cr.stroke() banner_box = (left, top, right, top + self.banner_size) self.draw_banner(banner_box) left += ((right - left) - w)/2 top += self.banner_size self.num_layout = self.context.create_pango_layout() desc = pango.FontDescription('sans ' + str(box_size * 0.3)) self.num_layout.set_font_description(desc) self.let_layout = self.context.create_pango_layout() desc = pango.FontDescription('sans ' + str(box_size * 0.6)) self.let_layout.set_font_description(desc) for y in range(puzzle.height): for x in range(puzzle.width): r = (left + x*box_size, top + y*box_size, left + (x+1)*box_size, top + (y+1)*box_size) self.draw_box(x, y, r) def draw_page(self, op, context, page_nr): self.context = context self.cr = context.get_cairo_context() self.cr.set_source_rgb(0, 0, 0) self.cr.set_line_width(0.5) w = context.get_width() h = context.get_height() (left, top, right, bottom) = (0, 0, w, h) # self.cr.rectangle(0, 0, w, h) # self.cr.stroke() col_width = clue_area_width(context) num_cols = int(w / col_width) inner_width = col_width * num_cols (left, right) = ((w - inner_width)/2, (w + inner_width)/2) if PuzzlePrinter.enlarged: size_w = w size_h = h elif h > w: size_w = (right - left) * 0.75 size_h = (bottom - top)/2 else: size_w = (right - left)/2 size_h = (bottom - top) * 0.75 (pw, ph) = self.min_puzzle_size(size_w, size_h) pw = int((pw + col_width - 1)/col_width) * col_width if PuzzlePrinter.enlarged: r = ((w - pw)/2, (h - ph)/2, (w + pw)/2, (h + ph)/2) inside_top = top inside_bot = bottom elif self.place == LEFT_TOP: r = (left, top, left+pw, top+ph) inside_top = top+ph inside_bot = bottom elif self.place == LEFT_BOT: r = (left, bottom-ph, left+pw, bottom) inside_top = top inside_bot = bottom-ph elif self.place == RIGHT_TOP: r = (right-pw, top, right, top+ph) inside_top = top+ph inside_bot = bottom elif self.place == RIGHT_BOT: r = (right-pw, bottom-ph, right, bottom) inside_top = top inside_bot = bottom-ph else: assert False def coltop(x0, x1): if x1 <= r[0] or x0 >= r[2]: return top else: return inside_top def colbot(x0, x1): if x1 <= r[0] or x0 >= r[2]: return bottom else: return inside_bot if PuzzlePrinter.enlarged: if page_nr == 0: self.draw_puzzle(r) else: heights = [bottom-top]*num_cols area = ClueLayout(self.puzzle, context, heights) for col in range(area.num_columns()): area.draw_column(col, self.cr, left + col*col_width, top) else: heights = [] for col in range(num_cols): x0 = left + col*col_width x1 = left + (col + 1)*col_width heights.append(colbot(x0, x1) - coltop(x0, x1)) area = ClueLayout(self.puzzle, context, heights) for col in range(area.num_columns()): x0 = left + col*col_width x1 = left + (col + 1)*col_width area.draw_column(col, self.cr, left + col*col_width, coltop(x0, x1)) self.draw_puzzle(r) def begin_print(self, op, context): if PuzzlePrinter.enlarged: op.set_n_pages(2) else: op.set_n_pages(1) def create_custom_widget(self, op): layouts = [ ('Right bottom (best for righties)', RIGHT_BOT), ('Left bottom (best for lefties)', LEFT_BOT), ('Right top', RIGHT_TOP), ('Left top', LEFT_TOP)] self.buttons = {} op.set_custom_tab_label('Crossword Options') main_box = gtk.VBox() box1 = gtk.HBox() main_box.pack_start(box1, False, False) box1.show() main_box.set_border_width(15) frame = gtk.Frame('Puzzle placement') box1.pack_start(frame, False, False) frame.show() box = gtk.VButtonBox() box.set_layout(gtk.BUTTONBOX_START) frame.add(box) box.show() button = None for (label, sel) in layouts: button = gtk.RadioButton(button, label) box.add(button) button.show() self.buttons[sel] = button button.set_active(PuzzlePrinter.place == sel) button = gtk.CheckButton('Print your answers') button.show() self.print_answers_button = button button.set_active(PuzzlePrinter.print_answers) main_box.pack_start(button, False, False, padding=10) button = gtk.CheckButton('Print enlarged (use two pages)') button.show() self.enlarge_button = button button.set_active(PuzzlePrinter.enlarged) main_box.pack_start(button, False, False) return main_box def custom_widget_apply(self, op, main_box): for (sel, button) in self.buttons.items(): if button.get_active(): place = sel self.place = place PuzzlePrinter.place = place PuzzlePrinter.print_answers = self.print_answers_button.get_active() PuzzlePrinter.enlarged = self.enlarge_button.get_active() def print_puzzle(self, win): op = gtk.PrintOperation() if PuzzlePrinter.settings != None: op.set_print_settings(PuzzlePrinter.settings) if PuzzlePrinter.page_setup != None: op.set_default_page_setup(PuzzlePrinter.page_setup) op.connect('begin-print', self.begin_print) op.connect('draw-page', self.draw_page) op.connect('create-custom-widget', self.create_custom_widget) op.connect('custom-widget-apply', self.custom_widget_apply) r = op.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG, win) if r == gtk.PRINT_OPERATION_RESULT_APPLY: PuzzlePrinter.settings = op.get_print_settings() def do_page_setup(self, win): if PuzzlePrinter.settings is None: PuzzlePrinter.settings = gtk.PrintSettings() r = gtk.print_run_page_setup_dialog(win, PuzzlePrinter.page_setup, PuzzlePrinter.settings) PuzzlePrinter.page_setup = r xword-2.0.0~rc2/xword/controller.py0000755000175000017500000002730211613150211016261 0ustar cdalecdaleimport puzzle ACROSS = puzzle.ACROSS DOWN = puzzle.DOWN class PuzzleController: def __init__(self, puzzle): self.puzzle = puzzle self.handlers = [] self.selection = [] self.highlight_ok = {} self.highlight_bad = {} self.to_check = [] self.checking = False self.check_rate = 0 mode = ACROSS n = self.puzzle.initial_number(mode) (x, y) = self.puzzle.cell_of_number(n) self.change_position(x, y, mode) 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.change_position(self.x, self.y, self.mode) 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 get_mode(self): return self.mode def update_state(self): 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('pos-update', (self.x, self.y)) self.do_update('title-update') if self.puzzle.is_mode_valid(self.x, self.y, ACROSS): self.do_update('across-update', self.puzzle.number(self.x, self.y, ACROSS)) if self.puzzle.is_mode_valid(self.x, self.y, DOWN): self.do_update('down-update', self.puzzle.number(self.x, self.y, DOWN)) def change_position(self, x, y, mode): if not self.puzzle.is_black(x, y): self.x = x self.y = y self.mode = mode if not self.puzzle.is_mode_valid(self.x, self.y, self.mode): self.mode = 1-self.mode self.update_state() def select_word(self, mode, n): self.halt_check() (x, y) = self.puzzle.number_map[n] (x, y) = self.puzzle.find_blank_cell(x, y, mode, 1) assert self.puzzle.is_mode_valid(x, y, mode) self.change_position(x, y, mode) def set_letter(self, letter, x=None, y=None): self.halt_check() if x == None: x = self.x if y == None: y = self.y self.set_response(x, y, letter) if self.puzzle.get_error(x, y) == puzzle.MISTAKE: self.puzzle.set_error(x, y, puzzle.FIXED_MISTAKE) self.do_update('box-update', x, y) if not self.puzzle.is_locked() and self.puzzle.is_puzzle_correct(): self.do_update('puzzle-finished') if self.puzzle.is_locked() and self.puzzle.is_puzzle_filled(): self.do_update('puzzle-filled') # For a cell (x, y), when the direction is D, I find the word at (x, y) # in direction not(D). For each cell in that word, I check if the # perpendicular word is fully filled in. If it isn't, I delete the # value in that cell. def kill_perpendicular(self): self.halt_check() dir = 1-self.mode if self.puzzle.is_mode_valid(self.x, self.y, dir): n = self.puzzle.number(self.x, self.y, dir) (x, y) = self.puzzle.number_map[n] hit = False while not hit: if not self.puzzle.is_word_filled(x, y, 1-dir): self.erase_letter(x, y) ((x, y), hit) = self.puzzle.next_cell(x, y, dir, 1, False) def erase_letter(self, x=None, y=None): self.halt_check() self.set_letter('', x, y) def move(self, dir, amt, skip_black=True, change_dir=True): self.halt_check() mode_valid = self.puzzle.is_mode_valid(self.x, self.y, dir) if self.mode <> dir and change_dir and mode_valid: self.change_position(self.x, self.y, dir) else: ((x, y), _) = self.puzzle.next_cell(self.x, self.y, dir, amt, skip_black) self.change_position(x, y, self.mode) def back_space(self): self.halt_check() self.erase_letter() self.move(self.mode, -1, False) def forward_space(self): self.halt_check() self.erase_letter() self.move(self.mode, 1, False) def next_word(self, incr): self.halt_check() n = self.puzzle.incr_number(self.x, self.y, self.mode, incr) if n == 0: mode = 1-self.mode if incr > 0: n = self.puzzle.initial_number(mode) else: n = self.puzzle.final_number(mode) else: mode = self.mode (x, y) = self.puzzle.cell_of_number(n) (x, y) = self.puzzle.find_blank_cell(x, y, mode, 1) self.change_position(x, y, mode) def input_char(self, skip_filled, c): self.halt_check() 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.change_position(x, y, self.mode) def halt_check(self): if not self.checking: return while self.checking: self.idle_event() def check(self, cells): self.halt_check() self.to_check = cells[:] self.checking = True for (x, y) in self.to_check: self.do_update('box-update', x, y) def sortfun((x1, y1), (x2, y2)): n1 = x1+y1 n2 = x2+y2 return n2-n1 self.to_check.sort(sortfun) self.check_rate = max(len(self.to_check)/5, 1) def check_letter(self): self.check([(self.x, self.y)]) def check_word(self): self.check(self.selection) def check_puzzle(self): self.check(self.puzzle.get_cells()) def check_complete(self): correct = True for (x, y) in self.highlight_bad.keys(): self.puzzle.set_error(x, y, puzzle.MISTAKE) correct = False self.do_update('box-update', x, y) self.do_update('check-result', correct) def solve(self, cells): self.halt_check() was_correct = self.puzzle.is_puzzle_correct() was_filled = self.puzzle.is_puzzle_filled() for (x, y) in cells: if not self.puzzle.is_cell_correct(x, y): self.puzzle.set_error(x, y, puzzle.CHEAT) self.set_response(x, y, self.puzzle.get_answer(x, y)) self.do_update('box-update', x, y) if (not was_correct and not self.puzzle.is_locked() and self.puzzle.is_puzzle_correct()): self.do_update('puzzle-finished') if (not was_filled and self.puzzle.is_locked() and self.puzzle.is_puzzle_filled()): self.do_update('puzzle-filled') def solve_letter(self): self.solve([(self.x, self.y)]) def solve_word(self): self.solve(self.selection) def solve_puzzle(self): self.solve(self.puzzle.get_cells()) def clear(self, cells): self.halt_check() for (x, y) in cells: if not self.puzzle.is_black(x, y): self.set_response(x, y, '') self.do_update('box-update', x, y) def clear_letter(self): self.clear([(self.x, self.y)]) def clear_word(self): self.clear(self.selection) def clear_puzzle(self): self.clear(self.puzzle.get_cells()) def set_response(self, x, y, c): self.puzzle.set_letter(x, y, c) self.do_update('letter-update', x, y) def count_cells(self): empty = 0 filled = 0 for (x, y) in self.puzzle.get_cells(): if self.puzzle.is_black(x, y): continue elif self.puzzle.is_blank(x, y): empty += 1 else: filled += 1 return (empty, filled) def is_word_filled(self, mode, n): (x, y) = self.puzzle.cell_of_number(n) return self.puzzle.is_word_filled(x, y, mode) def is_word_filled_at_position(self, x, y, mode): return self.puzzle.is_word_filled(x, y, mode) def is_highlight_ok(self, x, y): return self.highlight_ok.has_key((x, y)) def is_highlight_bad(self, x, y): return self.highlight_bad.has_key((x, y)) def is_highlight_none(self, x, y): return self.checking 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 def idle_event(self): if self.checking: update = [] if len(self.to_check) == 0: for c in self.highlight_ok.keys(): update.append(c) for c in self.highlight_bad.keys(): update.append(c) self.check_complete() self.highlight_ok = {} self.highlight_bad = {} self.checking = False else: n = self.check_rate while n > 0 and len(self.to_check) > 0: n -= 1 (x, y) = self.to_check.pop() update.append((x, y)) if self.puzzle.is_cell_correct(x, y) \ or self.puzzle.is_blank(x, y): self.highlight_ok[(x, y)] = 1 else: self.highlight_bad[(x, y)] = 1 for (x, y) in update: self.do_update('box-update', x, y) class DummyController: def __init__(self): pass def connect(self, ev, handler): pass def signal(self): pass def get_mode(self): return ACROSS def change_position(self, x, y, mode): pass def select_word(self, mode, n): pass def set_letter(self, letter): pass def kill_perpendicular(self): pass def erase_letter(self): pass def move(self, dir, amt, skip_black=True, change_dir=True): pass def back_space(self): pass def forward_space(self): pass def next_word(self, incr): pass def input_char(self, skip_filled, c): pass def check_word(self): pass def check_puzzle(self): pass def solve_word(self): pass def is_highlight_ok(self, x, y): return False def is_highlight_bad(self, x, y): return False def is_highlight_none(self, x, y): return False 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 [] def count_cells(self): return (0, 0) def idle_event(self): pass xword-2.0.0~rc2/xword/grid.py0000755000175000017500000002001611613113462015025 0ustar cdalecdaleimport puzzle import gtk import pango MIN_BOX_SIZE = 24 class GridWidget: def __init__(self, puzzle, control): self.puzzle = puzzle self.control = control self.area = gtk.DrawingArea() self.dbuf = None 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) self.sw.add_with_viewport(self.area) 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 * MIN_BOX_SIZE height = puzzle.height * MIN_BOX_SIZE self.area.set_size_request(width+1, height+1) else: self.box_size = MIN_BOX_SIZE self.dbuf = None self.area.queue_draw_area(0, 0, 32768, 32768) def configure_event(self, area, event): width, height = event.width, event.height if self.puzzle: # Don't forget that 1px is needed for the R&B borders bw = int((width - 1) / self.puzzle.width) bh = int((height - 1) / self.puzzle.height) self.box_size = min(bw, bh) self.width = self.box_size * self.puzzle.width + 1 self.height = self.box_size * self.puzzle.height + 1 self.x = int((width - self.width) / 2) self.y = int((height - self.height) / 2) self.dbuf = None else: self.width = width self.height = height self.x = 0 self.y = 0 def expose_event(self, area, event): if self.puzzle: if not self.dbuf: self.draw_puzzle(0, self.puzzle.width-1, 0, self.puzzle.height-1) srcx = event.area.x - self.x srcy = event.area.y - self.y area.window.draw_drawable(self.gc, self.dbuf, srcx, srcy, event.area.x, event.area.y, event.area.width, event.area.height) else: self.draw_empty() def draw_empty(self): pass def draw_puzzle(self, xmin, xmax, ymin, ymax): cm = self.area.get_colormap() self.white = cm.alloc_color('white') self.black = cm.alloc_color('black') self.realblack = cm.alloc_color('black') self.red = cm.alloc_color('red') self.hilite = cm.alloc_color('yellow') self.green = cm.alloc_color('lightgreen') self.gray = cm.alloc_color('lightblue') self.darkgray = cm.alloc_color('darkgray') num_size = int(self.box_size * 0.25) let_size = int(self.box_size * 0.45) msg_size = int(self.box_size * 0.85) self.num_font = pango.FontDescription('Sans %d' % num_size) self.let_font = pango.FontDescription('Sans %d' % let_size) self.msg_font = pango.FontDescription('Sans %d' % msg_size) if not self.dbuf: self.dbuf = gtk.gdk.Pixmap(self.area.window, self.width, self.height, -1) view = self.dbuf self.gc = view.new_gc(foreground = self.white, background = self.white) view.draw_rectangle(self.gc, True, 0, 0, self.width, self.height) else: view = self.dbuf for y in range(ymin, ymax+1): for x in range(xmin, xmax+1): self.draw_box(x, y) return True def draw_triangle(self, x0, y0, color, filled): view = self.dbuf 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, circled): view = self.dbuf if circled: self.gc.set_foreground(self.darkgray) view.draw_arc(self.gc, False, x0+1, y0+1, self.box_size-2, self.box_size-2, 0, 360*64) self.gc.set_foreground(self.black) 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 == puzzle.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 == puzzle.FIXED_MISTAKE: self.draw_triangle(x0, y0, self.black, True) elif error == puzzle.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.dbuf x0 = x*self.box_size y0 = y*self.box_size if self.puzzle.is_black(x, y): color = self.black elif self.control.is_highlight_ok(x, y): color = self.green elif self.control.is_highlight_bad(x, y): color = self.red elif self.control.is_highlight_none(x, y): color = self.white elif self.control.is_main_selection(x, y): color = self.hilite elif self.control.is_selected(x, y): color = self.gray 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] circled = self.puzzle.is_circled(x, y) if self.puzzle.cell_has_number(x, y): n = str(self.puzzle.number_of_cell(x, y)) else: n = '' self.draw_box_data(x0, y0, n, letter, error, circled) 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): self.draw_puzzle(x, x, y, 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) def update_all(self): self.draw_puzzle(0, self.puzzle.width-1, 0, self.puzzle.height-1) self.area.queue_draw_area(0, 0, 32768, 32768) def pos_update(self, (x, y)): hadj = self.sw.get_hadjustment() vadj = self.sw.get_vadjustment() x0 = self.x + x*self.box_size y0 = self.y + y*self.box_size x1 = x0 + self.box_size y1 = y0 + self.box_size hbuf = int(hadj.page_size * 0.20) vbuf = int(vadj.page_size * 0.20) if x0 < hadj.value + hbuf: hadj.value = float(max(0, x0 - hbuf)) if x1 > hadj.value + hadj.page_size - hbuf: hadj.value = float(min(hadj.upper - hadj.page_size, x1 - hadj.page_size + hbuf)) if y0 < vadj.value + vbuf: vadj.value = float(max(0, y0 - vbuf)) if y1 > vadj.value + vadj.page_size - vbuf: vadj.value = float(min(vadj.upper - vadj.page_size, y1 - vadj.page_size + vbuf)) xword-2.0.0~rc2/xword/pixmaps/0000755000175000017500000000000011613174573015217 5ustar cdalecdalexword-2.0.0~rc2/xword/pixmaps/crossword-check-all.png0000644000175000017500000000156011613113462021563 0ustar cdalecdalePNG  IHDRw=bKGD pHYs  ~tIME *IDATxMHTQy5OߝEPT SM DEJFN_Tr٪\T EED61)0SLf^4?{QLt7 L@@|Wp'ݾ:hCXEGuEZ.Ǯ3,@U*ՇXݏo/ 4\iRGY+C潫֖la)PtV7{@Bl`.F??(kr N0!J9tᐁQl-:)OSVV(iF 8@mmp*t8Nx(쨯U'Cu皓CuKb^v]wEEqX0/o/ ?Iq"t+0)ڶM5|݉: ˹>RH?ǹN'ʟ6ho?D)[^ygg2)&x5A\YWT/vl a:" 2Z0Hj)$?~1 $+݁xOdWQT&E(dIr /ȧzb{bcH!1A&-@,q#n"5<UQG)=ep=p/EOPɮGMMF vYsݎbAC%xӳdfw ҹsc9i)ܝy!TfY77en7IENDB`xword-2.0.0~rc2/xword/pixmaps/crossword-notepad.png0000644000175000017500000000144211613150600021364 0ustar cdalecdalePNG  IHDRw=sRGBbKGD pHYs B(xtIME7XLtIDATHՕMkQ;w&M$5tgЯDt%BDp%tٕ @WbAԕhMB UE1;EL&\ 3}w-8 l`ga3Je*N}Ukn*wI7i{uooXGEW|CO䉭\}wqWW/_ٛޮy/Κ{[yf~6fg3K/9َgu&pbdq埑a|h>n/1 d2'rAp={$4'.Zkrhvby=%M& 7 8VR >3|xDZћʥ>;Abc#c RJپ:B`U ^fm}zdG=ɔ!m!D9B I,q35!PJ!X.BJϫV=4㓇qg3Z")-,K=@.R! @BʦVT*ݜTe2KNNu+AX vHʥɡrSm c*vW>O C ~ommy.PX-p HvZ<@Ztn- 4ZKG/[MIENDB`xword-2.0.0~rc2/xword/pixmaps/crossword-solve-all.png0000644000175000017500000000113211613170231021626 0ustar cdalecdalePNG  IHDRw=sRGBbKGD pHYs  ~tIMEIIDATHǵKAbS N&Ae( s :tjA']Q!؆^|K.`>.y{}?0Z?ę{h8e;N*@* kG36*ι?Cj6PP:7T{tI`^=KP/!sY d.y(DbO*w77*.8gԷ}c[Y3d6æ(zVd6Z\V=LIJZpa_fZL̝'{"{JUէ }4odRga{~ 4r}2ZӀXՀ gM8nc(vY\d.yyu\ȳmÑ,1Pgt-e_L w! !S;'sB[d x;ČJiKb~ԎrIENDB`xword-2.0.0~rc2/xword/pixmaps/crossword-solve.png0000644000175000017500000000055711613113462021075 0ustar cdalecdalePNG  IHDRw=bKGD pHYs  ~tIME fIDATx͕ 03!:/2{4MhCq%JL[؏8\\;ށKG{,2qY Xv:,Mj o`Щ$RN`{cMQw#pJ)(O̶XsFl\;x.[7m JjDֵ|̢4Ŀ퉞"h"$D~AV7S_wy gLeZ_$Z2N-SB`pr-"JIeR) .!Jg<; VEev;(^l^7H$>X +x ].L&FFFuEQe]ڵkr9FUURe^AM{4Mct4ih,l6Ξ=i;wnB" Lo17}iHBy:]4҅SNvdR)Ǒ$ Ŏ;ؾ}{೴Фrv7nPRRB4%A2ٳgc=zʠ` HAKK ֆ(TUUo&q8زe HġC29 0p:b10=Bu"~ݻLf14773߇KdriBFGG`bbqp8Ȳl;\appI|W\cjj7oP^^޽{q\XVM&GzjPP(y<L$\l6aX,f3cL Copyright (c) 2005-2009, Bill McCloskey 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. ''' xword-2.0.0~rc2/README0000644000175000017500000001030511613113462013240 0ustar cdalecdaleThis is the README file for xword, describing how to build and install it with the Python distutils utility. This README file is for xword version 2.0.0 or later. Installation: ------------- Installation is similar to other Python libraries. If your system is well-set-up, and has the required libraries, there should be no difficulties. The executive summary of steps is: > tar -xzf xword-.tar.gz > cd xword- > python setup.py build > python setup.py install but please read the rest of this file before proceeding. If something goes wrong, your system adminstrator may be able to help you, or you can contact the xword developers at xword-devel@lists.alioth.debian.org. It's diagnostically useful to include all the output from setup.py. xword has been built and tested on Linux (Debian, Ubuntu). Details: -------- 0. What You Should Already Have: The following external programs and libraries must be present before you can run xword. Python (2.4 through 2.7) http://www.python.org gtk+-2.0 (2.4 or later) http://www.gtk.org/download/ pygtk2 (2.4 or later) http://www.pygtk.org python-bindings for the wnck-library http://rox4debian.berlios.de/0install/Python-Wnck.xml 1. What You Got From Us: The file "xword-.tar.gz". Procedure: ---------- 0. Unpack Unpack the .tar.gz file. The usual way is to run "tar -xzf" on the file you want to unpack. This will create a subdirectory named "xword-" in the directory where you run tar. 1. Build the xword library Switch to the newly-created directory, and run % python setup.py build The build command will create a "build" subdirectory in the top xword directory. Within "build" it will create a subdirectory with a system-dependent name. 2. Install To install xword, run % python setup.py install This will install xword in the standard location for Python extensions on your system. This is good, because then you won't have to do anything special to get xword to run. It's also bad, because unless you are the system administrator, you probably don't have permission to install anything in that directory. You have two options: a) Get a system administrator, become root, or use "sudo" to run the installation step. b) Tell distutils to install xword in a different place, like this: % python setup.py install --prefix= where is a directory that you can write to. The default value of is usually /usr/local. The installation procedure will create an executable script called "xword" in /bin, and a directory called "xword" in /lib/python2.x/site-packages (where 2.x is your python version number). (It's possible to use --home= instead of --prefix when installing xword. The only difference is that --home will put the python libraries in /lib/python instead of /lib/python2.x/site-packages.) 2.1. Set environment variables If /bin is not in your Unix command path, you'll need to add it to the PATH environment variable, or create a symbolic link from a directory that is in your path (or start xword the hard way by by typing /bin/xword). (Typing "echo $PATH" will print the current value of your path. The method for setting environment variables depends on which Unix shell you're using.) If /lib/python2.x/site-packages is not in your Python path, you'll have to add it to the PYTHONPATH environment variable. (Running the command % python -c "import sys; print sys.path" will print your Python path.) Running xword: -------------- At this point, you should have an executable file named "xword" in a bin directory in your execution path. You can now simply type "xword" at your shell prompt, and xword will start up. You may also run xword from the unpacked .tar.gz directory, with the command % PYTHONPATH=. scripts/xword By default, xword runs in organizer mode, displaying a list of known crossword puzzle files to get you started. If you don't want this, you can specify the crossword puzzle file to open on the command-line. xword-2.0.0~rc2/PKG-INFO0000644000175000017500000000110111613174573013461 0ustar cdalecdaleMetadata-Version: 1.0 Name: xword Version: 2.0.0~rc2 Summary: Reads and writes crossword puzzles in the Across Lite file format Home-page: http://alioth.debian.org/projects/xword/ Author: Cameron Dale Author-email: License: BSD Description: Xword is a GTK+ program that works well for doing crossword puzzles in the Across Lite file format used by The New York Times and others. As well as a clock, it supports printing. It also auto-saves puzzles as you solve them so that you can return to partially completed puzzles. Platform: UNKNOWN xword-2.0.0~rc2/scripts/0000755000175000017500000000000011613174573014062 5ustar cdalecdalexword-2.0.0~rc2/scripts/xword0000755000175000017500000000051411613113462015141 0ustar cdalecdale#!/usr/bin/env python from xword.main import MainWindow from xword.organizer import OrganizerWindow import sys import gtk if __name__ == '__main__': if len(sys.argv) <> 2: fname = None else: fname = sys.argv[1] if fname: w = MainWindow(fname) else: w = OrganizerWindow() gtk.main() xword-2.0.0~rc2/setup.py0000755000175000017500000000201311613113462014072 0ustar cdalecdale#!/usr/bin/env python # # Written by Cameron Dale # see LICENSE for license information # import sys assert sys.version_info >= (2,3), "Install Python 2.3 or greater" from distutils.core import setup import xword setup( name = "xword", version = xword.__version__, description = "Reads and writes crossword puzzles in the Across Lite file format", long_description = "Xword is a GTK+ program that works well for doing crossword puzzles in the" \ + " Across Lite file format used by The New York Times and others. As well as a" \ + " clock, it supports printing. It also auto-saves puzzles as you solve them so" \ + " that you can return to partially completed puzzles.", author = "Cameron Dale", author_email = "", url = "http://alioth.debian.org/projects/xword/", license = "BSD", packages = ["xword"], scripts = ["scripts/xword"], package_data = {"xword": ["pixmaps/*.png"]}, )