From 5ceda38732c57211d3f38e6cbbd7b8a521a0a9e5 Mon Sep 17 00:00:00 2001 From: Daniel Francis Date: Wed, 03 Oct 2012 00:23:16 +0000 Subject: Start sugarizing; use Gtk3 --- diff --git a/activity.info b/activity.info deleted file mode 100644 index 475641f..0000000 --- a/activity.info +++ /dev/null @@ -1,9 +0,0 @@ -[Activity] -name = Sudoku -activity_version = 1 -show_launcher = 1 -bundle_id = org.gnome.Sudoku -exec = sugar-activity activity.Activity -s -icon = activity-sudoku -license = GPLv3 - diff --git a/activity.py b/activity.py index c868c66..1c3603c 100644 --- a/activity.py +++ b/activity.py @@ -23,13 +23,15 @@ import os os.environ['PROGRAMRUNNING'] = 'SUGAR' import logging logging.basicConfig(level=logging.DEBUG) -logger = logging.getLogger('graph-plotter') +logger = logging.getLogger('activity') from gettext import gettext as _ -import gtk +from gi.repository import Gtk +logger.debug('Import basic core') -from sugar.datastore import datastore -from sugar.activity import activity +from sugar3.datastore import datastore +from sugar3.activity import activity +logger.debug('Import Sugar toolkit') try: import sweetener @@ -37,12 +39,14 @@ except ImportError: import sys sys.path.append(os.path.abspath('./sugar')) import sweetener +logger.debug('Import Sweetener') os.environ['DATA_DIR'] = os.path.abspath('./data') from options import Options +logger.debug('Import options') from canvas import Canvas - +logger.debug('Imported extra modules') class Activity(activity.Activity): def __init__(self, handle): @@ -53,6 +57,7 @@ class Activity(activity.Activity): self.canvas = Canvas(self.options, self) self.canvas.show() self.set_canvas(self.canvas) + logger.debug('Finish loading') def export(self, widget, data): jobject = datastore.create() diff --git a/activity/activity.info b/activity/activity.info index 475641f..b103f13 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,7 +1,6 @@ [Activity] name = Sudoku activity_version = 1 -show_launcher = 1 bundle_id = org.gnome.Sudoku exec = sugar-activity activity.Activity -s icon = activity-sudoku diff --git a/application.py b/application.py index fe11039..aa4ef72 100755 --- a/application.py +++ b/application.py @@ -21,15 +21,16 @@ import os import logging logging.basicConfig(level=logging.DEBUG) -import glib +from gi.repository import GLib import info logger = logging.getLogger(info.lower_name) appname = info.name # Some desktops like Gnome3-Shell show the Glib Prgname -glib.set_prgname(appname) -glib.set_application_name(appname) +GLib.set_prgname(appname) +GLib.set_application_name(appname) -import gtk +from gi.repository import Gtk +from gi.repository import Gdk from options import Options from canvas import Canvas @@ -42,35 +43,22 @@ os.chdir(this_dir) from gettext import gettext as _ -if 'programabspath' in os.environ['DATADIR']: - datadir = os.environ['DATADIR'].replace('programabspath', - this_dir) -else: - datadir = os.environ['DATADIR'] - -if os.environ['ICONDIR'] != 'SYSTEM': - logger.debug(this_dir) - icondir = os.environ['ICONDIR'].replace('programabspath', - this_dir) - icon_theme = gtk.icon_theme_get_for_screen(gtk.gdk.screen_get_default()) - icon_theme.append_search_path(icondir) - -class UnfullscreenButton(gtk.Window): +class UnfullscreenButton(Gtk.Window): def __init__(self): - gtk.Window.__init__(self) + Gtk.Window.__init__(self) self.set_decorated(False) self.set_resizable(False) - self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) self.set_border_width(0) self.props.accept_focus = False #Setup estimate of width, height - w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR) + w, h = Gtk.icon_size_lookup(Gtk.IconSize.LARGE_TOOLBAR) self._width = w self._height = h @@ -79,8 +67,8 @@ class UnfullscreenButton(gtk.Window): screen = self.get_screen() screen.connect('size-changed', self._screen_size_changed_cb) - self._button = gtk.Button(stock=gtk.STOCK_LEAVE_FULLSCREEN) - self._button.set_relief(gtk.RELIEF_NONE) + self._button = Gtk.Button(stock=Gtk.STOCK_LEAVE_FULLSCREEN) + self._button.set_relief(Gtk.ReliefStyle.NONE) self._button.show() self.add(self._button) @@ -100,28 +88,30 @@ class UnfullscreenButton(gtk.Window): self._reposition() -class Application(gtk.Window): +class Application(Gtk.Window): def __init__(self): - gtk.Window.__init__(self) + Gtk.Window.__init__(self) self.save_type = info.io_mode self.started = False self.file_path = None + self.filter = Gtk.FileFilter() if info.file_filter_name: - self.filter = gtk.FileFilter() self.filter.set_name(info.file_filter_name) if info.file_filter_mime: self.filter.add_mime_type(info.file_filter_mime) - for i in info.file_filter_patterns: - self.filter.add_pattern(i) - self.accel_group = gtk.AccelGroup() + if info.file_filter_pattern: + self.filter.add_pattern(info.file_filter_pattern) + else: + self.filter.set_name(_('All')) + self.filter.add_pattern('*') + self.accel_group = Gtk.AccelGroup() self.add_accel_group(self.accel_group) self.set_title(appname) logger.debug(info.lower_name) - self.set_icon(gtk.gdk.pixbuf_new_from_file(os.path.join(datadir, - 'appicon.svg'))) + self.set_icon_name('gtkslidy') self.connect('delete-event', lambda w, e: self.stop()) self.maximize() - self._vbox = gtk.VBox() + self._vbox = Gtk.VBox() self.options = Options(self) self.options.show() self._vbox.pack_start(self.options, False, True, 0) @@ -132,12 +122,12 @@ class Application(gtk.Window): self._vbox.show() self._is_fullscreen = False - self.set_events(gtk.gdk.ALL_EVENTS_MASK) + self.set_events(Gdk.EventMask.ALL_EVENTS_MASK) self.connect('motion-notify-event', self.motion_cb) - self.canvas.connect('expose-event', self.expose_event) + self.canvas.connect('draw', self.expose_event) - def expose_event(self, widget, event): - logger.debug('Exposing') + def expose_event(self, widget, context): + #logger.debug('Exposing') if not self.started: if self.file_path != None: self.canvas.read_file(self.file_path) @@ -171,17 +161,19 @@ class Application(gtk.Window): self.options.show() self._is_fullscreen = False self._unfullscreen_button.destroy() - gtk.Window.unfullscreen(self) + Gtk.Window.unfullscreen(self) def export(self, widget, data): format_name = data[3] filter_mime = data[2] mime_type = data[1] - filechooser = gtk.FileChooserDialog(_("Export..."), self, - gtk.FILE_CHOOSER_ACTION_SAVE, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK)) - file_filter = gtk.FileFilter() + filechooser = Gtk.FileChooserDialog(_("Export..."), self, + Gtk.FileChooserAction.SAVE, + (Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, + Gtk.ResponseType.OK)) + file_filter = Gtk.FileFilter() file_filter.set_name(format_name) file_filter.add_mime_type(filter_mime) filechooser.add_filter(file_filter) @@ -200,18 +192,19 @@ class Application(gtk.Window): if info.io_mode == info.CONFIG: self.file_path = os.path.join(os.environ['HOME'], '.' + info.lower_name) self.save() - gtk.main_quit() + Gtk.main_quit() def open(self): - filechooser = gtk.FileChooserDialog(_('Open...'), self, - gtk.FILE_CHOOSER_ACTION_OPEN, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OPEN, gtk.RESPONSE_OK)) + filechooser = Gtk.FileChooserDialog(_('Open...'), self, + Gtk.FileChooserAction.OPEN, + (Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) filechooser.add_filter(self.filter) response = filechooser.run() self.file_path = filechooser.get_filename() filechooser.destroy() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: logger.debug('Read file %s' % self.file_path) self.canvas.read_file(self.file_path) self.set_title('%s - %s' % (self.file_path, appname)) @@ -224,23 +217,25 @@ class Application(gtk.Window): self.canvas.write_file(self.file_path) def save_as(self): - filechooser = gtk.FileChooserDialog(_('Save...'), self, - gtk.FILE_CHOOSER_ACTION_SAVE, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK)) + filechooser = Gtk.FileChooserDialog(_('Save...'), self, + Gtk.FileChooserAction.SAVE, + (Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, + Gtk.ResponseType.OK)) filechooser.add_filter(self.filter) filechooser.set_do_overwrite_confirmation(True) response = filechooser.run() self.file_path = filechooser.get_filename() filechooser.destroy() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: logger.debug('Save file as %s' % self.file_path) self.canvas.write_file(self.file_path) self.set_title('%s - %s' % (self.file_path, appname)) -if __name__ == '__main__': - logger.debug('Initializing Graph Plotter') +def start(): + logger.debug('Initializing %s' % info.name) import sys if info.io_mode == info.DOCUMENT: if len(sys.argv) > 1: @@ -262,6 +257,8 @@ if __name__ == '__main__': window = Application() window.file_path = file_path window.show() - gtk.main() - logger.debug('Closing Graph Plotter') + Gtk.main() + logger.debug('Closing') exit() + +start() diff --git a/canvas.py b/canvas.py index 34eb00e..ba58b28 100644 --- a/canvas.py +++ b/canvas.py @@ -20,13 +20,40 @@ import logging logger = logging.getLogger('canvas') -import gtk +logger.debug('start canvas') +import math +from gi.repository import Gtk +from number_box import SudokuNumberBox +logger.debug('Import all') -class Canvas(gtk.AspectFrame): +class Canvas(Gtk.AspectFrame): def __init__(self, toolbar_box, activity): - gtk.AspectFrame.__init__(self) + Gtk.AspectFrame.__init__(self) + self.group_size = 9 + self.set_shadow_type(Gtk.ShadowType.NONE) self.activity = activity + self.table = Gtk.Table(rows = self.group_size, columns = self.group_size, homogeneous = True) + self.__entries__ = {} + for x in range(self.group_size): + for y in range(self.group_size): + e = SudokuNumberBox(upper = self.group_size) + e.x = x + e.y = y + self.table.attach(e, x, x+1, y, y+1, + ) + self.__entries__[(x, y)] = e + + self.eb = Gtk.EventBox() + self.eb.add(self.table) + self.add(self.eb) + self.table.set_row_spacings(1) + self.table.set_col_spacings(1) + box_side = int(math.sqrt(self.group_size)) + for n in range(1, box_side): + self.table.set_row_spacing(box_side*n-1, 2) + self.table.set_col_spacing(box_side*n-1, 2) + self.table.set_border_width(2) def write_file(self, path): pass diff --git a/defaults.py b/defaults.py new file mode 100644 index 0000000..93c6a53 --- /dev/null +++ b/defaults.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# defaults.py.in sets many important default global variables +# used throughout the game. Note that this file is processed by +# automake to set prefix paths etc. Please keep defaults.py.in +# in sync between glchess and gnome-sudoku. + +import os +import sys +import errno +import locale +import gettext + +from gi.repository import GLib + +try: + from defs import VERSION, PREFIX +except ImportError: + PREFIX = "/usr" + VERSION = "0.0.0" + +root_dir = os.path.dirname(os.path.dirname(__file__)) +if not os.path.exists(os.path.join(root_dir, "Makefile.am")): + # Running in installed mode + APP_DATA_DIR = os.path.join(PREFIX, 'share') + BASE_DIR = os.path.join(APP_DATA_DIR, 'gnome-sudoku') + IMAGE_DIR = os.path.join(BASE_DIR, 'images') + LOCALEDIR = os.path.join(APP_DATA_DIR, 'locale') + UI_DIR = BASE_DIR + PUZZLE_DIR = os.path.join(BASE_DIR, 'puzzles') +else: + # Running in uninstalled mode + sys.path.insert(0, os.path.abspath(root_dir)) + APP_DATA_DIR = os.path.join(root_dir, '../data') + IMAGE_DIR = os.path.join(root_dir, '../images') + LOCALEDIR = os.path.join(APP_DATA_DIR, 'locale') + UI_DIR = os.path.join(root_dir, '../data') + BASE_DIR = os.path.join(root_dir, '../data') + PUZZLE_DIR = BASE_DIR + +DOMAIN = 'gnome-games' +locale.bind_textdomain_codeset(DOMAIN, "UTF-8") # See Bug 608425 +gettext.bindtextdomain(DOMAIN, LOCALEDIR) +gettext.textdomain(DOMAIN) +from gettext import gettext as _ + +APPNAME = _("GNOME Sudoku") +APPNAME_SHORT = _("Sudoku") +COPYRIGHT = 'Copyright \xc2\xa9 2005-2008, Thomas M. Hinkle' +DESCRIPTION = _('GNOME Sudoku is a simple Sudoku generator and player. Sudoku is a Japanese logic puzzle.\n\nGNOME Sudoku is a part of GNOME Games.') +AUTHORS = ("Thomas M. Hinkle","John Stowers") +WEBSITE = 'http://www.gnome.org/projects/gnome-games/' +WEBSITE_LABEL = _('GNOME Games web site') +AUTO_SAVE = True +MIN_NEW_PUZZLES = 90 + +DATA_DIR = os.path.join(GLib.get_user_config_dir(),"gnome-sudoku/") + diff --git a/desktop/sweetener/basic_options.py b/desktop/sweetener/basic_options.py index 83636e1..44869fe 100644 --- a/desktop/sweetener/basic_options.py +++ b/desktop/sweetener/basic_options.py @@ -20,7 +20,7 @@ See class BasicOptions. # MA 02110-1301, USA. from gettext import gettext as _ -import gtk +from gi.repository import Gtk import stock from item import Item @@ -42,19 +42,20 @@ class BasicOptions(ItemGroup): ItemGroup.__init__(self, box, _('_File'), None) if activity.save_type != CONFIG: - new = Item(gtk.STOCK_NEW, True) + new = Item(Gtk.STOCK_NEW, True) new.connect('activate', lambda w: activity.new()) self.append_item(new) - _open = Item(gtk.STOCK_OPEN, True) + _open = Item(Gtk.STOCK_OPEN, True) _open.connect('activate', lambda w: activity.open()) self.append_item(_open) self.append_separator() - save_option = Item(gtk.STOCK_SAVE, True) + save_option = Item(Gtk.STOCK_SAVE, True) save_option.connect('activate', lambda w: activity.save()) self.append_item(save_option) - save_as_option = Item(gtk.STOCK_SAVE_AS) + save_as_option = Item(Gtk.STOCK_SAVE_AS) save_as_option.connect('activate', lambda w: activity.save_as()) self.append_item(save_as_option) + self.append_separator() if export_formats != None: if len(export_formats) == 1: stock.register('sweetener-%s' % export_formats[0][1], @@ -66,6 +67,6 @@ class BasicOptions(ItemGroup): export_formats[0]) self.append_item(export) self.append_separator() - _quit = Item(gtk.STOCK_QUIT) + _quit = Item(Gtk.STOCK_QUIT) _quit.connect('activate', lambda w: activity.stop()) self.append_item(_quit) diff --git a/desktop/sweetener/item.py b/desktop/sweetener/item.py index 1b5fc8e..80a2984 100644 --- a/desktop/sweetener/item.py +++ b/desktop/sweetener/item.py @@ -21,21 +21,21 @@ import logging logger = logging.getLogger('option') -import gobject -import gtk +from gi.repository import GObject +from gi.repository import Gtk import stock -class Item(gobject.GObject): - __gsignals__ = {'activate': (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, +class Item(GObject.GObject): + __gsignals__ = {'activate': (GObject.SignalFlags.RUN_LAST, + None, tuple())} menuitem = None toolitem = None def __init__(self, stock_id=None, important=False): - gobject.GObject.__init__(self) + GObject.GObject.__init__(self) self._stock_id = stock_id self.accel_group = None self.important = important @@ -52,9 +52,10 @@ class Item(gobject.GObject): stock_id = property(get_stock_id, set_stock_id) def get_menu_item(self): - self.menuitem = gtk.ImageMenuItem(self._stock_id) + self.menuitem = Gtk.ImageMenuItem.new_from_stock(self._stock_id, + self.accel_group) self.menuitem.connect('activate', self.activate_cb) - self.setup_accelerator() + #self.setup_accelerator() return self.menuitem def activate_cb(self, widget): @@ -66,10 +67,10 @@ class Item(gobject.GObject): if accelerator[1] > 0: self.menuitem.add_accelerator('activate', self.accel_group, accelerator[1], accelerator[0], - gtk.ACCEL_VISIBLE) + Gtk.AccelFlags.VISIBLE) def get_tool_item(self): - self.toolitem = gtk.ToolButton(self._stock_id) + self.toolitem = Gtk.ToolButton(self._stock_id) self.toolitem.connect('clicked', self.activate_cb) self.setup_tooltip() return self.toolitem @@ -78,7 +79,7 @@ class Item(gobject.GObject): if self.tooltip: self.toolitem.set_tooltip_text(self.tooltip) else: - text = gtk.stock_lookup(self.stock_id)[1] + text = Gtk.stock_lookup(self.stock_id).label self.toolitem.set_tooltip_text(text.replace('_', '')) def emit_signal(self, widget, signal_name): diff --git a/desktop/sweetener/itembox.py b/desktop/sweetener/itembox.py index b7ff60d..088996d 100644 --- a/desktop/sweetener/itembox.py +++ b/desktop/sweetener/itembox.py @@ -18,16 +18,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -import gtk +from gi.repository import GObject +from gi.repository import Gtk -class ItemBox(gtk.VBox): +class ItemBox(Gtk.VBox): def __init__(self, activity): - gtk.VBox.__init__(self) + GObject.GObject.__init__(self) self._parent = activity - self.menubar = gtk.MenuBar() - self.toolbar = gtk.Toolbar() + self.menubar = Gtk.MenuBar() + self.toolbar = Gtk.Toolbar() self.pack_start(self.menubar, False, True, 0) self.pack_start(self.toolbar, False, True, 0) self.menubar.show() - self.toolbar.show() diff --git a/desktop/sweetener/itemgroup.py b/desktop/sweetener/itemgroup.py index 3e4780e..16ac46c 100644 --- a/desktop/sweetener/itemgroup.py +++ b/desktop/sweetener/itemgroup.py @@ -18,18 +18,18 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -import gobject -import gtk +from gi.repository import GObject +from gi.repository import Gtk -class ItemGroup(gobject.GObject): +class ItemGroup(GObject.GObject): def __init__(self, box, name=None, icon=None): - gobject.GObject.__init__(self) + GObject.GObject.__init__(self) self.items = [] self.first_important = True - self.item = gtk.MenuItem(name) + self.item = Gtk.MenuItem.new_with_mnemonic(name) box.menubar.append(self.item) - self.menu = gtk.Menu() + self.menu = Gtk.Menu() self.item.set_submenu(self.menu) self.menu.show() self.item.show() @@ -44,9 +44,10 @@ class ItemGroup(gobject.GObject): self.menu.append(menuitem) if item.important: if self.first_important and len(self.toolbar): - separator = gtk.SeparatorToolItem() + separator = Gtk.SeparatorToolItem() separator.show() self.toolbar.insert(separator, -1) + self.toolbar.show() self.first_important = False tool_item = item.get_tool_item() self.toolbar.insert(tool_item, -1) @@ -54,11 +55,11 @@ class ItemGroup(gobject.GObject): self.items.append(item) def append_separator(self, important=False): - menuitem = gtk.SeparatorMenuItem() + menuitem = Gtk.SeparatorMenuItem() menuitem.show() self.menu.append(menuitem) if important: - toolitem = gtk.SeparatorToolItem() + toolitem = Gtk.SeparatorToolItem() toolitem.show() self.toolbar.insert(toolitem, -1) return toolitem @@ -72,11 +73,11 @@ class GhostGroup(ItemGroup): class SubGroup(ItemGroup): def __init__(self, group, name=None): - gobject.GObject.__init__(self) + GObject.GObject.__init__(self) self.items = [] - self.item = gtk.MenuItem(name) + self.item = Gtk.MenuItem(name) group.menu.append(self.item) - self.menu = gtk.Menu() + self.menu = Gtk.Menu() self.item.set_submenu(self.menu) self.menu.show() self.item.show() diff --git a/desktop/sweetener/stock.py b/desktop/sweetener/stock.py index 4f3b213..b92666d 100644 --- a/desktop/sweetener/stock.py +++ b/desktop/sweetener/stock.py @@ -20,9 +20,9 @@ import logging logger = logging.getLogger('stock') -import gtk +from gi.repository import Gtk -icon_factory = gtk.IconFactory() +icon_factory = Gtk.IconFactory() def register(name, label, accelerator, icon_name): @@ -30,46 +30,54 @@ def register(name, label, accelerator, icon_name): keyval = 0 mask = 0 else: - keyval, mask = gtk.accelerator_parse(accelerator) - gtk.stock_add([(name, label, mask, keyval, '')]) + keyval, mask = Gtk.accelerator_parse(accelerator) + logger.debug(keyval) + logger.debug(mask) + item = Gtk.StockItem.new() + item.stock_id = name + item.label = label + item.modifier = mask + item.keyval = keyval + Gtk.stock_add([item]) if icon_name: - icon_source = gtk.IconSource() + icon_source = Gtk.IconSource() icon_source.set_icon_name(icon_name) - icon = gtk.IconSet() + icon = Gtk.IconSet() icon.add_source(icon_source) icon_factory.add(name, icon) icon_factory.add_default() def overwrite_stock(stock_id, new_accelerator): - info = list(gtk.stock_lookup(stock_id)) - keyval, mask = gtk.accelerator_parse(new_accelerator) - info[2] = mask - info[3] = keyval + info = Gtk.stock_lookup(stock_id) + keyval, mask = Gtk.accelerator_parse(new_accelerator) + info.modifier = mask + info.keyval = keyval logger.debug(str(info)) - gtk.stock_add([(info[0], info[1], info[2], info[3], info[4])]) + Gtk.stock_add([info]) # Here we overwrite the key accelerators for some stock ids. # Feel free to add here any other stock id if you need it at your activity, # and send us a patch. -overwrite_stock(gtk.STOCK_SAVE_AS, 'S') -overwrite_stock(gtk.STOCK_ZOOM_IN, 'plus') -overwrite_stock(gtk.STOCK_ZOOM_OUT, 'minus') -overwrite_stock(gtk.STOCK_ZOOM_100, '0') +overwrite_stock(Gtk.STOCK_SAVE_AS, 'S') +overwrite_stock(Gtk.STOCK_ZOOM_IN, 'plus') +overwrite_stock(Gtk.STOCK_ZOOM_OUT, 'minus') +overwrite_stock(Gtk.STOCK_ZOOM_100, '0') # Key accelerator will be F11 on desktops and return on Sugar. -overwrite_stock(gtk.STOCK_FULLSCREEN, 'F11') -overwrite_stock(gtk.STOCK_ADD, 'A') -overwrite_stock(gtk.STOCK_REMOVE, 'Delete') -overwrite_stock(gtk.STOCK_SELECT_COLOR, 'L') +overwrite_stock(Gtk.STOCK_FULLSCREEN, 'F11') +overwrite_stock(Gtk.STOCK_ADD, 'A') +overwrite_stock(Gtk.STOCK_REMOVE, 'Delete') +overwrite_stock(Gtk.STOCK_SELECT_COLOR, 'L') def get_label(stock, underline): - text = gtk.stock_lookup(stock)[1] + text = Gtk.stock_lookup(stock)[1] if underline: text = text.replace('_', '') return text def get_accelerator(stock): - return gtk.stock_lookup(stock)[2:-1] + stock_id = Gtk.stock_lookup(stock) + return stock_id.modifier, stock_id.keyval diff --git a/number_box.py b/number_box.py new file mode 100644 index 0000000..a95c378 --- /dev/null +++ b/number_box.py @@ -0,0 +1,805 @@ +# -*- coding: utf-8 -*- +# This code is from GNOME Sudoku +# Adapted by: Daniel Francis + +from gi.repository import Gtk,Gdk,GObject,Pango,PangoCairo +import math +import random +import tracker_info +from gettext import gettext as _ + +# simple_debug +import logging +logger = logging.getLogger('number_box') +simple_debug = logger.debug + +ERROR_HIGHLIGHT_COLOR = (1.0, 0, 0) + +BASE_SIZE = 35 # The "normal" size of a box (in pixels) + +# And the standard font-sizes -- these should fit nicely with the +# BASE_SIZE +BASE_FONT_SIZE = Pango.SCALE * 13 +NOTE_FONT_SIZE = Pango.SCALE * 6 + +BORDER_WIDTH = 9.0 # The size of space we leave for a box +NORMAL_LINE_WIDTH = 1 # The size of the line we draw around a box + +DEBUG_COLORS = False + +def debug_set_color_rgba(cr, rgba): + COLORS = ("red","green","blue","yellow","purple","wheat","maroon","gray") + + if DEBUG_COLORS: + rgba = Gdk.RGBA() + rgba.parse(COLORS[random.randint(0,len(COLORS)-1)]) + + Gdk.cairo_set_source_rgba(cr, rgba) + +class NumberSelector (Gtk.EventBox): + + __gsignals__ = { + 'changed':(GObject.SignalFlags.RUN_LAST, None, ()), + } + + def __init__ (self, default = None, upper = 9): + self.value = default + GObject.GObject.__init__(self) + self.table = Gtk.Table() + self.add(self.table) + side = int(math.sqrt(upper)) + n = 1 + for y in range(side): + for x in range(side): + b = Gtk.Button() + l = Gtk.Label() + if n == self.value: + l.set_markup('%s'%n) + else: + l.set_markup('%s'%n) + b.add(l) + b.set_relief(Gtk.ReliefStyle.HALF) + l = b.get_children()[0] + b.set_border_width(0) + l.set_padding(0, 0) + l.get_alignment() + b.connect('clicked', self.number_clicked, n) + self.table.attach(b, x, x+1, y, y+1) + n += 1 + if self.value: + db = Gtk.Button() + l = Gtk.Label() + l.set_markup_with_mnemonic(''+_('_Clear')+'') + db.add(l) + l.show() + db.connect('clicked', self.number_clicked, 0) + self.table.attach(db, 0, side, side + 1, side + 2) + self.show_all() + + def number_clicked (self, button, n): + self.value = n + self.emit('changed') + + def get_value (self): + return self.value + + def set_value (self, n): + self.value = n + +class NumberBox (Gtk.DrawingArea): + + text = '' + top_note_text = '' + bottom_note_text = '' + read_only = False + _layout = None + _top_note_layout = None + _bottom_note_layout = None + text_color = None + highlight_color = None + shadow_color = None + custom_background_color = None + border_color = None + + __gsignals__ = { + 'value-about-to-change':(GObject.SignalFlags.RUN_LAST, None, ()), + 'notes-about-to-change':(GObject.SignalFlags.RUN_LAST, None, ()), + 'changed':(GObject.SignalFlags.RUN_LAST, None, ()), + # undo-change - A hacky way to handle the fact that we want to + # respond to undo's changes but we don't want undo to respond + # to itself... + 'undo-change':(GObject.SignalFlags.RUN_LAST, None, ()), + 'notes-changed':(GObject.SignalFlags.RUN_LAST, None, ()), + } + + base_state = Gtk.StateFlags.NORMAL + npicker = None + draw_boxes = False + + def __init__ (self, upper = 9, text = ''): + Gtk.DrawingArea.__init__(self) + self.upper = upper + self.parent_win = None + self.timer = None + self.font = self.get_style().font_desc + self.font.set_size(BASE_FONT_SIZE) + self.note_font = self.font.copy() + self.note_font.set_size(NOTE_FONT_SIZE) + self._top_note_layout = Pango.Layout(self.create_pango_context()) + self._top_note_layout.set_font_description(self.note_font) + self._bottom_note_layout = Pango.Layout(self.create_pango_context()) + self._bottom_note_layout.set_font_description(self.note_font) + self._base_stateflags = Gtk.StateFlags.NORMAL + self.top_note_list = [] + self.bottom_note_list = [] + self.tinfo = tracker_info.TrackerInfo() + self.set_property('can-focus', True) + self.set_property('events', Gdk.EventMask.ALL_EVENTS_MASK) + self.connect('button-press-event', self.button_press_cb) + self.connect('key-release-event', self.key_press_cb) + self.connect('enter-notify-event', self.pointer_enter_cb) + self.connect('leave-notify-event', self.pointer_leave_cb) + self.connect('focus-in-event', self.focus_in_cb) + self.connect('focus-out-event', self.focus_out_cb) + self.connect('motion-notify-event', self.motion_notify_cb) + self.set_text(text) + + def set_parent_win(self, new_parent): + self.parent_win = new_parent + + def set_timer(self, new_timer): + self.timer = new_timer + + def pointer_enter_cb (self, *args): + if not self.is_focus(): + self.set_state_flags(Gtk.StateFlags.PRELIGHT, False) + + def pointer_leave_cb (self, *args): + self.set_state_flags(self._base_stateflags, True) + self._toggle_box_drawing_(False) + + def focus_in_cb (self, *args): + self.set_state_flags(Gtk.StateFlags.SELECTED, True) + self._base_stateflags = Gtk.StateFlags.SELECTED + + def focus_out_cb (self, *args): + self.set_state_flags(Gtk.StateFlags.NORMAL, True) + self._base_stateflags = Gtk.StateFlags.NORMAL + self.destroy_npicker() + + def destroy_npicker (self): + if self.npicker: + self.npicker.destroy() + self.npicker = None + + def motion_notify_cb (self, *args): + if self.is_focus() and not self.read_only: + self._toggle_box_drawing_(True) + else: + self._toggle_box_drawing_(False) + + def _toggle_box_drawing_ (self, val): + if val and not self.draw_boxes: + self.draw_boxes = True + self.queue_draw() + if (not val) and self.draw_boxes: + self.draw_boxes = False + self.queue_draw() + + def button_press_cb (self, w, e): + if self.read_only: + return + if e.type == Gdk.EventType._2BUTTON_PRESS: + # ignore second click (this makes a double click in the + # middle of a cell get us a display of the numbers, rather + # than selecting a number. + return + if self.is_focus(): + x, y = e.get_coords() + alloc = self.get_allocation() + my_w = alloc.width + my_h = alloc.height + border_height = float(BORDER_WIDTH)/BASE_SIZE + + if float(y)/my_h < border_height: + self.show_note_editor(top = True) + elif float(y)/my_h > (1-border_height): + self.show_note_editor(top = False) + elif not self.npicker: + # In this case we're a normal old click... + # makes sure there is only one numer selector + self.show_number_picker() + else: + self.grab_focus() + + def key_press_cb (self, w, e): + if self.read_only: + return + if self.npicker: # kill number picker no matter what is pressed + self.destroy_npicker() + txt = Gdk.keyval_name(e.keyval) + if type(txt) == type(None): + # Make sure we don't trigger on unplugging the A/C charger etc + return + txt = txt.replace('KP_', '') + + # Add the new value if need be + if txt in [str(n) for n in range(1, self.upper+1)]: + if e.state & Gdk.ModifierType.CONTROL_MASK: + self.add_note_text(txt, top = True) + elif e.state & Gdk.ModifierType.MOD1_MASK: + self.remove_note_text(txt, top = True) + elif self.get_text() != txt or \ + (self.tracker_id != tracker_info.NO_TRACKER and + self.tinfo.current_tracker == tracker_info.NO_TRACKER): + # If there's no change, do nothing unless the player wants to + # change a tracked item while not tracking(ie commit a tracked + # change) + self.set_text_interactive(txt) + elif txt in ['0', 'Delete', 'BackSpace']: + self.set_text_interactive('') + elif txt in ['n', 'N']: + if e.state & Gdk.ModifierType.MOD1_MASK: + self.set_note_text_interactive(top_text = '') + else: + self.show_note_editor(top = True) + elif txt in ['m', 'M']: + if e.state & Gdk.ModifierType.MOD1_MASK: + self.set_note_text_interactive(bottom_text = '') + else: + self.show_note_editor(top = False) + + def add_note_text(self, txt, top = False): + if top: + note = self.top_note_text + else: + note = self.bottom_note_text + if txt not in note: + tmp = list(note) + tmp.append(txt) + tmp.sort() + note = ''.join(tmp) + if top: + self.set_note_text_interactive(top_text = note) + else: + self.set_note_text_interactive(bottom_text = note) + + def remove_note_text(self, txt, top = False): + if top: + note = self.top_note_text + else: + note = self.bottom_note_text + if txt in note: + note = note.replace(txt,'') + if top: + self.set_note_text_interactive(top_text = note) + else: + self.set_note_text_interactive(bottom_text = note) + + def note_changed_cb (self, w, top = False): + if top: + self.set_note_text_interactive(top_text = w.get_text()) + else: + self.set_note_text_interactive(bottom_text = w.get_text()) + + def note_focus_in(self, win, evt): + if (self.timer): + self.timer.resume_timing() + + def note_focus_out(self, wgt, evt): + if (self.timer): + self.timer.pause_timing() + + def show_note_editor (self, top = True): + alloc = self.get_allocation() + w = Gtk.Window() + w.set_property('skip-pager-hint', True) + w.set_property('skip-taskbar-hint', True) + w.set_decorated(False) + w.set_position(Gtk.WindowPosition.MOUSE) + w.set_size_request(alloc.width, alloc.height/2) + if self.parent_win: + w.set_transient_for(self.parent_win) + f = Gtk.Frame() + e = Gtk.Entry() + f.add(e) + if top: + e.set_text(self.top_note_text) + else: + e.set_text(self.bottom_note_text) + w.add(f) + e.connect('changed', self.note_changed_cb, top) + e.connect('focus-in-event', self.note_focus_in) + e.connect('focus-out-event', lambda e, ev, w: w.destroy(), w) + e.connect('focus-out-event', self.note_focus_out) + e.connect('activate', lambda e, w: w.destroy(), w) + _, x, y = self.get_window().get_origin() + if top: + w.move(x, y) + else: + w.move(x, y+int(alloc.height*0.6)) + w.show_all() + e.grab_focus() + + def number_changed_cb (self, num_selector): + self.destroy_npicker() + newval = num_selector.get_value() + if newval: + self.set_text_interactive(str(newval)) + else: + self.set_text_interactive('') + + def show_number_picker (self): + w = Gtk.Window(type = Gtk.WindowType.POPUP) + ns = NumberSelector(upper = self.upper, default = self.get_value()) + ns.connect('changed', self.number_changed_cb) + w.grab_focus() + w.add(ns) + _, xorigin, yorigin = self.get_window().get_origin() + x, y = (self.get_allocated_width(), self.get_allocated_height()) + popupx, popupy = w.get_size() + overlapx = popupx-x + overlapy = popupy-y + w.move(xorigin - (overlapx/2), yorigin - (overlapy/2)) + w.show() + self.npicker = w + + def set_text_interactive (self, text): + self.emit('value-about-to-change') + self.set_text(text) + self.queue_draw() + self.emit('changed') + + def set_font (self, font): + if type(font) == str: + font = Pango.FontDescription(font) + self.font = font + if self.text: + self.set_text(self.text) + self.queue_draw() + + def set_note_font (self, font): + if type(font) == str: + font = Pango.FontDescription(font) + self.note_font = font + self._top_note_layout.set_font_description(font) + self._bottom_note_layout.set_font_description(font) + self.queue_draw() + + def set_text (self, text): + self.text = text + self._layout = self.create_pango_layout(text) + self._layout.set_font_description(self.font) + + def show_note_text (self): + '''Display the notes for the current view + ''' + self.top_note_text = self.get_note_display(self.top_note_list)[1] + self._top_note_layout.set_markup(self.get_note_display(self.top_note_list)[2], -1) + self.bottom_note_text = self.get_note_display(self.bottom_note_list)[1] + self._bottom_note_layout.set_markup(self.get_note_display(self.bottom_note_list)[2], -1) + self.queue_draw() + + def set_note_text (self, top_text = None, bottom_text = None, for_hint = False): + '''Change the notes + ''' + if top_text is not None: + self.update_notelist(self.top_note_list, top_text) + if bottom_text is not None: + self.update_notelist(self.bottom_note_list, bottom_text, for_hint) + self.show_note_text() + + def set_note_text_interactive (self, *args, **kwargs): + self.emit('notes-about-to-change') + self.set_note_text(*args, **kwargs) + self.emit('notes-changed') + + def set_notelist(self, top_notelist, bottom_notelist): + '''Assign new note lists + ''' + if top_notelist: + self.top_note_list = top_notelist + if bottom_notelist: + self.bottom_note_list = bottom_notelist + + def get_note_display(self, notelist, tracker_id = None, include_untracked = True): + '''Parse a notelist for display + + Parse a notelist for the display. + notelist - This method works on one notelist at a time, so + top_note_list or bottom_note_list must be passed in. + tracker_id - can specify a particular tracker. The default is to use + tracker that is currently showing. + include_untracked - When set to True(default), the untracked notes will + be included in the output. Set it to false to exclude untracked + notes. + + The output is returned in 3 formats: + display_list - is tuple list in the format (notelist_index, tid, note) + notelist_index - the index within the notelist + tid - tracker id + note - value of the note + display_text - vanilla string representing all the values + markup_text - pango markup string that colors each note for its tracker + ''' + display_list = [] + display_text = '' + markup_text = '' + if tracker_id == None: + tracker_id = self.tinfo.showing_tracker + if include_untracked: + track_filter = [tracker_info.NO_TRACKER, tracker_id] + else: + track_filter = [tracker_id] + last_tracker = tracker_info.NO_TRACKER + for notelist_index, (tid, note) in enumerate(notelist[:]): + if tid not in track_filter: + continue + display_list.append((notelist_index, tid, note)) + display_text += note + if tid != last_tracker: + if self.tinfo.get_color_markup(last_tracker): + markup_text += '' + if self.tinfo.get_color_markup(tid): + markup_text += '' + last_tracker = tid + markup_text += note + if self.tinfo.get_color_markup(last_tracker): + markup_text += '' + return((display_list, display_text, markup_text)) + + def update_notelist(self, notelist, new_notes, for_hint = False): + '''Parse notes for a notelist + + A notelist stores individual notes in the format (tracker, note). The + sequence is also meaningful - it dictates the order in which the notes + are displayed. One notelist is maintained for the top + notes(top_note_list), and one for the bottom(bottom_note_list). This + method is responsible for maintaining those lists. + + When updating for hints(for_hint == True), the old notes are replaced + completely by the new notes and set with NO_TRACKER. + ''' + # Remove any duplicates + unique_notes = "" + for note in new_notes: + if note not in unique_notes: + unique_notes += note + # Create a list and text version of the notelist + display_list = self.get_note_display(notelist)[0] + display_text = self.get_note_display(notelist)[1] + if display_text == unique_notes: + return + # Remove deleted values from the notelist + del_offset = 0 + for display_index, (notelist_index, tid, old_note) in enumerate(display_list[:]): + if old_note not in unique_notes or for_hint: + del notelist[notelist_index + del_offset] + del display_list[display_index + del_offset] + del_offset -= 1 + else: + # Adjust the display_list index + display_list[display_index + del_offset] = (notelist_index + del_offset, tid, old_note) + # Insert any new values into the notelist + ins_offset = 0 + display_index = 0 + for new_index, new_note in enumerate(unique_notes): + add_note = False + # if the new notes are longer than the current ones - append + if len(display_list) <= display_index: + notelist_index = len(notelist) + ins_offset = 0 + add_note = True + # Otherwise - advance until we find the appropriate place to insert + else: + old_note = display_list[display_index][2] + if new_note != old_note: + notelist_index = display_list[display_index][0] + add_note = True + display_index += 1 + if add_note: + if for_hint: + use_tracker = tracker_info.NO_TRACKER + else: + use_tracker = self.tinfo.current_tracker + notelist.insert(notelist_index + ins_offset, (use_tracker, new_note)) + display_list.insert(new_index, (notelist_index + ins_offset, self.tinfo.current_tracker, new_note)) + ins_offset = ins_offset + 1 + self.trim_untracked_notes(notelist) + + def trim_untracked_notes(self, notelist): + untracked_text = self.get_note_display(notelist, tracker_info.NO_TRACKER)[1] + for tid, note in notelist[:]: + if note in untracked_text and tid != tracker_info.NO_TRACKER: + notelist.remove((tid, note)) + + def get_notes_for_undo(self): + '''Return the top and bottom notelists + ''' + return((self.top_note_list[:], self.bottom_note_list[:])) + + def set_notes_for_undo(self, notelists): + '''Reset the top and bottom notelists from an undo + ''' + self.top_note_list, self.bottom_note_list = notelists + self.show_note_text() + + @simple_debug + def do_draw(self, cr): + + w = self.get_allocated_width() + h = self.get_allocated_height() + style_ctx = self.get_style_context() + + self.draw_background_color(cr, style_ctx, w, h) + if self.is_focus(): + self.draw_highlight_box(cr, style_ctx, w, h) + if self.border_color is not None: + border_width = 3.0 + cr.set_source_rgb(*self.border_color) + cr.rectangle(border_width*0.5, border_width*0.5, w-border_width, h-border_width) + cr.set_line_width(border_width) + cr.stroke() + + if h < w: + scale = h/float(BASE_SIZE) + else: + scale = w/float(BASE_SIZE) + cr.scale(scale, scale) + + self.draw_text(cr, style_ctx) + if self.draw_boxes and self.is_focus(): + self.draw_note_area_highlight_box(cr, style_ctx) + + def draw_background_color (self, cr, style_ctx, w, h): + if self.read_only: + if self.custom_background_color: + r, g, b = self.custom_background_color + cr.set_source_rgb( + r*0.6, g*0.6, b*0.6 + ) + else: + #cr.set_source_color(self.style.base[Gtk.StateFlags.INSENSITIVE]) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_color(Gtk.StateFlags.INSENSITIVE)) + elif self.is_focus(): + #cr.set_source_color(self.style.base[Gtk.StateFlags.SELECTED]) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_color(Gtk.StateFlags.SELECTED)) + elif self.custom_background_color: + cr.set_source_rgb(*self.custom_background_color) + else: + #cr.set_source_color( + # self.style.base[self.state] + # ) + #Gdk.cairo_set_source_rgba( + cr.set_source_rgb(1.0, 1.0, 1.0) + cr.rectangle( + 0, 0, w, h, + ) + cr.fill() + + def draw_highlight_box (self, cr, style_ctx, w, h): + #cr.set_source_color( + # self.style.base[Gtk.StateFlags.SELECTED] + # ) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_background_color(Gtk.StateFlags.SELECTED)) + + border = 4 * w / BASE_SIZE + cr.rectangle( + # left-top + border*0.5, + border*0.5, + # bottom-right + w-border, + h-border, + ) + cr.set_line_width(border) + cr.stroke() + + def draw_note_area_highlight_box (self, cr, style_ctx): + # set up our paint brush... + #cr.set_source_color( + # self.style.mid[self.state] + # ) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_border_color(self.get_state_flags())) + + cr.set_line_width(NORMAL_LINE_WIDTH) + # top rectangle + cr.rectangle(NORMAL_LINE_WIDTH*0.5, + NORMAL_LINE_WIDTH*0.5, + BASE_SIZE-NORMAL_LINE_WIDTH, + BORDER_WIDTH-NORMAL_LINE_WIDTH) + cr.stroke() + # bottom rectangle + cr.rectangle(NORMAL_LINE_WIDTH*0.5, #x + BASE_SIZE - BORDER_WIDTH-(NORMAL_LINE_WIDTH*0.5), #y + BASE_SIZE-NORMAL_LINE_WIDTH, #x2 + BASE_SIZE-NORMAL_LINE_WIDTH #y2 + ) + cr.stroke() + + def draw_text (self, cr, style_ctx): + fontw, fonth = self._layout.get_pixel_size() + # Draw a shadow for tracked conflicts. This is done to + # differentiate between tracked and untracked conflicts. + if self.shadow_color: + cr.set_source_rgb(*self.shadow_color) + for xoff, yoff in [(1,1),(2,2)]: + cr.move_to((BASE_SIZE/2)-(fontw/2) + xoff, (BASE_SIZE/2) - (fonth/2) + yoff) + PangoCairo.show_layout(cr, self._layout) + if self.text_color: + cr.set_source_rgb(*self.text_color) + elif self.read_only: + #cr.set_source_color(self.style.text[Gtk.StateFlags.NORMAL]) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_color(Gtk.StateFlags.NORMAL)) + else: + #cr.set_source_color(self.style.text[self.state]) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_color(Gtk.StateFlags.NORMAL)) + + # And draw the text in the middle of the allocated space + if self._layout: + cr.move_to( + (BASE_SIZE/2)-(fontw/2), + (BASE_SIZE/2) - (fonth/2), + ) + PangoCairo.update_layout(cr, self._layout) + PangoCairo.show_layout(cr, self._layout) + + #cr.set_source_color(self.style.text[self.state]) + #Gdk.cairo_set_source_rgba( + debug_set_color_rgba( + cr, style_ctx.get_color(Gtk.StateFlags.NORMAL)) + + # And draw any note text... + if self._top_note_layout: + fontw, fonth = self._top_note_layout.get_pixel_size() + cr.move_to( + NORMAL_LINE_WIDTH, + 0, + ) + PangoCairo.update_layout(cr, self._top_note_layout) + PangoCairo.show_layout(cr, self._top_note_layout) + if self._bottom_note_layout: + fontw, fonth = self._bottom_note_layout.get_pixel_size() + cr.move_to( + NORMAL_LINE_WIDTH, + BASE_SIZE-fonth, + ) + PangoCairo.update_layout(cr, self._bottom_note_layout) + PangoCairo.show_layout(cr, self._bottom_note_layout) + + def set_text_color (self, color, shadow = None): + self.shadow_color = shadow + self.text_color = color + self.queue_draw() + + def set_background_color (self, color): + self.custom_background_color = color + self.queue_draw() + + def set_border_color (self, color): + self.border_color = color + self.queue_draw() + + def hide_notes (self): + pass + + def show_notes (self): + pass + + def set_value (self, v): + if 0 < v <= self.upper: + self.set_text(str(v)) + else: + self.set_text('') + self.queue_draw() + + def get_value (self): + try: + return int(self.text) + except: + return None + + def get_text (self): + return self.text + + def get_note_text (self): + return self.top_note_text, self.bottom_note_text + +class SudokuNumberBox (NumberBox): + + normal_color = None + tracker_id = None + error_color = (1.0, 0, 0) + highlight_color = ERROR_HIGHLIGHT_COLOR + + def set_value(self, val, tracker_id = None): + if tracker_id == None: + self.tracker_id = self.tinfo.current_tracker + else: + self.tracker_id = tracker_id + self.normal_color = self.tinfo.get_color(self.tracker_id) + self.set_text_color(self.normal_color) + super(SudokuNumberBox, self).set_value(val) + + def get_value_for_undo(self): + return(self.tracker_id, self.get_value(), self.tinfo.get_trackers_for_cell(self.x, self.y)) + + def set_value_for_undo (self, undo_val): + tracker_id, value, all_traces = undo_val + # When undo sets a value, switch to that tracker + if value: + self.tinfo.ui.select_tracker(tracker_id) + self.set_value(value, tracker_id) + self.tinfo.reset_trackers_for_cell(self.x, self.y, all_traces) + self.emit('undo_change') + + def recolor(self, tracker_id): + self.normal_color = self.tinfo.get_color(tracker_id) + self.set_text_color(self.normal_color) + + def set_error_highlight (self, val): + if val: + if (self.tracker_id != tracker_info.NO_TRACKER): + self.set_text_color(self.error_color, self.normal_color) + else: + self.set_text_color(self.error_color) + else: + self.set_text_color(self.normal_color) + + def set_read_only (self, val): + self.read_only = val + if not hasattr(self, 'bold_font'): + self.normal_font = self.font + self.bold_font = self.font.copy() + self.bold_font.set_weight(Pango.Weight.BOLD) + if self.read_only: + self.set_font(self.bold_font) + else: + self.set_font(self.normal_font) + self.queue_draw() + + def set_impossible (self, val): + if val: + if not self.get_text(): + self.set_text('X') + self.set_text_color(self.error_color) + elif self.get_text() == 'X': + self.set_text('') + self.set_text_color(self.normal_color) + self.queue_draw() + + +GObject.type_register(NumberBox) + +if __name__ == '__main__': + window = Gtk.Window() + window.connect('delete-event', Gtk.main_quit) + + def test_number_selector (): + nselector = NumberSelector(default = 3) + def tell_me (b): + print 'value->', b.get_value() + nselector.connect('changed', tell_me) + window.add(nselector) + + def test_number_box (): + window.set_size_request(100, 100) + nbox = NumberBox() + window.add(nbox) + +# test_number_selector() + test_number_box() + window.show_all() + Gtk.main() diff --git a/options.py b/options.py index 9e06358..4d3e58f 100644 --- a/options.py +++ b/options.py @@ -19,8 +19,6 @@ # MA 02110-1301, USA. from gettext import gettext as _ -import gobject -import gtk from sweetener.itembox import ItemBox from sweetener.basic_options import BasicOptions diff --git a/setup.py b/setup.py index 530f97c..c60f4d0 100755 --- a/setup.py +++ b/setup.py @@ -16,6 +16,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.activity import bundlebuilder +from sugar3.activity import bundlebuilder bundlebuilder.start() diff --git a/sugar/sweetener/basic_options.py b/sugar/sweetener/basic_options.py index 52f4ebd..0f5d579 100644 --- a/sugar/sweetener/basic_options.py +++ b/sugar/sweetener/basic_options.py @@ -19,7 +19,7 @@ from gettext import gettext as _ -from sugar.activity.widgets import ActivityToolbarButton +from sugar3.activity.widgets import ActivityToolbarButton import stock from item import Item diff --git a/sugar/sweetener/item.py b/sugar/sweetener/item.py index 291204a..8371aaf 100644 --- a/sugar/sweetener/item.py +++ b/sugar/sweetener/item.py @@ -21,21 +21,21 @@ import logging logger = logging.getLogger('option') -import gobject -import gtk -from sugar.graphics.toolbutton import ToolButton +from gi.repository import GObject +from gi.repository import Gtk +from sugar3.graphics.toolbutton import ToolButton import stock -class Item(gobject.GObject): - __gsignals__ = {'activate': (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, +class Item(GObject.GObject): + __gsignals__ = {'activate': (GObject.SignalFlags.RUN_LAST, + None, tuple())} toolitem = None - def __init__(self, stock_id=gtk.STOCK_CLEAR, important=False): - gobject.GObject.__init__(self) + def __init__(self, stock_id=Gtk.STOCK_CLEAR, important=False): + GObject.GObject.__init__(self) self._stock_id = stock_id self.accel_group = None self.important = important @@ -62,7 +62,7 @@ class Item(gobject.GObject): logger.debug(str(accelerator)) try: if accelerator[1] > 0: - self.toolitem.props.accelerator = gtk.accelerator_name( + self.toolitem.props.accelerator = Gtk.accelerator_name( accelerator[1], accelerator[0]) except: logger.error( @@ -82,6 +82,6 @@ class Item(gobject.GObject): if self.tooltip: self.toolitem.set_tooltip(self.tooltip) else: - text = gtk.stock_lookup(self._stock_id)[1] + text = Gtk.stock_lookup(self._stock_id)[1] self.toolitem.set_tooltip(text.replace('_', '')) self.setup_accelerator() diff --git a/sugar/sweetener/itembox.py b/sugar/sweetener/itembox.py index d861c52..99c45bd 100644 --- a/sugar/sweetener/itembox.py +++ b/sugar/sweetener/itembox.py @@ -17,16 +17,16 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. -import gtk -from sugar.graphics.toolbarbox import ToolbarBox -from sugar.activity.widgets import StopButton +from gi.repository import Gtk +from sugar3.graphics.toolbarbox import ToolbarBox +from sugar3.activity.widgets import StopButton class ItemBox(ToolbarBox): def __init__(self, activity): ToolbarBox.__init__(self) self._parent = activity - separator = gtk.SeparatorToolItem() + separator = Gtk.SeparatorToolItem() separator.set_draw(False) separator.set_expand(True) separator.show() diff --git a/sugar/sweetener/stock.py b/sugar/sweetener/stock.py index a6451a7..28f3956 100644 --- a/sugar/sweetener/stock.py +++ b/sugar/sweetener/stock.py @@ -19,12 +19,12 @@ import logging logger = logging.getLogger('stock') -import gtk +from gi.repository import Gtk -icon_factory = gtk.IconFactory() +icon_factory = Gtk.IconFactory() # Set the icon name for the stock items, this is used only in Sugar. -icons = {gtk.STOCK_ADD: 'list-add'} +icons = {Gtk.STOCK_ADD: 'list-add'} def register(name, label, accelerator, icon_name): @@ -32,12 +32,19 @@ def register(name, label, accelerator, icon_name): keyval = 0 mask = 0 else: - keyval, mask = gtk.accelerator_parse(accelerator) - gtk.stock_add([(name, label, mask, keyval, '')]) + keyval, mask = Gtk.accelerator_parse(accelerator) + logger.debug(keyval) + logger.debug(mask) + item = Gtk.StockItem.new() + item.stock_id = name + item.label = label + item.modifier = mask + item.keyval = keyval + Gtk.stock_add([item]) if icon_name: - icon_source = gtk.IconSource() + icon_source = Gtk.IconSource() icon_source.set_icon_name(icon_name) - icon = gtk.IconSet() + icon = Gtk.IconSet() icon.add_source(icon_source) icon_factory.add(name, icon) icon_factory.add_default() @@ -45,33 +52,35 @@ def register(name, label, accelerator, icon_name): def overwrite_stock(stock_id, new_accelerator): - info = list(gtk.stock_lookup(stock_id)) - keyval, mask = gtk.accelerator_parse(new_accelerator) - info[2] = mask - info[3] = keyval + info = Gtk.stock_lookup(stock_id) + keyval, mask = Gtk.accelerator_parse(new_accelerator) + info.modifier = mask + info.keyval = keyval logger.debug(str(info)) - gtk.stock_add([(info[0], info[1], info[2], info[3], info[4])]) + Gtk.stock_add([info]) # Here we overwrite the key accelerators for some stock ids. # Feel free to add here any other stock id if you need it at your activity, # and send us a patch. -overwrite_stock(gtk.STOCK_ZOOM_IN, 'plus') -overwrite_stock(gtk.STOCK_ZOOM_OUT, 'minus') -overwrite_stock(gtk.STOCK_ZOOM_100, '0') +overwrite_stock(Gtk.STOCK_SAVE_AS, 'S') +overwrite_stock(Gtk.STOCK_ZOOM_IN, 'plus') +overwrite_stock(Gtk.STOCK_ZOOM_OUT, 'minus') +overwrite_stock(Gtk.STOCK_ZOOM_100, '0') # Key accelerator will be F11 on desktops and return on Sugar. -overwrite_stock(gtk.STOCK_FULLSCREEN, 'Return') -overwrite_stock(gtk.STOCK_ADD, 'A') -overwrite_stock(gtk.STOCK_REMOVE, 'R') -overwrite_stock(gtk.STOCK_SELECT_COLOR, 'L') +overwrite_stock(Gtk.STOCK_FULLSCREEN, 'Return') +overwrite_stock(Gtk.STOCK_ADD, 'A') +overwrite_stock(Gtk.STOCK_REMOVE, 'R') +overwrite_stock(Gtk.STOCK_SELECT_COLOR, 'L') def get_label(stock, underline): - text = gtk.stock_lookup(stock)[1] + text = Gtk.stock_lookup(stock)[1] if underline: text = text.replace('_', '') return text def get_accelerator(stock): - return gtk.stock_lookup(stock)[2:-1] + stock_id = Gtk.stock_lookup(stock) + return stock_id.modifier, stock_id.keyval diff --git a/tracker_info.py b/tracker_info.py new file mode 100644 index 0000000..798c4ac --- /dev/null +++ b/tracker_info.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/python + +import random +import copy + +NO_TRACKER = -1 # Tracker id for untracked values + +class TrackerInfo(object): + '''Tracker state machine(singleton) + + The singleton instance of this class is used to manipulate tracker + selection and tracked values, as well as interrogate tracker colors. + + _tracks - dictionary for tracked values. The tracker id is used as the + key. A tracker is a dictionary that stored tracked values keyed by + its coordinates(x, y). _tracks[tracker_id][(x, y)] == tracked value + + current_tracker - The tracker id for the currently selected tracker + showing_tracker - The tracker id for the tracker that is currently being + viewed. The point to this member is to store off the tracker when + the player switches to "Untracked" so that the last tracker they were + working on stays in view after the switch. + ''' + __single = None + _tracks = {} + _colors = {} + current_tracker = NO_TRACKER + showing_tracker = NO_TRACKER + + def __new__(cls, *args, **kwargs): + '''Overridden to implement as a singleton + ''' + # Check to see if a __single exists already for this class + # Compare class types instead of just looking for None so + # that subclasses will create their own __single objects + if cls != type(cls.__single): + cls.__single = object.__new__(cls, *args, **kwargs) + return cls.__single + + def __init__(self): + # Only initialize the colors once + if self._colors: + return + # Use tango colors recommended here: + # http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines + for tracker_id, cols in enumerate( + [(32, 74, 135), # Sky Blue 3 + (78, 154, 6), # Chameleon 3 + (206, 92, 0), # Orange 3 + (143, 89, 2), # Chocolate 3 + (92, 53, 102), # Plum 3 + (85, 87, 83), # Aluminium 5 + (196, 160, 0) # Butter 3 + ]): + self._colors[tracker_id] = tuple([x / 255.0 for x in cols]) + + def load(self, pickle): + self.current_tracker, self.showing_tracker, self._tracks = pickle + + def save(self): + return (self.current_tracker, self.showing_tracker, self.get_trackers()) + + def create_tracker (self, tracker_id = 0): + '''Create storage for a new tracker + + tracker_id can be passed in to attempt creation of a specific id, but + if the tracker_id already exists then the passed number will be + incremented until a suitable key can be allocated. + ''' + if not tracker_id: + tracker_id = 0 + while self._tracks.has_key(tracker_id): + tracker_id += 1 + self._tracks[tracker_id] = {} + return tracker_id + + def get_tracker(self, tracker_id): + if self._tracks.has_key(tracker_id): + return self._tracks[tracker_id] + + def delete_tracker(self, tracker_id): + if self._tracks.has_key(tracker_id): + del self._tracks[tracker_id] + + def reset (self): + ''' Reset the tracker information + ''' + self._tracks = {} + self.current_tracker = NO_TRACKER + self.showing_tracker = NO_TRACKER + + def use_trackers (self, trackers): + self._tracks = trackers + + def get_trackers(self): + return copy.deepcopy(self._tracks) + + def set_tracker(self, tracker_id): + self.current_tracker = tracker_id + if tracker_id != NO_TRACKER: + self.showing_tracker = tracker_id + + def hide_tracker(self): + self.showing_tracker = NO_TRACKER + + def get_tracker_view(self): + return((self.current_tracker, self.showing_tracker)) + + def set_tracker_view(self, tview): + self.current_tracker, self.showing_tracker = tview + + def get_color (self, tracker_id): + # Untracked items don't get specially colored + if tracker_id == NO_TRACKER: + return None + # Create a random color for new trackers that are beyond the defaults + if not self._colors.has_key(tracker_id): + random_color = self._colors[0] + while random_color in self._colors.values(): + # If we have generated all possible colors, this will + # enter an infinite loop + random_color = (random.randint(0, 100)/100.0, + random.randint(0, 100)/100.0, + random.randint(0, 100)/100.0) + self._colors[tracker_id] = random_color + return self._colors[tracker_id] + + def get_color_markup(self, tracker_id): + color_tuple = self.get_color (tracker_id) + if not color_tuple: + return None + color_markup = '#' + color_markup += str(hex(int(color_tuple[0]*255))[2:]).zfill(2) + color_markup += str(hex(int(color_tuple[1]*255))[2:]).zfill(2) + color_markup += str(hex(int(color_tuple[2]*255))[2:]).zfill(2) + return color_markup.upper() + + def get_current_color(self): + return self.get_color(self.current_tracker) + + def get_showing_color(self): + return self.get_color(self.showing_tracker) + + def add_trace(self, x, y, value, tracker_id = None): + '''Add a tracked value + + By default(tracker_id set to None) this method adds a value to the + current tracker. tracker_id can be passed in to add it to a specific + tracker. + ''' + if tracker_id == None: + to_tracker = self.current_tracker + else: + to_tracker = tracker_id + # Need a tracker + if to_tracker == NO_TRACKER: + return + # Make sure the dictionary is available for the tracker. + if not self._tracks.has_key(to_tracker): + self._tracks[to_tracker] = {} + # Add it + self._tracks[to_tracker][(x, y)] = value + + def remove_trace(self, x, y, from_tracker = None): + '''Remove a tracked value + + By default(from_tracker set to None) this method removes all tracked + values for a particular cell(x, y coords). from_tracker can be passed + to remove tracked values from a particular tracker only. + ''' + if from_tracker == None: + from_tracks = self._tracks.keys() + else: + from_tracks = [from_tracker] + # Delete them + for tracker in from_tracks: + if self._tracks.has_key(tracker) and self._tracks[tracker].has_key((x, y)): + del self._tracks[tracker][(x, y)] + + def get_trackers_for_cell(self, x, y): + '''Return all trackers for a cell + + This function is used for the undo mechanism. A list in the format + (tracker, value) is returned so that it may later be reinstated with + reset_trackers_for_cell(). + ''' + ret = [] + for tracker, track in self._tracks.items(): + if track.has_key((x, y)): + ret.append((tracker, track[(x, y)])) + return ret + + def reset_trackers_for_cell(self, x, y, old_trackers): + '''Reset all trackers to a previous state for a cell + + This function is used for the undo mechanism. It reinstates the + tracked values the list created by get_trackers_for_cell(). + ''' + # Remove all the current traces + for tracker, track in self._tracks.items(): + if track.has_key((x, y)): + del self._tracks[tracker][(x, y)] + # Add the old ones back + for tracker, value in old_trackers: + self._tracks[tracker][(x, y)] = value + + -- cgit v0.9.1