Exemplo n.º 1
0
    def __init__ (self, run_selector = True):
        """run_selector means that we start our regular game.

        For testing purposes, it will be convenient to hand a
        run_selector=False to this method to avoid running the dialog
        and allow a tester to set up a game programmatically.
        """
        gconf_wrapper.GConfWrapper.__init__(self,
                                            gconf_wrapper.GConf('gnome-sudoku')
                                            )
        self.setup_gui()
        self.timer = ActiveTimer(self.w)
        self.won = False
        # add the accelerator group to our toplevel window
        self.worker_connections = []
        # setup sudoku maker...
        self.sudoku_maker = sudoku_maker.SudokuMaker()
        self.sudoku_tracker = saver.SudokuTracker()
        # generate puzzles while our use is working...
        self.show()
        if run_selector:
            self.do_stop()
            if self.select_game():
                # If this return True, the user closed...
                self.quit = True
            else:
                self.quit = False
                # Generate puzzles in background...
                if self.gconf['generate_puzzles_in_background']:
                    gobject.timeout_add_seconds(1, lambda *args: self.start_worker_thread() and True)
Exemplo n.º 2
0
class UI (gconf_wrapper.GConfWrapper):
    ui = '''<ui>
    <menubar name="MenuBar">
      <menu name="Game" action="Game">
        <menuitem action="New"/>
        <separator/>
        <menuitem action="PuzzleInfo"/>
        <separator/>
        <menuitem action="Print"/>
        <menuitem action="PrintMany"/>
        <separator/>
        <menuitem action="Close"/>
      </menu>
      <menu action="Edit">
        <menuitem action="Undo"/>
        <menuitem action="Redo"/>
        <separator/>
        <menuitem action="Clear"/>
        <menuitem action="ClearNotes"/>
      </menu>
      <menu action="View">
        <menuitem action="FullScreen"/>
        <separator/>
        <menuitem action="ToggleToolbar"/>
        <menuitem action="ToggleHighlight"/>
      </menu>
      <menu action="Tools">
        <menuitem action="ShowPossible"/>
        <menuitem action="AutofillCurrentSquare"/>
        <menuitem action="Autofill"/>
        <separator/>
        <menuitem action="AlwaysShowPossible"/>
        <menuitem action="ShowImpossibleImplications"/>
        <separator/>
        <menuitem action="Generator"/>
        <menuitem action="BackgroundGenerator"/>
        <separator/>
        <menuitem action="Tracker"/>
        </menu>
      <menu action="Help">
        <menuitem action="ShowHelp"/>
        <menuitem action="About"/>
      </menu>
    </menubar>
    <toolbar name="Toolbar">
      <toolitem action="New"/>
      <toolitem action="Print"/>
      <separator/>
      <toolitem action="Undo"/>
      <toolitem action="Redo"/>
      <separator/>
      <toolitem action="ShowPossible"/>
      <toolitem action="AutofillCurrentSquare"/>
      <separator/>
      <toolitem action="ToggleHighlight"/>
      <toolitem action="Tracker"/>
    </toolbar>
    </ui>'''

    initial_prefs = {'group_size':9,
                     'always_show_hints':0,
                     'player':os.environ.get('USERNAME', ''),
                     'difficulty':0.0,
                     'minimum_number_of_new_puzzles':MIN_NEW_PUZZLES,
                     'highlight':0,
                     'bg_black':1,
                     'bg_custom_color':'',
                     'show_tracker':False,
                     'width': 700,
                     'height': 675,
                     'auto_save_interval':60 # auto-save interval in seconds...
                     }

    @simple_debug
    def __init__ (self, run_selector = True):
        """run_selector means that we start our regular game.

        For testing purposes, it will be convenient to hand a
        run_selector=False to this method to avoid running the dialog
        and allow a tester to set up a game programmatically.
        """
        gconf_wrapper.GConfWrapper.__init__(self,
                                            gconf_wrapper.GConf('gnome-sudoku')
                                            )
        self.setup_gui()
        self.timer = ActiveTimer(self.w)
        self.won = False
        # add the accelerator group to our toplevel window
        self.worker_connections = []
        # setup sudoku maker...
        self.sudoku_maker = sudoku_maker.SudokuMaker()
        self.sudoku_tracker = saver.SudokuTracker()
        # generate puzzles while our use is working...
        self.show()
        if run_selector:
            self.do_stop()
            if self.select_game():
                # If this return True, the user closed...
                self.quit = True
            else:
                self.quit = False
                # Generate puzzles in background...
                if self.gconf['generate_puzzles_in_background']:
                    gobject.timeout_add_seconds(1, lambda *args: self.start_worker_thread() and True)


    @inactivate_new_game_etc
    def select_game (self):
        self.tb.hide()
        self.update_statusbar()
        choice = game_selector.NewOrSavedGameSelector().run_swallowed_dialog(self.swallower)
        if not choice:
            return True
        self.timer.start_timing()
        if choice[0] == game_selector.NewOrSavedGameSelector.NEW_GAME:
            self.gsd.change_grid(choice[1], 9)
            self.update_statusbar()
        if choice[0] == game_selector.NewOrSavedGameSelector.SAVED_GAME:
            saver.open_game(self, choice[1])
            self.update_statusbar()
        if self.gconf['show_toolbar']:
            self.tb.show()
        if self.gconf['always_show_hints']:
            self.gsd.update_all_hints()
        if self.gconf['highlight']:
            self.gsd.toggle_highlight(True)


    def show (self):
        self.gsd.show()
        self.w.show()

    def setup_gui (self):
        self.initialize_prefs()
        self.setup_main_window()
        self.gsd = gsudoku.SudokuGameDisplay()
        self.gsd.connect('puzzle-finished', self.you_win_callback)
        self.setup_color()
        self.setup_actions()
        self.setup_undo()
        self.setup_autosave()
        self.w.add_accel_group(self.uimanager.get_accel_group())
        self.setup_main_boxes()
        self.setup_tracker_interface()
        self.setup_toggles()

    def setup_main_window (self):
        gtk.window_set_default_icon_name('gnome-sudoku')
        self.w = gtk.Window()
        self.w.set_default_size(self.gconf['width'], self.gconf['height'])
        self.w.set_title(APPNAME_SHORT)
        self.w.connect('configure-event', self.resize_cb)
        self.w.connect('delete-event', self.quit_cb)
        self.uimanager = gtk.UIManager()

    def setup_actions (self):
        self.main_actions = gtk.ActionGroup('MainActions')
        self.main_actions.add_actions([
            ('Game', None, _('_Game')),
            ('New', gtk.STOCK_NEW, None,
             '<Control>n', _('New game'), self.new_cb),
            ('Print', gtk.STOCK_PRINT, None,
             None, _('Print current game'), self.print_game),
            ('PrintMany', gtk.STOCK_PRINT, _('Print _Multiple Sudokus'),
             None, _('Print more than one sudoku at a time.'), self.print_multiple_games),
            ('Close', gtk.STOCK_CLOSE, None, '<Control>w',
             _('Close Sudoku'), self.quit_cb),
            ('Tools', None, _('_Tools')),
            ('View', None, _('_View')),
            ('ShowPossible', gtk.STOCK_DIALOG_INFO, _('_Hint'),
             '<Control>h',
             _('Show which numbers could go in the current square.'),
             self.show_hint_cb),
            ('AutofillCurrentSquare', gtk.STOCK_APPLY, _('_Fill'), '<Control>f',
             _('Automatically fill in the current square if possible.'),
             self.auto_fill_current_square_cb),
            ('Autofill', gtk.STOCK_REFRESH, _('Fill _all squares'), '<Control>a',
             _('Automatically fill in all squares for which there is only one valid value.'),
             self.auto_fill_cb),
            ('FullScreen', gtk.STOCK_FULLSCREEN, None,
             'F11', None, self.full_screen_cb),
            ('Generator', None, _('_Generate new puzzles'), None, _('Generate new puzzles.'),
              self.generate_puzzle_gui, ),
            ('PuzzleInfo', gtk.STOCK_ABOUT, _('Puzzle _Statistics'),
             None, _('Show statistics about current puzzle'),
             self.show_info_cb),
            ('Help', None, _('_Help'),
             None, None, None),
            ('About', gtk.STOCK_ABOUT, None,
             None, None, self.show_about),
            ('ShowHelp', gtk.STOCK_HELP, _('_Contents'),
             'F1', None, self.show_help),
            ])
        self.main_actions.add_toggle_actions([
            ('AlwaysShowPossible',
             None,
             _('_Always show hint'),
             None,
             _('Always show possible numbers in a square'),
             self.auto_hint_cb),
            ('ShowImpossibleImplications',
             None,
             _('Warn about _unfillable squares'),
             None,
             _('Warn about squares made unfillable by a move'),
             self.impossible_implication_cb),
            ('Tracker', 'tracks', _('_Track additions'),
             '<Control>T',
             _('Mark new additions in a separate color so you can keep track of them.'),
             self.tracker_toggle_cb, False),
            ('ToggleToolbar', None, _('Show _Toolbar'), None, None, self.toggle_toolbar_cb, True),
            ('ToggleHighlight', gtk.STOCK_SELECT_COLOR, _('_Highlighter'),
             None, _('Highlight the current row, column and box'), self.toggle_highlight_cb, False),
            ('BackgroundGenerator', None, _('Generate new puzzles _while you play'),
             None,
             _('Generate new puzzles in the background while you play. This will automatically pause when the game goes into the background.'),
             self.toggle_generator_cb, True),
            ])

        self.edit_actions = gtk.ActionGroup('EditActions')
        self.edit_actions.add_actions(
            [('Edit', None, _('_Edit')),
             ('Undo', gtk.STOCK_UNDO, _('_Undo'), '<Control>z', _('Undo last action'), self.stop_dancer),
             ('Redo', gtk.STOCK_REDO, _('_Redo'), '<Shift><Control>z', _('Redo last action')),
             ('Clear', gtk.STOCK_CLEAR, _('_Clear'), '<Control>b', _("Clear entries you've filled in"), self.clear_cb),
             ('ClearNotes', None, _('Clear _Notes'), None, _("Clear notes and hints"), self.clear_notes_cb),
             ])
        self.uimanager.insert_action_group(self.main_actions, 0)
        self.uimanager.insert_action_group(self.edit_actions, 0)
        self.uimanager.add_ui_from_string(self.ui)

    def setup_undo (self):
        self.cleared = [] # used for Undo memory
        self.cleared_notes = [] # used for Undo memory
        # Set up our UNDO stuff
        undo_widg = self.edit_actions.get_action('Undo')
        redo_widg = self.edit_actions.get_action('Redo')
        self.history = Undo.UndoHistoryList(undo_widg, redo_widg)
        for entry in self.gsd.__entries__.values():
            Undo.UndoableGenericWidget(entry, self.history,
                                       set_method = 'set_value_from_undo',
                                       pre_change_signal = 'value-about-to-change'
                                       )
            Undo.UndoableGenericWidget(entry, self.history,
                                       set_method = 'set_notes',
                                       get_method = 'get_note_text',
                                       signal = 'notes-changed',
                                       pre_change_signal = 'value-about-to-change',
                                       )

    def setup_color (self):
        # setup background colors
        if self.gconf['bg_custom_color']:
            bgcol = self.gconf['bg_custom_color']
        elif self.gconf['bg_black']:
            bgcol = 'black'
        else:
            bgcol = None
        if bgcol:
            self.gsd.set_bg_color(bgcol)

    def setup_autosave (self):
        gobject.timeout_add_seconds(self.gconf['auto_save_interval'] or 60, # in seconds...
                            self.autosave)

    def setup_main_boxes (self):
        self.vb = gtk.VBox()
        # Add menu bar and toolbar...
        mb = self.uimanager.get_widget('/MenuBar')
        mb.show()
        self.vb.pack_start(mb, fill = False, expand = False)
        self.tb = self.uimanager.get_widget('/Toolbar')
        self.vb.pack_start(self.tb, fill = False, expand = False)
        self.main_area = gtk.HBox()
        self.swallower = SwappableArea(self.main_area)
        self.swallower.show()
        self.vb.pack_start(self.swallower, True, padding = 12)
        self.main_area.pack_start(self.gsd, padding = 6)
        self.main_actions.set_visible(True)
        self.game_box = gtk.VBox()
        self.main_area.show()
        self.vb.show()
        self.game_box.show()
        self.main_area.pack_start(self.game_box, False, padding = 12)
        self.statusbar = gtk.Statusbar()
        self.statusbar.show()
        self.vb.pack_end(self.statusbar, fill = False, expand = False)
        self.w.add(self.vb)

    def setup_toggles (self):
        # sync up toggles with gconf values...
        map(lambda tpl: self.gconf_wrap_toggle(*tpl),
            [('always_show_hints',
              self.main_actions.get_action('AlwaysShowPossible')),
             ('show_impossible_implications',
              self.main_actions.get_action('ShowImpossibleImplications')),
             ('generate_puzzles_in_background',
              self.main_actions.get_action('BackgroundGenerator')),
             ('show_toolbar',
              self.main_actions.get_action('ToggleToolbar')),
             ('highlight',
              self.main_actions.get_action('ToggleHighlight')),
             ('show_tracker',
              self.main_actions.get_action('Tracker')),
             ])

    @simple_debug
    def start_worker_thread (self, *args):
        n_new_puzzles = self.sudoku_maker.n_puzzles(new = True)
        if n_new_puzzles < self.gconf['minimum_number_of_new_puzzles']:
            self.worker = threading.Thread(target = lambda *args: self.sudoku_maker.work(limit = 5))
            self.worker_connections = [
                self.timer.connect('timing-started', self.sudoku_maker.resume),
                self.timer.connect('timing-stopped', self.sudoku_maker.pause)
                ]
            self.worker.start()
        return True

    @simple_debug
    def stop_worker_thread (self, *args):
        if hasattr(self, 'worker'):
            self.sudoku_maker.stop()
            for c in self.worker_connections:
                self.timer.disconnect(c)

    def stop_dancer (self, *args):
        if hasattr(self, 'dancer'):
            self.dancer.stop_dancing()
            delattr(self, 'dancer')

    @simple_debug
    def you_win_callback (self, grid):
        if hasattr(self, 'dancer'):
            return
        self.won = True
        # increase difficulty for next time.
        self.gconf['difficulty'] = self.gconf['difficulty'] + 0.1
        self.timer.finish_timing()
        self.sudoku_tracker.finish_game(self)
        sublabel = _("You completed the puzzle in %(totalTime)s (%(activeTime)s active)") % {'totalTime': self.timer.total_time_string(),
        'activeTime': self.timer.active_time_string()
                }
        sublabel += "\n"
        sublabel += ngettext("You got %(n)s hint", "You got %(n)s hints", self.gsd.hints) % {'n':self.gsd.hints}
        sublabel += "\n"
        if self.gsd.impossible_hints:
            sublabel += ngettext("You had %(n)s impossibility pointed out.",
                                 "You had %(n)s impossibilities pointed out.",
                                 self.gsd.impossible_hints) % {'n':self.gsd.impossible_hints}
            sublabel += "\n"
        if self.gsd.auto_fills:
            sublabel += ngettext("You used the auto-fill %(n)s time",
                                 "You used the auto-fill %(n)s times",
                                 self.gsd.auto_fills) % {'n':self.gsd.auto_fills}
        import dancer
        self.dancer = dancer.GridDancer(self.gsd)
        self.dancer.start_dancing()
        dialog_extras.show_message(_("You win!"), label = _("You win!"),
                                   sublabel = sublabel
                                   )

    @simple_debug
    def initialize_prefs (self):
        for k, v in self.initial_prefs.items():
            try:
                self.gconf[k]
            except:
                self.gconf[k] = v
        self.player = self.gconf['player']

    @simple_debug
    @inactivate_new_game_etc
    def new_cb (self, *args):
        if (self.gsd.grid and self.gsd.grid.is_changed() and not self.won):
            try:
                if dialog_extras.getBoolean(
                    label = _("Save this game before starting new one?"),
                    custom_yes = _("_Save game for later"),
                    custom_no = _("_Abandon game"),
                    ):
                    self.save_game()
                else:
                    self.sudoku_tracker.abandon_game(self)
            except dialog_extras.UserCancelledError:
                # User cancelled new game
                return
        self.do_stop()
        self.select_game()


    @simple_debug
    def stop_game (self):
        if (self.gsd.grid
            and self.gsd.grid.is_changed()
            and (not self.won)):
            try:
                if dialog_extras.getBoolean(label = _("Save game before closing?")):
                    self.save_game(self)
            except dialog_extras.UserCancelledError:
                return
            self.do_stop()

    def do_stop (self):
        self.stop_dancer()
        self.gsd.grid = None
        self.tracker_ui.reset()
        self.history.clear()
        self.won = False

    @simple_debug
    def resize_cb (self, widget, event):
        self.gconf['width'] = event.width
        self.gconf['height'] = event.height

    @simple_debug
    def quit_cb (self, *args):
        self.w.hide()
        if (self.gsd.grid
            and self.gsd.grid.is_changed()
            and (not self.won)):
            self.save_game(self)
        if gtk.main_level() > 1:
            # If we are in an embedded mainloop, that means that one
            # of our "swallowed" dialogs is active, in which case we
            # have to quit that mainloop before we can quit
            # properly.
            if self.swallower.running:
                d = self.swallower.running
                d.response(gtk.RESPONSE_DELETE_EVENT)
            gtk.main_quit() # Quit the embedded mainloop
            gobject.idle_add(self.quit_cb, 100) # Call ourselves again
                                               # to quit the main
                                               # mainloop
            return
        # make sure we really go away before doing our saving --
        # otherwise we appear sluggish.
        while gtk.events_pending():
            gtk.main_iteration()
        if self.won:
            self.gconf['current_game'] = ''
        if not self.won:
            if not self.gsd.grid:
                self.gconf['current_game'] = ''
        self.stop_worker_thread()
        # allow KeyboardInterrupts, which calls quit_cb outside the main loop
        try:
            gtk.main_quit()
        except RuntimeError:
            pass

    @simple_debug
    def save_game (self, *args):
        self.sudoku_tracker.save_game(self)

    def full_screen_cb (self, *args):
        if not hasattr(self, 'is_fullscreen'):
            self.is_fullscreen = False
        if self.is_fullscreen:
            self.w.unfullscreen()
            self.is_fullscreen = False
        else:
            self.w.fullscreen()
            self.is_fullscreen = True

    @simple_debug
    def clear_cb (self, *args):
        clearer = Undo.UndoableObject(
            self.do_clear, #action
            self.undo_clear, #inverse
            self.history #history
            )
        clearer.perform()

    # add a check to stop the dancer if she is dancing
    def do_clear (self, *args):
        self.cleared.append(self.gsd.reset_grid())
        self.stop_dancer()

    # add a check for finish in the undo to clear
    def undo_clear (self, *args):
        for entry in self.cleared.pop():
            self.gsd.add_value(*entry)
        if self.gsd.grid.check_for_completeness():
            self.gsd.emit('puzzle-finished')

    def clear_notes_cb (self, *args):
        clearer = Undo.UndoableObject(
            lambda *args: self.cleared_notes.append(self.gsd.clear_notes()), #action
            # clear_notes returns a list of tuples indicating the cleared notes...
            # (x,y,(top,bottom)) -- this is what we need for undoing
            lambda *args: [self.gsd.__entries__[t[0], t[1]].set_notes(t[2]) for t in self.cleared_notes.pop()], #inverse
            self.history
            )
        clearer.perform()

    @simple_debug
    def show_hint_cb (self, *args):
        self.gsd.show_hint()

    @simple_debug
    def auto_hint_cb (self, action):
        if action.get_active():
            self.gsd.always_show_hints = True
            self.gsd.update_all_hints()
        else:
            self.gsd.always_show_hints = False
            self.gsd.clear_hints()

    @simple_debug
    def impossible_implication_cb (self, action):
        if action.get_active():
            self.gsd.show_impossible_implications = True
        else:
            self.gsd.show_impossible_implications = False

    @simple_debug
    def auto_fill_cb (self, *args):
        if not hasattr(self, 'autofilled'):
            self.autofilled = []
        if not hasattr(self, 'autofiller'):
            self.autofiller = Undo.UndoableObject(
                self.do_auto_fill,
                self.undo_auto_fill,
                self.history
                )
        self.autofiller.perform()

    def do_auto_fill (self, *args):
        self.autofilled.append(self.gsd.auto_fill())
        if self.gconf['always_show_hints']:
            self.gsd.update_all_hints()

    def undo_auto_fill (self, *args):
        for entry in self.autofilled.pop():
            self.gsd.remove(entry[0], entry[1], do_removal = True)
        if self.gconf['always_show_hints']:
            self.gsd.update_all_hints()

    @simple_debug
    def auto_fill_current_square_cb (self, *args):
        self.gsd.auto_fill_current_entry()

    @simple_debug
    def setup_tracker_interface (self):
        self.trackers = {}
        self.tracker_ui = TrackerBox(self)
        self.tracker_ui.show_all()
        self.tracker_ui.hide()
        self.game_box.add(self.tracker_ui)

    @simple_debug
    def tracker_toggle_cb (self, widg):
        if widg.get_active():
            self.tracker_ui.show_all()
        else:
            self.tracker_ui.hide()

    @simple_debug
    def toggle_toolbar_cb (self, widg):
        if widg.get_active():
            self.tb.show()
        else:
            self.tb.hide()

    def set_statusbar_value (self, status):
        if not hasattr(self, 'sbid'):
            self.sbid = self.statusbar.get_context_id('game_info')
        self.statusbar.pop(self.sbid)
        self.statusbar.push(self.sbid, status)


    def update_statusbar (self, *args):
        if not self.gsd.grid:
            self.set_statusbar_value(" ")
            return True

        puzzle = self.gsd.grid.virgin.to_string()
        puzzle_diff = self.sudoku_maker.get_difficulty(puzzle)

        tot_string = _("Playing %(difficulty)s puzzle.") % {'difficulty':puzzle_diff.value_string()}
        tot_string += " " + "(%1.2f)" % puzzle_diff.value

        self.set_statusbar_value(tot_string)
        return True

    def toggle_highlight_cb (self, widg):
        if widg.get_active():
            self.gsd.toggle_highlight(True)
        else:
            self.gsd.toggle_highlight(False)

    @simple_debug
    def show_info_cb (self, *args):
        if not self.gsd.grid:
            dialog_extras.show_message(parent = self.w,
                                       title = _("Puzzle Information"),
                                       label = _("There is no current puzzle.")
                                       )
            return
        puzzle = self.gsd.grid.virgin.to_string()
        diff = self.sudoku_maker.get_difficulty(puzzle)
        information = _("Calculated difficulty: ")
        information += diff.value_string()
        information += " (%1.2f)" % diff.value
        information += "\n"
        information += _("Number of moves instantly fillable by elimination: ")
        information += str(int(diff.instant_elimination_fillable))
        information += "\n"
        information += _("Number of moves instantly fillable by filling: ")
        information += str(int(diff.instant_fill_fillable))
        information += "\n"
        information += _("Amount of trial-and-error required to solve: ")
        information += str(len(diff.guesses))
        dialog_extras.show_message(parent = self.w,
                                   title = _("Puzzle Statistics"),
                                   label = _("Puzzle Statistics"),
                                   sublabel = information)

    @simple_debug
    def toggle_generator_cb (self, toggle):
        if toggle.get_active():
            self.start_worker_thread()
        else:
            self.stop_worker_thread()

    @simple_debug
    def autosave (self):
        # this is called on a regular loop and will autosave if we
        # have reason to...
        if self.gsd.grid and self.gsd.grid.is_changed() and not self.won:
            self.sudoku_tracker.save_game(self)
        return True

    @simple_debug
    def show_about (self, *args):
        about = gtk.AboutDialog()
        about.set_transient_for(self.w)
        about.set_name(APPNAME)
        about.set_version(VERSION)
        about.set_copyright(COPYRIGHT)
        about.set_license(LICENSE[0] + '\n\n' + LICENSE[1] + '\n\n'  + LICENSE[2])
        about.set_wrap_license(True)
        about.set_comments(DESCRIPTION)
        about.set_authors(AUTHORS)
        about.set_website(WEBSITE)
        about.set_website_label(WEBSITE_LABEL)
        about.set_logo_icon_name("gnome-sudoku")
        about.set_translator_credits(_("translator-credits"))
        about.connect("response", lambda d, r: d.destroy())
        about.show()

    @simple_debug
    def show_help (self, *args):
        try:
            gtk.show_uri(self.w.get_screen(), "ghelp:gnome-sudoku", gtk.get_current_event_time())
        except gobject.GError, error:
            # FIXME: This should create a pop-up dialog
            print _('Unable to display help: %s') % str(error)