class PuzzleWindow(PuzzleWindowBase): #FIXME: fix the docstring. """ Main window with all components. """ def __init__(self, application, *args, **kwargs): self.Application = application self.AppArgs = self.Application.MyArgs # define and bind settings, options to a class variable global settings global options settings = SectionConfig(self.Application.id, self.__class__.__name__) options = OptionConfig(self.Application.id) self.settings = settings self.options = options self.targets = None # Gtk.TargetList.new([]) self.dragaction = Gdk.DragAction.PRIVATE self.number_picked = None self.init_picker_done = False super().__init__(self, *args, **kwargs) def _eb_drag_begin(self, widget, context): #print(dir(context)) if len(widget.get_children()[0].get_label().strip()): self.number_picked = widget.my_tag #print(self.AppArgs.picker.subpixbufs[5].get_width()) else: self.number_picked = None def _eb_drag_end(self, widget, context): self.number_picked = None def fill_pieces(self): """ Fill label with remaining pieces. """ #do nothing if user has show_pieces=False if not self.listboxPieces.get_visible(): return remaining_dict = self.puzzle.get_remaining() #print('remaining_dict',remaining_dict) thelistbox = self.listboxPieces allchildren = thelistbox.get_children() for achild in allchildren[:]: achild.destroy() label = Gtk.Label(_('Remaining numbers:')) thelistbox.add(label) label.set_visible(True) for number_counter in range(9): thestr1 = self.AppArgs.strings_to_use[number_counter + 1] remaining = 9 - remaining_dict[number_counter + 1] label = Gtk.Label(thestr1 * remaining) label.set_xalign(0) eb = Gtk.EventBox() eb.add(label) if remaining > 0: eb.connect('button-press-event', self.on_piece_pressed, number_counter + 1) eb.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, self.targets, self.dragaction) #eb.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [Gtk.TargetEntry("the num", Gtk.TargetFlags.SAME_APP, x)], Gdk.DragAction.PRIVATE ) eb.connect_after("drag-begin", self._eb_drag_begin) eb.drag_source_add_text_targets() eb.connect("drag-end", self._eb_drag_end) #eb.drag_source_set_icon_name("gtk-open") eb.drag_source_set_icon_pixbuf( self.AppArgs.picker.subpixbufs[number_counter + 1]) #print('fill_pieces', self.AppArgs.picker.subpixbufs[number_counter+1].get_width()) label.set_tooltip_text( _("Drag «{}» to the board...").format(thestr1)) else: label.set_tooltip_text( _("You have placed all «{}».").format(thestr1)) thelistbox.add(eb) eb.set_visible(True) eb.my_tag = str(number_counter + 1) label.set_visible(True) label = Gtk.Label(str(remaining_dict[0])) label.set_xalign(0.5) eb = Gtk.EventBox() eb.add(label) thelistbox.insert(eb, 1) eb.set_visible(True) label.set_visible(True) theimage = Gtk.Image.new_from_icon_name("gtk-zoom-100", Gtk.IconSize.DND) theimage.set_visible(True) self.remaining_image = theimage thelistbox.add(theimage) #theimage.set_from_pixbuf () self.listboxPieces.show_all() def on_piece_pressed(self, widget, event, *args): """ Handler for any piece.button-press-event. Triggered if label with remaining pieces is pressed. """ #TODO: show all x's in the puzzle. #print('on_piece_pressed',args, self.AppArgs.picker.subpixbufs[args[0]].get_width()) self.remaining_image.set_from_pixbuf( self.AppArgs.picker.subpixbufs[args[0]]) return False def on_piece_released(self, widget, event, *args): """ Handler for any piece.button-release-event. Triggered if button on label with remaining pieces is released. """ #TODO: reset "show all x's in the puzzle". #print('on_piece_released') #self.number_picked = None #return False pass def passed_time(self): """ Show time passed. Show time if option is enabled. Don't use this while playing history. """ if self.playing_history: return False self.timepassed = datetime.datetime.utcnow() - self.timer_started allsecs = self.timepassed.seconds secs = int(allsecs % 60) mins = allsecs // 60 % 60 hrs = allsecs // 3600 #if diff.days: prefix = '>' if self.timepassed.days else '' tmp = prefix + '{0:02d}:{1:02d}:{2:02d}'.format(hrs, mins, secs) #[:7] self.labelClock.set_label(tmp) return True and not self.exiting def picker_hide(self): '''Hide the picker. ''' if self.is_picker_visible: self.AppArgs.picker.PickerWindow.hide() self.is_picker_visible = False def picker_resize(self, optional=None): if self.puzzle and self.puzzle.the_9: #set also best ratio if self.AppArgs.picker.PickerWindow.get_size( ) != self.puzzle.the_9 - 2: #self.picker_size = self.puzzle.the_9 self.AppArgs.picker.image_resize(self.puzzle.the_9 - 2) self.fill_pieces() def picker_show(self, event, col, row): '''Show picker if not in constant cell. ''' if self.puzzle.puzzlenums[self.puzzle.current_cell]['const']: return False if event.button == 1: #find window start window_x, window_y = self.PuzzleWindow.get_window( ).get_root_coords(0, 0) #find eventbox start full_size = self.eventboxPuzzle.get_allocation() #find working restangle start restangle_x = self.working_restangle.x + 5 restangle_y = self.working_restangle.y + 5 size = self.working_restangle.width - 10 #the size of one cell the_9 = size / 9 arow_x = the_9 * col arow_y = the_9 * row left = window_x + restangle_x + full_size.x + arow_x top = window_y + restangle_y + full_size.y + arow_y self.AppArgs.picker.PickerWindow.move(left, top) self.AppArgs.picker.PickerWindow.show_all() self.AppArgs.picker.PickerWindow.grab_focus() self.is_picker_visible = True return True def puzzle_continue(self): ''' Continue last saved game. If last game was solved start a new. If saved values are not valid start a new. If nothing was done start a new. ''' self.puzzle = Puzzle(self.AppArgs.history.board, self.AppArgs.strings_to_use, self.AppArgs.font_scale, self.AppArgs.history.history, self.AppArgs.font) self.puzzle.undos = options.get('last_undos', 0) self.puzzle.go_to_last_position() self.puzzle.check_puzzle() self.drawingareaPuzzle.queue_draw() self.set_undoredo() oldtimediff = datetime.timedelta(days=self.AppArgs.history.days, seconds=self.AppArgs.history.seconds) self.fill_pieces() self.timer_started = datetime.datetime.utcnow() - oldtimediff GObject.timeout_add(500, self.passed_time) def puzzle_move_cursor(self, tocell): '''Paint new selected cell. ''' self.puzzle.puzzlenums[self.puzzle.current_cell]['sel'] = False self.puzzle.current_cell = tocell self.puzzle.puzzlenums[self.puzzle.current_cell]['sel'] = True self.drawingareaPuzzle.queue_draw() return True def puzzle_set_number(self, anumber=-1, undoredo=MOVE_NEW): '''Set a number in selected cell. If undo or redo is required use history. ''' previousnum = self.puzzle.puzzlenums[self.puzzle.current_cell]['num'] if undoredo == MOVE_NEW: if self.puzzle.undos: #remove all undos from history. We start a new node self.puzzle.history = self.puzzle.history[:-self.puzzle.undos] self.puzzle.undos = 0 self.puzzle.puzzlenums[self.puzzle.current_cell]['num'] = anumber self.puzzle.append_to_history(previousnum) elif undoredo == MOVE_UNDO: self.puzzle.undos += 1 lastplayed = self.puzzle.history[-self.puzzle.undos] lastcell = int(lastplayed[2:]) previousnum = int(lastplayed[0]) anumber = int(lastplayed[1:2]) self.puzzle_move_cursor(lastcell) self.puzzle.puzzlenums[ self.puzzle.current_cell]['num'] = previousnum elif undoredo == MOVE_REDO: lastplayed = self.puzzle.history[(len(self.puzzle.history) - self.puzzle.undos)] lastcell = int(lastplayed[2:]) anumber = int(lastplayed[1:2]) self.puzzle.undos -= 1 self.puzzle_move_cursor(lastcell) self.puzzle.puzzlenums[self.puzzle.current_cell]['num'] = anumber self.puzzle.check_puzzle() self.drawingareaPuzzle.queue_draw() self.set_undoredo() self.fill_pieces() #print('filled after set a piece') #print(['{}:{},'.format(x, self.puzzle.puzzlenums[x]['num']) for x in self.puzzle.puzzlenums]) if self.puzzle.solved: self.show_solved() return True def puzzle_start(self): '''Start a new puzzle. ''' options.set('last_undos', 0) options.set('last_history', '') options.set('last_solved', False) options.set('last_board', self.AppArgs.current_board) self.puzzle = Puzzle(self.AppArgs.current_board, self.AppArgs.strings_to_use, self.AppArgs.font_scale, None, self.AppArgs.font) self.fill_pieces() self.timer_started = datetime.datetime.utcnow() GObject.timeout_add(500, self.passed_time) def response_from_picker(self, *args, **kwargs): '''Triggered from picker. ''' #print(kwargs) self.is_picker_visible = False if kwargs['number_as_str'] != None and kwargs['number_as_str'] != '-1': self.puzzle_set_number(int(kwargs['number_as_str'])) return True def set_undoredo(self): '''Write history in conf and show/hide undo-redo buttons. ''' self.buttonUndo.set_sensitive( (len(self.puzzle.history) > 0 and self.puzzle.undos < len(self.puzzle.history))) self.buttonRedo.set_sensitive(self.puzzle.undos > 0) def show_solved(self): """ Show that puzzle is solved and call exit window. """ now = datetime.datetime.utcnow() #print('now,self.timer_started',now,self.timer_started) diff = now - self.timer_started #self.return_parameter = (True, str(diff)[2:7]) if self.is_picker_visible: self.AppArgs.picker.PickerWindow.hide() self.exiting = True self.msg('{}\n ({}: {})'.format(_('Solved!'), _('time passed'), str(diff)[2:7])) self.exit_requested() def get_cell_number(self, widget, event): ebW = widget.get_allocated_width() imgW = ebW // 9 #self.image1.get_allocated_width() ebH = widget.get_allocated_height() imgH = ebH // 9 #self.image1.get_allocated_height() xstart = (ebW - imgW) // 2 ystart = (ebH - imgH) // 2 cellsize = int(imgW // 9) cellx = event.x // (imgH // 9) celly = event.y // (imgW // 9) if event.x > xstart and event.x < ebW - xstart: if event.y > ystart and event.y < ebH - ystart: thex = event.x - xstart they = event.y - ystart cell_number = 9 * (event.y // (imgW // 9)) + event.x // (imgH // 9) + 1 else: cell_number = -1 return int(cell_number), cellsize, cellx, celly
class PuzzleWindow(Gtk.Window): """ Main window with all components. """ def __init__(self, app): # Set the app self.app = app # Basic initializations. self.we_can_exit_now = False self.return_parameter = (False, "00:00") self.picker = None self.picker_size = 10. self.previous_sel = None self.timer_started = None self.history_counter = 0 self.playing_history = False #self.strings_to_use = [' ','α','β','γ','δ','ε','ς','ζ','η','θ'] #self.strings_to_use = [' ','1','2','3','4','5','6','7','8','9'] # Init the settings module. self.dummy_for_settings = SectionConfig(self.app.name, self.__class__.__name__) global settings settings = self.dummy_for_settings self.dummy_for_options = OptionConfig(self.app.name) global options options = self.dummy_for_options Gtk.Window.__init__(self) self.set_title(self.app.localizedname) # Initializations required before loading glade file. # Bind the locale. locale.bindtextdomain(self.app.domain, os.path.join(self.app.BASE_DIR, 'locale')) locale.textdomain(self.app.domain) # Load app and window icon. self.set_icon(self.app.icon) # Bind message boxes. self.MessageBox = MessageBox(self) self.msg = self.MessageBox.Message # Glade stuff # Load Glade file to self self.builder = Gtk.Builder() try: self.builder.add_from_file( os.path.join(self.app.BASE_DIR, 'ui', 'puzzlewindow.glade')) except Exception as ex: print(str(ex)) print('\n{}:\n{}\n{}'.format( _('Error loading from Glade file'), os.path.join(self.app.BASE_DIR, 'ui', 'puzzlewindow.glade'), repr(ex))) sys.exit(ERROR_INVALID_GLADE_FILE) # Get gui objects self.MainBox = self.builder.get_object('MainBox') self.boxTimer = self.builder.get_object('boxTimer') self.boxMenu = self.builder.get_object('boxMenu') self.buttonHome = self.builder.get_object('buttonHome') self.drawingareaPuzzle = self.builder.get_object('drawingareaPuzzle') self.eventboxPuzzle = self.builder.get_object('eventboxPuzzle') self.imageClock = self.builder.get_object('imageClock') self.labelClock = self.builder.get_object('labelClock') self.labelVersion = self.builder.get_object('labelVersion') self.listboxPieces = self.builder.get_object('listboxPieces') # Connect signals existing in the Glade file self.builder.connect_signals(self) # Reparent our main container from glader file, # this way we have all Gtk.Window functionality using "self" thechild = self.builder.get_object('PuzzleWindow').get_child() thechild.get_parent().remove(thechild) self.add(thechild) # Connect generated signals. self.buttonHome.connect('clicked', self.on_buttonHome_clicked) self.connect('delete-event', self.on_PuzzleWindow_delete_event) self.connect('destroy', self.on_PuzzleWindow_destroy) self.connect('key-release-event', self.on_PuzzleWindow_key_release_event) self.connect('size-allocate', self.on_PuzzleWindow_size_allocate) self.connect('window-state-event', self.on_PuzzleWindow_window_state_event) # Get any properties of top window. # Set the label for labelVersion self.labelVersion.set_label(VERSIONSTR) self.can_focus = 'False' # Load any settings or run extra initializations self.post_initialisations() #********* Auto created "class defs" START ************************************************************ #********* Auto created handlers START ********************************* def on_PuzzleWindow_delete_event(self, widget, event, *args): """ Handler for PuzzleWindow.delete-event. """ self.set_unhandled_settings() return False def on_PuzzleWindow_destroy(self, widget, *args): """ Handler for PuzzleWindow.destroy. """ if self.picker: self.picker.destroy() self.exit_requested() return False def on_PuzzleWindow_key_release_event(self, widget, event, *args): """ Handler for PuzzleWindow.key-release-event. """ #if self.playing_history: #return False txt = Gdk.keyval_name(event.keyval) if type(txt) == type(None): return False txt = txt.replace('KP_', '') try: aunichar = chr(Gdk.keyval_to_unicode(event.keyval)) except: aunichar = None if txt in ['Up', 'Down', 'Left', 'Right']: self.move_selection(['Up', 'Down', 'Left', 'Right'].index(txt)) elif txt in [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'Delete' ] or txt in self.app.strings_to_use[1:]: self.set_num_in_selection(txt) elif aunichar and aunichar in self.app.strings_to_use[1:]: self.set_num_in_selection(aunichar) else: return False return True def on_PuzzleWindow_size_allocate(self, widget, allocation, *args): """ Handler for PuzzleWindow.size-allocate. """ self.save_my_size() def on_PuzzleWindow_window_state_event(self, widget, event, *args): """ Handler for PuzzleWindow.window-state-event. """ settings.set('maximized', ((int(event.new_window_state) & Gdk.WindowState.ICONIFIED) != Gdk.WindowState.ICONIFIED) and ((int(event.new_window_state) & Gdk.WindowState.MAXIMIZED) == Gdk.WindowState.MAXIMIZED)) self.save_my_size() def on_buttonHome_clicked(self, widget, *args): """ Handler for buttonHome.clicked. """ self.exit_requested() def on_drawingareaPuzzle_draw(self, widget, cr, *args): """ Handler for drawingareaPuzzle.draw. """ w = widget.get_allocated_width() h = widget.get_allocated_height() b = self.puzzle.draw(cr, w, h) self.fill_pieces() def on_eventboxPuzzle_button_press_event(self, widget, event, *args): """ Handler for eventboxPuzzle.button-press-event. """ self.picker_hide() col, rest = divmod(event.x - self.puzzle.startx, self.puzzle.the_9) row, rest = divmod(event.y - self.puzzle.starty, self.puzzle.the_9) if col >= 0 and row >= 0 and col < 9 and row < 9: blah = int(col) * 9 + int(row) if self.previous_sel != None: self.puzzle.puzzlenums[self.previous_sel]['sel'] = False self.puzzle.puzzlenums[blah]['sel'] = True self.previous_sel = blah self.drawingareaPuzzle.queue_draw() if not self.puzzle.puzzlenums[blah]['const']: if event.button == 3: self.puzzle.puzzlenums[self.previous_sel]['num'] = 0 self.puzzle.append_to_history( (self.previous_sel, self.puzzle.puzzlenums[self.previous_sel]['num'], self.labelClock.get_label())) elif event.button == 1: left, top = self.get_position() if self.picker_size != self.puzzle.the_9: self.picker_size = self.puzzle.the_9 self.picker.resize(self.picker_size, self.picker_size) self.picker.move(left + event.x, top + event.y) response = self.picker.get_number() if response: self.puzzle.puzzlenums[self.previous_sel]['num'] = int( response) self.puzzle.append_to_history( (self.previous_sel, self.puzzle.puzzlenums[self.previous_sel]['num'], self.labelClock.get_label())) else: return False self.puzzle.check_puzzle() self.drawingareaPuzzle.queue_draw() if self.puzzle.solved: self.show_solved() return True else: print('outside') #********* Auto created handlers END ********************************** def post_initialisations(self): """ Do some extra initializations. Display the version if a labelVersion is found. Set defaults (try to load them from a configuration file): - Window size and state (width, height and if maximized) Load other saved custom settings. """ # Set previous size and state width = settings.get('width', 350) height = settings.get('height', 350) self.set_title(self.app.localizedname) self.resize(width, height) if settings.get_bool('maximized', False): self.maximize() # Load any other settings here. if self.app.show_simple: self.boxMenu.set_visible(not self.app.show_simple) self.boxMenu.set_no_show_all(True) if not self.app.show_pieces: self.listboxPieces.set_no_show_all(True) if not self.app.show_timer: self.boxTimer.set_no_show_all(True) self.listboxPieces.set_visible(self.app.show_pieces) self.imageClock.set_visible(self.app.show_timer) self.labelClock.set_visible(self.app.show_timer) if self.app.thehistory: # history passed, user wants replay. self.set_sensitive(False) self.playing_history = True self.puzzle = Puzzle(self.app.puzzle_str, self.app.strings_to_use, self.app.font_scale, self.app.thehistory) else: self.start_puzzle() def set_unhandled_settings(self): """ Set, before exit, settings not applied during the session. Mass set some non critical settings for widgets (which where not setted when some widget's state changed). """ #custom settings history_str = str(self.puzzle.history) #history_str = history_str.replace("'",'"') #history_json = json.loads(history_str) #history_json_str = json.dump(history_json) #history_str = history_json_str #print() history_str = history_str.replace("}}, ", "}},\n ") history_str = history_str.replace("), ", "),\n ") options.set('last_history', history_str) def save_my_size(self): """ Save the window size into settings if not maximized. """ if not settings.get_bool('maximized', False): width, height = self.get_size() settings.set('width', width) settings.set('height', height) def exit_requested(self, *args): """ Set the param in order to exit from "run" method. """ self.set_transient_for() self.set_modal(False) self.set_unhandled_settings() self.we_can_exit_now = True self.destroy() #Will not call Handler for delete-event def run(self): """ Start the main loop. WARNING: The "destroy" event of the main window MUST set the "we_can_exit_now" to True, else program will never exit. Save settings on exit. Return "return_parameter" on exit. """ #now we can show the main window. self.show_all() #self.boxMenu.set_visible(not self.app.show_simple) #self.set_interactive_debugging (True) if self.app.thehistory: GObject.timeout_add(1000, self.play_history) while True: if self.we_can_exit_now: break while Gtk.events_pending(): Gtk.main_iteration() settings.save() return self.return_parameter #********* Auto created "class defs" END ************************************************************** def on_piece_pressed(self, widget, event, *args): """ Handler for any piece.button-press-event. """ print('pressed piece:', args[0]) def show_solved(self): """ Show a message and call exit window. """ now = datetime.datetime.utcnow() print('now,self.timer_started', now, self.timer_started) diff = now - self.timer_started self.return_parameter = (True, str(diff)[2:7]) self.msg('{}\n ({}: {})'.format(_('Solved!'), _('time passed'), self.return_parameter[1])) self.exit_requested() def set_num_in_selection(self, txt): """ Pokes the num in the puzzlenums dictionary. """ self.picker_hide() sel = self.puzzle.puzzlenums[self.previous_sel] if sel['const']: return if txt in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: sel['num'] = int(txt) elif txt in self.app.strings_to_use[1:]: sel['num'] = self.app.strings_to_use.index(txt) elif txt == 'Delete': sel['num'] = 0 else: return self.puzzle.append_to_history( (self.previous_sel, sel['num'], self.labelClock.get_label())) self.puzzle.check_puzzle() self.drawingareaPuzzle.queue_draw() if self.puzzle.solved: self.show_solved() def move_selection(self, where): """ Move selection to cell based on key pressed. """ self.picker_hide() if self.previous_sel == None: self.previous_sel = 0 self.puzzle.puzzlenums[0]['sel'] = True self.drawingareaPuzzle.queue_draw() else: sel = -1 if where == 0: sel = self.previous_sel - 1 elif where == 1: sel = self.previous_sel + 1 elif where == 2: sel = self.previous_sel - 9 elif where == 3: sel = self.previous_sel + 9 if sel >= 0 and sel <= 80: self.puzzle.puzzlenums[self.previous_sel]['sel'] = False self.previous_sel = sel self.puzzle.puzzlenums[self.previous_sel]['sel'] = True self.drawingareaPuzzle.queue_draw() def picker_hide(self): """ Hide the picker, if any. """ if self.picker: self.picker.hide() def fill_pieces(self): """ Fill label with remaining pieces. """ if not self.listboxPieces.get_visible(): return thelistbox = self.listboxPieces allchildren = thelistbox.get_children() for achild in allchildren[:]: achild.destroy() label = Gtk.Label(_('Remaining:')) thelistbox.add(label) label.set_visible(True) for x in range(9): label = Gtk.Label("<" + self.app.strings_to_use[x + 1] + '>: ' + str(9 - self.puzzle.pieces[x + 1])) eb = Gtk.EventBox() eb.connect("button-press-event", self.on_piece_pressed, x) eb.add(label) thelistbox.add(eb) eb.set_visible(True) label.set_visible(True) label = Gtk.Label("<.>: " + str(self.puzzle.pieces[0])) eb = Gtk.EventBox() eb.add(label) thelistbox.add(eb) eb.set_visible(True) label.set_visible(True) def start_puzzle(self): self.picker = NumberPicker(self, thelist=self.app.strings_to_use) self.picker.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.puzzle = Puzzle(self.app.puzzle_str, self.app.strings_to_use, self.app.font_scale) self.timer_started = datetime.datetime.utcnow() GObject.timeout_add(500, self.show_time_passed) def play_history(self): if self.history_counter >= len(self.app.thehistory) - 1: # Set to continue print('CONTINUE') self.playing_history = False self.picker = NumberPicker(self, thelist=self.app.strings_to_use) self.picker.set_type_hint(Gdk.WindowTypeHint.DIALOG) # set the timer timestr = self.labelClock.get_label() b = datetime.timedelta(seconds=int(timestr[:2]) * 60 + int(timestr[3:])) self.timer_started = datetime.datetime.utcnow() - b self.puzzle.check_puzzle() if self.puzzle.solved: #self.return_parameter = (True, self.labelClock.get_label()) self.show_solved() else: self.puzzle.check_puzzle() GObject.timeout_add(500, self.show_time_passed) self.set_sensitive(True) return False self.history_counter += 1 amove = self.app.thehistory[self.history_counter] self.puzzle.puzzlenums[amove[0]]['num'] = amove[1] self.labelClock.set_label(amove[2]) self.puzzle.check_puzzle() self.drawingareaPuzzle.queue_draw() return True def show_time_passed(self): """ Show time passed. Only works if option is enabled. """ if self.playing_history: return False diff = datetime.datetime.utcnow() - self.timer_started tmp = str(diff)[2:7] self.return_parameter = (self.return_parameter[0], tmp) self.labelClock.set_label(tmp) return True and not self.we_can_exit_now