diff --git a/autogen.sh b/autogen.sh
index 6d3283adaa..739e7f8d90 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -12,4 +12,4 @@ intltoolize --force
autoreconf -i
cd "$olddir"
-"$srcdir/configure" --enable-maintainer-mode "$@" --disable-python-byte-compile
+"$srcdir/configure" --enable-maintainer-mode "$@"
diff --git a/bin/sugar-backlight-setup b/bin/sugar-backlight-setup
index 4bebccfc0b..a686242b7c 100644
--- a/bin/sugar-backlight-setup
+++ b/bin/sugar-backlight-setup
@@ -19,6 +19,8 @@ import os
import sys
from functools import cmp_to_key
+import gi
+gi.require_version('GUdev', '1.0')
from gi.repository import GUdev
diff --git a/bin/sugar-control-panel b/bin/sugar-control-panel
index 00196d51fd..154bb6a383 100644
--- a/bin/sugar-control-panel
+++ b/bin/sugar-control-panel
@@ -15,6 +15,9 @@
# along with this program. If not, see .
import sys
+import gi
+
+gi.require_version('Gtk', '4.0')
from jarabe import config
diff --git a/bin/sugar-erase-bundle b/bin/sugar-erase-bundle
index 67c5a840a2..4c35c9da28 100755
--- a/bin/sugar-erase-bundle
+++ b/bin/sugar-erase-bundle
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
-import os
-import sys
+import argparse
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
@@ -8,20 +7,22 @@ DBusGMainLoop(set_as_default=True)
from jarabe.model import bundleregistry
-def cmd_help():
- print('Usage: sugar-erase-bundle bundle_id [...] \n\n\
- Erase activity bundles\n')
+def main():
+ parser = argparse.ArgumentParser(description="Erase activity bundles")
+ parser.add_argument("bundles", nargs="+", metavar="bundle_id",
+ help="Activity bundles to erase")
+ args = parser.parse_args()
+ registry = bundleregistry.get_registry()
-if len(sys.argv) < 2:
- cmd_help()
- exit(2)
+ for name in args.bundles:
+ bundle = registry.get_bundle(name)
+ if not bundle:
+ print('Cannot find %s bundle.' % name)
+ continue
+
+ registry.uninstall(bundle, delete_profile=True)
-for name in sys.argv[1:]:
- registry = bundleregistry.get_registry()
- bundle = registry.get_bundle(name)
- if not bundle:
- print('Cannot find %s bundle.' % name)
- continue
- registry.uninstall(bundle, delete_profile=True)
+if __name__ == '__main__':
+ main()
diff --git a/bin/sugar-install-bundle b/bin/sugar-install-bundle
index b1fc2cb6bf..33a836b7c1 100755
--- a/bin/sugar-install-bundle
+++ b/bin/sugar-install-bundle
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
+import argparse
import os
-import sys
from sugar4.bundle.activitybundle import ActivityBundle
@@ -8,21 +8,22 @@ from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
-def cmd_help():
- print('Usage: sugar-install-bundle activitybundle.xo [...] \n\n\
- Install activity bundles\n')
+def main():
+ parser = argparse.ArgumentParser(description="Install activity bundles")
+ parser.add_argument("bundles", nargs="+", metavar="activitybundle.xo",
+ help="Activity bundles to install")
+ args = parser.parse_args()
+ for name in args.bundles:
+ bundle = ActivityBundle(name)
+ path = bundle.install()
-if len(sys.argv) < 2:
- cmd_help()
- sys.exit(2)
+ # make sure that the BundleRegistry will add this bundle
+ # by triggering a GFileMonitor ATTRIBUTE_CHANGED event.
+ os.utime(path, None)
-for name in sys.argv[1:]:
- bundle = ActivityBundle(name)
- path = bundle.install()
+ print("%s: '%s' installed." % (parser.prog, name))
- # make sure that the BundleRegistry will add this bundle
- # by triggering a GFileMonitor ATTRIBUTE_CHANGED event.
- os.utime(path, None)
- print("%s: '%s' installed." % (sys.argv[0], name))
+if __name__ == '__main__':
+ main()
diff --git a/bin/sugar-launch b/bin/sugar-launch
index e28a0e2afc..3d009a3c6b 100644
--- a/bin/sugar-launch
+++ b/bin/sugar-launch
@@ -20,6 +20,9 @@ import sys
import dbus
import argparse
+import gi
+gi.require_version('Gtk', '4.0')
+
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
@@ -72,8 +75,8 @@ def _get_interpreter(exec_file):
if not abs_path:
return exec_file
- f = open(abs_path)
- line = f.readline(100)
+ with open(abs_path) as f:
+ line = f.readline(100)
if line.startswith('#!'):
cmds = line[2:].strip().split(' ')
cmds.append(abs_path)
@@ -92,5 +95,10 @@ if args.debug:
cmd_args.extend(_get_interpreter(act_args.pop(0)))
cmd_args.extend(act_args)
+env = activityfactory.get_environment(activity)
+if 'WAYLAND_DISPLAY' in os.environ:
+ env['WAYLAND_DISPLAY'] = os.environ['WAYLAND_DISPLAY']
+ env['GDK_BACKEND'] = 'wayland'
+
os.chdir(str(activity.get_path()))
-os.execvpe(cmd_args[0], cmd_args, activityfactory.get_environment(activity))
+os.execvpe(cmd_args[0], cmd_args, env)
diff --git a/bin/sugar.in b/bin/sugar.in
index b7e8d60e66..0b951183b7 100644
--- a/bin/sugar.in
+++ b/bin/sugar.in
@@ -32,12 +32,8 @@ if test -z "$SUGAR_ACTIVITIES_HIDDEN"; then
export SUGAR_ACTIVITIES_HIDDEN="$sugardatadir/activities.hidden"
fi
-export GTK2_RC_FILES="@prefix@/share/sugar/data/sugar-$SUGAR_SCALING.gtkrc"
-
-if ! test -f "$GTK2_RC_FILES"; then
- echo "sugar: ERROR: Gtk theme for scaling $SUGAR_SCALING not available in path $GTK2_RC_FILES"
- exit 1
-fi
+export GDK_BACKEND="wayland"
+export GTK_THEME="Sugar"
# Set default language
export LANG="${LANG:-en_US.utf8}"
diff --git a/configure.ac b/configure.ac
index 1c52f0481b..f51bb2a948 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
AC_INIT([Sugar],[0.121],[],[sugar])
-AC_PREREQ([2.59])
+AC_PREREQ([2.72])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR([configure.ac])
@@ -25,7 +25,7 @@ AC_SUBST([GETTEXT_PACKAGE])
AM_GLIB_GNU_GETTEXT
AC_ARG_ENABLE(update-mimedb,
- AC_HELP_STRING([--disable-update-mimedb],
+ AS_HELP_STRING([--disable-update-mimedb],
[disable the update-mime-database after install [default=no]]),,
enable_update_mimedb=yes)
AM_CONDITIONAL(ENABLE_UPDATE_MIMEDB, test x$enable_update_mimedb = xyes)
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am
index 6f0b172867..cad84fd2e8 100644
--- a/data/icons/Makefile.am
+++ b/data/icons/Makefile.am
@@ -11,6 +11,8 @@ sugar_DATA = \
module-network.svg \
module-power.svg \
module-updater.svg \
- module-webaccount.svg
+ module-webaccount.svg \
+ list-add.svg \
+ list-remove.svg
EXTRA_DIST = $(sugar_DATA)
diff --git a/extensions/cpsection/aboutcomputer/model.py b/extensions/cpsection/aboutcomputer/model.py
index 98630ad05d..5065b35770 100644
--- a/extensions/cpsection/aboutcomputer/model.py
+++ b/extensions/cpsection/aboutcomputer/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@
from jarabe import config
from jarabe.model.network import get_wireless_interfaces
-
_OFW_TREE = '/ofw'
_PROC_TREE = '/proc/device-tree'
_DMI_DIRECTORY = '/sys/class/dmi/id'
@@ -234,7 +233,7 @@ def _read_file(path):
def get_license():
license_file = os.path.join(config.data_path, 'GPLv3')
- lang = os.environ['LANG']
+ lang = os.environ.get('LANG', 'en_US.UTF-8')
if lang.endswith('UTF-8'):
lang = lang[:-6]
diff --git a/extensions/cpsection/aboutcomputer/view.py b/extensions/cpsection/aboutcomputer/view.py
index 1e3f605a76..67b34a2100 100644
--- a/extensions/cpsection/aboutcomputer/view.py
+++ b/extensions/cpsection/aboutcomputer/view.py
@@ -1,4 +1,4 @@
-# coding=utf-8
+# coding=utf-8
# Copyright (C) 2008, OLPC
# Copyright (C) 2009 Simon Schampijer
#
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
-from gi.repository import Gtk, Pango
-from gi.repository import Gdk
+from gi.repository import Gtk
from sugar4.graphics import style
@@ -33,19 +31,22 @@ def __init__(self, model, alerts=None):
self._model = model
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- self._group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ self._group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
scrollwindow = Gtk.ScrolledWindow()
scrollwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- self.pack_start(scrollwindow, True, True, 0)
- scrollwindow.show()
+ scrollwindow.set_vexpand(True)
+ scrollwindow.set_hexpand(True)
+ self.append(scrollwindow)
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- scrollwindow.add_with_viewport(self._vbox)
- self._vbox.show()
+ scrollwindow.set_child(self._vbox)
self._setup_identity()
@@ -55,78 +56,70 @@ def __init__(self, model, alerts=None):
def create_information_box(self, label_text, value_text):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
label = Gtk.Label(label=label_text)
- label.set_alignment(1, 0)
- label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box.pack_start(label, False, True, 0)
+ label.set_halign(Gtk.Align.END)
+ style.apply_css_to_widget(label, "* { color: %s; }" % style.COLOR_SELECTION_GREY.get_html())
+ box.append(label)
self._group.add_widget(label)
- label.show()
value = Gtk.Label(label=value_text)
- value.set_alignment(0, 0)
- box.pack_start(value, False, True, 0)
- value.show()
- box.show()
+ value.set_halign(Gtk.Align.START)
+ value.set_selectable(True)
+ box.append(value)
return box
def _setup_identity(self):
separator_identity = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_identity, False, True, 0)
- separator_identity.show()
+ self._vbox.append(separator_identity)
label_identity = Gtk.Label(label=_('Identity'))
- label_identity.set_alignment(0, 0)
- self._vbox.pack_start(label_identity, False, True, 0)
- label_identity.show()
+ label_identity.set_halign(Gtk.Align.START)
+ self._vbox.append(label_identity)
vbox_identity = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox_identity.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_margin_top(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_margin_start(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_margin_end(style.DEFAULT_SPACING * 2)
vbox_identity.set_spacing(style.DEFAULT_SPACING)
hardware_model = self._model.get_hardware_model()
if hardware_model:
- vbox_identity.pack_start(
- self.create_information_box(_('Model:'), hardware_model),
- False, True, 0)
+ vbox_identity.append(
+ self.create_information_box(_('Model:'), hardware_model))
- vbox_identity.pack_start(
+ vbox_identity.append(
self.create_information_box(_('Serial Number:'),
- self._model.get_serial_number()),
- False, True, 0)
+ self._model.get_serial_number()))
- self._vbox.pack_start(vbox_identity, False, True, 0)
- vbox_identity.show()
+ self._vbox.append(vbox_identity)
def _setup_software(self):
separator_software = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_software, False, True, 0)
- separator_software.show()
+ self._vbox.append(separator_software)
label_software = Gtk.Label(label=_('Software'))
- label_software.set_alignment(0, 0)
- self._vbox.pack_start(label_software, False, True, 0)
- label_software.show()
+ label_software.set_halign(Gtk.Align.START)
+ self._vbox.append(label_software)
box_software = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_software.set_border_width(style.DEFAULT_SPACING * 2)
+ box_software.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_software.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ box_software.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_software.set_margin_end(style.DEFAULT_SPACING * 2)
box_software.set_spacing(style.DEFAULT_SPACING)
- box_software.pack_start(
+ box_software.append(
self.create_information_box(_('Build:'),
- self._model.get_build_number()),
- False, True, 0)
+ self._model.get_build_number()))
- box_software.pack_start(
+ box_software.append(
self.create_information_box(_('Sugar:'),
- config.version),
- False, True, 0)
+ config.version))
- box_software.pack_start(
+ box_software.append(
self.create_information_box(_('Firmware:'),
- self._model.get_firmware_number()),
- False, True, 0)
+ self._model.get_firmware_number()))
- box_software.pack_start(
+ box_software.append(
self.create_information_box(_('Wireless Firmware:'),
- self._model.get_wireless_firmware()),
- False, True, 0)
+ self._model.get_wireless_firmware()))
days_from_last_update = self._model.days_from_last_update()
if days_from_last_update >= 0:
@@ -135,35 +128,33 @@ def _setup_software(self):
else:
msg = _('Today')
- box_software.pack_start(
- self.create_information_box(_('Last system update:'), msg),
- False, True, 0)
+ box_software.append(
+ self.create_information_box(_('Last system update:'), msg))
- self._vbox.pack_start(box_software, False, True, 0)
- box_software.show()
+ self._vbox.append(box_software)
def _setup_copyright(self):
separator_copyright = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_copyright, False, True, 0)
- separator_copyright.show()
+ self._vbox.append(separator_copyright)
label_copyright = Gtk.Label(label=_('Copyright and License'))
- label_copyright.set_alignment(0, 0)
- self._vbox.pack_start(label_copyright, False, True, 0)
- label_copyright.show()
+ label_copyright.set_halign(Gtk.Align.START)
+ self._vbox.append(label_copyright)
vbox_copyright = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox_copyright.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_margin_top(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_margin_start(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_margin_end(style.DEFAULT_SPACING * 2)
vbox_copyright.set_spacing(style.DEFAULT_SPACING)
copyright_text = '© 2006-2020 One Laptop per Child Association Inc,' \
' Sugar Labs Inc, Red Hat Inc, Collabora Ltd and' \
' Contributors.'
label_copyright = Gtk.Label(label=copyright_text)
- label_copyright.set_alignment(0, 0)
- label_copyright.set_size_request(Gdk.Screen.width() / 2, -1)
- label_copyright.set_line_wrap(True)
- label_copyright.show()
- vbox_copyright.pack_start(label_copyright, False, True, 0)
+ label_copyright.set_halign(Gtk.Align.START)
+ label_copyright.set_wrap(True)
+ label_copyright.set_max_width_chars(80)
+ vbox_copyright.append(label_copyright)
# TRANS: The word "Sugar" should not be translated.
info_text = _('Sugar is the graphical user interface that you are'
@@ -172,33 +163,28 @@ def _setup_copyright(self):
' change it and/or distribute copies of it under'
' certain conditions described therein.')
label_info = Gtk.Label(label=info_text)
- label_info.set_alignment(0, 0)
+ label_info.set_halign(Gtk.Align.START)
label_info.set_max_width_chars(80)
- label_info.set_line_wrap(True)
- label_info.set_size_request(Gdk.Screen.width() / 2, -1)
- label_info.show()
- vbox_copyright.pack_start(label_info, False, True, 0)
+ label_info.set_wrap(True)
+ vbox_copyright.append(label_info)
expander = Gtk.Expander(label=_('Full license:'))
expander.connect('notify::expanded', self.license_expander_cb)
- expander.show()
- vbox_copyright.pack_start(expander, False, True, 0)
+ vbox_copyright.append(expander)
# display secondary licenses, if any
for license_text in self._model.get_secondary_licenses():
label_license = Gtk.Label(label=license_text)
- label_license.set_alignment(0, 0)
- label_license.set_line_wrap(True)
- label_license.set_size_request(Gdk.Screen.width() / 2, -1)
- label_license.show()
+ label_license.set_halign(Gtk.Align.START)
+ label_license.set_wrap(True)
+ label_license.set_max_width_chars(80)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- vbox_copyright.pack_start(separator, False, True, 0)
- separator.show()
- vbox_copyright.pack_start(label_license, False, True, 0)
+ vbox_copyright.append(separator)
+ vbox_copyright.append(label_license)
- self._vbox.pack_start(vbox_copyright, True, True, 0)
- vbox_copyright.show()
+ vbox_copyright.set_vexpand(True)
+ self._vbox.append(vbox_copyright)
def license_expander_cb(self, expander, param_spec):
# load/destroy the license viewer on-demand, to avoid storing the
@@ -206,10 +192,11 @@ def license_expander_cb(self, expander, param_spec):
if expander.get_expanded():
view_license = Gtk.TextView()
view_license.set_editable(False)
+ view_license.set_wrap_mode(Gtk.WrapMode.WORD)
view_license.get_buffer().set_text(self._model.get_license())
- fd = Pango.FontDescription('Monospace')
- view_license.modify_font(fd)
- view_license.show()
- expander.add(view_license)
+ view_license.add_css_class('monospace')
+ expander.set_child(view_license)
else:
- expander.get_child().destroy()
+ child = expander.get_child()
+ if child:
+ expander.set_child(None)
diff --git a/extensions/cpsection/aboutme/__init__.py b/extensions/cpsection/aboutme/__init__.py
index 124bfd7066..e61a806775 100644
--- a/extensions/cpsection/aboutme/__init__.py
+++ b/extensions/cpsection/aboutme/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,11 +13,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
from sugar4 import profile
+
CLASS = 'AboutMe'
ICON = 'module-about_me'
TITLE = _('About Me')
diff --git a/extensions/cpsection/aboutme/model.py b/extensions/cpsection/aboutme/model.py
index 87dea08c4d..445f179ec7 100644
--- a/extensions/cpsection/aboutme/model.py
+++ b/extensions/cpsection/aboutme/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2010-14, Sugar Labs
# Copyright (C) 2010-14, Walter Bender
#
diff --git a/extensions/cpsection/aboutme/view.py b/extensions/cpsection/aboutme/view.py
index eb658acfdd..08a1b36f38 100644
--- a/extensions/cpsection/aboutme/view.py
+++ b/extensions/cpsection/aboutme/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
# Copyright (C) 2010-14, Sugar Labs
# Copyright (C) 2010-14, Walter Bender
# Copyright (C) 2014, Ignacio Rodriguez
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
@@ -31,7 +30,6 @@
from jarabe.intro.agepicker import AgePicker, save_age, load_age
from jarabe.intro.genderpicker import GenderPicker, save_gender, load_gender
-
_STROKE_COLOR = 0
_FILL_COLOR = 1
_NOCOLOR = XoColor('#010101,#FFFFFF')
@@ -110,7 +108,6 @@ def _previous_index(current_index):
def _get_current_index(color):
return colors.index([color.stroke, color.fill])
-
_PREVIOUS_FILL_COLOR = 0
_NEXT_FILL_COLOR = 1
_CURRENT_COLOR = 2
@@ -169,7 +166,10 @@ def __init__(self, model, alerts):
self._gender = ''
self._age = None
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
self._color = XoColor(self._model.get_color())
@@ -204,27 +204,21 @@ def _setup_nick(self):
self._nick_entry = Gtk.Entry()
self._nick_entry.set_width_chars(25)
grid.attach(self._nick_entry, 0, 0, 1, 1)
- self._nick_entry.show()
alert_grid = Gtk.Grid()
self._nick_alert = InlineAlert()
alert_grid.attach(self._nick_alert, 0, 0, 1, 1)
if 'nick' in self.restart_alerts:
self._nick_alert.props.msg = self.restart_msg
- self._nick_alert.show()
-
- center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_in_panel.add(grid)
- grid.show()
+ self._nick_alert.set_visible(True)
+ else:
+ self._nick_alert.set_visible(False)
- center_alert = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_alert.add(alert_grid)
- alert_grid.show()
+ grid.set_halign(Gtk.Align.CENTER)
+ alert_grid.set_halign(Gtk.Align.CENTER)
- self.pack_start(center_in_panel, False, False, 0)
- self.pack_start(center_alert, False, False, 0)
- center_in_panel.show()
- center_alert.show()
+ self.append(grid)
+ self.append(alert_grid)
def _setup_color(self):
grid = Gtk.Grid()
@@ -242,46 +236,38 @@ def _setup_color(self):
}
label_color = Gtk.Label(label=_('Click to change your color:'))
- label_color.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
+ style.apply_css_to_widget(label_color, "* { color: %s; }" % style.COLOR_SELECTION_GREY.get_html())
grid.attach(label_color, 0, 0, 3, 1)
- label_color.show()
current = 0
for picker_index in sorted(self._pickers.keys()):
if picker_index == _CURRENT_COLOR:
- left_separator = Gtk.SeparatorToolItem()
+ left_separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)
grid.attach(left_separator, current, 1, 1, 1)
- left_separator.show()
current += 1
picker = self._pickers[picker_index]
- picker.show()
grid.attach(picker, current, 1, 1, 1)
current += 1
if picker_index == _CURRENT_COLOR:
- right_separator = Gtk.SeparatorToolItem()
- right_separator.show()
+ right_separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)
grid.attach(right_separator, current, 1, 1, 1)
current += 1
label_color_error = Gtk.Label()
grid.attach(label_color_error, 0, 2, 3, 1)
- label_color_error.show()
self._color_alert = InlineAlert()
grid.attach(self._color_alert, 0, 3, 3, 1)
if 'color' in self.restart_alerts:
self._color_alert.props.msg = self.restart_msg
- self._color_alert.show()
-
- center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_in_panel.add(grid)
- grid.show()
+ self._color_alert.set_visible(True)
+ else:
+ self._color_alert.set_visible(False)
- self.pack_start(center_in_panel, False, False, 0)
- center_in_panel.show()
+ grid.set_halign(Gtk.Align.CENTER)
+ self.append(grid)
def _setup_gender(self):
self._saved_gender = load_gender()
@@ -293,20 +279,13 @@ def _setup_gender(self):
grid.set_column_spacing(style.DEFAULT_SPACING)
label_gender = Gtk.Label(label=_('Select gender:'))
- label_gender.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
+ style.apply_css_to_widget(label_gender, "* { color: %s; }" % style.COLOR_SELECTION_GREY.get_html())
grid.attach(label_gender, 0, 0, 1, 1)
- label_gender.show()
grid.attach(self._gender_pickers, 0, 1, 1, 1)
- self._gender_pickers.show()
- center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_in_panel.add(grid)
- grid.show()
-
- self.pack_start(center_in_panel, False, False, 0)
- center_in_panel.show()
+ grid.set_halign(Gtk.Align.CENTER)
+ self.append(grid)
def _setup_age(self):
self._saved_age = load_age()
@@ -316,37 +295,26 @@ def _setup_age(self):
grid.set_column_spacing(style.DEFAULT_SPACING)
self._age_pickers = AgePicker(self._saved_gender)
- center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_in_panel.add(self._age_pickers)
- self._age_pickers.show()
-
+
label = self._age_pickers.get_label()
-
label_age = Gtk.Label(label=_(label))
- label_age.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- left_align = Gtk.Alignment.new(0, 0, 0, 0)
- left_align.add(label_age)
- label_age.show()
- grid.attach(left_align, 0, 0, 1, 1)
- left_align.show()
-
- grid.attach(center_in_panel, 0, 1, 1, 1)
- center_in_panel.show()
-
- center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
- center_in_panel.add(grid)
- grid.show()
- self.pack_start(center_in_panel, False, False, 0)
- center_in_panel.show()
+ style.apply_css_to_widget(label_age, "* { color: %s; }" % style.COLOR_SELECTION_GREY.get_html())
+ label_age.set_halign(Gtk.Align.START)
+ grid.attach(label_age, 0, 0, 1, 1)
+
+ self._age_pickers.set_halign(Gtk.Align.CENTER)
+ grid.attach(self._age_pickers, 0, 1, 1, 1)
+
+ grid.set_halign(Gtk.Align.CENTER)
+ self.append(grid)
def setup(self):
pass
def undo(self):
self._model.undo()
- self._nick_alert.hide()
- self._color_alert.hide()
+ self._nick_alert.set_visible(False)
+ self._color_alert.set_visible(False)
# Undo gender or age changes
save_gender(self._saved_gender)
@@ -374,29 +342,31 @@ def __nick_timeout_cb(self, widget):
self._nick_sid = 0
if widget.get_text() == self._model.get_nick():
- self.restart_alerts.remove('nick')
+ if 'nick' in self.restart_alerts:
+ self.restart_alerts.remove('nick')
if not self.restart_alerts:
self.needs_restart = False
- self._nick_alert.hide()
+ self._nick_alert.set_visible(False)
return False
try:
self._model.set_nick(widget.get_text())
except ValueError as detail:
self._nick_alert.props.msg = detail
self._nick_valid = False
- self._nick_alert.show()
+ self._nick_alert.set_visible(True)
else:
self._nick_valid = True
if widget.get_text() == self._original_nick:
- self.restart_alerts.remove('nick')
+ if 'nick' in self.restart_alerts:
+ self.restart_alerts.remove('nick')
if not self.restart_alerts:
self.needs_restart = False
- self._nick_alert.hide()
+ self._nick_alert.set_visible(False)
else:
self._nick_alert.props.msg = self.restart_msg
self.needs_restart = True
self.restart_alerts.add('nick')
- self._nick_alert.show()
+ self._nick_alert.set_visible(True)
self._validate()
return False
@@ -408,7 +378,7 @@ def __color_changed_cb(self, colorpicker, color):
self.restart_alerts.add('color')
self._validate()
- self._color_alert.show()
+ self._color_alert.set_visible(True)
self._update_pickers(color)
return False
diff --git a/extensions/cpsection/background/__init__.py b/extensions/cpsection/background/__init__.py
index 60eaefe798..3b4743fc3f 100644
--- a/extensions/cpsection/background/__init__.py
+++ b/extensions/cpsection/background/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'Background'
ICON = 'image-x-generic'
TITLE = _('Background')
diff --git a/extensions/cpsection/background/model.py b/extensions/cpsection/background/model.py
index 6fa7584938..e1e6ca4dd2 100644
--- a/extensions/cpsection/background/model.py
+++ b/extensions/cpsection/background/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Agustin Zubiaga
+# Copyright (C) 2012 Agustin Zubiaga
# Copyright (C) 2013 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
@@ -42,7 +42,6 @@ def get_background_image_path():
settings = Gio.Settings.new(BACKGROUND_DIR)
return settings.get_string(BACKGROUND_IMAGE_PATH_KEY)
-
PREVIOUS_BACKGROUND_IMAGE_PATH = get_background_image_path()
@@ -67,7 +66,6 @@ def get_background_alpha_level():
alpha = 1.0
return alpha
-
PREVIOUS_BACKGROUND_ALPHA_LEVEL = get_background_alpha_level()
diff --git a/extensions/cpsection/background/view.py b/extensions/cpsection/background/view.py
index 6ef78afbfe..f6553f70d4 100644
--- a/extensions/cpsection/background/view.py
+++ b/extensions/cpsection/background/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Agustin Zubiaga
+# Copyright (C) 2012 Agustin Zubiaga
# Copyright (C) 2013 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
import os
from gi.repository import Gtk
@@ -41,40 +40,24 @@ def __init__(self, model, alerts=None):
self.connect('realize', self.__realize_cb)
self.connect('unrealize', self.__unrealize_cb)
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
label_box = Gtk.Box()
label_bg = Gtk.Label(label=_('Select a background:'))
- label_bg.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- label_bg.show()
- label_box.pack_start(label_bg, False, True, 0)
- label_box.show()
- self.pack_start(label_box, False, True, 1)
-
- clear_button = Gtk.Button()
- clear_button.set_label(_('Clear background'))
- clear_button.connect('clicked', self._clear_clicked_cb)
- clear_button.show()
- self.pack_end(clear_button, False, True, 0)
+ style.apply_css_to_widget(label_bg, "* { color: %s; }" % style.COLOR_SELECTION_GREY.get_html())
+ label_box.append(label_bg)
+ self.append(label_box)
scrolled_window = Gtk.ScrolledWindow()
- scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+ scrolled_window.set_has_frame(True)
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
- self.pack_start(scrolled_window, True, True, 0)
- scrolled_window.show()
-
- self._store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
-
- self._icon_view = Gtk.IconView.new_with_model(self._store)
- self._icon_view.set_selection_mode(Gtk.SelectionMode.SINGLE)
- self._icon_view.connect('selection-changed', self._background_selected)
- self._icon_view.set_pixbuf_column(0)
- self._icon_view.grab_focus()
- scrolled_window.add(self._icon_view)
- self._icon_view.show()
+ scrolled_window.set_vexpand(True)
+ self.append(scrolled_window)
alpha = self._model.get_background_alpha_level()
@@ -94,19 +77,31 @@ def __init__(self, model, alerts=None):
button.set_icon_name(icon_name)
button.value = value
button.props.active = value == alpha
- button.show()
- alpha_box.pack_start(button, False, True, 0)
+ alpha_box.append(button)
alpha_buttons.append(button)
for button in alpha_buttons:
button.connect('toggled', self._set_alpha_cb)
- alpha_alignment = Gtk.Alignment()
- alpha_alignment.set(0.5, 0, 0, 0)
- alpha_alignment.add(alpha_box)
- alpha_box.show()
- self.pack_start(alpha_alignment, False, False, 0)
- alpha_alignment.show()
+ alpha_box.set_halign(Gtk.Align.CENTER)
+ self.append(alpha_box)
+
+ clear_button = Gtk.Button()
+ clear_button.set_label(_('Clear background'))
+ clear_button.connect('clicked', self._clear_clicked_cb)
+ clear_button.set_valign(Gtk.Align.END)
+ self.append(clear_button)
+
+ self._flow_box = Gtk.FlowBox()
+ self._flow_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
+ self._flow_box.set_valign(Gtk.Align.START)
+ self._flow_box.set_max_children_per_line(6)
+ self._flow_box.set_column_spacing(6)
+ self._flow_box.set_row_spacing(6)
+ self._flow_box.set_homogeneous(True)
+ self._flow_box.connect('selected-children-changed', self._background_selected)
+ self._flow_box.grab_focus()
+ scrolled_window.set_child(self._flow_box)
self._paths_list = []
@@ -132,7 +127,14 @@ def _append_to_store(self, file_paths):
except GLib.GError:
pass
else:
- self._store.append([pixbuf, file_path])
+ texture = Gdk.Texture.new_for_pixbuf(pixbuf)
+ picture = Gtk.Picture.new_for_paintable(texture)
+ picture.set_size_request(style.XLARGE_ICON_SIZE, style.XLARGE_ICON_SIZE)
+
+ child = Gtk.FlowBoxChild()
+ child.set_child(picture)
+ child._image_path = file_path
+ self._flow_box.append(child)
self._paths_list.append(file_path)
self._append_to_store_sid = GLib.idle_add(self._append_to_store,
@@ -140,10 +142,7 @@ def _append_to_store(self, file_paths):
else:
self._select_background()
self._images_loaded = True
- window = self.get_window()
- if window is not None:
- window.set_cursor(None)
- Gdk.flush()
+ self.set_cursor(None)
self._append_to_store_sid = None
def _cancel_append_to_store(self):
@@ -153,51 +152,39 @@ def _cancel_append_to_store(self):
def __realize_cb(self, widget):
if self._images_loaded:
- self.get_window().set_cursor(None)
+ self.set_cursor(None)
else:
- self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
- Gdk.flush()
+ self.set_cursor(Gdk.Cursor.new_from_name('wait', None))
def __unrealize_cb(self, widget):
- self.get_window().set_cursor(None)
- Gdk.flush()
+ self.set_cursor(None)
def _set_alpha_cb(self, widget):
if widget.get_active():
self._model.set_background_alpha_level(widget.value)
- def _get_selected_path(self, widget):
- try:
- iter_ = self._store.get_iter(widget.get_selected_items()[0])
- image_path = self._store.get(iter_, 1)[0]
-
- return image_path, iter_
- except:
- return None
-
- def _background_selected(self, widget):
- selected = self._get_selected_path(widget)
-
- if selected is None:
+ def _background_selected(self, flow_box):
+ selected = flow_box.get_selected_children()
+ if not selected:
return
-
- image_path, _iter = selected
- iter_ = self._store.get_iter(widget.get_selected_items()[0])
- image_path = self._store.get(iter_, 1)[0]
+
+ child = selected[0]
+ image_path = child._image_path
self._model.set_background_image_path(image_path)
def _select_background(self):
background = self._model.get_background_image_path()
if background in self._paths_list:
- self._icon_view.select_path(
- Gtk.TreePath.new_from_string(
- '%s' % self._paths_list.index(background)))
+ index = self._paths_list.index(background)
+ child = self._flow_box.get_child_at_index(index)
+ if child:
+ self._flow_box.select_child(child)
def _clear_clicked_cb(self, widget, event=None):
self._model.set_background_image_path(None)
def setup(self):
- self.show_all()
+ pass
def apply(self):
self._cancel_append_to_store()
diff --git a/extensions/cpsection/backup/backends/backend_tools.py b/extensions/cpsection/backup/backends/backend_tools.py
index 409e081f99..3ef4d50d94 100644
--- a/extensions/cpsection/backup/backends/backend_tools.py
+++ b/extensions/cpsection/backup/backends/backend_tools.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Martin Abente Lahaye - tch@sugarlabs.org
+# Copyright (C) 2013, Martin Abente Lahaye - tch@sugarlabs.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/extensions/cpsection/backup/backends/volume.py b/extensions/cpsection/backup/backends/volume.py
index 20d1d744a8..5706606c27 100644
--- a/extensions/cpsection/backup/backends/volume.py
+++ b/extensions/cpsection/backup/backends/volume.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Martin Abente Lahaye - tch@sugarlabs.org
+# Copyright (C) 2013, Martin Abente Lahaye - tch@sugarlabs.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/extensions/cpsection/backup/backupmanager.py b/extensions/cpsection/backup/backupmanager.py
index 2f0285bb20..9fec142d00 100644
--- a/extensions/cpsection/backup/backupmanager.py
+++ b/extensions/cpsection/backup/backupmanager.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 Gonzalo Odiard
+# Copyright (c) 2013 Gonzalo Odiard
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -26,7 +26,6 @@
BACKENDS_MODULE = 'cpsection.backup.backends'
-
OPERATION_BACKUP = 'backup'
OPERATION_RESTORE = 'restore'
diff --git a/extensions/cpsection/backup/view.py b/extensions/cpsection/backup/view.py
index c4fbb59a78..602b700d16 100644
--- a/extensions/cpsection/backup/view.py
+++ b/extensions/cpsection/backup/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, SugarLabs
+# Copyright (C) 2013, SugarLabs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,7 +25,6 @@
from sugar4.graphics import style
from sugar4.graphics.xocolor import XoColor
-
from jarabe.controlpanel.sectionview import SectionView
from .backupmanager import BackupManager
@@ -45,56 +44,58 @@ def __init__(self, model, alerts=None):
# add the initial panel
self.set_canvas(SelectBackupRestorePanel(self))
self.grab_focus()
- self.show_all()
self.manager = BackupManager()
def set_canvas(self, canvas):
- if len(self.get_children()) > 0:
- self.remove(self.get_children()[0])
+ child = self.get_first_child()
+ if child:
+ self.remove(child)
if canvas:
- self.add(canvas)
+ self.append(canvas)
def undo(self):
- if self.get_children()[0].__class__ == OperationPanel:
- operation_panel = self.get_children()[0]
- if operation_panel._operator is not None:
- operation_panel._operator.cancel()
+ child = self.get_first_child()
+ if child and child.__class__ == OperationPanel:
+ if child._operator is not None:
+ child._operator.cancel()
-class _BackupButton(Gtk.EventBox):
+class _BackupButton(Gtk.Box):
__gproperties__ = {
'icon-name': (str, None, None, None, GObject.ParamFlags.READWRITE),
'pixel-size': (object, None, None, GObject.ParamFlags.READWRITE),
'title': (str, None, None, None, GObject.ParamFlags.READWRITE),
}
+ __gsignals__ = {
+ 'clicked': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ }
def __init__(self, **kwargs):
self._icon_name = None
self._pixel_size = style.GRID_CELL_SIZE
- self._xo_color = None
self._title = 'No Title'
- Gtk.EventBox.__init__(self, **kwargs)
+ Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, **kwargs)
+
+ self.add_css_class('sectionicon-transparent')
- self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._icon = Icon(icon_name=self._icon_name,
pixel_size=self._pixel_size,
xo_color=XoColor('#000000,#000000'))
- self._vbox.pack_start(self._icon, expand=False, fill=False, padding=0)
+ self.append(self._icon)
self._label = Gtk.Label(label=self._title)
- self._vbox.pack_start(self._label, expand=False, fill=False, padding=0)
+ self.append(self._label)
- self._vbox.set_spacing(style.DEFAULT_SPACING)
- self.set_visible_window(False)
- self.set_app_paintable(True)
- self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
+ self.set_spacing(style.DEFAULT_SPACING)
- self.add(self._vbox)
- self._vbox.show()
- self._label.show()
- self._icon.show()
+ self.click_gesture = Gtk.GestureClick.new()
+ self.click_gesture.connect("pressed", self.__pressed_cb)
+ self.add_controller(self.click_gesture)
+
+ def __pressed_cb(self, gesture, n_press, x, y):
+ self.emit("clicked")
def get_icon(self):
return self._icon
@@ -131,38 +132,38 @@ def __init__(self, view):
icon_name='backup-backup',
title=_('Save the contents of your Journal'),
pixel_size=style.GRID_CELL_SIZE)
- self.backup_btn.connect('button-press-event',
+ self.backup_btn.connect('clicked',
self.__backup_button_press_cb)
- hbox.pack_start(self.backup_btn, False, False, style.DEFAULT_SPACING)
+ self.backup_btn.set_margin_start(style.DEFAULT_SPACING)
+ hbox.append(self.backup_btn)
self.restore_btn = _BackupButton(
icon_name='backup-restore',
title=_('Restore the contents of your Journal'),
pixel_size=style.GRID_CELL_SIZE)
- self.restore_btn.connect('button-press-event',
+ self.restore_btn.connect('clicked',
self.__restore_button_press_cb)
- hbox.pack_start(self.restore_btn, False, False, style.DEFAULT_SPACING)
+ self.restore_btn.set_margin_start(style.DEFAULT_SPACING)
+ hbox.append(self.restore_btn)
hbox.set_valign(Gtk.Align.CENTER)
hbox.set_halign(Gtk.Align.CENTER)
- self.add(hbox)
- self.show_all()
+ self.append(hbox)
- def __backup_button_press_cb(self, button, event):
+ def __backup_button_press_cb(self, button):
operation_panel = OperationPanel(OPERATION_BACKUP, self._view)
self._view.set_canvas(operation_panel)
- def __restore_button_press_cb(self, button, event):
+ def __restore_button_press_cb(self, button):
operation_panel = OperationPanel(OPERATION_RESTORE, self._view)
self._view.set_canvas(operation_panel)
-class OperationPanel(Gtk.Grid):
+class OperationPanel(Gtk.Box):
def __init__(self, operation, view):
- Gtk.Grid.__init__(self)
- self.set_orientation(Gtk.Orientation.VERTICAL)
- self.set_row_spacing(style.DEFAULT_SPACING)
+ Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ self.set_spacing(style.DEFAULT_SPACING)
self.set_valign(Gtk.Align.CENTER)
self.set_halign(Gtk.Align.CENTER)
@@ -172,49 +173,38 @@ def __init__(self, operation, view):
_icon = Icon(icon_name='backup-%s' % operation,
pixel_size=style.XLARGE_ICON_SIZE)
- self.add(_icon)
- _icon.show()
+ self.append(_icon)
self._message_label = Gtk.Label()
- self._message_label.set_line_wrap(True)
+ self._message_label.set_wrap(True)
self._message_label.set_width_chars(40)
- self._message_label.set_single_line_mode(False)
- align = Gtk.Alignment.new(0.5, 0.5, 0, 0)
- align.set_padding(0, 0, style.GRID_CELL_SIZE * 2,
- style.GRID_CELL_SIZE * 2)
- align.show()
- align.add(self._message_label)
- self.add(align)
- self._message_label.show()
+ self._message_label.set_halign(Gtk.Align.CENTER)
+ self._message_label.set_valign(Gtk.Align.CENTER)
+ self._message_label.set_margin_bottom(style.GRID_CELL_SIZE * 2)
+ self._message_label.set_margin_end(style.GRID_CELL_SIZE * 2)
+ self.append(self._message_label)
self._options_combo = Gtk.ComboBox()
cell = Gtk.CellRendererText()
self._options_combo.pack_start(cell, True)
self._options_combo.add_attribute(cell, 'text', 0)
- self.add(self._options_combo)
+ self.append(self._options_combo)
self._progress_bar = Gtk.ProgressBar()
- self.add(self._progress_bar)
- self._progress_bar.set_size_request(
- Gdk.Screen.width() - style.GRID_CELL_SIZE * 6, -1)
+ self._progress_bar.set_hexpand(True)
+ self.append(self._progress_bar)
self._confirm_restore_chkbtn = Gtk.CheckButton()
- align = Gtk.Alignment()
- align.set_padding(0, 0, style.GRID_CELL_SIZE * 2,
- style.GRID_CELL_SIZE * 2)
- align.show()
- align.add(self._confirm_restore_chkbtn)
- self.add(align)
-
- btn_box = Gtk.ButtonBox()
- btn_box.show()
- self._continue_btn = Gtk.Button(_('Continue'))
- btn_box.add(self._continue_btn)
- self.add(btn_box)
+ self._confirm_restore_chkbtn.set_margin_bottom(style.GRID_CELL_SIZE * 2)
+ self._confirm_restore_chkbtn.set_margin_end(style.GRID_CELL_SIZE * 2)
+ self.append(self._confirm_restore_chkbtn)
+
+ btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self._continue_btn = Gtk.Button(label=_('Continue'))
+ btn_box.append(self._continue_btn)
+ self.append(btn_box)
self._continue_btn_handler_id = 0
- self.show()
-
# check if there are activities running
# and request close them if any.
if self._view.manager.need_stop_activities():
@@ -254,7 +244,7 @@ def _ask_options(self, message, options, continue_cb):
options_store.append([option['description'], option['value']])
self._options_combo.set_model(options_store)
- self._options_combo.show()
+ self._options_combo.set_visible(True)
if self._continue_btn_handler_id != 0:
self._continue_btn.disconnect(self._continue_btn_handler_id)
@@ -262,13 +252,13 @@ def _ask_options(self, message, options, continue_cb):
self._continue_btn_handler_id = self._continue_btn.connect(
'clicked', continue_cb)
self._continue_btn.set_label(_('Continue'))
- self._continue_btn.show()
+ self._continue_btn.set_visible(True)
def _show_error_message(self, message):
self._message_label.set_text(message)
- self._options_combo.hide()
+ self._options_combo.set_visible(False)
self._continue_btn.set_label(_('Retry'))
- self._continue_btn.show()
+ self._continue_btn.set_visible(True)
if self._continue_btn_handler_id != 0:
self._continue_btn.disconnect(self._continue_btn_handler_id)
self._continue_btn_handler_id = self._continue_btn.connect(
@@ -321,10 +311,10 @@ def _request_restore_confirmation(self):
'In order to do this, my Journal will first be emptied of all '
'its content; then the restored content will be added.'))
self._confirm_restore_chkbtn.set_label(_('Accept'))
- self._confirm_restore_chkbtn.show()
- self._options_combo.hide()
+ self._confirm_restore_chkbtn.set_visible(True)
+ self._options_combo.set_visible(False)
self._continue_btn.set_label(_('Confirm'))
- self._continue_btn.show()
+ self._continue_btn.set_visible(True)
if self._continue_btn_handler_id != 0:
self._continue_btn.disconnect(self._continue_btn_handler_id)
self._continue_btn_handler_id = self._continue_btn.connect(
@@ -333,8 +323,8 @@ def _request_restore_confirmation(self):
def __confirm_restore_cb(self, button):
if self._confirm_restore_chkbtn.get_active():
self._view.props.is_cancellable = False
- self._confirm_restore_chkbtn.hide()
- self._continue_btn.hide()
+ self._confirm_restore_chkbtn.set_visible(False)
+ self._continue_btn.set_visible(False)
self._message_label.set_text('')
self._internal_start_operation()
self._view.needs_restart = True
@@ -362,10 +352,10 @@ def _assign_parameter_backend(self, button):
def __operation_started_cb(self, backend):
self._message_label.set_text(_('Operation started'))
- self._options_combo.hide()
- self._continue_btn.hide()
+ self._options_combo.set_visible(False)
+ self._continue_btn.set_visible(False)
self._progress_bar.set_fraction(0.0)
- self._progress_bar.show()
+ self._progress_bar.set_visible(True)
def __operation_progress_cb(self, backend, fraction):
self._progress_bar.set_fraction(fraction)
diff --git a/extensions/cpsection/datetime/__init__.py b/extensions/cpsection/datetime/__init__.py
index 527078b320..e469292a88 100644
--- a/extensions/cpsection/datetime/__init__.py
+++ b/extensions/cpsection/datetime/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'TimeZone'
ICON = 'module-date_and_time'
TITLE = _('Date & Time')
diff --git a/extensions/cpsection/datetime/model.py b/extensions/cpsection/datetime/model.py
index 94a0576f28..a8c4914e62 100644
--- a/extensions/cpsection/datetime/model.py
+++ b/extensions/cpsection/datetime/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 One Laptop Per Child
+# Copyright (C) 2007, 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -60,10 +60,16 @@ def read_all_timezones(fn=_zone_tab):
timezones.append(tz)
return timezones
+_initial_timezone = None
+
def get_timezone():
+ global _initial_timezone
settings = Gio.Settings.new('org.sugarlabs.date')
- return settings.get_string('timezone')
+ tz = settings.get_string('timezone')
+ if _initial_timezone is None:
+ _initial_timezone = tz
+ return tz
def print_timezone():
@@ -100,5 +106,10 @@ def set_timezone(timezone):
return 1
+def undo():
+ global _initial_timezone
+ if _initial_timezone is not None:
+ set_timezone(_initial_timezone)
+
# inilialize the docstrings for the timezone
_initialize()
diff --git a/extensions/cpsection/datetime/view.py b/extensions/cpsection/datetime/view.py
index a122d16371..0b3eeaa217 100644
--- a/extensions/cpsection/datetime/view.py
+++ b/extensions/cpsection/datetime/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
@@ -26,6 +25,11 @@
from jarabe.controlpanel.inlinealert import InlineAlert
+CLASS = 'TimeZone'
+ICON = 'module-date_and_time'
+TITLE = _('Date & Time')
+
+
class TimeZone(SectionView):
def __init__(self, model, alerts):
SectionView.__init__(self)
@@ -35,54 +39,57 @@ def __init__(self, model, alerts):
self._zone_sid = 0
self._cursor_change_handler = None
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- self.connect('realize', self.__realize_cb)
+ self.connect('map', self.__map_cb)
self._entry = iconentry.IconEntry()
self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
'entry-search')
self._entry.add_clear_button()
- self.pack_start(self._entry, False, False, 0)
- self._entry.show()
+ self.append(self._entry)
self._scrolled_window = Gtk.ScrolledWindow()
self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
- self._scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
+ self._scrolled_window.set_has_frame(True)
self._store = Gtk.ListStore(GObject.TYPE_STRING)
zones = model.read_all_timezones()
for zone in zones:
self._store.append([zone])
- self._treeview = Gtk.TreeView(self._store)
+ self._treeview = Gtk.TreeView(model=self._store)
self._treeview.set_search_entry(self._entry)
self._treeview.set_search_equal_func(self._search, None)
self._treeview.set_search_column(0)
- self._scrolled_window.add(self._treeview)
- self._treeview.show()
+ self._scrolled_window.set_child(self._treeview)
- self._timezone_column = Gtk.TreeViewColumn(_('Timezone'))
+ self._timezone_column = Gtk.TreeViewColumn(title=_('Timezone'))
self._cell = Gtk.CellRendererText()
self._timezone_column.pack_start(self._cell, True)
self._timezone_column.add_attribute(self._cell, 'text', 0)
self._timezone_column.set_sort_column_id(0)
self._treeview.append_column(self._timezone_column)
- self.pack_start(self._scrolled_window, True, True, 0)
- self._scrolled_window.show()
+ self._scrolled_window.set_vexpand(True)
+ self.append(self._scrolled_window)
self._zone_alert_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
- self.pack_start(self._zone_alert_box, False, False, 0)
+ self.append(self._zone_alert_box)
self._zone_alert = InlineAlert()
- self._zone_alert_box.pack_start(self._zone_alert, True, True, 0)
+ self._zone_alert.set_hexpand(True)
+ self._zone_alert_box.append(self._zone_alert)
if 'zone' in self.restart_alerts:
self._zone_alert.props.msg = self.restart_msg
- self._zone_alert.show()
- self._zone_alert_box.show()
+ self._zone_alert.set_visible(True)
+ else:
+ self._zone_alert.set_visible(False)
self.setup()
@@ -90,9 +97,10 @@ def setup(self):
zone = self._model.get_timezone()
for row in self._store:
if zone == row[0]:
- self._treeview.set_cursor(row.path, self._timezone_column,
+ path = self._store.get_path(row.iter)
+ self._treeview.set_cursor(path, self._timezone_column,
False)
- self._treeview.scroll_to_cell(row.path, self._timezone_column,
+ self._treeview.scroll_to_cell(path, self._timezone_column,
True, 0.5, 0.5)
break
@@ -103,9 +111,9 @@ def setup(self):
def undo(self):
self._treeview.disconnect(self._cursor_change_handler)
self._model.undo()
- self._zone_alert.hide()
+ self._zone_alert.set_visible(False)
- def __realize_cb(self, widget):
+ def __map_cb(self, widget):
self._entry.grab_focus()
def _search(self, model, column, key, iterator, data=None):
@@ -118,20 +126,21 @@ def __zone_changed_cd(self, treeview, data=None):
list_, row = treeview.get_selection().get_selected()
if not row:
return False
- if self._model.get_timezone() == self._store.get_value(row, 0):
+ timezone_val = self._store.get_value(row, 0)
+ if self._model.get_timezone() == timezone_val:
return False
if self._zone_sid:
GLib.source_remove(self._zone_sid)
self._zone_sid = GLib.timeout_add(self._APPLY_TIMEOUT,
- self.__zone_timeout_cb, row)
+ self.__zone_timeout_cb, timezone_val)
return True
- def __zone_timeout_cb(self, row):
+ def __zone_timeout_cb(self, timezone_val):
self._zone_sid = 0
- self._model.set_timezone(self._store.get_value(row, 0))
+ self._model.set_timezone(timezone_val)
self.restart_alerts.append('zone')
self.needs_restart = True
self._zone_alert.props.msg = self.restart_msg
- self._zone_alert.show()
+ self._zone_alert.set_visible(True)
return False
diff --git a/extensions/cpsection/frame/__init__.py b/extensions/cpsection/frame/__init__.py
index 94cc56ce16..c41c7411fd 100644
--- a/extensions/cpsection/frame/__init__.py
+++ b/extensions/cpsection/frame/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'Frame'
ICON = 'module-frame'
TITLE = _('Frame')
diff --git a/extensions/cpsection/frame/model.py b/extensions/cpsection/frame/model.py
index 6955942204..39ad5ea6bd 100644
--- a/extensions/cpsection/frame/model.py
+++ b/extensions/cpsection/frame/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/extensions/cpsection/frame/view.py b/extensions/cpsection/frame/view.py
index 95fbfad73d..a13b8a1887 100644
--- a/extensions/cpsection/frame/view.py
+++ b/extensions/cpsection/frame/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import GLib
from gettext import gettext as _
@@ -22,7 +21,6 @@
from jarabe.controlpanel.sectionview import SectionView
-
_never = _('never')
_instantaneous = _('instantaneous')
_seconds_label = _('%s seconds')
@@ -42,99 +40,98 @@ def __init__(self, model, alerts):
self._trigger_size_change_handler = None
self.restart_alerts = alerts
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- self._group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ self._group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self.pack_start(separator, False, True, 0)
+ self.append(separator)
label = Gtk.Label(label=_('Activation Delay'))
- label.set_alignment(0, 0)
- self.pack_start(label, False, True, 0)
+ label.set_halign(Gtk.Align.START)
+ self.append(label)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box.set_border_width(style.DEFAULT_SPACING * 2)
+ box.set_margin_top(style.DEFAULT_SPACING * 2)
+ box.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ box.set_margin_start(style.DEFAULT_SPACING * 2)
+ box.set_margin_end(style.DEFAULT_SPACING * 2)
box.set_spacing(style.DEFAULT_SPACING)
- box.pack_start(self._setup_corner(), False, True, 0)
- box.pack_start(self._setup_edge(), False, True, 0)
+ box.append(self._setup_corner())
+ box.append(self._setup_edge())
- self.pack_start(box, False, True, 0)
+ self.append(box)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self.pack_start(separator, False, True, 0)
+ self.append(separator)
label = Gtk.Label(label=_('Activation Area'))
- label.set_alignment(0, 0)
- self.pack_start(label, False, True, 0)
+ label.set_halign(Gtk.Align.START)
+ self.append(label)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box.set_border_width(style.DEFAULT_SPACING * 2)
+ box.set_margin_top(style.DEFAULT_SPACING * 2)
+ box.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ box.set_margin_start(style.DEFAULT_SPACING * 2)
+ box.set_margin_end(style.DEFAULT_SPACING * 2)
box.set_spacing(style.DEFAULT_SPACING)
- box.pack_start(self._setup_trigger(), False, True, 0)
+ box.append(self._setup_trigger())
- self.pack_start(box, False, True, 0)
- self.show_all()
+ self.append(box)
self.setup()
def _setup_corner(self):
box_delay = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
label_delay = Gtk.Label(label=_('Corner'))
- label_delay.set_alignment(1, 0.75)
- label_delay.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_delay.pack_start(label_delay, False, True, 0)
+ label_delay.set_halign(Gtk.Align.END)
+ box_delay.append(label_delay)
self._group.add_widget(label_delay)
adj = Gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
- step_incr=100, page_incr=100, page_size=0)
- self._corner_delay_slider = Gtk.HScale()
- self._corner_delay_slider.set_adjustment(adj)
+ step_increment=100, page_increment=100, page_size=0)
+ self._corner_delay_slider = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adj)
self._corner_delay_slider.set_digits(0)
- self._corner_delay_slider.connect('format-value',
- self.__corner_delay_format_cb)
- box_delay.pack_start(self._corner_delay_slider, True, True, 0)
+ self._corner_delay_slider.set_hexpand(True)
+ self._corner_delay_slider.set_format_value_func(self.__corner_delay_format_cb)
+ box_delay.append(self._corner_delay_slider)
return box_delay
def _setup_edge(self):
box_delay = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
label_delay = Gtk.Label(label=_('Edge'))
- label_delay.set_alignment(1, 0.75)
- label_delay.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_delay.pack_start(label_delay, False, True, 0)
+ label_delay.set_halign(Gtk.Align.END)
+ box_delay.append(label_delay)
self._group.add_widget(label_delay)
adj = Gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
- step_incr=100, page_incr=100, page_size=0)
- self._edge_delay_slider = Gtk.HScale()
- self._edge_delay_slider.set_adjustment(adj)
+ step_increment=100, page_increment=100, page_size=0)
+ self._edge_delay_slider = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adj)
self._edge_delay_slider.set_digits(0)
- self._edge_delay_slider.connect('format-value',
- self.__edge_delay_format_cb)
- box_delay.pack_start(self._edge_delay_slider, True, True, 0)
+ self._edge_delay_slider.set_hexpand(True)
+ self._edge_delay_slider.set_format_value_func(self.__edge_delay_format_cb)
+ box_delay.append(self._edge_delay_slider)
return box_delay
def _setup_trigger(self):
box_trigger = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
label_trigger = Gtk.Label(label=_('Size'))
- label_trigger.set_alignment(1, 0.75)
- label_trigger.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_trigger.pack_start(label_trigger, False, True, 0)
+ label_trigger.set_halign(Gtk.Align.END)
+ box_trigger.append(label_trigger)
self._group.add_widget(label_trigger)
adj = Gtk.Adjustment(value=1, lower=1, upper=style.GRID_CELL_SIZE,
- step_incr=1, page_incr=1, page_size=0)
- self._trigger_size_slider = Gtk.HScale()
- self._trigger_size_slider.set_adjustment(adj)
+ step_increment=1, page_increment=1, page_size=0)
+ self._trigger_size_slider = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adj)
self._trigger_size_slider.set_digits(0)
- self._trigger_size_slider.connect('format-value',
- self.__trigger_size_format_cb)
- box_trigger.pack_start(self._trigger_size_slider, True, True, 0)
+ self._trigger_size_slider.set_hexpand(True)
+ self._trigger_size_slider.set_format_value_func(self.__trigger_size_format_cb)
+ box_trigger.append(self._trigger_size_slider)
return box_trigger
def setup(self):
@@ -208,7 +205,7 @@ def __trigger_size_changed_cb(self, scale, data=None):
def __trigger_size_timeout_cb(self, scale):
self._trigger_size_sid = 0
if scale.get_value() == self._model.get_trigger_size():
- return
+ return False
self._model.set_trigger_size(scale.get_value())
return False
@@ -240,4 +237,4 @@ def apply(self):
self.__edge_delay_timeout_cb(self._edge_delay_slider)
if self._trigger_size_sid:
GLib.source_remove(self._trigger_size_sid)
- self.__trigger_size_timeout_cb(self._trigger_size_sid)
+ self.__trigger_size_timeout_cb(self._trigger_size_slider)
diff --git a/extensions/cpsection/keyboard/__init__.py b/extensions/cpsection/keyboard/__init__.py
index a4555dc1ff..ac920b8b58 100644
--- a/extensions/cpsection/keyboard/__init__.py
+++ b/extensions/cpsection/keyboard/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009, OLPC
+# Copyright (C) 2009, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'Keyboard'
ICON = 'module-keyboard'
TITLE = _('Keyboard')
diff --git a/extensions/cpsection/keyboard/model.py b/extensions/cpsection/keyboard/model.py
index 07f48a5bc9..8049bfb4ed 100644
--- a/extensions/cpsection/keyboard/model.py
+++ b/extensions/cpsection/keyboard/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Sugar Labs
+# Copyright (C) 2013 Sugar Labs
# Copyright (C) 2009 OLPC
# Author: Sayamindu Dasgupta
#
@@ -16,78 +16,175 @@
# along with this program. If not, see .
#
+import os
+import logging
+import xml.etree.ElementTree as ET
+import gi
+gi.require_version('Xkl', '1.0')
from gi.repository import Xkl
from gi.repository import Gio
+from jarabe.config import data_path
+
_GROUP_NAME = 'grp' # The XKB name for group switch options
_KEYBOARD_DIR = 'org.sugarlabs.peripherals.keyboard'
_LAYOUTS_KEY = 'layouts'
_OPTIONS_KEY = 'options'
_MODEL_KEY = 'model'
+_EVDEV_XML_PATH = '/usr/share/X11/xkb/rules/evdev.xml'
class KeyboardManager(object):
- def __init__(self, display):
- self._engine = Xkl.Engine.get_instance(display)
- self._configregistry = Xkl.ConfigRegistry.get_instance(self._engine)
- self._configregistry.load(False)
- self._configrec = Xkl.ConfigRec()
- self._configrec.get_from_server(self._engine)
+ def __init__(self, display):
self._settings = Gio.Settings.new(_KEYBOARD_DIR)
+ self._engine = None
+ self._configrec = None
- def _populate_one(self, config_registry, item, store):
- store.append([item.get_description(), item.get_name()])
+ if display is not None:
+ try:
+ self._engine = Xkl.Engine.get_instance(display)
+ self._configrec = Xkl.ConfigRec()
+ self._configrec.get_from_server(self._engine)
+ except Exception:
+ logging.exception('Could not initialize Xkl engine')
+ self._engine = None
+ self._configrec = None
- def _populate_two(self, config_registry, item, subitem, store):
- layout = item.get_name()
- if subitem:
- description = '%s, %s' % (subitem.get_description(),
- item.get_description())
- variant = subitem.get_name()
- else:
- description = 'Default layout, %s' % item.get_description()
- variant = ''
- store.append([description, ('%s(%s)' % (layout, variant))])
+ self._models = []
+ self._languages = {}
+ self._layouts_for_lang = {}
+ self._options = []
+ self._load_xkb_registry()
+
+ def _load_xkb_registry(self):
+ lang_names = {}
+ iso_file = os.path.join(data_path, 'ISO-639-2_utf-8.txt')
+ if os.path.exists(iso_file):
+ try:
+ with open(iso_file, 'r', encoding='utf-8') as f:
+ for line in f:
+ parts = line.strip().split('|')
+ if len(parts) >= 4:
+ eng_name = parts[3].split(';')[0].strip()
+ if parts[0]:
+ lang_names[parts[0]] = eng_name
+ if parts[1]:
+ lang_names[parts[1]] = eng_name
+ except Exception:
+ logging.exception('Failed to parse ISO-639-2 file')
+
+ if not os.path.exists(_EVDEV_XML_PATH):
+ return
+
+ try:
+ tree = ET.parse(_EVDEV_XML_PATH)
+ root = tree.getroot()
+
+ for elem in root.iter():
+ if elem.tag.startswith('{'):
+ elem.tag = elem.tag.split('}', 1)[1]
+
+ model_list = root.find('modelList')
+ if model_list is not None:
+ for model_node in model_list.findall('model'):
+ config = model_node.find('configItem')
+ if config is not None:
+ name_node = config.find('name')
+ desc_node = config.find('description')
+ if name_node is not None and desc_node is not None:
+ self._models.append([desc_node.text, name_node.text])
+ self._models.sort()
+
+ layout_list = root.find('layoutList')
+ if layout_list is not None:
+ for layout_node in layout_list.findall('layout'):
+ config = layout_node.find('configItem')
+ if config is not None:
+ layout_name = config.find('name')
+ layout_desc = config.find('description')
+ if layout_name is not None and layout_desc is not None:
+ l_name = layout_name.text
+ l_desc = layout_desc.text
+
+ lang_list = config.find('languageList')
+ lang_codes = []
+ if lang_list is not None:
+ for lang_node in lang_list.findall('iso639Id'):
+ lang_codes.append(lang_node.text)
+
+ variants = [[l_desc, '%s()' % l_name]]
+
+ variant_list = layout_node.find('variantList')
+ if variant_list is not None:
+ for var_node in variant_list.findall('variant'):
+ var_config = var_node.find('configItem')
+ if var_config is not None:
+ v_name = var_config.find('name')
+ v_desc = var_config.find('description')
+ if v_name is not None and v_desc is not None:
+ variants.append([
+ '%s, %s' % (v_desc.text, l_desc),
+ '%s(%s)' % (l_name, v_name.text)
+ ])
+
+ for lang in lang_codes:
+ desc = lang_names.get(lang, lang.upper())
+ self._languages[lang] = desc
+ if lang not in self._layouts_for_lang:
+ self._layouts_for_lang[lang] = []
+ self._layouts_for_lang[lang].extend(variants)
+
+ for lang in self._layouts_for_lang:
+ self._layouts_for_lang[lang].sort()
+
+ option_list = root.find('optionList')
+ if option_list is not None:
+ for group in option_list.findall('group'):
+ config = group.find('configItem')
+ if config is not None:
+ name_node = config.find('name')
+ if name_node is not None and name_node.text == _GROUP_NAME:
+ for opt in group.findall('option'):
+ opt_config = opt.find('configItem')
+ if opt_config is not None:
+ opt_name = opt_config.find('name')
+ opt_desc = opt_config.find('description')
+ if opt_name is not None and opt_desc is not None:
+ self._options.append([opt_desc.text, opt_name.text])
+ self._options.sort()
+
+ except Exception:
+ logging.exception('Failed to parse evdev.xml')
def get_models(self):
"""Return list of supported keyboard models"""
- models = []
- self._configregistry.foreach_model(self._populate_one, models)
- models.sort()
- return models
+ return self._models
def get_languages(self):
"""Return list of supported keyboard languages"""
- languages = []
- self._configregistry.foreach_language(self._populate_one, languages)
+ languages = [[desc, code] for code, desc in self._languages.items()]
languages.sort()
return languages
def get_layouts_for_language(self, language):
"""Return list of supported keyboard layouts for a given language"""
- layouts = []
- self._configregistry.foreach_language_variant(language,
- self._populate_two,
- layouts)
- layouts.sort()
- return layouts
+ return self._layouts_for_lang.get(language, [])
def get_options_group(self):
"""Return list of supported options for switching keyboard group"""
- options = []
- self._configregistry.foreach_option(_GROUP_NAME, self._populate_one,
- options)
- options.sort()
- return options
+ return self._options
def get_current_model(self):
"""Return the enabled keyboard model"""
model = self._settings.get_string(_MODEL_KEY)
if not model:
- model = self._configrec.model
- self.set_model(model)
+ if self._configrec is not None:
+ model = self._configrec.model
+ self.set_model(model)
+ else:
+ model = 'pc105'
return model
def get_current_layouts(self):
@@ -96,28 +193,33 @@ def get_current_layouts(self):
if layouts:
return layouts
- layouts = self._configrec.layouts
- variants = self._configrec.variants
+ if self._configrec is not None:
+ layouts = self._configrec.layouts
+ variants = self._configrec.variants
- layout_list = []
- i = 0
- for layout in layouts:
- if len(variants) <= i or variants[i] == '':
- layout_list.append('%s(%s)' % (layout, ''))
- else:
- layout_list.append('%s(%s)' % (layout, variants[i]))
- i += 1
+ layout_list = []
+ i = 0
+ for layout in layouts:
+ if len(variants) <= i or variants[i] == '':
+ layout_list.append('%s(%s)' % (layout, ''))
+ else:
+ layout_list.append('%s(%s)' % (layout, variants[i]))
+ i += 1
- self.set_layouts(layout_list)
- return layout_list
+ self.set_layouts(layout_list)
+ return layout_list
+ return ['us']
def get_current_option_group(self):
"""Return the enabled option for switching keyboard group"""
options = self._settings.get_strv(_OPTIONS_KEY)
if not options:
- options = self._configrec.options
- self.set_option_group(options)
+ if self._configrec is not None:
+ options = self._configrec.options
+ self.set_option_group(options)
+ else:
+ options = []
for option in options:
if option.startswith(_GROUP_NAME):
@@ -127,15 +229,18 @@ def get_current_option_group(self):
def get_max_layouts(self):
"""Return the maximum number of layouts supported simultaneously"""
- return self._engine.get_max_num_groups()
+ if self._engine is not None:
+ return self._engine.get_max_num_groups()
+ return 4
def set_model(self, model):
"""Sets the supplied keyboard model"""
if model is None or not model:
return
self._settings.set_string(_MODEL_KEY, model)
- self._configrec.set_model(model)
- self._configrec.activate(self._engine)
+ if self._configrec is not None:
+ self._configrec.set_model(model)
+ self._configrec.activate(self._engine)
def set_option_group(self, option_group):
"""Sets the supplied option for switching keyboard group"""
@@ -148,8 +253,9 @@ def set_option_group(self, option_group):
options = [option_group]
self._settings.set_strv(_OPTIONS_KEY, options)
- self._configrec.set_options(options)
- self._configrec.activate(self._engine)
+ if self._configrec is not None:
+ self._configrec.set_options(options)
+ self._configrec.activate(self._engine)
def set_layouts(self, layouts):
"""Sets the supplied keyboard layouts (with variants)"""
@@ -158,11 +264,12 @@ def set_layouts(self, layouts):
self._settings.set_strv(_LAYOUTS_KEY, layouts)
- layouts_list = []
- variants_list = []
- for layout in layouts:
- layouts_list.append(layout.split('(')[0])
- variants_list.append(layout.split('(')[1][:-1])
- self._configrec.set_layouts(layouts_list)
- self._configrec.set_variants(variants_list)
- self._configrec.activate(self._engine)
+ if self._configrec is not None:
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+ self._configrec.set_layouts(layouts_list)
+ self._configrec.set_variants(variants_list)
+ self._configrec.activate(self._engine)
diff --git a/extensions/cpsection/keyboard/view.py b/extensions/cpsection/keyboard/view.py
index c0675e419d..1318f9046a 100644
--- a/extensions/cpsection/keyboard/view.py
+++ b/extensions/cpsection/keyboard/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Sugar Labs
+# Copyright (C) 2013, Sugar Labs
# Copyright (C) 2009, OLPC
# Author: Sayamindu Dasgupta
#
@@ -15,12 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
import os
import locale
from gi.repository import Gtk
-from gi.repository import GdkX11
+from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Pango
@@ -34,7 +33,8 @@
from jarabe.controlpanel.sectionview import SectionView
from jarabe.config import data_path
-CLASS = 'Language'
+
+CLASS = 'Keyboard'
ICON = 'module-keyboard'
TITLE = _('Keyboard')
@@ -76,18 +76,22 @@ class LayoutCombo(Gtk.Box):
def __init__(self, keyboard_manager, n):
GObject.GObject.__init__(self)
+ self.set_orientation(Gtk.Orientation.HORIZONTAL)
self._keyboard_manager = keyboard_manager
self._index = n
- self.set_border_width(style.DEFAULT_SPACING)
+ self.set_margin_top(style.DEFAULT_SPACING)
+ self.set_margin_bottom(style.DEFAULT_SPACING)
+ self.set_margin_start(style.DEFAULT_SPACING)
+ self.set_margin_end(style.DEFAULT_SPACING)
self.set_spacing(style.DEFAULT_SPACING)
- label = Gtk.Label(label=' %s ' % str(n + 1))
+ label = Gtk.Label(label=' %s ' %
+ (style.COLOR_SELECTION_GREY.get_svg(), str(n + 1)))
label.set_use_markup(True)
- label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- label.set_alignment(0.5, 0.5)
- self.pack_start(label, False, True, 0)
+ label.set_halign(Gtk.Align.CENTER)
+ label.set_valign(Gtk.Align.CENTER)
+ self.append(label)
self._klang_store = Gtk.ListStore(GObject.TYPE_STRING,
GObject.TYPE_STRING)
@@ -102,7 +106,8 @@ def __init__(self, keyboard_manager, n):
cell.props.ellipsize_set = True
self._klang_combo.pack_start(cell, True)
self._klang_combo.add_attribute(cell, 'text', 1)
- self.pack_start(self._klang_combo, expand=True, fill=True, padding=0)
+ self._klang_combo.set_hexpand(True)
+ self.append(self._klang_combo)
self._kvariant_store = None
self._kvariant_combo = Gtk.ComboBox(model=None)
@@ -114,8 +119,8 @@ def __init__(self, keyboard_manager, n):
cell.props.ellipsize_set = True
self._kvariant_combo.pack_start(cell, True)
self._kvariant_combo.add_attribute(cell, 'text', 1)
- self.pack_start(self._kvariant_combo, expand=True, fill=True,
- padding=0)
+ self._kvariant_combo.set_hexpand(True)
+ self.append(self._kvariant_combo)
self._klang_combo.set_active(self._index)
@@ -156,7 +161,11 @@ def _look_for_lang_and_layout(self, lang, layout):
def get_layout(self):
"""Gets the selected layout (with variant)"""
it = self._kvariant_combo.get_active_iter()
+ if it is None:
+ return ''
model = self._kvariant_combo.get_model()
+ if model is None:
+ return ''
return model.get(it, 0)[0]
def _set_kvariant_store(self, lang):
@@ -170,13 +179,21 @@ def _set_kvariant_store(self, lang):
def _klang_combo_changed_cb(self, combobox):
it = combobox.get_active_iter()
+ if it is None:
+ return
model = combobox.get_model()
+ if model is None:
+ return
lang = model.get(it, 0)[0]
self._set_kvariant_store(lang)
def _kvariant_combo_changed_cb(self, combobox):
it = combobox.get_active_iter()
+ if it is None:
+ return
model = combobox.get_model()
+ if model is None:
+ return
layout = model.get(it, 0)[0]
self.emit('selection-changed', layout, self._index)
@@ -196,26 +213,50 @@ def __init__(self, model, alerts):
self._group_switch_option = None
self._selected_group_switch_option = None
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- self._layout_table = Gtk.Table(rows=4, columns=2, homogeneous=False)
+ self._layout_table = Gtk.Grid()
+ self._layout_table.set_column_spacing(style.DEFAULT_SPACING)
+ self._layout_table.set_row_spacing(style.DEFAULT_SPACING)
_build_ISO_639_dictionary()
- self._keyboard_manager = model.KeyboardManager(
- GdkX11.x11_get_default_xdisplay())
+ display_manager = Gdk.DisplayManager.get()
+ xdisplay = None
+ for display in display_manager.list_displays():
+ if hasattr(display, 'get_xdisplay'):
+ xdisplay = display.get_xdisplay()
+ break
+
+ if xdisplay is None:
+ display_name = os.environ.get('DISPLAY')
+ if display_name:
+ try:
+ x11_display = display_manager.open_display(display_name)
+ if x11_display is not None and hasattr(x11_display, 'get_xdisplay'):
+ xdisplay = x11_display.get_xdisplay()
+ except Exception:
+ pass
+
+ if xdisplay is None:
+ logging.warning('Keyboard: Could not get X11 display, '
+ 'keyboard settings may not work.')
+ self._keyboard_manager = model.KeyboardManager(xdisplay)
self._layout_combo_list = []
self._layout_addremovebox_list = []
scrollwindow = Gtk.ScrolledWindow()
scrollwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- self.pack_start(scrollwindow, True, True, 0)
- scrollwindow.show()
+ scrollwindow.set_vexpand(True)
+ self.append(scrollwindow)
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- scrollwindow.add_with_viewport(self._vbox)
+ scrollwindow.set_child(self._vbox)
self.__kmodel_sid = None
self.__layout_sid = None
@@ -225,21 +266,20 @@ def __init__(self, model, alerts):
self._setup_layouts()
self._setup_group_switch_option()
- self._vbox.show()
-
def _setup_kmodel(self):
"""Adds the controls for changing the keyboard model"""
separator_kmodel = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_kmodel, False, True, 0)
- separator_kmodel.show_all()
+ self._vbox.append(separator_kmodel)
label_kmodel = Gtk.Label(label=_('Keyboard Model'))
- label_kmodel.set_alignment(0, 0)
- self._vbox.pack_start(label_kmodel, False, True, 0)
- label_kmodel.show_all()
+ label_kmodel.set_halign(Gtk.Align.START)
+ self._vbox.append(label_kmodel)
box_kmodel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_kmodel.set_border_width(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_margin_end(style.DEFAULT_SPACING * 2)
box_kmodel.set_spacing(style.DEFAULT_SPACING)
kmodel_store = Gtk.ListStore(GObject.TYPE_STRING,
@@ -261,9 +301,8 @@ def _setup_kmodel(self):
kmodel_combo.set_active_iter(row.iter)
break
- box_kmodel.pack_start(kmodel_combo, False, True, 0)
- self._vbox.pack_start(box_kmodel, False, True, 0)
- box_kmodel.show_all()
+ box_kmodel.append(kmodel_combo)
+ self._vbox.append(box_kmodel)
kmodel_combo.connect('changed', self.__kmodel_changed_cb)
@@ -275,11 +314,16 @@ def __kmodel_changed_cb(self, combobox):
combobox)
def __kmodel_timeout_cb(self, combobox):
+ self.__kmodel_sid = None
it = combobox.get_active_iter()
+ if it is None:
+ return False
model = combobox.get_model()
+ if model is None:
+ return False
self._selected_kmodel = model.get(it, 0)[0]
if self._selected_kmodel == self._keyboard_manager.get_current_model():
- return
+ return False
try:
self._keyboard_manager.set_model(self._selected_kmodel)
except Exception:
@@ -291,16 +335,17 @@ def _setup_group_switch_option(self):
"""Adds the controls for changing the group switch option of keyboard
"""
separator_group_option = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_group_option, False, True, 0)
- separator_group_option.show_all()
+ self._vbox.append(separator_group_option)
label_group_option = Gtk.Label(label=_('Key(s) to change layout'))
- label_group_option.set_alignment(0, 0)
- self._vbox.pack_start(label_group_option, False, True, 0)
- label_group_option.show_all()
+ label_group_option.set_halign(Gtk.Align.START)
+ self._vbox.append(label_group_option)
box_group_option = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_group_option.set_border_width(style.DEFAULT_SPACING * 2)
+ box_group_option.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_group_option.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ box_group_option.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_group_option.set_margin_end(style.DEFAULT_SPACING * 2)
box_group_option.set_spacing(style.DEFAULT_SPACING)
group_option_store = Gtk.ListStore(GObject.TYPE_STRING,
@@ -329,9 +374,8 @@ def _setup_group_switch_option(self):
if not found:
group_option_combo.set_active(0)
- box_group_option.pack_start(group_option_combo, False, True, 0)
- self._vbox.pack_start(box_group_option, False, True, 0)
- box_group_option.show_all()
+ box_group_option.append(group_option_combo)
+ self._vbox.append(box_group_option)
group_option_combo.connect('changed', self.__group_switch_changed_cb)
@@ -342,12 +386,17 @@ def __group_switch_changed_cb(self, combobox):
_APPLY_TIMEOUT, self.__group_switch_timeout_cb, combobox)
def __group_switch_timeout_cb(self, combobox):
+ self.__group_switch_sid = None
it = combobox.get_active_iter()
+ if it is None:
+ return False
model = combobox.get_model()
+ if model is None:
+ return False
self._selected_group_switch_option = model.get(it, 0)[0]
if self._selected_group_switch_option == \
self._keyboard_manager.get_current_option_group():
- return
+ return False
try:
self._keyboard_manager.set_option_group(
self._selected_group_switch_option)
@@ -359,73 +408,79 @@ def __group_switch_timeout_cb(self, combobox):
def _setup_layouts(self):
"""Adds the controls for changing the keyboard layouts"""
separator_klayout = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(separator_klayout, False, True, 0)
- separator_klayout.show_all()
+ self._vbox.append(separator_klayout)
label_klayout = Gtk.Label(label=_('Keyboard Layout(s)'))
- label_klayout.set_alignment(0, 0)
- label_klayout.show_all()
- self._vbox.pack_start(label_klayout, False, True, 0)
+ label_klayout.set_halign(Gtk.Align.START)
+ self._vbox.append(label_klayout)
self._klayouts = self._keyboard_manager.get_current_layouts()
for i in range(0, self._keyboard_manager.get_max_layouts()):
add_remove_box = self.__create_add_remove_box()
self._layout_addremovebox_list.append(add_remove_box)
- self._layout_table.attach(add_remove_box, 1, 2, i, i + 1)
+ self._layout_table.attach(add_remove_box, 1, i, 1, 1)
layout_combo = LayoutCombo(self._keyboard_manager, i)
layout_combo.connect('selection-changed',
self.__layout_combo_selection_changed_cb)
self._layout_combo_list.append(layout_combo)
- self._layout_table.attach(layout_combo, 0, 1, i, i + 1)
+ self._layout_table.attach(layout_combo, 0, i, 1, 1)
if i < len(self._klayouts):
- layout_combo.show_all()
layout_combo.select_layout(self._klayouts[i])
+ else:
+ layout_combo.set_visible(False)
- self._vbox.pack_start(self._layout_table, False, True, 0)
- self._layout_table.set_size_request(
- self._vbox.get_size_request()[0], -1)
- self._layout_table.show()
+ self._vbox.append(self._layout_table)
self._update_klayouts()
def __determine_add_remove_box_visibility(self):
i = 1
for box in self._layout_addremovebox_list:
if not i == len(self._selected_klayouts):
- box.props.visible = False
+ box.set_visible(False)
else:
- box.show_all()
+ box.set_visible(True)
+ add = box.get_first_child()
+ remove = add.get_next_sibling()
+ if add:
+ add.set_visible(True)
+ if remove:
+ remove.set_visible(True)
+
if i == 1:
# First row - no need for showing remove btn
- add, remove = box.get_children()
- remove.props.visible = False
+ if remove:
+ remove.set_visible(False)
if i == self._keyboard_manager.get_max_layouts():
# Last row - no need for showing add btn
- add, remove = box.get_children()
- add.props.visible = False
+ if add:
+ add.set_visible(False)
i += 1
def __create_add_remove_box(self):
- """Creates Gtk.Hbox with add/remove buttons"""
- add_icon = Icon(icon_name='list-add')
+ """Creates Gtk.Box with add/remove buttons"""
+ add_icon = Icon(icon_name='list-add',
+ pixel_size=style.STANDARD_ICON_SIZE)
add_button = Gtk.Button()
- add_button.set_image(add_icon)
+ add_button.add_css_class('flat')
+ add_button.set_child(add_icon)
add_button.connect('clicked',
self.__add_button_clicked_cb)
- remove_icon = Icon(icon_name='list-remove')
+ remove_icon = Icon(icon_name='list-remove',
+ pixel_size=style.STANDARD_ICON_SIZE)
remove_button = Gtk.Button()
- remove_button.set_image(remove_icon)
+ remove_button.add_css_class('flat')
+ remove_button.set_child(remove_icon)
remove_button.connect('clicked',
self.__remove_button_clicked_cb)
- add_remove_box = Gtk.HButtonBox()
- add_remove_box.set_layout(Gtk.ButtonBoxStyle.START)
+ add_remove_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
add_remove_box.set_spacing(10)
- add_remove_box.pack_start(add_button, True, True, 0)
- add_remove_box.pack_start(remove_button, True, True, 0)
+ add_remove_box.append(add_button)
+ add_remove_box.append(remove_button)
return add_remove_box
@@ -433,19 +488,21 @@ def __layout_combo_selection_changed_cb(self, combo, layout, index):
self._update_klayouts()
def __add_button_clicked_cb(self, button):
- self._layout_combo_list[len(self._selected_klayouts)].show_all()
+ self._layout_combo_list[len(self._selected_klayouts)].set_visible(True)
self._update_klayouts()
def __remove_button_clicked_cb(self, button):
- self._layout_combo_list[len(self._selected_klayouts) - 1].hide()
+ self._layout_combo_list[len(self._selected_klayouts) - 1].set_visible(False)
self._update_klayouts()
def _update_klayouts(self):
"""Responds to any changes in the keyboard layout options"""
self._selected_klayouts = []
for combo in self._layout_combo_list:
- if combo.props.visible:
- self._selected_klayouts.append(combo.get_layout())
+ if combo.get_visible():
+ layout = combo.get_layout()
+ if layout:
+ self._selected_klayouts.append(layout)
self.__determine_add_remove_box_visibility()
@@ -455,6 +512,7 @@ def _update_klayouts(self):
self.__layout_timeout_cb)
def __layout_timeout_cb(self):
+ self.__layout_sid = None
if self._selected_klayouts == \
self._keyboard_manager.get_current_layouts():
return
diff --git a/extensions/cpsection/language/__init__.py b/extensions/cpsection/language/__init__.py
index 88a28bf7ac..bd58e9d0d1 100644
--- a/extensions/cpsection/language/__init__.py
+++ b/extensions/cpsection/language/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'Language'
ICON = 'module-language'
TITLE = _('Language')
diff --git a/extensions/cpsection/language/model.py b/extensions/cpsection/language/model.py
index 45fc73a77a..d9d6ad2e47 100644
--- a/extensions/cpsection/language/model.py
+++ b/extensions/cpsection/language/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 One Laptop Per Child
+# Copyright (C) 2007, 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -242,7 +242,6 @@
}
-
def read_all_languages():
fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE)
lines = fdp.stdout.read().decode('utf-8', 'ignore').split('\n')
@@ -270,7 +269,7 @@ def read_all_languages():
else:
territory = title.split()[-1]
if locale_str.endswith('utf8') and len(lang):
- locales.append((LANGUAGES[lang], territory, locale_str))
+ locales.append((LANGUAGES.get(lang, lang), territory, locale_str))
locales.sort()
return locales
@@ -389,10 +388,13 @@ def set_languages(languages):
def set_languages_list(languages):
"""Set the system language using a list of preferred languages"""
colon = ':'
+ languages = [lang for lang in languages if lang is not None]
+ if not languages:
+ return
language_env = colon.join(languages)
+
lang_env = languages[0].strip('\n')
_write_i18n(lang_env, language_env)
-
# inilialize the docstrings for the language
_initialize()
diff --git a/extensions/cpsection/language/view.py b/extensions/cpsection/language/view.py
index 73fdd9f79c..e9b9f0131b 100644
--- a/extensions/cpsection/language/view.py
+++ b/extensions/cpsection/language/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
# Copyright (C) 2009, Simon Schampijer
# Copyright (C) 2014, Walter Bender
#
@@ -15,7 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
import logging
from gi.repository import Gtk
@@ -76,37 +75,43 @@ def __init__(self, model, alerts):
else:
self._country_dict[language].append([code, country])
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
explanation = gettext.gettext('Add languages in the order you prefer.'
' If a translation is not available,'
' the next in the list will be used.')
self._text = Gtk.Label(label=explanation)
- self._text.set_line_wrap(True)
- self._text.set_alignment(0, 0)
- self.pack_start(self._text, False, False, 0)
- self._text.show()
+ self._text.set_wrap(True)
+ self._text.set_halign(Gtk.Align.START)
+ self.append(self._text)
scrolled = Gtk.ScrolledWindow()
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- scrolled.show()
- self.pack_start(scrolled, True, True, 0)
+ scrolled.set_vexpand(True)
+ self.append(scrolled)
- self._table = Gtk.Table(rows=2, columns=4, homogeneous=False)
- self._table.set_border_width(style.DEFAULT_SPACING * 2)
- self._table.show()
- scrolled.add_with_viewport(self._table)
+ self._table = Gtk.Grid()
+ self._table.set_margin_top(style.DEFAULT_SPACING * 2)
+ self._table.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self._table.set_margin_start(style.DEFAULT_SPACING * 2)
+ self._table.set_margin_end(style.DEFAULT_SPACING * 2)
+ scrolled.set_child(self._table)
self._lang_alert_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
- self.pack_start(self._lang_alert_box, False, True, 0)
+ self.append(self._lang_alert_box)
self._lang_alert = InlineAlert()
- self._lang_alert_box.pack_start(self._lang_alert, True, True, 0)
+ self._lang_alert.set_hexpand(True)
+ self._lang_alert_box.append(self._lang_alert)
if 'lang' in self.restart_alerts:
self._lang_alert.props.msg = self.restart_msg
- self._lang_alert.show()
- self._lang_alert_box.show()
+ self._lang_alert.set_visible(True)
+ else:
+ self._lang_alert.set_visible(False)
self.setup()
@@ -117,15 +122,10 @@ def _add_row(self, locale_code=None):
self._selected_lang_count += 1
- self._table.resize(self._selected_lang_count * 2, 3)
-
label = Gtk.Label(label=str(self._selected_lang_count))
- label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
self._labels.append(label)
self._attach_to_table(label, 0, 1, self._selected_lang_count * 2 - 1,
xpadding=1, ypadding=1)
- label.show()
locale_language = None
locale_country = None
@@ -173,8 +173,7 @@ def _add_row(self, locale_code=None):
self._language_buttons.append(new_language_button)
self._attach_to_table(
- new_language_button, 1, 2, self._selected_lang_count * 2 - 1,
- yoptions=Gtk.AttachOptions.SHRINK)
+ new_language_button, 1, 2, self._selected_lang_count * 2 - 1)
self._language_widgets.append(new_language_widget)
self._attach_to_table(new_language_widget, 1, 2,
@@ -184,8 +183,7 @@ def _add_row(self, locale_code=None):
self._country_buttons.append(new_country_button)
self._attach_to_table(
- new_country_button, 2, 3, self._selected_lang_count * 2 - 1,
- yoptions=Gtk.AttachOptions.SHRINK)
+ new_country_button, 2, 3, self._selected_lang_count * 2 - 1)
self._country_widgets.append(new_country_widget)
self._attach_to_table(new_country_widget, 2, 3,
@@ -198,20 +196,17 @@ def _add_row(self, locale_code=None):
self._attach_to_table(add_remove_box, 3, 4,
self._selected_lang_count * 2 - 1)
- add_remove_box.show_all()
-
if self._selected_lang_count > 1:
previous_add_removes = self._add_remove_boxes[-2]
- previous_add_removes.hide()
+ previous_add_removes.set_visible(False)
# Hide the Remove button if the new added row is the only
# language.
elif self._selected_lang_count == 1:
- add_button_, remove_button = add_remove_box.get_children()
- remove_button.props.visible = False
-
- new_language_button.show()
- new_country_button.show()
+ add_button_ = add_remove_box.get_first_child()
+ remove_button = add_button_.get_next_sibling()
+ if remove_button:
+ remove_button.set_visible(False)
def _build_country_list(self, language, idx=None):
country_list = []
@@ -229,15 +224,12 @@ def _build_country_list(self, language, idx=None):
def _attach_to_table(self, widget, left, right, above,
xpadding=style.DEFAULT_SPACING,
- ypadding=style.DEFAULT_SPACING,
- yoptions=Gtk.AttachOptions.FILL):
- self._table.attach(widget, left, right,
- above,
- above + 1,
- xoptions=Gtk.AttachOptions.FILL,
- yoptions=yoptions,
- xpadding=xpadding,
- ypadding=ypadding)
+ ypadding=style.DEFAULT_SPACING):
+ widget.set_margin_start(xpadding)
+ widget.set_margin_end(xpadding)
+ widget.set_margin_top(ypadding)
+ widget.set_margin_bottom(ypadding)
+ self._table.attach(widget, left, above, right - left, 1)
def _delete_last_row(self):
"""Deletes the last two rows of the table"""
@@ -245,41 +237,42 @@ def _delete_last_row(self):
self._selected_lang_count -= 1
label = self._labels.pop()
- label.destroy()
+ self._table.remove(label)
add_remove_box = self._add_remove_boxes.pop()
- add_remove_box.destroy()
+ self._table.remove(add_remove_box)
language_button = self._language_buttons.pop()
- language_button.destroy()
+ self._table.remove(language_button)
country_button = self._country_buttons.pop()
- country_button.destroy()
+ self._table.remove(country_button)
language_widget = self._language_widgets.pop()
- language_widget.destroy()
+ self._table.remove(language_widget)
country_widget = self._country_widgets.pop()
- country_widget.destroy()
+ self._table.remove(country_widget)
# Remove language code associated with last row
self._country_codes.pop()
- self._table.resize(self._selected_lang_count * 2, 3)
-
if self._selected_lang_count < 1:
return
- self._add_remove_boxes[-1].show_all()
+ self._add_remove_boxes[-1].set_visible(True)
# Hide or show the Remove button in the new last row,
# depending if it is the only language.
add_remove_box = self._add_remove_boxes[-1]
- add_button_, remove_button = add_remove_box.get_children()
+ add_button_ = add_remove_box.get_first_child()
+ remove_button = add_button_.get_next_sibling()
if self._selected_lang_count == 1:
- remove_button.props.visible = False
+ if remove_button:
+ remove_button.set_visible(False)
else:
- remove_button.props.visible = True
+ if remove_button:
+ remove_button.set_visible(True)
def setup(self):
for locale in self._selected_locales:
@@ -291,29 +284,32 @@ def _delete_all_rows(self):
def undo(self):
self._model.undo()
- self._lang_alert.hide()
+ self._lang_alert.set_visible(False)
self._delete_all_rows()
def _create_add_remove_box(self):
- """Creates Gtk.Hbox with add/remove buttons"""
- add_icon = Icon(icon_name='list-add')
+ """Creates Gtk.Box with add/remove buttons"""
+ add_icon = Icon(icon_name='list-add',
+ pixel_size=style.STANDARD_ICON_SIZE)
add_button = Gtk.Button()
- add_button.set_image(add_icon)
+ add_button.add_css_class('flat')
+ add_button.set_child(add_icon)
add_button.connect('clicked',
self.__add_button_clicked_cb)
- remove_icon = Icon(icon_name='list-remove')
+ remove_icon = Icon(icon_name='list-remove',
+ pixel_size=style.STANDARD_ICON_SIZE)
remove_button = Gtk.Button()
- remove_button.set_image(remove_icon)
+ remove_button.add_css_class('flat')
+ remove_button.set_child(remove_icon)
remove_button.connect('clicked',
self.__remove_button_clicked_cb)
- add_remove_box = Gtk.HButtonBox()
- add_remove_box.set_layout(Gtk.ButtonBoxStyle.START)
+ add_remove_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
add_remove_box.set_spacing(10)
- add_remove_box.pack_start(add_button, True, True, 0)
- add_remove_box.pack_start(remove_button, True, True, 0)
+ add_remove_box.append(add_button)
+ add_remove_box.append(remove_button)
return add_remove_box
@@ -325,7 +321,7 @@ def __remove_button_clicked_cb(self, button):
self._delete_last_row()
self._check_change()
- def _language_changed(self, widget, event, item):
+ def _language_changed(self, gesture, n_press, x, y, item):
i = item['index']
for language_key in list(self._language_dict.keys()):
@@ -338,11 +334,11 @@ def _language_changed(self, widget, event, item):
self._language_buttons[i].button_cb()
self._country_buttons[i].set_widget_label(
- _translate_country(self._country_dict[language_key][0][1])),
+ _translate_country(self._country_dict[language_key][0][1]))
self._country_codes[i] = self._country_dict[language_key][0][0]
old_country_widget = self._country_widgets[i]
- old_country_widget.destroy()
+ self._table.remove(old_country_widget)
new_country_widget = set_palette_list(new_country_list)
self._country_buttons[i].set_widget(new_country_widget)
@@ -353,7 +349,7 @@ def _language_changed(self, widget, event, item):
self._update_country(new_country_list[0])
- def _country_changed(self, widget, event, item):
+ def _country_changed(self, gesture, n_press, x, y, item):
self._update_country(item)
def _update_country(self, item):
@@ -374,7 +370,7 @@ def _check_change(self):
self.needs_restart = False
if 'lang' in self.restart_alerts:
self.restart_alerts.remove('lang')
- self._lang_alert.hide()
+ self._lang_alert.set_visible(False)
if self._lang_sid:
GLib.source_remove(self._lang_sid)
self._model.undo()
@@ -393,77 +389,68 @@ def __lang_timeout_cb(self, codes):
self.restart_alerts.append('lang')
self.needs_restart = True
self._lang_alert.props.msg = self.restart_msg
- self._lang_alert.show()
+ self._lang_alert.set_visible(True)
except IOError as e:
logging.exception('Error writing i18n config %s', e)
self.undo()
self._lang_alert.props.msg = gettext.gettext(
'Error writing language configuration (%s)') % e
- self._lang_alert.show()
+ self._lang_alert.set_visible(True)
self.props.is_valid = False
return False
-class FilterToolItem(Gtk.ToolItem):
+class FilterToolItem(Gtk.Box):
def __init__(self, primary_icon, secondary_icon, default_label,
widget):
- Gtk.ToolItem.__init__(self)
-
- self.set_size_request(style.GRID_CELL_SIZE * 3, -1)
+ Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self._widget = widget
self._visible = False
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
- self.add(grid)
- grid.show()
+ self.append(grid)
self._primary_icon = Icon(icon_name=primary_icon)
self._secondary_icon = Icon(icon_name=secondary_icon)
self._button = Gtk.Button()
- self._button.set_image(self._primary_icon)
- self._primary_icon.show()
- self._secondary_icon.show()
+ self._button.add_css_class('flat')
+ self._button.set_child(self._primary_icon)
grid.attach(self._button, 0, 0, 1, 1)
- self._button.show()
self._button.connect('clicked', self.button_cb)
- event_box = Gtk.EventBox()
self._label_widget = Gtk.Label()
- self._label_widget.set_alignment(0.0, 0.5)
+ self._label_widget.set_halign(Gtk.Align.START)
+ self._label_widget.set_valign(Gtk.Align.CENTER)
self._label_widget.set_use_markup(True)
self.set_widget_label(default_label)
- event_box.add(self._label_widget)
- self._label_widget.show()
- grid.attach(event_box, 1, 0, 1, 1)
- event_box.show()
- # Allow clicking on the label in addition to the button
- event_box.set_events(Gdk.EventMask.TOUCH_MASK)
- event_box.connect('touch-event', self._touch_event_cb)
+
+ click = Gtk.GestureClick.new()
+ click.connect('pressed', self._touch_event_cb)
+ self._label_widget.add_controller(click)
+ grid.attach(self._label_widget, 1, 0, 1, 1)
def set_widget(self, widget):
- self._widget.destroy()
self._widget = widget
if self._visible:
- self._widget.show()
+ self._widget.set_visible(True)
else:
- self._widget.hide()
+ self._widget.set_visible(False)
def is_visible(self):
return self._visible
- def _touch_event_cb(self, widget, event):
- if event.type in [Gdk.EventType.TOUCH_BEGIN]:
- self.button_cb(widget)
+ def _touch_event_cb(self, gesture, n, x, y):
+ self.button_cb()
def button_cb(self, widget=None):
if self._visible:
- self._widget.hide()
- self._button.set_image(self._primary_icon)
+ self._widget.set_visible(False)
+ self._button.set_child(self._primary_icon)
else:
- self._widget.show()
- self._button.set_image(self._secondary_icon)
+ self._widget.set_visible(True)
+ self._button.set_child(self._secondary_icon)
self._visible = not self._visible
def set_widget_label(self, label):
@@ -477,12 +464,7 @@ class BlackLabel(PaletteMenuItem):
''' Label in palette menu item with black text on white background '''
def __init__(self, text_label=None):
- PaletteMenuItem.__init__(self, text_label=None, text_maxlen=0)
-
- self.id_enter_notify_cb = self.connect('enter-notify-event',
- self.__enter_notify_cb)
- self.id_leave_notify_cb = self.connect('leave-notify-event',
- self.__leave_notify_cb)
+ PaletteMenuItem.__init__(self, text_label="", text_maxlen=0)
self.set_label(text_label)
def set_label(self, text_label):
@@ -491,23 +473,22 @@ def set_label(self, text_label):
text_label + ''
self.label.set_markup(text)
- def __enter_notify_cb(self, widget, event):
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_HIGHLIGHT.get_gdk_color())
-
- def __leave_notify_cb(self, widget, event):
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
-
def set_palette_list(palette_list):
menu_item = BlackLabel(text_label='English')
- req2 = menu_item.get_preferred_size()[1]
- item_width = req2.width
- item_height = req2.height + style.DEFAULT_PADDING
-
- palette_width = int(Gdk.Screen.width() / 2)
- palette_height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 3
+ item_width = style.GRID_CELL_SIZE * 3
+ item_height = style.GRID_CELL_SIZE + style.DEFAULT_PADDING
+
+ from gi.repository import Gdk
+ display = Gdk.Display.get_default()
+ monitors = display.get_monitors()
+ if monitors.get_n_items() > 0:
+ geometry = monitors.get_item(0).get_geometry()
+ palette_width = geometry.width
+ palette_height = geometry.height
+ else:
+ palette_width = 800
+ palette_height = 600
nx = min(3, int(palette_width / item_width))
ny = min(8, int(palette_height / item_height), len(palette_list))
@@ -525,14 +506,13 @@ def set_palette_list(palette_list):
grid = Gtk.Grid()
grid.set_row_spacing(style.DEFAULT_PADDING)
grid.set_column_spacing(0)
- grid.set_border_width(0)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scrolled_window.set_size_request(nx * item_width, ny * item_height)
- scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
- scrolled_window.add_with_viewport(grid)
- grid.show()
+ scrolled_window.set_has_frame(True)
+ scrolled_window.set_child(grid)
+ scrolled_window.set_visible(False)
x = 0
y = 0
@@ -540,13 +520,14 @@ def set_palette_list(palette_list):
for item in palette_list:
menu_item = BlackLabel(item['label'])
- menu_item.connect('button-release-event', item['callback'], item)
+ gesture = Gtk.GestureClick.new()
+ gesture.connect('released', item['callback'], item)
+ menu_item.add_controller(gesture)
+
grid.attach(menu_item, x, y, 1, 1)
x += 1
if x == nx:
x = 0
y += 1
- menu_item.show()
-
return scrolled_window
diff --git a/extensions/cpsection/modemconfiguration/model.py b/extensions/cpsection/modemconfiguration/model.py
index 9586189456..abd246adc0 100755
--- a/extensions/cpsection/modemconfiguration/model.py
+++ b/extensions/cpsection/modemconfiguration/model.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- encoding: utf-8 -*-
# Copyright (C) 2009 Paraguay Educa, Martin Abente
# Copyright (C) 2013 Miguel González
#
@@ -17,7 +17,7 @@
import logging
import locale
-from xml.etree.cElementTree import ElementTree
+from xml.etree.ElementTree import ElementTree
from gettext import gettext as _
import dbus
@@ -26,7 +26,6 @@
from jarabe.model import network
-
PROVIDERS_PATH = "/usr/share/mobile-broadband-provider-info/"\
"serviceproviders.xml"
@@ -206,8 +205,7 @@ def get(self, country_code):
try:
return self._data[country_code]
except KeyError:
- raise KeyError('Not found country name for code "%s"'
- % country_code)
+ return country_code # Fallback to code if name not found
class ServiceProvidersParser(object):
diff --git a/extensions/cpsection/modemconfiguration/view.py b/extensions/cpsection/modemconfiguration/view.py
index 6627ab78f7..ae739beca7 100644
--- a/extensions/cpsection/modemconfiguration/view.py
+++ b/extensions/cpsection/modemconfiguration/view.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- encoding: utf-8 -*-
# Copyright (C) 2009 Paraguay Educa, Martin Abente
# Copyright (C) 2010 Andrés Ambrois
# Copyright (C) 2012 Ajay Garg
@@ -27,7 +27,6 @@
from jarabe.controlpanel.sectionview import SectionView
-
from .model import ServiceProvidersError
APPLY_TIMEOUT = 1000
@@ -41,23 +40,19 @@ def _create_providers_list_store(items):
class EntryWithLabel(Gtk.Box):
- __gtype_name__ = 'SugarEntryWithLabel'
def __init__(self, label_text):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
self.label = Gtk.Label(label=label_text)
- self.label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- self.label.set_alignment(1, 0.5)
- self.pack_start(self.label, False, True, 0)
- self.label.show()
+ self.label.set_halign(Gtk.Align.END)
+ self.label.set_valign(Gtk.Align.CENTER)
+ self.append(self.label)
self._entry = Gtk.Entry()
self._entry.set_max_length(25)
self._entry.set_width_chars(25)
- self.pack_start(self._entry, False, True, 0)
- self._entry.show()
+ self.append(self._entry)
def get_entry(self):
return self._entry
@@ -73,53 +68,58 @@ def __init__(self, model, alerts=None):
self.restart_alerts = alerts
self._timeout_sid = 0
- self.set_border_width(style.DEFAULT_SPACING)
+ self.set_margin_top(style.DEFAULT_SPACING)
+ self.set_margin_bottom(style.DEFAULT_SPACING)
+ self.set_margin_start(style.DEFAULT_SPACING)
+ self.set_margin_end(style.DEFAULT_SPACING)
self.set_spacing(style.DEFAULT_SPACING)
- self._label_grp = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
- self._combo_grp = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ self._label_grp = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
+ self._combo_grp = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
scrolled_win = Gtk.ScrolledWindow()
scrolled_win.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
- self.add(scrolled_win)
- scrolled_win.show()
+ scrolled_win.set_vexpand(True)
+ self.append(scrolled_win)
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
- main_box.set_border_width(style.DEFAULT_SPACING)
- scrolled_win.add_with_viewport(main_box)
- main_box.show()
+ main_box.set_margin_top(style.DEFAULT_SPACING)
+ main_box.set_margin_bottom(style.DEFAULT_SPACING)
+ main_box.set_margin_start(style.DEFAULT_SPACING)
+ main_box.set_margin_end(style.DEFAULT_SPACING)
+ scrolled_win.set_child(main_box)
explanation = _('You will need to provide the following information'
' to set up a mobile broadband connection to a'
' cellular (3G) network.')
self._text = Gtk.Label(label=explanation)
- self._text.set_line_wrap(True)
- self._text.set_alignment(0, 0)
- main_box.pack_start(self._text, True, False, 0)
- self._text.show()
+ self._text.set_wrap(True)
+ self._text.set_halign(Gtk.Align.START)
+ main_box.append(self._text)
self._upper_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
- self._upper_box.set_border_width(style.DEFAULT_SPACING)
- main_box.pack_start(self._upper_box, True, False, 0)
- self._upper_box.show()
+ self._upper_box.set_margin_top(style.DEFAULT_SPACING)
+ self._upper_box.set_margin_bottom(style.DEFAULT_SPACING)
+ self._upper_box.set_margin_start(style.DEFAULT_SPACING)
+ self._upper_box.set_margin_end(style.DEFAULT_SPACING)
+ main_box.append(self._upper_box)
country_store = Gtk.ListStore(str, object)
- country_store.append([])
+ country_store.append(['', None])
provider_store = Gtk.ListStore(str, object)
- provider_store.append([])
+ provider_store.append(['', None])
plan_store = Gtk.ListStore(str, object)
- plan_store.append([])
+ plan_store.append(['', None])
self._country_combo = self._add_combo(country_store, _('Country:'))
self.provider_combo = self._add_combo(provider_store, _('Provider:'))
self.plan_combo = self._add_combo(plan_store, _('Plan:'))
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- main_box.pack_start(separator, True, False, 0)
- separator.show()
+ main_box.append(separator)
try:
self.service_providers = self._model.ServiceProviders()
@@ -152,50 +152,48 @@ def __init__(self, model, alerts=None):
self.plan_combo.connect("changed", self._plan_selected_cb)
lower_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
- lower_box.set_border_width(style.DEFAULT_SPACING)
- main_box.pack_start(lower_box, True, False, 0)
- lower_box.show()
+ lower_box.set_margin_top(style.DEFAULT_SPACING)
+ lower_box.set_margin_bottom(style.DEFAULT_SPACING)
+ lower_box.set_margin_start(style.DEFAULT_SPACING)
+ lower_box.set_margin_end(style.DEFAULT_SPACING)
+ main_box.append(lower_box)
self._username_entry = EntryWithLabel(_('Username:'))
self._username_entry.entry.connect('changed', self.__entry_changed_cb)
self._label_grp.add_widget(self._username_entry.label)
self._combo_grp.add_widget(self._username_entry.entry)
- lower_box.pack_start(self._username_entry, False, True, 0)
- self._username_entry.show()
+ lower_box.append(self._username_entry)
self._password_entry = EntryWithLabel(_('Password:'))
self._password_entry.entry.connect('changed', self.__entry_changed_cb)
self._label_grp.add_widget(self._password_entry.label)
self._combo_grp.add_widget(self._password_entry.entry)
- lower_box.pack_start(self._password_entry, False, True, 0)
- self._password_entry.show()
+ lower_box.append(self._password_entry)
self._number_entry = EntryWithLabel(_('Number:'))
self._number_entry.entry.connect('changed', self.__entry_changed_cb)
self._label_grp.add_widget(self._number_entry.label)
self._combo_grp.add_widget(self._number_entry.entry)
- lower_box.pack_start(self._number_entry, False, True, 0)
- self._number_entry.show()
+ lower_box.append(self._number_entry)
self._apn_entry = EntryWithLabel(_('Access Point Name (APN):'))
self._apn_entry.entry.connect('changed', self.__entry_changed_cb)
self._label_grp.add_widget(self._apn_entry.label)
self._combo_grp.add_widget(self._apn_entry.entry)
- lower_box.pack_start(self._apn_entry, False, True, 0)
- self._apn_entry.show()
+ lower_box.append(self._apn_entry)
self._pin_entry = EntryWithLabel(_('Personal Identity Number (PIN):'))
self._pin_entry.entry.connect('changed', self.__entry_changed_cb)
self._label_grp.add_widget(self._pin_entry.label)
self._combo_grp.add_widget(self._pin_entry.entry)
- lower_box.pack_start(self._pin_entry, False, True, 0)
- self._pin_entry.show()
+ lower_box.append(self._pin_entry)
self.setup()
def _add_combo(self, store, label_text=''):
- label = Gtk.Label(label_text)
- label.set_alignment(1, 0.5)
+ label = Gtk.Label(label=label_text)
+ label.set_halign(Gtk.Align.END)
+ label.set_valign(Gtk.Align.CENTER)
self._label_grp.add_widget(label)
combo = Gtk.ComboBox()
@@ -209,13 +207,10 @@ def _add_combo(self, store, label_text=''):
combo.add_attribute(renderer_text, "text", 0)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
- box.pack_start(label, False, True, 0)
- label.show()
- box.pack_start(combo, False, True, 0)
- combo.show()
+ box.append(label)
+ box.append(combo)
- self._upper_box.pack_start(box, False, True, 0)
- box.show()
+ self._upper_box.append(box)
return combo
def undo(self):
@@ -291,7 +286,7 @@ def _plan_selected_cb(self, combo):
self.service_providers.set_plan(plan.idx)
plan = self.service_providers.get_plan()
- self._username_entry.entry.set_text(plan.username)
- self._password_entry.entry.set_text(plan.password)
- self._number_entry.entry.set_text(plan.number)
- self._apn_entry.entry.set_text(plan.apn)
+ self._username_entry.entry.set_text(plan.username or '')
+ self._password_entry.entry.set_text(plan.password or '')
+ self._number_entry.entry.set_text(plan.number or '')
+ self._apn_entry.entry.set_text(plan.apn or '')
diff --git a/extensions/cpsection/network/model.py b/extensions/cpsection/network/model.py
index bbeb4ff39c..63744e0142 100644
--- a/extensions/cpsection/network/model.py
+++ b/extensions/cpsection/network/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2014 Sugar Labs, Frederick Grose
#
# This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,6 @@
from jarabe.model import network
-
KEYWORDS = ['network', 'jabber', 'radio', 'server', 'social', 'help']
@@ -134,5 +133,4 @@ def set_publish_information(value):
settings.set_boolean('publish-gadget', value)
return 0
-
nm_client = NM.Client()
diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py
index 778578ef52..4158876b1b 100644
--- a/extensions/cpsection/network/view.py
+++ b/extensions/cpsection/network/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
# Copyright (C) 2014, Sugar Labs, Frederick Grose
#
# This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import Gdk
from gettext import gettext as _
@@ -31,6 +30,7 @@
import os
+
CLASS = 'Network'
ICON = 'module-network'
TITLE = _('Network')
@@ -100,7 +100,6 @@ def prop_changed(widget, param):
if not (flags & Gio.SettingsBindFlags.NO_SENSITIVITY):
self.bind_writable(key, widget, "sensitive", False)
-
Gio.Settings.bind_with_convert = bind_with_convert
Gio.Settings.__setitem__ = __setitem__
@@ -131,14 +130,13 @@ class SettingBox(Gtk.Box):
def __init__(self, name, size_group=None):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
- label = Gtk.Label(name)
- label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- label.set_alignment(1, 0.5)
+ label = Gtk.Label(label=name)
+ label.add_css_class('dim-label')
+ label.set_xalign(1)
+ label.set_yalign(0.5)
if size_group is not None:
size_group.add_widget(label)
- self.pack_start(label, False, False, 0)
- label.show()
+ self.append(label)
class ComboSettingBox(Gtk.Box):
@@ -155,14 +153,14 @@ def __init__(self, name, setting, setting_key,
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
setting_box = SettingBox(name, size_group)
- self.pack_start(setting_box, False, False, 0)
- setting_box.show()
+ self.append(setting_box)
model = Gtk.ListStore(str, str, object)
combo_box = Gtk.ComboBox(model=model)
combo_box.connect('changed', self.__combo_changed_cb)
- setting_box.pack_start(combo_box, True, True, 0)
- combo_box.show()
+ setting_box.append(combo_box)
+ combo_box.set_hexpand(True)
+ combo_box.set_vexpand(True)
cell_renderer = Gtk.CellRendererText()
cell_renderer.props.ellipsize = Pango.EllipsizeMode.MIDDLE
@@ -172,8 +170,7 @@ def __init__(self, name, setting, setting_key,
combo_box.props.id_column = 1
self._settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self._settings_box.show()
- self.pack_start(self._settings_box, False, False, 0)
+ self.append(self._settings_box)
for optset in option_sets:
model.append(optset)
@@ -184,12 +181,14 @@ def __init__(self, name, setting, setting_key,
def __combo_changed_cb(self, combobox):
giter = combobox.get_active_iter()
new_box = combobox.get_model().get(giter, 2)[0]
- current_box = self._settings_box.get_children()
+ current_box = []
+ child = self._settings_box.get_first_child()
+ if child:
+ current_box.append(child)
if current_box:
self._settings_box.remove(current_box[0])
- self._settings_box.add(new_box)
- new_box.show()
+ self._settings_box.append(new_box)
class OptionalSettingsBox(Gtk.Box):
@@ -206,9 +205,10 @@ def __init__(self, name, setting, setting_key, contents_box):
check_button = Gtk.CheckButton()
check_button.props.label = name
check_button.connect('toggled', self.__button_toggled_cb, contents_box)
- check_button.show()
- self.pack_start(check_button, True, True, 0)
- self.pack_start(contents_box, False, False, 0)
+ self.append(check_button)
+ check_button.set_hexpand(True)
+ check_button.set_vexpand(True)
+ self.append(contents_box)
setting.bind(setting_key, check_button, 'active',
Gio.SettingsBindFlags.DEFAULT)
@@ -224,19 +224,19 @@ class HostPortSettingBox(SettingBox):
def __init__(self, name, alert, setting, size_group=None):
SettingBox.__init__(self, name, size_group)
- self.pack_start(alert, False, True, 0)
- alert.hide()
+ self.append(alert)
+ alert.set_visible(False)
host_entry = Gtk.Entry()
- self.pack_start(host_entry, True, True, 0)
- host_entry.show()
+ self.append(host_entry)
+ host_entry.set_hexpand(True)
+ host_entry.set_vexpand(True)
setting.bind('host', host_entry, 'text', Gio.SettingsBindFlags.DEFAULT)
# port number 0 means n/a
port_entry = NumberEntry()
- self.pack_start(port_entry, False, False, 0)
- port_entry.show()
+ self.append(port_entry)
setting.bind_with_convert('port',
port_entry,
"text",
@@ -257,8 +257,9 @@ def __init__(self, name, setting, setting_key, size_group=None,
SettingBox.__init__(self, name, size_group)
entry = Gtk.Entry()
- self.pack_start(entry, True, True, 0)
- entry.show()
+ self.append(entry)
+ entry.set_hexpand(True)
+ entry.set_vexpand(True)
if password_field:
entry.set_visibility(False)
@@ -276,8 +277,9 @@ def __init__(self, name, setting, setting_key,
SettingBox.__init__(self, name, size_group)
entry = Gtk.Entry()
- self.pack_start(entry, True, True, 0)
- entry.show()
+ self.append(entry)
+ entry.set_hexpand(True)
+ entry.set_vexpand(True)
if password_field:
entry.set_visibility(False)
@@ -306,179 +308,179 @@ def __init__(self, model, alerts):
self._proxy_settings = {}
self._proxy_inline_alerts = {}
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
self._radio_alert_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
scrolled = Gtk.ScrolledWindow()
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- self.add(scrolled)
- scrolled.show()
+ scrolled.set_hexpand(True)
+ scrolled.set_vexpand(True)
+ self.append(scrolled)
workspace = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- scrolled.add_with_viewport(workspace)
- workspace.show()
+ viewport = Gtk.Viewport()
+ viewport.set_child(workspace)
+ scrolled.set_child(viewport)
separator_wireless = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- workspace.pack_start(separator_wireless, False, True, 0)
- separator_wireless.show()
+ workspace.append(separator_wireless)
label_wireless = Gtk.Label(label=_('Wireless'))
- label_wireless.set_alignment(0, 0)
- workspace.pack_start(label_wireless, False, True, 0)
- label_wireless.show()
+ label_wireless.set_xalign(0)
+ label_wireless.set_yalign(0)
+ workspace.append(label_wireless)
box_wireless = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_wireless.set_border_width(style.DEFAULT_SPACING * 2)
+ box_wireless.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_wireless.set_margin_end(style.DEFAULT_SPACING * 2)
+ box_wireless.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_wireless.set_margin_bottom(style.DEFAULT_SPACING * 2)
box_wireless.set_spacing(style.DEFAULT_SPACING)
radio_info = Gtk.Label(label=_('The wireless radio may be turned'
' off to save battery life.'))
- radio_info.set_alignment(0, 0)
- radio_info.set_line_wrap(True)
- radio_info.show()
- box_wireless.pack_start(radio_info, False, True, 0)
+ radio_info.set_xalign(0)
+ radio_info.set_yalign(0)
+ radio_info.set_wrap(True)
+ box_wireless.append(radio_info)
box_radio = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
self._button = Gtk.CheckButton()
- self._button.set_alignment(0, 0)
- box_radio.pack_start(self._button, False, True, 0)
- self._button.show()
+ self._button.set_halign(Gtk.Align.START)
+ self._button.set_valign(Gtk.Align.START)
+ box_radio.append(self._button)
label_radio = Gtk.Label(label=_('Radio'))
- label_radio.set_alignment(0, 0.5)
- box_radio.pack_start(label_radio, False, True, 0)
- label_radio.show()
+ label_radio.set_xalign(0)
+ label_radio.set_yalign(0.5)
+ box_radio.append(label_radio)
- box_wireless.pack_start(box_radio, False, True, 0)
- box_radio.show()
+ box_wireless.append(box_radio)
self._radio_alert = InlineAlert()
- self._radio_alert_box.pack_start(self._radio_alert, False, True, 0)
- box_radio.pack_end(self._radio_alert_box, False, True, 0)
- self._radio_alert_box.show()
+ self._radio_alert.set_visible(False)
+ self._radio_alert_box.append(self._radio_alert)
+ box_radio.append(self._radio_alert_box)
+ self._radio_alert_box.set_hexpand(True)
+ self._radio_alert_box.set_halign(Gtk.Align.END)
if 'radio' in self.restart_alerts:
self._radio_alert.props.msg = self.restart_msg
- self._radio_alert.show()
- wireless_info = Gtk.Label(
- label=_('Discard wireless connections if'
+ wireless_info = Gtk.Label(label=_('Discard wireless connections if'
' you have trouble connecting to the network'))
- wireless_info.set_alignment(0, 0)
- wireless_info.set_line_wrap(True)
- wireless_info.show()
- box_wireless.pack_start(wireless_info, False, True, 0)
+ wireless_info.set_xalign(0)
+ wireless_info.set_yalign(0)
+ wireless_info.set_wrap(True)
+ box_wireless.append(wireless_info)
box_clear_wireless = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
self._clear_wireless_button = Gtk.Button()
self._clear_wireless_button.set_label(
_('Discard wireless connections'))
- box_clear_wireless.pack_start(
- self._clear_wireless_button, False, True, 0)
+ box_clear_wireless.append(
+ self._clear_wireless_button)
if not self._model.have_wireless_networks():
self._clear_wireless_button.set_sensitive(False)
- self._clear_wireless_button.show()
- box_wireless.pack_start(box_clear_wireless, False, True, 0)
- box_clear_wireless.show()
+ box_wireless.append(box_clear_wireless)
- workspace.pack_start(box_wireless, False, True, 0)
- box_wireless.show()
+ workspace.append(box_wireless)
separator_mesh = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- workspace.pack_start(separator_mesh, False, False, 0)
- separator_mesh.show()
+ workspace.append(separator_mesh)
label_mesh = Gtk.Label(label=_('Collaboration'))
- label_mesh.set_alignment(0, 0)
- workspace.pack_start(label_mesh, False, True, 0)
- label_mesh.show()
+ label_mesh.set_xalign(0)
+ label_mesh.set_yalign(0)
+ workspace.append(label_mesh)
box_mesh = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_mesh.set_border_width(style.DEFAULT_SPACING * 2)
+ box_mesh.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_mesh.set_margin_end(style.DEFAULT_SPACING * 2)
+ box_mesh.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_mesh.set_margin_bottom(style.DEFAULT_SPACING * 2)
box_mesh.set_spacing(style.DEFAULT_SPACING)
- server_info = Gtk.Label(_("The server is the equivalent of what"
+ server_info = Gtk.Label(label=_("The server is the equivalent of what"
" room you are in; people on the same server"
" will be able to see each other, even when"
" they aren't on the same network."))
- server_info.set_alignment(0, 0)
- server_info.set_line_wrap(True)
- box_mesh.pack_start(server_info, False, True, 0)
- server_info.show()
+ server_info.set_xalign(0)
+ server_info.set_yalign(0)
+ server_info.set_wrap(True)
+ box_mesh.append(server_info)
box_server = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
label_server = Gtk.Label(label=_('Server:'))
- label_server.set_alignment(1, 0.5)
- label_server.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- box_server.pack_start(label_server, False, True, 0)
+ label_server.add_css_class('dim-label')
+ label_server.set_xalign(1)
+ label_server.set_yalign(0.5)
+ box_server.append(label_server)
group.add_widget(label_server)
- label_server.show()
self._entry = Gtk.Entry()
self._entry.set_alignment(0)
- self._entry.set_size_request(int(Gdk.Screen.width() / 3), -1)
- box_server.pack_start(self._entry, False, True, 0)
- self._entry.show()
- box_mesh.pack_start(box_server, False, True, 0)
- box_server.show()
-
- social_help_info = Gtk.Label(
- _('Social Help is a forum that lets you connect with developers'
+ self._entry.set_hexpand(True)
+ box_server.append(self._entry)
+ box_mesh.append(box_server)
+
+ social_help_info = Gtk.Label(label=_('Social Help is a forum that lets you connect with developers'
' and discuss Sugar Activities. Changing servers means'
' discussions will happen in a different place with'
' different people.'))
- social_help_info.set_alignment(0, 0)
- social_help_info.set_line_wrap(True)
- box_mesh.pack_start(social_help_info, False, True, 0)
- social_help_info.show()
+ social_help_info.set_xalign(0)
+ social_help_info.set_yalign(0)
+ social_help_info.set_wrap(True)
+ box_mesh.append(social_help_info)
social_help_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
social_help_label = Gtk.Label(label=_('Social Help Server:'))
- social_help_label.set_alignment(1, 0.5)
- social_help_label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- social_help_box.pack_start(social_help_label, False, True, 0)
+ social_help_label.add_css_class('dim-label')
+ social_help_label.set_xalign(1)
+ social_help_label.set_yalign(0.5)
+ social_help_box.append(social_help_label)
group.add_widget(social_help_label)
- social_help_label.show()
self._social_help_entry = Gtk.Entry()
self._social_help_entry.set_alignment(0)
- self._social_help_entry.set_size_request(
- int(Gdk.Screen.width() / 3), -1)
- social_help_box.pack_start(self._social_help_entry, False, True, 0)
- self._social_help_entry.show()
- box_mesh.pack_start(social_help_box, False, True, 0)
- social_help_box.show()
+ self._social_help_entry.set_hexpand(True)
+ social_help_box.append(self._social_help_entry)
+ box_mesh.append(social_help_box)
- workspace.pack_start(box_mesh, False, True, 0)
- box_mesh.show()
+ workspace.append(box_mesh)
separator_proxy = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- workspace.pack_start(separator_proxy, False, False, 0)
- separator_proxy.show()
+ workspace.append(separator_proxy)
self._add_proxy_section(workspace)
self.setup()
def _add_proxy_section(self, workspace):
- label_proxy = Gtk.Label(_('Proxy'))
- label_proxy.set_alignment(0, 0)
- workspace.pack_start(label_proxy, False, True, 0)
- label_proxy.show()
+ label_proxy = Gtk.Label(label=_('Proxy'))
+ label_proxy.set_xalign(0)
+ label_proxy.set_yalign(0)
+ workspace.append(label_proxy)
box_proxy = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_proxy.set_border_width(style.DEFAULT_SPACING * 2)
+ box_proxy.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_proxy.set_margin_end(style.DEFAULT_SPACING * 2)
+ box_proxy.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_proxy.set_margin_bottom(style.DEFAULT_SPACING * 2)
box_proxy.set_spacing(style.DEFAULT_SPACING)
- workspace.pack_start(box_proxy, False, True, 0)
- box_proxy.show()
+ workspace.append(box_proxy)
self._proxy_alert = Alert()
self._proxy_alert.props.title = _('Error')
self._proxy_alert.props.msg = _('Proxy settings cannot be verified')
- box_proxy.pack_start(self._proxy_alert, False, False, 0)
+ box_proxy.append(self._proxy_alert)
self._proxy_alert.connect('response', self._response_cb)
- self._proxy_alert.hide()
+ self._proxy_alert.set_visible(False)
# GSettings schemas for proxy:
schemas = ['org.sugarlabs.system.proxy',
@@ -500,7 +502,7 @@ def _add_proxy_section(self, workspace):
self._proxy_settings[schema] = proxy_setting
self._proxy_inline_alerts[schema] = alert
- size_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
automatic_proxy_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
manual_proxy_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
@@ -514,8 +516,7 @@ def _add_proxy_section(self, workspace):
_('Method:'), self._proxy_settings['org.sugarlabs.system.proxy'],
'mode', option_sets, size_group)
- box_proxy.pack_start(box_mode, False, False, 0)
- box_mode.show()
+ box_proxy.append(box_mode)
url_box = StringSettingBox(
_('Configuration URL:'),
@@ -523,46 +524,47 @@ def _add_proxy_section(self, workspace):
'autoconfig-url',
size_group)
- automatic_proxy_box.pack_start(url_box, True, True, 0)
- url_box.show()
+ automatic_proxy_box.append(url_box)
+
+ url_box.set_hexpand(True)
+
+ url_box.set_vexpand(True)
wpad_help_text = _('Web Proxy Autodiscovery is used when a'
' Configuration URL is not provided. This is not'
' recommended for untrusted public networks.')
- automatic_proxy_help = Gtk.Label(wpad_help_text)
- automatic_proxy_help.set_alignment(0, 0)
- automatic_proxy_help.set_line_wrap(True)
- automatic_proxy_help.show()
- automatic_proxy_box.pack_start(automatic_proxy_help, True, True, 0)
+ automatic_proxy_help = Gtk.Label(label=wpad_help_text)
+ automatic_proxy_help.set_xalign(0)
+ automatic_proxy_help.set_yalign(0)
+ automatic_proxy_help.set_wrap(True)
+ automatic_proxy_box.append(automatic_proxy_help)
+ automatic_proxy_help.set_hexpand(True)
+ automatic_proxy_help.set_vexpand(True)
# HTTP Section
schema = 'org.sugarlabs.system.proxy.http'
box_http = HostPortSettingBox(
_('HTTP Proxy:'), self._proxy_inline_alerts[schema],
self._proxy_settings[schema], size_group)
- manual_proxy_box.pack_start(box_http, False, False, 0)
- box_http.show()
+ manual_proxy_box.append(box_http)
auth_contents_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
auth_box = OptionalSettingsBox(
_('Use authentication'),
self._proxy_settings[schema],
'use-authentication', auth_contents_box)
- manual_proxy_box.pack_start(auth_box, False, False, 0)
- auth_box.show()
+ manual_proxy_box.append(auth_box)
proxy_http_setting = Gio.Settings.new(schema)
proxy_http_setting.delay()
box_username = StringSettingBox(
_('Username:'),
self._proxy_settings[schema],
'authentication-user', size_group)
- auth_contents_box.pack_start(box_username, False, False, 0)
- box_username.show()
+ auth_contents_box.append(box_username)
box_password = StringSettingBox(
_('Password:'),
self._proxy_settings[schema],
'authentication-password', size_group, password_field=True)
- auth_contents_box.pack_start(box_password, False, False, 0)
- box_password.show()
+ auth_contents_box.append(box_password)
# HTTPS Section
schema = 'org.sugarlabs.system.proxy.https'
@@ -570,8 +572,7 @@ def _add_proxy_section(self, workspace):
_('HTTPS Proxy:'), self._proxy_inline_alerts[schema],
self._proxy_settings[schema],
size_group)
- manual_proxy_box.pack_start(box_https, False, False, 0)
- box_https.show()
+ manual_proxy_box.append(box_https)
# FTP Section
schema = 'org.sugarlabs.system.proxy.ftp'
@@ -579,8 +580,7 @@ def _add_proxy_section(self, workspace):
_('FTP Proxy:'), self._proxy_inline_alerts[schema],
self._proxy_settings[schema],
size_group)
- manual_proxy_box.pack_start(box_ftp, False, False, 0)
- box_ftp.show()
+ manual_proxy_box.append(box_ftp)
# SOCKS Section
schema = 'org.sugarlabs.system.proxy.socks'
@@ -588,15 +588,13 @@ def _add_proxy_section(self, workspace):
_('SOCKS Proxy:'), self._proxy_inline_alerts[schema],
self._proxy_settings[schema],
size_group)
- manual_proxy_box.pack_start(box_socks, False, False, 0)
- box_socks.show()
+ manual_proxy_box.append(box_socks)
box_ignore = StringSettingBox_with_convert(
_('Ignore Hosts:'),
self._proxy_settings['org.sugarlabs.system.proxy'], 'ignore-hosts',
type_as_to_string, string_to_type_as, size_group)
- manual_proxy_box.pack_start(box_ignore, False, False, 0)
- box_ignore.show()
+ manual_proxy_box.append(box_ignore)
def setup(self):
self._entry.set_text(self._start_jabber)
@@ -606,7 +604,7 @@ def setup(self):
radio_state = self._model.get_radio()
except self._model.ReadError as detail:
self._radio_alert.props.msg = detail
- self._radio_alert.show()
+ self._radio_alert.set_visible(True)
else:
self._button.set_active(radio_state)
@@ -619,16 +617,16 @@ def setup(self):
'clicked', self.__wireless_configuration_reset_cb)
def _response_cb(self, alert, response_id):
- if response_id is Gtk.ResponseType.APPLY:
- self._proxy_alert.hide()
+ if response_id == Gtk.ResponseType.APPLY:
+ self._proxy_alert.set_visible(False)
self._apply_proxy_settings()
self.show_restart_alert = True
self.emit('add-alert')
- elif response_id is Gtk.ResponseType.CANCEL:
+ elif response_id == Gtk.ResponseType.CANCEL:
self.undo()
self._proxy_alert.remove_button(Gtk.ResponseType.APPLY)
self._proxy_alert.remove_button(Gtk.ResponseType.CANCEL)
- self._proxy_alert.hide()
+ self._proxy_alert.set_visible(False)
self.emit('set-toolbar-sensitivity', True)
def _ping_servers(self):
@@ -642,7 +640,7 @@ def _ping_servers(self):
non_blank_host_name_counter += 1
response = os.system("ping -c 1 -W 1 " + hostname)
if (response):
- self._proxy_inline_alerts[schema].show()
+ self._proxy_inline_alerts[schema].set_visible(True)
response_to_return = False
if non_blank_host_name_counter == 0:
response_to_return = False
@@ -651,7 +649,6 @@ def _ping_servers(self):
def _verify_settings(self):
self._proxy_alert.props.title = _('Please Wait!')
self._proxy_alert.props.msg = _('Proxy settings are being verified.')
- self._proxy_alert.show()
flag_all_true = True
g_proxy_schema = self._proxy_settings['org.sugarlabs.system.proxy']
g_mode = Gio.Settings.get_string(g_proxy_schema, 'mode')
@@ -664,7 +661,7 @@ def _verify_settings(self):
flag_all_true = self._ping_servers()
if flag_all_true:
self.show_restart_alert = True
- self._proxy_alert.hide()
+ self._proxy_alert.set_visible(False)
self._apply_proxy_settings()
else:
self._proxy_alert.props.title = _('Error!')
@@ -675,11 +672,9 @@ def _verify_settings(self):
icon = Icon(icon_name='dialog-cancel')
self._proxy_alert.add_button(Gtk.ResponseType.APPLY,
_('Break my internet'), icon)
- icon.show()
icon = Icon(icon_name='dialog-ok')
self._proxy_alert.add_button(Gtk.ResponseType.CANCEL,
_('Reset'), icon)
- icon.show()
def _apply_proxy_settings(self):
for setting in list(self._proxy_settings.values()):
@@ -703,11 +698,11 @@ def apply(self):
def undo(self):
self._button.disconnect(self._radio_change_handler)
- self._radio_alert.hide()
+ self._radio_alert.set_visible(False)
for setting in list(self._proxy_settings.values()):
setting.revert()
for alert in list(self._proxy_inline_alerts.values()):
- alert.hide()
+ alert.set_visible(False)
def _validate(self):
if self._radio_valid:
@@ -721,6 +716,7 @@ def __radio_toggled_cb(self, widget, data=None):
self._model.set_radio(radio_state)
except self._model.ReadError as detail:
self._radio_alert.props.msg = detail
+ self._radio_alert.set_visible(True)
self._radio_valid = False
else:
self._radio_valid = True
diff --git a/extensions/cpsection/power/__init__.py b/extensions/cpsection/power/__init__.py
index 0311aeadc0..aee59ebd57 100644
--- a/extensions/cpsection/power/__init__.py
+++ b/extensions/cpsection/power/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,13 +13,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
import os
POWERD_FLAG_DIR = '/etc/powerd/flags'
-if os.access(POWERD_FLAG_DIR, os.W_OK):
- CLASS = 'Power'
+
+
+CLASS = 'Power'
ICON = 'module-power'
TITLE = _('Power')
KEYWORDS = ['automatic', 'power', 'suspend', 'battery']
diff --git a/extensions/cpsection/power/model.py b/extensions/cpsection/power/model.py
index 34ee824189..4804773c32 100644
--- a/extensions/cpsection/power/model.py
+++ b/extensions/cpsection/power/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -40,9 +40,7 @@ def __str__(self):
def using_powerd():
- # directory exists if powerd running, and it's recent
- # enough to be controllable.
- return os.access(POWERD_FLAG_DIR, os.W_OK)
+ return False
def get_automatic_pm():
@@ -75,15 +73,21 @@ def set_automatic_pm(enabled):
return
# ohmd
- bus = dbus.SystemBus()
- proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
- keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+ try:
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
+ keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+
+ if enabled == 'on' or enabled == 1:
+ keystore.SetKey('suspend.automatic_pm', 1)
+ elif enabled == 'off' or enabled == 0:
+ keystore.SetKey('suspend.automatic_pm', 0)
+ except dbus.exceptions.DBusException:
+ _logger.warning('Could not connect to %s. Falling back to GSettings.' % OHM_SERVICE_NAME)
if enabled == 'on' or enabled == 1:
- keystore.SetKey('suspend.automatic_pm', 1)
enabled = True
elif enabled == 'off' or enabled == 0:
- keystore.SetKey('suspend.automatic_pm', 0)
enabled = False
else:
raise ValueError(_('Error in automatic pm argument, use on/off.'))
diff --git a/extensions/cpsection/power/view.py b/extensions/cpsection/power/view.py
index 590d2fbc72..da14e359b9 100644
--- a/extensions/cpsection/power/view.py
+++ b/extensions/cpsection/power/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gettext import gettext as _
@@ -32,65 +31,63 @@ def __init__(self, model, alerts):
self._automatic_pm_valid = True
self._automatic_pm_change_handler = None
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
self.set_spacing(style.DEFAULT_SPACING)
- group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
+ group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
self._automatic_pm_alert_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
separator_pm = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self.pack_start(separator_pm, False, True, 0)
- separator_pm.show()
+ self.append(separator_pm)
label_pm = Gtk.Label(label=_('Power management'))
- label_pm.set_alignment(0, 0)
- self.pack_start(label_pm, False, True, 0)
- label_pm.show()
+ label_pm.set_xalign(0)
+ label_pm.set_yalign(0)
+ self.append(label_pm)
box_pm = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- box_pm.set_border_width(style.DEFAULT_SPACING * 2)
+ box_pm.set_margin_start(style.DEFAULT_SPACING * 2)
+ box_pm.set_margin_end(style.DEFAULT_SPACING * 2)
+ box_pm.set_margin_top(style.DEFAULT_SPACING * 2)
+ box_pm.set_margin_bottom(style.DEFAULT_SPACING * 2)
box_pm.set_spacing(style.DEFAULT_SPACING)
box_automatic_pm = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
- label_automatic_pm = Gtk.Label(
- label=_('Automatic power management (increases battery life)'))
- label_automatic_pm.set_alignment(0, 0.5)
+ label_automatic_pm = Gtk.Label(label=_('Automatic power management (increases battery life)'))
+ label_automatic_pm.set_xalign(0)
+ label_automatic_pm.set_yalign(0.5)
self._automatic_button = Gtk.CheckButton()
- self._automatic_button.set_alignment(0, 0)
- box_automatic_pm.pack_start(self._automatic_button, False, True, 0)
- box_automatic_pm.pack_start(label_automatic_pm, False, True, 0)
- self._automatic_button.show()
- label_automatic_pm.show()
+ self._automatic_button.set_halign(Gtk.Align.START)
+ self._automatic_button.set_valign(Gtk.Align.START)
+ box_automatic_pm.append(self._automatic_button)
+ box_automatic_pm.append(label_automatic_pm)
group.add_widget(label_automatic_pm)
- box_pm.pack_start(box_automatic_pm, False, True, 0)
- box_automatic_pm.show()
+ box_pm.append(box_automatic_pm)
self._automatic_pm_alert = InlineAlert()
label_automatic_pm_error = Gtk.Label()
group.add_widget(label_automatic_pm_error)
- self._automatic_pm_alert_box.pack_start(label_automatic_pm_error,
- expand=False, fill=True,
- padding=0)
- label_automatic_pm_error.show()
- self._automatic_pm_alert_box.pack_start(self._automatic_pm_alert,
- expand=False, fill=True,
- padding=0)
- box_pm.pack_end(self._automatic_pm_alert_box, False, True, 0)
- self._automatic_pm_alert_box.show()
+ self._automatic_pm_alert_box.append(label_automatic_pm_error)
+ self._automatic_pm_alert_box.append(self._automatic_pm_alert)
+ box_pm.append(self._automatic_pm_alert_box)
if 'automatic_pm' in self.restart_alerts:
self._automatic_pm_alert.props.msg = self.restart_msg
- self._automatic_pm_alert.show()
- self.pack_start(box_pm, False, True, 0)
- box_pm.show()
+ self.append(box_pm)
self.setup()
def setup(self):
try:
automatic_state = self._model.get_automatic_pm()
- except Exception as detail:
- self._automatic_pm_alert.props.msg = detail
- self._automatic_pm_alert.show()
+ except self._model.ReadError as detail:
+ self._automatic_pm_alert.props.msg = str(detail)
+ self._automatic_pm_alert.set_visible(True)
else:
self._automatic_button.set_active(automatic_state)
@@ -101,8 +98,9 @@ def setup(self):
def undo(self):
self._automatic_button.disconnect(self._automatic_pm_change_handler)
- self._model.undo()
- self._automatic_pm_alert.hide()
+ if hasattr(self._model, 'undo'):
+ self._model.undo()
+ self._automatic_pm_alert.set_visible(False)
def _validate(self):
if self._automatic_pm_valid:
@@ -116,7 +114,8 @@ def __automatic_pm_toggled_cb(self, widget, data=None):
self._model.set_automatic_pm(state)
except Exception as detail:
print(detail)
- self._automatic_pm_alert.props.msg = detail
+ self._automatic_pm_alert.props.msg = str(detail)
+ self._automatic_pm_alert.set_visible(True)
else:
self._automatic_pm_valid = True
diff --git a/extensions/cpsection/updater/__init__.py b/extensions/cpsection/updater/__init__.py
index ad06fc3776..f86f97eb81 100644
--- a/extensions/cpsection/updater/__init__.py
+++ b/extensions/cpsection/updater/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,9 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
+
CLASS = 'ActivityUpdater'
ICON = 'module-updater'
TITLE = _('Software Update')
diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py
index 216db55006..9fb5acdcf3 100644
--- a/extensions/cpsection/updater/view.py
+++ b/extensions/cpsection/updater/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2008, One Laptop Per Child
# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
@@ -14,13 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
from gettext import ngettext
import locale
import logging
-from gi.repository import GObject
+from gi.repository import GLib
from gi.repository import Gtk
from sugar4.graphics import style
@@ -49,28 +48,28 @@ def __init__(self, model, alerts):
self.__finished_cb)
self.set_spacing(style.DEFAULT_SPACING)
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
self._top_label = Gtk.Label()
- self._top_label.set_line_wrap(True)
+ self._top_label.set_wrap(True)
self._top_label.set_justify(Gtk.Justification.LEFT)
self._top_label.props.xalign = 0
- self.pack_start(self._top_label, False, True, 0)
- self._top_label.show()
+ self.append(self._top_label)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self.pack_start(separator, False, True, 0)
- separator.show()
+ self.append(separator)
self._bottom_label = Gtk.Label()
- self._bottom_label.set_line_wrap(True)
+ self._bottom_label.set_wrap(True)
self._bottom_label.set_justify(Gtk.Justification.LEFT)
self._bottom_label.props.xalign = 0
self._bottom_label.set_markup(
_('Software updates correct errors, eliminate security '
'vulnerabilities, and provide new features.'))
- self.pack_start(self._bottom_label, False, True, 0)
- self._bottom_label.show()
+ self.append(self._bottom_label)
self._update_box = None
self._progress_pane = None
@@ -82,7 +81,7 @@ def __init__(self, model, alerts):
updater.STATE_UPDATING):
self._switch_to_progress_pane()
self._progress_pane.set_message(_('Update in progress...'))
- self.connect('destroy', self.__destroy_cb)
+ self.connect('unrealize', self.__destroy_cb)
def __destroy_cb(self, widget):
self._model.disconnect(self._id_progresss)
@@ -92,10 +91,10 @@ def __destroy_cb(self, widget):
self._model.clean()
def _switch_to_update_box(self, updates):
- if self._update_box in self.get_children():
+ if self._update_box and self._update_box.get_parent() == self:
return
- if self._progress_pane in self.get_children():
+ if self._progress_pane and self._progress_pane.get_parent() == self:
self.remove(self._progress_pane)
self._progress_pane = None
@@ -108,11 +107,12 @@ def _switch_to_update_box(self, updates):
'clicked',
self.__install_button_clicked_cb)
- self.pack_start(self._update_box, expand=True, fill=True, padding=0)
- self._update_box.show()
+ self.append(self._update_box)
+ self._update_box.set_hexpand(True)
+ self._update_box.set_vexpand(True)
def _switch_to_progress_pane(self):
- if self._progress_pane in self.get_children():
+ if self._progress_pane and self._progress_pane.get_parent() == self:
return
if self._model.get_state() == updater.STATE_CHECKING:
@@ -121,7 +121,7 @@ def _switch_to_progress_pane(self):
top_message = _('Installing updates...')
self._top_label.set_markup('%s' % top_message)
- if self._update_box in self.get_children():
+ if self._update_box and self._update_box.get_parent() == self:
self.remove(self._update_box)
self._update_box = None
@@ -131,16 +131,15 @@ def _switch_to_progress_pane(self):
'clicked',
self.__cancel_button_clicked_cb)
- self.pack_start(
- self._progress_pane, expand=True, fill=False, padding=0)
- self._progress_pane.show()
+ self.append(self._progress_pane)
+ self._progress_pane.set_hexpand(True)
def _clear_center(self):
- if self._progress_pane in self.get_children():
+ if self._progress_pane and self._progress_pane.get_parent() == self:
self.remove(self._progress_pane)
self._progress_pane = None
- if self._update_box in self.get_children():
+ if self._update_box and self._update_box.get_parent() == self:
self.remove(self._update_box)
self._update_box = None
@@ -169,7 +168,7 @@ def __updates_available_cb(self, model, updates):
'You can install %s updates',
available_updates)
top_message = top_message % available_updates
- top_message = GObject.markup_escape_text(top_message)
+ top_message = GLib.markup_escape_text(top_message)
self._top_label.set_markup('%s' % top_message)
@@ -206,7 +205,7 @@ def __finished_cb(self, model, installed_updates, failed_updates,
top_message = ngettext('%s update was installed',
'%s updates were installed', num_installed)
top_message = top_message % num_installed
- top_message = GObject.markup_escape_text(top_message)
+ top_message = GLib.markup_escape_text(top_message)
self._top_label.set_markup('%s' % top_message)
self._clear_center()
@@ -221,28 +220,33 @@ class ProgressPane(Gtk.Box):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.set_spacing(style.DEFAULT_PADDING)
- self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_margin_start(style.DEFAULT_SPACING * 2)
+ self.set_margin_end(style.DEFAULT_SPACING * 2)
+ self.set_margin_top(style.DEFAULT_SPACING * 2)
+ self.set_margin_bottom(style.DEFAULT_SPACING * 2)
self._progress = Gtk.ProgressBar()
- self.pack_start(self._progress, True, True, 0)
- self._progress.show()
+ self.append(self._progress)
+ self._progress.set_hexpand(True)
+ self._progress.set_vexpand(True)
self._label = Gtk.Label()
- self._label.set_line_wrap(True)
+ self._label.set_wrap(True)
self._label.set_property('xalign', 0.5)
- self._label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_BUTTON_GREY.get_gdk_color())
- self.pack_start(self._label, True, True, 0)
- self._label.show()
- alignment_box = Gtk.Alignment.new(xalign=0.5, yalign=0.5,
- xscale=0, yscale=0)
- self.pack_start(alignment_box, True, True, 0)
- alignment_box.show()
+ self.append(self._label)
+ self._label.set_hexpand(True)
+ self._label.set_vexpand(True)
+
+ alignment_box = Gtk.Box()
+ alignment_box.set_halign(Gtk.Align.CENTER)
+ alignment_box.set_valign(Gtk.Align.CENTER)
+ self.append(alignment_box)
+ alignment_box.set_hexpand(True)
+ alignment_box.set_vexpand(True)
- self.cancel_button = Gtk.Button(stock=Gtk.STOCK_CANCEL)
- alignment_box.add(self.cancel_button)
- self.cancel_button.show()
+ self.cancel_button = Gtk.Button(label=_('Cancel'))
+ alignment_box.append(self.cancel_button)
def set_message(self, message):
self._label.set_text(message)
@@ -261,36 +265,35 @@ def __init__(self, updates):
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- self.pack_start(scrolled_window, True, True, 0)
- scrolled_window.show()
+ self.append(scrolled_window)
+ scrolled_window.set_hexpand(True)
+ scrolled_window.set_vexpand(True)
self._update_list = UpdateList(updates)
self._update_list.props.model.connect('row-changed',
self.__row_changed_cb)
- scrolled_window.add(self._update_list)
- self._update_list.show()
+ scrolled_window.set_child(self._update_list)
bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
bottom_box.set_spacing(style.DEFAULT_SPACING)
- self.pack_start(bottom_box, False, True, 0)
- bottom_box.show()
+ self.append(bottom_box)
self._size_label = Gtk.Label()
self._size_label.props.xalign = 0
self._size_label.set_justify(Gtk.Justification.LEFT)
- bottom_box.pack_start(self._size_label, True, True, 0)
- self._size_label.show()
+ bottom_box.append(self._size_label)
+ self._size_label.set_hexpand(True)
+ self._size_label.set_vexpand(True)
- self.refresh_button = Gtk.Button(stock=Gtk.STOCK_REFRESH)
- bottom_box.pack_start(self.refresh_button, False, True, 0)
- self.refresh_button.show()
+ self.refresh_button = Gtk.Button(label=_('Refresh'))
+ bottom_box.append(self.refresh_button)
- self.install_button = Gtk.Button(_('Install selected'))
- self.install_button.props.image = Icon(
- icon_name='emblem-downloads',
- pixel_size=style.SMALL_ICON_SIZE)
- bottom_box.pack_start(self.install_button, False, True, 0)
- self.install_button.show()
+ self.install_button = Gtk.Button()
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
+ box.append(Icon(icon_name='emblem-downloads', pixel_size=style.SMALL_ICON_SIZE))
+ box.append(Gtk.Label(label=_('Install selected')))
+ self.install_button.set_child(box)
+ bottom_box.append(self.install_button)
self._update_total_size_label()
@@ -326,7 +329,7 @@ class UpdateList(Gtk.TreeView):
def __init__(self, updates):
list_model = UpdateListModel(updates)
- Gtk.TreeView.__init__(self, list_model)
+ Gtk.TreeView.__init__(self, model=list_model)
self.set_reorderable(False)
self.set_enable_search(False)
@@ -335,7 +338,6 @@ def __init__(self, updates):
toggle_renderer = Gtk.CellRendererToggle()
toggle_renderer.props.activatable = True
toggle_renderer.props.xpad = style.DEFAULT_PADDING
- toggle_renderer.props.indicator_size = style.zoom(26)
toggle_renderer.connect('toggled', self.__toggled_cb)
toggle_column = Gtk.TreeViewColumn()
diff --git a/extensions/cpsection/webaccount/__init__.py b/extensions/cpsection/webaccount/__init__.py
index a5e249c0a1..222012ad3d 100644
--- a/extensions/cpsection/webaccount/__init__.py
+++ b/extensions/cpsection/webaccount/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender
+# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,12 +13,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
+
CLASS = 'WebServicesConfig'
ICON = 'module-webaccount'
TITLE = _('Web Services')
diff --git a/extensions/cpsection/webaccount/view.py b/extensions/cpsection/webaccount/view.py
index 130f3b8778..6af6749f52 100644
--- a/extensions/cpsection/webaccount/view.py
+++ b/extensions/cpsection/webaccount/view.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
+# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
from gi.repository import GLib
@@ -54,7 +53,6 @@ def __init__(self, model, alerts):
fill_color=style.COLOR_TRANSPARENT.get_svg())
grid.attach(icon, 0, 0, 1, 1)
- icon.show()
label = Gtk.Label()
label.set_justify(Gtk.Justification.CENTER)
@@ -65,23 +63,25 @@ def __init__(self, model, alerts):
_('No web services are installed.\n'
'Please visit %s for more details.' %
'http://wiki.sugarlabs.org/go/WebServices'))))
- label.show()
grid.attach(label, 0, 1, 1, 1)
- alignment = Gtk.Alignment.new(0.5, 0.5, 0.1, 0.1)
- alignment.add(grid)
- grid.show()
-
- self.add(alignment)
- alignment.show()
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.CENTER)
+ grid.set_hexpand(True)
+ grid.set_vexpand(True)
+
+ self.append(grid)
return
grid.set_row_spacing(style.DEFAULT_SPACING * 4)
grid.set_column_spacing(style.DEFAULT_SPACING * 4)
- grid.set_border_width(style.DEFAULT_SPACING * 2)
+ grid.set_margin_start(style.DEFAULT_SPACING * 2)
+ grid.set_margin_end(style.DEFAULT_SPACING * 2)
+ grid.set_margin_top(style.DEFAULT_SPACING * 2)
+ grid.set_margin_bottom(style.DEFAULT_SPACING * 2)
grid.set_column_homogeneous(True)
- width = Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE
+ width = 800 - 2 * style.GRID_CELL_SIZE
nx = int(width / (style.GRID_CELL_SIZE + style.DEFAULT_SPACING * 4))
self._service_config_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -91,49 +91,46 @@ def __init__(self, model, alerts):
for service in services:
service_grid = Gtk.Grid()
icon = CanvasIcon(icon_name=service.get_icon_name())
- icon.show()
- service_grid.attach(icon, x, y, 1, 1)
+ service_grid.attach(icon, 0, 0, 1, 1)
- icon.connect('activate', service.config_service_cb, None,
- self._service_config_box)
+ gesture = Gtk.GestureClick()
+ gesture.connect('pressed', self._on_service_clicked, service)
+ icon.add_controller(gesture)
label = Gtk.Label()
label.set_justify(Gtk.Justification.CENTER)
name = get_service_name(service)
label.set_markup(name)
- service_grid.attach(label, x, y + 1, 1, 1)
- label.show()
+ service_grid.attach(label, 0, 1, 1, 1)
grid.attach(service_grid, x, y, 1, 1)
- service_grid.show()
x += 1
if x == nx:
x = 0
y += 1
- alignment = Gtk.Alignment.new(0.5, 0, 0, 0)
- alignment.add(grid)
- grid.show()
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.START)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.pack_start(alignment, False, False, 0)
- alignment.show()
+ vbox.append(grid)
scrolled = Gtk.ScrolledWindow()
- vbox.pack_start(scrolled, True, True, 0)
+ vbox.append(scrolled)
+ scrolled.set_hexpand(True)
+ scrolled.set_vexpand(True)
- self.add(vbox)
+ self.append(vbox)
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- scrolled.show()
workspace = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- scrolled.add_with_viewport(workspace)
- workspace.show()
+ scrolled.set_child(workspace)
- workspace.add(self._service_config_box)
- workspace.show_all()
- vbox.show()
+ workspace.append(self._service_config_box)
def undo(self):
pass
+
+ def _on_service_clicked(self, gesture, n_press, x, y, service):
+ service.config_service_cb(gesture.get_widget(), None, self._service_config_box)
diff --git a/extensions/cpsection/webaccount/web_service.py b/extensions/cpsection/webaccount/web_service.py
index 998acb120d..3a56f18e09 100644
--- a/extensions/cpsection/webaccount/web_service.py
+++ b/extensions/cpsection/webaccount/web_service.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
+# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
class WebService():
def get_icon_name(self):
- raise "Not implemented"
+ raise NotImplementedError("Not implemented")
def config_service_cb(self, widget, event, container):
- raise "Not implemented"
+ raise NotImplementedError("Not implemented")
diff --git a/extensions/deviceicon/audio.py b/extensions/deviceicon/audio.py
index e1b7c12487..f53b74ecc8 100644
--- a/extensions/deviceicon/audio.py
+++ b/extensions/deviceicon/audio.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 Martin Dengler
+# Copyright (C) 2008 Martin Dengler
# Copyright (C) 2014 Emil Dudev
# Copyright (C) 2014 Walter Bender
# Copyright (C) 2014 Gonzalo Odiard
@@ -98,68 +98,62 @@ def __init__(self, text, icon_name, device):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self._device = device
- self._ok_icon = Icon(icon_name='dialog-ok')
- self._cancel_icon = Icon(icon_name='dialog-cancel')
+ self._button_icon = Icon(icon_name='dialog-cancel')
icon = Icon(pixel_size=style.SMALL_ICON_SIZE)
icon.props.icon_name = icon_name
icon.props.xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
style.COLOR_BUTTON_GREY.get_svg()))
- icon.show()
- label = Gtk.Label(text)
- label.show()
+ label = Gtk.Label(label=text)
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
grid.attach(icon, 0, 0, 1, 1)
grid.attach(label, 1, 0, 1, 1)
- grid.show()
- alignment = Gtk.Alignment()
- alignment.set(0.5, 0, 0, 0)
- alignment.add(grid)
- alignment.show()
-
- self.add(alignment)
+ alignment = Gtk.Box()
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.START)
+ alignment.append(grid)
+ self.append(alignment)
adjustment = Gtk.Adjustment(
value=device.props.level,
lower=0,
upper=100 + sound.VOLUME_STEP,
- step_incr=sound.VOLUME_STEP,
- page_incr=sound.VOLUME_STEP,
+ step_increment=sound.VOLUME_STEP,
+ page_increment=sound.VOLUME_STEP,
page_size=sound.VOLUME_STEP)
self._adjustment = adjustment
- hscale = Gtk.HScale()
- hscale.props.draw_value = False
+ hscale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
+ hscale.set_draw_value(False)
hscale.set_adjustment(adjustment)
hscale.set_digits(0)
hscale.set_size_request(style.GRID_CELL_SIZE * 4, -1)
- hscale.show()
button = Gtk.Button()
- button.props.relief = Gtk.ReliefStyle.NONE
- button.props.focus_on_click = False
+ button.set_has_frame(False)
+ button.set_focusable(False)
+ button.set_child(self._button_icon)
button.connect('clicked', self.__muted_clicked_cb)
- button.show()
self._button = button
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
grid.attach(hscale, 0, 0, 1, 1)
grid.attach(button, 1, 0, 1, 1)
- grid.show()
- alignment = Gtk.Alignment()
- alignment.set(0.5, 0, 0, 0)
- alignment.set_padding(0, 0, style.DEFAULT_SPACING,
- style.DEFAULT_SPACING)
- alignment.add(grid)
- alignment.show()
+ alignment = Gtk.Box()
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.START)
+ alignment.set_margin_bottom(style.DEFAULT_SPACING)
+ alignment.set_margin_start(style.DEFAULT_SPACING)
+ alignment.set_margin_end(style.DEFAULT_SPACING)
+ alignment.append(grid)
- self.add(alignment)
+ self.append(alignment)
self._adjustment_hid = \
self._adjustment.connect('value-changed',
@@ -172,9 +166,9 @@ def update_level(self):
def update_muted(self):
if self._device.props.muted:
- self._button.set_image(self._ok_icon)
+ self._button_icon.props.icon_name = 'dialog-ok'
else:
- self._button.set_image(self._cancel_icon)
+ self._button_icon.props.icon_name = 'dialog-cancel'
def __level_adjusted_cb(self, device, data=None):
value = self._adjustment.props.value
@@ -209,21 +203,17 @@ def __init__(self, primary_text, output_text, input_text, output_model,
self._capture_manager = AudioManagerWidget(input_text,
'media-audio-input',
input_model)
- self._capture_manager.show()
separator = PaletteMenuItemSeparator()
- separator.show()
self._speaker_manager = AudioManagerWidget(output_text,
'speaker-100',
output_model)
- self._speaker_manager.show()
self._box = PaletteMenuBox()
self._box.append_item(self._capture_manager, 0, 0)
self._box.append_item(separator, 0, 0)
self._box.append_item(self._speaker_manager, 0, 0)
- self._box.show()
self.set_content(self._box)
diff --git a/extensions/deviceicon/battery.py b/extensions/deviceicon/battery.py
index 32ee4f15fb..9bb469169d 100644
--- a/extensions/deviceicon/battery.py
+++ b/extensions/deviceicon/battery.py
@@ -38,7 +38,6 @@
from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.model.session import get_session_manager
-
_ICON_NAME = 'battery'
_STATUS_CHARGING = 0
@@ -87,10 +86,10 @@ def _update_info(self):
current_level = self._model.props.level
xo_color = self._color
badge_name = None
- self.show()
+ self.set_visible(True)
if not self._model.props.present:
- self.hide()
+ self.set_visible(False)
status = _STATUS_NOT_PRESENT
elif self._model.props.charging:
@@ -128,22 +127,22 @@ def __init__(self, primary_text):
self._progress_widget = PaletteMenuBox()
self.set_content(self._progress_widget)
- self._progress_widget.show()
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
self._progress_widget.append_item(inner_box, vertical_padding=0)
- inner_box.show()
self._progress_bar = Gtk.ProgressBar()
self._progress_bar.set_size_request(
style.zoom(style.GRID_CELL_SIZE * 4), -1)
- inner_box.pack_start(self._progress_bar, True, True, 0)
- self._progress_bar.show()
+ self._progress_bar.set_hexpand(True)
+ self._progress_bar.set_vexpand(True)
+ inner_box.append(self._progress_bar)
self._status_label = Gtk.Label()
- inner_box.pack_start(self._status_label, True, True, 0)
- self._status_label.show()
+ self._status_label.set_hexpand(True)
+ self._status_label.set_vexpand(True)
+ inner_box.append(self._status_label)
def set_info(self, percentage, seconds, status):
self._level = percentage
@@ -305,14 +304,11 @@ def __check_capacity(self):
def setup(tray):
- bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
- up_proxy = bus.get_object('org.freedesktop.UPower',
- '/org/freedesktop/UPower')
- upower = dbus.Interface(up_proxy, 'org.freedesktop.UPower')
-
- for device_path in upower.EnumerateDevices():
- device = bus.get_object('org.freedesktop.UPower', device_path)
- device_prop_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE)
- device_type = device_prop_iface.Get(_UP_DEVICE_IFACE, 'Type')
- if device_type == _UP_TYPE_BATTERY:
- tray.add_device(DeviceView(device_path))
+ try:
+ client = UPowerGlib.Client.new_full(None)
+ devices = client.get_devices()
+ for device in devices:
+ if device.kind == UPowerGlib.DeviceKind.BATTERY:
+ tray.add_device(DeviceView(device.get_object_path()))
+ except Exception as e:
+ logging.warning("Could not setup battery device: %s", e)
diff --git a/extensions/deviceicon/display.py b/extensions/deviceicon/display.py
index 55a13ac18e..0904a1a925 100644
--- a/extensions/deviceicon/display.py
+++ b/extensions/deviceicon/display.py
@@ -91,27 +91,25 @@ def __init__(self, text, icon_name):
icon.props.icon_name = icon_name
icon.props.xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
style.COLOR_BUTTON_GREY.get_svg()))
- icon.show()
- label = Gtk.Label(text)
- label.show()
+ label = Gtk.Label(label=text)
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
grid.attach(icon, 0, 0, 1, 1)
grid.attach(label, 1, 0, 1, 1)
- grid.show()
- alignment = Gtk.Alignment()
- alignment.set(0.5, 0, 0, 0)
- alignment.add(grid)
- alignment.show()
- self.add(alignment)
+ alignment = Gtk.Box()
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.START)
+ alignment.append(grid)
+ self.append(alignment)
- alignment = Gtk.Alignment()
- alignment.set(0.5, 0, 0, 0)
- alignment.set_padding(0, 0, style.DEFAULT_SPACING,
- style.DEFAULT_SPACING)
+ alignment = Gtk.Box()
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.START)
+ alignment.set_margin_start(style.DEFAULT_SPACING)
+ alignment.set_margin_end(style.DEFAULT_SPACING)
self._model = brightness.get_instance()
self._model_changed_hid = \
@@ -123,8 +121,8 @@ def __init__(self, text, icon_name):
value=self._model.get_brightness(),
lower=0,
upper=self._model.get_max_brightness() + 1,
- step_incr=self._model.get_step_amount(),
- page_incr=self._model.get_step_amount(),
+ step_increment=self._model.get_step_amount(),
+ page_increment=self._model.get_step_amount(),
page_size=self._model.get_step_amount())
self._adjustment = adjustment
@@ -132,23 +130,20 @@ def __init__(self, text, icon_name):
self._adjustment_hid = \
self._adjustment.connect('value-changed', self.__adjusted_cb)
- hscale = Gtk.HScale()
- hscale.props.draw_value = False
+ hscale = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
+ hscale.set_draw_value(False)
hscale.set_adjustment(adjustment)
hscale.set_digits(0)
hscale.set_size_request(style.GRID_CELL_SIZE * 4, -1)
- alignment.add(hscale)
- hscale.show()
+ alignment.append(hscale)
else:
self._progress_bar = Gtk.ProgressBar()
self._progress_bar.set_size_request(
style.zoom(style.GRID_CELL_SIZE * 4), -1)
- alignment.props.top_padding = style.DEFAULT_PADDING
- alignment.add(self._progress_bar)
- self._progress_bar.show()
+ alignment.set_margin_top(style.DEFAULT_PADDING)
+ alignment.append(self._progress_bar)
- alignment.show()
- self.add(alignment)
+ self.append(alignment)
def __brightness_changed_cb(self, model, value):
self.update(value)
@@ -187,10 +182,8 @@ def __init__(self):
self._screenshot = PaletteMenuItem(_('Take a screenshot'))
icon = Icon(icon_name='camera-external',
pixel_size=style.SMALL_ICON_SIZE)
- self._screenshot.set_image(icon)
- icon.show()
+ self._screenshot.set_icon_widget(icon)
self._screenshot.connect('activate', self.__screenshot_cb)
- self._screenshot.show()
self._box = PaletteMenuBox()
@@ -200,7 +193,6 @@ def __init__(self):
self._add_brightness_manager()
self._box.append_item(self._screenshot, 0, 0)
- self._box.show()
self.set_content(self._box)
self.connect('popup', self.__popup_cb)
@@ -208,10 +200,8 @@ def __init__(self):
def _add_brightness_manager(self):
self._brightness_manager = BrightnessManagerWidget(_('Brightness'),
'brightness-100')
- self._brightness_manager.show()
separator = PaletteMenuItemSeparator()
- separator.show()
self._box.append_item(self._brightness_manager, 0, 0)
self._box.append_item(separator, 0, 0)
@@ -222,14 +212,14 @@ def __popup_cb(self, palette):
def __screenshot_cb(self, palette):
frame_ = frame.get_view()
- frame_.hide()
+ frame_.set_visible(False)
GLib.idle_add(self.__take_screenshot_cb, frame_)
def __take_screenshot_cb(self, frame_):
if frame_.is_visible():
return True
take_screenshot()
- frame_.show()
+ frame_.set_visible(True)
return False
diff --git a/extensions/deviceicon/frame.py b/extensions/deviceicon/frame.py
index d5495d8c39..36a7e1e196 100644
--- a/extensions/deviceicon/frame.py
+++ b/extensions/deviceicon/frame.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, OLPC
+# Copyright (C) 2012, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,6 +16,8 @@
import logging
from gettext import gettext as _
+from gi.repository import Gtk
+
from sugar4 import profile
from sugar4.graphics.tray import TrayIcon
from sugar4.graphics.palette import Palette
@@ -46,7 +48,9 @@ def __init__(self):
TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
self._input_method = Maliit.InputMethod()
- self.connect('button-release-event', self.__button_release_event_cb)
+ click_controller = Gtk.GestureClick()
+ click_controller.connect('released', self.__button_release_event_cb)
+ self.add_controller(click_controller)
self.set_palette_invoker(FrameWidgetInvoker(self))
def create_palette(self):
@@ -54,10 +58,10 @@ def create_palette(self):
palette.set_group_id('frame')
return palette
- def __button_release_event_cb(self, widget, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
self._input_method.show()
frame = jarabe.frame.get_view()
- frame.hide()
+ frame.set_visible(False)
def setup(tray):
diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py
index 55ab523db4..cbe3c5e66d 100644
--- a/extensions/deviceicon/network.py
+++ b/extensions/deviceicon/network.py
@@ -43,7 +43,6 @@
from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.view.pulsingicon import PulsingIcon
-
IP_ADDRESS_TEXT_TEMPLATE = _('IP address: %s')
_GSM_STATE_NOT_READY = 0
@@ -87,38 +86,46 @@ def __init__(self, primary_text):
self._channel_label = Gtk.Label()
self._channel_label.props.xalign = 0.0
- self._channel_label.show()
self._ip_address_label = Gtk.Label()
self._ip_address_label.props.xalign = 0.0
- self._ip_address_label.show()
self._info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._disconnect_item = PaletteMenuItem(_('Disconnect'))
icon = Icon(pixel_size=style.SMALL_ICON_SIZE,
icon_name='media-eject')
- self._disconnect_item.set_image(icon)
+ self._disconnect_item.set_icon_widget(icon)
self._disconnect_item.connect('activate',
self.__disconnect_activate_cb)
- self._info.add(self._disconnect_item)
+ self._info.append(self._disconnect_item)
separator = PaletteMenuItemSeparator()
- self._info.pack_start(separator, True, True, 0)
+ self._info.append(separator)
def _padded(child, xalign=0, yalign=0.5):
- padder = Gtk.Alignment.new(xalign=xalign, yalign=yalign,
- xscale=1, yscale=0.33)
- padder.set_padding(style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING)
- padder.add(child)
+ padder = Gtk.Box()
+ padder.set_hexpand(True)
+ padder.set_vexpand(True)
+ padder.set_margin_top(style.DEFAULT_SPACING)
+ padder.set_margin_bottom(style.DEFAULT_SPACING)
+ padder.set_margin_start(style.DEFAULT_SPACING)
+ padder.set_margin_end(style.DEFAULT_SPACING)
+ child.set_hexpand(True)
+ if xalign == 0:
+ child.set_halign(Gtk.Align.START)
+ elif xalign == 0.5:
+ child.set_halign(Gtk.Align.CENTER)
+ if yalign == 0:
+ child.set_valign(Gtk.Align.START)
+ elif yalign == 0.5:
+ child.set_valign(Gtk.Align.CENTER)
+ padder.append(child)
return padder
- self._info.pack_start(_padded(self._channel_label), True, True, 0)
- self._info.pack_start(_padded(self._ip_address_label), True, True, 0)
- self._info.show_all()
+ self._info.append(_padded(self._channel_label))
+ self._info.append(_padded(self._ip_address_label))
+ self._info.set_visible(True)
def set_connecting(self):
self.props.secondary_text = _('Connecting...')
@@ -127,7 +134,7 @@ def _set_connected(self, iaddress):
self.set_content(self._info)
self.props.secondary_text = _('Connected')
self._set_ip_address(iaddress)
- self._disconnect_item.show()
+ self._disconnect_item.set_visible(True)
def set_connected_with_frequency(self, frequency, iaddress):
self._set_connected(iaddress)
@@ -140,7 +147,7 @@ def set_connected_with_channel(self, channel, iaddress):
def set_disconnected(self):
self.props.primary_text = _('No wireless connection')
self.props.secondary_text = ''
- self._disconnect_item.hide()
+ self._disconnect_item.set_visible(False)
self.set_content(None)
def __disconnect_activate_cb(self, menuitem):
@@ -174,25 +181,34 @@ def __init__(self):
self._speed_label = Gtk.Label()
self._speed_label.props.xalign = 0.0
- self._speed_label.show()
self._ip_address_label = Gtk.Label()
self._info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
def _padded(child, xalign=0, yalign=0.5):
- padder = Gtk.Alignment.new(xalign=xalign, yalign=yalign,
- xscale=1, yscale=0.33)
- padder.set_padding(style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING)
- padder.add(child)
+ padder = Gtk.Box()
+ padder.set_hexpand(True)
+ padder.set_vexpand(True)
+ padder.set_margin_top(style.DEFAULT_SPACING)
+ padder.set_margin_bottom(style.DEFAULT_SPACING)
+ padder.set_margin_start(style.DEFAULT_SPACING)
+ padder.set_margin_end(style.DEFAULT_SPACING)
+ child.set_hexpand(True)
+ if xalign == 0:
+ child.set_halign(Gtk.Align.START)
+ elif xalign == 0.5:
+ child.set_halign(Gtk.Align.CENTER)
+ if yalign == 0:
+ child.set_valign(Gtk.Align.START)
+ elif yalign == 0.5:
+ child.set_valign(Gtk.Align.CENTER)
+ padder.append(child)
return padder
- self._info.pack_start(_padded(self._speed_label), True, True, 0)
- self._info.pack_start(_padded(self._ip_address_label), True, True, 0)
- self._info.show_all()
+ self._info.append(_padded(self._speed_label))
+ self._info.append(_padded(self._ip_address_label))
+ self._info.set_visible(True)
self.set_content(self._info)
self.props.secondary_text = _('Connected')
@@ -231,58 +247,65 @@ def __init__(self):
self._toggle_state_item = PaletteMenuItem('')
self._toggle_state_item.connect('activate', self.__toggle_state_cb)
- self.info_box.pack_start(self._toggle_state_item, True, True, 0)
- self._toggle_state_item.show()
+ self.info_box.append(self._toggle_state_item)
self.error_title_label = Gtk.Label(label="")
- self.error_title_label.set_alignment(0, 0.5)
- self.error_title_label.set_line_wrap(True)
- self.info_box.pack_start(self.error_title_label, True, True, 0)
- self.error_description_label = Gtk.Label(label="")
- self.error_description_label.set_alignment(0, 0.5)
- self.error_description_label.set_line_wrap(True)
- self.info_box.pack_start(self.error_description_label, True, True, 0)
+ self.error_title_label.set_halign(Gtk.Align.START)
+ self.error_title_label.set_valign(Gtk.Align.CENTER)
+ self.error_title_label.set_wrap(True)
+ self.error_title_label.set_hexpand(True)
+ self.error_title_label.set_vexpand(True)
+ self.info_box.append(self.error_title_label)
+ self.error_description_label = Gtk.Label()
+ self.error_description_label.set_halign(Gtk.Align.START)
+ self.error_description_label.set_valign(Gtk.Align.CENTER)
+ self.error_description_label.set_wrap(True)
+ self.error_description_label.set_hexpand(True)
+ self.error_description_label.set_vexpand(True)
+ self.info_box.append(self.error_description_label)
self.connection_info_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
icon = Icon(icon_name='data-upload',
pixel_size=style.SMALL_ICON_SIZE)
- self.connection_info_box.pack_start(icon, True, True, 0)
- icon.show()
+ self.connection_info_box.append(icon)
self._data_label_up = Gtk.Label()
self._data_label_up.props.xalign = 0.0
label_alignment = self._add_widget_with_padding(self._data_label_up)
- self.connection_info_box.pack_start(label_alignment, True, True, 0)
- self._data_label_up.show()
- label_alignment.show()
+ self.connection_info_box.append(label_alignment)
icon = Icon(icon_name='data-download',
pixel_size=style.SMALL_ICON_SIZE)
- self.connection_info_box.pack_start(icon, True, True, 0)
- icon.show()
+ self.connection_info_box.append(icon)
self._data_label_down = Gtk.Label()
self._data_label_down.props.xalign = 0.0
label_alignment = self._add_widget_with_padding(self._data_label_down)
- self.connection_info_box.pack_start(label_alignment, True, True, 0)
- self._data_label_down.show()
- label_alignment.show()
-
- self.info_box.pack_start(self.connection_info_box, True, True, 0)
+ self.connection_info_box.append(label_alignment)
- self.info_box.show()
+ self.info_box.append(self.connection_info_box)
self.set_content(self.info_box)
self.update_state(_GSM_STATE_NOT_READY)
def _add_widget_with_padding(self, child, xalign=0, yalign=0.5):
- alignment = Gtk.Alignment.new(xalign=xalign, yalign=yalign,
- xscale=1, yscale=0.33)
- alignment.set_padding(style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING,
- style.DEFAULT_SPACING)
- alignment.add(child)
- return alignment
+ padder = Gtk.Box()
+ padder.set_hexpand(True)
+ padder.set_vexpand(True)
+ padder.set_margin_top(style.DEFAULT_SPACING)
+ padder.set_margin_bottom(style.DEFAULT_SPACING)
+ padder.set_margin_start(style.DEFAULT_SPACING)
+ padder.set_margin_end(style.DEFAULT_SPACING)
+ child.set_hexpand(True)
+ if xalign == 0:
+ child.set_halign(Gtk.Align.START)
+ elif xalign == 0.5:
+ child.set_halign(Gtk.Align.CENTER)
+ if yalign == 0:
+ child.set_valign(Gtk.Align.START)
+ elif yalign == 0.5:
+ child.set_valign(Gtk.Align.CENTER)
+ padder.append(child)
+ return padder
def update_state(self, state, reason=0):
self._current_state = state
@@ -299,14 +322,14 @@ def _update_label_and_text(self, reason=0):
self.props.secondary_text = _('Disconnected')
icon = Icon(icon_name='dialog-ok',
pixel_size=style.SMALL_ICON_SIZE)
- self._toggle_state_item.set_image(icon)
+ self._toggle_state_item.set_icon_widget(icon)
elif self._current_state == _GSM_STATE_CONNECTING:
self._toggle_state_item.set_label(_('Cancel'))
self.props.secondary_text = _('Connecting...')
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- self._toggle_state_item.set_image(icon)
+ self._toggle_state_item.set_icon_widget(icon)
elif self._current_state == _GSM_STATE_CONNECTED:
self._failed_connection = False
@@ -314,7 +337,7 @@ def _update_label_and_text(self, reason=0):
self.update_connection_time()
icon = Icon(icon_name='media-eject',
pixel_size=style.SMALL_ICON_SIZE)
- self._toggle_state_item.set_image(icon)
+ self._toggle_state_item.set_icon_widget(icon)
elif self._current_state == _GSM_STATE_FAILED:
message_error = self._get_error_by_nm_reason(reason)
@@ -327,8 +350,8 @@ def __toggle_state_cb(self, menuitem):
if self._current_state == _GSM_STATE_NOT_READY:
pass
elif self._current_state == _GSM_STATE_DISCONNECTED:
- self.error_title_label.hide()
- self.error_description_label.hide()
+ self.error_title_label.set_visible(False)
+ self.error_description_label.set_visible(False)
self.emit('gsm-connect')
elif self._current_state == _GSM_STATE_CONNECTING:
self.emit('gsm-disconnect')
@@ -345,11 +368,11 @@ def add_alert(self, error, suggestion):
title = _('Error: %s') % error
self.error_title_label.set_markup('%s' % title)
- self.error_title_label.show()
+ self.error_title_label.set_visible(True)
message = _('Suggestion: %s') % suggestion
self.error_description_label.set_text(message)
- self.error_description_label.show()
+ self.error_description_label.set_visible(True)
def update_connection_time(self, connection_time=None):
if connection_time is not None:
@@ -411,7 +434,6 @@ def __init__(self, device):
self._icon.props.base_color = self._inactive_color
self.set_icon_widget(self._icon)
- self._icon.show()
self.set_palette_invoker(FrameWidgetInvoker(self))
self.props.hide_tooltip_on_click = False
@@ -620,7 +642,6 @@ def __init__(self, device, state):
self._icon.props.base_color = self._inactive_color
self.set_icon_widget(self._icon)
- self._icon.show()
self.set_palette_invoker(FrameWidgetInvoker(self))
self.props.hide_tooltip_on_click = False
@@ -847,7 +868,7 @@ def _update_state(self, state, old_state, reason):
self._palette.update_connection_time()
self._palette.update_stats(0, 0)
if self._palette is not None:
- self._palette.connection_info_box.show()
+ self._palette.connection_info_box.set_visible(True)
elif state is network.NM_DEVICE_STATE_DISCONNECTED:
gsm_state = _GSM_STATE_DISCONNECTED
@@ -855,7 +876,7 @@ def _update_state(self, state, old_state, reason):
if self._connection_time_handler is not None:
GLib.source_remove(self._connection_time_handler)
if self._palette is not None:
- self._palette.connection_info_box.hide()
+ self._palette.connection_info_box.set_visible(False)
elif state in [network.NM_DEVICE_STATE_UNMANAGED,
network.NM_DEVICE_STATE_UNAVAILABLE,
diff --git a/extensions/deviceicon/speech.py b/extensions/deviceicon/speech.py
index 77fb8bd745..9cc259e30b 100644
--- a/extensions/deviceicon/speech.py
+++ b/extensions/deviceicon/speech.py
@@ -28,7 +28,6 @@
from jarabe.frame.frameinvoker import FrameWidgetInvoker
from jarabe.model import speech
-
_ICON_NAME = 'microphone'
@@ -62,18 +61,16 @@ def __init__(self, primary_text, manager):
box = PaletteMenuBox()
self.set_content(box)
- box.show()
self._play_icon = Icon(icon_name='player_play')
self._pause_icon = Icon(icon_name='player_pause')
self._play_pause_menu = PaletteMenuItem(
icon_name='player_play',
text_label=_('Say selected text'),
- accelerator='Shift+Alt+S')
- self._play_pause_menu.set_image(self._play_icon)
+ accelerator='s')
+ self._play_pause_menu.set_icon_widget(self._play_icon)
self._play_pause_menu.connect('activate', self.__play_activated_cb)
box.append_item(self._play_pause_menu)
- self._play_pause_menu.show()
self._stop_menu = PaletteMenuItem(icon_name='player_stop',
text_label=_('Stop playback'))
@@ -83,37 +80,32 @@ def __init__(self, primary_text, manager):
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
- pitch_label = Gtk.Label(_('Pitch'))
+ pitch_label = Gtk.Label(label=_('Pitch'))
box.append_item(pitch_label, vertical_padding=0)
- pitch_label.show()
self._adj_pitch = Gtk.Adjustment(value=self._manager.get_pitch(),
lower=self._manager.MIN_PITCH,
upper=self._manager.MAX_PITCH)
- hscale_pitch = Gtk.HScale()
+ hscale_pitch = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
hscale_pitch.set_adjustment(self._adj_pitch)
hscale_pitch.set_draw_value(False)
box.append_item(hscale_pitch, vertical_padding=0)
- hscale_pitch.show()
- rate_label = Gtk.Label(_('Rate'))
+ rate_label = Gtk.Label(label=_('Rate'))
box.append_item(rate_label, vertical_padding=0)
- rate_label.show()
self._adj_rate = Gtk.Adjustment(value=self._manager.get_rate(),
lower=self._manager.MIN_RATE,
upper=self._manager.MAX_RATE)
- hscale_rate = Gtk.HScale()
+ hscale_rate = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
hscale_rate.set_adjustment(self._adj_rate)
hscale_rate.set_draw_value(False)
box.append_item(hscale_rate, vertical_padding=0)
- hscale_rate.show()
self._adj_pitch.connect('value-changed', self.__adj_pitch_changed_cb)
self._adj_rate.connect('value-changed', self.__adj_rate_changed_cb)
@@ -137,17 +129,17 @@ def __stop_activated_cb(self, widget):
def _set_menu_state(self, manager, signal):
if signal == 'play':
- self._play_pause_menu.set_image(self._pause_icon)
+ self._play_pause_menu.set_icon_widget(self._pause_icon)
self._play_pause_menu.set_label(_('Pause playback'))
self._stop_menu.set_sensitive(True)
elif signal == 'pause':
- self._play_pause_menu.set_image(self._play_icon)
+ self._play_pause_menu.set_icon_widget(self._play_icon)
self._play_pause_menu.set_label(_('Say selected text'))
self._stop_menu.set_sensitive(True)
elif signal == 'stop':
- self._play_pause_menu.set_image(self._play_icon)
+ self._play_pause_menu.set_icon_widget(self._play_icon)
self._play_pause_menu.set_label(_('Say selected text'))
self._stop_menu.set_sensitive(False)
diff --git a/extensions/deviceicon/touchpad.py b/extensions/deviceicon/touchpad.py
index 0e2866553a..6bab675318 100644
--- a/extensions/deviceicon/touchpad.py
+++ b/extensions/deviceicon/touchpad.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010, Walter Bender, Sugar Labs
+# Copyright (C) 2010, Walter Bender, Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gettext import gettext as _
import os
@@ -52,7 +51,9 @@ def __init__(self):
TrayIcon.__init__(self, icon_name=icon_name, xo_color=color)
self.set_palette_invoker(FrameWidgetInvoker(self))
- self.connect('button-release-event', self.__button_release_event_cb)
+ click_controller = Gtk.GestureClick()
+ click_controller.connect('released', self.__button_release_event_cb)
+ self.add_controller(click_controller)
def create_palette(self):
""" Create a palette for this icon; called by the Sugar framework
@@ -61,11 +62,11 @@ def create_palette(self):
self.palette.set_group_id('frame')
return self.palette
- def __button_release_event_cb(self, widget, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
""" Callback for button release event; used to invoke touchpad-mode
change. """
self.palette.toggle_mode()
- return True
+ gesture.set_state(Gtk.EventSequenceState.CLAIMED)
class ResourcePalette(Palette):
@@ -81,10 +82,9 @@ def __init__(self, primary_text, icon):
self.set_content(vbox)
self._status_text = Gtk.Label()
- vbox.pack_start(self._status_text, True, True, style.DEFAULT_PADDING)
- self._status_text.show()
-
- vbox.show()
+ self._status_text.set_margin_top(style.DEFAULT_PADDING)
+ self._status_text.set_margin_bottom(style.DEFAULT_PADDING)
+ vbox.append(self._status_text)
self._mode = _read_touchpad_mode()
self._update()
diff --git a/extensions/deviceicon/volume.py b/extensions/deviceicon/volume.py
index 4aeeaf34da..3dd8a23aeb 100644
--- a/extensions/deviceicon/volume.py
+++ b/extensions/deviceicon/volume.py
@@ -32,7 +32,6 @@
from jarabe.view.palettes import VolumePalette
from jarabe.frame.frameinvoker import FrameWidgetInvoker
-
_icons = {}
volume_monitor = None
@@ -45,7 +44,7 @@ def __init__(self, mount):
self._mount = mount
self._icon_name = get_mount_icon_name(mount,
- Gtk.IconSize.LARGE_TOOLBAR)
+ Gtk.IconSize.LARGE)
# TODO: retrieve the colors from the owner of the device
color = get_mount_color(self._mount)
@@ -63,13 +62,10 @@ def create_palette(self):
icon = Icon(icon_name=self._icon_name,
pixel_size=style.SMALL_ICON_SIZE,
xo_color=color)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
menu_item.connect('activate', self.__show_contents_cb)
- palette.content_box.pack_start(menu_item, True, True, 0)
- palette.content_box.reorder_child(menu_item, 0)
- menu_item.show()
+ palette.content_box.prepend(menu_item)
return palette
@@ -113,7 +109,7 @@ def _mount(volume, tray):
if volume.get_mount() is None and volume.can_mount():
# TODO: pass None as mount_operation, or better, SugarMountOperation
flags = 0
- mount_operation = Gtk.MountOperation(parent=tray.get_toplevel())
+ mount_operation = Gtk.MountOperation(parent=tray.get_root())
cancellable = None
user_data = None
volume.mount(flags, mount_operation, cancellable, _mount_cb, user_data)
diff --git a/extensions/globalkey/brightness.py b/extensions/globalkey/brightness.py
index 7f0a82d069..2f8413a1b0 100644
--- a/extensions/globalkey/brightness.py
+++ b/extensions/globalkey/brightness.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015 Martin Abente Lahaye
+# Copyright (C) 2015 Martin Abente Lahaye
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/extensions/globalkey/screenshot.py b/extensions/globalkey/screenshot.py
index ee84dfad0b..e4621c87c4 100644
--- a/extensions/globalkey/screenshot.py
+++ b/extensions/globalkey/screenshot.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2009 Simon Schampijer, James Zaki
#
# This program is free software: you can redistribute it and/or modify
@@ -16,7 +16,7 @@
from jarabe.model.screenshot import take_screenshot
-BOUND_KEYS = ['1', 'Print']
+BOUND_KEYS = ['1', 'Print']
def handle_key_press(key):
diff --git a/extensions/globalkey/speech.py b/extensions/globalkey/speech.py
index a159db792b..d0866ac754 100644
--- a/extensions/globalkey/speech.py
+++ b/extensions/globalkey/speech.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 One Laptop Per Child
+# Copyright (C) 2011 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,7 +15,7 @@
from jarabe.model import speech
-BOUND_KEYS = ['s']
+BOUND_KEYS = ['s']
def handle_key_press(key):
diff --git a/extensions/globalkey/viewhelp.py b/extensions/globalkey/viewhelp.py
index f969d450eb..4d749b7355 100644
--- a/extensions/globalkey/viewhelp.py
+++ b/extensions/globalkey/viewhelp.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Kalpa Welivitigoda
+# Copyright (C) 2013 Kalpa Welivitigoda
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
from jarabe.view.viewhelp import setup_view_help
from jarabe.model import shell
-BOUND_KEYS = ['h']
+BOUND_KEYS = ['h']
def handle_key_press(key):
diff --git a/extensions/globalkey/viewsource.py b/extensions/globalkey/viewsource.py
index 236a51d571..2d3499a42a 100644
--- a/extensions/globalkey/viewsource.py
+++ b/extensions/globalkey/viewsource.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
#
# This program is free software: you can redistribute it and/or modify
@@ -17,8 +17,7 @@
from jarabe.view.viewsource import setup_view_source
from jarabe.model import shell
-
-BOUND_KEYS = ['XF86KbdLightOnOff', 'v']
+BOUND_KEYS = ['XF86KbdLightOnOff', 'v']
def handle_key_press(key):
diff --git a/src/jarabe/apisocket.py b/src/jarabe/apisocket.py
index d2afc4fd5a..8be4206065 100644
--- a/src/jarabe/apisocket.py
+++ b/src/jarabe/apisocket.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Daniel Narvaez
+# Copyright (C) 2013, Daniel Narvaez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -68,7 +68,7 @@ def get_xo_color(self, request):
self._client.send_result(request, [color_string.split(",")])
def close(self, request):
- self._activity.get_window().close(GLib.get_current_time())
+ self._activity.get_window().close()
self._client.send_result(request, [])
@@ -90,9 +90,9 @@ def _session_manager_shutdown_cb(self, event):
self._client.send_notification("activity.stop")
def show_object_chooser(self, request):
- chooser = ObjectChooser(self._activity)
+ chooser = ObjectChooser(self._activity.get_window())
chooser.connect('response', self._chooser_response_cb, request)
- chooser.show()
+ chooser.set_visible(True)
def _chooser_response_cb(self, chooser, response_id, request):
if response_id == Gtk.ResponseType.ACCEPT:
@@ -101,7 +101,7 @@ def _chooser_response_cb(self, chooser, response_id, request):
else:
self._client.send_result(request, [None])
- chooser.destroy()
+ chooser.close()
class DatastoreAPI(API):
diff --git a/src/jarabe/controlpanel/cmd.py b/src/jarabe/controlpanel/cmd.py
index 2d71dd7ec4..119152a8ae 100644
--- a/src/jarabe/controlpanel/cmd.py
+++ b/src/jarabe/controlpanel/cmd.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 One Laptop Per Child
+# Copyright (C) 2007, 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,7 +22,6 @@
from jarabe import config
-
_RESTART = 1
_same_option_warning = _('sugar-control-panel: WARNING, found more than one'
diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py
index 43d441a172..b94cd3a298 100644
--- a/src/jarabe/controlpanel/gui.py
+++ b/src/jarabe/controlpanel/gui.py
@@ -26,6 +26,7 @@
from sugar4.graphics.icon import Icon
from sugar4.graphics import style
from sugar4.graphics.alert import Alert, TimeoutAlert
+from sugar4.graphics.xocolor import XoColor
from jarabe.model.session import get_session_manager
from jarabe.controlpanel.toolbar import MainToolbar
@@ -36,22 +37,56 @@
_logger = logging.getLogger('ControlPanel')
+def _set_css_bg(widget, color):
+ if color == style.COLOR_BLACK:
+ widget.set_css_classes(['controlpanel-bg-black'])
+ elif color == style.COLOR_WHITE:
+ widget.set_css_classes(['controlpanel-bg-white'])
+ else:
+ widget.set_css_classes([])
+
+# Inject CSS for backgrounds
+provider = Gtk.CssProvider()
+css = b'''
+.controlpanel-bg-black { background-color: %s; }
+.controlpanel-bg-white { background-color: %s; }
+.sectionicon-transparent { background-color: transparent; }
+.sectionicon-label { color: white; }
+.toolbar-title { color: white; font-weight: bold; }
+''' % (style.COLOR_BLACK.get_html().encode('utf-8'),
+ style.COLOR_WHITE.get_html().encode('utf-8'))
+provider.load_from_data(css)
+Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
+ provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+
class ControlPanel(Gtk.Window):
__gtype_name__ = 'SugarControlPanel'
- def __init__(self, window_id):
+ def __init__(self, window_id=None):
self.parent_window_id = window_id
- Gtk.Window.__init__(self)
+ super().__init__()
self._calculate_max_columns()
- self.set_border_width(style.LINE_WIDTH)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
+ self.set_margin_start(style.LINE_WIDTH)
+ self.set_margin_end(style.LINE_WIDTH)
+ self.set_margin_top(style.LINE_WIDTH)
+ self.set_margin_bottom(style.LINE_WIDTH)
self.set_decorated(False)
self.set_resizable(False)
self.set_modal(True)
+ self.set_focusable(True)
+ self.add_css_class('controlpanel-bg-black')
- self.set_can_focus(True)
- self.connect('key-press-event', self.__key_press_event_cb)
+ # Set transient parent window
+ shell_model = shell.get_model()
+ if shell_model and shell_model._main_window:
+ self.set_transient_for(shell_model._main_window)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
self._toolbar = None
self._canvas = None
@@ -64,120 +99,173 @@ def __init__(self, window_id):
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(self._hbox, True, True, 0)
- self._hbox.show()
+ self._hbox.set_hexpand(True)
+ self._hbox.set_vexpand(True)
+ self._vbox.append(self._hbox)
+ self._hbox.set_visible(True)
- self._main_view = Gtk.EventBox()
- self._hbox.pack_start(self._main_view, True, True, 0)
- self._main_view.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_BLACK.get_gdk_color())
- self._main_view.show()
+ self._main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self._main_view.set_hexpand(True)
+ self._main_view.set_vexpand(True)
+ self._hbox.append(self._main_view)
+ _set_css_bg(self._main_view, style.COLOR_BLACK)
+ self._main_view.set_visible(True)
- self.add(self._vbox)
- self._vbox.show()
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
self.connect('realize', self.__realize_cb)
+ self.connect('destroy', self.__destroy_cb)
self._options = self._get_options()
self._current_option = None
self._setup_main()
self._setup_section()
self._show_main_view()
- Gdk.Screen.get_default().connect(
- 'size-changed', self.__size_changed_cb)
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ monitors.get_item(0).connect('notify::geometry', self.__size_changed_cb)
self._busy_count = 0
self._selected = []
def __realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- window = self.get_window()
- window.set_accept_focus(True)
- if self.parent_window_id:
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- parent = Gdk.Window.new(None, attributes, None)
- window.set_transient_for(parent)
-
# the modal windows counter is updated to disable hot keys - SL#4601
+ self._has_modal = True
shell.get_model().push_modal()
- def __size_changed_cb(self, event):
+ def __destroy_cb(self, widget):
+ if getattr(self, '_has_modal', False):
+ self._has_modal = False
+ shell.get_model().pop_modal()
+
+ def __size_changed_cb(self, event, pspec=None):
self._calculate_max_columns()
def busy(self):
if self._busy_count == 0:
- self._old_cursor = self.get_window().get_cursor()
- self._set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
+ self.set_cursor(Gdk.Cursor.new_from_name("wait"))
self._busy_count += 1
def unbusy(self):
self._busy_count -= 1
if self._busy_count == 0:
- self._set_cursor(self._old_cursor)
-
- def _set_cursor(self, cursor):
- self.get_window().set_cursor(cursor)
- Gdk.flush()
+ self.set_cursor(None)
def add_alert(self, alert):
- self._vbox.pack_start(alert, False, False, 0)
- self._vbox.reorder_child(alert, 2)
+ self._alert = alert
+ if self._separator:
+ self._vbox.insert_child_after(alert, self._separator)
+ else:
+ self._vbox.prepend(alert)
def remove_alert(self, alert):
self._vbox.remove(alert)
def grab_focus(self):
- # overwrite grab focus in order to grab focus on the view
- self._main_view.get_child().grab_focus()
+ if self._main_view.get_first_child():
+ self._main_view.get_first_child().grab_focus()
+
+ def _get_screen_width(self):
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ return monitors.get_item(0).get_geometry().width
+ return 1024
+
+ def _get_screen_height(self):
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ return monitors.get_item(0).get_geometry().height
+ return 768
def _calculate_max_columns(self):
- self._max_columns = int(0.285 * (float(Gdk.Screen.width()) /
- style.GRID_CELL_SIZE - 3))
+ sw = self._get_screen_width()
+ sh = self._get_screen_height()
+ self._max_columns = int(0.285 * (float(sw) / style.GRID_CELL_SIZE - 3))
offset = style.GRID_CELL_SIZE
- width = Gdk.Screen.width() - offset * 2
- height = Gdk.Screen.height() - offset * 2
+ width = sw - offset * 2
+ height = sh - offset * 2
self.set_size_request(width, height)
- if hasattr(self, '_table'):
- for child in self._table.get_children():
- child.destroy()
+ if hasattr(self, '_table') and self._table is not None:
+ while self._table.get_first_child() is not None:
+ self._table.remove(self._table.get_first_child())
self._setup_options()
def _set_canvas(self, canvas):
- if self._canvas in self._main_view:
+ if self._canvas == canvas:
+ return
+ if self._canvas and self._canvas.get_parent() == self._main_view:
self._main_view.remove(self._canvas)
if canvas:
- self._main_view.add(canvas)
+ if canvas.get_parent() is not None and canvas.get_parent() != self._main_view:
+ canvas.get_parent().remove(canvas)
+ if canvas.get_parent() != self._main_view:
+ canvas.set_hexpand(True)
+ canvas.set_vexpand(True)
+ self._main_view.append(canvas)
self._canvas = canvas
def _set_toolbar(self, toolbar):
+ if self._toolbar == toolbar:
+ return
if self._toolbar:
- self._vbox.remove(self._toolbar)
- self._vbox.pack_start(toolbar, False, False, 0)
- self._vbox.reorder_child(toolbar, 0)
+ if self._toolbar.get_parent() == self._vbox:
+ self._vbox.remove(self._toolbar)
+
self._toolbar = toolbar
+ if not toolbar:
+ if self._separator and self._separator.get_parent() == self._vbox:
+ self._vbox.remove(self._separator)
+ return
+
+ if toolbar.get_parent() is not None and toolbar.get_parent() != self._vbox:
+ toolbar.get_parent().remove(toolbar)
+
+ if toolbar.get_parent() != self._vbox:
+ self._vbox.insert_child_after(toolbar, None)
+
if not self._separator:
self._separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- self._vbox.pack_start(self._separator, False, False, 0)
- self._vbox.reorder_child(self._separator, 1)
- self._separator.show()
+ self._separator.set_visible(True)
+
+ if self._separator.get_parent() is not None:
+ self._separator.get_parent().remove(self._separator)
+
+ self._vbox.insert_child_after(self._separator, toolbar)
def _setup_main(self):
self._main_toolbar = MainToolbar()
- self._table = Gtk.Table()
- self._table.set_col_spacings(style.GRID_CELL_SIZE)
- self._table.set_row_spacings(style.GRID_CELL_SIZE)
- self._table.set_border_width(style.GRID_CELL_SIZE)
+ self._table = Gtk.Grid()
+ self._table.set_column_spacing(style.GRID_CELL_SIZE)
+ self._table.set_row_spacing(style.GRID_CELL_SIZE)
+ self._table.set_margin_start(style.GRID_CELL_SIZE)
+ self._table.set_margin_end(style.GRID_CELL_SIZE)
+ self._table.set_margin_top(style.GRID_CELL_SIZE)
+ self._table.set_margin_bottom(style.GRID_CELL_SIZE)
+ self._table.set_column_homogeneous(True)
+ self._table.set_row_homogeneous(True)
+ self._table.set_halign(Gtk.Align.CENTER)
+ self._table.set_valign(Gtk.Align.CENTER)
self._scrolledwindow = Gtk.ScrolledWindow()
- self._scrolledwindow.set_can_focus(False)
+ self._scrolledwindow.set_focusable(False)
self._scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
- self._scrolledwindow.add_with_viewport(self._table)
- child = self._scrolledwindow.get_child()
- child.modify_bg(
- Gtk.StateType.NORMAL, style.COLOR_BLACK.get_gdk_color())
+ self._scrolledwindow.set_hexpand(True)
+ self._scrolledwindow.set_vexpand(True)
+
+ self._viewport = Gtk.Viewport()
+ self._viewport.set_child(self._table)
+ self._scrolledwindow.set_child(self._viewport)
+
+ _set_css_bg(self._scrolledwindow, style.COLOR_BLACK)
self._setup_options()
self._main_toolbar.connect('stop-clicked',
@@ -205,18 +293,16 @@ def _setup_options(self):
title=self._options[option]['title'],
xo_color=self._options[option]['color'],
pixel_size=style.GRID_CELL_SIZE)
- sectionicon.connect('button-press-event',
+ sectionicon.connect('option-clicked',
self.__select_option_cb, option)
- sectionicon.show()
+ sectionicon.set_visible(True)
if option == 'aboutme':
- self._table.attach(sectionicon, 0, 1, 0, 1)
+ self._table.attach(sectionicon, 0, 0, 1, 1)
elif option == 'aboutcomputer':
- self._table.attach(sectionicon, 1, 2, 0, 1)
+ self._table.attach(sectionicon, 1, 0, 1, 1)
else:
- self._table.attach(sectionicon,
- column, column + 1,
- row, row + 1)
+ self._table.attach(sectionicon, column, row, 1, 1)
column += 1
if column == self._max_columns:
column = 0
@@ -226,31 +312,28 @@ def _setup_options(self):
def _show_main_view(self):
if self._section_view is not None:
- self._section_view.destroy()
self._section_view = None
self._set_toolbar(self._main_toolbar)
- self._main_toolbar.show()
+ self._main_toolbar.set_visible(True)
self._set_canvas(self._scrolledwindow)
- self._main_view.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_BLACK.get_gdk_color())
- self._table.show()
- self._scrolledwindow.show()
+ _set_css_bg(self._main_view, style.COLOR_BLACK)
+ self._table.set_visible(True)
+ self._scrolledwindow.set_visible(True)
entry = self._main_toolbar.get_entry()
entry.set_text('')
- entry.connect('icon-press', self.__clear_icon_pressed_cb)
self.grab_focus()
- def __key_press_event_cb(self, window, event):
- if event.keyval == Gdk.KEY_Return:
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ if keyval == Gdk.KEY_Return:
if len(self._selected) == 1:
self.show_section_view(self._selected[0])
return True
- if event.keyval == Gdk.KEY_Escape:
+ if keyval == Gdk.KEY_Escape:
if self._toolbar == self._main_toolbar:
self.__stop_clicked_cb(None)
- self.destroy()
+ self.close()
else:
self.__cancel_clicked_cb(None)
return True
@@ -264,7 +347,7 @@ def __key_press_event_cb(self, window, event):
entry.grab_focus()
return False
- def __clear_icon_pressed_cb(self, entry, icon_pos, event):
+ def __clear_icon_pressed_cb(self, entry, icon_pos, event=None):
self.grab_focus()
def _update(self, query):
@@ -291,12 +374,11 @@ def show_section_view(self, option):
self._set_toolbar(self._section_toolbar)
icon = self._section_toolbar.get_icon()
- icon.set_from_icon_name(self._options[option]['icon'],
- Gtk.IconSize.LARGE_TOOLBAR)
+ icon.set_icon_name(self._options[option]['icon'])
icon.props.xo_color = self._options[option]['color']
title = self._section_toolbar.get_title()
title.set_text(self._options[option]['title'])
- self._section_toolbar.show()
+ self._section_toolbar.set_visible(True)
self._current_option = option
@@ -312,7 +394,8 @@ def show_section_view(self, option):
self._options[option]['alerts'])
self._set_canvas(self._section_view)
- self._section_view.show()
+ self._section_view.set_visible(True)
+ _set_css_bg(self._main_view, style.COLOR_WHITE)
finally:
self.unbusy()
@@ -326,8 +409,7 @@ def show_section_view(self, option):
self.__create_restart_alert_cb)
self._section_view.connect('set-toolbar-sensitivity',
self.__set_toolbar_sensitivity_cb)
- self._main_view.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
+ _set_css_bg(self._main_view, style.COLOR_WHITE)
def set_section_view_auto_close(self):
"""Automatically close the control panel if there is "nothing to do"
@@ -354,7 +436,8 @@ def _get_options(self):
options[item]['view'] = view_class
options[item]['icon'] = getattr(mod, 'ICON', item)
options[item]['title'] = getattr(mod, 'TITLE', item)
- options[item]['color'] = getattr(mod, 'COLOR', None)
+ color = getattr(mod, 'COLOR', None)
+ options[item]['color'] = color
keywords = getattr(mod, 'KEYWORDS', [])
keywords.append(options[item]['title'].lower())
if item not in keywords:
@@ -398,20 +481,20 @@ def __create_restart_alert_cb(self, widget=None, event=None):
icon = Icon(icon_name='dialog-cancel')
alert.add_button(Gtk.ResponseType.CANCEL,
_('Cancel changes'), icon)
- icon.show()
+ icon.set_visible(True)
if self._section_view.props.is_deferrable:
icon = Icon(icon_name='dialog-ok')
alert.add_button(Gtk.ResponseType.ACCEPT, _('Later'), icon)
- icon.show()
+ icon.set_visible(True)
icon = Icon(icon_name='system-restart')
alert.add_button(Gtk.ResponseType.APPLY, _('Restart now'), icon)
- icon.show()
+ icon.set_visible(True)
self.add_alert(alert)
alert.connect('response', self.__response_cb)
- alert.show()
+ alert.set_visible(True)
def __response_cb(self, alert, response_id):
self.remove_alert(alert)
@@ -440,7 +523,7 @@ def __quit_timeout_cb(self):
alert.connect('response', self.__quit_accept_cb)
self.add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def __quit_accept_cb(self, alert, response_id):
self.remove_alert(alert)
@@ -452,18 +535,20 @@ def __quit_accept_cb(self, alert, response_id):
self.busy()
get_session_manager().shutdown_completed()
- def __select_option_cb(self, button, event, option):
+ def __select_option_cb(self, button, option):
self.show_section_view(option)
def __search_changed_cb(self, maintoolbar, query):
self._update(query)
def __stop_clicked_cb(self, widget):
- shell.get_model().pop_modal()
- self.destroy()
+ if getattr(self, '_has_modal', False):
+ self._has_modal = False
+ shell.get_model().pop_modal()
+ self.close()
def __close_request_cb(self, widget, event=None):
- self.destroy()
+ self.close()
def __valid_section_cb(self, section_view, pspec):
section_is_valid = section_view.props.is_valid
@@ -502,14 +587,17 @@ def undo(self):
except Exception as detail:
_logger.debug('Error undo option: %s', detail)
-
if hasattr(ControlPanel, 'set_css_name'):
ControlPanel.set_css_name('controlpanel')
-class _SectionIcon(Gtk.EventBox):
+class _SectionIcon(Gtk.Box):
__gtype_name__ = 'SugarSectionIcon'
+ __gsignals__ = {
+ 'option-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ }
+
__gproperties__ = {
'icon-name': (str, None, None, None, GObject.ParamFlags.READWRITE),
'pixel-size': (object, None, None, GObject.ParamFlags.READWRITE),
@@ -523,28 +611,36 @@ def __init__(self, **kwargs):
self._xo_color = None
self._title = 'No Title'
- Gtk.EventBox.__init__(self, **kwargs)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs)
+
+ # Ensure the box is pickable for clicks even in transparent areas
+ self.add_css_class('sectionicon-transparent')
+ self.set_halign(Gtk.Align.CENTER)
+ self.set_valign(Gtk.Align.CENTER)
+ self.set_hexpand(True)
+ self.set_vexpand(True)
- self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._icon = Icon(icon_name=self._icon_name,
pixel_size=self._pixel_size,
xo_color=self._xo_color)
- self._vbox.pack_start(self._icon, expand=False, fill=False, padding=0)
+ self.append(self._icon)
self._label = Gtk.Label(label=self._title)
- self._label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self._vbox.pack_start(self._label, expand=False, fill=False, padding=0)
-
- self._vbox.set_spacing(style.DEFAULT_SPACING)
- self.set_visible_window(False)
- self.set_app_paintable(True)
- self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
-
- self.add(self._vbox)
- self._vbox.show()
- self._label.show()
- self._icon.show()
+ self._label.add_css_class('sectionicon-label')
+
+ self.append(self._label)
+
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ click = Gtk.GestureClick()
+ click.connect('pressed', self._on_clicked)
+ self.add_controller(click)
+
+ self._label.set_visible(True)
+ self._icon.set_visible(True)
+
+ def _on_clicked(self, gesture, n_press, x, y):
+ self.emit('option-clicked')
def get_icon(self):
return self._icon
diff --git a/src/jarabe/controlpanel/inlinealert.py b/src/jarabe/controlpanel/inlinealert.py
index f756c4a95a..37dacae75a 100644
--- a/src/jarabe/controlpanel/inlinealert.py
+++ b/src/jarabe/controlpanel/inlinealert.py
@@ -13,10 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-from gi.repository import Gtk
from gi.repository import GObject
-
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import Pango
from sugar4.graphics import style
from sugar4.graphics.icon import Icon
@@ -51,20 +51,21 @@ def __init__(self, **kwargs):
self._msg_label = Gtk.Label()
self._msg_label.set_max_width_chars(150)
self._msg_label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
- self._msg_label.set_alignment(0, 0.5)
- self._msg_label.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
+ self._msg_label.set_halign(Gtk.Align.START)
+ self._msg_label.set_valign(Gtk.Align.CENTER)
+ self._msg_label.add_css_class('inline-alert-label')
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, **kwargs)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL, **kwargs)
self.set_spacing(style.DEFAULT_SPACING)
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
-
- self.pack_start(self._icon, False, False, 0)
- self.pack_start(self._msg_label, False, False, 0)
- self._msg_label.show()
- self._icon.show()
+
+ self.add_css_class('sugar-inline-alert')
+
+ self.append(self._icon)
+ self.append(self._msg_label)
+ self._msg_label.set_visible(True)
+ self._icon.set_visible(True)
+ self.set_visible(True)
def do_set_property(self, pspec, value):
if pspec.name == 'msg':
diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py
index 33552d7dd4..3f642a917d 100644
--- a/src/jarabe/controlpanel/sectionview.py
+++ b/src/jarabe/controlpanel/sectionview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, OLPC
+# Copyright (C) 2008, OLPC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import GObject
from gi.repository import Gtk
from gettext import gettext as _
@@ -40,7 +39,8 @@ class SectionView(Gtk.Box):
_APPLY_TIMEOUT = 1000
def __init__(self):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
+ self.add_css_class('cpanelsectionview')
self._is_valid = True
self._is_cancellable = True
self._is_deferrable = True
@@ -72,7 +72,3 @@ def do_get_property(self, pspec):
def undo(self):
"""Undo here the changes that have been made in this section."""
pass
-
-
-if hasattr(SectionView, 'set_css_name'):
- SectionView.set_css_name('cpanelsectionview')
diff --git a/src/jarabe/controlpanel/toolbar.py b/src/jarabe/controlpanel/toolbar.py
index c75604d354..b97a1ab348 100644
--- a/src/jarabe/controlpanel/toolbar.py
+++ b/src/jarabe/controlpanel/toolbar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 One Laptop Per Child
+# Copyright (C) 2007, 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,14 +21,13 @@
def _(msg):
return gettext.dgettext('sugar', msg)
-
from sugar4.graphics.icon import Icon
from sugar4.graphics.toolbutton import ToolButton
from sugar4.graphics import iconentry
from sugar4.graphics import style
-class MainToolbar(Gtk.Toolbar):
+class MainToolbar(Gtk.Box):
""" Main toolbar of the control panel
"""
__gtype_name__ = 'MainToolbar'
@@ -43,45 +42,40 @@ class MainToolbar(Gtk.Toolbar):
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
+ self.add_css_class('toolbar')
self._add_separator()
- tool_item = Gtk.ToolItem()
- self.insert(tool_item, -1)
- tool_item.show()
- self._search_entry = iconentry.IconEntry()
- self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
- 'entry-search')
- self._search_entry.add_clear_button()
- self._search_entry.set_width_chars(25)
+ tool_item = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self.append(tool_item)
+ tool_item.set_visible(True)
+ self._search_entry = Gtk.SearchEntry()
text = _('Search in %s') % _('Settings')
self._search_entry.set_placeholder_text(text)
- self._search_entry.connect('changed', self.__search_entry_changed_cb)
- tool_item.add(self._search_entry)
- self._search_entry.show()
+ self._search_entry.connect('search-changed', self.__search_entry_changed_cb)
+ tool_item.append(self._search_entry)
+ self._search_entry.set_visible(True)
self._add_separator(True)
self.stop = ToolButton(icon_name='dialog-cancel')
- self.stop.set_tooltip(_('Done'))
+ self.stop.set_tooltip_text(_('Done'))
self.stop.connect('clicked', self.__stop_clicked_cb)
- self.stop.show()
- self.insert(self.stop, -1)
- self.stop.show()
+ self.stop.set_visible(True)
+ self.append(self.stop)
def get_entry(self):
return self._search_entry
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ spacer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
if expand:
- separator.set_expand(True)
+ spacer.set_hexpand(True)
else:
- separator.set_size_request(style.DEFAULT_SPACING, -1)
- self.insert(separator, -1)
- separator.show()
+ spacer.set_size_request(style.DEFAULT_SPACING, -1)
+ self.append(spacer)
+ spacer.set_visible(True)
def __search_entry_changed_cb(self, search_entry):
self.emit('search-changed', search_entry.props.text)
@@ -90,7 +84,7 @@ def __stop_clicked_cb(self, button):
self.emit('stop-clicked')
-class SectionToolbar(Gtk.Toolbar):
+class SectionToolbar(Gtk.Box):
""" Toolbar of the sections of the control panel
"""
__gtype_name__ = 'SectionToolbar'
@@ -105,7 +99,8 @@ class SectionToolbar(Gtk.Toolbar):
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
+ self.add_css_class('toolbar')
self._add_separator()
@@ -115,21 +110,22 @@ def __init__(self):
self._add_separator()
self._title = Gtk.Label()
+ self._title.add_css_class('toolbar-title')
self._add_widget(self._title)
self._add_separator(True)
- self.cancel_button = ToolButton('dialog-cancel')
- self.cancel_button.set_tooltip(_('Cancel'))
+ self.cancel_button = ToolButton(icon_name='dialog-cancel')
+ self.cancel_button.set_tooltip_text(_('Cancel'))
self.cancel_button.connect('clicked', self.__cancel_button_clicked_cb)
- self.insert(self.cancel_button, -1)
- self.cancel_button.show()
+ self.append(self.cancel_button)
+ self.cancel_button.set_visible(True)
- self.accept_button = ToolButton('dialog-ok')
- self.accept_button.set_tooltip(_('Ok'))
+ self.accept_button = ToolButton(icon_name='dialog-ok')
+ self.accept_button.set_tooltip_text(_('Ok'))
self.accept_button.connect('clicked', self.__accept_button_clicked_cb)
- self.insert(self.accept_button, -1)
- self.accept_button.show()
+ self.append(self.accept_button)
+ self.accept_button.set_visible(True)
def get_icon(self):
return self._icon
@@ -138,24 +134,24 @@ def get_title(self):
return self._title
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ spacer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
if expand:
- separator.set_expand(True)
+ spacer.set_hexpand(True)
else:
- separator.set_size_request(style.DEFAULT_SPACING, -1)
- self.insert(separator, -1)
- separator.show()
+ spacer.set_size_request(style.DEFAULT_SPACING, -1)
+ self.append(spacer)
+ spacer.set_visible(True)
def _add_widget(self, widget, expand=False):
- tool_item = Gtk.ToolItem()
- tool_item.set_expand(expand)
+ tool_item = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ if expand:
+ tool_item.set_hexpand(True)
- tool_item.add(widget)
- widget.show()
+ tool_item.append(widget)
+ widget.set_visible(True)
- self.insert(tool_item, -1)
- tool_item.show()
+ self.append(tool_item)
+ tool_item.set_visible(True)
def __cancel_button_clicked_cb(self, widget, data=None):
self.emit('cancel-clicked')
diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py
index 18c87f0f79..dcb50c4d11 100644
--- a/src/jarabe/desktop/activitieslist.py
+++ b/src/jarabe/desktop/activitieslist.py
@@ -44,6 +44,15 @@
from jarabe.util.normalize import normalize_string
+def _get_children(box):
+ children = []
+ child = box.get_first_child()
+ while child:
+ children.append(child)
+ child = child.get_next_sibling()
+ return children
+
+
class ActivitiesTreeView(Gtk.TreeView):
__gtype_name__ = 'SugarActivitiesTreeView'
@@ -53,35 +62,34 @@ class ActivitiesTreeView(Gtk.TreeView):
}
def __init__(self):
- Gtk.TreeView.__init__(self)
+ super().__init__()
self.set_can_focus(False)
self._query = ''
self.set_headers_visible(False)
- self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
- Gdk.EventMask.TOUCH_MASK |
- Gdk.EventMask.BUTTON_RELEASE_MASK)
selection = self.get_selection()
- selection.set_mode(Gtk.SelectionMode.NONE)
+ selection.set_mode(Gtk.SelectionMode.SINGLE)
self._model = ListModel()
self._model.set_visible_func(self.__model_visible_cb)
self.set_model(self._model)
self._favorite_columns = []
+ self._favorite_cells = []
for i in range(desktop.get_number_of_views()):
- self.fav_column = Gtk.TreeViewColumn()
- self.cell_favorite = CellRendererFavorite(i)
- self.cell_favorite.connect('clicked', self.__favorite_clicked_cb)
- self.fav_column.pack_start(self.cell_favorite, True)
- self.fav_column.set_cell_data_func(self.cell_favorite,
- self.__favorite_set_data_cb)
- self.append_column(self.fav_column)
-
- self._favorite_columns.append(self.fav_column)
+ fav_column = Gtk.TreeViewColumn()
+ cell_favorite = CellRendererFavorite(i)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
+ fav_column.pack_start(cell_favorite, True)
+ fav_column.set_cell_data_func(cell_favorite,
+ self.__favorite_set_data_cb)
+ self.append_column(fav_column)
+ self._favorite_columns.append(fav_column)
+ self._favorite_cells.append(cell_favorite)
self.cell_icon = CellRendererActivityIcon()
+ self.cell_icon.connect('clicked', self.__activity_icon_clicked_cb)
column = Gtk.TreeViewColumn()
column.pack_start(self.cell_icon, True)
@@ -94,7 +102,6 @@ def __init__(self):
cell_text = Gtk.CellRendererText()
cell_text.props.ellipsize = style.ELLIPSIZE_MODE_DEFAULT
cell_text.props.ellipsize_set = True
-
column = Gtk.TreeViewColumn()
column.props.sizing = Gtk.TreeViewColumnSizing.GROW_ONLY
column.props.expand = True
@@ -139,23 +146,12 @@ def __init__(self):
self._invoker = TreeViewInvoker()
self._invoker.attach_treeview(self)
- self.button_press_handler = None
- self.button_reslease_handler = None
- self.icon_clicked_handler = None
- self.row_activated_handler = None
- if hasattr(self.props, 'activate_on_single_click'):
- # Gtk+ 3.8 and later
- self.props.activate_on_single_click = True
- self.row_activated_handler = self.connect('row-activated',
- self.__row_activated_cb)
- else:
- self.icon_clicked_handler = self.cell_icon.connect(
- 'clicked', self.__icon_clicked_cb)
- self.button_press_handler = self.connect(
- 'button-press-event', self.__button_press_cb)
- self.button_reslease_handler = self.connect(
- 'button-release-event', self.__button_release_cb)
- self._row_activated_armed_path = None
+ self.props.activate_on_single_click = True
+ self.row_activated_handler = self.connect('row-activated',
+ self.__row_activated_cb)
+
+ def __activity_icon_clicked_cb(self, cell, path):
+ self._start_activity(path)
def __favorite_set_data_cb(self, column, cell, model, tree_iter, data):
favorite = \
@@ -174,61 +170,25 @@ def __favorite_clicked_cb(self, cell, path):
not row[self._model.column_favorites[cell.favorite_view]],
cell.favorite_view)
- def __icon_clicked_cb(self, cell, path):
- """
- A click on activity icon cell is to start an activity.
- """
- logging.debug('__icon_clicked_cb')
- self._start_activity(path)
-
def __row_activated_cb(self, treeview, path, col):
"""
A click on cells other than the favorite toggle is to start an
- activity. Gtk+ 3.8 and later.
+ activity.
"""
logging.debug('__row_activated_cb')
- if col is not treeview.get_column(0):
+
+ columns = treeview.get_columns()
+ try:
+ col_idx = columns.index(col)
+ except ValueError:
+ col_idx = -1
+
+ num_favorite_views = desktop.get_number_of_views()
+ if 0 <= col_idx < num_favorite_views:
+ pass
+ else:
self._start_activity(path)
- def __button_to_path(self, event, event_type):
- if event.window != self.get_bin_window() or \
- event.button != 1 or \
- event.type != event_type:
- return None
-
- pos = self.get_path_at_pos(int(event.x), int(event.y))
- if pos is None:
- return None
-
- path, column, x_, y_ = pos
- if column == self._icon_column:
- return None
-
- if column in self._favorite_columns:
- return None
-
- return path
-
- def __button_press_cb(self, widget, event):
- logging.debug('__button_press_cb')
- path = self.__button_to_path(event, Gdk.EventType.BUTTON_PRESS)
- if path is None:
- return
-
- self._row_activated_armed_path = path
-
- def __button_release_cb(self, widget, event):
- logging.debug('__button_release_cb')
- path = self.__button_to_path(event, Gdk.EventType.BUTTON_RELEASE)
- if path is None:
- return
-
- if self._row_activated_armed_path != path:
- return
-
- self._start_activity(path)
- self._row_activated_armed_path = None
-
def _start_activity(self, path):
model = self.get_model()
row = model[path]
@@ -236,7 +196,11 @@ def _start_activity(self, path):
registry = bundleregistry.get_registry()
bundle = registry.get_bundle(row[self._model.column_bundle_id])
- misc.launch(bundle)
+ if bundle:
+ misc.launch(bundle)
+ else:
+ logging.warning('Bundle not found in registry: %s',
+ row[self._model.column_bundle_id])
def set_filter(self, query):
"""Set a new query and refilter the model, return the number
@@ -310,7 +274,8 @@ def connect_to_scroller(self, scrolled):
scrolled.connect('scroll-start', self._scroll_start_cb)
scrolled.connect('scroll-end', self._scroll_end_cb)
self.cell_icon.connect_to_scroller(scrolled)
- self.cell_favorite.connect_to_scroller(scrolled)
+ for cell in self._favorite_cells:
+ cell.connect_to_scroller(scrolled)
def _scroll_start_cb(self, event):
self._invoker.detach()
@@ -342,8 +307,10 @@ def __init__(self):
self._model = Gtk.ListStore()
self._model.set_column_types(column_types)
self._model_filter = self._model.filter_new()
- Gtk.TreeModelSort.__init__(self, model=self._model_filter)
+ super().__init__(model=self._model_filter)
self.set_sort_column_id(self.column_title, Gtk.SortType.ASCENDING)
+
+ self._registry_sids = []
GLib.idle_add(self.__connect_to_bundle_registry_cb)
@@ -351,9 +318,17 @@ def __connect_to_bundle_registry_cb(self):
registry = bundleregistry.get_registry()
for info in registry:
self._add_activity(info)
- registry.connect('bundle-added', self.__activity_added_cb)
- registry.connect('bundle-changed', self.__activity_changed_cb)
- registry.connect('bundle-removed', self.__activity_removed_cb)
+ self._registry_sids.append(registry.connect('bundle-added', self.__activity_added_cb))
+ self._registry_sids.append(registry.connect('bundle-changed', self.__activity_changed_cb))
+ self._registry_sids.append(registry.connect('bundle-removed', self.__activity_removed_cb))
+
+ def do_dispose(self):
+ registry = bundleregistry.get_registry()
+ for sid in self._registry_sids:
+ if registry.handler_is_connected(sid):
+ registry.disconnect(sid)
+ self._registry_sids = []
+ super().do_dispose()
def __activity_added_cb(self, activity_registry, activity_info):
self._add_activity(activity_info)
@@ -438,6 +413,8 @@ def __init__(self, favorite_view):
self.props.height = style.GRID_CELL_SIZE
self.props.size = style.SMALL_ICON_SIZE
self.props.icon_name = desktop.get_favorite_icons()[favorite_view]
+ self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg()
self.props.mode = Gtk.CellRendererMode.ACTIVATABLE
@@ -457,54 +434,48 @@ def __init__(self):
self.props.size = style.STANDARD_ICON_SIZE
self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
- self.props.mode = Gtk.CellRendererMode.ACTIVATABLE
+ self.props.mode = Gtk.CellRendererMode.INERT
prelit_color = profile.get_color()
self.props.prelit_stroke_color = prelit_color.get_stroke_color()
self.props.prelit_fill_color = prelit_color.get_fill_color()
-class ClearMessageBox(Gtk.EventBox):
+class ClearMessageBox(Gtk.Box):
def __init__(self, message, button_callback):
- Gtk.EventBox.__init__(self)
-
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
- alignment = Gtk.Alignment.new(0.5, 0.5, 0.1, 0.1)
- self.add(alignment)
- alignment.show()
+ self.set_name("clear-message-box")
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- alignment.add(box)
- box.show()
+ box.set_halign(Gtk.Align.CENTER)
+ box.set_valign(Gtk.Align.CENTER)
+ box.set_vexpand(True)
+ self.append(box)
icon = Icon(pixel_size=style.LARGE_ICON_SIZE,
icon_name='system-search',
stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
fill_color=style.COLOR_TRANSPARENT.get_svg())
- box.pack_start(icon, expand=True, fill=False, padding=0)
- icon.show()
+ box.append(icon)
label = Gtk.Label()
- color = style.COLOR_BUTTON_GREY.get_html()
+ color = style.COLOR_BLACK.get_html()
label.set_markup('%s' % (
color, GLib.markup_escape_text(message)))
- box.pack_start(label, expand=True, fill=False, padding=0)
- label.show()
+ box.append(label)
- button_box = Gtk.HButtonBox()
- button_box.set_layout(Gtk.ButtonBoxStyle.CENTER)
- box.pack_start(button_box, False, True, 0)
- button_box.show()
+ button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ button_box.set_halign(Gtk.Align.CENTER)
+ box.append(button_box)
button = Gtk.Button(label=_('Clear search'))
button.connect('clicked', button_callback)
- button.props.image = Icon(icon_name='dialog-cancel',
- pixel_size=style.SMALL_ICON_SIZE)
- button_box.pack_start(button, expand=True, fill=False, padding=0)
- button.show()
+ button_icon = Icon(icon_name='dialog-cancel',
+ pixel_size=style.SMALL_ICON_SIZE)
+ button.set_child(button_icon)
+ button_box.append(button)
class ActivitiesList(Gtk.Box):
@@ -517,22 +488,24 @@ class ActivitiesList(Gtk.Box):
def __init__(self):
logging.debug('STARTUP: Loading the activities list')
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._scrolled_window = Gtk.ScrolledWindow()
self._scrolled_window.set_can_focus(False)
self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
- self._scrolled_window.set_shadow_type(Gtk.ShadowType.NONE)
- self._scrolled_window.connect('key-press-event',
- self.__key_press_event_cb)
- self.pack_start(self._scrolled_window, True, True, 0)
- self._scrolled_window.show()
+ self._scrolled_window.set_has_frame(False)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_pressed_cb)
+ self._scrolled_window.add_controller(key_controller)
+
+ self._scrolled_window.set_vexpand(True)
+ self.append(self._scrolled_window)
self._tree_view = ActivitiesTreeView()
self._tree_view.connect('erase-activated', self.__erase_activated_cb)
- self._scrolled_window.add(self._tree_view)
- self._tree_view.show()
+ self._scrolled_window.set_child(self._tree_view)
scrolling_detector = ScrollingDetector(self._scrolled_window)
self._tree_view.connect_to_scroller(scrolling_detector)
@@ -555,15 +528,15 @@ def set_filter(self, query):
self._hide_clear_message()
def __desktop_view_icons_changed_cb(self, model):
- self._tree_view.destroy()
+ # Unparent the old tree view before replacing.
self._tree_view = ActivitiesTreeView()
self._tree_view.connect('erase-activated', self.__erase_activated_cb)
- self._scrolled_window.add(self._tree_view)
- self._tree_view.show()
+ self._scrolled_window.set_child(self._tree_view)
- def __key_press_event_cb(self, scrolled_window, event):
- keyname = Gdk.keyval_name(event.keyval)
+ def __key_pressed_cb(self, controller, keyval, keycode, state):
+ keyname = Gdk.keyval_name(keyval)
+ scrolled_window = controller.get_widget()
vadjustment = scrolled_window.props.vadjustment
if keyname == 'Up':
if vadjustment.props.value > vadjustment.props.lower:
@@ -580,38 +553,39 @@ def __key_press_event_cb(self, scrolled_window, event):
return True
def _show_clear_message(self):
- if self._clear_message_box in self.get_children():
+ children = _get_children(self)
+ if self._clear_message_box in children:
return
- if self._scrolled_window in self.get_children():
+ if self._scrolled_window in children:
self.remove(self._scrolled_window)
self._clear_message_box = ClearMessageBox(
message=_('No matching activities'),
button_callback=self.__clear_button_clicked_cb)
- self.pack_end(self._clear_message_box, True, True, 0)
- self._clear_message_box.show()
+ self._clear_message_box.set_vexpand(True)
+ self.append(self._clear_message_box)
def __clear_button_clicked_cb(self, button):
self.emit('clear-clicked')
def _hide_clear_message(self):
- if self._scrolled_window in self.get_children():
+ children = _get_children(self)
+ if self._scrolled_window in children:
return
- if self._clear_message_box in self.get_children():
+ if self._clear_message_box in children:
self.remove(self._clear_message_box)
self._clear_message_box = None
- self.pack_end(self._scrolled_window, True, True, 0)
- self._scrolled_window.show()
+ self._scrolled_window.set_vexpand(True)
+ self.append(self._scrolled_window)
def add_alert(self, alert):
if self._alert is not None:
self.remove_alert()
self._alert = alert
- self.pack_start(alert, False, True, 0)
- self.reorder_child(alert, 0)
+ self.prepend(alert)
def remove_alert(self):
self.remove(self._alert)
@@ -680,12 +654,10 @@ def __init__(self, activity_info):
self._favorite_icons.append(
Icon(icon_name=desktop.get_favorite_icons()[i],
pixel_size=style.SMALL_ICON_SIZE))
- self._favorite_items[i].set_image(self._favorite_icons[i])
- self._favorite_icons[i].show()
+ self._favorite_items[i].set_icon_widget(self._favorite_icons[i])
self._favorite_items[i].connect(
'activate', self.__change_favorite_activate_cb, i)
self.menu_box.append_item(self._favorite_items[i])
- self._favorite_items[i].show()
if activity_info.is_user_activity():
self._add_erase_option(registry, activity_info)
@@ -698,23 +670,23 @@ def __init__(self, activity_info):
self.__activity_changed_cb, i))
self._update_favorite_item(i)
- self.menu_box.connect('destroy', self.__destroy_cb)
+ def do_dispose(self):
+ registry = bundleregistry.get_registry()
+ for sid in self._activity_changed_sid:
+ if registry.handler_is_connected(sid):
+ registry.disconnect(sid)
+ self._activity_changed_sid = []
+ super().do_dispose()
def _add_erase_option(self, registry, activity_info):
menu_item = PaletteMenuItem(_('Erase'), 'list-remove')
menu_item.connect('activate', self.__erase_activate_cb)
self.menu_box.append_item(menu_item)
- menu_item.show()
if not os.access(activity_info.get_path(), os.W_OK) or \
registry.is_activity_protected(self._bundle_id):
menu_item.props.sensitive = False
- def __destroy_cb(self, palette):
- registry = bundleregistry.get_registry()
- for i in range(desktop.get_number_of_views()):
- registry.disconnect(self._activity_changed_sid[i])
-
def _update_favorite_item(self, favorite_view):
if self._favorites[favorite_view]:
self._favorite_items[favorite_view].set_label(_('Remove favorite'))
diff --git a/src/jarabe/desktop/activitychooser.py b/src/jarabe/desktop/activitychooser.py
index 3cebce1960..c6c6ec527d 100644
--- a/src/jarabe/desktop/activitychooser.py
+++ b/src/jarabe/desktop/activitychooser.py
@@ -32,29 +32,26 @@
from jarabe.util.normalize import normalize_string
-class TitleBox(Gtk.Toolbar):
+class TitleBox(Gtk.Box):
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self.close_button = ToolButton(icon_name='dialog-cancel')
self.close_button.set_tooltip(_('Close'))
- self.insert(self.close_button, -1)
- self.close_button.show()
+ self.append(self.close_button)
+ self.close_button.set_visible(True)
self._label = Gtk.Label()
- self._label.set_alignment(0, 0.5)
+ self._label.set_halign(Gtk.Align.START)
+ self._label.set_valign(Gtk.Align.CENTER)
+ self._label.set_hexpand(True)
- tool_item = Gtk.ToolItem()
- tool_item.set_expand(True)
- tool_item.add(self._label)
- self._label.show()
- self.insert(tool_item, 0)
- tool_item.show()
+ self.append(self._label)
+ self._label.set_visible(True)
def set_title(self, title):
self._label.set_markup('%s' % title)
- self._label.show()
-
+ self._label.set_visible(True)
_AUTOSEARCH_TIMEOUT = 1000
@@ -70,18 +67,40 @@ class ActivityChooser(Gtk.Window):
}
def __init__(self):
- Gtk.Window.__init__(self)
+ super().__init__()
self.set_decorated(False)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
- self.set_border_width(style.LINE_WIDTH)
self.set_resizable(False)
self.set_modal(True)
- self.set_can_focus(True)
+ self.set_focusable(True)
+
+ # Set transient parent window
+ from jarabe.model import shell as _shell
+ shell_model = _shell.get_model()
+ if shell_model and shell_model._main_window:
+ self.set_transient_for(shell_model._main_window)
+
+ display = Gdk.Display.get_default()
+ width = 1024 - style.GRID_CELL_SIZE * 2
+ height = 768 - style.GRID_CELL_SIZE * 2
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geo = monitors.get_item(0).get_geometry()
+ width = geo.width - style.GRID_CELL_SIZE * 2
+ height = geo.height - style.GRID_CELL_SIZE * 2
+
+ self.set_default_size(
+ width * 3 / 4,
+ height * 2 / 3)
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self.add(self._vbox)
- self._vbox.show()
+ self._vbox.set_margin_start(style.LINE_WIDTH)
+ self._vbox.set_margin_end(style.LINE_WIDTH)
+ self._vbox.set_margin_top(style.LINE_WIDTH)
+ self._vbox.set_margin_bottom(style.LINE_WIDTH)
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
self._title_box = TitleBox()
self._title_box.close_button.connect(
@@ -89,33 +108,40 @@ def __init__(self):
self.__close_button_clicked_cb)
self._title_box.set_size_request(-1, style.GRID_CELL_SIZE)
- self._vbox.pack_start(self._title_box, False, True, 0)
- self._title_box.show()
+ self._vbox.append(self._title_box)
+ self._title_box.set_visible(True)
- self.set_size_request(
- (Gdk.Screen.height() - style.GRID_CELL_SIZE * 3) * 3 / 4,
- (Gdk.Screen.height() - style.GRID_CELL_SIZE * 2) * 2 / 3)
- self.connect('key-press-event', self.__key_press_event_cb)
+ key_controller = Gtk.EventControllerKey()
+ key_controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
+
self.connect('realize', self.__realize_cb)
self._list_view = ActivitiesList()
self.search_bar = SearchBar()
- self._vbox.pack_start(self.search_bar, False, False, 0)
+ self._vbox.append(self.search_bar)
self.search_bar.connect('query-changed',
self.__toolbar_query_changed_cb)
- self.search_bar.search_entry.connect('key-press-event',
- self.__key_press_event_cb)
+
+ search_key_controller = Gtk.EventControllerKey()
+ search_key_controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
+ search_key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.search_bar.search_entry.add_controller(search_key_controller)
self.search_bar.search_entry.grab_focus()
+
self._scrolled_window = Gtk.ScrolledWindow()
self._scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
+ self._scrolled_window.set_vexpand(True)
+ self._scrolled_window.set_hexpand(True)
- self._scrolled_window.add(self._list_view)
+ self._scrolled_window.set_child(self._list_view)
- self._vbox.pack_start(self._scrolled_window, True, True, 0)
+ self._vbox.append(self._scrolled_window)
- self._list_view.show()
+ self._list_view.set_visible(True)
self._list_view.connect('clear-clicked',
self.__activitylist_clear_clicked_cb,
self.search_bar)
@@ -126,39 +152,22 @@ def __init__(self):
self.tree_view.fav_column.set_visible(False)
self.tree_view.version_column.set_visible(False)
+ # Disconnect default row-activated handler to prevent double launch
if self.tree_view.row_activated_handler:
self.tree_view.disconnect(self.tree_view.row_activated_handler)
- if self.tree_view.button_press_handler:
- self.tree_view.disconnect(self.tree_view.button_press_handler)
- if self.tree_view.button_reslease_handler:
- self.tree_view.disconnect(self.tree_view.button_reslease_handler)
- if self.tree_view.icon_clicked_handler:
- self.tree_view.disconnect(self.tree_view.icon_clicked_handler)
-
- if hasattr(self.tree_view.props, 'activate_on_single_click'):
- # Gtk+ 3.8 and later
- self.tree_view.props.activate_on_single_click = True
- self.tree_view.connect('row-activated', self.__row_activated_cb)
- else:
- self.tree_view.cell_icon.connect('clicked',
- self.__icon_clicked_cb)
- self.tree_view.connect('button-press-event',
- self.__button_press_cb)
- self.tree_view.connect('button-release-event',
- self.__button_release_cb)
- self._row_activated_armed_path = None
+ self.tree_view.row_activated_handler = None
- self.show()
+ self.tree_view.props.activate_on_single_click = True
+ self.tree_view.connect('row-activated', self.__row_activated_cb)
+
+ self.set_visible(True)
def __close_button_clicked_cb(self, button):
- self.destroy()
+ shell.get_model().pop_modal()
+ self.close()
def __realize_cb(self, widget):
shell.get_model().push_modal()
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- window = self.get_window()
- window.set_accept_focus(True)
- shell.get_model().push_modal()
def __toolbar_query_changed_cb(self, toolbar, query):
self._query = normalize_string(query.decode('utf-8'))
@@ -167,32 +176,38 @@ def __toolbar_query_changed_cb(self, toolbar, query):
toolbar.search_entry._icon_selected = \
self._list_view.get_activities_selected()
- # verify if one off the selected names is a perfect match
- # this is needed by th case of activities with names contained
+ # verify if one of the selected names is a perfect match
+ # this is needed by the case of activities with names contained
# in other activities like 'Paint' and 'MusicPainter'
+ activity = None
for activity in self._list_view.get_activities_selected():
if activity['name'].upper() == query.upper():
toolbar.search_entry._icon_selected = [activity]
break
# Don't change the selection if the entry has been autocompleted
- if len(toolbar.search_entry._icon_selected) == 1 \
- and not toolbar.search_entry.get_text() == activity['name']:
- pos = toolbar.search_entry.get_position()
- toolbar.search_entry.set_text(
- toolbar.search_entry._icon_selected[0]['name'])
- toolbar.search_entry.select_region(pos, -1)
-
- def __key_press_event_cb(self, widget, event):
- keyname = Gdk.keyval_name(event.keyval)
+ # if activity is not None and len(toolbar.search_entry._icon_selected) == 1 \
+ # and not toolbar.search_entry.get_text() == activity['name']:
+ # pos = toolbar.search_entry.get_position()
+ # toolbar.search_entry.set_text(
+ # toolbar.search_entry._icon_selected[0]['name'])
+ # toolbar.search_entry.select_region(pos, -1)
+
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ keyname = Gdk.keyval_name(keyval)
if keyname == 'Escape':
- self.destroy()
+ shell.get_model().pop_modal()
+ self.close()
+ return True
- if not self.search_bar.search_entry.has_focus():
- self.search_bar.search_entry.grab_focus()
+ search_entry = self.search_bar.search_entry
+ has_focus = search_entry.has_focus() or search_entry.get_focus_child() is not None
+ if not has_focus:
+ search_entry.grab_focus()
+ widget = controller.get_widget()
if widget == self.search_bar.search_entry:
- if event.keyval == Gdk.KEY_Return:
+ if keyval == Gdk.KEY_Return:
model = self.tree_view.get_model()
if len(model) > 1:
return True
@@ -202,8 +217,10 @@ def __key_press_event_cb(self, widget, event):
activity_id = activityfactory.create_activity_id()
self.emit('activity-selected', bundle_id, activity_id)
- self.destroy()
+ shell.get_model().pop_modal()
+ self.close()
return True
+ return False
def __activitylist_clear_clicked_cb(self, list_view, toolbar):
toolbar.clear_query()
@@ -215,35 +232,8 @@ def _got_row_tree_view(self, row):
bundle_id = row[self.tree_view._model.column_bundle_id]
activity_id = activityfactory.create_activity_id()
self.emit('activity-selected', bundle_id, activity_id)
- self.destroy()
-
- def __button_press_cb(self, widget, event):
- path = self.tree_view.__button_to_path(event,
- Gdk.EventType.BUTTON_PRESS)
- if path is None:
- return
-
- self._row_activated_armed_path = path
-
- def __button_release_cb(self, widget, event):
- path = self.tree_view.__button_to_path(event,
- Gdk.EventType.BUTTON_PRESS)
- if path is None:
- return
-
- if self._row_activated_armed_path != path:
- return
-
- model = self.tree_view.get_model()
- row = model[path]
- self._got_row_tree_view(row)
- self._row_activated_armed_path = None
-
- def __icon_clicked_cb(self, tree_view, path):
- model = tree_view.get_model()
- row = model[path]
- self._got_row_tree_view(row)
- return True
+ shell.get_model().pop_modal()
+ self.close()
def __row_activated_cb(self, treeview, path, col):
if col is not treeview.get_column(0):
@@ -253,7 +243,7 @@ def __row_activated_cb(self, treeview, path, col):
return True
-class SearchBar(Gtk.Toolbar):
+class SearchBar(Gtk.Box):
'''
New Toolbar below the Titlebox of sugar3.graphics PopWindow.
This toolbar contains textentry for search.
@@ -267,42 +257,34 @@ class SearchBar(Gtk.Toolbar):
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self._query = None
self._autosearch_timer = None
- # self.set_border_width(10)
- tool_item = Gtk.ToolItem()
- self.insert(tool_item, -1)
- tool_item.set_expand(True)
- tool_item.show()
-
- self.search_entry = iconentry.IconEntry()
- self.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
- 'entry-search')
- self.search_entry.set_can_focus(True)
- self.search_entry.add_clear_button()
+
+ self.search_entry = Gtk.SearchEntry()
+ self.search_entry.set_focusable(True)
self.search_entry.set_width_chars(20)
self.search_entry.connect('activate', self._entry_activated_cb)
- self.search_entry.connect('changed', self._entry_changed_cb)
+ self.search_entry.connect('search-changed', self._entry_changed_cb)
+ self.search_entry.set_hexpand(True)
- tool_item.add(self.search_entry)
- self.search_entry.show()
+ self.append(self.search_entry)
+ self.search_entry.set_visible(True)
self._add_separator()
def clear_query(self):
self.search_entry.props.text = ''
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ separator = Gtk.Box()
if expand:
- separator.set_expand(True)
+ separator.set_hexpand(True)
else:
separator.set_size_request(style.GRID_CELL_SIZE,
style.GRID_CELL_SIZE)
- self.insert(separator, -1)
- separator.show()
+ self.append(separator)
+ separator.set_visible(True)
def _entry_activated_cb(self, entry):
if self._autosearch_timer:
@@ -315,7 +297,7 @@ def _entry_activated_cb(self, entry):
def _entry_changed_cb(self, entry):
if not entry.props.text:
- entry.activate()
+ entry.emit('activate')
return
if self._autosearch_timer:
@@ -326,5 +308,5 @@ def _entry_changed_cb(self, entry):
def _autosearch_timer_cb(self):
logging.debug('_autosearch_timer_cb')
self._autosearch_timer = None
- self.search_entry.activate()
+ self.search_entry.emit('activate')
return False
diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py
index 3b4289bca5..835f6461eb 100644
--- a/src/jarabe/desktop/favoriteslayout.py
+++ b/src/jarabe/desktop/favoriteslayout.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2010 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
@@ -19,20 +19,51 @@
import hashlib
from gettext import gettext as _
+from gi.repository import Gtk
from gi.repository import Gdk
+import gi
+gi.require_version('Gsk', '4.0')
+gi.require_version('Graphene', '1.0')
+from gi.repository import Gsk, Graphene
from sugar4.graphics import style
from jarabe.model import bundleregistry
from jarabe.desktop.grid import Grid
-
_logger = logging.getLogger('FavoritesLayout')
_CELL_SIZE = 4
_BASE_SCALE = 1000
+class _SizeRequest:
+ def __init__(self, w, h):
+ self.width = w
+ self.height = h
+
+
+def get_child_size_request(child):
+ min_w, nat_w, _, _ = child.measure(Gtk.Orientation.HORIZONTAL, -1)
+ min_h, nat_h, _, _ = child.measure(Gtk.Orientation.VERTICAL, -1)
+ return _SizeRequest(nat_w, nat_h)
+
+
+def allocate_child(child, x, y, width, height):
+ transform = Gsk.Transform().translate(Graphene.Point().init(x, y))
+ child.allocate(width, height, -1, transform)
+
+
+def _get_screen_height():
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ monitor = monitors.get_item(0)
+ return monitor.get_geometry().height
+ return 768
+
+
class Layout(object):
def __init__(self):
@@ -56,6 +87,7 @@ def setup(self, allocation, owner_icon, activity_icon=None):
if self._grid is not None:
if self._width == allocation.width and \
self._height == allocation.height:
+ self._allocate_owner_icon(allocation, owner_icon, activity_icon)
return
self._width = allocation.width
self._height = allocation.height
@@ -68,7 +100,7 @@ def setup(self, allocation, owner_icon, activity_icon=None):
def _allocate_owner_icon(self, allocation, owner_icon, activity_icon):
# add owner icon to the grid, precisely centered on the screen
# if not None, add an activity icon directly below the owner icon
- owner_request = owner_icon.size_request()
+ owner_request = get_child_size_request(owner_icon)
owner_width, owner_height = owner_request.width, owner_request.height
height = allocation.height + allocation.y
width = allocation.width
@@ -87,40 +119,44 @@ def _allocate_owner_icon(self, allocation, owner_icon, activity_icon):
# calculate x coordinate and create allocation
owner_icon_allocation = Gdk.Rectangle()
- owner_icon_allocation.x = (width - owner_width) / 2
- owner_icon_allocation.y = allocation.y + y
+ owner_icon_allocation.x = int((width - owner_width) / 2)
+ owner_icon_allocation.y = int(allocation.y + y)
owner_icon_allocation.width = owner_width
owner_icon_allocation.height = owner_height
- owner_icon.size_allocate(owner_icon_allocation)
+
+ allocate_child(owner_icon, owner_icon_allocation.x, owner_icon_allocation.y, owner_icon_allocation.width, owner_icon_allocation.height)
# Determine grid coordinates and add to grid
- owner_grid_width, owner_grid_height = \
- self._get_child_grid_size(owner_icon)
- grid_x = int(owner_icon_allocation.x / float(_CELL_SIZE))
- grid_y = int(owner_icon_allocation.y / float(_CELL_SIZE))
- self._grid.add(owner_icon, owner_grid_width, owner_grid_height,
- grid_x, grid_y, locked=True)
+ if not self._grid.is_in_grid(owner_icon):
+ owner_grid_width, owner_grid_height = \
+ self._get_child_grid_size(owner_icon)
+ grid_x = int(owner_icon_allocation.x / float(_CELL_SIZE))
+ grid_y = int(owner_icon_allocation.y / float(_CELL_SIZE))
+ self._grid.add(owner_icon, owner_grid_width, owner_grid_height,
+ grid_x, grid_y, locked=True)
if activity_icon is None:
return
# Position the current activity below the XO icon
# FIXME must ensure we cross into next grid cell here..
- activity_request = activity_icon.size_request()
+ activity_request = get_child_size_request(activity_icon)
activity_icon_allocation = Gdk.Rectangle()
- activity_icon_allocation.x = (width - activity_request.width) / 2
- activity_icon_allocation.y = owner_icon_allocation.y + owner_height
+ activity_icon_allocation.x = int((width - activity_request.width) / 2)
+ activity_icon_allocation.y = int(owner_icon_allocation.y + owner_height)
activity_icon_allocation.width = activity_request.width
activity_icon_allocation.height = activity_request.height
- activity_icon.size_allocate(activity_icon_allocation)
+
+ allocate_child(activity_icon, activity_icon_allocation.x, activity_icon_allocation.y, activity_icon_allocation.width, activity_icon_allocation.height)
# Determine grid coordinates and add to grid
- activity_grid_width, activity_grid_height = \
- self._get_child_grid_size(activity_icon)
- x = int(activity_icon_allocation.x / float(_CELL_SIZE))
- y = int(activity_icon_allocation.y / float(_CELL_SIZE))
- self._grid.add(activity_icon, activity_grid_width,
- activity_grid_height, x, y, locked=True)
+ if not self._grid.is_in_grid(activity_icon):
+ activity_grid_width, activity_grid_height = \
+ self._get_child_grid_size(activity_icon)
+ x = int(activity_icon_allocation.x / float(_CELL_SIZE))
+ y = int(activity_icon_allocation.y / float(_CELL_SIZE))
+ self._grid.add(activity_icon, activity_grid_width,
+ activity_grid_height, x, y, locked=True)
def allocate_children(self, allocation, children):
pass
@@ -130,37 +166,22 @@ def move_icon(self, child, x, y, allocation):
def move(self, child, x, y, allocation=None):
self._grid.move(child, x / _CELL_SIZE, y / _CELL_SIZE, locked=True)
- child_request = child.size_request()
- rect = self._grid.get_child_rect(child)
-
- child_allocation = Gdk.Rectangle()
- child_allocation.x = int(round(rect.x * _CELL_SIZE))
- child_allocation.y = int(round(rect.y * _CELL_SIZE))
- child_allocation.width = child_request.width
- child_allocation.height = child_request.height
- child.size_allocate(child_allocation)
+ child.queue_allocate()
def _get_child_grid_size(self, child):
- request = child.size_request()
+ request = get_child_size_request(child)
width = math.ceil(request.width / _CELL_SIZE)
height = math.ceil(request.height / _CELL_SIZE)
return int(width), int(height)
def __grid_child_changed_cb(self, grid, child, allocation):
- request = child.size_request()
- rect = self._grid.get_child_rect(child)
- child_allocation = Gdk.Rectangle()
- child_allocation.x = int(round(rect.x * _CELL_SIZE))
- child_allocation.y = int(round(rect.y * _CELL_SIZE)) + allocation.y
- child_allocation.width = request.width
- child_allocation.height = request.height
- child.size_allocate(child_allocation)
+ child.queue_allocate()
class SpreadLayout(ViewLayout):
def __init__(self):
- ViewLayout.__init__(self)
+ super().__init__()
def remove(self, child):
if self._grid is None:
@@ -190,14 +211,14 @@ def allocate_children(self, allocation, children):
self._grid.add(child, width, height, x, y, locked=False)
- requisition = child.get_preferred_size()[0]
+ requisition = get_child_size_request(child)
rect = self._grid.get_child_rect(child)
child_allocation = Gdk.Rectangle()
child_allocation.x = int(round(rect.x * _CELL_SIZE))
child_allocation.y = int(round(rect.y * _CELL_SIZE)) + allocation.y
child_allocation.width = requisition.width
child_allocation.height = requisition.height
- child.size_allocate(child_allocation)
+ allocate_child(child, child_allocation.x, child_allocation.y, child_allocation.width, child_allocation.height)
class RandomLayout(SpreadLayout):
@@ -216,7 +237,7 @@ class RandomLayout(SpreadLayout):
"""String used to identify this layout in home view dropdown palette."""
def __init__(self):
- SpreadLayout.__init__(self)
+ super().__init__()
self.fixed_positions = {}
def _add_fixed_position(self, icon, allocation, locked=False):
@@ -238,7 +259,7 @@ def _add_fixed_position(self, icon, allocation, locked=False):
def allocate_children(self, allocation, children):
for child in children:
- child_requisition = child.size_request()
+ child_requisition = get_child_size_request(child)
if not self._grid.is_in_grid(child):
self._add_fixed_position(child, allocation)
@@ -257,20 +278,20 @@ def allocate_children(self, allocation, children):
y = None
if x is None or y is None:
- self._grid.add(child, child_requisition.width / _CELL_SIZE,
- child_requisition.height / _CELL_SIZE)
+ self._grid.add(child, int(child_requisition.width / _CELL_SIZE),
+ int(child_requisition.height / _CELL_SIZE))
else:
- self._grid.add(child, child_requisition.width / _CELL_SIZE,
- child_requisition.height / _CELL_SIZE,
- x / _CELL_SIZE, y / _CELL_SIZE)
+ self._grid.add(child, int(child_requisition.width / _CELL_SIZE),
+ int(child_requisition.height / _CELL_SIZE),
+ int(x / _CELL_SIZE), int(y / _CELL_SIZE))
rect = self._grid.get_child_rect(child)
child_allocation = Gdk.Rectangle()
child_allocation.x = int(round(rect.x * _CELL_SIZE))
- child_allocation.y = int(round(rect.y * _CELL_SIZE)) + allocation.y
+ child_allocation.y = int(round(rect.y * _CELL_SIZE))
child_allocation.width = child_requisition.width
child_allocation.height = child_requisition.height
- child.size_allocate(child_allocation)
+ allocate_child(child, child_allocation.x, child_allocation.y, child_allocation.width, child_allocation.height)
def move_icon(self, child, x, y, allocation):
ViewLayout.move(self, child, x, y)
@@ -287,7 +308,6 @@ def move_icon(self, child, x, y, allocation):
y * allocation.height / float(_BASE_SCALE))
self.fixed_positions[child] = (x, y)
-
_MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING
_RING_SPACING_FACTOR = 0.95
_SPIRAL_SPACING_FACTOR = 0.75
@@ -310,7 +330,7 @@ class RingLayout(ViewLayout):
"""String used to identify this layout in home view dropdown palette."""
def __init__(self):
- ViewLayout.__init__(self)
+ super().__init__()
self._spiral_mode = False
def _calculate_radius_and_icon_size(self, children_count):
@@ -369,7 +389,7 @@ def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width,
def _calculate_maximum_radius(self, icon_size):
""" Return the maximum radius including encroachment. """
- r = (Gdk.Screen.height() - style.GRID_CELL_SIZE) / 2 - \
+ r = (_get_screen_height() - style.GRID_CELL_SIZE) / 2 - \
style.DEFAULT_SPACING
return r - (icon_size * _MAXIMUM_RADIUS_PADDING_FACTOR)
@@ -417,15 +437,14 @@ def allocate_children(self, allocation, children):
y += icon_size / 2
child.set_size(icon_size)
- new_width = child.get_preferred_width()[0]
- new_height = child.get_preferred_height()[0]
+ new_width = child.measure(Gtk.Orientation.HORIZONTAL, -1)[1]
+ new_height = child.measure(Gtk.Orientation.VERTICAL, -1)[1]
child_allocation = Gdk.Rectangle()
- child_allocation.x = allocation.x + x
- child_allocation.y = allocation.y + y
+ child_allocation.x = int(allocation.x + x)
+ child_allocation.y = int(allocation.y + y)
child_allocation.width = new_width
child_allocation.height = new_height
- child.size_allocate(child_allocation)
-
+ allocate_child(child, child_allocation.x, child_allocation.y, child_allocation.width, child_allocation.height)
_SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75
"""Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced."""
@@ -472,7 +491,7 @@ class SunflowerLayout(RingLayout):
"""String used to identify this layout in home view dropdown palette."""
def __init__(self):
- RingLayout.__init__(self)
+ super().__init__()
self.skipped_indices = []
def _calculate_radius_and_icon_size(self, children_count):
@@ -540,7 +559,7 @@ class BoxLayout(RingLayout):
"""String used to identify this layout in home view dropdown palette."""
def __init__(self):
- RingLayout.__init__(self)
+ super().__init__()
def _calculate_position(self, radius, icon_size, index, children_count,
width, height, sin=None, cos=None):
@@ -586,7 +605,7 @@ class TriangleLayout(RingLayout):
"""String used to identify this layout in home view dropdown palette."""
def __init__(self):
- RingLayout.__init__(self)
+ super().__init__()
def _calculate_radius_and_icon_size(self, children_count):
# use slightly larger minimum radius than parent, because sides
diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py
index de58d74f67..8f2d95a3c1 100644
--- a/src/jarabe/desktop/favoritesview.py
+++ b/src/jarabe/desktop/favoritesview.py
@@ -57,8 +57,6 @@
_logger = logging.getLogger('FavoritesView')
-_ICON_DND_TARGET = ('activity-icon', Gtk.TargetFlags.SAME_WIDGET, 0)
-
LAYOUT_MAP = {favoriteslayout.RingLayout.key: favoriteslayout.RingLayout,
favoriteslayout.BoxLayout.key: favoriteslayout.BoxLayout,
favoriteslayout.TriangleLayout.key:
@@ -77,12 +75,13 @@ class FavoritesBox(Gtk.Box):
__gtype_name__ = 'SugarFavoritesBox'
def __init__(self, favorite_view):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.favorite_view = favorite_view
self._view = FavoritesView(self)
- self.pack_start(self._view, True, True, 0)
- self._view.show()
+ self.append(self._view)
+ self._view.set_vexpand(True)
+ self._view.set_hexpand(True)
self._alert = None
@@ -94,14 +93,13 @@ def set_resume_mode(self, resume_mode):
def grab_focus(self):
# overwrite grab focus in order to grab focus from the parent
- self._view.grab_focus()
+ return self._view.grab_focus()
def add_alert(self, alert):
if self._alert is not None:
self.remove_alert()
self._alert = alert
- self.pack_start(alert, False, True, 0)
- self.reorder_child(alert, 0)
+ self.prepend(alert)
def remove_alert(self):
self.remove(self._alert)
@@ -128,22 +126,10 @@ def __init__(self, box):
activity_icon=current_activity)
self.set_can_focus(False)
- self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
- Gdk.EventMask.POINTER_MOTION_HINT_MASK)
- self.drag_dest_set(0, [], 0)
-
- # Drag and drop is set only for the Random layout. This is
- # the flag that enables or disables it.
+ # Drag and drop is set only for the Random layout.
self._dragging_mode = False
+ self._drop_target = None
- self._drag_motion_hid = None
- self._drag_drop_hid = None
- self._drag_data_received_hid = None
-
- self._dragging = False
- self._pressed_button = None
- self._press_start_x = 0
- self._press_start_y = 0
self._hot_x = None
self._hot_y = None
self._last_clicked_icon = None
@@ -156,7 +142,9 @@ def __init__(self, box):
favorites_settings = get_settings(self._box.favorite_view)
favorites_settings.changed.connect(self.__settings_changed_cb)
- self._set_layout(favorites_settings.layout)
+ layout_set = self._set_layout(favorites_settings.layout)
+ if layout_set:
+ self.set_layout(self._layout)
def __settings_changed_cb(self, **kwargs):
favorites_settings = get_settings(self._box.favorite_view)
@@ -172,23 +160,21 @@ def __settings_changed_cb(self, **kwargs):
def _set_layout(self, layout):
if layout not in LAYOUT_MAP:
- logging.warn('Unknown favorites layout: %r', layout)
+ logging.warning('Unknown favorites layout: %r', layout)
layout = favoriteslayout.RingLayout.key
assert layout in LAYOUT_MAP
if self._layout is not None and self._dragging_mode:
- self.disconnect(self._drag_motion_hid)
- self.disconnect(self._drag_drop_hid)
- self.disconnect(self._drag_data_received_hid)
+ if self._drop_target:
+ self.remove_controller(self._drop_target)
+ self._drop_target = None
if layout == favoriteslayout.RandomLayout.key:
self._dragging_mode = True
- self._drag_motion_hid = self.connect(
- 'drag-motion', self.__drag_motion_cb)
- self._drag_drop_hid = self.connect(
- 'drag-drop', self.__drag_drop_cb)
- self._drag_data_received_hid = self.connect(
- 'drag-data-received', self.__drag_data_received_cb)
+ self._drop_target = Gtk.DropTarget.new(type=GObject.TYPE_STRING, actions=Gdk.DragAction.MOVE)
+ self._drop_target.connect('motion', self.__drag_motion_cb)
+ self._drop_target.connect('drop', self.__drag_drop_cb)
+ self.add_controller(self._drop_target)
else:
self._dragging_mode = False
@@ -197,97 +183,62 @@ def _set_layout(self, layout):
layout = property(None, _set_layout)
- def do_add(self, child):
+ def add(self, child):
if child != self._owner_icon and child != self._activity_icon:
self._children.append(child)
- child.connect('button-press-event', self.__button_press_cb)
- child.connect('button-release-event', self.__button_release_cb)
- child.connect('motion-notify-event', self.__motion_notify_event_cb)
- child.connect('drag-begin', self.__drag_begin_cb)
- if child.get_realized():
- child.set_parent_window(self.get_parent_window())
- child.set_parent(self)
-
- def __button_release_cb(self, widget, event):
- if self._dragging:
- return True
- return False
+
+ drag_source = Gtk.DragSource.new()
+ drag_source.set_actions(Gdk.DragAction.MOVE)
+ drag_source.connect("prepare", self.__drag_prepare_cb, child)
+ drag_source.connect("drag-begin", self.__drag_begin_cb, child)
+ child.add_controller(drag_source)
- def __button_press_cb(self, widget, event):
- if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
- self._last_clicked_icon = widget
- self._pressed_button = event.button
- self._press_start_x = event.x
- self._press_start_y = event.y
- return False
+ child.set_parent(self)
- def __motion_notify_event_cb(self, widget, event):
+ def __drag_prepare_cb(self, source, x, y, child):
if not self._dragging_mode:
- return False
-
- # if the mouse button is not pressed, no drag should occurr
- if not event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
- self._pressed_button = None
- return False
-
- if event.is_hint:
- x, y, state_ = event.window.get_pointer()
- else:
- x = event.x
- y = event.y
-
- if widget.drag_check_threshold(int(self._press_start_x),
- int(self._press_start_y),
- int(x),
- int(y)):
- self._dragging = True
- target_entry = Gtk.TargetEntry.new(*_ICON_DND_TARGET)
- target_list = Gtk.TargetList.new([target_entry])
- widget.drag_begin(target_list,
- Gdk.DragAction.MOVE,
- 1,
- event)
- return False
+ return None
+ self._last_clicked_icon = child
+ v = GObject.Value(GObject.TYPE_STRING, "activity-icon")
+ return Gdk.ContentProvider.new_for_value(v)
- def __drag_begin_cb(self, widget, context):
+ def __drag_begin_cb(self, source, drag, child):
if not self._dragging_mode:
- return False
-
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(widget.props.file_name)
-
- self._hot_x = pixbuf.props.width / 2
- self._hot_y = pixbuf.props.height / 2
- Gtk.drag_set_icon_pixbuf(context, pixbuf, self._hot_x, self._hot_y)
-
- def __drag_motion_cb(self, widget, context, x, y, time):
+ return
+ if not child.props.file_name:
+ return
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(child.props.file_name)
+ self._hot_x = pixbuf.get_width() / 2
+ self._hot_y = pixbuf.get_height() / 2
+ texture = Gdk.Texture.new_for_pixbuf(pixbuf)
+ source.set_icon(texture, self._hot_x, self._hot_y)
+ except Exception as e:
+ logging.error("Failed to create drag icon: %s", e)
+ self._hot_x = 0
+ self._hot_y = 0
+
+ def __drag_motion_cb(self, target, x, y):
if self._last_clicked_icon is not None:
- Gdk.drag_status(context, context.get_suggested_action(), time)
- return True
- return False
+ return Gdk.DragAction.MOVE
+ return Gdk.DragAction(0)
- def __drag_drop_cb(self, widget, context, x, y, time):
+ def __drag_drop_cb(self, target, value, x, y):
if self._last_clicked_icon is not None:
- target = Gdk.Atom.intern_static_string(_ICON_DND_TARGET[0])
- self.drag_get_data(context, target, time)
+ allocation = Gdk.Rectangle()
+ allocation.x = 0
+ allocation.y = 0
+ allocation.width = self.get_width()
+ allocation.height = self.get_height()
self._layout.move_icon(self._last_clicked_icon,
- x - self._hot_x, y - self._hot_y,
- self.get_allocation())
+ x - self._hot_x, y - self._hot_y, allocation)
- self._pressed_button = None
- self._press_start_x = None
- self._press_start_y = None
self._hot_x = None
self._hot_y = None
self._last_clicked_icon = None
- self._dragging = False
-
return True
return False
- def __drag_data_received_cb(self, widget, context, x, y, selection_data,
- info, time):
- Gdk.drop_finish(context, success=True, time_=time)
-
def __connect_to_bundle_registry_cb(self):
registry = bundleregistry.get_registry()
@@ -312,7 +263,6 @@ def _add_activity(self, activity_info):
icon.props.pixel_size = style.STANDARD_ICON_SIZE
# icon.set_resume_mode(self._resume_mode)
self.add(icon)
- icon.show()
def __activity_added_cb(self, activity_registry, activity_info):
registry = bundleregistry.get_registry()
@@ -493,15 +443,11 @@ def __palette_activate_cb(self, palette):
def __palette_entry_activate_cb(self, palette, metadata):
self._resume(metadata)
- def do_get_preferred_width(self):
- width = CanvasIcon.do_get_preferred_width(self)[0]
- width += ActivityIcon._BORDER_WIDTH * 2
- return (width, width)
-
- def do_get_preferred_height(self):
- height = CanvasIcon.do_get_preferred_height(self)[0]
- height += ActivityIcon._BORDER_WIDTH * 2
- return (height, height)
+ def do_measure(self, orientation, for_size):
+ min_size, nat_size, min_base, nat_base = super().do_measure(orientation, for_size)
+ min_size += ActivityIcon._BORDER_WIDTH * 2
+ nat_size += ActivityIcon._BORDER_WIDTH * 2
+ return (min_size, nat_size, min_base, nat_base)
def __button_activate_cb(self, icon):
self._activate()
@@ -582,15 +528,17 @@ def __init__(self, activity_info, journal_entries):
xo_color=color)
menu_item.connect('activate', self.__resume_entry_cb, entry)
menu_items.append(menu_item)
- menu_item.show()
if journal_entries:
separator = PaletteMenuItemSeparator()
menu_items.append(separator)
- separator.show()
for i in range(0, len(menu_items)):
- self.menu_box.pack_start(menu_items[i], True, True, 0)
+ menu_items[i].set_hexpand(True)
+ menu_items[i].set_vexpand(True)
+ menu_items[i].set_halign(Gtk.Align.FILL)
+ menu_items[i].set_valign(Gtk.Align.FILL)
+ self.menu_box.append(menu_items[i])
def __resume_entry_cb(self, menu_item, entry):
if entry is not None:
@@ -605,17 +553,30 @@ def __init__(self):
self._home_model = shell.get_model()
self._home_activity = self._home_model.get_active_activity()
- if self._home_activity is not None:
- self._update()
+ if self._home_activity is None:
+ self._home_activity = self._get_journal_activity()
+
+ self._update()
self._home_model.connect('active-activity-changed',
self.__active_activity_changed_cb)
self.connect_after('activate', self.__activate_cb)
+ def _get_journal_activity(self):
+ for activity in self._home_model._activities:
+ if activity.is_journal():
+ return activity
+ return None
+
def __activate_cb(self, icon):
- window = self._home_model.get_active_activity().get_window()
- window.activate(Gtk.get_current_event_time())
+ active_activity = self._home_model.get_active_activity()
+ if active_activity is None:
+ # No activity open: reveal the Journal directly.
+ from jarabe.journal import journalactivity
+ journalactivity.get_journal().reveal()
+ else:
+ self._home_model.activate_activity(active_activity)
def _update(self):
if self._home_activity is not None:
@@ -625,33 +586,51 @@ def _update(self):
if self._home_activity.is_journal():
if self._unbusy():
GLib.timeout_add(100, self._unbusy)
+ else:
+ self.props.xo_color = get_owner_instance().props.color
self.props.pixel_size = style.STANDARD_ICON_SIZE
if self.palette is not None:
- self.palette.destroy()
+ if hasattr(self.palette, "popdown"):
+ self.palette.popdown(immediate=True)
self.palette = None
def _unbusy(self):
- if self.get_window():
- import jarabe.desktop.homewindow
- jarabe.desktop.homewindow.get_instance().unbusy()
+ import jarabe.desktop.homewindow
+ hw = jarabe.desktop.homewindow.get_instance()
+ if hw and hw.get_root():
+ hw.unbusy()
return False
return True
def create_palette(self):
+ if self._home_activity is None:
+ # Journal may not be registered yet – initialize it now.
+ from jarabe.journal import journalactivity as _ja
+ _ja.get_journal()
+ self._home_activity = self._get_journal_activity()
+
if self._home_activity is not None:
if self._home_activity.is_journal():
palette = JournalPalette(self._home_activity)
else:
palette = CurrentActivityPalette(self._home_activity)
self.connect_to_palette_pop_events(palette)
- else:
- palette = None
- return palette
+ return palette
+
+ return None
def __active_activity_changed_cb(self, home_model, home_activity):
- self._home_activity = home_activity
+ if home_activity is None:
+ self._home_activity = self._get_journal_activity()
+ if self._home_activity is None:
+ # journal not yet registered – initialize it
+ from jarabe.journal import journalactivity as _ja
+ _ja.get_journal()
+ self._home_activity = self._get_journal_activity()
+ else:
+ self._home_activity = home_activity
self._update()
@@ -666,15 +645,6 @@ class OwnerIcon(BuddyIcon):
def __init__(self, size):
BuddyIcon.__init__(self, buddy=get_owner_instance(), pixel_size=size)
- # This is a workaround to skip the callback for
- # enter-notify-event in the parent class the first time.
- def __enter_notify_event_cb(icon, event):
- self.unset_state_flags(Gtk.StateFlags.PRELIGHT)
- self.disconnect(self._enter_notify_hid)
-
- self._enter_notify_hid = self.connect('enter-notify-event',
- __enter_notify_event_cb)
-
def create_palette(self):
palette = BuddyMenu(get_owner_instance())
@@ -688,9 +658,13 @@ def create_palette(self):
text = _('Register again')
register_menu = PaletteMenuItem(text, 'media-record')
- register_menu.connect('activate', self.__register_activate_cb)
- palette.menu_box.pack_end(register_menu, True, True, 0)
- register_menu.show()
+ register_menu.connect('item-activated', self.__register_activate_cb)
+ register_menu.set_hexpand(True)
+ register_menu.set_vexpand(True)
+ register_menu.set_halign(Gtk.Align.FILL)
+ register_menu.set_valign(Gtk.Align.FILL)
+ palette.menu_box.append_item(register_menu)
+ register_menu.set_visible(True)
self.connect_to_palette_pop_events(palette)
diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py
index f4dbd54c4d..c502cf4020 100644
--- a/src/jarabe/desktop/friendview.py
+++ b/src/jarabe/desktop/friendview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
@@ -26,7 +26,7 @@
class FriendView(Gtk.Box):
def __init__(self, buddy, **kwargs):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs)
# round icon sizes to an even number so that it can be accurately
# centered in a larger bounding box also of even dimensions
@@ -35,10 +35,13 @@ def __init__(self, buddy, **kwargs):
self._buddy = buddy
self._buddy_icon = BuddyIcon(buddy)
self._buddy_icon.props.pixel_size = size
- self.add(self._buddy_icon)
- self._buddy_icon.show()
+ self.append(self._buddy_icon)
self._activity_icon = CanvasIcon(pixel_size=size)
+ # Always keep the activity icon in the box; use set_visible() to
+ # show/hide it.
+ self._activity_icon.set_visible(False)
+ self.append(self._activity_icon)
self._update_activity()
self._buddy.connect('notify::current-activity',
@@ -54,9 +57,8 @@ def _get_new_icon_name(self, ps_activity):
return None
def _remove_activity_icon(self):
- if self._activity_icon.get_visible():
- self._activity_icon.hide()
- self.remove(self._activity_icon)
+ # Hide the widget.
+ self._activity_icon.set_visible(False)
def __buddy_notify_current_activity_cb(self, buddy, pspec):
self._update_activity()
@@ -73,9 +75,8 @@ def _update_activity(self):
if name:
self._activity_icon.props.file_name = name
self._activity_icon.props.xo_color = self._buddy.props.color
- if not self._activity_icon.get_visible():
- self.add(self._activity_icon)
- self._activity_icon.show()
+ # Make visible; widget is always a child of the box.
+ self._activity_icon.set_visible(True)
else:
self._remove_activity_icon()
diff --git a/src/jarabe/desktop/grid.py b/src/jarabe/desktop/grid.py
index 48b32d4eed..4248df9eee 100644
--- a/src/jarabe/desktop/grid.py
+++ b/src/jarabe/desktop/grid.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 Red Hat, Inc.
+# Copyright (C) 2007 Red Hat, Inc.
# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
@@ -20,23 +20,29 @@
from gi.repository import Gdk
from gi.repository import GLib
+import gi
+gi.require_version('SugarExt', '2.0')
from gi.repository import SugarExt
-
_PLACE_TRIALS = 20
_MAX_WEIGHT = 255
_REFRESH_RATE = 200
_MAX_COLLISIONS_PER_REFRESH = 20
-class Grid(SugarExt.Grid):
+class GridWeight(SugarExt.Grid):
+ def __init__(self):
+ GObject.GObject.__init__(self)
+
+
+class Grid(GridWeight):
__gsignals__ = {
'child-changed': (GObject.SignalFlags.RUN_FIRST, None,
([GObject.TYPE_PYOBJECT])),
}
def __init__(self, width, height):
- GObject.GObject.__init__(self)
+ GridWeight.__init__(self)
self._children = []
self._child_rects = {}
@@ -193,12 +199,22 @@ def __solve_collisions_cb(self):
return True
+ def _intersect_rects(self, rect1, rect2):
+ x = max(rect1.x, rect2.x)
+ y = max(rect1.y, rect2.y)
+ w = min(rect1.x + rect1.width, rect2.x + rect2.width) - x
+ h = min(rect1.y + rect1.height, rect2.y + rect2.height) - y
+ intersection = Gdk.Rectangle()
+ if w > 0 and h > 0:
+ intersection.x, intersection.y, intersection.width, intersection.height = x, y, w, h
+ return True, intersection
+ return False, intersection
+
def _detect_collisions(self, child):
collision_found = False
child_rect = self._child_rects[child]
for c in self._children:
- intersects_, intersection = Gdk.rectangle_intersect(
- child_rect, self._child_rects[c])
+ intersects_, intersection = self._intersect_rects(child_rect, self._child_rects[c])
if c != child and intersection.width > 0:
collision_found = True
if (c not in self._locked_children and
diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py
index 98df01d234..4044e53183 100644
--- a/src/jarabe/desktop/groupbox.py
+++ b/src/jarabe/desktop/groupbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -37,13 +37,12 @@ def __init__(self, toolbar):
# Round off icon size to an even number to ensure that the icon
owner_icon = BuddyIcon(get_owner_instance(),
style.LARGE_ICON_SIZE & ~1)
- ViewContainer.__init__(self, layout, owner_icon)
- self.set_can_focus(False)
+ super().__init__(layout, owner_icon)
+ self.set_focusable(False)
self._query = ''
toolbar.connect('query-changed', self._toolbar_query_changed_cb)
- toolbar.search_entry.connect('icon-press',
- self.__clear_icon_pressed_cb)
+
self._friends = {}
friends_model = friends.get_model()
@@ -58,7 +57,6 @@ def add_friend(self, buddy_info):
icon = FriendView(buddy_info)
self.add(icon)
self._friends[buddy_info.get_key()] = icon
- icon.show()
def _friend_added_cb(self, data_model, buddy_info):
self.add_friend(buddy_info)
@@ -67,7 +65,6 @@ def _friend_removed_cb(self, data_model, key):
icon = self._friends[key]
self.remove(icon)
del self._friends[key]
- icon.destroy()
def _toolbar_query_changed_cb(self, toolbar, query):
self._query = normalize_string(query)
@@ -75,5 +72,5 @@ def _toolbar_query_changed_cb(self, toolbar, query):
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
- def __clear_icon_pressed_cb(self, entry, icon_pos, event):
+ def __clear_icon_pressed_cb(self, entry, icon_pos):
self.grab_focus()
diff --git a/src/jarabe/desktop/homebackgroundbox.py b/src/jarabe/desktop/homebackgroundbox.py
index b9c00f51e3..24610cfc3b 100644
--- a/src/jarabe/desktop/homebackgroundbox.py
+++ b/src/jarabe/desktop/homebackgroundbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Agustin Zubiaga
+# Copyright (C) 2012 Agustin Zubiaga
# Copyright (C) 2013 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
@@ -21,6 +21,7 @@
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import Gio
+from gi.repository import Graphene
BACKGROUND_DIR = 'org.sugarlabs.user.background'
BACKGROUND_IMAGE_PATH_KEY = 'image-path'
@@ -56,29 +57,39 @@ def get_background_alpha_level():
class HomeBackgroundBox(Gtk.Box):
def __init__(self):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._background_pixbuf = None
self._update_background_image()
- self.connect('draw', self.__draw_cb)
self._settings = Gio.Settings.new(BACKGROUND_DIR)
self._settings.connect('changed', self.__conf_changed_cb, None)
- def __draw_cb(self, widget, context):
- if self._background_pixbuf is None:
- return
-
- alloc = widget.get_allocation()
-
- if self._background_pixbuf.get_width() != alloc.width or \
- self._background_pixbuf.get_height() != alloc.height:
- self._background_pixbuf = self._background_pixbuf.scale_simple(
- alloc.width,
- alloc.height,
- GdkPixbuf.InterpType.TILES)
- Gdk.cairo_set_source_pixbuf(context, self._background_pixbuf, 0, 0)
- alpha = get_background_alpha_level()
- context.paint_with_alpha(alpha)
+ def do_snapshot(self, snapshot):
+ if self._background_pixbuf is not None:
+ width = self.get_width()
+ height = self.get_height()
+
+ if (self._background_pixbuf.get_width() != width or
+ self._background_pixbuf.get_height() != height):
+ scaled = self._background_pixbuf.scale_simple(
+ width, height, GdkPixbuf.InterpType.TILES)
+ else:
+ scaled = self._background_pixbuf
+
+ texture = Gdk.Texture.new_for_pixbuf(scaled)
+ alpha = get_background_alpha_level()
+
+ rect = Graphene.Rect()
+ rect.init(0, 0, width, height)
+
+ snapshot.push_opacity(alpha)
+ snapshot.append_texture(texture, rect)
+ snapshot.pop()
+
+ child = self.get_first_child()
+ while child is not None:
+ self.snapshot_child(child, snapshot)
+ child = child.get_next_sibling()
def __conf_changed_cb(self, settings, key, data):
self._update_background_image()
diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py
index bbd5cbe1bf..61ec984ef3 100644
--- a/src/jarabe/desktop/homebox.py
+++ b/src/jarabe/desktop/homebox.py
@@ -25,23 +25,41 @@
from jarabe.model import desktop
+def _get_children(box):
+ children = []
+ child = box.get_first_child()
+ while child:
+ children.append(child)
+ child = child.get_next_sibling()
+ return children
+
+
class HomeBox(Gtk.Box):
__gtype_name__ = 'SugarHomeBox'
def __init__(self, toolbar):
logging.debug('STARTUP: Loading the home view')
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._favorites_views_indicies = []
for i in range(desktop.get_number_of_views()):
self._favorites_views_indicies.append(i)
self._list_view_index = self._favorites_views_indicies[-1] + 1
+ self._view_stack = Gtk.Stack()
+ self._view_stack.set_transition_type(Gtk.StackTransitionType.NONE)
+ self._view_stack.set_vexpand(True)
+ self.append(self._view_stack)
+
self._favorites_boxes = []
for i in range(desktop.get_number_of_views()):
- self._favorites_boxes.append(FavoritesBox(i))
+ box = FavoritesBox(i)
+ self._favorites_boxes.append(box)
+ self._view_stack.add_named(box, f"favorite_{i}")
+
self._list_view = ActivitiesList()
+ self._view_stack.add_named(self._list_view, "list")
self._desktop_model = desktop.get_model()
self._desktop_model.connect('desktop-view-icons-changed',
@@ -50,18 +68,25 @@ def __init__(self, toolbar):
toolbar.search_entry._icon_selected = []
toolbar.connect('query-changed', self.__toolbar_query_changed_cb)
toolbar.connect('view-changed', self.__toolbar_view_changed_cb)
- toolbar.search_entry.connect('key-press-event',
- self.__search_entry_key_press_event_cb)
- toolbar.search_entry.connect('icon-press',
- self.__clear_icon_pressed_cb)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed',
+ self.__search_entry_key_pressed_cb)
+ toolbar.search_entry.add_controller(key_controller)
+
+
self._list_view.connect('clear-clicked',
self.__activitylist_clear_clicked_cb, toolbar)
- self._set_view(self._favorites_views_indicies[0])
+ self._last_view = self._favorites_views_indicies[0]
self._query = ''
self._resume_mode = Gio.Settings.new(
'org.sugarlabs.user').get_boolean('resume-activity')
+ # Show the default view after all state is initialised
+ self._set_view(self._favorites_views_indicies[0])
+
+
def __desktop_view_icons_changed_cb(self, model):
number_of_views = desktop.get_number_of_views()
@@ -91,87 +116,87 @@ def __toolbar_query_changed_cb(self, toolbar, query):
toolbar.search_entry._icon_selected = \
self._list_view.get_activities_selected()
- # verify if one off the selected names is a perfect match
- # this is needed by th case of activities with names contained
+ # verify if one of the selected names is a perfect match
+ # this is needed by the case of activities with names contained
# in other activities like 'Paint' and 'MusicPainter'
+ activity = None
for activity in self._list_view.get_activities_selected():
if activity['name'].upper() == query.upper():
toolbar.search_entry._icon_selected = [activity]
break
# Don't change the selection if the entry has been autocompleted
- if len(toolbar.search_entry._icon_selected) == 1 \
+ if activity is not None and len(toolbar.search_entry._icon_selected) == 1 \
and not toolbar.search_entry.get_text() == activity['name']:
pos = toolbar.search_entry.get_position()
toolbar.search_entry.set_text(
toolbar.search_entry._icon_selected[0]['name'])
toolbar.search_entry.select_region(pos, -1)
+ if not query and self._resume_mode:
+ self._list_view.get_activities_selected()
+ else:
+ self.set_resume_mode(False)
+
+ # when query has any string, then set search view else revert
+ if query:
+ self._set_view(self._list_view_index)
+ else:
+ self._set_view(self._last_view)
+
def __toolbar_view_changed_cb(self, toolbar, view):
- self._set_view(view)
+ self.set_view(view)
- def __search_entry_key_press_event_cb(self, entry, event):
+ def __search_entry_key_pressed_cb(self, controller, keyval, keycode, state):
# wherever a single item is selected in a desktop view,
# launch the activity on pressing return
- if event.keyval == Gdk.KEY_Return and len(entry._icon_selected) == 1:
+ entry = controller.get_widget()
+ if keyval == Gdk.KEY_Return and len(entry._icon_selected) == 1:
self._list_view.run_activity(entry._icon_selected[0]['bundle_id'],
self._resume_mode)
entry._icon_selected = []
self.set_resume_mode(self._resume_mode)
+ return True
+
+ return False
def __activitylist_clear_clicked_cb(self, widget, toolbar):
- toolbar.clear_query()
+ toolbar.search_entry.set_text('')
+ toolbar.search_entry.grab_focus()
- def __clear_icon_pressed_cb(self, entry, icon_pos, event):
+ def __clear_icon_pressed_cb(self, entry, icon_pos):
self.grab_focus()
def grab_focus(self):
- # overwrite grab focus to be able to grab focus on the
- # views which are packed inside a box
- children = self.get_children()
- if self._list_view in children:
- self._list_view.grab_focus()
- else:
- for i in range(desktop.get_number_of_views()):
- if self._favorites_boxes[i] in children:
- self._favorites_boxes[i].grab_focus()
+ super().grab_focus()
+
+ def set_view(self, view):
+ if view in self._favorites_views_indicies:
+ self._last_view = view
+ self._set_view(view)
def _set_view(self, view):
if view in self._favorites_views_indicies:
favorite = self._favorites_views_indicies.index(view)
-
- children = self.get_children()
- if self._list_view in children:
- self.remove(self._list_view)
- else:
- for i in range(desktop.get_number_of_views()):
- if i != favorite and self._favorites_boxes[i] in children:
- self.remove(self._favorites_boxes[i])
-
- if self._favorites_boxes[favorite] not in children:
- self.add(self._favorites_boxes[favorite])
- self._favorites_boxes[favorite].show()
- self._favorites_boxes[favorite].grab_focus()
+ self._view_stack.set_visible_child_name(f"favorite_{favorite}")
+ self._favorites_boxes[favorite].grab_focus()
elif view == self._list_view_index:
- children = self.get_children()
- for i in range(desktop.get_number_of_views()):
- if self._favorites_boxes[i] in children:
- self.remove(self._favorites_boxes[i])
-
- if self._list_view not in children:
- self.add(self._list_view)
- self._list_view.show()
- self._list_view.grab_focus()
+ self._view_stack.set_visible_child_name("list")
+ self._list_view.grab_focus()
else:
raise ValueError('Invalid view: %r' % view)
_REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes
def resume(self):
- pass
+ visible = self._view_stack.get_visible_child()
+ if hasattr(visible, 'resume'):
+ visible.resume()
def suspend(self):
- pass
+ visible = self._view_stack.get_visible_child()
+ if hasattr(visible, 'suspend'):
+ visible.suspend()
def set_resume_mode(self, resume_mode, favorite_view=0):
self._resume_mode = resume_mode
diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py
index 7fb44468c2..c1a654ea70 100644
--- a/src/jarabe/desktop/homewindow.py
+++ b/src/jarabe/desktop/homewindow.py
@@ -35,7 +35,6 @@
from jarabe.model import shell
from jarabe import config
-
_HOME_PAGE = 0
_GROUP_PAGE = 1
_MESH_PAGE = 2
@@ -44,78 +43,96 @@
_instance = None
-class HomeWindow(Gtk.ApplicationWindow):
+def _get_children(box):
+ children = []
+ child = box.get_first_child()
+ while child:
+ children.append(child)
+ child = child.get_next_sibling()
+ return children
+
+
+class HomeWindow(Gtk.Box):
def __init__(self):
logging.debug('STARTUP: Loading the desktop window')
- Gtk.ApplicationWindow.__init__(self)
- self.set_has_resize_grip(False)
-
- accel_group = Gtk.AccelGroup()
- self.sugar_accel_group = accel_group
- self.add_accel_group(accel_group)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
+ self.add_css_class('home-window')
+ self.add_css_class('background')
self._active = False
self._fully_obscured = True
- screen = self.get_screen()
- screen.connect('size-changed', self.__screen_size_changed_cb)
- self.set_default_size(screen.get_width(),
- screen.get_height())
+ display = Gdk.Display.get_default()
+ self._monitors = display.get_monitors()
+ self._monitors_hid = self._monitors.connect('items-changed', self.__screen_size_changed_cb)
+ self._monitor_geom_hid = None
+ self._current_monitor = None
+ if self._monitors.get_n_items() > 0:
+ self._current_monitor = self._monitors.get_item(0)
+ self._monitor_geom_hid = self._current_monitor.connect('notify::geometry', self.__screen_size_changed_cb)
+ geom = self._current_monitor.get_geometry()
+ self.set_size_request(geom.width, geom.height)
icons_path = os.path.join(config.data_path, 'icons')
- Gtk.IconTheme.get_for_screen(screen).append_search_path(icons_path)
-
- self.__screen_size_changed_cb(None)
+ icon_theme = Gtk.IconTheme.get_for_display(display)
+ icon_theme.add_search_path(icons_path)
- self.realize()
self._busy_count = 0
self.busy()
- self.set_type_hint(Gdk.WindowTypeHint.DESKTOP)
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
+ key_controller = Gtk.EventControllerKey.new()
+ key_controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
+ key_controller.connect('key-pressed', self.__key_pressed_cb)
+ key_controller.connect('key-released', self.__key_released_cb)
+ self.add_controller(key_controller)
- self.add_events(Gdk.EventMask.VISIBILITY_NOTIFY_MASK |
- Gdk.EventMask.BUTTON_PRESS_MASK)
- self.connect('visibility-notify-event',
- self._visibility_notify_event_cb)
- self.connect('map-event', self.__map_event_cb)
- self.connect('key-press-event', self.__key_press_event_cb)
- self.connect('key-release-event', self.__key_release_event_cb)
+ focus_controller = Gtk.EventControllerFocus.new()
+ focus_controller.connect('leave', self.__focus_out_event_cb)
+ self.add_controller(focus_controller)
self._box = HomeBackgroundBox()
+ self._box.set_hexpand(True)
self._toolbar = ViewToolbar()
- self._box.pack_start(self._toolbar, False, True, 0)
- self._toolbar.show()
+ self._toolbar.add_css_class('toolbar')
+ self._box.append(self._toolbar)
self._alert = None
+ self._view_stack = Gtk.Stack()
+ self._view_stack.set_transition_type(Gtk.StackTransitionType.NONE)
+ self._view_stack.set_vexpand(True)
+ self._box.append(self._view_stack)
+
self._home_box = HomeBox(self._toolbar)
- self._box.pack_start(self._home_box, True, True, 0)
- self._home_box.show()
+ self._view_stack.add_named(self._home_box, 'home')
self._toolbar.show_view_buttons()
- # Loads the Gsettings value for activity 'resume-mode'
setting = Gio.Settings.new('org.sugarlabs.user')
self._resume_mode = setting.get_boolean('resume-activity')
self._home_box.set_resume_mode(self._resume_mode)
self._group_box = GroupBox(self._toolbar)
+ self._view_stack.add_named(self._group_box, 'group')
+
self._mesh_box = MeshBox(self._toolbar)
+ self._view_stack.add_named(self._mesh_box, 'mesh')
+
self._transition_box = TransitionBox()
+ self._view_stack.add_named(self._transition_box, 'transition')
- self.add(self._box)
- self._box.show()
+ self._view_stack.set_visible_child_name('home')
+
+ self.append(self._box)
self._transition_box.connect('completed',
self._transition_completed_cb)
- shell.get_model().zoom_level_changed.connect(
+ self._zoom_hid = shell.get_model().zoom_level_changed.connect(
self.__zoom_level_changed_cb)
- self._alt_timeout_sid = None
+ self._alt_held = False
def add_alert(self, alert):
self._alert = alert
@@ -128,8 +145,8 @@ def remove_alert(self, alert):
def _show_alert(self):
if self._alert:
- self._box.pack_start(self._alert, False, False, 0)
- self._box.reorder_child(self._alert, 1)
+ self._box.append(self._alert)
+ self._box.reorder_child_after(self._alert, self._toolbar)
def _hide_alert(self):
if self._alert:
@@ -143,24 +160,16 @@ def _deactivate_view(self, level):
elif level == ShellModel.ZOOM_MESH:
self._mesh_box.suspend()
- def __screen_size_changed_cb(self, screen):
- screen = Gdk.Screen.get_default()
- n = screen.get_number()
- rect = screen.get_monitor_geometry(n)
- geometry = Gdk.Geometry()
- geometry.max_width = geometry.base_width = geometry.min_width = \
- rect.width
- geometry.max_height = geometry.base_height = geometry.min_height = \
- rect.height
- geometry.width_inc = geometry.height_inc = geometry.min_aspect = \
- geometry.max_aspect = 1
- hints = Gdk.WindowHints(Gdk.WindowHints.ASPECT |
- Gdk.WindowHints.BASE_SIZE |
- Gdk.WindowHints.MAX_SIZE |
- Gdk.WindowHints.MIN_SIZE)
- workarea = screen.get_monitor_workarea(n)
- self.move(workarea.x, workarea.y)
- self.set_geometry_hints(None, geometry, hints)
+ def __screen_size_changed_cb(self, *args):
+ if self._monitors.get_n_items() > 0:
+ monitor = self._monitors.get_item(0)
+ if self._current_monitor != monitor:
+ if self._current_monitor and self._monitor_geom_hid:
+ self._current_monitor.disconnect(self._monitor_geom_hid)
+ self._current_monitor = monitor
+ self._monitor_geom_hid = monitor.connect('notify::geometry', self.__screen_size_changed_cb)
+ geom = monitor.get_geometry()
+ self.set_size_request(geom.width, geom.height)
def _activate_view(self, level):
if level == ShellModel.ZOOM_HOME:
@@ -168,65 +177,50 @@ def _activate_view(self, level):
elif level == ShellModel.ZOOM_MESH:
self._mesh_box.resume()
- def _visibility_notify_event_cb(self, window, event):
- fully_obscured = (
- event.get_state() == Gdk.VisibilityState.FULLY_OBSCURED)
- if self._fully_obscured == fully_obscured:
- return
- self._fully_obscured = fully_obscured
-
- if fully_obscured:
- self._deactivate_view(shell.get_model().zoom_level)
- else:
- self._activate_view(shell.get_model().zoom_level)
-
- def __is_alt(self, event):
- # When shift is on, becomes
- shift = (event.state & Gdk.ModifierType.SHIFT_MASK) == 1
- return event.keyval in [Gdk.KEY_Alt_L, Gdk.KEY_Alt_R] or \
- event.keyval in [Gdk.KEY_Meta_L, Gdk.KEY_Meta_R] and shift
+ def __is_alt(self, keyval, state):
+ # with Shift held, Alt reports as Meta
+ shift = (state & Gdk.ModifierType.SHIFT_MASK) != 0
+ return keyval in [Gdk.KEY_Alt_L, Gdk.KEY_Alt_R] or \
+ keyval in [Gdk.KEY_Meta_L, Gdk.KEY_Meta_R] and shift
- def __key_press_event_cb(self, window, event):
- if self.__is_alt(event) and not self._alt_timeout_sid:
+ def __key_pressed_cb(self, controller, keyval, keycode, state):
+ if self.__is_alt(keyval, state) and not getattr(self, '_alt_held', False):
self._home_box.set_resume_mode(not self._resume_mode)
- self._alt_timeout_sid = GLib.timeout_add(100,
- self.__alt_timeout_cb)
+ self._alt_held = True
- if not self._toolbar.search_entry.props.has_focus:
- self._toolbar.search_entry.grab_focus()
+ _modifier_mask = (Gdk.ModifierType.CONTROL_MASK |
+ Gdk.ModifierType.ALT_MASK |
+ Gdk.ModifierType.SUPER_MASK |
+ Gdk.ModifierType.META_MASK)
+ has_modifier = bool(state & _modifier_mask)
- return False
+ unicode_char = Gdk.keyval_to_unicode(keyval)
+ is_printable = unicode_char >= 0x20 and unicode_char != 0x7F
- def __key_release_event_cb(self, window, event):
- if self.__is_alt(event) and self._alt_timeout_sid:
- self._home_box.set_resume_mode(self._resume_mode)
- GLib.source_remove(self._alt_timeout_sid)
- self._alt_timeout_sid = None
+ if is_printable and not has_modifier:
+ search_entry = self._toolbar.search_entry
+ if search_entry is None:
+ return False
- return False
+ has_focus = bool(search_entry.get_state_flags() & Gtk.StateFlags.FOCUS_WITHIN)
- def __alt_timeout_cb(self):
- display = Gdk.Display.get_default()
- screen_, x_, y_, modmask = display.get_pointer()
- if modmask & Gdk.ModifierType.MOD1_MASK:
- return True
+ if not has_focus:
+ search_entry.grab_focus()
- self._home_box.set_resume_mode(self._resume_mode)
- if self._alt_timeout_sid:
- GLib.source_remove(self._alt_timeout_sid)
- self._alt_timeout_sid = None
+ return False
+
+ def __key_released_cb(self, controller, keyval, keycode, state):
+ if self.__is_alt(keyval, state) and getattr(self, '_alt_held', False):
+ self._home_box.set_resume_mode(self._resume_mode)
+ self._alt_held = False
return False
- def __map_event_cb(self, widget, event):
- # have to make the desktop window active
- # since metacity doesn't make it on startup
- timestamp = event.get_time()
- window = self.get_window()
- if not timestamp:
- timestamp = Gtk.get_current_event_time()
- window.focus(timestamp)
+ def __focus_out_event_cb(self, controller):
+ if getattr(self, '_alt_held', False):
+ self._home_box.set_resume_mode(self._resume_mode)
+ self._alt_held = False
def __zoom_level_changed_cb(self, **kwargs):
old_level = kwargs['old_level']
@@ -238,11 +232,7 @@ def __zoom_level_changed_cb(self, **kwargs):
if old_level != ShellModel.ZOOM_ACTIVITY and \
new_level != ShellModel.ZOOM_ACTIVITY:
self._hide_alert()
- children = self._box.get_children()
- if len(children) >= 2:
- self._box.remove(children[1])
- self._box.pack_start(self._transition_box, True, True, 0)
- self._transition_box.show()
+ self._view_stack.set_visible_child_name('transition')
if new_level == ShellModel.ZOOM_HOME:
end_size = style.XLARGE_ICON_SIZE
@@ -270,47 +260,57 @@ def _update_view(self, level):
return
self._hide_alert()
- children = self._box.get_children()
- if len(children) >= 2:
- self._box.remove(children[1])
if level == ShellModel.ZOOM_HOME:
- self._box.pack_start(self._home_box, True, True, 0)
- self._home_box.show()
+ self._view_stack.set_visible_child_name('home')
self._toolbar.clear_query()
self._toolbar.set_placeholder_text_for_view(_('Home'))
self._toolbar.show_view_buttons()
+ GLib.idle_add(self._home_box.grab_focus)
elif level == ShellModel.ZOOM_GROUP:
- self._box.pack_start(self._group_box, True, True, 0)
- self._group_box.show()
+ self._view_stack.set_visible_child_name('group')
self._toolbar.clear_query()
self._toolbar.set_placeholder_text_for_view(_('Group'))
- self._toolbar.hide_view_buttons()
+ self._toolbar.show_view_buttons()
+ GLib.idle_add(self._group_box.grab_focus)
elif level == ShellModel.ZOOM_MESH:
- self._box.pack_start(self._mesh_box, True, True, 0)
- self._mesh_box.show()
+ self._view_stack.set_visible_child_name('mesh')
self._toolbar.clear_query()
self._toolbar.set_placeholder_text_for_view(_('Neighborhood'))
self._toolbar.hide_view_buttons()
+ GLib.idle_add(self._mesh_box.grab_focus)
self._show_alert()
+ def do_dispose(self):
+ if hasattr(self, '_zoom_hid'):
+ shell.get_model().zoom_level_changed.disconnect(self._zoom_hid)
+ del self._zoom_hid
+ if hasattr(self, '_monitors_hid') and self._monitors:
+ self._monitors.disconnect(self._monitors_hid)
+ del self._monitors_hid
+ if hasattr(self, '_monitor_geom_hid') and self._monitor_geom_hid and self._current_monitor:
+ self._current_monitor.disconnect(self._monitor_geom_hid)
+ self._monitor_geom_hid = None
+ self._current_monitor = None
+ super().do_dispose()
+
def get_home_box(self):
return self._home_box
def busy(self):
if self._busy_count == 0:
- self._old_cursor = self.get_window().get_cursor()
- self._set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
+ self._old_cursor = self.get_cursor()
+ self._set_cursor(Gdk.Cursor.new_from_name('wait'))
self._busy_count += 1
def unbusy(self):
self._busy_count -= 1
if self._busy_count == 0:
+ # Restore previous cursor — None means system default (arrow)
self._set_cursor(self._old_cursor)
def _set_cursor(self, cursor):
- self.get_window().set_cursor(cursor)
- Gdk.flush()
+ self.set_cursor(cursor)
def get_instance():
@@ -318,3 +318,4 @@ def get_instance():
if not _instance:
_instance = HomeWindow()
return _instance
+
diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py
index f1f0d7b357..5a3449b8e3 100644
--- a/src/jarabe/desktop/keydialog.py
+++ b/src/jarabe/desktop/keydialog.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2009 One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
@@ -18,13 +18,12 @@
from gettext import gettext as _
from gi.repository import Gtk
-from gi.repository import Gdk
+
import dbus
-from sugar4.graphics import style
-from jarabe.model import network
+from jarabe.model import network
IW_AUTH_ALG_OPEN_SYSTEM = 'open'
IW_AUTH_ALG_SHARED_KEY = 'shared'
@@ -64,8 +63,9 @@ def hash_passphrase(passphrase):
elif len(passphrase) < 64:
while len(passphrase) < 64:
passphrase += passphrase[:64 - len(passphrase)]
- passphrase = hashlib.md5(passphrase).digest()
- return string_to_hex(passphrase)[:26]
+ # Use hexdigest() directly: digest() returns raw bytes that are not
+ # guaranteed to be valid UTF-8, causing UnicodeDecodeError in Python 3.
+ return hashlib.md5(passphrase.encode('utf-8')).hexdigest()[:26]
class CanceledKeyRequestError(dbus.DBusException):
@@ -78,7 +78,8 @@ def __init__(self):
class KeyDialog(Gtk.Dialog):
def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, response):
- Gtk.Dialog.__init__(self, flags=Gtk.DialogFlags.MODAL)
+ super().__init__()
+ self.set_modal(True)
self.set_title('Wireless Key Required')
self._response = response
@@ -89,30 +90,38 @@ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, response):
self._rsn_flags = rsn_flags
self._dev_caps = dev_caps
+ # Set spacing once for all children in the content area
+ self.get_content_area().set_spacing(6)
+
display_name = network.ssid_to_display_name(ssid)
label = Gtk.Label(label=_("A wireless encryption key is required for\n"
" the wireless network '%s'.")
% (display_name, ))
- self.vbox.pack_start(label, True, True, 0)
+ label.set_hexpand(True)
+ label.set_vexpand(True)
+ self.get_content_area().append(label)
+ label.set_visible(True)
- self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OK, Gtk.ResponseType.OK)
+ self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
+ self.add_button(_("OK"), Gtk.ResponseType.OK)
self.set_default_response(Gtk.ResponseType.OK)
def add_key_entry(self):
self._entry = Gtk.Entry(visibility=True)
self._entry.connect('changed', self._update_response_sensitivity)
self._entry.connect('activate', self.__entry_activate_cb)
- self.vbox.pack_start(self._entry, True, True, 0)
- self.vbox.set_spacing(6)
+ self._entry.set_hexpand(True)
+ self._entry.set_vexpand(True)
+ self.get_content_area().append(self._entry)
+ self._entry.set_visible(True)
- button = Gtk.CheckButton(_("Show Password"))
- button.props.draw_indicator = True
- button.props.active = self._entry.get_visibility()
+ button = Gtk.CheckButton(label=_("Show Password"))
+ button.set_active(self._entry.get_visibility())
button.connect("toggled", self.__button_toggled_cb)
- self.vbox.pack_start(button, True, True, 0)
-
- self.vbox.show_all()
+ button.set_hexpand(True)
+ button.set_vexpand(True)
+ self.get_content_area().append(button)
+ button.set_visible(True)
self._update_response_sensitivity()
self._entry.grab_focus()
@@ -137,68 +146,84 @@ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, response):
dev_caps, response)
# WEP key type
- self.key_store = Gtk.ListStore(str, int)
- self.key_store.append(['Passphrase (128-bit)', WEP_PASSPHRASE])
- self.key_store.append(['Hex (40/128-bit)', WEP_HEX])
- self.key_store.append(['ASCII (40/128-bit)', WEP_ASCII])
-
- self.key_combo = Gtk.ComboBox(model=self.key_store)
- cell = Gtk.CellRendererText()
- self.key_combo.pack_start(cell, True)
- self.key_combo.add_attribute(cell, 'text', 0)
- self.key_combo.set_active(0)
- self.key_combo.connect('changed', self.__key_combo_changed_cb)
+ self.key_combo = Gtk.DropDown.new_from_strings([
+ _('Passphrase (128-bit)'),
+ _('Hex (40/128-bit)'),
+ _('ASCII (40/128-bit)')
+ ])
+ self.key_combo.connect('notify::selected', self.__key_combo_changed_cb)
+ self.key_combo.set_hexpand(True)
+ self.key_combo.set_vexpand(True)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- hbox.pack_start(Gtk.Label(_('Key Type:')), True, True, 0)
- hbox.pack_start(self.key_combo, True, True, 0)
- hbox.show_all()
- self.vbox.pack_start(hbox, True, True, 0)
+ label1 = Gtk.Label(label=_('Key Type:'))
+ label1.set_hexpand(True)
+ label1.set_vexpand(True)
+ hbox.append(label1)
+ label1.set_visible(True)
+
+ hbox.append(self.key_combo)
+ self.key_combo.set_visible(True)
+
+ hbox.set_hexpand(True)
+ hbox.set_vexpand(True)
+ self.get_content_area().append(hbox)
+ hbox.set_visible(True)
# Key entry field
self.add_key_entry()
# WEP authentication mode
- self.auth_store = Gtk.ListStore(str, str)
- self.auth_store.append(['Open System', IW_AUTH_ALG_OPEN_SYSTEM])
- self.auth_store.append(['Shared Key', IW_AUTH_ALG_SHARED_KEY])
-
- self.auth_combo = Gtk.ComboBox(model=self.auth_store)
- cell = Gtk.CellRendererText()
- self.auth_combo.pack_start(cell, True)
- self.auth_combo.add_attribute(cell, 'text', 0)
- self.auth_combo.set_active(0)
-
- hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- hbox.pack_start(Gtk.Label(_('Authentication Type:')), True, True, 0)
- hbox.pack_start(self.auth_combo, True, True, 0)
- hbox.show_all()
-
- self.vbox.pack_start(hbox, True, True, 0)
-
- def __key_combo_changed_cb(self, widget):
+ self.auth_combo = Gtk.DropDown.new_from_strings([
+ _('Open System'),
+ _('Shared Key')
+ ])
+ self.auth_combo.set_hexpand(True)
+ self.auth_combo.set_vexpand(True)
+
+ hbox2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ label2 = Gtk.Label(label=_('Authentication Type:'))
+ label2.set_hexpand(True)
+ label2.set_vexpand(True)
+ hbox2.append(label2)
+ label2.set_visible(True)
+
+ hbox2.append(self.auth_combo)
+ self.auth_combo.set_visible(True)
+
+ hbox2.set_hexpand(True)
+ hbox2.set_vexpand(True)
+ self.get_content_area().append(hbox2)
+ hbox2.set_visible(True)
+
+ def __key_combo_changed_cb(self, widget, pspec):
self._update_response_sensitivity()
def _get_security(self):
key = self._entry.get_text()
- it = self.key_combo.get_active_iter()
- (key_type, ) = self.key_store.get(it, 1)
+ selected_key = self.key_combo.get_selected()
+ if selected_key == 0:
+ key_type = WEP_PASSPHRASE
+ elif selected_key == 1:
+ key_type = WEP_HEX
+ else:
+ key_type = WEP_ASCII
if key_type == WEP_PASSPHRASE:
key = hash_passphrase(key)
elif key_type == WEP_ASCII:
key = string_to_hex(key)
- it = self.auth_combo.get_active_iter()
- (auth_alg, ) = self.auth_store.get(it, 1)
+ selected_auth = self.auth_combo.get_selected()
+ auth_alg = IW_AUTH_ALG_OPEN_SYSTEM if selected_auth == 0 else IW_AUTH_ALG_SHARED_KEY
return (key, auth_alg)
def print_security(self):
(key, auth_alg) = self._get_security()
print('Key: %s' % key)
- print('Auth: %d' % auth_alg)
+ print('Auth: %s' % auth_alg)
def create_security(self):
(key, auth_alg) = self._get_security()
@@ -207,8 +232,14 @@ def create_security(self):
def _update_response_sensitivity(self, ignored=None):
key = self._entry.get_text()
- it = self.key_combo.get_active_iter()
- (key_type, ) = self.key_store.get(it, 1)
+
+ selected_key = self.key_combo.get_selected()
+ if selected_key == 0:
+ key_type = WEP_PASSPHRASE
+ elif selected_key == 1:
+ key_type = WEP_HEX
+ else:
+ key_type = WEP_ASCII
valid = False
if key_type == WEP_PASSPHRASE:
@@ -233,21 +264,24 @@ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, response):
dev_caps, response)
self.add_key_entry()
- self.store = Gtk.ListStore(str)
- self.store.append([_('WPA & WPA2 Personal')])
-
- self.combo = Gtk.ComboBox(model=self.store)
- cell = Gtk.CellRendererText()
- self.combo.pack_start(cell, True)
- self.combo.add_attribute(cell, 'text', 0)
- self.combo.set_active(0)
+ self.combo = Gtk.DropDown.new_from_strings([_('WPA & WPA2 Personal')])
+ self.combo.set_hexpand(True)
+ self.combo.set_vexpand(True)
self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- self.hbox.pack_start(Gtk.Label(_('Wireless Security:')), True, True, 0)
- self.hbox.pack_start(self.combo, True, True, 0)
- self.hbox.show_all()
-
- self.vbox.pack_start(self.hbox, True, True, 0)
+ label = Gtk.Label(label=_('Wireless Security:'))
+ label.set_hexpand(True)
+ label.set_vexpand(True)
+ self.hbox.append(label)
+ label.set_visible(True)
+
+ self.hbox.append(self.combo)
+ self.combo.set_visible(True)
+
+ self.hbox.set_hexpand(True)
+ self.hbox.set_vexpand(True)
+ self.get_content_area().append(self.hbox)
+ self.hbox.set_visible(True)
def _get_security(self):
return self._entry.get_text()
@@ -285,10 +319,7 @@ def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, response):
dev_caps, response)
key_dialog.connect('response', _key_dialog_response_cb)
- key_dialog.show_all()
- width, height = key_dialog.get_size()
- key_dialog.move(Gdk.Screen.width() / 2 - width / 2,
- style.GRID_CELL_SIZE * 2)
+ key_dialog.set_visible(True)
def _key_dialog_response_cb(key_dialog, response_id):
@@ -298,8 +329,8 @@ def _key_dialog_response_cb(key_dialog, response_id):
secrets = key_dialog.create_security()
if response_id in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.NONE,
- Gtk.ResponseType.DELETE_EVENT]:
- # key dialog dialog was canceled; send the error back to NM
+ Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
+ # key dialog was canceled; send the error back to NM
response.set_error(CanceledKeyRequestError())
elif response_id == Gtk.ResponseType.OK:
if not secrets:
@@ -308,4 +339,4 @@ def _key_dialog_response_cb(key_dialog, response_id):
else:
raise RuntimeError('Unhandled key dialog response %d' % response_id)
- key_dialog.destroy()
+ key_dialog.close()
diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
index 9c73f80941..21f5efdc00 100644
--- a/src/jarabe/desktop/meshbox.py
+++ b/src/jarabe/desktop/meshbox.py
@@ -46,7 +46,6 @@
from jarabe.model.adhoc import get_adhoc_manager_instance
from jarabe.journal import misc
-
_FILTERED_ALPHA = 0.33
@@ -82,19 +81,19 @@ def create_palette(self):
item = PaletteMenuItem(_('Resume'))
icon = Icon(
pixel_size=style.SMALL_ICON_SIZE, icon_name='activity-start')
- item.set_image(icon)
- item.connect('activate', self.__palette_item_clicked_cb)
+ item.set_icon_widget(icon)
+ item.connect('item-activated', self.__palette_item_clicked_cb)
menu_box.append_item(item)
elif not private and is_joinable:
item = PaletteMenuItem(_('Join'))
icon = Icon(
pixel_size=style.SMALL_ICON_SIZE, icon_name='activity-start')
- item.set_image(icon)
- item.connect('activate', self.__palette_item_clicked_cb)
+ item.set_icon_widget(icon)
+ item.connect('item-activated', self.__palette_item_clicked_cb)
menu_box.append_item(item)
+ menu_box.set_visible(True)
palette.set_content(menu_box)
- menu_box.show_all()
self.connect_to_palette_pop_events(palette)
return palette
@@ -117,7 +116,6 @@ def __init__(self, model):
self._icons = {}
self._icon = self._create_icon()
- self._icon.show()
self.add_icon(self._icon, center=True)
self._icon.palette_invoker.cache_palette = False
@@ -147,13 +145,11 @@ def _add_buddy(self, buddy):
icon = BuddyIcon(buddy, style.STANDARD_ICON_SIZE)
self._icons[buddy.props.key] = icon
self.add_icon(icon)
- icon.show()
def __buddy_removed_cb(self, activity, buddy):
icon = self._icons[buddy.props.key]
del self._icons[buddy.props.key]
self.remove(icon)
- icon.destroy()
def set_filter(self, query):
text_to_check = self._model.bundle.get_name().lower() + \
@@ -180,7 +176,7 @@ class DeviceObserver(GObject.GObject):
}
def __init__(self, device):
- GObject.GObject.__init__(self)
+ super().__init__()
self._bus = dbus.SystemBus()
self.device = device
@@ -369,7 +365,7 @@ def __init__(self, toolbar):
owner_icon = BuddyIcon(get_owner_instance(),
style.STANDARD_ICON_SIZE & ~1)
ViewContainer.__init__(self, layout, owner_icon)
- self.set_can_focus(False)
+ self.set_focusable(False)
self.wireless_networks = {}
self._adhoc_manager = None
@@ -384,8 +380,7 @@ def __init__(self, toolbar):
self._query = ''
toolbar.connect('query-changed', self._toolbar_query_changed_cb)
- toolbar.search_entry.connect('icon-press',
- self.__clear_icon_pressed_cb)
+
for buddy_model in self._model.get_buddies():
self._add_buddy(buddy_model)
@@ -423,7 +418,6 @@ def _add_buddy(self, buddy_model):
return
icon = BuddyIcon(buddy_model)
self.add(icon)
- icon.show()
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
@@ -448,7 +442,6 @@ def __buddy_notify_current_activity_cb(self, buddy_model, pspec):
def _add_activity(self, activity_model):
icon = ActivityView(activity_model)
self.add(icon)
- icon.show()
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
@@ -471,7 +464,6 @@ def _add_ap_to_network(self, ap):
icon = WirelessNetworkView(ap)
self.wireless_networks[hash_value] = icon
self.add(icon)
- icon.show()
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
@@ -565,13 +557,11 @@ def remove_adhoc_networks(self):
def _add_adhoc_network_icon(self, channel):
icon = SugarAdhocView(channel)
self.add(icon)
- icon.show()
self._adhoc_networks.append(icon)
def _add_olpc_mesh_icon(self, mesh_mgr, channel):
icon = OlpcMeshView(mesh_mgr, channel)
self.add(icon)
- icon.show()
self._mesh.append(icon)
def enable_olpc_mesh(self, mesh_device):
@@ -616,5 +606,5 @@ def _toolbar_query_changed_cb(self, toolbar, query):
if hasattr(icon, 'set_filter'):
icon.set_filter(self._query)
- def __clear_icon_pressed_cb(self, entry, icon_pos, event):
+ def __clear_icon_pressed_cb(self, entry, icon_pos):
self.grab_focus()
diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py
index 5d1b5c8965..b791bb387c 100644
--- a/src/jarabe/desktop/networkviews.py
+++ b/src/jarabe/desktop/networkviews.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
# Copyright (C) 2009-2010 One Laptop per Child
#
@@ -42,7 +42,6 @@
from jarabe.model.network import WirelessSecurity
from jarabe.model.adhoc import get_adhoc_manager_instance
-
_AP_ICON_NAME = 'network-wireless'
_OLPC_MESH_ICON_NAME = 'network-mesh'
@@ -52,7 +51,14 @@
class WirelessNetworkView(EventPulsingIcon):
def __init__(self, initial_ap):
- EventPulsingIcon.__init__(self, pixel_size=style.STANDARD_ICON_SIZE,
+ if initial_ap.mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(initial_ap.ssid):
+ icon_name = 'network-adhoc-1' # Default channel
+ else:
+ icon_name = get_icon_state(_AP_ICON_NAME, initial_ap.strength)
+
+ EventPulsingIcon.__init__(self, icon_name=icon_name,
+ pixel_size=style.STANDARD_ICON_SIZE,
cache=True)
self._bus = dbus.SystemBus()
self._access_points = {initial_ap.model.object_path: initial_ap}
@@ -126,27 +132,24 @@ def _create_palette(self):
self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self._connect_item = PaletteMenuItem(_('Connect'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='dialog-ok')
- self._connect_item.set_image(icon)
- self._connect_item.connect('activate', self.__connect_activate_cb)
- self.menu_box.add(self._connect_item)
+ self._connect_item = PaletteMenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('item-activated', self.__connect_activate_cb)
+ self.menu_box.append(self._connect_item)
+ self._connect_item.set_visible(True)
- self._disconnect_item = PaletteMenuItem(_('Disconnect'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='media-eject')
- self._disconnect_item.set_image(icon)
+ self._disconnect_item = PaletteMenuItem(_('Disconnect'), 'media-eject')
self._disconnect_item.connect(
- 'activate', self.__disconnect_activate_cb)
- self.menu_box.add(self._disconnect_item)
+ 'item-activated', self.__disconnect_activate_cb)
+ self.menu_box.append(self._disconnect_item)
+ self._disconnect_item.set_visible(True)
- self._forget_item = PaletteMenuItem(_('Forget'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='list-remove')
- self._forget_item.set_image(icon)
- self._forget_item.connect('activate', self.__forget_activate_cb)
- self.menu_box.add(self._forget_item)
+ self._forget_item = PaletteMenuItem(_('Forget'), 'list-remove')
+ self._forget_item.connect('item-activated', self.__forget_activate_cb)
+ self.menu_box.append(self._forget_item)
+ self._forget_item.set_visible(True)
p.set_content(self.menu_box)
- self.menu_box.show_all()
+ self.menu_box.set_visible(True)
self.connect_to_palette_pop_events(p)
@@ -270,21 +273,21 @@ def _update_state(self):
state == network.NM_DEVICE_STATE_NEED_AUTH or \
state == network.NM_DEVICE_STATE_IP_CONFIG:
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connecting...')
self.props.pulsing = True
elif state == network.NM_DEVICE_STATE_ACTIVATED:
network.set_connected()
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connected')
self.props.pulsing = False
else:
if self._disconnect_item:
- self._disconnect_item.hide()
- self._connect_item.show()
+ self._disconnect_item.set_visible(False)
+ self._connect_item.set_visible(True)
self._palette.props.secondary_text = None
self.props.pulsing = False
@@ -292,9 +295,9 @@ def _update_color(self):
self.props.base_color = self._color
if self._filtered:
self.props.pulsing = False
- self.props.alpha = _FILTERED_ALPHA
+ self.set_opacity(_FILTERED_ALPHA)
else:
- self.props.alpha = 1.0
+ self.set_opacity(1.0)
def __disconnect_activate_cb(self, item):
ap_paths = list(self._access_points.keys())
@@ -529,22 +532,19 @@ def _create_palette(self):
self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self._connect_item = PaletteMenuItem(_('Connect'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='dialog-ok')
- self._connect_item.set_image(icon)
- self._connect_item.connect('activate', self.__connect_activate_cb)
- self.menu_box.add(self._connect_item)
+ self._connect_item = PaletteMenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('item-activated', self.__connect_activate_cb)
+ self.menu_box.append(self._connect_item)
+ self._connect_item.set_visible(True)
- self._disconnect_item = PaletteMenuItem(_('Disconnect'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='media-eject')
- self._disconnect_item.set_image(icon)
+ self._disconnect_item = PaletteMenuItem(_('Disconnect'), 'media-eject')
self._disconnect_item.connect(
- 'activate', self.__disconnect_activate_cb)
- self.menu_box.add(self._disconnect_item)
+ 'item-activated', self.__disconnect_activate_cb)
+ self.menu_box.append(self._disconnect_item)
+ self._disconnect_item.set_visible(False)
palette_.set_content(self.menu_box)
- self.menu_box.show_all()
- self._disconnect_item.hide()
+ self.menu_box.set_visible(True)
self.connect_to_palette_pop_events(palette_)
@@ -575,20 +575,20 @@ def __state_changed_cb(self, adhoc_manager, channel, device_state):
if (state >= network.NM_DEVICE_STATE_PREPARE) and \
(state <= network.NM_DEVICE_STATE_IP_CONFIG):
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connecting...')
self.props.pulsing = True
elif state == network.NM_DEVICE_STATE_ACTIVATED:
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connected')
self.props.pulsing = False
else:
if self._disconnect_item:
- self._disconnect_item.hide()
- self._connect_item.show()
+ self._disconnect_item.set_visible(False)
+ self._connect_item.set_visible(True)
self._palette.props.secondary_text = None
self.props.pulsing = False
self._update_color()
@@ -597,9 +597,9 @@ def _update_color(self):
self.props.base_color = self._state_color
if self._filtered:
self.props.pulsing = False
- self.alpha = _FILTERED_ALPHA
+ self.set_opacity(_FILTERED_ALPHA)
else:
- self.alpha = 1.0
+ self.set_opacity(1.0)
def __members_changed_cb(self, adhoc_manager, channel, has_members):
if channel == self._channel:
@@ -673,14 +673,13 @@ def _create_palette(self):
self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self._connect_item = PaletteMenuItem(_('Connect'))
- icon = Icon(pixel_size=style.SMALL_ICON_SIZE, icon_name='dialog-ok')
- self._connect_item.set_image(icon)
- self._connect_item.connect('activate', self.__connect_activate_cb)
- self.menu_box.add(self._connect_item)
+ self._connect_item = PaletteMenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('item-activated', self.__connect_activate_cb)
+ self.menu_box.append(self._connect_item)
+ self._connect_item.set_visible(True)
_palette.set_content(self.menu_box)
- self.menu_box.show_all()
+ self.menu_box.set_visible(True)
return _palette
@@ -720,29 +719,29 @@ def _update(self):
network.NM_DEVICE_STATE_NEED_AUTH,
network.NM_DEVICE_STATE_IP_CONFIG]:
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connecting...')
self.props.pulsing = True
elif state == network.NM_DEVICE_STATE_ACTIVATED:
if self._disconnect_item:
- self._disconnect_item.show()
- self._connect_item.hide()
+ self._disconnect_item.set_visible(True)
+ self._connect_item.set_visible(False)
self._palette.props.secondary_text = _('Connected')
self.props.pulsing = False
else:
if self._disconnect_item:
- self._disconnect_item.hide()
- self._connect_item.show()
+ self._disconnect_item.set_visible(False)
+ self._connect_item.set_visible(True)
self._palette.props.secondary_text = None
self.props.pulsing = False
def _update_color(self):
self.props.base_color = profile.get_color()
if self._filtered:
- self.alpha = _FILTERED_ALPHA
+ self.set_opacity(_FILTERED_ALPHA)
else:
- self.alpha = 1.0
+ self.set_opacity(1.0)
def __connect_activate_cb(self, icon):
self._connect()
diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py
index 89b7ea5efc..599b5a2fe4 100644
--- a/src/jarabe/desktop/schoolserver.py
+++ b/src/jarabe/desktop/schoolserver.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 One Laptop Per Child
+# Copyright (C) 2007, 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/desktop/snowflakelayout.py b/src/jarabe/desktop/snowflakelayout.py
index 0ea36e37ba..2cefd1301e 100644
--- a/src/jarabe/desktop/snowflakelayout.py
+++ b/src/jarabe/desktop/snowflakelayout.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,49 +17,34 @@
from gi.repository import Gtk
from gi.repository import Gdk
+import gi
+gi.require_version('Gsk', '4.0')
+gi.require_version('Graphene', '1.0')
+from gi.repository import Gsk, Graphene
from sugar4.graphics import style
-
_BASE_DISTANCE = style.zoom(25)
_CHILDREN_FACTOR = style.zoom(3)
-class SnowflakeLayout(Gtk.Container):
+class SnowflakeLayout(Gtk.Widget):
__gtype_name__ = 'SugarSnowflakeLayout'
def __init__(self):
- Gtk.Container.__init__(self)
- self.set_has_window(False)
+ super().__init__()
self._nflakes = 0
self._children = {}
- def do_realize(self):
- self.set_realized(True)
- self.set_window(self.get_parent_window())
+ def do_dispose(self):
for child in list(self._children.keys()):
- child.set_parent_window(self.get_parent_window())
- self.queue_resize()
+ child.unparent()
+ self._children.clear()
+ self._nflakes = 0
- def do_add(self, child):
- if child.get_realized():
- child.set_parent_window(self.get_parent_window())
+ def add(self, child):
child.set_parent(self)
- def do_forall(self, include_internals, callback):
- for child in list(self._children.keys()):
- callback(child)
-
- def do_remove(self, child):
- child.unparent()
-
- def add_icon(self, child, center=False):
- if not center:
- self._nflakes += 1
-
- self._children[child] = center
- self.add(child)
-
def remove(self, child):
if child not in self._children:
return
@@ -68,44 +53,40 @@ def remove(self, child):
self._nflakes -= 1
del self._children[child]
- self.remove(child)
+ child.unparent()
- def do_get_preferred_size(self):
- size = self._calculate_size()
- requisition = Gtk.Requisition()
- requisition.width = size
- requisition.height = size
- return (requisition, requisition)
+ def add_icon(self, child, center=False):
+ if not center:
+ self._nflakes += 1
- def do_get_preferred_width(self):
- size = self._calculate_size()
- return (size, size)
+ self._children[child] = center
+ self.add(child)
- def do_get_preferred_height(self):
- size = self._calculate_size()
- return (size, size)
+ def _child_size(self, child):
+ _, nat_w, _, _ = child.measure(Gtk.Orientation.HORIZONTAL, -1)
+ _, nat_h, _, _ = child.measure(Gtk.Orientation.VERTICAL, -1)
+ return nat_w, nat_h
- def do_size_allocate(self, allocation):
- self.set_allocation(allocation)
+ def do_measure(self, orientation, for_size):
+ size = self._calculate_size()
+ return (size, size, -1, -1)
+ def do_size_allocate(self, width, height, baseline):
r = self._get_radius()
index = 0
for child, centered in list(self._children.items()):
- child_request = child.size_request()
- child_width, child_height = \
- child_request.width, child_request.height
+ child_width, child_height = self._child_size(child)
+
rect = Gdk.Rectangle()
- rect.x = 0
- rect.y = 0
rect.width = child_width
rect.height = child_height
- width = allocation.width - child_width
- height = allocation.height - child_height
+ w = width - child_width
+ h = height - child_height
if centered:
- rect.x = allocation.x + width / 2
- rect.y = allocation.y + height / 2
+ rect.x = w / 2
+ rect.y = h / 2
else:
angle = 2 * math.pi * index / self._nflakes
@@ -115,30 +96,30 @@ def do_size_allocate(self, allocation):
dx = math.cos(angle) * r
dy = math.sin(angle) * r
- rect.x = int(allocation.x + width / 2 + dx)
- rect.y = int(allocation.y + height / 2 + dy)
+ rect.x = int(w / 2 + dx)
+ rect.y = int(h / 2 + dy)
index += 1
- child.size_allocate(rect)
+ transform = Gsk.Transform.new().translate(Graphene.Point().init(rect.x, rect.y))
+ child.allocate(rect.width, rect.height, -1, transform)
def _get_radius(self):
radius = int(_BASE_DISTANCE + _CHILDREN_FACTOR * self._nflakes)
for child, centered in list(self._children.items()):
if centered:
- child_request = child.size_request()
- child_width, child_height = \
- child_request.width, child_request.height
+ child_width, child_height = self._child_size(child)
radius += max(child_width, child_height) / 2
-
return radius
def _calculate_size(self):
thickness = 0
for child in list(self._children.keys()):
- child_request = child.size_request()
- child_width, child_height = \
- child_request.width, child_request.height
+ child_width, child_height = self._child_size(child)
thickness = max(thickness, max(child_width, child_height))
return self._get_radius() * 2 + thickness
+
+ def do_snapshot(self, snapshot):
+ for child in list(self._children.keys()):
+ self.snapshot_child(child, snapshot)
diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py
index bd3dc8d151..fa0419eb08 100644
--- a/src/jarabe/desktop/transitionbox.py
+++ b/src/jarabe/desktop/transitionbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/desktop/viewcontainer.py b/src/jarabe/desktop/viewcontainer.py
index 3a2d2dd33a..f20cf5510c 100644
--- a/src/jarabe/desktop/viewcontainer.py
+++ b/src/jarabe/desktop/viewcontainer.py
@@ -16,16 +16,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from gi.repository import Gtk
+from gi.repository import Gdk, Gtk
+# We subclass Gtk.Widget and manage children manually using
+# set_parent() / unparent() and a custom LayoutManager.
-class ViewContainer(Gtk.Container):
+
+class ViewContainer(Gtk.Widget):
__gtype_name__ = 'SugarViewContainer'
def __init__(self, layout, owner_icon, activity_icon=None, **kwargs):
- Gtk.Container.__init__(self, **kwargs)
- self.set_has_window(False)
- self.set_can_focus(True)
+ super().__init__(**kwargs)
+ self.set_focusable(True)
self._activity_icon = None
self._owner_icon = None
@@ -36,22 +38,20 @@ def __init__(self, layout, owner_icon, activity_icon=None, **kwargs):
if owner_icon:
self._owner_icon = owner_icon
- self.add(self._owner_icon)
- self._owner_icon.show()
+ self._owner_icon.set_parent(self)
if activity_icon:
self._activity_icon = activity_icon
- self.add(self._activity_icon)
- self._activity_icon.show()
+ self._activity_icon.set_parent(self)
- def do_add(self, child):
+ def add(self, child):
+ """Add a child widget to this container."""
if child != self._owner_icon and child != self._activity_icon:
self._children.append(child)
- if child.get_realized():
- child.set_parent_window(self.get_parent_window())
child.set_parent(self)
- def do_remove(self, child):
+ def remove(self, child):
+ """Remove a child widget from this container."""
was_visible = child.get_visible()
if child in self._children:
self._children.remove(child)
@@ -60,23 +60,60 @@ def do_remove(self, child):
if was_visible and self.get_visible():
self.queue_resize()
- def do_size_allocate(self, allocation):
- self.set_allocation(allocation)
+ def do_measure(self, orientation, for_size):
+ min_size = 0
+ nat_size = 0
+ for child in self.get_children():
+ child_min, child_nat, _, _ = child.measure(orientation, for_size)
+ min_size = max(min_size, child_min)
+ nat_size = max(nat_size, child_nat)
+ return (min_size, nat_size, -1, -1)
+
+ def do_size_allocate(self, width, height, baseline):
+ if not self._layout:
+ return
+
+ allocation = Gdk.Rectangle()
+ allocation.x = 0
+ allocation.y = 0
+ allocation.width = width
+ allocation.height = height
+
if self._owner_icon:
self._layout.setup(allocation, self._owner_icon,
self._activity_icon)
self._layout.allocate_children(allocation, self._children)
- def do_forall(self, include_internals, callback):
- for child in self._children:
- callback(child)
+ def do_snapshot(self, snapshot):
+ if not self._layout:
+ return
+ for child in self.get_children():
+ self.snapshot_child(child, snapshot)
+
+ def get_children(self):
+ all_children = list(self._children)
if self._owner_icon:
- callback(self._owner_icon)
+ all_children.append(self._owner_icon)
if self._activity_icon:
- callback(self._activity_icon)
+ all_children.append(self._activity_icon)
+ return all_children
def set_layout(self, layout):
+ """Set the layout manager, removing all current children."""
for child in self.get_children():
self.remove(child)
self._layout = layout
+
+ def do_dispose(self):
+ while self._children:
+ child = self._children.pop()
+ child.unparent()
+ if self._owner_icon:
+ self._owner_icon.unparent()
+ self._owner_icon = None
+ if self._activity_icon:
+ self._activity_icon.unparent()
+ self._activity_icon = None
+ super().do_dispose()
+
diff --git a/src/jarabe/desktop/viewtoolbar.py b/src/jarabe/desktop/viewtoolbar.py
index 4dacca7c94..0d1dab942f 100644
--- a/src/jarabe/desktop/viewtoolbar.py
+++ b/src/jarabe/desktop/viewtoolbar.py
@@ -30,13 +30,16 @@
from sugar4.graphics import iconentry
from sugar4.graphics.radiotoolbutton import RadioToolButton
+
from jarabe.desktop import favoritesview
from jarabe.model import desktop
_AUTOSEARCH_TIMEOUT = 1000
+# Gtk.Box + regular buttons would require toolkit changes too.
+
-class ViewToolbar(Gtk.Toolbar):
+class ViewToolbar(Gtk.Box):
__gtype_name__ = 'SugarViewToolbar'
__gsignals__ = {
@@ -47,7 +50,8 @@ class ViewToolbar(Gtk.Toolbar):
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
+ self.add_css_class('sugar-toolbar')
self._favorites_views_indicies = []
for i in range(desktop.get_number_of_views()):
@@ -63,10 +67,6 @@ def __init__(self):
self._add_separator()
- tool_item = Gtk.ToolItem()
- self.insert(tool_item, -1)
- tool_item.show()
-
self.search_entry = iconentry.IconEntry()
self.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
'entry-search')
@@ -75,8 +75,9 @@ def __init__(self):
self.search_entry.set_width_chars(25)
self.search_entry.connect('activate', self._entry_activated_cb)
self.search_entry.connect('changed', self._entry_changed_cb)
- tool_item.add(self.search_entry)
- self.search_entry.show()
+
+ self.append(self.search_entry)
+ self.search_entry.set_visible(True)
self._add_separator(expand=True)
@@ -84,20 +85,23 @@ def __init__(self):
self._favorites_buttons = []
for i in range(desktop.get_number_of_views()):
self._add_favorites_button(i)
- toolitem = Gtk.ToolItem()
- toolitem.add(self._button_box)
- self.insert(toolitem, -1)
- self._button_box.show()
- toolitem.show()
self._list_button = RadioToolButton(icon_name='view-list')
- self._list_button.props.group = self._favorites_buttons[0]
+ self._list_button.set_group(self._favorites_buttons[0])
self._list_button.props.tooltip = _('List view')
self._list_button.props.accelerator = \
_('%d' % (len(self._favorites_views_indicies) + 1))
self._list_view_toggle_id = self._list_button.connect(
'toggled', self.__view_button_toggled_cb, self._list_view_index)
- self.insert(self._list_button, -1)
+ self._button_box.append(self._list_button)
+
+ if self._favorites_buttons:
+ self._favorites_buttons[0].set_active(True)
+
+ toolitem = Gtk.Box()
+ toolitem.append(self._button_box)
+ toolitem.set_visible(True)
+ self.append(toolitem)
self._add_separator()
@@ -108,19 +112,19 @@ def _add_favorites_button(self, i):
self.__view_button_toggled_cb,
self._favorites_views_indicies[i])
if i > 0:
- self._favorites_buttons[i].props.group = self._favorites_buttons[0]
- self._button_box.add(self._favorites_buttons[i])
- self._favorites_buttons[i].show()
+ self._favorites_buttons[i].set_group(self._favorites_buttons[0])
+ self._button_box.append(self._favorites_buttons[i])
+ self._favorites_buttons[i].set_visible(True)
def show_view_buttons(self):
for i in range(desktop.get_number_of_views()):
- self._favorites_buttons[i].show()
- self._list_button.show()
+ self._favorites_buttons[i].set_visible(True)
+ self._list_button.set_visible(True)
def hide_view_buttons(self):
for i in range(desktop.get_number_of_views()):
- self._favorites_buttons[i].hide()
- self._list_button.hide()
+ self._favorites_buttons[i].set_visible(False)
+ self._list_button.set_visible(False)
def clear_query(self):
self.search_entry.props.text = ''
@@ -130,15 +134,14 @@ def set_placeholder_text_for_view(self, view_name):
self.search_entry.set_placeholder_text(text)
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ separator = Gtk.Box()
+ separator.set_visible(True)
if expand:
- separator.set_expand(True)
+ separator.set_hexpand(True)
else:
separator.set_size_request(style.GRID_CELL_SIZE,
style.GRID_CELL_SIZE)
- self.insert(separator, -1)
- separator.show()
+ self.append(separator)
def _entry_activated_cb(self, entry):
if self._autosearch_timer:
@@ -153,7 +156,7 @@ def _entry_activated_cb(self, entry):
def _entry_changed_cb(self, entry):
if not entry.props.text:
- entry.activate()
+ entry.emit('activate')
return
if self._autosearch_timer:
@@ -164,7 +167,7 @@ def _entry_changed_cb(self, entry):
def _autosearch_timer_cb(self):
logging.debug('_autosearch_timer_cb')
self._autosearch_timer = None
- self.search_entry.activate()
+ self.search_entry.emit('activate')
return False
def __view_button_toggled_cb(self, button, view):
@@ -180,7 +183,7 @@ def __desktop_view_icons_changed_cb(self, model):
n = len(self._favorites_views_indicies)
self._favorites_views_indicies.append(n)
self._add_favorites_button(n)
- self._favorites_buttons[n].show()
+ self._favorites_buttons[n].set_visible(True)
elif number_of_views < len(self._favorites_views_indicies):
for i in range(len(self._favorites_views_indicies) -
number_of_views):
@@ -188,10 +191,10 @@ def __desktop_view_icons_changed_cb(self, model):
logging.debug('removing FavoritesButton %d' % (n))
button = self._favorites_buttons[n]
self._favorites_buttons.remove(button)
- button.destroy()
+ self._button_box.remove(button)
self._favorites_views_indicies.remove(
self._favorites_views_indicies[n])
- self._button_box.show()
+ self._button_box.set_visible(True)
self._list_view_index = number_of_views
self._list_button.props.accelerator = \
@@ -199,35 +202,42 @@ def __desktop_view_icons_changed_cb(self, model):
self._list_button.disconnect(self._list_view_toggle_id)
self._list_view_toggle_id = self._list_button.connect(
'toggled', self.__view_button_toggled_cb, self._list_view_index)
- self._list_button.show()
+ self._list_button.set_visible(True)
class FavoritesButton(RadioToolButton):
__gtype_name__ = 'SugarFavoritesButton'
def __init__(self, favorite_view):
- RadioToolButton.__init__(self)
+ super().__init__()
self.props.tooltip = desktop.get_view_labels()[favorite_view]
self.props.accelerator = _('%d' % (favorite_view + 1))
- self.props.group = None
+ self.set_group(None)
self.props.icon_name = desktop.get_view_icons()[favorite_view]
favorites_settings = favoritesview.get_settings(favorite_view)
self._layout = favorites_settings.layout
- # someday, this will be a Gtk.Table()
layouts_grid = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- layout_item = None
+ prev_layout_item = None
for layoutid, layoutclass in sorted(favoritesview.LAYOUT_MAP.items()):
layout_item = RadioToolButton(icon_name=layoutclass.icon_name,
- group=layout_item, active=False)
+ active=False)
+ if prev_layout_item:
+ layout_item.set_group(prev_layout_item)
+ else:
+ layout_item.set_group(None)
+ prev_layout_item = layout_item
if layoutid == self._layout:
layout_item.set_active(True)
- layouts_grid.pack_start(layout_item, True, False, 0)
+ layout_item.set_visible(True)
+ layout_item.set_hexpand(True)
+ layout_item.set_halign(Gtk.Align.CENTER)
+ layouts_grid.append(layout_item)
layout_item.connect('toggled', self.__layout_activate_cb,
layoutid, favorite_view)
- layouts_grid.show_all()
+ layouts_grid.set_visible(True)
self.props.palette.set_content(layouts_grid)
def __layout_activate_cb(self, menu_item, layout, favorite_view):
diff --git a/src/jarabe/frame/__init__.py b/src/jarabe/frame/__init__.py
index a3076f5541..2b7ae1eabc 100644
--- a/src/jarabe/frame/__init__.py
+++ b/src/jarabe/frame/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007, Red Hat, Inc.
+# Copyright (C) 2006-2007, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,7 +15,6 @@
from jarabe.frame.frame import Frame
-
_view = None
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py
index ddd6f00b02..a2030bed86 100644
--- a/src/jarabe/frame/activitiestray.py
+++ b/src/jarabe/frame/activitiestray.py
@@ -22,6 +22,7 @@
from gi.repository import GObject
from gi.repository import Gio
+from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gdk
@@ -52,7 +53,6 @@
from jarabe.frame.notification import NotificationPulsingIcon
from jarabe.frame.clipboardmenu import ClipboardMenu
from jarabe.frame.clipboardobject import ClipboardObject
-from jarabe.frame.clipboardtray import ContextMap
from jarabe.frame.clipboardobject import Format
import jarabe.frame
from jarabe.frame import clipboard
@@ -61,7 +61,7 @@
class ActivityButton(RadioToolButton):
def __init__(self, home_activity, group):
- RadioToolButton.__init__(self, group=group)
+ super().__init__(group=group)
self.set_palette_invoker(FrameWidgetInvoker(self))
self.palette_invoker.cache_palette = False
@@ -69,7 +69,6 @@ def __init__(self, home_activity, group):
self._home_activity = home_activity
self._notify_launch_hid = None
- self._context_map = ContextMap()
self._cb_object = None
self._icon = NotificationPulsingIcon()
self._icon.props.base_color = home_activity.get_icon_color()
@@ -77,28 +76,19 @@ def __init__(self, home_activity, group):
XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
style.COLOR_TOOLBAR_GREY.get_svg()))
if self._home_activity.is_journal():
- self._icon.drag_dest_set(Gtk.DestDefaults.ALL,
- [], Gdk.DragAction.COPY)
- self._icon.drag_dest_add_text_targets()
- self._icon.drag_dest_add_image_targets()
- self._icon.drag_dest_add_uri_targets()
- self._icon.connect('drag_drop', self.__drag_drop_cb)
- self._icon.connect('drag_data_received',
- self.__journal_drag_data_received_cb)
+ from gi.repository import GdkPixbuf
+ self._drop_target = Gtk.DropTarget.new(type=GObject.TYPE_STRING, actions=Gdk.DragAction.COPY)
+ self._drop_target.set_gtypes([GObject.TYPE_STRING, Gdk.FileList.__gtype__, GdkPixbuf.Pixbuf.__gtype__])
+ self._drop_target.connect('drop', self.__drag_drop_cb)
+ self._icon.add_controller(self._drop_target)
if home_activity.get_icon_path():
self._icon.props.file = home_activity.get_icon_path()
else:
- window = home_activity.get_window()
-
- if not window.get_icon_is_fallback():
- pixbuf = window.get_icon()
- self._icon.pixbuf = pixbuf
- else:
- self._icon.props.icon_name = 'image-missing'
+ self._icon.props.icon_name = 'image-missing'
self.set_icon_widget(self._icon)
- self._icon.show()
+ self._icon.set_visible(True)
if home_activity.props.launch_status == shell.Activity.LAUNCHING:
self._icon.props.pulsing = True
@@ -107,42 +97,30 @@ def __init__(self, home_activity, group):
elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
self._on_failed_launch()
- def __drag_drop_cb(self, widget, context, x, y, time):
- context_targets = context.list_targets()
- if not self._context_map.has_context(context):
- self._context_map.add_context(context, 0, len(context_targets))
-
- for target in context_targets:
- if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'):
- widget.drag_get_data(context, target, time)
-
- cb_menu = ClipboardMenu(self._cb_object)
- cb_menu._copy_to_journal()
-
- logging.debug('Saved to Journal')
- return True
-
- def __journal_drag_data_received_cb(self, widget, context, x, y, selection,
- targetType, time):
+ def __drag_drop_cb(self, target, value, x, y):
data = None
- if selection.get_pixbuf() is not None:
- data = selection.get_pixbuf()
- if len(selection.get_uris()) != 0:
- data = selection.get_uris()[0].encode()
- if selection.get_text() is not None:
- data = selection.get_text().encode()
- if data is None:
- data = selection.get_data()
-
- self._cb_object = ClipboardObject(0, "")
-
- mime_type = selection.get_data_type().name()
- cb_format = Format(mime_type, data, on_disk=False)
- self._cb_object.add_format(cb_format)
-
- logging.debug('ActivityTray: got data for target')
-
- Gtk.drag_finish(context, True, True, Gtk.get_current_event_time())
+ mime_type = "text/plain"
+ if isinstance(value, str):
+ data = value.encode()
+ elif hasattr(value, 'get_files'):
+ files = value.get_files()
+ if files:
+ data = files[0].get_uri().encode()
+ mime_type = "text/uri-list"
+ elif hasattr(value, 'savev'):
+ data = value
+ mime_type = "image/png"
+
+ if data:
+ self._cb_object = ClipboardObject(0, "")
+ cb_format = Format(mime_type, data, on_disk=False)
+ self._cb_object.add_format(cb_format)
+
+ cb_menu = ClipboardMenu(self._cb_object)
+ cb_menu._copy_to_journal()
+ logging.debug('Saved to Journal')
+ return True
+ return False
def create_palette(self):
if self._home_activity.is_journal():
@@ -184,12 +162,11 @@ class InviteButton(ToolButton):
}
def __init__(self, invite):
- ToolButton.__init__(self)
+ super().__init__()
self._invite = invite
self.connect('clicked', self.__clicked_cb)
- self.connect('destroy', self.__destroy_cb)
bundle_registry = bundleregistry.get_registry()
bundle = bundle_registry.get_bundle(invite.get_bundle_id())
@@ -201,17 +178,19 @@ def __init__(self, invite):
else:
self._icon.props.icon_name = 'image-missing'
self.set_icon_widget(self._icon)
- self._icon.show()
+ self._icon.set_visible(True)
+ invoker = FrameWidgetInvoker(self)
palette = InvitePalette(invite)
- palette.props.invoker = FrameWidgetInvoker(self)
+ invoker.palette = palette
palette.set_group_id('frame')
palette.connect('remove-invite', self.__remove_invite_cb)
- self.set_palette(palette)
+ self.set_palette_invoker(invoker)
self._notif_icon = NotificationIcon()
- self._notif_icon.connect('button-release-event',
- self.__button_release_event_cb)
+ click = Gtk.GestureClick.new()
+ click.connect('released', self.__button_release_event_cb)
+ self._notif_icon.add_controller(click)
self._notif_icon.props.xo_color = invite.get_color()
if bundle is not None:
@@ -222,7 +201,7 @@ def __init__(self, invite):
frame = jarabe.frame.get_view()
frame.add_notification(self._notif_icon, Gtk.CornerType.TOP_LEFT)
- def __button_release_event_cb(self, icon, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
if self._notif_icon is not None:
frame = jarabe.frame.get_view()
frame.remove_notification(self._notif_icon)
@@ -236,11 +215,12 @@ def __clicked_cb(self, button):
def __remove_invite_cb(self, palette):
self.emit('remove-invite')
- def __destroy_cb(self, button):
+ def do_unroot(self):
if self._notif_icon is not None:
frame = jarabe.frame.get_view()
frame.remove_notification(self._notif_icon)
self._notif_icon = None
+ super().do_unroot()
class InvitePalette(Palette):
@@ -257,17 +237,17 @@ def __init__(self, invite):
self.menu_box = PaletteMenuBox()
self.set_content(self.menu_box)
- self.menu_box.show()
+ self.menu_box.set_visible(True)
menu_item = PaletteMenuItem(_('Join'), icon_name='dialog-ok')
menu_item.connect('activate', self.__join_activate_cb)
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = PaletteMenuItem(_('Decline'), icon_name='dialog-cancel')
menu_item.connect('activate', self.__decline_activate_cb)
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
bundle_id = invite.get_bundle_id()
@@ -337,7 +317,7 @@ def __notification_received_cb(self, **kwargs):
button = NotificationButton(name)
button.set_icon(icon)
- button.show()
+ button.set_visible(True)
self.add_item(button)
self._buttons_by_name[name] = button
@@ -361,17 +341,18 @@ def __buffer_cleared_cb(self, **kwargs):
def __activity_added_cb(self, home_model, home_activity):
logging.debug('__activity_added_cb: %r', home_activity)
- if self.get_children():
- group = self.get_children()[0]
- else:
- group = None
+ group = None
+ for child in self.get_children():
+ if isinstance(child, ActivityButton):
+ group = child
+ break
button = ActivityButton(home_activity, group)
self.add_item(button)
self._buttons[home_activity] = button
self._buttons_by_name[home_activity.get_activity_id()] = button
button.connect('clicked', self.__activity_clicked_cb, home_activity)
- button.show()
+ button.set_visible(True)
def __activity_removed_cb(self, home_model, home_activity):
logging.debug('__activity_removed_cb: %r', home_activity)
@@ -381,16 +362,15 @@ def __activity_removed_cb(self, home_model, home_activity):
del self._buttons_by_name[home_activity.get_activity_id()]
def _activate_activity(self, home_activity):
+ if home_activity not in self._buttons:
+ return
+
button = self._buttons[home_activity]
self._freeze_button_clicks = True
- button.props.active = True
+ button.set_active(True)
self._freeze_button_clicks = False
- self.scroll_to_item(button)
- # Redraw immediately.
- # The widget may not be realized yet, and then there is no window.
- while Gtk.events_pending():
- Gtk.main_iteration()
+ GLib.idle_add(self.scroll_to_item, button)
def __activity_changed_cb(self, home_model, home_activity):
logging.debug('__activity_changed_cb: %r', home_activity)
@@ -413,13 +393,13 @@ def __tabbing_activity_changed_cb(self, home_model, home_activity):
self._activate_activity(home_activity)
def __activity_clicked_cb(self, button, home_activity):
- if not self._freeze_button_clicks and button.props.active:
+ if not self._freeze_button_clicks and button.get_active():
logging.debug('ActivitiesTray.__activity_clicked_cb')
window = home_activity.get_window()
if window:
- window.activate(Gtk.get_current_event_time())
frame = jarabe.frame.get_view()
frame.hide()
+ window.present()
def __remove_invite_cb(self, icon, invite):
self._invites.remove_invite(invite)
@@ -435,12 +415,12 @@ def _add_invite(self, invite):
item = InviteButton(invite)
item.connect('remove-invite', self.__remove_invite_cb, invite)
self.add_item(item)
- item.show()
+ item.set_visible(True)
self._invite_to_item[invite] = item
def _remove_invite(self, invite):
self.remove_item(self._invite_to_item[invite])
- self._invite_to_item[invite].destroy()
+ self._invite_to_item[invite].unparent()
del self._invite_to_item[invite]
def __new_file_transfer_cb(self, **kwargs):
@@ -453,7 +433,7 @@ def __new_file_transfer_cb(self, **kwargs):
button = OutgoingTransferButton(file_transfer)
self.add_item(button)
- button.show()
+ button.set_visible(True)
class BaseTransferButton(ToolButton):
@@ -461,22 +441,23 @@ class BaseTransferButton(ToolButton):
"""
def __init__(self, file_transfer):
- ToolButton.__init__(self)
+ super().__init__()
self.file_transfer = file_transfer
file_transfer.connect('notify::state', self.__notify_state_cb)
icon = Icon()
self.props.icon_widget = icon
- icon.show()
+ icon.set_visible(True)
self.notif_icon = NotificationIcon()
- self.notif_icon.connect('button-release-event',
- self.__button_release_event_cb)
+ click = Gtk.GestureClick.new()
+ click.connect('released', self.__button_release_event_cb)
+ self.notif_icon.add_controller(click)
self.connect('clicked', self.__button_clicked_cb)
- def __button_release_event_cb(self, icon, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
if self.notif_icon is not None:
frame = jarabe.frame.get_view()
frame.remove_notification(self.notif_icon)
@@ -488,7 +469,7 @@ def __button_clicked_cb(self, button):
def remove(self):
frame = jarabe.frame.get_view()
frame.remove_notification(self.notif_icon)
- self.props.parent.remove(self)
+ self.unparent()
def __notify_state_cb(self, file_transfer, pspec):
logging.debug('_update state: %r %r', file_transfer.props.state,
@@ -512,7 +493,8 @@ def __init__(self, file_transfer):
file_transfer.connect('notify::transferred-bytes',
self.__notify_transferred_bytes_cb)
- icons = Gio.content_type_get_icon(file_transfer.mime_type).props.names
+ content_type = Gio.content_type_from_mime_type(file_transfer.mime_type)
+ icons = Gio.content_type_get_icon(content_type).get_names()
icons.append('application-octet-stream')
for icon_name in icons:
icon_name = 'transfer-from-%s' % icon_name
@@ -533,7 +515,9 @@ def __init__(self, file_transfer):
def create_palette(self):
palette = IncomingTransferPalette(self.file_transfer)
palette.connect('dismiss-clicked', self.__dismiss_clicked_cb)
- palette.props.invoker = FrameWidgetInvoker(self)
+ invoker = FrameWidgetInvoker(self)
+ invoker.palette = palette
+ self.set_palette_invoker(invoker)
palette.set_group_id('frame')
return palette
@@ -588,7 +572,8 @@ class OutgoingTransferButton(BaseTransferButton):
def __init__(self, file_transfer):
BaseTransferButton.__init__(self, file_transfer)
- icons = Gio.content_type_get_icon(file_transfer.mime_type).props.names
+ content_type = Gio.content_type_from_mime_type(file_transfer.mime_type)
+ icons = Gio.content_type_get_icon(content_type).get_names()
icons.append('application-octet-stream')
for icon_name in icons:
icon_name = 'transfer-to-%s' % icon_name
@@ -609,7 +594,9 @@ def __init__(self, file_transfer):
def create_palette(self):
palette = OutgoingTransferPalette(self.file_transfer)
palette.connect('dismiss-clicked', self.__dismiss_clicked_cb)
- palette.props.invoker = FrameWidgetInvoker(self)
+ invoker = FrameWidgetInvoker(self)
+ invoker.palette = palette
+ self.set_palette_invoker(invoker)
palette.set_group_id('frame')
return palette
@@ -702,80 +689,80 @@ def __notify_state_cb(self, file_transfer, pspec):
def _update(self):
box = PaletteMenuBox()
self.set_content(box)
- box.show()
+ box.set_visible(True)
logging.debug('_update state: %r', self.file_transfer.props.state)
if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
menu_item = PaletteMenuItem(_('Accept'))
icon = Icon(icon_name='dialog-ok',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__accept_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = PaletteMenuItem(_('Decline'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__decline_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
if self.file_transfer.description:
text = self.file_transfer.description.replace('\n', ' ')
label = Gtk.Label(label=text)
label.set_max_width_chars(style.MENU_WIDTH_CHARS)
label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
- inner_box.add(label)
- label.show()
+ inner_box.append(label)
+ label.set_visible(True)
mime_type = self.file_transfer.mime_type
type_description = mime.get_mime_description(mime_type)
size = self._format_size(self.file_transfer.file_size)
label = Gtk.Label(label='%s (%s)' % (size, type_description))
- inner_box.add(label)
- label.show()
+ inner_box.append(label)
+ label.set_visible(True)
elif self.file_transfer.props.state in \
[filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]:
menu_item = PaletteMenuItem(_('Cancel'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__cancel_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
self.progress_bar = Gtk.ProgressBar()
- inner_box.add(self.progress_bar)
- self.progress_bar.show()
+ inner_box.append(self.progress_bar)
+ self.progress_bar.set_visible(True)
self.progress_label = Gtk.Label(label='')
- inner_box.add(self.progress_label)
- self.progress_label.show()
+ inner_box.append(self.progress_label)
+ self.progress_label.set_visible(True)
self.update_progress()
@@ -783,11 +770,11 @@ def _update(self):
menu_item = PaletteMenuItem(_('Dismiss'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__dismiss_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
self.update_progress()
@@ -797,21 +784,21 @@ def _update(self):
menu_item = PaletteMenuItem(_('Dismiss'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__dismiss_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
text = _('The other participant canceled the file transfer')
label = Gtk.Label(label=text)
- inner_box.add(label)
- label.show()
+ inner_box.append(label)
+ label.set_visible(True)
def __accept_activate_cb(self, menu_item):
# TODO: figure out the best place to get rid of that temp file
@@ -871,66 +858,66 @@ def _update(self):
box = PaletteMenuBox()
self.set_content(box)
- box.show()
+ box.set_visible(True)
if new_state == filetransfer.FT_STATE_PENDING:
menu_item = PaletteMenuItem(_('Cancel'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__cancel_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
if self.file_transfer.description:
label = Gtk.Label(label=self.file_transfer.description)
- inner_box.add(label)
- label.show()
+ inner_box.append(label)
+ label.set_visible(True)
mime_type = self.file_transfer.mime_type
type_description = mime.get_mime_description(mime_type)
size = self._format_size(self.file_transfer.file_size)
label = Gtk.Label(label='%s (%s)' % (size, type_description))
- inner_box.add(label)
- label.show()
+ inner_box.append(label)
+ label.set_visible(True)
elif new_state in [filetransfer.FT_STATE_ACCEPTED,
filetransfer.FT_STATE_OPEN]:
menu_item = PaletteMenuItem(_('Cancel'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__cancel_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
self.progress_bar = Gtk.ProgressBar()
- inner_box.add(self.progress_bar)
- self.progress_bar.show()
+ inner_box.append(self.progress_bar)
+ self.progress_bar.set_visible(True)
self.progress_label = Gtk.Label(label='')
- inner_box.add(self.progress_label)
- self.progress_label.show()
+ inner_box.append(self.progress_label)
+ self.progress_label.set_visible(True)
self.update_progress()
@@ -939,11 +926,11 @@ def _update(self):
menu_item = PaletteMenuItem(_('Dismiss'))
icon = Icon(icon_name='dialog-cancel',
pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
menu_item.connect('activate', self.__dismiss_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
self.update_progress()
diff --git a/src/jarabe/frame/clipboard.py b/src/jarabe/frame/clipboard.py
index 315aece02f..922c47e4e2 100644
--- a/src/jarabe/frame/clipboard.py
+++ b/src/jarabe/frame/clipboard.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,14 +20,12 @@
import tempfile
from gi.repository import GObject
-from gi.repository import Gtk
from gi.repository import Gdk
from sugar4 import mime
from jarabe.frame.clipboardobject import ClipboardObject, Format
-
_instance = None
@@ -35,17 +33,17 @@ class Clipboard(GObject.GObject):
__gsignals__ = {
'object-added': (GObject.SignalFlags.RUN_FIRST, None,
- ([object])),
+ (object,)),
'object-deleted': (GObject.SignalFlags.RUN_FIRST, None,
- ([GObject.TYPE_INT64])),
+ (GObject.TYPE_INT64,)),
'object-selected': (GObject.SignalFlags.RUN_FIRST, None,
- ([int])),
+ (GObject.TYPE_INT64,)),
'object-state-changed': (GObject.SignalFlags.RUN_FIRST, None,
- ([object])),
+ (object,)),
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._objects = {}
self._next_id = 0
@@ -99,7 +97,7 @@ def delete_object(self, object_id):
cb_object = self._objects.pop(object_id)
cb_object.destroy()
if not self._objects:
- gtk_clipboard = Gtk.Display.get_default().get_clipboard()
+ gtk_clipboard = Gdk.Display.get_default().get_clipboard()
gtk_clipboard.set_content(None)
self.emit('object-deleted', object_id)
logging.debug('Deleted object with object_id %r', object_id)
@@ -137,7 +135,7 @@ def _process_object(self, cb_object):
elif 'text/unicode' in list(formats.keys()):
self.add_object_format(
cb_object.get_id(), 'text/plain',
- data=formats['UTF8_STRING'].get_data(), on_disk=False)
+ data=formats['text/unicode'].get_data(), on_disk=False)
def get_object(self, object_id):
logging.debug('Clipboard.get_object')
diff --git a/src/jarabe/frame/clipboardicon.py b/src/jarabe/frame/clipboardicon.py
index 2be3218cc2..b9bcaf3713 100644
--- a/src/jarabe/frame/clipboardicon.py
+++ b/src/jarabe/frame/clipboardicon.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
@@ -18,8 +18,8 @@
from gi.repository import Gtk
from gi.repository import Gdk
-from gi.repository import SugarExt
from gi.repository import GLib
+from gi.repository import GObject
from sugar4.graphics.radiotoolbutton import RadioToolButton
from sugar4.graphics.icon import Icon
@@ -40,8 +40,10 @@ class ClipboardIcon(RadioToolButton):
def __init__(self, cb_object, group):
RadioToolButton.__init__(self, group=group)
- self.props.palette_invoker = FrameWidgetInvoker(self)
- self.palette_invoker.props.toggle_palette = True
+ invoker = FrameWidgetInvoker(self)
+ self.set_palette_invoker(invoker)
+ if hasattr(invoker, '_toggle_palette'):
+ invoker._toggle_palette = True
self._cb_object = cb_object
self.owns_clipboard = False
@@ -54,17 +56,22 @@ def __init__(self, cb_object, group):
color = profile.get_color()
self._icon.props.xo_color = color
self.set_icon_widget(self._icon)
- self._icon.show()
+ self._icon.set_visible(True)
cb_service = clipboard.get_instance()
cb_service.connect('object-state-changed',
self._object_state_changed_cb)
cb_service.connect('object-selected', self._object_selected_cb)
- child = self.get_child()
- child.connect('drag-data-get', self._drag_data_get_cb)
self.connect('notify::active', self._notify_active_cb)
+ self._clipboard = Gdk.Display.get_default().get_clipboard()
+ self._clipboard.connect('notify::content', self._on_clipboard_content_changed)
+
+ def _on_clipboard_content_changed(self, clipboard, pspec):
+ if not getattr(self, '_setting_clipboard', False):
+ self.owns_clipboard = False
+
def create_palette(self):
palette = ClipboardMenu(self._cb_object)
palette.set_group_id('frame')
@@ -73,15 +80,6 @@ def create_palette(self):
def get_object_id(self):
return self._cb_object.get_id()
- def _drag_data_get_cb(self, widget, context, selection, target_type,
- event_time):
- target_atom = selection.get_target()
- target_name = target_atom.name()
- logging.debug('_drag_data_get_cb: requested target %s', target_name)
- data = self._cb_object.get_formats()[target_name].get_data()
- selection.set(target_atom, 8, data)
- GLib.source_remove(self._timeout_id)
-
def _put_in_clipboard(self):
logging.debug('ClipboardIcon._put_in_clipboard')
@@ -89,22 +87,32 @@ def _put_in_clipboard(self):
raise ValueError('Object is not complete, cannot be put into the'
' clipboard.')
- clipboard = Gdk.Display.get_default().get_clipboard()
+ gdk_clipboard = Gdk.Display.get_default().get_clipboard()
try:
- content_provider = Gdk.ContentProvider.new_for_bytes(
- self._cb_object.get_mime_type(),
- self._cb_object)
- stored = clipboard.set_content(content_provider)
- # this is empty as I don't yet know what exceptions can be raised here.
- except:
- stored = SugarExt.clipboard_set_with_data(
- self._cb_object.get_mime_type(),
- self._cb_object)
-
- if not stored:
- logging.error('GtkClipboard.set_with_data failed!')
- else:
- self.owns_clipboard = True
+ formats = self._cb_object.get_formats()
+ providers = []
+
+ for fmt_name, format_ in formats.items():
+ data = format_.get_data()
+ if isinstance(data, str):
+ if fmt_name == 'text/plain':
+ v = GObject.Value(GObject.TYPE_STRING, data)
+ providers.append(Gdk.ContentProvider.new_for_value(v))
+ else:
+ gbytes = GLib.Bytes.new(data.encode('utf-8'))
+ providers.append(Gdk.ContentProvider.new_for_bytes(fmt_name, gbytes))
+ elif isinstance(data, bytes):
+ gbytes = GLib.Bytes.new(data)
+ providers.append(Gdk.ContentProvider.new_for_bytes(fmt_name, gbytes))
+
+ if providers:
+ provider = Gdk.ContentProvider.new_union(providers)
+ self._setting_clipboard = True
+ gdk_clipboard.set_content(provider)
+ self._setting_clipboard = False
+ self.owns_clipboard = True
+ except Exception as e:
+ logging.error('Failed to put in clipboard: %s', e)
def _object_state_changed_cb(self, cb_service, cb_object):
if cb_object != self._cb_object:
@@ -117,11 +125,11 @@ def _object_state_changed_cb(self, cb_service, cb_object):
else:
self._icon.props.icon_name = 'application-octet-stream'
- child = self.get_child()
- child.connect('drag-begin', self._drag_begin_cb)
- child.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
- self._get_targets(),
- Gdk.DragAction.COPY)
+ if not hasattr(self, '_drag_source'):
+ self._drag_source = Gtk.DragSource.new()
+ self._drag_source.connect('prepare', self._on_drag_prepare)
+ self._drag_source.connect('drag-begin', self._drag_begin_cb)
+ self.add_controller(self._drag_source)
if cb_object.get_percent() == 100:
self.props.sensitive = True
@@ -150,23 +158,41 @@ def show_notification(self):
frame = jarabe.frame.get_view()
self._timeout_id = frame.add_notification(
self._notif_icon, Gtk.CornerType.BOTTOM_LEFT)
- self._notif_icon.connect('drag-data-get', self._drag_data_get_cb)
- self._notif_icon.connect('drag-begin', self._drag_begin_cb)
- self._notif_icon.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
- self._get_targets(),
- Gdk.DragAction.COPY)
-
- def _drag_begin_cb(self, widget, context):
- # TODO: We should get the pixbuf from the icon, with colors, etc.
- frame = jarabe.frame.get_view()
- self._timeout_id = GLib.timeout_add(
- jarabe.frame.frame.NOTIFICATION_DURATION,
- lambda: frame.remove_notification(self._notif_icon))
- icon_theme = Gtk.IconTheme.get_default()
- pixbuf = icon_theme.load_icon(self._icon.props.icon_name,
- style.STANDARD_ICON_SIZE, 0)
- Gtk.drag_set_icon_pixbuf(context, pixbuf, hot_x=pixbuf.props.width / 2,
- hot_y=pixbuf.props.height / 2)
+
+ notif_drag_source = Gtk.DragSource.new()
+ notif_drag_source.connect('prepare', self._on_drag_prepare)
+ notif_drag_source.connect('drag-begin', self._drag_begin_cb)
+ self._notif_icon.add_controller(notif_drag_source)
+
+ def _on_drag_prepare(self, source, x, y):
+ formats = self._cb_object.get_formats()
+ providers = []
+ for fmt_name, format_ in formats.items():
+ data = format_.get_data()
+ if isinstance(data, str):
+ if fmt_name == 'text/plain':
+ v = GObject.Value(GObject.TYPE_STRING, data)
+ providers.append(Gdk.ContentProvider.new_for_value(v))
+ else:
+ gbytes = GLib.Bytes.new(data.encode('utf-8'))
+ providers.append(Gdk.ContentProvider.new_for_bytes(fmt_name, gbytes))
+ elif isinstance(data, bytes):
+ gbytes = GLib.Bytes.new(data)
+ providers.append(Gdk.ContentProvider.new_for_bytes(fmt_name, gbytes))
+
+ if providers:
+ return Gdk.ContentProvider.new_union(providers)
+
+ return None
+
+ def _drag_begin_cb(self, source, drag):
+ if hasattr(self, '_timeout_id') and self._timeout_id > 0:
+ GLib.source_remove(self._timeout_id)
+ self._timeout_id = 0
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
+ paintable = icon_theme.lookup_icon(self._icon.props.icon_name, None, style.STANDARD_ICON_SIZE, 1, 0, 0)
+ if paintable:
+ source.set_icon(paintable, 0, 0)
def _notify_active_cb(self, widget, pspec):
if self.props.active:
diff --git a/src/jarabe/frame/clipboardmenu.py b/src/jarabe/frame/clipboardmenu.py
index 040c2266f3..5046372b65 100644
--- a/src/jarabe/frame/clipboardmenu.py
+++ b/src/jarabe/frame/clipboardmenu.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
import os
import logging
from gi.repository import Gio
+from gi.repository import GLib
from gi.repository import Gtk
@@ -30,7 +31,7 @@
from sugar4 import mime
from sugar4 import env
from sugar4 import profile
-from sugar4.activity.i18n import pgettext
+from gettext import pgettext
from jarabe.frame import clipboard
from jarabe.journal import misc
@@ -45,6 +46,13 @@ def __init__(self, cb_object):
self._cb_object = cb_object
self.set_group_id('frame')
+
+ self.action_group = Gio.SimpleActionGroup.new()
+ self.menu.insert_action_group('clip', self.action_group)
+
+ open_action = Gio.SimpleAction.new('open-activity', GLib.VariantType('s'))
+ open_action.connect('activate', self._on_open_activity_activated)
+ self.action_group.add_action(open_action)
cb_service = clipboard.get_instance()
cb_service.connect('object-state-changed',
@@ -52,14 +60,14 @@ def __init__(self, cb_object):
self._remove_item = MenuItem(pgettext('Clipboard', 'Remove'),
'list-remove')
- self._remove_item.connect('activate', self._remove_item_activate_cb)
+ self._remove_item.connect('clicked', self._remove_item_activate_cb)
self.menu.append(self._remove_item)
- self._remove_item.show()
+ self._remove_item.set_visible(True)
self._open_item = MenuItem(_('Open'), 'zoom-activity')
- self._open_item.connect('activate', self._open_item_activate_cb)
+ self._open_item.connect('clicked', self._open_item_activate_cb)
self.menu.append(self._open_item)
- self._open_item.show()
+ self._open_item.set_visible(True)
self._journal_item = MenuItem(_('Keep'))
color = profile.get_color()
@@ -68,26 +76,25 @@ def __init__(self, cb_object):
xo_color=color)
self._journal_item.set_image(icon)
- self._journal_item.connect('activate', self._journal_item_activate_cb)
+ self._journal_item.connect('clicked', self._journal_item_activate_cb)
self.menu.append(self._journal_item)
- self._journal_item.show()
+ self._journal_item.set_visible(True)
self._update()
def _update_open_submenu(self):
activities = self._get_activities()
logging.debug('_update_open_submenu: %r', activities)
- child = self._open_item.get_child()
if activities is None or len(activities) <= 1:
- child.set_text(_('Open'))
+ self._open_item.set_text(_('Open'))
if self._open_item.get_submenu() is not None:
self._open_item.set_submenu(None)
return
- child.set_text(_('Open with'))
+ self._open_item.set_text(_('Open with'))
+
submenu = Gio.Menu.new()
self._open_item.set_submenu(submenu)
- submenu.show()
for service_name in activities:
registry = bundleregistry.get_registry()
@@ -96,11 +103,9 @@ def _update_open_submenu(self):
if not activity_info:
logging.warning('Activity %s is unknown.', service_name)
- item = Gio.MenuItem(activity_info.get_name())
- item.connect('activate', self._open_submenu_item_activate_cb,
- service_name)
+ item = Gio.MenuItem.new(activity_info.get_name(), 'clip.open-activity')
+ item.set_action_and_target_value('clip.open-activity', GLib.Variant('s', service_name))
submenu.append_item(item)
- item.show()
def _update_items_visibility(self):
activities = self._get_activities()
@@ -153,6 +158,10 @@ def _open_item_activate_cb(self, menu_item):
misc.resume(jobject.metadata, self._get_activities()[0])
jobject.destroy()
+ def _on_open_activity_activated(self, action, parameter):
+ service_name = parameter.get_string()
+ self._open_submenu_item_activate_cb(None, service_name)
+
def _open_submenu_item_activate_cb(self, menu_item, service_name):
logging.debug('_open_submenu_item_activate_cb')
percent = self._cb_object.get_percent()
@@ -176,11 +185,9 @@ def _write_to_temp_file(self, data, is_pixbuf):
f, file_path = tempfile.mkstemp(dir=tmp_dir)
try:
- if not is_pixbuf:
- os.write(f, data)
- else:
- _type = self._cb_object.get_mime_type().split('/').pop()
- data.savev(file_path, _type, [], [])
+ if hasattr(data, 'get_data'):
+ data = data.get_data()
+ os.write(f, data)
finally:
os.close(f)
return file_path
diff --git a/src/jarabe/frame/clipboardobject.py b/src/jarabe/frame/clipboardobject.py
index b58dea5f71..73501a9458 100644
--- a/src/jarabe/frame/clipboardobject.py
+++ b/src/jarabe/frame/clipboardobject.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
import urllib.parse
from gi.repository import Gio
from gi.repository import Gtk
+from gi.repository import Gdk
from gettext import gettext as _
from sugar4 import mime
@@ -59,21 +60,16 @@ def get_icon(self):
return generic_type.icon
icons = Gio.content_type_get_icon(mime_type)
- icon_name = None
if icons is not None:
- icon_theme = Gtk.IconTheme.get_default()
- for icon_name in icons.props.names:
- icon_info = (
- icon_theme.lookup_icon(icon_name,
- Gtk.IconSize.LARGE_TOOLBAR, 0))
- if icon_info is not None:
- del icon_info
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
+ for icon_name in icons.get_names():
+ if icon_theme.has_icon(icon_name):
return icon_name
return 'application-octet-stream'
def get_preview(self):
- for mime_type in ['UTF8_STRING']:
+ for mime_type in ['UTF8_STRING', 'text/plain']:
if mime_type in self._formats:
return self._formats[mime_type].get_data()
return ''
diff --git a/src/jarabe/frame/clipboardpanelwindow.py b/src/jarabe/frame/clipboardpanelwindow.py
index 671f27981a..1c32173826 100644
--- a/src/jarabe/frame/clipboardpanelwindow.py
+++ b/src/jarabe/frame/clipboardpanelwindow.py
@@ -17,13 +17,19 @@
from urllib.parse import urlparse
import hashlib
-from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import GLib
from jarabe.frame.framewindow import FrameWindow
from jarabe.frame.clipboardtray import ClipboardTray
-from jarabe.frame import clipboard
+import jarabe.frame.clipboard as clipboard
+
+# Clipboard target names that carry no data content.
+_SKIP_TARGETS = frozenset([
+ 'TIMESTAMP', 'TARGETS', 'MULTIPLE', 'SAVE_TARGETS',
+ 'DELETE', 'INSERT_SELECTION', 'INSERT_PROPERTY',
+])
class ClipboardPanelWindow(FrameWindow):
@@ -33,63 +39,118 @@ def __init__(self, frame, orientation):
self._frame = frame
- # Listening for new clipboard objects
- # NOTE: we need to keep a reference to Gdk.Clipboard in order to keep
- # listening to it.
- self._clipboard = Gtk.Display.get_default().get_clipboard()
+ # Listening for new clipboard objects.
+ self._clipboard = Gdk.Display.get_default().get_clipboard()
self._clipboard.connect('changed', self._owner_change_cb)
self._clipboard_tray = ClipboardTray()
- self._clipboard_tray.show()
+ self._clipboard_tray.set_visible(True)
self.append(self._clipboard_tray)
- # Receiving dnd drops
- self.drag_dest_set(0, [], 0)
- self.connect('drag-motion', self._clipboard_tray.drag_motion_cb)
- self.connect('drag-leave', self._clipboard_tray.drag_leave_cb)
- self.connect('drag-drop', self._clipboard_tray.drag_drop_cb)
- self.connect('drag-data-received',
- self._clipboard_tray.drag_data_received_cb)
-
- def _owner_change_cb(self, clipboard):
+ def _owner_change_cb(self, cb):
logging.debug('owner_change_cb')
if self._clipboard_tray.owns_clipboard():
return
- cb_service = clipboard.get_instance()
-
- content = clipboard.get_content()
-
- if not content:
+ formats = cb.get_formats()
+ if formats is None:
return
- formats = content.ref_formats()
-
- if formats.is_empty():
+ mime_types = formats.get_mime_types()
+ if not mime_types:
return
- mime_types = formats.get_mime_types()
- content_is_uri = False
- for mime_type in mime_types:
- if mime_type == 'text/uri-list':
- content_is_uri = True
- mt = mime_type
-
- if content_is_uri:
- uri = content.get_value()
- filename = uri[len('file://'):].strip()
- md5 = self._md5_for_file(filename)
- data_hash = hash(md5)
- else:
- data_hash = hash(content.get_value())
-
- key = cb_service.add_object(name="", data_hash=data_hash)
- if key is None:
+ # Filter out internal/control targets.
+ targets = [t for t in mime_types if t not in _SKIP_TARGETS]
+ if not targets:
return
- cb_service.set_object_percent(key, percent=0)
- self._add_content(key, content, mt)
- cb_service.set_object_percent(key, percent=100)
+
+ cb_service = clipboard.get_instance()
+
+ # Read all targets asynchronously; track how many are still pending.
+ state = {'pending': len(targets), 'key': None, 'data_hash': None, 'cancelled': False}
+ for mime_type in targets:
+ cb.read_async(
+ [mime_type],
+ GLib.PRIORITY_DEFAULT,
+ None,
+ self._read_mime_cb,
+ (cb_service, mime_type, state))
+
+ # ------------------------------------------------------------------
+ # Async reading chain
+ # ------------------------------------------------------------------
+
+ def _read_mime_cb(self, cb, result, user_data):
+ cb_service, mime_type, state = user_data
+ try:
+ stream, _ = cb.read_finish(result)
+ if stream:
+ stream.read_bytes_async(
+ 65536,
+ GLib.PRIORITY_DEFAULT,
+ None,
+ self._read_bytes_cb,
+ (cb_service, mime_type, state, stream))
+ return
+ except Exception as e:
+ logging.debug('Could not read clipboard mime %s: %s', mime_type, e)
+
+ # Count this target as done even on failure.
+ self._check_complete(cb_service, state)
+
+ def _read_bytes_cb(self, stream, result, user_data):
+ cb_service, mime_type, state, _ = user_data
+ try:
+ raw = stream.read_bytes_finish(result)
+ data = raw.get_data() if raw else None
+ if data and not state['cancelled']:
+ on_disk = False
+ if mime_type == 'text/uri-list':
+ # Extract first URI and compute data hash
+ text = data.decode('utf-8', errors='replace')
+ uris = [u.strip() for u in text.splitlines()
+ if u.strip() and not u.strip().startswith('#')]
+ if uris:
+ uri = uris[0]
+ scheme = urlparse(uri).scheme
+ on_disk = (scheme == 'file')
+ if on_disk and state['data_hash'] is None:
+ filename = urlparse(uri).path
+ state['data_hash'] = hash(
+ self._md5_for_file(filename))
+ data = uri
+ else:
+ if state['data_hash'] is None:
+ state['data_hash'] = hash(data)
+ # Decode text/* to str; keep binary as bytes.
+ if mime_type.startswith('text/'):
+ try:
+ data = data.decode('utf-8', errors='replace')
+ except Exception:
+ pass
+
+ if state['key'] is None:
+ # Create the clipboard object now that we have a hash
+ state['key'] = cb_service.add_object(name='', data_hash=state['data_hash'])
+ if state['key'] is None:
+ state['cancelled'] = True
+ else:
+ cb_service.set_object_percent(state['key'], percent=0)
+
+ if not state['cancelled']:
+ cb_service.add_object_format(
+ state['key'], mime_type, data, on_disk=on_disk)
+ except Exception as e:
+ logging.debug('Error reading bytes for %s: %s', mime_type, e)
+
+ self._check_complete(cb_service, state)
+
+ def _check_complete(self, cb_service, state):
+ state['pending'] -= 1
+ if state['pending'] <= 0 and state['key'] is not None and not state['cancelled']:
+ cb_service.set_object_percent(state['key'], percent=100)
def _md5_for_file(self, file_name):
'''Calculate md5 for file data
@@ -98,37 +159,13 @@ def _md5_for_file(self, file_name):
'''
block_size = 8192
md5 = hashlib.md5()
- f = open(file_name, 'r')
- while True:
- data = f.read(block_size)
- if not data:
- break
- md5.update(data)
- f.close()
+ try:
+ with open(file_name, 'rb') as f:
+ while True:
+ data = f.read(block_size)
+ if not data:
+ break
+ md5.update(data)
+ except OSError as e:
+ logging.warning('md5_for_file failed for %s: %s', file_name, e)
return md5.digest()
-
- def _add_content(self, key, content, mime_type):
- result, value = content.get_value()
- if not result:
- logging.warning('no data for content %s.',
- mime_type)
- return
-
- logging.debug('adding type ' + mime_type + '.')
- cb_service = clipboard.get_instance()
- if mime_type == 'text/uri-list':
- uri = content.get_value()
-
- scheme, netloc_, path_, parameters_, query_, fragment_ = \
- urlparse(uri)
- on_disk = (scheme == 'file')
-
- cb_service.add_object_format(key,
- mime_type,
- uri,
- on_disk)
- else:
- cb_service.add_object_format(key,
- mime_type,
- content.get_value(),
- on_disk=False)
diff --git a/src/jarabe/frame/clipboardtray.py b/src/jarabe/frame/clipboardtray.py
index fe39ab2ae6..ccf7676604 100644
--- a/src/jarabe/frame/clipboardtray.py
+++ b/src/jarabe/frame/clipboardtray.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,6 +17,9 @@
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gio
from sugar4.graphics import tray
from sugar4.graphics import style
@@ -25,49 +28,55 @@
from jarabe.frame.clipboardicon import ClipboardIcon
-class ContextMap(object):
- """Maps a drag context to the clipboard object involved in the dragging."""
-
- def __init__(self):
- self._context_map = {}
-
- def add_context(self, context, object_id, data_types):
- """Establishes the mapping. data_types will serve us for reference-
- counting this mapping.
- """
- self._context_map[context] = [object_id, data_types]
-
- def get_object_id(self, context):
- """Retrieves the object_id associated with context.
- Will release the association when this function was called as many
- times as the number of data_types that this clipboard object contains.
- """
- [object_id, data_types_left] = self._context_map[context]
-
- data_types_left = data_types_left - 1
- if data_types_left == 0:
- del self._context_map[context]
- else:
- self._context_map[context] = [object_id, data_types_left]
-
- return object_id
-
- def has_context(self, context):
- return context in self._context_map
+def _get_screen_height():
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ return monitors.get_item(0).get_geometry().height
+ return 768
class ClipboardTray(tray.VTray):
- MAX_ITEMS = Gdk.Screen.height() // style.GRID_CELL_SIZE - 2
+ MAX_ITEMS = _get_screen_height() // style.GRID_CELL_SIZE - 2
def __init__(self):
tray.VTray.__init__(self, align=tray.ALIGN_TO_END)
self._icons = {}
- self._context_map = ContextMap()
cb_service = clipboard.get_instance()
- cb_service.connect('object-added', self._object_added_cb)
- cb_service.connect('object-deleted', self._object_deleted_cb)
+ self._object_added_hid = cb_service.connect('object-added', self._object_added_cb)
+ self._object_deleted_hid = cb_service.connect('object-deleted', self._object_deleted_cb)
+
+ builder = Gdk.ContentFormatsBuilder.new()
+ builder.add_mime_type('journal-object-id')
+ builder.add_mime_type('text/uri-list')
+ builder.add_mime_type('text/plain')
+ builder.add_mime_type('text/html')
+ builder.add_mime_type('image/png')
+ builder.add_mime_type('image/jpeg')
+ builder.add_mime_type('image/gif')
+ builder.add_mime_type('image/svg+xml')
+ builder.add_gtype(GObject.TYPE_STRING)
+ formats = builder.to_formats()
+
+ self._drop_target = Gtk.DropTargetAsync.new(formats, actions=Gdk.DragAction.COPY | Gdk.DragAction.MOVE)
+ self._drop_target.connect('drop', self._on_drop_async_cb)
+ self.add_controller(self._drop_target)
+
+ def do_dispose(self):
+ cb_service = clipboard.get_instance()
+ if hasattr(self, '_object_added_hid'):
+ cb_service.disconnect(self._object_added_hid)
+ del self._object_added_hid
+ if hasattr(self, '_object_deleted_hid'):
+ cb_service.disconnect(self._object_deleted_hid)
+ del self._object_deleted_hid
+ if getattr(self, '_drop_target', None) is not None:
+ self.remove_controller(self._drop_target)
+ self._drop_target = None
+ super().do_dispose()
def owns_clipboard(self):
for icon in list(self._icons.values()):
@@ -75,125 +84,119 @@ def owns_clipboard(self):
return True
return False
- def _add_selection(self, object_id, selection):
- if not selection.get_data():
- return
-
- selection_data = selection.get_data()
-
- selection_type_atom = selection.get_data_type()
- selection_type = selection_type_atom.name()
-
- logging.debug('ClipboardTray: adding type %r', selection_type)
-
- cb_service = clipboard.get_instance()
- if selection_type == 'text/uri-list':
- uris = selection.get_uris()
- if len(uris) > 1:
- raise NotImplementedError('Multiple uris in text/uri-list'
- ' still not supported.')
-
- cb_service.add_object_format(object_id,
- selection_type,
- uris[0],
- on_disk=True)
- else:
- cb_service.add_object_format(object_id,
- selection_type,
- selection_data,
- on_disk=False)
-
def _object_added_cb(self, cb_service, cb_object):
+ group = None
if self._icons:
group = list(self._icons.values())[0]
- else:
- group = None
icon = ClipboardIcon(cb_object, group)
self.add_item(icon)
- icon.show()
+ icon.set_visible(True)
self._icons[cb_object.get_id()] = icon
- objects_to_delete = self.get_children()[:-self.MAX_ITEMS]
- for icon in objects_to_delete:
- logging.debug('ClipboardTray: deleting surplus object')
- cb_service = clipboard.get_instance()
- cb_service.delete_object(icon.get_object_id())
+ # Enforce MAX_ITEMS
+ children = []
+ child = self.get_first_child()
+ while child:
+ children.append(child)
+ child = child.get_next_sibling()
+
+ if len(children) > self.MAX_ITEMS:
+ objects_to_delete = children[:-self.MAX_ITEMS]
+ for icon_to_delete in objects_to_delete:
+ logging.debug('ClipboardTray: deleting surplus object')
+ cb_service = clipboard.get_instance()
+ cb_service.delete_object(icon_to_delete.get_object_id())
logging.debug('ClipboardTray: %r was added', cb_object.get_id())
def _object_deleted_cb(self, cb_service, object_id):
- icon = self._icons[object_id]
- self.remove_item(icon)
- del self._icons[object_id]
- # select the last available icon
- if self._icons:
- last_icon = self.get_children()[-1]
- last_icon.props.active = True
+ icon = self._icons.get(object_id)
+ if icon:
+ self.remove_item(icon)
+ del self._icons[object_id]
+
+ # select the last available icon if the deleted one was active
+ if icon.props.active and self._icons:
+ children = []
+ child = self.get_first_child()
+ while child:
+ children.append(child)
+ child = child.get_next_sibling()
+ if children:
+ last_icon = children[-1]
+ if hasattr(last_icon, 'set_active'):
+ last_icon.set_active(True)
logging.debug('ClipboardTray: %r was deleted', object_id)
- def drag_motion_cb(self, widget, context, x, y, time):
- logging.debug('ClipboardTray._drag_motion_cb')
-
- if self._internal_drag(context):
- Gdk.drag_status(context, Gdk.DragAction.MOVE, time)
- else:
- Gdk.drag_status(context, Gdk.DragAction.COPY, time)
- self.props.drag_active = True
-
- return True
-
- def drag_leave_cb(self, widget, context, time):
- self.props.drag_active = False
-
- def drag_drop_cb(self, widget, context, x, y, time):
- logging.debug('ClipboardTray._drag_drop_cb')
-
- if self._internal_drag(context):
- # TODO: We should move the object within the clipboard here
- if not self._context_map.has_context(context):
- Gdk.drop_finish(context, False, Gtk.get_current_event_time())
+ def _on_drop_async_cb(self, target, drop, x, y):
+ logging.debug('ClipboardTray._on_drop_async_cb')
+ formats = drop.get_formats()
+ mime_types = formats.get_mime_types()
+ if not mime_types:
return False
-
+
+ skip_targets = frozenset(['TIMESTAMP', 'TARGETS', 'MULTIPLE', 'SAVE_TARGETS', 'DELETE', 'INSERT_SELECTION', 'INSERT_PROPERTY'])
+ valid_targets = [t for t in mime_types if t not in skip_targets]
+ if not valid_targets:
+ return False
+
cb_service = clipboard.get_instance()
object_id = cb_service.add_object(name="")
-
- context_targets = context.list_targets()
- self._context_map.add_context(context, object_id, len(context_targets))
-
- for target in context_targets:
- if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'):
- widget.drag_get_data(context, target, time)
-
- cb_service.set_object_percent(object_id, percent=100)
-
+ cb_service.set_object_percent(object_id, percent=0)
+
+ state = {'pending': len(valid_targets), 'object_id': object_id}
+ for mime_type in valid_targets:
+ drop.read_async([mime_type], GLib.PRIORITY_DEFAULT, None, self._on_read_raw_cb, (mime_type, state))
return True
- def drag_data_received_cb(self, widget, context, x, y, selection,
- targetType, time):
- logging.debug('ClipboardTray: got data for target %r',
- selection.get_target())
-
- object_id = self._context_map.get_object_id(context)
+ def _on_read_raw_cb(self, drop, result, user_data):
+ mime_type, state = user_data
try:
- if selection is None:
- logging.warn('ClipboardTray: empty selection for target %s',
- selection.get_target())
- else:
- self._add_selection(object_id, selection)
-
- finally:
- # If it's the last target to be processed, finish
- # the dnd transaction
- if not self._context_map.has_context(context):
- Gdk.drop_finish(context, True, Gtk.get_current_event_time())
-
- def _internal_drag(self, context):
- source_widget = Gtk.drag_get_source_widget(context)
- if source_widget is None:
- return False
- view_ancestor = source_widget.get_ancestor(Gtk.Viewport)
- if view_ancestor is self._viewport:
- return True
- return False
+ stream, out_mime = drop.read_finish(result)
+ if stream:
+ stream.read_bytes_async(65536, GLib.PRIORITY_DEFAULT, None, self._on_bytes_read_cb, (stream, mime_type, state))
+ return
+ except Exception as e:
+ logging.error("Failed to read raw drop: %s", e)
+ self._check_complete(state)
+
+ def _on_bytes_read_cb(self, stream, result, user_data):
+ stream, mime_type, state = user_data
+ try:
+ bytes_data = stream.read_bytes_finish(result)
+ data = bytes_data.get_data() if bytes_data else None
+ if data:
+ cb_service = clipboard.get_instance()
+
+ if mime_type == 'text/uri-list':
+ try:
+ text = data.decode('utf-8').strip()
+ lines = [line.strip() for line in text.splitlines() if line.strip() and not line.startswith('#')]
+ if lines:
+ uri = lines[0]
+ f = Gio.File.new_for_uri(uri)
+ path = f.get_path()
+ if path:
+ cb_service.add_object_format(state['object_id'], mime_type, path, on_disk=True)
+ self._check_complete(state)
+ return
+ except Exception:
+ pass
+
+ if mime_type.startswith('text/') or mime_type == 'journal-object-id':
+ try:
+ data = data.decode('utf-8')
+ except Exception:
+ pass
+ cb_service.add_object_format(state['object_id'], mime_type, data, on_disk=False)
+ except Exception as e:
+ logging.error("Failed to read dropped bytes: %s", e)
+ self._check_complete(state)
+
+ def _check_complete(self, state):
+ state['pending'] -= 1
+ if state['pending'] <= 0:
+ cb_service = clipboard.get_instance()
+ cb_service.set_object_percent(state['object_id'], percent=100)
diff --git a/src/jarabe/frame/devicestray.py b/src/jarabe/frame/devicestray.py
index c3c2136abe..6e72a7ddfd 100644
--- a/src/jarabe/frame/devicestray.py
+++ b/src/jarabe/frame/devicestray.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -31,23 +31,24 @@ def __init__(self):
if f.endswith('.py') and not f.startswith('__'):
module_name = f[:-3]
try:
- mod = importlib.import_module('deviceicon.' + module_name, globals())
+ mod = importlib.import_module('deviceicon.' + module_name)
mod.setup(self)
- except Exception:
- logging.exception('Exception while loading extension:')
+ except Exception as e:
+ logging.error(f'Exception while loading extension {module_name}: {e}', exc_info=True)
def add_device(self, view):
index = 0
relative_index = getattr(view, 'FRAME_POSITION_RELATIVE', -1)
+
for item in self.get_children():
- current_relative_index = getattr(item, 'FRAME_POSITION_RELATIVE',
- 0)
+ current_relative_index = getattr(item, 'FRAME_POSITION_RELATIVE', 0)
if current_relative_index >= relative_index:
index += 1
else:
break
+
self.add_item(view, index=index)
- view.show()
+ view.set_visible(True)
def remove_device(self, view):
self.remove_item(view)
diff --git a/src/jarabe/frame/eventarea.py b/src/jarabe/frame/eventarea.py
index 35d78cdce6..c86d91ecb9 100644
--- a/src/jarabe/frame/eventarea.py
+++ b/src/jarabe/frame/eventarea.py
@@ -1,26 +1,12 @@
-# Copyright (C) 2007, Red Hat, Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GObject
-from gi.repository import GLib
-#from gi.repository import Wnck
+import logging
-from sugar4.graphics import style
+import gi
+gi.require_version('Gtk', '4.0')
+from gi.repository import Gtk, GObject, GLib, Gdk
+
+HAS_LAYER_SHELL = False
+from sugar4.graphics import style
_MAX_DELAY = 1000
@@ -36,13 +22,21 @@ class EventArea(GObject.GObject):
}
def __init__(self, settings):
- GObject.GObject.__init__(self)
+ super().__init__()
+
self._hover = False
+ self._is_active = False
self._sids = {}
self._boxes = {}
self._tags = {}
+
+ self._edge_delay = _MAX_DELAY
+ self._corner_delay = _MAX_DELAY
+
+
+
for tag in _BOXES:
box = self._box(tag)
self._tags[box] = tag
@@ -51,54 +45,94 @@ def __init__(self, settings):
settings.connect('changed', self._settings_changed_cb)
self._settings_changed_cb(settings, None)
- # Figure out a replacement for this.
- #screen = Wnck.Screen.get_default()
- #screen.connect('window-stacking-changed',
- # self._window_stacking_changed_cb)
-
def _box(self, tag):
- box = Gtk.Invisible()
- box.connect('enter-notify-event', self._enter_notify_cb)
- box.connect('leave-notify-event', self._leave_notify_cb)
- box.drag_dest_set(0, [], 0)
- box.connect('drag-motion', self._drag_motion_cb)
- box.connect('drag-leave', self._drag_leave_cb)
- box.realize()
- return box
+ window = Gtk.Box()
+ window.add_css_class('transparent-window')
+
+ provider = Gtk.CssProvider()
+ provider.load_from_string('.transparent-window, .transparent-window:hover { background-color: rgba(0, 0, 0, 0.01); }')
+ window.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+ if tag == 'nw':
+ window.set_valign(Gtk.Align.START)
+ window.set_halign(Gtk.Align.START)
+ elif tag == 'ne':
+ window.set_valign(Gtk.Align.START)
+ window.set_halign(Gtk.Align.END)
+ elif tag == 'se':
+ window.set_valign(Gtk.Align.END)
+ window.set_halign(Gtk.Align.END)
+ elif tag == 'sw':
+ window.set_valign(Gtk.Align.END)
+ window.set_halign(Gtk.Align.START)
+ elif tag == 'n':
+ window.set_valign(Gtk.Align.START)
+ window.set_halign(Gtk.Align.FILL)
+ elif tag == 's':
+ window.set_valign(Gtk.Align.END)
+ window.set_halign(Gtk.Align.FILL)
+ elif tag == 'e':
+ window.set_valign(Gtk.Align.FILL)
+ window.set_halign(Gtk.Align.END)
+ elif tag == 'w':
+ window.set_valign(Gtk.Align.FILL)
+ window.set_halign(Gtk.Align.START)
+
+
+ controller = Gtk.EventControllerMotion()
+ controller.connect('enter', self._enter_notify_cb, window)
+ controller.connect('leave', self._leave_notify_cb, window)
+ window.add_controller(controller)
+
+ # Drop support
+ drop_target = Gtk.DropTargetAsync.new(None, Gdk.DragAction.COPY)
+ drop_target.connect('accept', self._drag_accept_cb, window)
+ drop_target.connect('drag-enter', self._drag_motion_cb, window)
+ drop_target.connect('drag-leave', self._drag_leave_cb, window)
+ window.add_controller(drop_target)
+
+ return window
def _settings_changed_cb(self, settings, key):
self._edge_delay = min(settings.get_int('edge-delay'), _MAX_DELAY)
self._corner_delay = min(settings.get_int('corner-delay'), _MAX_DELAY)
ts = min(settings.get_int('trigger-size'), style.GRID_CELL_SIZE)
- sw = Gdk.Screen.width()
- sh = Gdk.Screen.height()
if self._edge_delay == _MAX_DELAY:
self._hide(_EDGES)
else:
- self._move('n', ts, -1, sw - ts * 2, ts + 1)
- self._move('e', -1, ts, ts + 1, sh - ts * 2)
- self._move('s', ts, sh - ts, sw - ts * 2, ts + 1)
- self._move('w', sw - ts, ts, ts + 1, sh - ts * 2)
+ self._move('n', ts)
+ self._move('e', ts)
+ self._move('s', ts)
+ self._move('w', ts)
if self._corner_delay == _MAX_DELAY:
self._hide(_CORNERS)
else:
- self._move('nw', -1, -1, ts + 1, ts + 1)
- self._move('ne', sw - ts, -1, ts + 1, ts + 1)
- self._move('se', sw - ts, sh - ts, ts + 1, ts + 1)
- self._move('sw', -1, sh - ts, ts + 1, ts + 1)
+ self._move('nw', ts)
+ self._move('ne', ts)
+ self._move('se', ts)
+ self._move('sw', ts)
def _hide(self, tags):
for tag in tags:
- self._move(tag, -20, -20, 1, 1)
-
- def _move(self, tag, x, y, width, height):
- window = self._boxes[tag].get_window()
- window.set_events(Gdk.EventMask.POINTER_MOTION_MASK |
- Gdk.EventMask.ENTER_NOTIFY_MASK |
- Gdk.EventMask.LEAVE_NOTIFY_MASK)
- window.move_resize(x, y, width, height)
+ self._boxes[tag].set_visible(False)
+
+ def _move(self, tag, ts):
+ window = self._boxes[tag]
+ if tag in ['n', 's']:
+ window.set_size_request(-1, ts)
+ window.set_margin_start(ts)
+ window.set_margin_end(ts)
+ elif tag in ['e', 'w']:
+ window.set_size_request(ts, -1)
+ window.set_margin_top(ts)
+ window.set_margin_bottom(ts)
+ else:
+ window.set_size_request(ts, ts)
+
+ if self._is_active:
+ window.set_visible(True)
def _notify_enter(self):
if not self._hover:
@@ -110,50 +144,59 @@ def _notify_leave(self):
self._hover = False
self.emit('leave')
- def _enter_notify_cb(self, widget, event):
- if self._sids:
- GLib.source_remove(widget.sid)
- del self._sids[widget]
+ def _enter_notify_cb(self, controller, x, y, window):
+ if window in self._sids:
+ GLib.source_remove(self._sids[window])
+ del self._sids[window]
delay = None
- if self._tags[widget] in _CORNERS:
+ if self._tags[window] in _CORNERS:
delay = self._corner_delay
- if self._tags[widget] in _EDGES:
+ if self._tags[window] in _EDGES:
delay = self._edge_delay
if delay is not None:
- self._sids[widget] = GLib.timeout_add(delay,
- self.__delay_cb,
- widget)
+ self._sids[window] = GLib.timeout_add(delay, self.__delay_cb, window)
- def __delay_cb(self, widget):
- del self._sids[widget]
+ def __delay_cb(self, window):
+ del self._sids[window]
self._notify_enter()
return False
- def _leave_notify_cb(self, widget, event):
- if widget in self._sids:
- GLib.source_remove(self._sids[widget])
- del self._sids[widget]
+ def _leave_notify_cb(self, controller, window):
+ if window in self._sids:
+ GLib.source_remove(self._sids[window])
+ del self._sids[window]
self._notify_leave()
- def _drag_motion_cb(self, widget, drag_context, x, y, timestamp):
- Gdk.drag_status(drag_context, 0, timestamp)
- self._notify_enter()
+ def _drag_accept_cb(self, drop_target, drop, window):
return True
- def _drag_leave_cb(self, widget, drag_context, timestamp):
+ def _drag_motion_cb(self, drop_target, drop, x, y, window):
+ self._notify_enter()
+ return Gdk.DragAction.COPY
+
+ def _drag_leave_cb(self, drop_target, drop, window):
self._notify_leave()
- return True
def show(self):
- for box in list(self._boxes.values()):
- box.show()
+ self._is_active = True
+ if not HAS_LAYER_SHELL:
+ return
+ for tag in _BOXES:
+ delay = self._corner_delay if tag in _CORNERS else self._edge_delay
+ if delay != _MAX_DELAY:
+ self._boxes[tag].set_visible(True)
def hide(self):
+ self._is_active = False
+ if not HAS_LAYER_SHELL:
+ return
for box in list(self._boxes.values()):
- box.hide()
+ box.set_visible(False)
- def _window_stacking_changed_cb(self, screen):
- for box in list(self._boxes.values()):
- box.get_window().raise_()
+ def set_visible(self, visible):
+ if visible:
+ self.show()
+ else:
+ self.hide()
diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py
index 25150d9ea2..37330029ef 100644
--- a/src/jarabe/frame/frame.py
+++ b/src/jarabe/frame/frame.py
@@ -20,7 +20,6 @@
from gi.repository import GLib
from gi.repository import Gio
-from sugar4.graphics import animator
from sugar4.graphics import style
from sugar4.graphics import palettegroup
from sugar4 import profile
@@ -35,7 +34,6 @@
from jarabe.frame.notification import NotificationIcon, NotificationWindow
from jarabe.model import notifications
-
TOP_RIGHT = 0
TOP_LEFT = 1
BOTTOM_RIGHT = 2
@@ -44,15 +42,15 @@
NOTIFICATION_DURATION = 5000
-class _Animation(animator.Animation):
-
- def __init__(self, frame, end):
- start = frame.current_position
- animator.Animation.__init__(self, start, end)
- self._frame = frame
+def _get_screen_size():
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geometry = monitors.get_item(0).get_geometry()
+ return geometry.width, geometry.height
+ return 1024, 768
- def next_frame(self, current):
- self._frame.move(current)
class Frame(object):
@@ -69,20 +67,49 @@ def __init__(self):
self._bottom_panel = None
self._wanted = False
- self.current_position = 0.0
- self._animator = None
-
+ self._hide_timeout_id = 0
+
self._event_area = EventArea(self.settings)
self._event_area.connect('enter', self._enter_corner_cb)
- self._event_area.show()
-
+ self._event_area.connect('leave', self._leave_corner_cb)
+
self._top_panel = self._create_top_panel()
self._bottom_panel = self._create_bottom_panel()
self._left_panel = self._create_left_panel()
self._right_panel = self._create_right_panel()
- screen = Gdk.Screen.get_default()
- screen.connect('size-changed', self._size_changed_cb)
+ for panel in (self._top_panel, self._bottom_panel,
+ self._left_panel, self._right_panel):
+ if panel:
+ controller = Gtk.EventControllerMotion.new()
+ controller.connect('enter', self._panel_enter_cb, panel)
+ controller.connect('leave', self._panel_leave_cb, panel)
+ panel.add_controller(controller)
+
+ from jarabe.model import shell
+ shell_model = shell.get_model()
+ if hasattr(shell_model, '_overlay') and shell_model._overlay:
+ # Add event area hot corners
+ for box in self._event_area._boxes.values():
+ shell_model._overlay.add_overlay(box)
+ box.set_visible(True)
+
+ # Add frame panels
+ shell_model._overlay.add_overlay(self._top_panel)
+ shell_model._overlay.add_overlay(self._bottom_panel)
+ shell_model._overlay.add_overlay(self._left_panel)
+ shell_model._overlay.add_overlay(self._right_panel)
+
+
+
+ display = Gdk.Display.get_default()
+ if display:
+ self._monitors = display.get_monitors()
+ if self._monitors:
+ self._monitors.connect('items-changed', self._size_changed_cb)
+ for i in range(self._monitors.get_n_items()):
+ monitor = self._monitors.get_item(i)
+ monitor.connect('notify::geometry', self._monitor_geometry_changed_cb)
self._notif_by_icon = {}
@@ -93,7 +120,12 @@ def __init__(self):
self.__notification_cancelled_cb)
def is_visible(self):
- return self.current_position != 0.0
+ # Check if any panel is currently revealed
+ for panel in (self._top_panel, self._bottom_panel,
+ self._left_panel, self._right_panel):
+ if panel and panel.get_reveal_child():
+ return True
+ return False
visible = property(is_visible, None)
@@ -104,47 +136,37 @@ def toggle(self):
self.hide()
def hide(self):
+ self._cancel_hide_timeout()
if not self._wanted:
return
-
self._wanted = False
-
- if self._animator:
- self._animator.stop()
-
palettegroup.popdown_all()
- self._animator = animator.Animator(0.5, widget=self._top_panel)
- self._animator.add(_Animation(self, 0.0))
- self._animator.start()
+ for panel in (self._top_panel, self._bottom_panel,
+ self._left_panel, self._right_panel):
+ if panel:
+ panel.set_reveal_child(False)
def show(self):
+ self._cancel_hide_timeout()
if self._wanted:
return
-
self._wanted = True
-
- if self._animator:
- self._animator.stop()
-
- self._animator = animator.Animator(0.5, widget=self._top_panel)
- self._animator.add(_Animation(self, 1.0))
- self._animator.start()
-
- def move(self, pos):
- self.current_position = pos
- self._update_position()
+ for panel in (self._top_panel, self._bottom_panel,
+ self._left_panel, self._right_panel):
+ if panel:
+ panel.set_reveal_child(True)
def _create_top_panel(self):
panel = self._create_panel(Gtk.PositionType.TOP)
zoom_toolbar = ZoomToolbar()
panel.append(zoom_toolbar, expand=False)
- zoom_toolbar.show()
+ zoom_toolbar.set_visible(True)
zoom_toolbar.connect('level-clicked', self._level_clicked_cb)
activities_tray = ActivitiesTray()
panel.append(activities_tray)
- activities_tray.show()
+ activities_tray.set_visible(True)
return panel
@@ -153,7 +175,7 @@ def _create_bottom_panel(self):
devices_tray = DevicesTray()
panel.append(devices_tray)
- devices_tray.show()
+ devices_tray.set_visible(True)
return panel
@@ -162,56 +184,80 @@ def _create_right_panel(self):
tray = FriendsTray()
panel.append(tray)
- tray.show()
+ tray.set_visible(True)
return panel
def _create_left_panel(self):
panel = ClipboardPanelWindow(self, Gtk.PositionType.LEFT)
-
return panel
def _create_panel(self, orientation):
panel = FrameWindow(orientation)
-
return panel
- def _move_panel(self, panel, pos, x1, y1, x2, y2):
- x = (x2 - x1) * pos + x1
- y = (y2 - y1) * pos + y1
-
- panel.move(int(x), int(y))
-
- # FIXME we should hide and show as necessary to free memory
- if not panel.props.visible:
- panel.show()
-
def _level_clicked_cb(self, zoom_toolbar):
self.hide()
def _update_position(self):
- screen_h = Gdk.Screen.height()
- screen_w = Gdk.Screen.width()
-
- self._move_panel(self._top_panel, self.current_position,
- 0, - self._top_panel.size, 0, 0)
-
- self._move_panel(self._bottom_panel, self.current_position,
- 0, screen_h, 0, screen_h - self._bottom_panel.size)
-
- self._move_panel(self._left_panel, self.current_position,
- - self._left_panel.size, 0, 0, 0)
-
- self._move_panel(self._right_panel, self.current_position,
- screen_w, 0, screen_w - self._right_panel.size, 0)
-
- def _size_changed_cb(self, screen):
+ # With Revealer approach, animation is handled by the Revealer widget.
+ # This method is kept for compatibility (called on screen resize).
+ # Ensure panels reflect wanted state after a resize.
+ for panel in (self._top_panel, self._bottom_panel,
+ self._left_panel, self._right_panel):
+ if panel is None:
+ continue
+ panel.set_reveal_child(self._wanted)
+
+ def _size_changed_cb(self, monitors, position, removed, added):
+ for i in range(added):
+ monitor = monitors.get_item(position + i)
+ if monitor:
+ monitor.connect('notify::geometry', self._monitor_geometry_changed_cb)
+ self._update_position()
+
+ def _monitor_geometry_changed_cb(self, monitor, pspec):
self._update_position()
def _enter_corner_cb(self, event_area):
+ self._cancel_hide_timeout()
self.toggle()
+ def _panel_enter_cb(self, controller, x, y, panel):
+ panel.hover = True
+ self._cancel_hide_timeout()
+
+ def _panel_leave_cb(self, controller, panel):
+ panel.hover = False
+ self._check_auto_hide()
+
+ def _leave_corner_cb(self, event_area):
+ self._check_auto_hide()
+
+ def _cancel_hide_timeout(self):
+ if self._hide_timeout_id:
+ GLib.source_remove(self._hide_timeout_id)
+ self._hide_timeout_id = 0
+
+ def _check_auto_hide(self):
+ self._cancel_hide_timeout()
+ # Schedule auto-hide check after 250ms
+ self._hide_timeout_id = GLib.timeout_add(250, self._auto_hide_cb)
+
+ def _auto_hide_cb(self):
+ self._hide_timeout_id = 0
+ any_hover = self._event_area._hover or \
+ (self._top_panel and getattr(self._top_panel, 'hover', False)) or \
+ (self._bottom_panel and getattr(self._bottom_panel, 'hover', False)) or \
+ (self._left_panel and getattr(self._left_panel, 'hover', False)) or \
+ (self._right_panel and getattr(self._right_panel, 'hover', False))
+
+ if not any_hover and self._wanted:
+ self.hide()
+ return False
+
def notify_key_press(self):
+ self._cancel_hide_timeout()
self.toggle()
'''
@@ -227,22 +273,27 @@ def add_notification(self, icon, corner=Gtk.CornerType.TOP_LEFT,
window = NotificationWindow()
- screen = Gdk.Screen.get_default()
if corner == Gtk.CornerType.TOP_LEFT:
- window.move(0, 0)
+ window.set_halign(Gtk.Align.START)
+ window.set_valign(Gtk.Align.START)
elif corner == Gtk.CornerType.TOP_RIGHT:
- window.move(screen.get_width() - style.GRID_CELL_SIZE, 0)
+ window.set_halign(Gtk.Align.END)
+ window.set_valign(Gtk.Align.START)
elif corner == Gtk.CornerType.BOTTOM_LEFT:
- window.move(0, screen.get_height() - style.GRID_CELL_SIZE)
+ window.set_halign(Gtk.Align.START)
+ window.set_valign(Gtk.Align.END)
elif corner == Gtk.CornerType.BOTTOM_RIGHT:
- window.move(screen.get_width() - style.GRID_CELL_SIZE,
- screen.get_height() - style.GRID_CELL_SIZE)
- else:
- raise ValueError('Inalid corner: %r' % corner)
+ window.set_halign(Gtk.Align.END)
+ window.set_valign(Gtk.Align.END)
+
+ window.append(icon)
+ icon.set_visible(True)
+ window.set_visible(True)
- window.add(icon)
- icon.show()
- window.show()
+ from jarabe.model import shell
+ shell_model = shell.get_model()
+ if hasattr(shell_model, '_overlay') and shell_model._overlay:
+ shell_model._overlay.add_overlay(window)
self._notif_by_icon[icon] = window
@@ -256,10 +307,15 @@ def remove_notification(self, icon):
return
window = self._notif_by_icon[icon]
- window.destroy()
+ parent = window.get_parent()
+ if parent:
+ if hasattr(parent, 'remove_overlay'):
+ parent.remove_overlay(window)
+ else:
+ window.unparent()
del self._notif_by_icon[icon]
- def __button_release_event_cb(self, icon, data=None):
+ def __button_release_event_cb(self, gesture, n_press, x, y, icon):
self.remove_notification(icon)
self.show()
@@ -267,7 +323,10 @@ def __notification_received_cb(self, **kwargs):
logging.debug('__notification_received_cb')
icon = NotificationIcon()
icon.show_badge()
- icon.connect('button-release-event', self.__button_release_event_cb)
+
+ click = Gtk.GestureClick.new()
+ click.connect('released', self.__button_release_event_cb, icon)
+ icon.add_controller(click)
hints = kwargs['hints']
@@ -292,6 +351,5 @@ def __notification_received_cb(self, **kwargs):
self.add_notification(icon, Gtk.CornerType.TOP_LEFT, duration)
def __notification_cancelled_cb(self, **kwargs):
- # Do nothing for now. Our notification UI is so simple, there's no
# point yet.
pass
diff --git a/src/jarabe/frame/frameinvoker.py b/src/jarabe/frame/frameinvoker.py
index 0e62a83ebd..403047170f 100644
--- a/src/jarabe/frame/frameinvoker.py
+++ b/src/jarabe/frame/frameinvoker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, Eduardo Silva
+# Copyright (C) 2007, Eduardo Silva
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -22,10 +22,24 @@
def _get_screen_area():
frame_thickness = style.GRID_CELL_SIZE
+ display = Gdk.Display.get_default()
+ width = 1024
+ height = 768
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ max_x, max_y = 0, 0
+ for i in range(monitors.get_n_items()):
+ geometry = monitors.get_item(i).get_geometry()
+ max_x = max(max_x, geometry.x + geometry.width)
+ max_y = max(max_y, geometry.y + geometry.height)
+ width = max_x
+ height = max_y
+
screen_area = Gdk.Rectangle()
screen_area.x = screen_area.y = frame_thickness
- screen_area.width = Gdk.Screen.width() - frame_thickness
- screen_area.height = Gdk.Screen.height() - frame_thickness
+ screen_area.width = width - frame_thickness
+ screen_area.height = height - frame_thickness
return screen_area
@@ -33,7 +47,6 @@ def _get_screen_area():
class FrameWidgetInvoker(WidgetInvoker):
def __init__(self, widget):
- WidgetInvoker.__init__(self, widget, widget.get_child())
-
+ WidgetInvoker.__init__(self, widget, widget)
self._position_hint = self.ANCHORED
self._screen_area = _get_screen_area()
diff --git a/src/jarabe/frame/framewindow.py b/src/jarabe/frame/framewindow.py
index 5e6d018ba9..0b7f52c248 100644
--- a/src/jarabe/frame/framewindow.py
+++ b/src/jarabe/frame/framewindow.py
@@ -15,11 +15,32 @@
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import Graphene
+import gi
+
+gi.require_version('Gsk', '4.0')
+from gi.repository import Gsk
+
+try:
+ gi.require_version('Gtk4LayerShell', '1.0')
+ from gi.repository import Gtk4LayerShell
+except ValueError:
+ Gtk4LayerShell = None
from sugar4.graphics import style
-class FrameContainer(Gtk.Bin):
+def _get_screen_size():
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geometry = monitors.get_item(0).get_geometry()
+ return geometry.width, geometry.height
+ return 1024, 768
+
+
+class FrameContainer(Gtk.Widget):
"""A container class for frame panel rendering. Hosts a child 'box' where
frame elements can be added. Excludes grid-sized squares at each end
of the frame panel, and a space alongside the inside of the screen where
@@ -28,58 +49,80 @@ class FrameContainer(Gtk.Bin):
__gtype_name__ = 'SugarFrameContainer'
def __init__(self, position):
- Gtk.Bin.__init__(self)
+ super().__init__()
self._position = position
if self.is_vertical():
- box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
else:
- box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- self.add(box)
- box.show()
+ self._box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self._box.set_parent(self)
+
+ def do_dispose(self):
+ if self._box:
+ self._box.unparent()
+ self._box = None
+ super().do_dispose()
+
+ def get_child_box(self):
+ return self._box
def is_vertical(self):
return self._position in (Gtk.PositionType.LEFT,
Gtk.PositionType.RIGHT)
- def do_draw(self, cr):
+ def do_snapshot(self, snapshot):
+ width = self.get_width()
+ height = self.get_height()
+
+ # Render CSS background (e.g. framewindow background color)
+ context = self.get_style_context()
+ snapshot.render_background(context, 0, 0, width, height)
+
# Draw the inner border as a rectangle
- r, g, b, a = style.COLOR_BUTTON_GREY.get_rgba()
- cr.set_source_rgba(r, g, b, a)
+ color = Gdk.RGBA()
+ color.parse(style.COLOR_BUTTON_GREY.get_svg())
- allocation = self.get_allocation()
if self.is_vertical():
x = style.GRID_CELL_SIZE \
if self._position == Gtk.PositionType.LEFT else 0
y = style.GRID_CELL_SIZE
- width = style.LINE_WIDTH
- height = allocation.height - (style.GRID_CELL_SIZE * 2)
+ rect_width = style.LINE_WIDTH
+ rect_height = height - (style.GRID_CELL_SIZE * 2)
else:
x = style.GRID_CELL_SIZE
y = style.GRID_CELL_SIZE \
if self._position == Gtk.PositionType.TOP else 0
- height = style.LINE_WIDTH
- width = allocation.width - (style.GRID_CELL_SIZE * 2)
-
- cr.rectangle(x, y, width, height)
- cr.fill()
-
- Gtk.Bin.do_draw(self, cr)
- return False
-
- def do_size_request(self, req):
+ rect_height = style.LINE_WIDTH
+ rect_width = width - (style.GRID_CELL_SIZE * 2)
+
+ if rect_width > 0 and rect_height > 0:
+ rect = Graphene.Rect()
+ rect.init(x, y, rect_width, rect_height)
+ snapshot.append_color(color, rect)
+
+ if self._box:
+ self.snapshot_child(self._box, snapshot)
+
+ def do_measure(self, orientation, for_size):
+ if self._box:
+ self._box.measure(orientation, for_size)
+
+ sw, sh = _get_screen_size()
if self.is_vertical():
- req.height = Gdk.Screen.height()
- req.width = style.GRID_CELL_SIZE + style.LINE_WIDTH
+ if orientation == Gtk.Orientation.VERTICAL:
+ return sh, sh, -1, -1
+ else:
+ w = style.GRID_CELL_SIZE + style.LINE_WIDTH
+ return w, w, -1, -1
else:
- req.width = Gdk.Screen.width()
- req.height = style.GRID_CELL_SIZE + style.LINE_WIDTH
-
- self.get_child().size_request()
-
- def do_size_allocate(self, allocation):
- self.set_allocation(allocation)
+ if orientation == Gtk.Orientation.HORIZONTAL:
+ return sw, sw, -1, -1
+ else:
+ h = style.GRID_CELL_SIZE + style.LINE_WIDTH
+ return h, h, -1, -1
+ def do_size_allocate(self, width, height, baseline):
# exclude grid squares at two ends of the frame
# allocate remaining space to child box, minus the space needed for
# drawing the border
@@ -88,74 +131,118 @@ def do_size_allocate(self, allocation):
allocation.x = 0 if self._position == Gtk.PositionType.LEFT \
else style.LINE_WIDTH
allocation.y = style.GRID_CELL_SIZE
- allocation.width = self.get_allocation().width - style.LINE_WIDTH
- allocation.height = self.get_allocation().height \
- - (style.GRID_CELL_SIZE * 2)
+ allocation.width = max(0, width - style.LINE_WIDTH)
+ allocation.height = max(0, height - (style.GRID_CELL_SIZE * 2))
else:
allocation.x = style.GRID_CELL_SIZE
allocation.y = 0 if self._position == Gtk.PositionType.TOP \
else style.LINE_WIDTH
- allocation.width = self.get_allocation().width \
- - (style.GRID_CELL_SIZE * 2)
- allocation.height = self.get_allocation().height - style.LINE_WIDTH
+ allocation.width = max(0, width - (style.GRID_CELL_SIZE * 2))
+ allocation.height = max(0, height - style.LINE_WIDTH)
+
+ if self._box:
+ transform = Gsk.Transform.new().translate(Graphene.Point().init(allocation.x, allocation.y))
+ self._box.allocate(allocation.width, allocation.height, baseline, transform)
+
- self.get_child().size_allocate(allocation)
+class FrameWindow(Gtk.Revealer):
+ """A frame panel that slides in/out using Gtk.Revealer.
+ GTK3 used floating Gtk.Window objects that were moved with window.move().
+ In GTK4, Gtk.Revealer is the correct widget for slide-in/out animations.
+ It handles clipping, animation, and positioning natively.
-class FrameWindow(Gtk.Window):
+ The Revealer wraps a FrameContainer (the actual drawing surface).
+ It is added to the shell's Gtk.Overlay as an overlay child, positioned
+ at its respective screen edge via valign/halign.
+ """
__gtype_name__ = 'SugarFrameWindow'
def __init__(self, position):
- Gtk.Window.__init__(self)
- self.set_has_resize_grip(False)
+ # Choose transition type based on panel edge
+ if position == Gtk.PositionType.TOP:
+ transition = Gtk.RevealerTransitionType.SLIDE_DOWN
+ elif position == Gtk.PositionType.BOTTOM:
+ transition = Gtk.RevealerTransitionType.SLIDE_UP
+ elif position == Gtk.PositionType.LEFT:
+ transition = Gtk.RevealerTransitionType.SLIDE_RIGHT
+ else: # RIGHT
+ transition = Gtk.RevealerTransitionType.SLIDE_LEFT
+
+ super().__init__(
+ transition_type=transition,
+ transition_duration=500,
+ reveal_child=False,
+ )
+
self.hover = False
self.size = style.GRID_CELL_SIZE + style.LINE_WIDTH
-
- accel_group = Gtk.AccelGroup()
- self.sugar_accel_group = accel_group
- self.add_accel_group(accel_group)
-
self._position = position
- self.set_decorated(False)
- self.connect('realize', self._realize_cb)
- self.connect('enter-notify-event', self._enter_notify_cb)
- self.connect('leave-notify-event', self._leave_notify_cb)
+ # Shortcut controller
+ self.shortcut_controller = Gtk.ShortcutController.new()
+ self.shortcut_controller.set_scope(Gtk.ShortcutScope.GLOBAL)
+ self.add_controller(self.shortcut_controller)
+ self.sugar_accel_group = self.shortcut_controller
+ # Hover tracking
+ controller = Gtk.EventControllerMotion.new()
+ controller.connect('enter', self._enter_notify_cb)
+ controller.connect('leave', self._leave_notify_cb)
+ self.add_controller(controller)
+
+ # The actual drawing surface
self._container = FrameContainer(position)
- self.add(self._container)
- self._container.show()
+ self._container.add_css_class('framewindow')
+ self.set_child(self._container)
+
+ # Position the revealer at its screen edge via Overlay alignment
+ if position == Gtk.PositionType.TOP:
+ self.set_valign(Gtk.Align.START)
+ self.set_halign(Gtk.Align.FILL)
+ elif position == Gtk.PositionType.BOTTOM:
+ self.set_valign(Gtk.Align.END)
+ self.set_halign(Gtk.Align.FILL)
+ elif position == Gtk.PositionType.LEFT:
+ self.set_valign(Gtk.Align.FILL)
+ self.set_halign(Gtk.Align.START)
+ elif position == Gtk.PositionType.RIGHT:
+ self.set_valign(Gtk.Align.FILL)
+ self.set_halign(Gtk.Align.END)
+
self._update_size()
- screen = Gdk.Screen.get_default()
- screen.connect('size-changed', self._size_changed_cb)
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors:
+ monitors.connect('items-changed', self._size_changed_cb)
def append(self, child, expand=True, fill=True):
- self._container.get_child().pack_start(child, expand=expand, fill=fill,
- padding=0)
+ if expand:
+ if self._container.is_vertical():
+ child.set_vexpand(True)
+ else:
+ child.set_hexpand(True)
+ if not fill:
+ if self._container.is_vertical():
+ child.set_valign(Gtk.Align.CENTER)
+ else:
+ child.set_halign(Gtk.Align.CENTER)
+ self._container.get_child_box().append(child)
def _update_size(self):
- if self._position == Gtk.PositionType.TOP \
- or self._position == Gtk.PositionType.BOTTOM:
- self.resize(Gdk.Screen.width(), self.size)
+ sw, sh = _get_screen_size()
+ if self._position in (Gtk.PositionType.TOP, Gtk.PositionType.BOTTOM):
+ self._container.set_size_request(sw, self.size)
else:
- self.resize(self.size, Gdk.Screen.height())
-
- def _realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DOCK)
- self.get_window().set_accept_focus(False)
+ self._container.set_size_request(self.size, sh)
- def _enter_notify_cb(self, window, event):
- if event.detail != Gdk.NotifyType.INFERIOR:
- self.hover = True
+ def _enter_notify_cb(self, controller, x, y):
+ self.hover = True
- def _leave_notify_cb(self, window, event):
- if event.detail != Gdk.NotifyType.INFERIOR:
- self.hover = False
+ def _leave_notify_cb(self, controller):
+ self.hover = False
- def _size_changed_cb(self, screen):
+ def _size_changed_cb(self, monitors, position, removed, added):
self._update_size()
-
-
-if hasattr(FrameWindow, 'set_css_name'):
- FrameWindow.set_css_name('framewindow')
diff --git a/src/jarabe/frame/friendstray.py b/src/jarabe/frame/friendstray.py
index 2ab67cc950..40f660f54b 100644
--- a/src/jarabe/frame/friendstray.py
+++ b/src/jarabe/frame/friendstray.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -56,13 +56,16 @@ def __init__(self):
neighborhood.get_model().connect('activity-added',
self.__neighborhood_activity_added_cb)
+ # always display ourselves by default
+ self.add_buddy(get_owner_instance())
+
def add_buddy(self, buddy):
if buddy.props.key in self._buddies:
return
icon = FriendIcon(buddy)
self.add_item(icon)
- icon.show()
+ icon.set_visible(True)
self._buddies[buddy.props.key] = icon
@@ -74,9 +77,8 @@ def remove_buddy(self, buddy):
del self._buddies[buddy.props.key]
def clear(self):
- for item in self.get_children():
- self.remove_item(item)
- item.destroy()
+ for child in self.get_children():
+ self.remove_item(child)
self._buddies = {}
def __neighborhood_activity_added_cb(self, neighborhood_model,
diff --git a/src/jarabe/frame/notification.py b/src/jarabe/frame/notification.py
index c7f323aae4..ad534d1e09 100644
--- a/src/jarabe/frame/notification.py
+++ b/src/jarabe/frame/notification.py
@@ -21,6 +21,7 @@
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import Graphene
from sugar4 import profile
from sugar4.graphics import style
@@ -44,28 +45,24 @@ class NotificationBox(Gtk.Box):
ELLIPSIS_AND_BREAKS = 6
def __init__(self, name):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._name = name
self._notifications_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self._notifications_box.show()
self._scrolled_window = Gtk.ScrolledWindow()
- self._scrolled_window.add_with_viewport(self._notifications_box)
+ self._scrolled_window.set_child(self._notifications_box)
self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
- self._scrolled_window.show()
separator = PaletteMenuItemSeparator()
- separator.show()
clear_item = PaletteMenuItem(_('Clear notifications'), 'dialog-cancel')
clear_item.connect('activate', self.__clear_cb)
- clear_item.show()
- self.add(self._scrolled_window)
- self.add(separator)
- self.add(clear_item)
+ self.append(self._scrolled_window)
+ self.append(separator)
+ self.append(clear_item)
self._service = notifications.get_service()
entries = self._service.retrieve_by_name(self._name)
@@ -73,44 +70,54 @@ def __init__(self, name):
if entries:
for entry in entries:
self._add(entry['summary'], entry['body'])
+ else:
+ self.set_visible(False)
- self._service.notification_received.connect(
+ self._notify_handler = self._service.notification_received.connect(
self.__notification_received_cb)
- self.connect('destroy', self.__destroy_cb)
+ def _get_entries(self):
+ entries = []
+ child = self._notifications_box.get_first_child()
+ while child:
+ entries.append(child)
+ child = child.get_next_sibling()
+ return entries
def _update_scrolled_size(self):
- entries = self._notifications_box.get_children()
+ entries = self._get_entries()
height = 0
for entry in entries[:self.MAX_ENTRIES]:
- requests = entry.get_preferred_size()
- height += requests[1].height
+ _min, nat_height, _min_b, _nat_b = entry.measure(
+ Gtk.Orientation.VERTICAL, -1)
+ height += nat_height
self._scrolled_window.set_size_request(-1, height)
def _add(self, summary, body):
icon = Icon()
icon.props.icon_name = 'emblem-notification'
- icon.props.icon_size = Gtk.IconSize.SMALL_TOOLBAR
+ icon.props.pixel_size = style.SMALL_ICON_SIZE
+
icon.props.xo_color = \
XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
style.COLOR_BLACK.get_svg()))
- icon.show()
summary_label = Gtk.Label()
summary_label.set_max_width_chars(style.MENU_WIDTH_CHARS)
summary_label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
- summary_label.set_alignment(0, 0.5)
+ summary_label.set_xalign(0.0)
+ summary_label.set_yalign(0.5)
summary_label.set_markup('%s' % summary)
- summary_label.show()
body_label = Gtk.Label()
- body_label.set_alignment(0, 0.5)
+ body_label.set_xalign(0.0)
+ body_label.set_yalign(0.5)
if hasattr(body_label, 'set_lines'):
body_label.set_max_width_chars(style.MENU_WIDTH_CHARS)
- body_label.set_line_wrap(True)
+ body_label.set_wrap(True)
body_label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
body_label.set_lines(self.LINES)
body_label.set_justify(Gtk.Justification.FILL)
@@ -124,38 +131,43 @@ def _add(self, summary, body):
body = textwrap.fill(body, width=style.MENU_WIDTH_CHARS)
body_label.set_text(body)
- body_label.show()
grid = Gtk.Grid()
- grid.set_border_width(style.DEFAULT_SPACING)
grid.set_column_spacing(style.DEFAULT_SPACING)
grid.set_row_spacing(0)
+ grid.set_margin_top(style.DEFAULT_SPACING)
+ grid.set_margin_bottom(style.DEFAULT_SPACING)
+ grid.set_margin_start(style.DEFAULT_SPACING)
+ grid.set_margin_end(style.DEFAULT_SPACING)
grid.attach(icon, 0, 0, 1, 2)
grid.attach(summary_label, 1, 0, 1, 1)
grid.attach(body_label, 1, 1, 1, 1)
- grid.show()
- self._notifications_box.add(grid)
+ self._notifications_box.append(grid)
self._update_scrolled_size()
- self.show()
def __clear_cb(self, clear_item):
logging.debug('NotificationBox.__clear_cb')
- for entry in self._notifications_box.get_children():
- self._notifications_box.remove(entry)
+ child = self._notifications_box.get_first_child()
+ while child:
+ next_child = child.get_next_sibling()
+ if child.get_parent() == self._notifications_box:
+ child.unparent()
+ child = next_child
self._service.clear_by_name(self._name)
- self.hide()
+ self.set_visible(False)
def __notification_received_cb(self, **kwargs):
logging.debug('NotificationBox.__notification_received_cb')
if kwargs.get('app_name', '') == self._name:
self._add(kwargs.get('summary', ''), kwargs.get('body', ''))
- def __destroy_cb(self, box):
- logging.debug('NotificationBox.__destroy_cb')
+ def do_unroot(self):
+ logging.debug('NotificationBox.do_unroot')
service = notifications.get_service()
- service.notification_received.disconnect(
- self. __notification_received_cb)
+ if hasattr(self, '_notify_handler') and self._notify_handler:
+ service.notification_received.disconnect(self._notify_handler)
+ self._notify_handler = 0
class NotificationButton(ToolButton):
@@ -168,9 +180,12 @@ def __init__(self, name):
self.palette_invoker.cache_palette = False
self.connect('clicked', self.__clicked_cb)
+ def remove_notification(self, icon):
+ # no-op: notifications are managed by the frame view
+ logging.debug('NotificationButton.remove_notification called')
+
def set_icon(self, icon):
self._icon = icon
- self._icon.show()
self.set_icon_widget(self._icon)
def show_badge(self):
@@ -223,21 +238,25 @@ def show_badge(self):
def hide_badge(self):
self._badge = None
- def do_draw(self, cr):
- PulsingIcon.do_draw(self, cr)
+ def do_snapshot(self, snapshot):
+ PulsingIcon.do_snapshot(self, snapshot)
if self._badge:
- allocation = self.get_allocation()
+ width = self.get_width()
+ height = self.get_height()
- # XXX assume icon is centered in its container
offset = int(self.props.pixel_size / 2) - self.get_badge_size()
- x = int(allocation.width / 2) + offset
- y = int(allocation.height / 2) + offset
-
- cr.set_source_surface(self._badge, x, y)
+ x = int(width / 2) + offset
+ y = int(height / 2) + offset
+
+ badge_size = self.get_badge_size()
+ rect = Graphene.Rect()
+ rect.init(x, y, badge_size, badge_size)
+ cr = snapshot.append_cairo(rect)
+ cr.set_source_surface(self._badge, 0, 0)
cr.paint()
-class NotificationIcon(Gtk.EventBox):
+class NotificationIcon(Gtk.Box):
__gtype_name__ = 'SugarNotificationIcon'
__gproperties__ = {
@@ -252,15 +271,13 @@ def __init__(self, **kwargs):
self._icon = NotificationPulsingIcon()
self._icon.props.pixel_size = style.STANDARD_ICON_SIZE
- Gtk.EventBox.__init__(self, **kwargs)
- self.props.visible_window = False
+ super().__init__(**kwargs)
self._icon.props.pulse_color = \
XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
style.COLOR_TRANSPARENT.get_svg()))
self._icon.props.pulsing = True
- self.add(self._icon)
- self._icon.show()
+ self.append(self._icon)
GLib.timeout_add_seconds(self._PULSE_TIMEOUT,
self.__stop_pulsing_cb)
@@ -305,20 +322,16 @@ def hide_badge(self):
self._icon.hide_badge()
-class NotificationWindow(Gtk.Window):
+class NotificationWindow(Gtk.Box):
__gtype_name__ = 'SugarNotificationWindow'
def __init__(self, **kwargs):
-
- Gtk.Window.__init__(self, **kwargs)
-
- self.set_decorated(False)
- self.set_resizable(False)
- self.connect('realize', self._realize_cb)
-
- def _realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- self.get_window().set_accept_focus(False)
-
- color = Gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html())
- self.modify_bg(Gtk.StateType.NORMAL, color)
+ # strip Gtk.Window-specific kwargs not valid on Gtk.Box
+ box_kwargs = {k: v for k, v in kwargs.items()
+ if k not in ('decorated', 'resizable', 'focusable')}
+ super().__init__(**box_kwargs)
+
+ self.add_css_class('notification-window')
+ self.add_css_class('frame-notification')
+ self.set_valign(Gtk.Align.START)
+ self.set_halign(Gtk.Align.START)
diff --git a/src/jarabe/frame/zoomtoolbar.py b/src/jarabe/frame/zoomtoolbar.py
index e5b18ab37f..70ea743041 100644
--- a/src/jarabe/frame/zoomtoolbar.py
+++ b/src/jarabe/frame/zoomtoolbar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2009 Simon Schampijer
#
# This program is free software: you can redistribute it and/or modify
@@ -29,14 +29,17 @@
from jarabe.model import shell
-class ZoomToolbar(Gtk.Toolbar):
+class ZoomToolbar(Gtk.Box):
+ __gtype_name__ = 'SugarZoomToolbar'
+
__gsignals__ = {
'level-clicked': (GObject.SignalFlags.RUN_FIRST, None,
([]))
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
+ self.add_css_class('toolbar')
# we shouldn't be mirrored in RTL locales
self.set_direction(Gtk.TextDirection.LTR)
@@ -67,29 +70,37 @@ def __init__(self):
shell_model.zoom_level_changed.connect(self.__zoom_level_changed_cb)
def _add_button(self, icon_name, label, accelerator, zoom_level):
- if self.get_children():
- group = self.get_children()[0]
- else:
- group = None
+ group = self.get_first_child()
button = RadioToolButton(icon_name=icon_name, group=group,
accelerator=accelerator)
- button.connect('clicked', self.__level_clicked_cb, zoom_level)
- self.add(button)
- button.show()
+ button.connect('toggled', self.__level_toggled_cb, zoom_level)
+ self.append(button)
- palette = Palette(GLib.markup_escape_text(label))
- palette.props.invoker = FrameWidgetInvoker(button)
+ invoker = FrameWidgetInvoker(button)
+ palette = Palette(label)
+ invoker.palette = palette
palette.set_group_id('frame')
- button.set_palette(palette)
+ button.set_palette_invoker(invoker)
return button
- def __level_clicked_cb(self, button, level):
+ def __level_toggled_cb(self, button, level):
if not button.get_active():
return
- shell.get_model().set_zoom_level(level)
+ model = shell.get_model()
+ if level == shell.ShellModel.ZOOM_ACTIVITY and \
+ model.get_active_activity() is None:
+ # No activity open: show Journal instead of the empty compositor.
+ from jarabe.journal import journalactivity
+ journalactivity.get_journal().show_journal()
+ # Restore the toolbar button to the current zoom level so the
+ # toolbar stays consistent (we didn't change the zoom level).
+ GLib.idle_add(self._set_zoom_level, model.zoom_level)
+ else:
+ model.set_zoom_level(level)
+
self.emit('level-clicked')
def __zoom_level_changed_cb(self, **kwargs):
@@ -98,12 +109,12 @@ def __zoom_level_changed_cb(self, **kwargs):
def _set_zoom_level(self, new_level):
logging.debug('new zoom level: %r', new_level)
if new_level == shell.ShellModel.ZOOM_MESH:
- self._mesh_button.props.active = True
+ self._mesh_button.set_active(True)
elif new_level == shell.ShellModel.ZOOM_GROUP:
- self._groups_button.props.active = True
+ self._groups_button.set_active(True)
elif new_level == shell.ShellModel.ZOOM_HOME:
- self._home_button.props.active = True
+ self._home_button.set_active(True)
elif new_level == shell.ShellModel.ZOOM_ACTIVITY:
- self._activity_button.props.active = True
+ self._activity_button.set_active(True)
else:
raise ValueError('Invalid zoom level: %r' % (new_level))
diff --git a/src/jarabe/intro/__init__.py b/src/jarabe/intro/__init__.py
index 63ffc9ad15..eb965d7d24 100644
--- a/src/jarabe/intro/__init__.py
+++ b/src/jarabe/intro/__init__.py
@@ -1,4 +1,4 @@
-import os
+import os
from gi.repository import Gio
diff --git a/src/jarabe/intro/agepicker.py b/src/jarabe/intro/agepicker.py
index 9ae9dbe3eb..5d7292b924 100644
--- a/src/jarabe/intro/agepicker.py
+++ b/src/jarabe/intro/agepicker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014, Sugar Labs
+# Copyright (C) 2014, Sugar Labs
# Copyright (C) 2014, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
@@ -34,6 +33,18 @@
from jarabe.intro.genderpicker import GENDERS
_group_labels = None
+_settings = None
+
+
+def _get_settings():
+ global _settings
+
+ if _settings is None:
+ _settings = Gio.Settings.new('org.sugarlabs.user')
+
+ return _settings
+
+
_SECONDS_PER_YEAR = 365 * 24 * 60 * 60.
_DEFAULT_PROMPT = _('Select grade:')
_DEFAULT_LABELS = [_('Preschool'), _('Kindergarten'), _('1st Grade'),
@@ -87,7 +98,7 @@ def group_label_to_age(label):
def load_age():
group_labels = get_group_labels()
- settings = Gio.Settings.new('org.sugarlabs.user')
+ settings = _get_settings()
birth_timestamp = settings.get_int('birth-timestamp')
if birth_timestamp == 0:
@@ -109,29 +120,36 @@ def load_age():
def save_age(age):
birth_timestamp = calculate_birth_timestamp(age)
- settings = Gio.Settings.new('org.sugarlabs.user')
- settings.set_int('birth-timestamp', birth_timestamp)
+ settings = _get_settings()
+
+ if settings.get_int('birth-timestamp') != birth_timestamp:
+ settings.set_int('birth-timestamp', birth_timestamp)
- # Record the label so we know it was set
- settings.set_string('group-label', age_to_group_label(age))
+ group_label = age_to_group_label(age)
+ if settings.get_string('group-label') != group_label:
+ settings.set_string('group-label', group_label)
class GroupLabels():
- GROUP_LABEL = []
+ GROUP_LABEL = _DEFAULT_PROMPT
AGES = []
LABELS = []
ICONS = []
def __init__(self):
- f = open(os.environ['SUGAR_GROUP_LABELS'], 'r')
- json_data = f.read()
- f.close()
- group_labels = json.loads(json_data)
- self.GROUP_LABEL = group_labels['group-label']
- for item in group_labels['group-items']:
- self.ICONS.append([item['female-icon'], item['male-icon']])
- self.LABELS.append(_(item['label']))
- self.AGES.append(item['age'])
+ labels_path = os.environ.get('SUGAR_GROUP_LABELS', '/etc/sugar/group-labels')
+ try:
+ f = open(labels_path, 'r')
+ json_data = f.read()
+ f.close()
+ group_labels = json.loads(json_data)
+ self.GROUP_LABEL = group_labels.get('group-label', _DEFAULT_PROMPT)
+ for item in group_labels.get('group-items', []):
+ self.ICONS.append([item['female-icon'], item['male-icon']])
+ self.LABELS.append(_(item['label']))
+ self.AGES.append(item['age'])
+ except Exception:
+ self.GROUP_LABEL = _DEFAULT_PROMPT
def get_group_labels():
@@ -146,27 +164,17 @@ def get_group_labels():
class Picker(Gtk.Grid):
def __init__(self, icon, label):
- Gtk.Grid.__init__(self)
+ super().__init__()
self._button = EventIcon(pixel_size=style.LARGE_ICON_SIZE,
icon_name=icon)
self.attach(self._button, 0, 0, 1, 1)
- self._button.hide()
+ self._button.set_visible(True)
- self._label = Gtk.Label(label.replace(' ', '\n'))
+ self._label = Gtk.Label(label=label.replace(' ', '\n'))
self._label.props.justify = Gtk.Justification.CENTER
self.attach(self._label, 0, 1, 1, 1)
- self._label.hide()
-
- def show_all(self):
- self._button.show()
- self._label.show()
- self.show()
-
- def hide_all(self):
- self._button.hide()
- self._label.hide()
- self.hide()
+ self._label.set_visible(True)
def connect(self, callback, arg):
self._button.connect('activate', callback, arg)
@@ -178,12 +186,21 @@ def set_icon(self, icon):
self._button.set_icon_name(icon)
+def _get_screen_width():
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ return monitors.get_item(0).get_geometry().width
+ return 1024
+
+
class AgePicker(Gtk.Grid):
age_changed_signal = GObject.Signal('age-changed', arg_types=([int]))
def __init__(self, gender, page=None):
- Gtk.Grid.__init__(self)
+ super().__init__()
self.set_row_spacing(style.DEFAULT_SPACING)
self.set_column_spacing(style.DEFAULT_SPACING)
@@ -203,7 +220,7 @@ def __init__(self, gender, page=None):
gender_index = GENDERS.index(self._gender)
age_index = age_to_index(self._age)
- width = Gdk.Screen.width()
+ width = _get_screen_width()
num_ages = len(self._group_labels.AGES)
for i in range(num_ages):
@@ -213,17 +230,17 @@ def __init__(self, gender, page=None):
self._pickers[i].connect(self._button_activate_cb, i)
self._fixed = Gtk.Fixed()
- fixed_size = width - 4 * style.GRID_CELL_SIZE
+ fixed_size = max(1, width - 4 * style.GRID_CELL_SIZE)
self._fixed.set_size_request(fixed_size, -1)
self.attach(self._fixed, 0, 0, 1, 1)
- self._fixed.show()
+ self._fixed.set_visible(True)
self._age_adj = Gtk.Adjustment(value=age_index, lower=0,
- upper=num_ages - 1, step_incr=1,
- page_incr=3, page_size=0)
+ upper=num_ages - 1, step_increment=1,
+ page_increment=3, page_size=0)
self._age_adj.connect('value-changed', self.__age_adj_changed_cb)
- self._age_slider = Gtk.HScale()
+ self._age_slider = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL)
self._age_slider.set_draw_value(False)
self._age_slider.set_adjustment(self._age_adj)
self.attach(self._age_slider, 0, 1, 1, 1)
@@ -233,14 +250,18 @@ def __init__(self, gender, page=None):
self._configure(width)
- Gdk.Screen.get_default().connect('size-changed', self._configure_cb)
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ monitors.get_item(0).connect('notify::geometry', self._configure_cb)
- def _configure_cb(self, event=None):
- width = Gdk.Screen.width()
+ def _configure_cb(self, event=None, pspec=None):
+ width = _get_screen_width()
self._configure(width)
def _configure(self, width):
- fixed_size = width - 4 * style.GRID_CELL_SIZE
+ fixed_size = max(1, width - 4 * style.GRID_CELL_SIZE)
self._fixed.set_size_request(fixed_size, -1)
num_ages = len(self._group_labels.AGES)
@@ -251,10 +272,10 @@ def _configure(self, width):
if num_ages + 2 < width / style.LARGE_ICON_SIZE:
for i in range(num_ages):
- self._pickers[i].show_all()
- self._age_slider.hide()
+ self._pickers[i].set_visible(True)
+ self._age_slider.set_visible(False)
else:
- self._age_slider.show()
+ self._age_slider.set_visible(True)
value = self._age_adj.get_value()
self._set_age_picker(int(value + 0.5))
@@ -264,9 +285,9 @@ def get_label(self):
def _set_age_picker(self, age_index):
for i in range(len(self._group_labels.AGES)):
if i == age_index:
- self._pickers[i].show_all()
+ self._pickers[i].set_visible(True)
else:
- self._pickers[i].hide_all()
+ self._pickers[i].set_visible(False)
self._do_selected(age_index)
def __age_adj_changed_cb(self, widget):
diff --git a/src/jarabe/intro/colorpicker.py b/src/jarabe/intro/colorpicker.py
index 14ae4385ba..6a9711384d 100644
--- a/src/jarabe/intro/colorpicker.py
+++ b/src/jarabe/intro/colorpicker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,21 +21,25 @@
from sugar4.graphics.xocolor import XoColor
-class ColorPicker(Gtk.EventBox):
+class ColorPicker(Gtk.Box):
def __init__(self):
- Gtk.EventBox.__init__(self)
+ super().__init__()
self._xo_color = None
self._xo = Icon(pixel_size=style.XLARGE_ICON_SIZE,
icon_name='computer-xo')
self._set_random_colors()
- self.connect('button-press-event', self._button_press_cb)
- self.add(self._xo)
- self._xo.show()
-
- def _button_press_cb(self, widget, event):
- if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
+
+ click = Gtk.GestureClick()
+ click.connect('pressed', self._button_press_cb)
+ self.add_controller(click)
+
+ self.append(self._xo)
+ self._xo.set_visible(True)
+
+ def _button_press_cb(self, gesture, n_press, x, y):
+ if gesture.get_current_button() == 1:
self._set_random_colors()
def get_color(self):
diff --git a/src/jarabe/intro/genderpicker.py b/src/jarabe/intro/genderpicker.py
index e545c3ffd7..776a7c79a0 100644
--- a/src/jarabe/intro/genderpicker.py
+++ b/src/jarabe/intro/genderpicker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014, Sugar Labs
+# Copyright (C) 2014, Sugar Labs
# Copyright (C) 2014, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GObject
@@ -23,7 +22,6 @@
from sugar4.graphics import style
from sugar4.graphics.xocolor import XoColor
-
GENDERS = ['female', 'male']
@@ -45,7 +43,7 @@ class GenderPicker(Gtk.Grid):
gender_changed_signal = GObject.Signal('gender-changed', arg_types=([str]))
def __init__(self):
- Gtk.Grid.__init__(self)
+ super().__init__()
self.set_row_spacing(style.DEFAULT_SPACING)
self.set_column_spacing(style.DEFAULT_SPACING)
@@ -60,7 +58,7 @@ def __init__(self):
self._buttons[-1].connect('activate',
self._button_activate_cb, i)
self.attach(self._buttons[-1], i * 2, 0, 1, 1)
- self._buttons[-1].show()
+ self._buttons[-1].set_visible(True)
self.reset_button = EventIcon(pixel_size=style.SMALL_ICON_SIZE,
icon_name='entry-cancel')
@@ -68,7 +66,7 @@ def __init__(self):
self._reset_button_activate_cb)
self.attach(self.reset_button, 1, 0, 1, 1)
self.reset_button.xo_color = XoColor('#010101,#a0a0a0')
- self.reset_button.show()
+ self.reset_button.set_visible(True)
def _reset_button_activate_cb(self, widget):
self._set_gender('')
diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
index 246492c1a9..2dc6eb1e30 100644
--- a/src/jarabe/intro/window.py
+++ b/src/jarabe/intro/window.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -80,7 +80,7 @@ def create_profile(user_profile):
logging.debug("Generating user keypair")
- cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % (keypath, )
+ cmd = "ssh-keygen -q -t rsa -f %s -C '' -N ''" % (keypath, )
(s, o) = subprocess.getstatusoutput(cmd)
if s != 0:
logging.error('Could not generate key pair: %d %s', s, o)
@@ -94,7 +94,7 @@ class _Page(Gtk.Box):
}
def __init__(self):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.valid = False
def set_valid(self, valid):
@@ -115,26 +115,26 @@ def __init__(self, intro):
_Page.__init__(self)
self._intro = intro
- alignment = Gtk.Alignment.new(0.5, 0.5, 0, 0)
- self.pack_start(alignment, expand=True, fill=True, padding=0)
-
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
- alignment.add(grid)
+ grid.set_row_spacing(style.DEFAULT_SPACING)
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.CENTER)
+ grid.set_hexpand(True)
+ grid.set_vexpand(True)
+ self.append(grid)
+ grid.set_visible(True)
label = Gtk.Label(label=_('Name:'))
grid.attach(label, 0, 0, 1, 1)
- label.show()
+ label.set_visible(True)
self._entry = Gtk.Entry()
self._entry.connect('notify::text', self._text_changed_cb)
self._entry.set_size_request(style.zoom(300), -1)
self._entry.set_max_length(45)
grid.attach(self._entry, 0, 1, 1, 1)
- self._entry.show()
-
- grid.show()
- alignment.show()
+ self._entry.set_visible(True)
def _text_changed_cb(self, entry, pspec):
valid = len(entry.props.text.strip()) > 0
@@ -155,23 +155,23 @@ class _ColorPage(_Page):
def __init__(self):
_Page.__init__(self)
- alignment = Gtk.Alignment.new(0.5, 0.5, 0, 0)
- self.pack_start(alignment, expand=True, fill=True, padding=0)
-
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
- alignment.add(grid)
+ grid.set_row_spacing(style.DEFAULT_SPACING)
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.CENTER)
+ grid.set_hexpand(True)
+ grid.set_vexpand(True)
+ self.append(grid)
+ grid.set_visible(True)
label = Gtk.Label(label=_('Click to change color:'))
grid.attach(label, 0, 0, 1, 1)
- label.show()
+ label.set_visible(True)
self._cp = colorpicker.ColorPicker()
grid.attach(self._cp, 0, 1, 1, 1)
- self._cp.show()
-
- grid.show()
- alignment.show()
+ self._cp.set_visible(True)
self._color = self._cp.get_color()
self.set_valid(True)
@@ -185,23 +185,23 @@ class _GenderPage(_Page):
def __init__(self):
_Page.__init__(self)
- alignment = Gtk.Alignment.new(0.5, 0.5, 0, 0)
- self.pack_start(alignment, expand=True, fill=True, padding=0)
-
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
- alignment.add(grid)
+ grid.set_row_spacing(style.DEFAULT_SPACING)
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.CENTER)
+ grid.set_hexpand(True)
+ grid.set_vexpand(True)
+ self.append(grid)
+ grid.set_visible(True)
label = Gtk.Label(label=_('Select gender:'))
grid.attach(label, 0, 0, 1, 1)
- label.show()
+ label.set_visible(True)
self._gp = genderpicker.GenderPicker()
grid.attach(self._gp, 0, 1, 1, 1)
- self._gp.show()
-
- grid.show()
- alignment.show()
+ self._gp.set_visible(True)
self._gender = self._gp.get_gender()
self.set_valid(True)
@@ -218,24 +218,24 @@ class _AgePage(_Page):
def __init__(self, gender):
_Page.__init__(self)
- alignment = Gtk.Alignment.new(0.5, 0.5, 0, 0)
- self.pack_start(alignment, expand=True, fill=True, padding=0)
-
grid = Gtk.Grid()
grid.set_column_spacing(style.DEFAULT_SPACING)
- alignment.add(grid)
+ grid.set_row_spacing(style.DEFAULT_SPACING)
+ grid.set_halign(Gtk.Align.CENTER)
+ grid.set_valign(Gtk.Align.CENTER)
+ grid.set_hexpand(True)
+ grid.set_vexpand(True)
+ self.append(grid)
+ grid.set_visible(True)
self._ap = agepicker.AgePicker(gender, self)
label = Gtk.Label(label=_(self._ap.get_label()))
grid.attach(label, 0, 0, 1, 1)
- label.show()
+ label.set_visible(True)
grid.attach(self._ap, 0, 1, 1, 1)
- self._ap.show()
-
- grid.show()
- alignment.show()
+ self._ap.set_visible(True)
self._age = self._ap.get_age()
@@ -258,14 +258,27 @@ class _IntroBox(Gtk.Box):
PAGE_LAST = len(PAGES) - 1
def __init__(self, start_on_age_page):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
- self.set_border_width(style.zoom(30))
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
+
+ self.set_hexpand(True)
+ self.set_vexpand(True)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self.set_margin_start(style.zoom(30))
+ self.set_margin_end(style.zoom(30))
+ self.set_margin_top(style.zoom(30))
+ self.set_margin_bottom(style.zoom(30))
self._page = 0
self._name_page = _NamePage(self)
self._color_page = _ColorPage()
self._gender_page = _GenderPage()
self._age_page = _AgePage(None)
+
+ for page in (self._name_page, self._color_page,
+ self._gender_page, self._age_page):
+ page.connect('notify::valid', self._page_valid_changed_cb)
+
self._current_page = None
self._next_button = None
@@ -297,8 +310,8 @@ def __init__(self, start_on_age_page):
self._setup_page()
def _setup_page(self):
- for child in self.get_children():
- self.remove(child)
+ while self.get_first_child() is not None:
+ self.remove(self.get_first_child())
def _setup_name_page(self):
self._current_page = self._name_page
@@ -326,49 +339,64 @@ def _setup_age_page(self):
]
setup_methods[self._page](self)
- self.pack_start(self._current_page, True, True, 0)
- self._current_page.show()
+ self._current_page.set_hexpand(True)
+ self._current_page.set_vexpand(True)
+ self.append(self._current_page)
+ self._current_page.set_visible(True)
- button_box = Gtk.HButtonBox()
+ button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
if self._page == self.PAGE_FIRST:
- button_box.set_layout(Gtk.ButtonBoxStyle.END)
+ button_box.set_halign(Gtk.Align.END)
else:
- button_box.set_layout(Gtk.ButtonBoxStyle.EDGE)
- back_button = Gtk.Button(_('Back'))
- image = Icon(icon_name='go-left')
- back_button.set_image(image)
+ button_box.set_halign(Gtk.Align.FILL)
+ back_button = Gtk.Button()
+ back_button.add_css_class('intro-nav-button')
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
+ box.append(Icon(icon_name='go-left'))
+ box.append(Gtk.Label(label=_('Back')))
+ back_button.set_child(box)
back_button.connect('clicked', self._back_activated_cb)
- button_box.pack_start(back_button, True, True, 0)
- back_button.show()
+ back_button.set_hexpand(True)
+ back_button.set_halign(Gtk.Align.START)
+ back_button.set_margin_start(style.zoom(15))
+ back_button.set_margin_bottom(style.zoom(15))
+ button_box.append(back_button)
+ back_button.set_visible(True)
self._next_button = Gtk.Button()
- image = Icon(icon_name='go-right')
- self._next_button.set_image(image)
-
+ self._next_button.add_css_class('intro-nav-button')
+ box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING)
+ self._next_label = Gtk.Label(label=_('Next'))
+ box.append(self._next_label)
+ box.append(Icon(icon_name='go-right'))
+ self._next_button.set_child(box)
+
if self._page == self.PAGE_LAST:
- self._next_button.set_label(_('Done'))
+ self._next_label.set_text(_('Done'))
self._next_button.connect('clicked', self._done_activated_cb)
else:
- self._next_button.set_label(_('Next'))
+ self._next_label.set_text(_('Next'))
self._next_button.connect('clicked', self._next_activated_cb)
-
+
self._current_page.activate()
self._update_next_button()
- button_box.pack_start(self._next_button, True, True, 0)
- self._next_button.show()
+ self._next_button.set_hexpand(True)
+ self._next_button.set_halign(Gtk.Align.END)
+ self._next_button.set_margin_end(style.zoom(15))
+ self._next_button.set_margin_bottom(style.zoom(15))
+ button_box.append(self._next_button)
+ self._next_button.set_visible(True)
- self._current_page.connect('notify::valid',
- self._page_valid_changed_cb)
-
- self.pack_start(button_box, False, True, 0)
- button_box.show()
+ self.append(button_box)
+ button_box.set_visible(True)
if not self.PAGES[self._page]:
next(self)
def _update_next_button(self):
- self._next_button.set_sensitive(self._current_page.props.valid)
+ if self._next_button is not None:
+ self._next_button.set_sensitive(self._current_page.props.valid)
def _page_valid_changed_cb(self, page, pspec):
self._update_next_button()
@@ -416,7 +444,7 @@ def __init__(self):
self.age = 0
-class IntroWindow(Gtk.Window):
+class IntroWindow(Gtk.Box):
__gtype_name__ = 'SugarIntroWindow'
__gsignals__ = {
@@ -424,20 +452,24 @@ class IntroWindow(Gtk.Window):
}
def __init__(self, start_on_age_page=False):
- Gtk.Window.__init__(self)
+ super().__init__()
- self.props.decorated = False
- self.maximize()
+ self.set_hexpand(True)
+ self.set_vexpand(True)
+ self.add_css_class('intro-window')
self._intro_box = _IntroBox(start_on_age_page)
self._intro_box.connect('done', self._done_cb)
- self.add(self._intro_box)
- self._intro_box.show()
- self.connect('key-press-event', self.__key_press_cb)
+ self.append(self._intro_box)
+ self._intro_box.set_visible(True)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_cb)
+ self.add_controller(key_controller)
def _done_cb(self, box, user_profile):
- self.hide()
+ self.set_visible(False)
GLib.idle_add(self._create_profile_cb, user_profile)
def _create_profile_cb(self, user_profile):
@@ -446,22 +478,23 @@ def _create_profile_cb(self, user_profile):
return False
- def __key_press_cb(self, widget, event):
- if Gdk.keyval_name(event.keyval) == 'Return':
+ def __key_press_cb(self, controller, keyval, keycode, state):
+ if Gdk.keyval_name(keyval) == 'Return':
next(self._intro_box)
return True
- if Gdk.keyval_name(event.keyval) == 'Escape':
+ if Gdk.keyval_name(keyval) == 'Escape':
self._intro_box.back()
return True
return False
-
-if hasattr(IntroWindow, 'set_css_name'):
- IntroWindow.set_css_name('introwindow')
-
+IntroWindow.set_css_name('introwindow')
if __name__ == '__main__':
+ win = Gtk.Window()
w = IntroWindow()
- w.show()
- w.connect('destroy', Gtk.main_quit)
- Gtk.main()
+ win.set_child(w)
+ win.set_visible(True)
+
+ loop = GLib.MainLoop()
+ win.connect('close-request', lambda w: loop.quit())
+ loop.run()
diff --git a/src/jarabe/journal/bundlelauncher.py b/src/jarabe/journal/bundlelauncher.py
index dc94fcd9d3..ef1097ea87 100644
--- a/src/jarabe/journal/bundlelauncher.py
+++ b/src/jarabe/journal/bundlelauncher.py
@@ -45,7 +45,7 @@ def get_bundle(bundle_id=None, object_id=None):
bundle = bundleregistry.get_registry().get_bundle(bundle_id)
if bundle is None:
logging.warning('Activity with the bundle_id %s was not found',
- mime_type)
+ bundle_id)
return None
return bundle
diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py
index b83f5e78cc..70dba8bc98 100644
--- a/src/jarabe/journal/detailview.py
+++ b/src/jarabe/journal/detailview.py
@@ -18,6 +18,7 @@
from gi.repository import GObject
from gi.repository import Gtk
+from gi.repository import Gdk
from sugar4.graphics import style
from sugar4.graphics.icon import Icon
@@ -26,11 +27,20 @@
from jarabe.journal import model
+def _set_css_bg(widget, color):
+ if color == style.COLOR_PANEL_GREY:
+ widget.set_css_classes(['detailview-bg-panel'])
+ elif color == style.COLOR_SELECTION_GREY:
+ widget.set_css_classes(['detailview-bg-selection'])
+ else:
+ widget.set_css_classes([])
+
+
class DetailView(Gtk.Box):
__gtype_name__ = 'DetailView'
__gsignals__ = {
- 'go-back-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'go-back-clicked': (GObject.SignalFlags.RUN_FIRST, None, ()),
}
def __init__(self, journalactivity, **kwargs):
@@ -38,30 +48,30 @@ def __init__(self, journalactivity, **kwargs):
self._metadata = None
self._expanded_entry = None
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
back_bar = BackBar()
- back_bar.connect('button-release-event',
- self.__back_bar_release_event_cb)
- self.pack_start(back_bar, False, True, 0)
-
- self.show_all()
+ click = Gtk.GestureClick()
+ click.connect('released', self.__back_bar_release_event_cb)
+ back_bar.add_controller(click)
+ self.append(back_bar)
+ back_bar.set_visible(True)
def _fav_icon_activated_cb(self, fav_icon):
keep = not self._expanded_entry.get_keep()
self._expanded_entry.set_keep(keep)
fav_icon.props.keep = keep
- def __back_bar_release_event_cb(self, back_bar, event):
+ def __back_bar_release_event_cb(self, gesture, n_press, x, y):
self.emit('go-back-clicked')
- return False
def _update_view(self):
if self._expanded_entry is None:
self._expanded_entry = ExpandedEntry(self._journalactivity)
- self.pack_start(self._expanded_entry, True, True, 0)
+ self._expanded_entry.set_vexpand(True)
+ self.append(self._expanded_entry)
self._expanded_entry.set_metadata(self._metadata)
- self.show_all()
+ self._expanded_entry.set_visible(True)
def refresh(self):
logging.debug('DetailView.refresh')
@@ -79,40 +89,36 @@ def set_metadata(self, metadata):
type=object, getter=get_metadata, setter=set_metadata)
-class BackBar(Gtk.EventBox):
+class BackBar(Gtk.Box):
def __init__(self):
- Gtk.EventBox.__init__(self)
- self.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_PANEL_GREY.get_gdk_color())
- hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_PADDING)
- hbox.set_border_width(style.DEFAULT_PADDING)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_PADDING)
+ _set_css_bg(self, style.COLOR_PANEL_GREY)
+ self.set_margin_start(style.DEFAULT_PADDING)
+ self.set_margin_end(style.DEFAULT_PADDING)
+ self.set_margin_top(style.DEFAULT_PADDING)
+ self.set_margin_bottom(style.DEFAULT_PADDING)
+
icon = Icon(icon_name='go-previous', pixel_size=style.SMALL_ICON_SIZE,
fill_color=style.COLOR_TOOLBAR_GREY.get_svg())
- hbox.pack_start(icon, False, False, 0)
+ self.append(icon)
+ icon.set_visible(True)
label = Gtk.Label()
label.set_text(_('Back'))
- halign = Gtk.Alignment.new(0, 0.5, 0, 1)
- halign.add(label)
- hbox.pack_start(halign, True, True, 0)
- hbox.show()
- self.add(hbox)
-
- if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL:
- # Reverse hbox children.
- for child in hbox.get_children():
- hbox.reorder_child(child, 0)
-
- self.connect('enter-notify-event', self.__enter_notify_event_cb)
- self.connect('leave-notify-event', self.__leave_notify_event_cb)
-
- def __enter_notify_event_cb(self, box, event):
- box.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_SELECTION_GREY.get_gdk_color())
- return False
-
- def __leave_notify_event_cb(self, box, event):
- box.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_PANEL_GREY.get_gdk_color())
- return False
+ label.set_halign(Gtk.Align.START)
+ label.set_valign(Gtk.Align.CENTER)
+ label.set_hexpand(True)
+ self.append(label)
+ label.set_visible(True)
+
+ motion = Gtk.EventControllerMotion()
+ motion.connect('enter', self.__enter_notify_event_cb)
+ motion.connect('leave', self.__leave_notify_event_cb)
+ self.add_controller(motion)
+
+ def __enter_notify_event_cb(self, controller, x, y):
+ _set_css_bg(self, style.COLOR_SELECTION_GREY)
+
+ def __leave_notify_event_cb(self, controller):
+ _set_css_bg(self, style.COLOR_PANEL_GREY)
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index f14ee0ad17..dd667bf958 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -22,6 +22,7 @@
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gtk
+from gi.repository import Gdk
import json
from sugar4.graphics import style
@@ -40,37 +41,43 @@
from jarabe.journal import journalwindow
-class Separator(Gtk.Box):
+def _set_css_bg(widget, color):
+ if color == style.COLOR_PANEL_GREY:
+ widget.set_css_classes(['expandedentry-bg-panel'])
+ elif color == style.COLOR_WHITE:
+ widget.set_css_classes(['expandedentry-bg-white'])
+ else:
+ widget.set_css_classes([])
+
+class Separator(Gtk.Box):
def __init__(self, orientation):
- Gtk.Box.__init__(
- self, orientation=Gtk.Orientation.VERTICAL, background_color=style.COLOR_PANEL_GREY.get_gdk_color())
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
+ _set_css_bg(self, style.COLOR_PANEL_GREY)
-class BuddyList(Gtk.Alignment):
+class BuddyList(Gtk.Box):
def __init__(self, buddies):
- Gtk.Alignment.__init__(self)
- self.set(0, 0, 0, 0)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
- hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
for buddy in buddies:
nick_, color = buddy
icon = CanvasIcon(icon_name='computer-xo',
xo_color=XoColor(color),
pixel_size=style.STANDARD_ICON_SIZE)
icon.set_palette(BuddyPalette(buddy))
- hbox.pack_start(icon, True, True, 0)
- self.add(hbox)
+ icon.set_hexpand(True)
+ self.append(icon)
class TextView(Gtk.TextView):
def __init__(self):
- Gtk.TextView.__init__(self)
+ super().__init__()
text_buffer = Gtk.TextBuffer()
self.set_buffer(text_buffer)
- self.set_left_margin(style.DEFAULT_PADDING)
+ self.set_margin_start(style.DEFAULT_PADDING)
self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
@@ -92,7 +99,7 @@ class CommentsView(Gtk.TreeView):
COMMENT_ERASE_ICON_COLOR = 5
def __init__(self):
- Gtk.TreeView.__init__(self)
+ super().__init__()
self.set_headers_visible(False)
self._store = Gtk.ListStore(str, object, str, str, str, object)
self._comments = []
@@ -158,16 +165,16 @@ def _erase_comment_cb(self, widget, event):
icon = Icon(icon_name='dialog-cancel')
alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
- icon.show()
+ icon.set_visible(True)
ok_icon = Icon(icon_name='dialog-ok')
alert.add_button(Gtk.ResponseType.OK, erase_string, ok_icon)
- ok_icon.show()
+ ok_icon.set_visible(True)
alert.connect('response', self._erase_alert_response_cb, entry)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def _erase_alert_response_cb(self, alert, response_id, entry):
journalwindow.get_journal_window().remove_alert(alert)
@@ -202,10 +209,10 @@ def __init__(self):
self.props.mode = Gtk.CellRendererMode.ACTIVATABLE
-class BaseExpandedEntry(GObject.GObject):
+class BaseExpandedEntry(object):
- def __init__(self):
- # Create a header
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
self._keep_icon = None
self._keep_sid = None
self._icon = None
@@ -215,19 +222,21 @@ def __init__(self):
def create_header(self):
header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ header.set_spacing(style.DEFAULT_SPACING)
self._keep_icon = self._create_keep_icon()
- header.pack_start(self._keep_icon, False, False, style.DEFAULT_SPACING)
+ header.append(self._keep_icon)
self._icon_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- header.pack_start(self._icon_box, False, False, style.DEFAULT_SPACING)
+ header.append(self._icon_box)
self._title = self._create_title()
- header.pack_start(self._title, True, True, 0)
+ self._title.set_hexpand(True)
+ header.append(self._title)
# TODO: create a version list popup instead of a date label
self._date = self._create_date()
- header.pack_start(self._date, False, False, style.DEFAULT_SPACING)
+ header.append(self._date)
return header
@@ -244,77 +253,78 @@ def _create_date(self):
return date
-class ExpandedEntry(Gtk.EventBox, BaseExpandedEntry):
+class ExpandedEntry(Gtk.Box, BaseExpandedEntry):
- def __init__(self, journalactivity):
+ def __init__(self, journalactivity, **kwargs):
+ super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs)
BaseExpandedEntry.__init__(self)
self._journalactivity = journalactivity
- Gtk.EventBox.__init__(self)
- self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self.add(self._vbox)
+ self._vbox = self
self.in_focus = False
self._metadata = None
self._update_title_sid = None
- self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color())
+ _set_css_bg(self, style.COLOR_WHITE)
self._header = self.create_header()
- self._vbox.pack_start(self._header, False, False,
- style.DEFAULT_SPACING * 2)
+ self._header.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self._vbox.append(self._header)
+
self._keep_sid = self._keep_icon.connect(
'toggled', self._keep_icon_toggled_cb)
self._title.connect('activate', self._title_entered)
- self._title.connect(
- 'focus-out-event', self._focus_out_cb)
- self._title.connect(
- 'focus-in-event', self._focus_in_cb)
-
- if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL:
- # Reverse header children.
- for child in self._header.get_children():
- self._header.reorder_child(child, 0)
-
- # Create a two-column body
- body_box = Gtk.EventBox()
- body_box.set_border_width(style.DEFAULT_SPACING)
- body_box.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self._vbox.pack_start(body_box, True, True, 0)
- body = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- body_box.add(body)
+
+ focus_controller = Gtk.EventControllerFocus()
+ focus_controller.connect('leave', self._focus_out_cb)
+ focus_controller.connect('enter', self._focus_in_cb)
+ self._title.add_controller(focus_controller)
+
+ body_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ body_box.set_margin_start(style.DEFAULT_SPACING)
+ body_box.set_margin_end(style.DEFAULT_SPACING)
+ body_box.set_margin_top(style.DEFAULT_SPACING)
+ body_box.set_margin_bottom(style.DEFAULT_SPACING)
+ _set_css_bg(body_box, style.COLOR_WHITE)
+ body_box.set_vexpand(True)
+ self._vbox.append(body_box)
first_column = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- body.pack_start(first_column, False, False, style.DEFAULT_SPACING)
+ first_column.set_margin_end(style.DEFAULT_SPACING)
+ body_box.append(first_column)
second_column = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- body.pack_start(second_column, True, True, 0)
+ second_column.set_hexpand(True)
+ second_column.set_vexpand(True)
+ body_box.append(second_column)
- # First body column
self._preview_box = Gtk.Frame()
- style_context = self._preview_box.get_style_context()
- style_context.add_class('journal-preview-box')
- first_column.pack_start(self._preview_box, False, True, 0)
+ self._preview_box.add_css_class('journal-preview-box')
+ self._preview_box.set_vexpand(True)
+ first_column.append(self._preview_box)
self._technical_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- first_column.pack_start(self._technical_box, False, False, 0)
+ first_column.append(self._technical_box)
# Second body column
description_box, self._description = self._create_description()
- second_column.pack_start(description_box, True, True,
- style.DEFAULT_SPACING)
+ description_box.set_vexpand(True)
+ description_box.set_margin_bottom(style.DEFAULT_SPACING)
+ second_column.append(description_box)
tags_box, self._tags = self._create_tags()
- second_column.pack_start(tags_box, True, True,
- style.DEFAULT_SPACING)
+ tags_box.set_vexpand(True)
+ tags_box.set_margin_bottom(style.DEFAULT_SPACING)
+ second_column.append(tags_box)
comments_box, self._comments = self._create_comments()
- second_column.pack_start(comments_box, True, True,
- style.DEFAULT_SPACING)
+ comments_box.set_vexpand(True)
+ comments_box.set_margin_bottom(style.DEFAULT_SPACING)
+ second_column.append(comments_box)
self._buddy_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- second_column.pack_start(self._buddy_list, True, False, 0)
- self.show_all()
+ second_column.append(self._buddy_list)
+ self.set_visible(True)
def set_metadata(self, metadata):
if self._metadata == metadata:
@@ -326,30 +336,29 @@ def set_metadata(self, metadata):
self._keep_icon.handler_unblock(self._keep_sid)
self._icon = self._create_icon()
- for child in self._icon_box.get_children():
- self._icon_box.remove(child)
- # FIXME: self._icon_box.foreach(self._icon_box.remove)
- self._icon_box.pack_start(self._icon, False, False, 0)
+ while self._icon_box.get_first_child() is not None:
+ self._icon_box.remove(self._icon_box.get_first_child())
+ self._icon_box.append(self._icon)
self._date.set_text(misc.get_date(metadata))
self._title.set_text(metadata.get('title', _('Untitled')))
if self._preview_box.get_child():
- self._preview_box.remove(self._preview_box.get_child())
- self._preview_box.add(self._create_preview())
+ self._preview_box.set_child(None)
+ self._preview_box.set_child(self._create_preview())
- for child in self._technical_box.get_children():
- self._technical_box.remove(child)
- # FIXME: self._technical_box.foreach(self._technical_box.remove)
- self._technical_box.pack_start(self._create_technical(),
- False, False, style.DEFAULT_SPACING)
+ while self._technical_box.get_first_child() is not None:
+ self._technical_box.remove(self._technical_box.get_first_child())
+ tb = self._create_technical()
+ tb.set_margin_bottom(style.DEFAULT_SPACING)
+ self._technical_box.append(tb)
- for child in self._buddy_list.get_children():
- self._buddy_list.remove(child)
- # FIXME: self._buddy_list.foreach(self._buddy_list.remove)
- self._buddy_list.pack_start(self._create_buddy_list(), False, False,
- style.DEFAULT_SPACING)
+ while self._buddy_list.get_first_child() is not None:
+ self._buddy_list.remove(self._buddy_list.get_first_child())
+ bl = self._create_buddy_list()
+ bl.set_margin_bottom(style.DEFAULT_SPACING)
+ self._buddy_list.append(bl)
description = metadata.get('description', '')
self._description.get_buffer().set_text(description)
@@ -375,33 +384,34 @@ def _create_icon(self):
def _create_preview(self):
- box = Gtk.EventBox()
- box.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color())
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ _set_css_bg(box, style.COLOR_WHITE)
metadata = self._metadata
pixbuf = get_preview_pixbuf(metadata.get('preview', ''))
has_preview = pixbuf is not None
if has_preview:
- im = Gtk.Image()
- im.set_from_pixbuf(pixbuf)
- box.add(im)
- im.show()
+ im = Gtk.Image.new_from_paintable(Gdk.Texture.new_for_pixbuf(pixbuf))
+ box.append(im)
+ im.set_visible(True)
else:
label = Gtk.Label()
label.set_text(_('No preview'))
width, height = PREVIEW_SIZE[0], PREVIEW_SIZE[1]
label.set_size_request(width, height)
- box.add(label)
- label.show()
+ box.append(label)
+ label.set_visible(True)
- box.connect_after('button-release-event',
- self._preview_box_button_release_event_cb)
+ click = Gtk.GestureClick()
+ click.set_propagation_phase(Gtk.PropagationPhase.BUBBLE)
+ click.connect('released', self._preview_box_button_release_event_cb)
+ box.add_controller(click)
return box
def _create_technical(self):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.props.spacing = style.DEFAULT_SPACING
+ vbox.set_spacing(style.DEFAULT_SPACING)
if 'filesize' in self._metadata:
filesize = self._metadata['filesize']
@@ -416,12 +426,12 @@ def _create_technical(self):
for line in lines:
linebox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- vbox.pack_start(linebox, False, False, 0)
+ vbox.append(linebox)
text = Gtk.Label()
text.set_markup('%s' % (
style.COLOR_BUTTON_GREY.get_html(), line))
- linebox.pack_start(text, False, False, 0)
+ linebox.append(text)
return vbox
@@ -440,74 +450,78 @@ def _format_date(self):
def _create_buddy_list(self):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.props.spacing = style.DEFAULT_SPACING
+ vbox.set_spacing(style.DEFAULT_SPACING)
text = Gtk.Label()
text.set_markup('%s' % (
style.COLOR_BUTTON_GREY.get_html(), _('Participants:')))
- halign = Gtk.Alignment.new(0, 0, 0, 0)
- halign.add(text)
- vbox.pack_start(halign, False, False, 0)
+ text.set_halign(Gtk.Align.START)
+ vbox.append(text)
if self._metadata.get('buddies'):
buddies = list(json.loads(self._metadata['buddies']).values())
- vbox.pack_start(BuddyList(buddies), False, False, 0)
+ vbox.append(BuddyList(buddies))
return vbox
return vbox
def _create_scrollable(self, widget, label=None):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.props.spacing = style.DEFAULT_SPACING
+ vbox.set_spacing(style.DEFAULT_SPACING)
if label is not None:
text = Gtk.Label()
text.set_markup('%s' % (
style.COLOR_BUTTON_GREY.get_html(), label))
- halign = Gtk.Alignment.new(0, 0, 0, 0)
- halign.add(text)
- vbox.pack_start(halign, False, False, 0)
+ text.set_halign(Gtk.Align.START)
+ vbox.append(text)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
- scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
- scrolled_window.add(widget)
- vbox.pack_start(scrolled_window, True, True, 0)
+ scrolled_window.set_has_frame(True)
+ scrolled_window.set_child(widget)
+ scrolled_window.set_vexpand(True)
+ scrolled_window.set_hexpand(True)
+ vbox.append(scrolled_window)
return vbox
def _create_description(self):
widget = TextView()
- widget.connect('focus-in-event', self._focus_in_cb)
- widget.connect('focus-out-event',
- self._description_tags_focus_out_event_cb)
+ focus = Gtk.EventControllerFocus()
+ focus.connect('enter', self._focus_in_cb)
+ focus.connect('leave', self._description_tags_focus_out_event_cb)
+ widget.add_controller(focus)
return self._create_scrollable(widget, label=_('Description:')), widget
def _create_tags(self):
widget = TextView()
- widget.connect('focus-in-event', self._focus_in_cb)
- widget.connect('focus-out-event',
- self._description_tags_focus_out_event_cb)
+ focus = Gtk.EventControllerFocus()
+ focus.connect('enter', self._focus_in_cb)
+ focus.connect('leave', self._description_tags_focus_out_event_cb)
+ widget.add_controller(focus)
return self._create_scrollable(widget, label=_('Tags:')), widget
def _create_comments(self):
widget = CommentsView()
widget.connect('comments-changed', self._comments_changed_cb)
- widget.connect('focus-in-event', self._focus_in_cb)
- widget.connect('focus-out-event', self._focus_out_cb)
+ focus = Gtk.EventControllerFocus()
+ focus.connect('enter', self._focus_in_cb)
+ focus.connect('leave', self._focus_out_cb)
+ widget.add_controller(focus)
return self._create_scrollable(widget, label=_('Comments:')), widget
- def _focus_in_cb(self, widget, event):
+ def _focus_in_cb(self, controller):
self.in_focus = True
- def _focus_out_cb(self, widget, event):
+ def _focus_out_cb(self, controller):
self.in_focus = False
def _title_entered(self, widget):
self._update_entry()
- self._title.hide()
- self._title.show()
+ self._title.set_visible(False)
+ self._title.set_visible(True)
def _title_notify_text_cb(self, entry, pspec):
if not self._update_title_sid:
@@ -530,7 +544,7 @@ def _title_changed_event_cb(self, widget):
self._metadata.get('title_set_by_user', 0)
)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
self._update_entry()
@@ -544,7 +558,7 @@ def _title_alert_response_cb(self, alert, response_id, old_title, old_title_set_
self._metadata['title_set_by_user'] = old_title_set_by_user
self._update_entry(needs_update=True)
- def _description_tags_focus_out_event_cb(self, text_view, event):
+ def _description_tags_focus_out_event_cb(self, controller):
self._update_entry()
def _comments_changed_cb(self, event, comments):
@@ -564,7 +578,6 @@ def _update_entry(self, needs_update=False):
self._metadata['title_set_by_user'] = '1'
needs_update = True
-
bounds = self._tags.get_buffer().get_bounds()
old_tags = self._metadata.get('tags', None)
new_tags = self._tags.get_buffer().get_text(bounds[0], bounds[1],
@@ -610,7 +623,7 @@ def __icon_activate_cb(self, button):
alert_window=journalwindow.get_journal_window())
return True
- def _preview_box_button_release_event_cb(self, button, event):
+ def _preview_box_button_release_event_cb(self, gesture, n_press, x, y):
logging.debug('_preview_box_button_release_event_cb')
misc.resume(self._metadata,
alert_window=journalwindow.get_journal_window())
diff --git a/src/jarabe/journal/iconmodel.py b/src/jarabe/journal/iconmodel.py
index 713b89c09d..25bb8b53b0 100644
--- a/src/jarabe/journal/iconmodel.py
+++ b/src/jarabe/journal/iconmodel.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Gonzalo Odiard
+# Copyright (C) 2013, Gonzalo Odiard
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
-class IconModel(GObject.GObject, Gtk.TreeModel, Gtk.TreeDragSource):
+class IconModel(GObject.GObject, Gtk.TreeModel):
__gtype_name__ = 'JournalIconModel'
__gsignals__ = {
@@ -38,15 +38,15 @@ class IconModel(GObject.GObject, Gtk.TreeModel, Gtk.TreeDragSource):
COLUMN_PREVIEW = 2
_COLUMN_TYPES = {
- COLUMN_UID: str,
- COLUMN_TITLE: str,
- COLUMN_PREVIEW: str,
+ COLUMN_UID: GObject.TYPE_STRING,
+ COLUMN_TITLE: GObject.TYPE_STRING,
+ COLUMN_PREVIEW: GObject.TYPE_STRING,
}
_PAGE_SIZE = 100
def __init__(self, query):
- GObject.GObject.__init__(self)
+ super().__init__()
self._last_requested_index = None
self._cached_row = None
@@ -88,7 +88,7 @@ def do_iter_n_children(self, iterator):
def do_get_value(self, iterator, column):
if self.view_is_resizing:
- return None
+ return ""
index = iterator.user_data
if index == self._last_requested_index:
diff --git a/src/jarabe/journal/iconview.py b/src/jarabe/journal/iconview.py
index b5b83d7bf8..2c330988b9 100644
--- a/src/jarabe/journal/iconview.py
+++ b/src/jarabe/journal/iconview.py
@@ -20,6 +20,7 @@
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GLib
+from gi.repository import Gdk
from jarabe.journal.iconmodel import IconModel
from sugar4.graphics.icon import Icon
@@ -29,69 +30,102 @@
from sugar4.activity.activity import PREVIEW_SIZE
-class PreviewRenderer(Gtk.CellRendererPixbuf):
+def _set_css_bg(widget, color):
+ widget.set_css_classes(
+ ['iconview-bg-white'] if color == style.COLOR_WHITE else [])
- def __init__(self, **kwds):
- Gtk.CellRendererPixbuf.__init__(self, **kwds)
- self._preview_data = None
- def set_preview_data(self, data):
- self._preview_data = data
+class PreviewFlowBox(Gtk.FlowBox):
+ __gtype_name__ = 'PreviewFlowBox'
+
+ __gsignals__ = {
+ 'item-activated': (GObject.SignalFlags.RUN_FIRST, None, (object,)),
+ }
- def do_render(self, cr, widget, background_area, cell_area, flags):
- self.props.pixbuf = get_preview_pixbuf(self._preview_data)
- Gtk.CellRendererPixbuf.do_render(self, cr, widget, background_area,
- cell_area, flags)
+ def __init__(self, title_col, preview_col):
+ super().__init__()
- def do_get_size(self, widget, cell_area):
- x_offset, y_offset, width, height = Gtk.CellRendererPixbuf.do_get_size(
- self, widget, cell_area)
- width = PREVIEW_SIZE[0]
- height = PREVIEW_SIZE[1]
- return (x_offset, y_offset, width, height)
+ self._preview_col = preview_col
+ self._title_col = title_col
+ self.set_selection_mode(Gtk.SelectionMode.SINGLE)
+ self.set_valign(Gtk.Align.START)
+ self.set_max_children_per_line(10)
+ self.set_column_spacing(6)
+ self.set_row_spacing(6)
+ self.set_homogeneous(True)
+
+ self.connect('child-activated', self._on_child_activated)
-class PreviewIconView(Gtk.IconView):
+ def get_model(self):
+ return getattr(self, '_model', None)
- def __init__(self, title_col, preview_col):
- Gtk.IconView.__init__(self)
+ def set_model(self, model):
+ self._model = model
+ while self.get_first_child():
+ self.remove(self.get_first_child())
- self._preview_col = preview_col
- self._title_col = title_col
+ if model is None:
+ return
- self.set_spacing(3)
+ iter_ = model.get_iter_first()
+ while iter_ is not None:
+ self._add_item(model, iter_)
+ iter_ = model.iter_next(iter_)
- _preview_renderer = PreviewRenderer()
- _preview_renderer.set_alignment(0.5, 0.5)
- self.pack_start(_preview_renderer, False)
- self.set_cell_data_func(_preview_renderer,
- self._preview_data_func, None)
+ def _add_item(self, model, iter_):
+ uid = model.get_value(iter_, IconModel.COLUMN_UID)
+ title = model.get_value(iter_, self._title_col)
+ preview_data = model.get_value(iter_, self._preview_col)
- _title_renderer = Gtk.CellRendererText()
- _title_renderer.set_alignment(0.5, 0.5)
- self.pack_start(_title_renderer, True)
- self.set_cell_data_func(_title_renderer,
- self._title_data_func, None)
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ box.set_spacing(3)
- def _preview_data_func(self, view, cell, store, i, data):
- preview_data = store.get_value(i, self._preview_col)
- cell.set_preview_data(preview_data)
+ pixbuf = get_preview_pixbuf(preview_data)
+ if pixbuf:
+ picture = Gtk.Picture.new_for_paintable(Gdk.Texture.new_for_pixbuf(pixbuf))
+ else:
+ picture = Gtk.Picture()
+
+ picture.set_size_request(PREVIEW_SIZE[0], PREVIEW_SIZE[1])
+ picture.set_valign(Gtk.Align.CENTER)
+ picture.set_halign(Gtk.Align.CENTER)
+ box.append(picture)
+
+ label = Gtk.Label()
+ label.set_markup(title)
+ label.set_halign(Gtk.Align.CENTER)
+ box.append(label)
- def _title_data_func(self, view, cell, store, i, data):
- title = store.get_value(i, self._title_col)
- cell.props.markup = title
+ path = model.get_path(iter_)
+ child = Gtk.FlowBoxChild()
+ child.set_child(box)
+ child._model_path = path
-class IconView(Gtk.Bin):
+ self.append(child)
+
+ def _on_child_activated(self, flowbox, child):
+ self.emit('item-activated', child._model_path)
+
+ def get_path_at_pos(self, x, y):
+ child = self.get_child_at_pos(int(x), int(y))
+ if child:
+ return child._model_path
+ return None
+
+
+class IconView(Gtk.Box):
__gtype_name__ = 'JournalBaseIconView'
__gsignals__ = {
- 'clear-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'clear-clicked': (GObject.SignalFlags.RUN_FIRST, None, ()),
'entry-activated': (GObject.SignalFlags.RUN_FIRST,
- None, ([str])),
+ None, (str,)),
}
def __init__(self, toolbar):
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._query = {}
self._model = None
self._progress_bar = None
@@ -99,27 +133,22 @@ def __init__(self, toolbar):
self._scroll_position = 0.
self._toolbar = toolbar
- Gtk.Bin.__init__(self)
-
self.connect('map', self.__map_cb)
self.connect('unrealize', self.__unrealize_cb)
- self.connect('destroy', self.__destroy_cb)
self._scrolled_window = Gtk.ScrolledWindow()
self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
- self.add(self._scrolled_window)
- self._scrolled_window.show()
+ self._scrolled_window.set_vexpand(True)
+ self.append(self._scrolled_window)
+ self._scrolled_window.set_visible(True)
- self.icon_view = PreviewIconView(IconModel.COLUMN_TITLE,
+ self.icon_view = PreviewFlowBox(IconModel.COLUMN_TITLE,
IconModel.COLUMN_PREVIEW)
self.icon_view.connect('item-activated', self.__item_activated_cb)
- self.icon_view.connect('button-release-event',
- self.__button_release_event_cb)
-
- self._scrolled_window.add(self.icon_view)
- self.icon_view.show()
+ self._scrolled_window.set_child(self.icon_view)
+ self.icon_view.set_visible(True)
# Auto-update stuff
self._fully_obscured = True
@@ -130,22 +159,12 @@ def __init__(self, toolbar):
model.updated.connect(self.__model_updated_cb)
model.deleted.connect(self.__model_deleted_cb)
- def __button_release_event_cb(self, icon_view, event):
- path = icon_view.get_path_at_pos(int(event.x), int(event.y))
- if path is None:
- return False
- uid = icon_view.get_model()[path][IconModel.COLUMN_UID]
- self.emit('entry-activated', uid)
- return False
+
def __item_activated_cb(self, icon_view, path):
- uid = icon_view.get_model()[path][IconModel.COLUMN_UID]
+ uid = self._model[path][IconModel.COLUMN_UID]
self.emit('entry-activated', uid)
- def _thumb_data_func(self, view, cell, store, i, data):
- preview_data = store.get_value(i, IconModel.COLUMN_PREVIEW)
- cell.props.pixbuf = get_preview_pixbuf(preview_data)
-
def __model_created_cb(self, sender, signal, object_id):
if self._is_new_item_visible(object_id):
self._set_dirty()
@@ -164,11 +183,7 @@ def _is_new_item_visible(self, object_id):
return not object_id.startswith('/')
return object_id.startswith(self._query['mountpoints'][0])
- def do_size_allocate(self, allocation):
- self.set_allocation(allocation)
- self.get_child().size_allocate(allocation)
-
- def __destroy_cb(self, widget):
+ def do_unroot(self):
if self._model is not None:
self._model.stop()
@@ -193,15 +208,15 @@ def refresh(self):
def __model_ready_cb(self, tree_model):
self._stop_progress_bar()
- self._scroll_position = self.icon_view.props.vadjustment.props.value
+ self._scroll_position = self._scrolled_window.get_vadjustment().props.value
logging.debug('IconView.__model_ready_cb %r', self._scroll_position)
# Cannot set it up earlier because will try to access the model
# and it needs to be ready.
self.icon_view.set_model(self._model)
- self.icon_view.props.vadjustment.props.value = self._scroll_position
- self.icon_view.props.vadjustment.value_changed()
+ self._scrolled_window.get_vadjustment().props.value = self._scroll_position
+ # vadjustment.value_changed is gone or automatic, no need to call it
if len(tree_model) == 0:
documents_path = model.get_documents_path()
@@ -222,11 +237,10 @@ def __model_ready_cb(self, tree_model):
def __map_cb(self, widget):
logging.debug('IconView.__map_cb %r', self._scroll_position)
- self.icon_view.props.vadjustment.props.value = self._scroll_position
- self.icon_view.props.vadjustment.value_changed()
+ self._scrolled_window.get_vadjustment().props.value = self._scroll_position
def __unrealize_cb(self, widget):
- self._scroll_position = self.icon_view.props.vadjustment.props.value
+ self._scroll_position = self._scrolled_window.get_vadjustment().props.value
logging.debug('IconView.__map_cb %r', self._scroll_position)
def _is_query_empty(self):
@@ -245,74 +259,103 @@ def __model_progress_cb(self, tree_model):
self._last_progress_bar_pulse = time.time()
def _start_progress_bar(self):
- alignment = Gtk.Alignment.new(xalign=0.5, yalign=0.5,
- xscale=0.5, yscale=0)
- self.remove(self.get_child())
- self.add(alignment)
- alignment.show()
+ alignment = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.CENTER)
+
+ while self.get_first_child():
+ self.remove(self.get_first_child())
+
+ self.append(alignment)
+ alignment.set_visible(True)
self._progress_bar = Gtk.ProgressBar()
self._progress_bar.props.pulse_step = 0.01
self._last_progress_bar_pulse = time.time()
- alignment.add(self._progress_bar)
- self._progress_bar.show()
+ alignment.append(self._progress_bar)
+ self._progress_bar.set_visible(True)
def _stop_progress_bar(self):
if self._progress_bar is None:
return
- self.remove(self.get_child())
- self.add(self._scrolled_window)
+ while self.get_first_child():
+ self.remove(self.get_first_child())
+ self.append(self._scrolled_window)
self._progress_bar = None
def _show_message(self, message, show_clear_query=False):
- self.remove(self.get_child())
-
- background_box = Gtk.EventBox()
- background_box.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self.add(background_box)
-
- alignment = Gtk.Alignment.new(0.5, 0.5, 0.1, 0.1)
- background_box.add(alignment)
+ while self.get_first_child():
+ self.remove(self.get_first_child())
+
+ background_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ background_box.set_hexpand(True)
+ background_box.set_vexpand(True)
+ _set_css_bg(background_box, style.COLOR_WHITE)
+ self.append(background_box)
+
+ alignment = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.CENTER)
+ alignment.set_hexpand(True)
+ alignment.set_vexpand(True)
+ background_box.append(alignment)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- alignment.add(box)
+ alignment.append(box)
icon = Icon(pixel_size=style.LARGE_ICON_SIZE,
icon_name='activity-journal',
stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
fill_color=style.COLOR_TRANSPARENT.get_svg())
- box.pack_start(icon, expand=True, fill=False, padding=0)
+ icon.set_hexpand(True)
+ icon.set_vexpand(True)
+ box.append(icon)
label = Gtk.Label()
color = style.COLOR_BUTTON_GREY.get_html()
label.set_markup('%s' % (
color, GLib.markup_escape_text(message)))
- box.pack_start(label, expand=True, fill=False, padding=0)
+ label.set_hexpand(True)
+ label.set_vexpand(True)
+ box.append(label)
if show_clear_query:
- button_box = Gtk.HButtonBox()
- button_box.set_layout(Gtk.ButtonBoxStyle.CENTER)
- box.pack_start(button_box, False, True, 0)
- button_box.show()
+ button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ button_box.set_halign(Gtk.Align.CENTER)
+ button_box.set_margin_top(style.DEFAULT_SPACING)
+ box.append(button_box)
+ button_box.set_visible(True)
button = Gtk.Button(label=_('Clear search'))
button.connect('clicked', self.__clear_button_clicked_cb)
- button.props.image = Icon(icon_name='dialog-cancel',
- pixel_size=style.SMALL_ICON_SIZE)
- button_box.pack_start(button, expand=True, fill=False, padding=0)
-
- background_box.show_all()
+
+ icon_img = Icon(icon_name='dialog-cancel',
+ pixel_size=style.SMALL_ICON_SIZE)
+
+ box_img = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
+ box_img.append(icon_img)
+ lbl = Gtk.Label(label=_('Clear search'))
+ box_img.append(lbl)
+ button.set_child(box_img)
+
+ button_box.append(button)
+
+ background_box.set_visible(True)
+ alignment.set_visible(True)
+ box.set_visible(True)
+ icon.set_visible(True)
+ label.set_visible(True)
def __clear_button_clicked_cb(self, button):
self.emit('clear-clicked')
def _clear_message(self):
- if self.get_child() == self._scrolled_window:
+ if self.get_first_child() == self._scrolled_window:
return
- self.remove(self.get_child())
- self.add(self._scrolled_window)
- self._scrolled_window.show()
+ while self.get_first_child():
+ self.remove(self.get_first_child())
+ self.append(self._scrolled_window)
+ self._scrolled_window.set_visible(True)
def _set_dirty(self):
if self._fully_obscured:
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 5801e38b77..fb2f23cf60 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -22,11 +22,11 @@
import os
from dbus import service
-
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import GObject
+from gi.repository import GLib
from sugar4.graphics.alert import ErrorAlert
from sugar4 import env
@@ -135,7 +135,7 @@ def _chooser_response_cb(self, chooser, response_id, chooser_id):
self.ObjectChooserResponse(chooser_id, object_id)
else:
self.ObjectChooserCancelled(chooser_id)
- chooser.destroy()
+ chooser.close()
del chooser
@service.method(J_DBUS_INTERFACE, in_signature='is',
@@ -145,15 +145,10 @@ def ChooseObject(self, parent_id, what_filter=''):
This method is kept for backwards compatibility
"""
chooser_id = uuid.uuid4().hex
- if parent_id:
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- parent = Gdk.Window.new(None, attributes, None)
- else:
- parent = None
+ parent = None
chooser = ObjectChooser(parent, what_filter)
chooser.connect('response', self._chooser_response_cb, chooser_id)
- chooser.show()
+ chooser.present()
return chooser_id
@@ -162,15 +157,10 @@ def ChooseObject(self, parent_id, what_filter=''):
def ChooseObjectWithFilter(self, parent_id, what_filter='',
filter_type=None, show_preview=False):
chooser_id = uuid.uuid4().hex
- if parent_id:
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- parent = Gdk.Window.new(None, attributes, None)
- else:
- parent = None
+ parent = None
chooser = ObjectChooser(parent, what_filter, filter_type, show_preview)
chooser.connect('response', self._chooser_response_cb, chooser_id)
- chooser.show()
+ chooser.present()
return chooser_id
@@ -194,8 +184,12 @@ class JournalActivity(JournalWindow):
def __init__(self):
logging.debug('STARTUP: Loading the journal')
JournalWindow.__init__(self)
+ self.add_css_class('journal-window')
+ self.add_css_class('background')
- self.set_title(_('Journal'))
+ activity_id = activityfactory.create_activity_id()
+ self.activity_id = str(activity_id)
+ self.bundle_id = _BUNDLE_ID
self._main_view = None
self._project_view = None
@@ -203,60 +197,64 @@ def __init__(self):
self._list_view = None
self._detail_view = None
self._main_toolbox = None
+ self._edit_toolbox = None
self._detail_toolbox = None
self._volumes_toolbar = None
+ self._key_controller = None
+ self._toolbox = None
self._mount_point = '/'
self._active_view = JournalViews.MAIN
self.project_metadata = None
self._editing_mode = False
+ self._init_done = False
+ self._critical_space_alert = None
+
+ self._dbus_service = JournalActivityDBusService(self)
+
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
+ session.get_session_manager().shutdown_signal.connect(
+ self._session_manager_shutdown_cb)
+
+ def _setup(self):
self._setup_main_view()
self._setup_secondary_view()
self._setup_project_view()
- self.add_events(Gdk.EventMask.ALL_EVENTS_MASK)
self._realized_sid = self.connect('realize', self.__realize_cb)
- self.connect('window-state-event', self.__window_state_event_cb)
- self.connect('key-press-event', self.__key_press_event_cb)
- self.connect('focus-in-event', self._focus_in_event_cb)
- self.connect('focus-out-event', self._focus_out_event_cb)
- model.created.connect(self.__model_created_cb)
- model.updated.connect(self.__model_updated_cb)
- model.deleted.connect(self.__model_deleted_cb)
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
+ self._key_controller = key_controller
- self._dbus_service = JournalActivityDBusService(self)
+ focus_controller = Gtk.EventControllerFocus()
+ focus_controller.connect('enter', self._focus_in_event_cb)
+ focus_controller.connect('leave', self._focus_out_event_cb)
+ self.add_controller(focus_controller)
- self.iconify()
-
- self._critical_space_alert = None
+ self._init_done = True
+ self.show_main_view()
+ self.minimize()
self._check_available_space()
- session.get_session_manager().shutdown_signal.connect(
- self._session_manager_shutdown_cb)
-
def volume_error_cb(self, gobject, message, severity):
alert = ErrorAlert(title=severity, msg=message)
alert.connect('response', self.__alert_response_cb)
self.add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def __alert_response_cb(self, alert, response_id):
self.remove_alert(alert)
def __realize_cb(self, window):
- activity_id = activityfactory.create_activity_id()
- data = GObject.GObject()
- settattr(data, 'activity_id', str(activity_id))
- settattr(data, 'bundle_id', _BUNDLE_ID)
- gdk_window = window.get_window()
- gdk_window.set_user_data(data)
-
self.disconnect(self._realized_sid)
self._realized_sid = None
def _session_manager_shutdown_cb(self, event):
- self.destroy()
+ self.close()
def can_close(self):
return False
@@ -269,6 +267,8 @@ def list_view_signal_connect(self, list_view):
self.__title_edit_started_cb)
list_view.connect('title-edit-finished',
self.__title_edit_finished_cb)
+ list_view.connect('title-edit-canceled',
+ self.__title_edit_canceled_cb)
list_view.connect('selection-changed',
self.__selection_changed_cb)
list_view.connect('project-view-activate',
@@ -288,24 +288,25 @@ def _setup_main_view(self):
self._add_new_box = AddNewBar(_('Add new project'))
self._add_new_box.activate.connect(self.__add_project_activate_cb)
- self._main_view.pack_start(self._add_new_box, False, True,
- style.DEFAULT_SPACING)
- self._main_view.set_can_focus(True)
+ self._add_new_box.set_margin_top(style.DEFAULT_SPACING)
+ self._add_new_box.set_margin_bottom(style.DEFAULT_SPACING)
+ self._main_view.append(self._add_new_box)
+ self._main_view.set_focusable(True)
self._list_view = ListView(self, enable_multi_operations=True)
self.list_view_signal_connect(self._list_view)
tree_view = self._list_view.tree_view
tree_view.connect('choose-project', self.__choose_project_cb)
- self._main_view.pack_start(self._list_view, True, True, 0)
- self._list_view.show_all()
+ self._list_view.set_vexpand(True)
+ self._main_view.append(self._list_view)
+ self._list_view.set_visible(True)
volumes_toolbar = self._create_volumes_toolbar()
- self._main_view.pack_start(volumes_toolbar, False, True, 0)
+ self._main_view.append(volumes_toolbar)
self._main_toolbox.connect('query-changed', self._query_changed_cb)
- self._main_toolbox.search_entry.connect('icon-press',
- self.__search_icon_pressed_cb)
+
self._main_toolbox.set_mount_point(self._mount_point)
def _setup_project_view(self):
@@ -314,15 +315,17 @@ def _setup_project_view(self):
add_new_box = AddNewBar()
add_new_box.activate.connect(self.__add_new_activate_cb)
- add_new_box.show_all()
- project_vbox.pack_start(add_new_box, False, True,
- style.DEFAULT_SPACING / 3)
+ add_new_box.set_visible(True)
+ add_new_box.set_margin_top(style.DEFAULT_SPACING // 3)
+ add_new_box.set_margin_bottom(style.DEFAULT_SPACING // 3)
+ project_vbox.append(add_new_box)
self._entry_project = add_new_box.get_entry()
self._list_view_project = self._project_view.create_list_view_project()
self.list_view_signal_connect(self._list_view_project)
- project_vbox.pack_start(self._list_view_project, True, True, 0)
- self._list_view_project.show()
+ self._list_view_project.set_vexpand(True)
+ project_vbox.append(self._list_view_project)
+ self._list_view_project.set_visible(True)
def get_add_new_box(self):
return self._add_new_box
@@ -331,6 +334,8 @@ def get_list_view(self):
return self._list_view
def project_view_activated_cb(self, list_view, metadata):
+ if not self._init_done:
+ return
self.project_metadata = metadata
self._project_view.set_project_metadata(self.project_metadata)
@@ -340,12 +345,12 @@ def project_view_activated_cb(self, list_view, metadata):
self.set_canvas(self._project_view)
self._toolbox = self._main_toolbox
self.set_toolbar_box(self._toolbox)
- self._toolbox.show()
+ self._toolbox.set_visible(True)
query = {}
query['project_id'] = self.project_metadata['uid']
self._list_view_project.update_with_query(query)
- self._project_view.show_all()
+ self._project_view.set_visible(True)
def _setup_secondary_view(self):
self._secondary_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
@@ -355,8 +360,9 @@ def _setup_secondary_view(self):
self._detail_view = DetailView(self)
self._detail_view.connect('go-back-clicked', self.__go_back_clicked_cb)
- self._secondary_view.pack_end(self._detail_view, True, True, 0)
- self._detail_view.show()
+ self._detail_view.set_vexpand(True)
+ self._secondary_view.append(self._detail_view)
+ self._detail_view.set_visible(True)
def __add_project_activate_cb(self, bar, title):
initialize_journal_object(
@@ -366,29 +372,30 @@ def __add_project_activate_cb(self, bar, title):
def __add_new_activate_cb(self, bar, title):
shell_model = shell.get_model()
activity = shell_model.get_active_activity()
- if activity.has_shell_window():
+ if activity and activity.has_shell_window():
return
if shell.get_model().has_modal():
return
chooser = ActivityChooser()
- activity.push_shell_window(chooser)
- chooser.connect('hide', activity.pop_shell_window)
+ if activity:
+ activity.push_shell_window(chooser)
+ chooser.connect('unmap', activity.pop_shell_window)
text = _("Choose an activity to start '%s' with") % title
chooser.set_title(text)
chooser.connect('activity-selected',
self.__activity_selected_cb,
title)
- chooser.show_all()
+ chooser.set_visible(True)
def __activity_selected_cb(self, widget, bundle_id, activity_id, title):
initialize_journal_object(
title=title, bundle_id=bundle_id,
activity_id=activity_id, project_metadata=self.project_metadata)
- def __key_press_event_cb(self, widget, event):
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
'''
Entries can be scrolled with 'Up'/'Down' arrow keys.
Search can be started by pressing 'Ctrl' + 'F' keys
@@ -397,38 +404,45 @@ def __key_press_event_cb(self, widget, event):
Pressing 'Return' in Detail View starts the activity
'''
- keyname = Gdk.keyval_name(event.keyval)
+ keyname = Gdk.keyval_name(keyval)
if self._active_view == JournalViews.MAIN:
if keyname == 'Up' or keyname == 'Down':
if not self._list_view.tree_view.has_focus():
self._list_view.tree_view.grab_focus()
+ return True
elif keyname == 'Escape':
self._main_toolbox.clear_query()
if not self._list_view.tree_view.has_focus():
self._list_view.tree_view.grab_focus()
+ return True
- elif event.state & Gdk.ModifierType.CONTROL_MASK and keyname == 'f':
+ elif state & Gdk.ModifierType.CONTROL_MASK and keyname == 'f':
self._main_toolbox.search_entry.grab_focus()
+ return True
elif self._active_view == JournalViews.DETAIL:
if keyname == 'Left':
path, col = self._list_view.tree_view.get_cursor()
self._list_view.tree_view.grab_focus()
column = self._list_view.tree_view.get_column(5)
- self._list_view.tree_view.set_cursor_on_cell(path, column, None, True)
+ if path and column:
+ self._list_view.tree_view.set_cursor_on_cell(path, column, None, True)
self._detail_view.emit('go-back-clicked')
+ return True
if keyname == 'Return':
metadata = self._detail_toolbox.get_metadata()
misc.resume(metadata,
alert_window=journalwindow.get_journal_window())
+ return True
+ return False
def __choose_project_cb(self, tree_view, metadata_to_send):
- project_chooser = ObjectChooser(self.get_window())
- project_chooser.show_all()
+ project_chooser = ObjectChooser(None)
+ project_chooser.present()
project_chooser.connect('response', self.__project_chooser_response_cb,
metadata_to_send)
project_chooser._toolbar._proj_list_button_clicked_cb(None)
@@ -436,7 +450,7 @@ def __choose_project_cb(self, tree_view, metadata_to_send):
def __project_chooser_response_cb(self, project_chooser, response_value,
metadata_to_send):
if response_value == Gtk.ResponseType.DELETE_EVENT:
- project_chooser.destroy()
+ project_chooser.close()
return
object_id = project_chooser.get_selected_object_id()
@@ -445,7 +459,7 @@ def __project_chooser_response_cb(self, project_chooser, response_value,
datastore.delete(metadata_to_send['uid'])
jobject_to_send.metadata['project_id'] = metadata['uid']
datastore.write(jobject_to_send)
- project_chooser.destroy()
+ project_chooser.close()
def __detail_clicked_cb(self, list_view, object_id):
metadata = model.get(object_id)
@@ -473,26 +487,34 @@ def __go_back_clicked_cb(self, detail_view):
self.show_main_view()
def _query_changed_cb(self, toolbar, query):
+ if not self._init_done:
+ return
self._list_view.update_with_query(query)
self.show_main_view()
self._main_toolbox.search_entry.grab_focus()
- Gtk.Entry.do_move_cursor(self._main_toolbox.search_entry,
- Gtk.MovementStep.BUFFER_ENDS, 1, False)
+ # setting cursor position directly
+ self._main_toolbox.search_entry.set_position(-1)
self._add_new_box.props.visible = \
query.get('activity') == PROJECT_BUNDLE_ID
- def __search_icon_pressed_cb(self, entry, icon_pos, event):
+ def __search_icon_pressed_cb(self, entry, icon_pos, event=None):
self._main_view.grab_focus()
def __title_edit_started_cb(self, list_view):
- self.disconnect_by_func(self.__key_press_event_cb)
+ GLib.idle_add(self.remove_controller, self._key_controller)
def __title_edit_finished_cb(self, list_view, new_text, path):
+ self.add_controller(self._key_controller)
list_view_model = list_view.get_model()
iterator = list_view_model.get_iter(path)
list_view_model[iterator][ListModel.COLUMN_TITLE] = new_text
+ def __title_edit_canceled_cb(self, list_view):
+ self.add_controller(self._key_controller)
+
def show_main_view(self):
+ if not self._init_done:
+ return
self._active_view = JournalViews.MAIN
self.project_metadata = None
if self._editing_mode:
@@ -503,13 +525,15 @@ def show_main_view(self):
self._toolbox = self._main_toolbox
self.set_toolbar_box(self._toolbox)
- self._toolbox.show()
+ self._toolbox.set_visible(True)
if self.canvas != self._main_view:
self.set_canvas(self._main_view)
- self._main_view.show()
+ self._main_view.set_visible(True)
def _show_secondary_view(self, object_id):
+ if not self._init_done:
+ return
self._active_view = JournalViews.DETAIL
metadata = model.get(object_id)
try:
@@ -518,7 +542,7 @@ def _show_secondary_view(self, object_id):
logging.exception('Exception while displaying entry:')
self.set_toolbar_box(self._detail_toolbox)
- self._detail_toolbox.show()
+ self._detail_toolbox.set_visible(True)
try:
self._detail_view.props.metadata = metadata
@@ -526,7 +550,7 @@ def _show_secondary_view(self, object_id):
logging.exception('Exception while displaying entry:')
self.set_canvas(self._secondary_view)
- self._secondary_view.show()
+ self._secondary_view.set_visible(True)
def show_object(self, object_id):
metadata = model.get(object_id)
@@ -543,11 +567,15 @@ def __volume_changed_cb(self, volume_toolbar, mount_point):
self._edit_toolbox.batch_copy_button.update_mount_point()
def __model_created_cb(self, sender, **kwargs):
+ if not self._init_done:
+ return
misc.handle_bundle_installation(model.get(kwargs['object_id']))
self._main_toolbox.refresh_filters()
self._check_available_space()
def __model_updated_cb(self, sender, **kwargs):
+ if not self._init_done:
+ return
misc.handle_bundle_installation(model.get(kwargs['object_id']))
if self.canvas == self._secondary_view and \
@@ -557,24 +585,21 @@ def __model_updated_cb(self, sender, **kwargs):
self._check_available_space()
def __model_deleted_cb(self, sender, **kwargs):
+ if not self._init_done:
+ return
if self.canvas == self._secondary_view and \
kwargs['object_id'] == self._detail_view.props.metadata['uid']:
self.show_main_view()
- def _focus_in_event_cb(self, window, event):
+ def _focus_in_event_cb(self, controller):
self._set_is_visible(True)
- def _focus_out_event_cb(self, window, event):
+ def _focus_out_event_cb(self, controller):
self._set_is_visible(False)
- def __window_state_event_cb(self, window, event):
- logging.debug('window_state_event_cb %r', self)
- if event.changed_mask & Gdk.WindowState.ICONIFIED:
- state = event.new_window_state
- visible = not state & Gdk.WindowState.ICONIFIED
- self._set_is_visible(visible)
-
def _set_is_visible(self, visible):
+ if not self._init_done:
+ return
if self._active_view == JournalViews.MAIN:
self._list_view.set_is_visible(visible)
elif self._active_view == JournalViews.PROJECT:
@@ -593,22 +618,28 @@ def _check_available_space(self):
free_space = stat[0] * stat[4]
if free_space < (_SPACE_THRESHOLD * 1024 * 1024):
self._critical_space_alert = ModalAlert()
- self._critical_space_alert.connect('destroy',
+ self._critical_space_alert.connect('close-request',
self.__alert_closed_cb)
- self._critical_space_alert.show()
+ self._critical_space_alert.set_visible(True)
def __alert_closed_cb(self, data):
self.show_main_view()
self.reveal()
self._critical_space_alert = None
+ return False
def set_active_volume(self, mount):
self._volumes_toolbar.set_active_volume(mount)
def show_journal(self):
- """Become visible and show main view"""
+ """Become visible, show main view, and focus the search entry."""
self.reveal()
self.show_main_view()
+ # Focus the search entry so the user can type immediately.
+ if self._init_done and self._main_toolbox is not None:
+ GLib.idle_add(self._main_toolbox.search_entry.grab_focus)
+ else:
+ self.grab_focus()
def get_total_number_of_entries(self):
list_view_model = self.get_list_view().get_model()
@@ -650,7 +681,28 @@ def get_journal():
global _journal
if _journal is None:
_journal = JournalActivity()
- _journal.show()
+
+ shell.get_model().stack.add_named(_journal, "journal")
+
+ _journal._setup()
+
+ # register with the shell model so the frame (F6) can track it
+ from jarabe.model.shell import Activity
+ from jarabe.model.bundleregistry import get_registry
+ from jarabe.model.buddy import get_owner_instance
+
+ registry = get_registry()
+ journal_info = registry.get_bundle('org.laptop.JournalActivity')
+
+ home_activity = Activity(
+ activity_info=journal_info,
+ activity_id=_journal.activity_id,
+ color=get_owner_instance().props.color,
+ window=_journal
+ )
+ shell.get_model()._add_activity(home_activity)
+ shell.get_model().emit('launch-completed', home_activity)
+
return _journal
diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py
index c39d4671d3..9498a98151 100644
--- a/src/jarabe/journal/journalentrybundle.py
+++ b/src/jarabe/journal/journalentrybundle.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2007, One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index 4f256a5ff4..471d7b2fbc 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -53,7 +53,6 @@
from jarabe.journal import journalwindow
from jarabe.webservice import accountsmanager
-
_AUTOSEARCH_TIMEOUT = 1000
_ACTION_ANYTIME = 0
@@ -80,7 +79,7 @@ class MainToolbox(ToolbarBox):
arg_types=([object]))
def __init__(self, default_what_filter=None, default_filter_type=None):
- ToolbarBox.__init__(self)
+ super().__init__()
self._mount_point = None
self._filter_type = default_filter_type
self._what_filter = default_what_filter
@@ -89,69 +88,58 @@ def __init__(self, default_what_filter=None, default_filter_type=None):
self._default_what_filter = default_what_filter
self._default_filter_type = default_filter_type
- self.search_entry = iconentry.IconEntry()
- self.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
- 'entry-search')
+ self.search_entry = Gtk.SearchEntry()
text = _('Search in %s') % _('Journal')
self.search_entry.set_placeholder_text(text)
self.search_entry.connect('activate', self._search_entry_activated_cb)
- self.search_entry.connect('changed', self._search_entry_changed_cb)
- self.search_entry.add_clear_button()
+ self.search_entry.connect('search-changed', self._search_entry_changed_cb)
+ self._last_toolbar_width = 0
self._autosearch_timer = None
self._add_widget(self.search_entry, expand=True)
- self._favorite_button = ToggleToolButton('emblem-favorite')
+ self._favorite_button = ToggleToolButton(icon_name='emblem-favorite')
self._favorite_button.set_tooltip(_('Favorite entries'))
self._favorite_button.connect('toggled',
self.__favorite_button_toggled_cb)
self.toolbar.append(self._favorite_button)
- self._favorite_button.show()
+ self._favorite_button.set_visible(True)
- self._proj_list_button = ToggleToolButton('project-box')
+ self._proj_list_button = ToggleToolButton(icon_name='project-box')
self._proj_list_button.set_tooltip(_('Projects'))
self._proj_list_button.connect('toggled',
self._proj_list_button_clicked_cb)
self.toolbar.append(self._proj_list_button)
- self._proj_list_button.show()
+ self._proj_list_button.set_visible(True)
if not self._proj_list_button.props.active:
self._what_widget_contents = None
self._what_search_button = FilterToolItem(
'view-type', _('Anything'))
- self._what_widget.show()
+ # self._what_widget is created in FilterToolItem
self.toolbar.append(self._what_search_button)
- self._what_search_button.show()
+ self._what_search_button.set_visible(True)
self._when_search_button = FilterToolItem(
'view-created', _('Anytime'), self._get_when_search_items())
self.toolbar.append(self._when_search_button)
- self._when_search_button.show()
+ self._when_search_button.set_visible(True)
self._sorting_button = SortingButton()
self.toolbar.append(self._sorting_button)
self._sorting_button.connect('sort-property-changed',
self.__sort_changed_cb)
- self._sorting_button.show()
-
- '''
- # TODO: enable it when the DS supports saving the buddies.
- self._with_widget = Gtk.ToolItem()
- self._with_search_button = FilterToolItem(
- 'view-who', _('Anyone'), self._with_widget)
- self._with_widget.show()
- self.toolbar.append(self._with_search_button)
- self._with_search_button.show()
- self._get_with_search_items()
- '''
+ self._sorting_button.set_visible(True)
self._query = self._build_query()
self.refresh_filters()
- self.connect('size-allocate', self.__size_allocate_cb)
-
- def __size_allocate_cb(self, widget, allocation):
- GLib.idle_add(self._update_buttons, allocation.width)
+ def do_size_allocate(self, width, height, baseline):
+ super().do_size_allocate(width, height, baseline)
+
+ if width != self._last_toolbar_width:
+ self._last_toolbar_width = width
+ GLib.idle_add(self._update_buttons, width)
def _update_buttons(self, toolbar_width):
# Show the label next to the button icon if there is room on
@@ -160,11 +148,15 @@ def _update_buttons(self, toolbar_width):
if not important:
self.search_entry.set_size_request(
- toolbar_width - style.GRID_CELL_SIZE * 7, 0)
+ toolbar_width - style.GRID_CELL_SIZE * 7, -1)
+ else:
+ self.search_entry.set_size_request(
+ toolbar_width - style.GRID_CELL_SIZE * 11, -1)
- self._what_search_button.set_is_important(important)
- self._when_search_button.set_is_important(important)
- # self._with_search_button.set_is_important(important)
+ if hasattr(self._what_search_button, 'set_is_important'):
+ self._what_search_button.set_is_important(important)
+ if hasattr(self._when_search_button, 'set_is_important'):
+ self._when_search_button.set_is_important(important)
return False
@@ -192,43 +184,15 @@ def _get_when_search_items(self):
return set_palette_list(when_list)
- '''
- def _get_with_search_items(self):
- with_list = []
- with_list.append({'label':_('Anyone'),
- 'callback': self._with_palette_cb,
- 'id': _ACTION_EVERYBODY})
- with_list.append({'separator': True})
- with_list.append({'label':_('My friends'),
- 'callback': self._with_palette_cb,
- 'id': _ACTION_MY_FRIENDS})
- with_list.append({'label':_('My class'),
- 'callback': self._with_palette_cb,
- 'id': _ACTION_MY_CLASS})
- with_list.append({'separator': True})
- # TODO: Ask the model for buddies.
- for i, buddy in enumerate(model.get_buddies()):
- nick, color = buddy
- with_list.append({'label': nick,
- 'callback': self._with_palette_cb,
- 'icon': 'computer-xo',
- 'xocolors': XOColor(color),
- 'id': i + _ACTION_MY_CLASS + 1})
-
- widget = set_palette_list(with_list)
- self._with_widget.add(widget)
- widget.show()
- '''
-
def _add_widget(self, widget, expand=False):
tool_item = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
tool_item.set_hexpand(expand)
tool_item.append(widget)
- widget.show()
+ widget.set_visible(True)
- self.toolbar.append(tool_item, -1)
- tool_item.show()
+ self.toolbar.append(tool_item)
+ tool_item.set_visible(True)
def _build_query(self):
query = {}
@@ -279,7 +243,7 @@ def _build_query(self):
property_, order = self._sorting_button.get_current_sort()
- if order == Gtk.SortType.ASCENDING:
+ if order == "ASC":
sign = '+'
else:
sign = '-'
@@ -321,7 +285,7 @@ def _search_entry_activated_cb(self, search_entry):
def _search_entry_changed_cb(self, search_entry):
if not search_entry.props.text:
- search_entry.activate()
+ search_entry.emit('activate')
return
if self._autosearch_timer:
@@ -332,7 +296,7 @@ def _search_entry_changed_cb(self, search_entry):
def _autosearch_timer_cb(self):
logging.debug('_autosearch_timer_cb')
self._autosearch_timer = None
- self.search_entry.activate()
+ self.search_entry.emit('activate')
return False
def set_mount_point(self, mount_point):
@@ -460,19 +424,15 @@ def refresh_filters(self):
for item in sorted(what_list_activities, key=lambda x: x['label']):
self._what_list.append(item)
- if self._what_widget_contents is not None:
- self._what_widget.remove(self._what_widget_contents)
self._what_widget_contents = set_palette_list(self._what_list)
- self._what_widget.add(self._what_widget_contents)
- self._what_widget_contents.show()
+ self._what_search_button.props.palette.set_content(self._what_widget_contents)
+ self._what_widget_contents.set_visible(True)
def _proj_list_button_clicked_cb(self, proj_list_button):
if self._proj_list_button.props.active:
- self._what_widget.hide()
- self._what_search_button.hide()
+ self._what_search_button.set_visible(False)
else:
- self._what_widget.show()
- self._what_search_button.show()
+ self._what_search_button.set_visible(True)
self._update_if_needed()
def __favorite_button_toggled_cb(self, favorite_button):
@@ -497,17 +457,10 @@ def clear_query(self):
self._when_search_button.set_widget_label(_('Anytime'))
self._when_filter = None
- '''
- self._with_search_button.set_widget_icon(icon_name='view-who')
- self._with_search_button.set_widget_label(_('Anyone'))
- self._with_filter = None
- '''
-
self._favorite_button.props.active = False
if self._proj_list_button.props.active:
- self._what_widget.show()
- self._what_search_button.show()
+ self._what_search_button.set_visible(True)
self._proj_list_button.props.active = False
self._update_if_needed()
@@ -520,7 +473,7 @@ class DetailToolbox(ToolbarBox):
}
def __init__(self, journalactivity):
- ToolbarBox.__init__(self)
+ super().__init__()
self._journalactivity = journalactivity
self._metadata = None
self._temp_file_path = None
@@ -529,18 +482,18 @@ def __init__(self, journalactivity):
self._resume = ToolButton('activity-start')
self._resume.connect('clicked', self._resume_clicked_cb)
self.toolbar.append(self._resume)
- self._resume.show()
+ self._resume.set_visible(True)
self._resume_menu = None
color = profile.get_color()
self._copy = ToolButton()
icon = Icon(icon_name='edit-copy', xo_color=color)
self._copy.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
self._copy.set_tooltip(_('Copy to'))
self._copy.connect('clicked', self._copy_clicked_cb)
self.toolbar.append(self._copy)
- self._copy.show()
+ self._copy.set_visible(True)
self._duplicate = ToolButton()
icon = Icon(icon_name='edit-duplicate', xo_color=color)
@@ -554,17 +507,17 @@ def __init__(self, journalactivity):
self._refresh.set_tooltip(_('Refresh'))
self._refresh.connect('clicked', self._refresh_clicked_cb)
self.toolbar.append(self._refresh)
- self._refresh.show()
+ self._refresh.set_visible(True)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
self.toolbar.append(separator)
- separator.show()
+ separator.set_visible(True)
erase_button = ToolButton('list-remove')
erase_button.set_tooltip(_('Erase'))
erase_button.connect('clicked', self._erase_button_clicked_cb)
- self.toolbar.append(erase_button, -1)
- erase_button.show()
+ self.toolbar.append(erase_button)
+ erase_button.set_visible(True)
def set_metadata(self, metadata):
self._metadata = metadata
@@ -607,13 +560,13 @@ def _erase_button_clicked_cb(self, button):
% self._metadata['title']
icon = Icon(icon_name='dialog-cancel')
alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
- icon.show()
+ icon.set_visible(True)
ok_icon = Icon(icon_name='dialog-ok')
alert.add_button(Gtk.ResponseType.OK, erase_string, ok_icon)
- ok_icon.show()
+ ok_icon.set_visible(True)
alert.connect('response', self.__erase_alert_response_cb)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def __erase_alert_response_cb(self, alert, response_id):
journalwindow.get_journal_window().remove_alert(alert)
@@ -632,9 +585,9 @@ def _refresh_copy_palette(self):
palette = self._copy.get_palette()
# Use the menu defined in CopyMenu
- for menu_item in palette.menu.get_children():
- palette.menu.remove(menu_item)
- menu_item.destroy()
+ while palette.menu.get_first_child():
+ child = palette.menu.get_first_child()
+ child.unparent()
CopyMenuBuilder(self._journalactivity, self.__get_uid_list_cb,
self.__volume_error_cb, palette.menu)
@@ -646,12 +599,12 @@ def _refresh_duplicate_palette(self):
color = misc.get_icon_color(self._metadata)
self._copy.get_icon_widget().props.xo_color = color
if self._metadata['mountpoint'] == '/':
- self._duplicate.show()
+ self._duplicate.set_visible(True)
icon = self._duplicate.get_icon_widget()
icon.props.xo_color = color
- icon.show()
+ icon.set_visible(True)
else:
- self._duplicate.hide()
+ self._duplicate.set_visible(False)
def _refresh_refresh_palette(self):
if self._refresh is None:
@@ -661,8 +614,8 @@ def _refresh_refresh_palette(self):
self._refresh.get_icon_widget().props.xo_color = color
palette = self._refresh.get_palette()
- for menu_item in palette.menu.get_children():
- palette.menu.remove(menu_item)
+ while palette.menu.get_first_child():
+ palette.menu.get_first_child().unparent()
for account in accountsmanager.get_configured_accounts():
if hasattr(account, 'get_shared_journal_entry'):
@@ -686,19 +639,19 @@ def _refresh_resume_palette(self):
palette = self._resume.get_palette()
if self._resume_menu is not None:
- self._resume_menu.destroy()
+ pass
self._resume_menu = PaletteMenuBox()
palette.set_content(self._resume_menu)
- self._resume_menu.show()
+ self._resume_menu.set_visible(True)
for activity_info in misc.get_activities(self._metadata):
menu_item = PaletteMenuItem(file_name=activity_info.get_icon(),
text_label=activity_info.get_name())
- menu_item.connect('activate', self._resume_menu_item_activate_cb,
+ menu_item.connect('clicked', self._resume_menu_item_activate_cb,
activity_info.get_bundle_id())
self._resume_menu.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
if not misc.can_resume(self._metadata):
self._resume.set_tooltip(_('No activity to start entry'))
@@ -714,10 +667,10 @@ class SortingButton(ToolButton):
}
def __init__(self):
- ToolButton.__init__(self)
+ super().__init__()
self._property = 'timestamp'
- self._order = Gtk.SortType.ASCENDING
+ self._order = "ASC"
self.props.tooltip = _('Sort view')
self.props.icon_name = 'view-lastedit'
@@ -727,7 +680,7 @@ def __init__(self):
menu_box = PaletteMenuBox()
self.props.palette.set_content(menu_box)
- menu_box.show()
+ menu_box.set_visible(True)
sort_options = [
('timestamp', 'view-lastedit', _('Sort by date modified')),
@@ -739,23 +692,23 @@ def __init__(self):
button = PaletteMenuItem(label)
button_icon = Icon(pixel_size=style.SMALL_ICON_SIZE,
icon_name=icon)
- button.set_image(button_icon)
- button_icon.show()
- button.connect('activate',
+ button.set_icon_widget(button_icon)
+ button_icon.set_visible(True)
+ button.connect('clicked',
self.__sort_type_changed_cb,
property_,
icon)
- button.show()
+ button.set_visible(True)
menu_box.append_item(button)
def __sort_type_changed_cb(self, widget, property_, icon_name):
if self._property == property_:
- if self._order == Gtk.SortType.ASCENDING:
- self._order = Gtk.SortType.DESCENDING
+ if self._order == "ASC":
+ self._order = "DESC"
else:
- self._order = Gtk.SortType.ASCENDING
+ self._order = "ASC"
else:
- self._order = Gtk.SortType.ASCENDING
+ self._order = "ASC"
self._property = property_
self.emit('sort-property-changed')
@@ -769,24 +722,21 @@ def get_current_sort(self):
class EditToolbox(ToolbarBox):
def __init__(self, journalactivity):
- ToolbarBox.__init__(self)
+ super().__init__()
self._journalactivity = journalactivity
- self.toolbar.add(SelectNoneButton(journalactivity))
- self.toolbar.add(SelectAllButton(journalactivity))
+ self.toolbar.append(SelectNoneButton(journalactivity))
+ self.toolbar.append(SelectAllButton(journalactivity))
- self.toolbar.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
+ self.toolbar.append(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
self.batch_copy_button = BatchCopyButton(journalactivity)
- self.toolbar.add(self.batch_copy_button)
- self.toolbar.add(BatchEraseButton(journalactivity))
+ self.toolbar.append(self.batch_copy_button)
+ self.toolbar.append(BatchEraseButton(journalactivity))
- self.toolbar.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
+ self.toolbar.append(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
self._multi_select_info_widget = MultiSelectEntriesInfoWidget()
- self.toolbar.add(self._multi_select_info_widget)
-
- self.show_all()
- self.toolbar.show_all()
+ self.toolbar.append(self._multi_select_info_widget)
def display_selected_entries_status(self):
info_widget = self._multi_select_info_widget
@@ -802,7 +752,7 @@ def set_selected_entries(self, selected):
class SelectNoneButton(ToolButton):
def __init__(self, journalactivity):
- ToolButton.__init__(self, 'select-none')
+ super().__init__('select-none')
self.props.tooltip = _('Deselect all')
self._journalactivity = journalactivity
@@ -815,7 +765,7 @@ def __do_deselect_all(self, widget_clicked):
class SelectAllButton(ToolButton):
def __init__(self, journalactivity):
- ToolButton.__init__(self, 'select-all')
+ super().__init__('select-all')
self.props.tooltip = _('Select all')
self._journalactivity = journalactivity
@@ -829,7 +779,7 @@ class BatchEraseButton(ToolButton):
def __init__(self, journalactivity):
self._journalactivity = journalactivity
- ToolButton.__init__(self, 'edit-delete')
+ super().__init__('edit-delete')
self.connect('clicked', self.__button_cliecked_cb)
self.props.tooltip = _('Erase')
@@ -855,7 +805,7 @@ class BatchCopyButton(ToolButton):
def __init__(self, journalactivity):
self._journalactivity = journalactivity
- ToolButton.__init__(self, 'edit-copy')
+ super().__init__('edit-copy')
self.props.tooltip = _('Copy')
self.connect('clicked', self.__clicked_cb)
self._menu_builder = None
@@ -885,17 +835,17 @@ def __get_uid_list_cb(self):
class MultiSelectEntriesInfoWidget(Gtk.Box):
def __init__(self):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._selected_entries = 0
self._total = 0
self._label = Gtk.Label()
self._label.set_vexpand(True)
- self._label.set_valign(True)
+ self._label.set_valign(Gtk.Align.CENTER)
self.append(self._label)
- self.show()
+ self.set_visible(True)
def set_total_number_of_entries(self, total):
self._total = total
@@ -908,105 +858,80 @@ def display_selected_entries(self):
message = _('Selected %(selected)d of %(total)d') % {
'selected': self._selected_entries, 'total': self._total}
self._label.set_text(message)
- self._label.show()
+ self._label.set_visible(True)
-class FilterToolItem(Gtk.Box):
+class FilterToolItem(ToolButton):
__gsignals__ = {
'changed': (GObject.SignalFlags.RUN_LAST, None, ([])), }
def __init__(self, default_icon, default_label, palette_content=None):
- self._palette_invoker = ToolInvoker()
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+ super().__init__(icon_name=default_icon)
self._label = default_label
self.set_size_request(style.GRID_CELL_SIZE, -1)
- self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- self.append(box)
- self.set_widget_icon(icon_name=default_icon)
-
- self._label_widget = Gtk.Label()
- self._label_widget.set_alignment(0.0, 0.5)
- self._label_widget.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
- self._label_widget.set_max_width_chars(_LABEL_MAX_WIDTH)
- self._label_widget.set_use_markup(True)
- self._label_widget.set_markup(default_label)
- self.box.append(self._label_widget)
- self._label_widget.show()
-
-
self._hide_tooltip_on_click = True
- self._palette_invoker.attach_tool(self)
- self._palette_invoker.props.toggle_palette = True
- self._palette_invoker.props.lock_palette = True
+ self.get_palette_invoker().props.toggle_palette = True
+ self.get_palette_invoker().props.lock_palette = True
- self.palette = Palette(_('Select filter'))
- self.palette.set_invoker(self._palette_invoker)
+ if not self.get_palette():
+ self.set_palette(Palette(_('Select filter')))
+ else:
+ self.props.palette.props.primary_text = _('Select filter')
+
if palette_content:
- self.props.palette.set_content(palette_content)
+ if isinstance(palette_content, list):
+ menu_box = PaletteMenuBox()
+ for item in palette_content:
+ if 'icon_name' in item:
+ menu_item = PaletteMenuItem(item['label'],
+ item['icon_name'])
+ else:
+ menu_item = PaletteMenuItem(text_label=item['label'])
+ if 'callback' in item:
+ def _click_cb(btn, cb=item['callback'], i=item, widget=menu_item):
+ cb(widget, None, i)
+ menu_item.connect('clicked', _click_cb)
+ menu_box.append_item(menu_item)
+ menu_item.set_visible(True)
+ self.props.palette.set_content(menu_box)
+ menu_box.set_visible(True)
+ else:
+ self.props.palette.set_content(palette_content)
- self.append(self.palette)
+ self.set_widget_icon(icon_name=default_icon)
+ self.set_is_important(False)
def set_widget_icon(self, icon_name=None, file_name=None):
+ from sugar4.graphics.xocolor import XoColor
+ xo_color = XoColor('white')
if file_name is not None:
- icon = Icon(file=file_name,
+ icon = Icon(file_name=file_name,
pixel_size=style.SMALL_ICON_SIZE,
- xo_color=XoColor('white'))
+ xo_color=xo_color)
else:
icon = Icon(icon_name=icon_name,
pixel_size=style.SMALL_ICON_SIZE,
- xo_color=XoColor('white'))
- self.box.prepend(icon)
- icon.show()
+ xo_color=xo_color)
+ self.set_icon_widget(icon)
+ icon.set_visible(True)
def set_widget_label(self, label=None):
- # FIXME: Ellipsis is not working on these labels.
if label is None:
label = self._label
if len(label) > _LABEL_MAX_WIDTH:
label = label[0:7] + '...' + label[-7:]
- self._label_widget.set_markup(label)
self._label = label
+ if self._important:
+ self.set_label(self._label)
+ else:
+ self.set_label(None)
- def create_palette(self):
- return None
-
- def get_palette(self):
- return self._palette_invoker.palette
-
- def set_palette(self, palette):
- self._palette_invoker.palette = palette
-
- palette = GObject.Property(
- type=object, setter=set_palette, getter=get_palette)
-
- def get_palette_invoker(self):
- return self._palette_invoker
-
- def set_palette_invoker(self, palette_invoker):
- self._palette_invoker.detach()
- self._palette_invoker = palette_invoker
-
- palette_invoker = GObject.Property(
- type=object, setter=set_palette_invoker, getter=get_palette_invoker)
-
- def do_snapshot(self, snapshot):
- if self.palette and self.palette.is_up():
- _, bounds = self.compute_bounds(self)
- black = Gdk.RGBA()
- snapshot.append_color(black, bounds)
- snapshot.save()
-
- Gtk.Box.do_snapshot(self, snapshot)
-
- if self.palette and self.palette.is_up():
- invoker = self.palette.props.invoker
- invoker.draw_rectangle(snapshot, self.palette)
-
- return False
-
+ def set_is_important(self, important):
+ self._important = important
+ self.set_widget_label(None)
if hasattr(FilterToolItem, 'set_css_name'):
FilterToolItem.set_css_name('filtertoolbutton')
@@ -1018,15 +943,22 @@ def set_palette_list(palette_list):
text_label=palette_list[0]['label'])
else:
_menu_item = PaletteMenuItem(text_label=palette_list[0]['label'])
- req2 = _menu_item.get_preferred_size()[1]
- menuitem_width = req2.width
- menuitem_height = req2.height
-
- palette_width = Gdk.Screen.width() - style.GRID_CELL_SIZE
- palette_height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 3
+
+ menuitem_width = style.GRID_CELL_SIZE * 3
+ menuitem_height = style.GRID_CELL_SIZE
+
+ display = Gdk.Display.get_default()
+ monitor = display.get_monitors().get_item(0) if display else None
+ if monitor:
+ geom = monitor.get_geometry()
+ palette_width = max(800, geom.width) - style.GRID_CELL_SIZE
+ palette_height = max(600, geom.height) - style.GRID_CELL_SIZE * 3
+ else:
+ palette_width = 800 - style.GRID_CELL_SIZE
+ palette_height = 600 - style.GRID_CELL_SIZE * 3
- nx = min(_MAXIMUM_PALETTE_COLUMNS, int(palette_width / menuitem_width))
- ny = min(int(palette_height / menuitem_height), len(palette_list) + 1)
+ nx = max(1, min(_MAXIMUM_PALETTE_COLUMNS, int(palette_width / menuitem_width)))
+ ny = max(1, min(int(palette_height / menuitem_height), len(palette_list) + 1))
if ny >= len(palette_list):
nx = 1
ny = len(palette_list)
@@ -1034,8 +966,7 @@ def set_palette_list(palette_list):
grid = Gtk.Grid()
grid.set_row_spacing(style.DEFAULT_PADDING)
grid.set_column_spacing(0)
- grid.set_border_width(0)
- grid.show()
+ grid.set_visible(True)
x = 0
y = 0
@@ -1053,8 +984,7 @@ def set_palette_list(palette_list):
text_label=item['label'],
xo_color=xo_color)
else:
- menu_item = PaletteMenuItem()
- menu_item.set_label(item['label'])
+ menu_item = PaletteMenuItem(text_label=item['label'])
menu_item.set_size_request(style.GRID_CELL_SIZE * 3, -1)
@@ -1064,14 +994,17 @@ def set_palette_list(palette_list):
x = 0
y += 1
else:
- menu_item.connect('button-release-event', item['callback'], item)
+ def _click_cb(btn, cb=item['callback'], i=item, widget=menu_item):
+ cb(widget, None, i)
+ menu_item.connect('clicked', _click_cb)
+
grid.attach(menu_item, x, y, 1, 1)
x += 1
if x == nx:
x = 0
y += 1
- menu_item.show()
+ menu_item.set_visible(True)
if palette_height < (y * menuitem_height + style.GRID_CELL_SIZE):
# if the grid is bigger than the palette, put in a scrolledwindow
@@ -1080,7 +1013,7 @@ def set_palette_list(palette_list):
Gtk.PolicyType.AUTOMATIC)
scrolled_window.set_size_request(nx * menuitem_width,
(ny + 1) * menuitem_height)
- scrolled_window.add_with_viewport(grid)
+ scrolled_window.set_child(grid)
return scrolled_window
return grid
@@ -1090,24 +1023,33 @@ class AddNewBar(Gtk.Box):
activate = GObject.Signal('activate', arg_types=[str])
def __init__(self, placeholder=None):
- Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self._button = EventIcon(icon_name='list-add')
- self._button.connect('button-release-event',
- self.__button_release_event_cb)
+
+ click = Gtk.GestureClick()
+ click.connect('released', self.__button_release_event_cb)
+ self._button.add_controller(click)
+
self._button.fill_color = style.COLOR_TOOLBAR_GREY.get_svg()
- self._button.set_tooltip(_('Add New'))
- self.pack_start(self._button, False, True, 0)
- self._button.show()
-
- self._entry = iconentry.IconEntry()
- self._entry.connect('key-press-event', self.__key_press_cb)
+ self._button.set_tooltip_text(_('Add New'))
+ self.append(self._button)
+ self._button.set_visible(True)
+
+ self._entry = Gtk.Entry()
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_cb)
+ self._entry.add_controller(key_controller)
+
if placeholder is None:
placeholder = _('Add new entry')
self._entry.set_placeholder_text(placeholder)
- self._entry.add_clear_button()
- self.pack_start(self._entry, True, True, 0)
- self._entry.show()
+ self._entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, 'edit-clear-symbolic')
+ self._entry.connect('icon-press', lambda entry, pos: entry.set_text('') if pos == Gtk.EntryIconPosition.SECONDARY else None)
+ self._entry.set_hexpand(True)
+ self.append(self._entry)
+ self._entry.set_visible(True)
def get_entry(self):
return self._entry
@@ -1115,11 +1057,13 @@ def get_entry(self):
def get_button(self):
return self._button
- def __key_press_cb(self, window, event):
- if event.keyval == Gdk.KEY_Return:
- return self._maybe_activate()
+ def __key_press_cb(self, controller, keyval, keycode, state):
+ if keyval == Gdk.KEY_Return:
+ self._maybe_activate()
+ return True
+ return False
- def __button_release_event_cb(self, button, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
self._maybe_activate()
def _maybe_activate(self):
diff --git a/src/jarabe/journal/journalwindow.py b/src/jarabe/journal/journalwindow.py
index e73012497e..3bd6095eec 100644
--- a/src/jarabe/journal/journalwindow.py
+++ b/src/jarabe/journal/journalwindow.py
@@ -14,25 +14,131 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from sugar4.graphics.window import Window
+import logging
+_logger = logging.getLogger('JournalWindow')
+
+from gi.repository import Gtk, Gdk
from gettext import gettext as _
_journal_window = None
+_css = b'''
+.journal-window {
+ background-color: #FFFFFF;
+}
+'''
+_provider = Gtk.CssProvider()
+_provider.load_from_data(_css)
-class JournalWindow(Window):
- def __init__(self):
+class JournalWindow(Gtk.Box):
+ def __init__(self):
global _journal_window
- Window.__init__(self)
+ Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
+
+ display = Gdk.Display.get_default()
+ if display:
+ Gtk.StyleContext.add_provider_for_display(
+ display, _provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+ self.add_css_class('journal-window')
_journal_window = self
- self.set_icon_name('activity-journal')
- self.set_title(_('Journal'))
- # Stop the user from closing the journal window.
- self.connect('delete-event', lambda widget, event: True)
+ self._toolbar_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self._alert_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self._canvas_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+ self._canvas_area.set_vexpand(True)
+ self._canvas_area.set_hexpand(True)
+
+ self.append(self._toolbar_area)
+ self.append(self._alert_area)
+ self.append(self._canvas_area)
+
+ self._canvas = None
+ self._toolbar_box = None
+ self._presenting = False
+
+ @property
+ def canvas(self):
+ return self._canvas
+
+ def set_canvas(self, widget):
+ if self._canvas == widget:
+ return
+ if self._canvas is not None and self._canvas.get_parent() == self._canvas_area:
+ self._canvas_area.remove(self._canvas)
+ self._canvas = widget
+ if widget is not None:
+ if widget.get_parent() is not None:
+ parent = widget.get_parent()
+ if parent != self._canvas_area:
+ parent.remove(widget)
+ if widget.get_parent() != self._canvas_area:
+ self._canvas_area.append(widget)
+
+ def set_toolbar_box(self, toolbar):
+ if self._toolbar_box == toolbar:
+ return
+ if self._toolbar_box is not None and \
+ self._toolbar_box.get_parent() == self._toolbar_area:
+ self._toolbar_area.remove(self._toolbar_box)
+ self._toolbar_box = toolbar
+ if toolbar is not None:
+ if toolbar.get_parent() is not None:
+ parent = toolbar.get_parent()
+ if parent != self._toolbar_area:
+ parent.remove(toolbar)
+ if toolbar.get_parent() != self._toolbar_area:
+ self._toolbar_area.append(toolbar)
+
+ def get_toolbar_box(self):
+ return self._toolbar_box
+
+ def add_alert(self, alert):
+ self._alert_area.append(alert)
+ alert.set_visible(True)
+
+ def remove_alert(self, alert):
+ if alert.get_parent() == self._alert_area:
+ self._alert_area.remove(alert)
+
+ def minimize(self):
+ pass
+
+ def get_title(self):
+ return _('Journal')
+
+ def present(self):
+ if self._presenting:
+ return
+ self._presenting = True
+ try:
+ _logger.debug('present() called')
+ self.set_visible(True)
+
+ from jarabe.model import shell
+ shell_model = shell.get_model()
+ if not shell_model:
+ return
+
+ for act in shell_model._activities:
+ if act.is_journal():
+ if shell_model.get_active_activity() != act:
+ shell_model.activate_activity(act)
+ return
+ finally:
+ self._presenting = False
+
+ def reveal(self):
+ _logger.debug('reveal() called')
+ self.set_visible(True)
+ self.present()
+
+ def close(self):
+ self.set_visible(False)
def get_journal_window():
diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py
index 1726763653..f941607f86 100644
--- a/src/jarabe/journal/keepicon.py
+++ b/src/jarabe/journal/keepicon.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -24,35 +24,32 @@ class KeepIcon(Gtk.ToggleButton):
__gtype_name__ = 'SugarKeepIcon'
def __init__(self):
- Gtk.ToggleButton.__init__(self)
- self.set_relief(Gtk.ReliefStyle.NONE)
- self.set_focus_on_click(False)
+ super().__init__()
+ self.props.has_frame = False
+ self.props.focusable = False
self._icon = Icon(icon_name='emblem-favorite',
pixel_size=style.SMALL_ICON_SIZE)
- self.set_image(self._icon)
+ self.set_child(self._icon)
self.connect('toggled', self.__toggled_cb)
- self.connect('button-press-event', self.__button_press_event_cb)
- self.connect('button-release-event', self.__button_release_event_cb)
- self._xo_color = profile.get_color()
+ click = Gtk.GestureClick()
+ click.connect('pressed', self.__button_press_event_cb)
+ click.connect('released', self.__button_release_event_cb)
+ self.add_controller(click)
- def do_get_preferred_width(self):
- return 0, style.GRID_CELL_SIZE
+ self._xo_color = profile.get_color()
- def do_get_preferred_height(self):
- return 0, style.GRID_CELL_SIZE
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
- def __button_press_event_cb(self, widget, event):
+ def __button_press_event_cb(self, gesture, n_press, x, y):
# We need to use a custom CSS class because in togglebuttons
# the 'active' class doesn't only match the button press, they
# can be left in the active state.
- style_context = self.get_style_context()
- style_context.add_class('toggle-press')
+ self.add_css_class('toggle-press')
- def __button_release_event_cb(self, widget, event):
- style_context = self.get_style_context()
- style_context.remove_class('toggle-press')
+ def __button_release_event_cb(self, gesture, n_press, x, y):
+ self.remove_css_class('toggle-press')
def __toggled_cb(self, widget):
if self.get_active():
@@ -61,6 +58,5 @@ def __toggled_cb(self, widget):
self._icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
-
if hasattr(KeepIcon, 'set_css_name'):
KeepIcon.set_css_name('canvasicon')
diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py
index cb13a39018..55b91f8986 100644
--- a/src/jarabe/journal/listmodel.py
+++ b/src/jarabe/journal/listmodel.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009, Tomeu Vizoso
+# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -28,13 +28,12 @@
from jarabe.journal import model
from jarabe.journal import misc
-
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
-class ListModel(GObject.GObject, Gtk.TreeModel, Gtk.TreeDragSource):
+class ListModel(GObject.GObject, Gtk.TreeModel):
__gtype_name__ = 'JournalListModel'
__gsignals__ = {
@@ -57,29 +56,33 @@ class ListModel(GObject.GObject, Gtk.TreeModel, Gtk.TreeDragSource):
COLUMN_SELECT = 12
_COLUMN_TYPES = {
- COLUMN_UID: str,
- COLUMN_FAVORITE: bool,
- COLUMN_ICON: str,
- COLUMN_ICON_COLOR: object,
- COLUMN_TITLE: str,
- COLUMN_TIMESTAMP: str,
- COLUMN_CREATION_TIME: str,
- COLUMN_FILESIZE: str,
- COLUMN_PROGRESS: int,
- COLUMN_BUDDY_1: object,
- COLUMN_BUDDY_3: object,
- COLUMN_BUDDY_2: object,
- COLUMN_SELECT: bool,
+ COLUMN_UID: GObject.TYPE_STRING,
+ COLUMN_FAVORITE: GObject.TYPE_BOOLEAN,
+ COLUMN_ICON: GObject.TYPE_STRING,
+ COLUMN_ICON_COLOR: GObject.TYPE_PYOBJECT,
+ COLUMN_TITLE: GObject.TYPE_STRING,
+ COLUMN_TIMESTAMP: GObject.TYPE_STRING,
+ COLUMN_CREATION_TIME: GObject.TYPE_STRING,
+ COLUMN_FILESIZE: GObject.TYPE_STRING,
+ COLUMN_PROGRESS: GObject.TYPE_INT,
+ COLUMN_BUDDY_1: GObject.TYPE_PYOBJECT,
+ COLUMN_BUDDY_2: GObject.TYPE_PYOBJECT,
+ COLUMN_BUDDY_3: GObject.TYPE_PYOBJECT,
+ COLUMN_SELECT: GObject.TYPE_BOOLEAN,
}
+ _dummy_xo_color = None
+ _dummy_buddy = []
+
_PAGE_SIZE = 10
def __init__(self, query):
- GObject.GObject.__init__(self)
+ super().__init__()
self._last_requested_index = None
self._temp_drag_file_uid = None
self._cached_row = None
+ self._pyobject_cache = {}
self._query = query
self._all_ids = []
t = time.time()
@@ -152,6 +155,19 @@ def __reconnect_updates_cb(self, metadata, filepath, uid):
def do_get_value(self, iterator, column):
if self.view_is_resizing:
+ t = ListModel._COLUMN_TYPES[column]
+ if t == GObject.TYPE_STRING:
+ return ""
+ elif t == GObject.TYPE_BOOLEAN:
+ return False
+ elif t == GObject.TYPE_INT:
+ return 0
+ elif t == GObject.TYPE_PYOBJECT:
+ if column == ListModel.COLUMN_ICON_COLOR:
+ if ListModel._dummy_xo_color is None:
+ ListModel._dummy_xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), style.COLOR_TRANSPARENT.get_svg()))
+ return ListModel._dummy_xo_color
+ return ListModel._dummy_buddy
return None
index = iterator.user_data
@@ -167,6 +183,9 @@ def do_get_value(self, iterator, column):
self._last_requested_index = index
self._cached_row = []
+ self._pyobject_cache[index] = self._cached_row
+ if len(self._pyobject_cache) > 200:
+ self._pyobject_cache.clear()
self._cached_row.append(metadata['uid'])
self._cached_row.append(metadata.get('keep', '0') == '1')
self._cached_row.append(misc.get_icon_name(metadata))
@@ -234,7 +253,9 @@ def do_get_value(self, iterator, column):
self._cached_row.append([nick, XoColor(color)])
continue
- self._cached_row.append(None)
+ self._cached_row.append(ListModel._dummy_buddy)
+
+ self._cached_row.append(self.is_selected(metadata['uid']))
return self._cached_row[column]
@@ -271,28 +292,6 @@ def do_iter_has_child(self, iterator):
def do_iter_parent(self, iterator):
return (False, Gtk.TreeIter())
- def do_drag_data_get(self, path, selection):
- uid = self[path][ListModel.COLUMN_UID]
- target_atom = selection.get_target()
- target_name = target_atom.name()
- if target_name == 'text/uri-list':
- # Only get a new temp path if we have a new file, the frame
- # requests a path many times and if we give it a new path it
- # ends up with a broken path
- if uid != self._temp_drag_file_uid:
- # Get hold of a reference so the temp file doesn't get deleted
- self._temp_drag_file_path = model.get_file(uid)
- self._temp_drag_file_uid = uid
- logging.debug('putting %r in selection', self._temp_drag_file_path)
- selection.set(target_atom, 8, self._temp_drag_file_path)
- return True
- if target_name == 'journal-object-id':
- # uid is unicode but Gtk.SelectionData.set() needs str
- selection.set(target_atom, 8, str(uid))
- return True
-
- return False
-
def set_selected(self, uid, value):
if value:
self._selected.append(uid)
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 8ea2e2cc2e..e1219c47e0 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -27,7 +27,6 @@
from sugar4.graphics import style
from sugar4.graphics.alert import ConfirmationAlert
from sugar4.graphics.icon import Icon, CellRendererIcon
-from sugar4.graphics.scrollingdetector import ScrollingDetector
from sugar4 import util
from sugar4 import profile
from sugar4.graphics.palettewindow import TreeViewInvoker
@@ -38,7 +37,6 @@
from jarabe.journal import misc
from jarabe.journal import journalwindow
-
UPDATE_INTERVAL = 300
PROJECT_BUNDLE_ID = 'org.sugarlabs.Project'
@@ -56,7 +54,7 @@ class TreeView(Gtk.TreeView):
}
def __init__(self, journalactivity):
- Gtk.TreeView.__init__(self)
+ super().__init__()
self._journalactivity = journalactivity
self.icon_activity_column = None
@@ -67,11 +65,9 @@ def __init__(self, journalactivity):
self.set_headers_visible(False)
self.set_enable_search(False)
- self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
- Gdk.EventMask.TOUCH_MASK |
- Gdk.EventMask.BUTTON_RELEASE_MASK)
def connect_to_scroller(self, scrolled):
+ # scrolling detector logic
scrolled.connect('scroll-start', self._scroll_start_cb)
scrolled.connect('scroll-end', self._scroll_end_cb)
@@ -113,7 +109,7 @@ def create_palette(self, path, column):
iterator,
column_index
)
- if buddy_value is not None:
+ if buddy_value:
nick, xo_color = buddy_value
palette = BuddyPalette(
(nick,
@@ -130,23 +126,11 @@ def __volume_error_cb(self, palette, message, severity):
def __choose_project_cb(self, palette, metadata_to_copy):
self.emit('choose-project', metadata_to_copy)
- def do_size_request(self, requisition):
- # HACK: We tell the model that the view is just resizing so it can
- # avoid hitting both D-Bus and disk.
- tree_model = self.get_model()
- if tree_model is not None:
- tree_model.view_is_resizing = True
- try:
- Gtk.TreeView.do_size_request(self, requisition)
- finally:
- if tree_model is not None:
- tree_model.view_is_resizing = False
-
def __del__(self):
self._invoker.detach()
-class BaseListView(Gtk.Bin):
+class BaseListView(Gtk.Box):
__gtype_name__ = 'JournalBaseListView'
__gsignals__ = {
@@ -167,18 +151,21 @@ def __init__(self, journalactivity, enable_multi_operations=False):
self._last_progress_bar_pulse = None
self._scroll_position = 0.
self._projects_view_active = False
+
+ self._current_child = None
- Gtk.Bin.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.connect('map', self.__map_cb)
self.connect('unmap', self.__unmap_cb)
- self.connect('destroy', self.__destroy_cb)
self._scrolled_window = Gtk.ScrolledWindow()
self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
- self.add(self._scrolled_window)
- self._scrolled_window.show()
+ self._scrolled_window.set_vexpand(True)
+ self.append(self._scrolled_window)
+ self._current_child = self._scrolled_window
+ self._scrolled_window.set_visible(True)
self.tree_view = TreeView(self._journalactivity)
self.tree_view.connect('detail-clicked', self.__detail_clicked_cb)
@@ -186,16 +173,13 @@ def __init__(self, journalactivity, enable_multi_operations=False):
selection = self.tree_view.get_selection()
selection.set_mode(Gtk.SelectionMode.SINGLE)
self.tree_view.props.fixed_height_mode = True
- self._scrolled_window.add(self.tree_view)
- self.tree_view.show()
+ self._scrolled_window.set_child(self.tree_view)
+ self.tree_view.set_visible(True)
self.cell_title = None
self.cell_icon = None
self._title_column = None
- self.sort_column = None
- self._scrolling_detector = ScrollingDetector(self._scrolled_window)
- self.tree_view.connect_to_scroller(self._scrolling_detector)
-
+
self._add_columns()
self.enable_drag_and_copy()
@@ -210,17 +194,58 @@ def __init__(self, journalactivity, enable_multi_operations=False):
model.created.connect(self.__model_created_cb)
model.updated.connect(self.__model_updated_cb)
model.deleted.connect(self.__model_deleted_cb)
+
+ def _switch_child(self, new_child):
+ if self._current_child == new_child:
+ return
+ if self._current_child:
+ self._current_child.unparent()
+ self.append(new_child)
+ self._current_child = new_child
def enable_drag_and_copy(self):
- self.tree_view.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
- [Gtk.TargetEntry.new(
- 'text/uri-list', 0, 0),
- Gtk.TargetEntry.new(
- 'journal-object-id', 0, 0)],
- Gdk.DragAction.COPY)
+ self._drag_source = Gtk.DragSource.new()
+ self._drag_source.set_actions(Gdk.DragAction.COPY)
+ self._drag_source.connect('prepare', self.__drag_prepare_cb)
+ self._drag_source.connect('drag-begin', self.__drag_begin_cb)
+ self.tree_view.add_controller(self._drag_source)
def disable_drag_and_copy(self):
- self.tree_view.unset_rows_drag_source()
+ if hasattr(self, '_drag_source'):
+ self.tree_view.remove_controller(self._drag_source)
+ del self._drag_source
+ def __drag_prepare_cb(self, drag_source, x, y):
+ path_info = self.tree_view.get_path_at_pos(int(x), int(y))
+ if not path_info:
+ return None
+ path, column, cell_x, cell_y = path_info
+ self._drag_path = path
+
+ row = self.tree_view.get_model()[path]
+ uid = row[ListModel.COLUMN_UID]
+
+ providers = []
+
+ bytes1 = GLib.Bytes.new(uid.encode('utf-8'))
+ providers.append(Gdk.ContentProvider.new_for_bytes("journal-object-id", bytes1))
+
+ file_path = model.get_file(uid)
+ if file_path:
+ uri = 'file://' + file_path + '\r\n'
+ bytes2 = GLib.Bytes.new(uri.encode('utf-8'))
+ providers.append(Gdk.ContentProvider.new_for_bytes("text/uri-list", bytes2))
+
+ return Gdk.ContentProvider.new_union(providers)
+
+ def __drag_begin_cb(self, drag_source, drag):
+ if hasattr(self, '_drag_path'):
+ row = self.tree_view.get_model()[self._drag_path]
+ try:
+ texture = Gdk.Texture.new_from_filename(row[ListModel.COLUMN_ICON])
+ drag_source.set_icon(texture, 0, 0)
+ except GLib.Error:
+ pass
+ self._is_dragging = True
def __model_created_cb(self, sender, signal, object_id):
if self._is_new_item_visible(object_id):
@@ -253,7 +278,10 @@ def _add_columns(self):
cell_select.connect('toggled', self.__cell_select_toggled_cb)
cell_select.props.activatable = True
cell_select.props.xpad = style.DEFAULT_PADDING
- cell_select.props.indicator_size = style.zoom(26)
+
+ # It might be, if so we just ignore it.
+ if hasattr(cell_select.props, 'indicator_size'):
+ cell_select.props.indicator_size = style.zoom(26)
column = Gtk.TreeViewColumn()
column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
@@ -264,7 +292,6 @@ def _add_columns(self):
cell_favorite = CellRendererFavorite()
cell_favorite.connect('clicked', self._favorite_clicked_cb)
- cell_favorite.connect_to_scroller(self._scrolling_detector)
self._fav_column = Gtk.TreeViewColumn()
self._fav_column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
@@ -275,7 +302,6 @@ def _add_columns(self):
self.tree_view.append_column(self._fav_column)
self.cell_icon = CellRendererActivityIcon()
- self.cell_icon.connect_to_scroller(self._scrolling_detector)
column = Gtk.TreeViewColumn()
self.tree_view.icon_activity_column = column
@@ -357,15 +383,7 @@ def _get_width_for_string(self, text):
width, height_ = layout.get_pixel_size()
return width
- def do_size_allocate(self, allocation):
- self.set_allocation(allocation)
- self.get_child().size_allocate(allocation)
-
- def do_size_request(self, requisition):
- requisition.width, requisition.height = \
- self.get_child().size_request()
-
- def __destroy_cb(self, widget):
+ def do_unroot(self):
if self._model is not None:
self._model.stop()
@@ -393,7 +411,7 @@ def __progress_data_cb(self, column, cell, tree_model,
def __favorite_set_data_cb(self, column, cell, tree_model,
tree_iter, data):
favorite = tree_model[tree_iter][ListModel.COLUMN_FAVORITE]
- if favorite:
+ if favorite and str(favorite) not in ('0', 'false', 'False'):
cell.props.xo_color = profile.get_color()
else:
cell.props.xo_color = None
@@ -411,9 +429,7 @@ def _favorite_clicked_cb(self, cell, path):
metadata['keep'] = '1'
self._model[iterator][ListModel.COLUMN_FAVORITE] = '1'
- cell_rect = self.tree_view.get_cell_area(path, self._fav_column)
- self.tree_view.queue_draw_area(cell_rect.x, cell_rect.y,
- cell_rect.width, cell_rect.height)
+ self.tree_view.queue_draw()
# HACK for https://bugs.sugarlabs.org/ticket/4944
# Icon does not update automatically if there is only one journal entry
@@ -452,10 +468,12 @@ def update_with_query(self, query_dict):
def refresh(self, new_query=False):
logging.debug('ListView.refresh query %r', self._query)
self._stop_progress_bar()
- window = self.get_toplevel().get_window()
- if window is not None:
- window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
- Gdk.flush()
+
+ # Cursor setting
+ root = self.get_root()
+ if root:
+ root.set_cursor(Gdk.Cursor.new_from_name("wait"))
+ Gdk.Display.get_default().flush()
GLib.idle_add(self._do_refresh, new_query)
def _do_refresh(self, new_query=False):
@@ -471,10 +489,11 @@ def _do_refresh(self, new_query=False):
self._model.connect('ready', self.__model_ready_cb)
self._model.connect('progress', self.__model_progress_cb)
self._model.setup(self.__model_updated_cb)
- window = self.get_toplevel().get_window()
- if window is not None:
- window.set_cursor(None)
- Gdk.flush()
+
+ root = self.get_root()
+ if root:
+ root.set_cursor(None)
+ Gdk.Display.get_default().flush()
def __model_ready_cb(self, tree_model):
self._stop_progress_bar()
@@ -482,12 +501,6 @@ def __model_ready_cb(self, tree_model):
self._scroll_position = self.tree_view.props.vadjustment.props.value
logging.debug('ListView.__model_ready_cb %r', self._scroll_position)
- x11_window = self.tree_view.get_window()
-
- if x11_window is not None:
- # prevent glitches while later vadjustment setting, see #1235
- self.tree_view.get_bin_window().hide()
-
# if the selection was preserved, restore it
if self._backup_selected is not None:
tree_model.restore_selection(self._backup_selected)
@@ -498,11 +511,6 @@ def __model_ready_cb(self, tree_model):
self.tree_view.set_model(self._model)
self.tree_view.props.vadjustment.props.value = self._scroll_position
- self.tree_view.props.vadjustment.value_changed()
-
- if x11_window is not None:
- # prevent glitches while later vadjustment setting, see #1235
- self.tree_view.get_bin_window().show()
if len(tree_model) == 0:
if self._query.get('project_id', None):
@@ -534,7 +542,6 @@ def _can_clear_query(self):
def __map_cb(self, widget):
logging.debug('ListView.__map_cb %r', self._scroll_position)
self.tree_view.props.vadjustment.props.value = self._scroll_position
- self.tree_view.props.vadjustment.value_changed()
self.set_is_visible(True)
def __unmap_cb(self, widget):
@@ -558,100 +565,90 @@ def __model_progress_cb(self, tree_model):
self._last_progress_bar_pulse = time.time()
def _start_progress_bar(self):
- alignment = Gtk.Alignment.new(xalign=0.5, yalign=0.5,
- xscale=0.5, yscale=0)
- self.remove(self.get_child())
- self.add(alignment)
- alignment.show()
+ alignment = Gtk.Box()
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.CENTER)
+
+ self._switch_child(alignment)
+ alignment.set_visible(True)
self._progress_bar = Gtk.ProgressBar()
self._progress_bar.props.pulse_step = 0.01
self._last_progress_bar_pulse = time.time()
- alignment.add(self._progress_bar)
- self._progress_bar.show()
+ alignment.append(self._progress_bar)
+ self._progress_bar.set_visible(True)
def _stop_progress_bar(self):
if self._progress_bar is None:
return
- self.remove(self.get_child())
- self.add(self._scrolled_window)
+ self._switch_child(self._scrolled_window)
self._progress_bar = None
def _show_message(self, message, show_clear_query=False):
- self.remove(self.get_child())
-
- background_box = Gtk.EventBox()
- background_box.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self.add(background_box)
-
- alignment = Gtk.Alignment.new(0.5, 0.5, 0.1, 0.1)
- background_box.add(alignment)
+ background_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ # Apply CSS background color
+ background_box.add_css_class('bg-white')
+
+ self._switch_child(background_box)
+
+ alignment = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.CENTER)
+ alignment.set_vexpand(True)
+ background_box.append(alignment)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- alignment.add(box)
+ alignment.append(box)
icon = Icon(pixel_size=style.LARGE_ICON_SIZE,
icon_name='activity-journal',
stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
fill_color=style.COLOR_TRANSPARENT.get_svg())
- box.pack_start(icon, expand=True, fill=False, padding=0)
+ box.append(icon)
+ icon.set_visible(True)
label = Gtk.Label()
color = style.COLOR_BUTTON_GREY.get_html()
label.set_markup('%s' % (
color, GLib.markup_escape_text(message)))
- box.pack_start(label, expand=True, fill=False, padding=0)
+ box.append(label)
+ label.set_visible(True)
if not self.get_projects_view_active():
if show_clear_query:
- button_box = Gtk.HButtonBox()
- button_box.set_layout(Gtk.ButtonBoxStyle.CENTER)
- box.pack_start(button_box, False, True, 0)
- button_box.show()
+ button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ button_box.set_halign(Gtk.Align.CENTER)
+ box.append(button_box)
+ button_box.set_visible(True)
- button = Gtk.Button(label=_('Clear search'))
+ button = Gtk.Button()
button.connect('clicked', self.__clear_button_clicked_cb)
- button.props.image = Icon(icon_name='dialog-cancel',
- pixel_size=style.SMALL_ICON_SIZE)
- button_box.pack_start(button, expand=True, fill=False,
- padding=0)
- background_box.show_all()
+ # Build icon+label child.
+ btn_icon = Icon(icon_name='dialog-cancel', pixel_size=style.SMALL_ICON_SIZE)
+ box_btn = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ box_btn.append(btn_icon)
+ box_btn.append(Gtk.Label(label=_('Clear search')))
+ button.set_child(box_btn)
+
+ button_box.append(button)
+ button.set_visible(True)
+
+ background_box.set_visible(True)
def __clear_button_clicked_cb(self, button):
self.emit('clear-clicked')
def _clear_message(self):
- if self.get_child() == self._scrolled_window:
+ if self._current_child == self._scrolled_window:
return
- self.remove(self.get_child())
- self.add(self._scrolled_window)
- self._scrolled_window.show()
+ self._switch_child(self._scrolled_window)
+ self._scrolled_window.set_visible(True)
def update_dates(self):
if not self.tree_view.get_realized():
return
- visible_range = self.tree_view.get_visible_range()
- if visible_range is None:
- return
-
- logging.debug('ListView.update_dates')
-
- path, end_path = visible_range
- tree_model = self.tree_view.get_model()
-
- while True:
- cel_rect = self.tree_view.get_cell_area(path,
- self.sort_column)
- x, y = self.tree_view.convert_tree_to_widget_coords(cel_rect.x,
- cel_rect.y)
- self.tree_view.queue_draw_area(x, y, cel_rect.width,
- cel_rect.height)
- if path == end_path:
- break
- next_iter = tree_model.iter_next(tree_model.get_iter(path))
- path = tree_model.get_path(next_iter)
+ self.tree_view.queue_draw()
def _set_dirty(self):
if self._fully_obscured or self._updates_disabled:
@@ -723,6 +720,8 @@ class ListView(BaseListView):
([])),
'title-edit-finished': (GObject.SignalFlags.RUN_FIRST, None,
([str, object])),
+ 'title-edit-canceled': (GObject.SignalFlags.RUN_FIRST, None,
+ ([])),
'project-view-activate': (GObject.SignalFlags.RUN_FIRST, None,
([object])),
}
@@ -731,11 +730,13 @@ def __init__(self, journalactivity, enable_multi_operations=False):
BaseListView.__init__(self, journalactivity, enable_multi_operations)
self._is_dragging = False
- self.tree_view.connect('drag-begin', self.__drag_begin_cb)
- self.tree_view.connect('drag-data-get', self.__drag_data_get_cb)
- self.tree_view.connect('button-release-event',
- self.__button_release_event_cb)
- self.tree_view.connect('key-press-event', self._key_press_event_cb)
+ click_controller = Gtk.GestureClick()
+ click_controller.connect('released', self.__button_release_event_cb)
+ self.tree_view.add_controller(click_controller)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self._key_press_event_cb)
+ self.tree_view.add_controller(key_controller)
self.cell_title.connect('edited', self.__cell_title_edited_cb)
self.cell_title.connect('editing-canceled', self.__editing_canceled_cb)
@@ -751,21 +752,21 @@ def __init__(self, journalactivity, enable_multi_operations=False):
column.pack_start(cell_detail, True)
self.tree_view.append_column(column)
- def _key_press_event_cb(self, tree_view, event):
- '''
- Adds keyboard accessibility to the journal.
- Activity can be resumed by pressing 'Enter' key.
- Entry can be renamed by pressing 'Ctrl' + 'F2' keys
- Detail View can be opened with 'Right' arrow key.
- '''
- keyname = Gdk.keyval_name(event.keyval)
- path, col = self.tree_view.get_cursor()
+ def _key_press_event_cb(self, controller, keyval, keycode, state):
+ keyname = Gdk.keyval_name(keyval)
+
+ path_col = self.tree_view.get_cursor()
+ if not path_col or path_col[0] is None:
+ return False
+
+ path, col = path_col
if self.tree_view.has_focus():
if keyname == 'Return':
self.__icon_clicked_cb(None, path)
+ return True
- if event.state & Gdk.ModifierType.CONTROL_MASK and keyname == 'F2':
+ if state & Gdk.ModifierType.CONTROL_MASK and keyname == 'F2':
row = self.tree_view.get_model()[path]
metadata = model.get(row[ListModel.COLUMN_UID])
self.cell_title.props.editable = model.is_editable(metadata)
@@ -774,43 +775,41 @@ def _key_press_event_cb(self, tree_view, event):
self.emit('title-edit-started')
column = self.tree_view.get_column(3)
- tree_view.set_cursor_on_cell(path, column, self.cell_title,
+ self.tree_view.set_cursor_on_cell(path, column, self.cell_title,
start_editing=True)
+ return True
if keyname == 'Right':
tree_iter = self._model.get_iter(path)
uid = self._model[tree_iter][ListModel.COLUMN_UID]
self.emit('detail-clicked', uid)
+ return True
+ if keyname in ('Delete', 'KP_Delete'):
+ row = self.tree_view.get_model()[path]
+ uid = row[ListModel.COLUMN_UID]
+ metadata = model.get(uid)
+ if model.is_editable(metadata):
+ model.delete(uid)
+ return True
+
+ if keyname == 'Escape':
+ self.emit('clear-clicked')
+ return True
+
+ return False
def is_dragging(self):
return self._is_dragging
- def __drag_begin_cb(self, widget, drag_context):
- path, _column = self.tree_view.get_cursor()
- if path is None:
- return
-
- row = self.tree_view.get_model()[path]
- _pixbuf = GdkPixbuf.Pixbuf.new_from_file(row[ListModel.COLUMN_ICON])
- self.tree_view.drag_source_set_icon_pixbuf(_pixbuf)
- self._is_dragging = True
-
- def __drag_data_get_cb(self, widget, context, selection, info, time):
- # HACK: Gtk.TreeDragSource does not work for us on Gtk 3.16+, so
- # use our drag source code instead
- path, _column = self.tree_view.get_cursor()
- model = self.tree_view.get_model()
- model.do_drag_data_get(path, selection)
-
- def __button_release_event_cb(self, tree_view, event):
+ def __button_release_event_cb(self, gesture, n_press, x, y):
try:
if self._is_dragging:
return
finally:
self._is_dragging = False
- pos = tree_view.get_path_at_pos(int(event.x), int(event.y))
+ pos = self.tree_view.get_path_at_pos(int(x), int(y))
if pos is None:
return
@@ -824,7 +823,7 @@ def __button_release_event_cb(self, tree_view, event):
if self.cell_title.props.editable:
self.emit('title-edit-started')
- tree_view.set_cursor_on_cell(path, column, self.cell_title,
+ self.tree_view.set_cursor_on_cell(path, column, self.cell_title,
start_editing=True)
def __detail_cell_clicked_cb(self, cell, path):
@@ -850,7 +849,7 @@ def __cell_title_edited_cb(self, cell, path, new_text):
alert.props.msg = _('The title is usually not left empty')
alert.connect('response', self._cell_title_alert_response_cb, path, new_text)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
return
if old_text != new_text:
@@ -861,12 +860,13 @@ def _cell_title_alert_response_cb(self, alert, response_id, path, new_text):
journalwindow.get_journal_window().remove_alert(alert)
iterator = self._model.get_iter(path)
- if response_id is Gtk.ResponseType.OK:
+ if response_id == Gtk.ResponseType.OK:
self._model[iterator][ListModel.COLUMN_TITLE] = new_text
self.emit('title-edit-finished', new_text, path)
def __editing_canceled_cb(self, cell):
self.cell_title.props.editable = False
+ self.emit('title-edit-canceled')
class CellRendererFavorite(CellRendererIcon):
@@ -926,7 +926,7 @@ def __init__(self, column_index):
self.nick = None
def set_buddy(self, buddy):
- if buddy is None:
+ if not buddy:
self.props.icon_name = None
self.nick = None
else:
diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
index 1c5473e121..2a953ddf64 100644
--- a/src/jarabe/journal/misc.py
+++ b/src/jarabe/journal/misc.py
@@ -69,16 +69,17 @@ def _get_icon_for_mime(mime_type):
def get_mount_icon_name(mount, size):
icon = mount.get_icon()
if isinstance(icon, Gio.ThemedIcon):
- icon_theme = Gtk.IconTheme.get_default()
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
for icon_name in icon.props.names:
- lookup = icon_theme.lookup_icon(icon_name, size, 0)
+ lookup = icon_theme.lookup_icon(icon_name, None, size, 1, 0, 0)
if lookup is not None:
- file_name = lookup.get_filename()
- if '/icons/sugar/' not in file_name:
+ icon_file = lookup.get_file()
+ file_name = icon_file.get_path() if icon_file else None
+ if file_name is not None and '/icons/sugar/' not in file_name:
continue
return icon_name
logging.error('Cannot find icon name for %s, %s', icon, mount)
- return 'drive'
+ return 'drive-harddisk'
def get_icon_name(metadata):
@@ -283,8 +284,8 @@ def launch(bundle, activity_id=None, object_id=None, uri=None, color=None,
shell_model = shell.get_model()
activity = shell_model.get_activity_by_id(activity_id)
if activity is not None:
- logging.debug('re-launch %r', activity.get_window())
- activity.get_window().activate(Gtk.get_current_event_time())
+ logging.debug('re-launch via activate_activity for %r', activity)
+ shell_model.activate_activity(activity)
return
if not shell_model.can_launch_activity():
@@ -322,7 +323,7 @@ def _downgrade_option_alert(bundle, metadata):
bundle.get_activity_version()
alert.connect('response', _downgrade_alert_response_cb, metadata)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def _downgrade_alert_response_cb(alert, response_id, metadata):
@@ -378,9 +379,10 @@ def handle_bundle_installation(metadata, force_downgrade=False):
registry = bundleregistry.get_registry()
- window = journalwindow.get_journal_window().get_window()
- window.set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
- Gdk.flush()
+ window = journalwindow.get_journal_window()
+ if window:
+ window.set_cursor(Gdk.Cursor.new_from_name('wait'))
+ Gdk.Display.get_default().flush()
try:
installed = registry.install(bundle, force_downgrade)
@@ -390,8 +392,9 @@ def handle_bundle_installation(metadata, force_downgrade=False):
logging.exception('Could not install bundle %s', bundle.get_path())
return None, False
finally:
- window.set_cursor(None)
- Gdk.flush()
+ if window:
+ window.set_cursor(None)
+ Gdk.Display.get_default().flush()
# If we just installed a bundle, update the datastore accordingly.
# We do not do this for JournalEntryBundles because the JEB code transforms
@@ -415,9 +418,9 @@ def get_mount_color(mount):
uuid = mount.get_uuid()
if uuid:
- sha_hash.update(uuid)
+ sha_hash.update(uuid.encode('utf-8'))
elif path is None:
- sha_hash.update(str(time.time()))
+ sha_hash.update(str(time.time()).encode('utf-8'))
else:
mount_name = os.path.basename(path)
mount_name = mount_name.encode('utf-8')
diff --git a/src/jarabe/journal/modalalert.py b/src/jarabe/journal/modalalert.py
index a774a2c2d5..b301486ce6 100644
--- a/src/jarabe/journal/modalalert.py
+++ b/src/jarabe/journal/modalalert.py
@@ -15,11 +15,13 @@
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import GLib
from gettext import gettext as _
from sugar4.graphics.icon import Icon
from sugar4.graphics import style
from sugar4 import profile
+from jarabe.model import shell
class ModalAlert(Gtk.Window):
@@ -27,71 +29,100 @@ class ModalAlert(Gtk.Window):
__gtype_name__ = 'SugarModalAlert'
def __init__(self):
- Gtk.Window.__init__(self)
+ super().__init__()
- self.set_border_width(style.LINE_WIDTH)
offset = style.GRID_CELL_SIZE
- width = Gdk.Screen.width() - offset * 2
- height = Gdk.Screen.height() - offset * 2
- self.set_size_request(width, height)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
+
+ # Calculate dynamic size based on primary monitor geometry
+ display = Gdk.Display.get_default()
+ monitor = display.get_monitors().get_item(0)
+ if monitor is not None:
+ geometry = monitor.get_geometry()
+ width = geometry.width - offset * 2
+ height = geometry.height - offset * 2
+ self.set_default_size(width, height)
+ else:
+ self.set_default_size(800, 600)
+
self.set_decorated(False)
self.set_resizable(False)
self.set_modal(True)
- self._main_view = Gtk.EventBox()
+ # Set transient parent window
+ shell_model = shell.get_model()
+ if shell_model and shell_model._main_window:
+ self.set_transient_for(shell_model._main_window)
+
+ # Key controller: Escape closes
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
+
+ self._main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ # Apply black background CSS
+ self._main_view.add_css_class('modal-bg')
+
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._vbox.set_spacing(style.DEFAULT_SPACING)
- self._vbox.set_border_width(style.GRID_CELL_SIZE * 2)
- self._main_view.modify_bg(Gtk.StateType.NORMAL,
- style.COLOR_BLACK.get_gdk_color())
- self._main_view.add(self._vbox)
- self._vbox.show()
+ self._vbox.set_margin_top(style.GRID_CELL_SIZE * 2)
+ self._vbox.set_margin_bottom(style.GRID_CELL_SIZE * 2)
+ self._vbox.set_margin_start(style.GRID_CELL_SIZE * 2)
+ self._vbox.set_margin_end(style.GRID_CELL_SIZE * 2)
+
+ self._main_view.append(self._vbox)
+ self._vbox.set_visible(True)
color = profile.get_color()
icon = Icon(icon_name='activity-journal',
pixel_size=style.XLARGE_ICON_SIZE,
xo_color=color)
- self._vbox.pack_start(icon, expand=False, fill=False, padding=0)
- icon.show()
+ self._vbox.append(icon)
+ icon.set_visible(True)
self._title = Gtk.Label()
- self._title.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
self._title.set_markup('%s' % _('Your Journal is full'))
- self._vbox.pack_start(self._title, expand=False, fill=False, padding=0)
- self._title.show()
+ self._vbox.append(self._title)
+ self._title.set_visible(True)
self._message = Gtk.Label(
label=_('Please delete some old Journal'
' entries to make space for new ones.'))
- self._message.modify_fg(Gtk.StateType.NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self._vbox.pack_start(self._message, expand=False,
- fill=False, padding=0)
- self._message.show()
-
- alignment = Gtk.Alignment.new(xalign=0.5, yalign=0.5,
- xscale=0.0, yscale=0.0)
- self._vbox.pack_start(alignment, expand=False, fill=True, padding=0)
- alignment.show()
+ self._vbox.append(self._message)
+ self._message.set_visible(True)
+
+ # Alignment can be replaced by Box with hexpand/vexpand
+ alignment = Gtk.Box()
+ alignment.set_hexpand(True)
+ alignment.set_vexpand(True)
+ alignment.set_halign(Gtk.Align.CENTER)
+ alignment.set_valign(Gtk.Align.CENTER)
+ self._vbox.append(alignment)
+ alignment.set_visible(True)
self._show_journal = Gtk.Button()
self._show_journal.set_label(_('Show Journal'))
- alignment.add(self._show_journal)
- self._show_journal.show()
+ alignment.append(self._show_journal)
+ self._show_journal.set_visible(True)
self._show_journal.connect('clicked', self.__show_journal_cb)
- self.add(self._main_view)
- self._main_view.show()
+ self.set_child(self._main_view)
+ self._main_view.set_visible(True)
self.connect('realize', self.__realize_cb)
def __realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- self.get_window().set_accept_focus(True)
+ self.set_focusable(True)
+ shell.get_model().push_modal()
+
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ if keyval == Gdk.KEY_Escape:
+ shell.get_model().pop_modal()
+ self.close()
+ return True
+ return False
def __show_journal_cb(self, button):
- """The opener will listen on the destroy signal"""
- self.destroy()
+ """The opener will listen on the close-request signal"""
+ shell.get_model().pop_modal()
+ self.close()
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
index c46489812c..7461342cf0 100644
--- a/src/jarabe/journal/model.py
+++ b/src/jarabe/journal/model.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007-2011, One Laptop per Child
+# Copyright (C) 2007-2011, One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -30,13 +30,13 @@
from gi.repository import Gio
from gi.repository import GLib
+import gi
+gi.require_version('SugarExt', '2.0')
from gi.repository import SugarExt
-
from sugar4 import dispatch
from sugar4 import mime
from sugar4 import util
-
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
@@ -222,8 +222,8 @@ def __init__(self, query, page_size):
BaseResultSet.__init__(self, query, page_size)
def find(self, query):
- entries, total_count = _get_datastore().find(query, PROPERTIES,
- byte_arrays=True)
+ entries, total_count = _call_datastore('find', query, PROPERTIES,
+ byte_arrays=True)
for entry in entries:
entry['mountpoint'] = '/'
@@ -233,7 +233,7 @@ def find(self, query):
def find_ids(self, query):
copy = query.copy()
copy.pop('mountpoints', '/')
- return _get_datastore().find_ids(copy)
+ return _call_datastore('find_ids', copy)
class InplaceResultSet(BaseResultSet):
@@ -503,7 +503,6 @@ def _get_file_metadata_from_json(path, fetch_preview):
metadata = None
mount_point = _get_mount_point(path)
subdir = ''
- # check if the file is a subdirectory
if mount_point != dir_path:
subdir = os.path.relpath(dir_path, mount_point)
@@ -556,6 +555,23 @@ def _get_datastore():
return _datastore
+def _call_datastore(method, *args, **kwargs):
+ try:
+ return getattr(_get_datastore(), method)(*args, **kwargs)
+ except dbus.DBusException as e:
+ if e.get_dbus_name() in ['org.freedesktop.DBus.Error.ServiceUnknown',
+ 'org.freedesktop.DBus.Error.NoReply',
+ 'org.freedesktop.DBus.Error.Disconnected']:
+ logging.warning('Datastore connection failed, retrying once.')
+ global _datastore
+ _datastore = None
+ try:
+ return getattr(_get_datastore(), method)(*args, **kwargs)
+ except Exception:
+ raise e
+ raise
+
+
def _datastore_created_cb(object_id):
created.send(None, object_id=object_id)
@@ -602,7 +618,7 @@ def get(object_id):
metadata = _get_file_metadata(object_id, stat)
metadata['mountpoint'] = _get_mount_point(object_id)
else:
- metadata = _get_datastore().get_properties(object_id, byte_arrays=True)
+ metadata = _call_datastore('get_properties', object_id, byte_arrays=True)
metadata['mountpoint'] = '/'
return metadata
@@ -614,7 +630,7 @@ def get_file(object_id):
logging.debug('get_file asked for file with path %r', object_id)
return object_id
logging.debug('get_file asked for entry with id %r', object_id)
- file_path = _get_datastore().get_filename(object_id)
+ file_path = _call_datastore('get_filename', object_id)
if file_path:
return util.TempFilePath(file_path)
return None
@@ -627,7 +643,7 @@ def get_file_size(object_id):
if os.path.exists(object_id):
return os.stat(object_id).st_size
- file_path = _get_datastore().get_filename(object_id)
+ file_path = _call_datastore('get_filename', object_id)
if file_path:
size = os.stat(file_path).st_size
os.remove(file_path)
@@ -640,14 +656,14 @@ def get_unique_values(key):
"""Returns a list with the different values a property has taken
"""
empty_dict = dbus.Dictionary({}, signature='ss')
- return _get_datastore().get_uniquevaluesfor(key, empty_dict)
+ return _call_datastore('get_uniquevaluesfor', key, empty_dict)
def delete(object_id):
"""Removes an object from persistent storage
"""
if not os.path.exists(object_id):
- _get_datastore().delete(object_id)
+ _call_datastore('delete', object_id)
else:
os.unlink(object_id)
dir_path = os.path.dirname(object_id)
@@ -655,7 +671,6 @@ def delete(object_id):
mount_point = _get_mount_point(object_id)
subdir = ''
- # check if the file is a subdirectory
if mount_point != dir_path:
subdir = os.path.relpath(dir_path, mount_point)
@@ -720,18 +735,18 @@ def error_handler(error):
if metadata.get('mountpoint', '/') == '/':
if metadata.get('uid', ''):
- _get_datastore().update(metadata['uid'],
- dbus.Dictionary(metadata),
- file_path,
- transfer_ownership,
- reply_handler=updated_reply_handler,
- error_handler=error_handler)
+ _call_datastore('update', metadata['uid'],
+ dbus.Dictionary(metadata),
+ file_path,
+ transfer_ownership,
+ reply_handler=updated_reply_handler,
+ error_handler=error_handler)
else:
- _get_datastore().create(dbus.Dictionary(metadata),
- file_path,
- transfer_ownership,
- reply_handler=created_reply_handler,
- error_handler=error_handler)
+ _call_datastore('create', dbus.Dictionary(metadata),
+ file_path,
+ transfer_ownership,
+ reply_handler=created_reply_handler,
+ error_handler=error_handler)
else:
_write_entry_on_external_device(
metadata, file_path, ready_callback=ready_callback)
@@ -826,7 +841,6 @@ def _splice_cb(*args):
metadata_dir_path = os.path.join(metadata['mountpoint'],
JOURNAL_METADATA_DIR)
- # check if the file is in a subdirectory in Documents or device
if original_dir_name.startswith(metadata['mountpoint']) and \
original_dir_name != metadata['mountpoint']:
subdir = os.path.relpath(original_dir_name, metadata['mountpoint'])
@@ -923,7 +937,6 @@ def is_editable(metadata):
return True
return os.access(metadata['mountpoint'], os.W_OK)
-
_documents_path = None
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index a5f384c592..f3e2aeb01c 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -19,7 +19,6 @@
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
-#from gi.repository import Wnck
from sugar4.graphics import style
from sugar4.graphics.toolbutton import ToolButton
@@ -28,11 +27,16 @@
from jarabe.journal.listview import BaseListView
from jarabe.journal.listmodel import ListModel
from jarabe.journal.journaltoolbox import MainToolbox
-from jarabe.journal.volumestoolbar import VolumesToolbar
from jarabe.model import bundleregistry
from jarabe.journal.iconview import IconView
+# Inject CSS for modal background
+provider = Gtk.CssProvider()
+css = b".modal-bg { background-color: %s; }" % style.COLOR_BLACK.get_html().encode('utf-8')
+provider.load_from_data(css)
+Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
class ObjectChooser(Gtk.Window):
@@ -44,93 +48,110 @@ class ObjectChooser(Gtk.Window):
def __init__(self, parent=None, what_filter='', filter_type=None,
show_preview=False):
- Gtk.Window.__init__(self)
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
+ super().__init__()
self.set_decorated(False)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
- self.set_border_width(style.LINE_WIDTH)
- self.set_has_resize_grip(False)
+ self.set_modal(True)
self._selected_object_id = None
self._show_preview = show_preview
- self.add_events(Gdk.EventMask.VISIBILITY_NOTIFY_MASK)
- self.connect('visibility-notify-event',
- self.__visibility_notify_event_cb)
- self.connect('delete-event', self.__delete_event_cb)
- self.connect('key-press-event', self.__key_press_event_cb)
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
if parent is None:
logging.warning('ObjectChooser: No parent window specified')
+ # Fall back to main sugar window as transient parent
+ from jarabe.model import shell as _shell
+ shell_model = _shell.get_model()
+ if shell_model and shell_model.get_active_window():
+ self.set_transient_for(shell_model.get_active_window())
else:
- self.connect('realize', self.__realize_cb, parent)
-
- #screen = Wnck.Screen.get_default()
- #screen.connect('window-closed', self.__window_closed_cb, parent)
+ self.set_transient_for(parent)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self.add(vbox)
- vbox.show()
+ vbox.add_css_class('modal-bg')
+ # Margin instead of border_width
+ vbox.set_margin_start(style.LINE_WIDTH)
+ vbox.set_margin_end(style.LINE_WIDTH)
+ vbox.set_margin_top(style.LINE_WIDTH)
+ vbox.set_margin_bottom(style.LINE_WIDTH)
+
+ self.set_child(vbox)
+ vbox.set_visible(True)
title_box = TitleBox(what_filter, filter_type)
- title_box.connect('volume-changed', self.__volume_changed_cb)
title_box.close_button.connect('clicked',
self.__close_button_clicked_cb)
title_box.set_size_request(-1, style.GRID_CELL_SIZE)
- vbox.pack_start(title_box, False, True, 0)
- title_box.show()
+ vbox.append(title_box)
+ title_box.set_visible(True)
separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
- vbox.pack_start(separator, False, True, 0)
- separator.show()
+ vbox.append(separator)
+ separator.set_visible(True)
self._toolbar = MainToolbox(default_what_filter=what_filter,
default_filter_type=filter_type)
self._toolbar.connect('query-changed', self.__query_changed_cb)
self._toolbar.set_size_request(-1, style.GRID_CELL_SIZE)
- vbox.pack_start(self._toolbar, False, True, 0)
- self._toolbar.show()
+ vbox.append(self._toolbar)
+ self._toolbar.set_visible(True)
if not self._show_preview:
self._list_view = ChooserListView(self._toolbar)
self._list_view.connect('entry-activated',
self.__entry_activated_cb)
self._list_view.connect('clear-clicked', self.__clear_clicked_cb)
- vbox.pack_start(self._list_view, True, True, 0)
- self._list_view.show()
+ self._list_view.set_vexpand(True)
+ vbox.append(self._list_view)
+ self._list_view.set_visible(True)
else:
self._icon_view = IconView(self._toolbar)
self._icon_view.connect('entry-activated',
self.__entry_activated_cb)
self._icon_view.connect('clear-clicked', self.__clear_clicked_cb)
- vbox.pack_start(self._icon_view, True, True, 0)
- self._icon_view.show()
-
- width = Gdk.Screen.width() - style.GRID_CELL_SIZE * 2
- height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 2
- self.set_size_request(width, height)
+ self._icon_view.set_vexpand(True)
+ vbox.append(self._icon_view)
+ self._icon_view.set_visible(True)
+
+ display = Gdk.Display.get_default()
+ screen_width, screen_height = 800, 600
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geo = monitors.get_item(0).get_geometry()
+ screen_width = geo.width - style.GRID_CELL_SIZE * 2
+ screen_height = geo.height - style.GRID_CELL_SIZE * 2
+ self.set_default_size(screen_width, screen_height)
self._toolbar.update_filters('/', what_filter, filter_type)
- def __realize_cb(self, chooser, parent):
- self.get_window().set_transient_for(parent)
- # TODO: Should we disconnect the signal here?
+ self.set_focus(self._toolbar.search_entry)
+ self.connect('unmap', self.__visibility_notify_event_cb)
+ self.connect('map', self.__map_cb)
+ self.connect('close-request', self.__close_request_cb)
+ self.connect('realize', self.__realize_cb)
+ self.connect('unrealize', self.__unrealize_cb)
+
+ def __realize_cb(self, widget):
+ from jarabe.model import shell as _shell
+ _shell.get_model().push_modal()
- def __window_closed_cb(self, screen, window, parent):
- if window.get_xid() == parent.get_xid():
- self.destroy()
+ def __unrealize_cb(self, widget):
+ from jarabe.model import shell as _shell
+ _shell.get_model().pop_modal()
def __entry_activated_cb(self, list_view, uid):
self._selected_object_id = uid
self.emit('response', Gtk.ResponseType.ACCEPT)
- def __delete_event_cb(self, chooser, event):
- self.emit('response', Gtk.ResponseType.DELETE_EVENT)
-
- def __key_press_event_cb(self, widget, event):
- keyname = Gdk.keyval_name(event.keyval)
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ keyname = Gdk.keyval_name(keyval)
if keyname == 'Escape':
self.emit('response', Gtk.ResponseType.DELETE_EVENT)
+ return True
+ return False
def __close_button_clicked_cb(self, button):
self.emit('response', Gtk.ResponseType.DELETE_EVENT)
@@ -144,27 +165,35 @@ def __query_changed_cb(self, toolbar, query):
else:
self._icon_view.update_with_query(query)
- def __volume_changed_cb(self, volume_toolbar, mount_point):
- logging.debug('Selected volume: %r.', mount_point)
- self._toolbar.set_mount_point(mount_point)
- def __visibility_notify_event_cb(self, window, event):
+ def __visibility_notify_event_cb(self, widget):
logging.debug('visibility_notify_event_cb %r', self)
- visible = event.get_state() == Gdk.VisibilityState.FULLY_OBSCURED
+ visible = self.get_mapped()
if not self._show_preview:
self._list_view.set_is_visible(visible)
else:
self._icon_view.set_is_visible(visible)
+ def __map_cb(self, widget):
+ if not self._show_preview:
+ self._list_view.set_is_visible(True)
+ else:
+ self._icon_view.set_is_visible(True)
+
+ def __close_request_cb(self, window):
+ self.emit('response', Gtk.ResponseType.DELETE_EVENT)
+ return True
+
def __clear_clicked_cb(self, list_view):
self._toolbar.clear_query()
-class TitleBox(VolumesToolbar):
+class TitleBox(Gtk.Box):
__gtype_name__ = 'TitleBox'
def __init__(self, what_filter='', filter_type=None):
- VolumesToolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
+ self._children = []
label = Gtk.Label()
title = _('Choose an object')
@@ -176,23 +205,22 @@ def __init__(self, what_filter='', filter_type=None):
bundle.get_name()
label.set_markup('%s' % title)
- label.set_alignment(0, 0.5)
+ label.set_halign(Gtk.Align.START)
+ label.set_valign(Gtk.Align.CENTER)
self._add_widget(label, expand=True)
self.close_button = ToolButton(icon_name='dialog-cancel')
self.close_button.set_tooltip(_('Close'))
- self.insert(self.close_button, -1)
- self.close_button.show()
+ self._add_widget(self.close_button)
+ self.close_button.set_visible(True)
def _add_widget(self, widget, expand=False):
- tool_item = Gtk.ToolItem()
- tool_item.set_expand(expand)
+ if expand:
+ widget.set_hexpand(True)
- tool_item.add(widget)
- widget.show()
-
- self.insert(tool_item, -1)
- tool_item.show()
+ self.append(widget)
+ self._children.append(widget)
+ widget.set_visible(True)
class ChooserListView(BaseListView):
@@ -209,9 +237,10 @@ def __init__(self, toolbar):
self._toolbar = toolbar
self.tree_view.props.hover_selection = True
-
- self.tree_view.connect('button-release-event',
- self.__button_release_event_cb)
+
+ click_controller = Gtk.GestureClick()
+ click_controller.connect('released', self.__button_release_event_cb)
+ self.tree_view.add_controller(click_controller)
def _can_clear_query(self):
return self._toolbar.is_filter_changed()
@@ -223,16 +252,13 @@ def create_palette(self, x, y):
# We don't want show the palette in the object chooser
pass
- def __button_release_event_cb(self, tree_view, event):
- if event.window != tree_view.get_bin_window():
- return False
-
- pos = tree_view.get_path_at_pos(int(event.x), int(event.y))
+ def __button_release_event_cb(self, gesture, n_press, x, y):
+ pos = self.tree_view.get_path_at_pos(int(x), int(y))
if pos is None:
return False
path, column_, x_, y_ = pos
- uid = tree_view.get_model()[path][ListModel.COLUMN_UID]
+ uid = self.tree_view.get_model()[path][ListModel.COLUMN_UID]
self.emit('entry-activated', uid)
return False
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
index feb82e4e40..b241a84070 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -24,7 +24,7 @@
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
-#from gi.repository import SugarExt
+
from sugar4.graphics import style
from sugar4.graphics.palette import Palette
@@ -47,6 +47,14 @@
PROJECT_BUNDLE_ID = 'org.sugarlabs.Project'
+def _popdown_palette_chain(widget):
+ p = widget.get_parent()
+ while p:
+ if hasattr(p, 'popdown'):
+ p.popdown()
+ p = p.get_parent()
+
+
class ObjectPalette(Palette):
__gtype_name__ = 'ObjectPalette'
@@ -90,88 +98,99 @@ def __init__(self, journalactivity, metadata, detail=False):
resume_label = _('Start')
resume_with_label = _('Start with')
menu_item = MenuItem(resume_label, 'activity-start')
- menu_item.connect('activate', self.__start_activate_cb)
+ menu_item.connect('clicked', self.__start_activate_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = MenuItem(resume_with_label, 'activity-start')
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
start_with_menu = StartWithMenu(self._metadata)
- menu_item.set_submenu(start_with_menu)
+ start_with_menu.set_parent(menu_item)
+ start_with_menu.set_position(Gtk.PositionType.RIGHT)
+ start_with_menu.set_has_arrow(False)
+ menu_item.connect('clicked', lambda btn, m=start_with_menu: m.popup())
elif metadata.get('activity', None) == PROJECT_BUNDLE_ID:
open_label = _('Open')
menu_item = MenuItem(open_label, 'project-box')
- menu_item.connect('activate', self.__open_project_activate_cb)
+ menu_item.connect('clicked', self.__open_project_activate_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
else:
menu_item = MenuItem(_('No activity to start entry'))
menu_item.set_sensitive(False)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = MenuItem(_('Copy to'))
icon = Icon(icon_name='edit-copy', xo_color=color,
pixel_size=style.SMALL_ICON_SIZE)
menu_item.set_image(icon)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
copy_menu = CopyMenu(self._journalactivity, self.__get_uid_list_cb)
copy_menu.connect('volume-error', self.__volume_error_cb)
- menu_item.set_submenu(copy_menu)
+ copy_menu.set_parent(menu_item)
+ copy_menu.set_position(Gtk.PositionType.RIGHT)
+ copy_menu.set_has_arrow(False)
+ menu_item.connect('clicked', lambda btn, m=copy_menu: m.popup())
if not metadata.get('activity', None) == PROJECT_BUNDLE_ID:
menu_item = MenuItem(_('Send to project...'), 'project-box')
- menu_item.connect('activate', self.__copy_to_project_activated_cb)
+ menu_item.connect('clicked', self.__copy_to_project_activated_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
if self._metadata['mountpoint'] == '/':
menu_item = MenuItem(_('Duplicate'))
icon = Icon(icon_name='edit-duplicate', xo_color=color,
pixel_size=style.SMALL_ICON_SIZE)
menu_item.set_image(icon)
- menu_item.connect('activate', self.__duplicate_activate_cb)
+ menu_item.connect('clicked', self.__duplicate_activate_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = MenuItem(_('Send to'), 'document-send')
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
friends_menu = FriendsMenu()
friends_menu.connect('friend-selected', self.__friend_selected_cb)
- menu_item.set_submenu(friends_menu)
+ friends_menu.set_parent(menu_item)
+ friends_menu.set_position(Gtk.PositionType.RIGHT)
+ friends_menu.set_has_arrow(False)
+ menu_item.connect('clicked', lambda btn, m=friends_menu: m.popup())
if detail is True:
menu_item = MenuItem(_('View Details'), 'go-right')
- menu_item.connect('activate', self.__detail_activate_cb)
+ menu_item.connect('clicked', self.__detail_activate_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
menu_item = MenuItem(_('Erase'), 'list-remove')
- menu_item.connect('activate', self.__erase_activate_cb)
+ menu_item.connect('clicked', self.__erase_activate_cb)
self.menu.append(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
def __get_uid_list_cb(self):
return [self._metadata['uid']]
def __copy_to_project_activated_cb(self, menu_item):
self.emit('choose-project', self._metadata)
- self.destroy()
+ self.popdown()
def __open_project_activate_cb(self, menu_item):
self._journalactivity.project_view_activated_cb(
list_view=None,
metadata=self._metadata)
+ self.popdown()
def __start_activate_cb(self, menu_item):
misc.resume(self._metadata,
alert_window=journalwindow.get_journal_window())
+ self.popdown()
def __duplicate_activate_cb(self, menu_item):
try:
@@ -181,6 +200,7 @@ def __duplicate_activate_cb(self, menu_item):
self.emit('volume-error',
_('Error while copying the entry. %s') % e.strerror,
_('Error'))
+ self.popdown()
def __erase_activate_cb(self, menu_item):
alert = Alert()
@@ -190,13 +210,14 @@ def __erase_activate_cb(self, menu_item):
% self._metadata['title']
icon = Icon(icon_name='dialog-cancel')
alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
- icon.show()
+ icon.set_visible(True)
ok_icon = Icon(icon_name='dialog-ok')
alert.add_button(Gtk.ResponseType.OK, erase_string, ok_icon)
- ok_icon.show()
+ ok_icon.set_visible(True)
alert.connect('response', self.__erase_alert_response_cb)
journalwindow.get_journal_window().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
+ self.popdown()
def __erase_alert_response_cb(self, alert, response_id):
journalwindow.get_journal_window().remove_alert(alert)
@@ -209,6 +230,7 @@ def __erase_alert_response_cb(self, alert, response_id):
def __detail_activate_cb(self, menu_item):
self.emit('detail-clicked', self._metadata['uid'])
+ self.popdown()
def __volume_error_cb(self, menu_item, message, severity):
self.emit('volume-error', message, severity)
@@ -233,6 +255,7 @@ def __friend_selected_cb(self, menu_item, buddy):
filetransfer.start_transfer(buddy, file_name, title, description,
mime_type)
+ self.popdown()
def popup(self, immediate=False, state=None):
if self._journalactivity.get_list_view().is_dragging():
@@ -241,7 +264,7 @@ def popup(self, immediate=False, state=None):
Palette.popup(self, immediate)
-class CopyMenu(Gtk.PopoverMenu):
+class CopyMenu(Gtk.Popover):
__gtype_name__ = 'JournalCopyMenu'
__gsignals__ = {
@@ -250,7 +273,12 @@ class CopyMenu(Gtk.PopoverMenu):
}
def __init__(self, journalactivity, get_uid_list_cb):
- Gtk.PopoverMenu.__init__(self)
+ Gtk.Popover.__init__(self)
+ self.add_css_class('palette-popover')
+ self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
+
CopyMenuBuilder(journalactivity, get_uid_list_cb,
self.__volume_error_cb, self)
@@ -276,44 +304,38 @@ def __init__(self, journalactivity, get_uid_list_cb, __volume_error_cb,
self._create_menu_items()
def _create_menu_items(self):
- self.g_menu = Gio.Menu.new()
- application = Gio.Application.get_default()
+ vbox = getattr(self._menu, '_vbox', self._menu)
+
if self._add_clipboard_menu:
- copy_action = Gio.SimpleAction.new("copy_clipboard", None)
- application.add_action(copy_action)
clipboard_menu = ClipboardMenu(self._get_uid_list_cb)
clipboard_menu.set_image(Icon(icon_name='toolbar-edit',
pixel_size=style.SMALL_ICON_SIZE))
clipboard_menu.connect('volume-error', self.__volume_error_cb)
- self.g_menu.append_item(clipboard_menu)
- clipboard_menu.show()
+ vbox.append(clipboard_menu)
+ clipboard_menu.set_visible(True)
if self._journalactivity.get_mount_point() != '/':
color = profile.get_color()
- volume_action = Gio.SimpleAction.new("copy_volume", None)
- application.add_action(volume_action)
journal_menu = VolumeMenu(self._journalactivity,
self._get_uid_list_cb, _('Journal'), '/')
journal_menu.set_image(Icon(icon_name='activity-journal',
xo_color=color,
pixel_size=style.SMALL_ICON_SIZE))
journal_menu.connect('volume-error', self.__volume_error_cb)
- self.g_menu.append_item(journal_menu)
- journal_menu.show()
+ vbox.append(journal_menu)
+ journal_menu.set_visible(True)
documents_path = model.get_documents_path()
if documents_path is not None and \
self._journalactivity.get_mount_point() != documents_path:
- volume_action = Gio.SimpleAction.new("copy_volume", None)
- application.add_action(volume_action)
documents_menu = VolumeMenu(self._journalactivity,
self._get_uid_list_cb, _('Documents'),
documents_path)
documents_menu.set_image(Icon(icon_name='user-documents',
pixel_size=style.SMALL_ICON_SIZE))
documents_menu.connect('volume-error', self.__volume_error_cb)
- self.g_menu.append_item(documents_menu)
- documents_menu.show()
+ vbox.append(documents_menu)
+ documents_menu.set_visible(True)
volume_monitor = Gio.VolumeMonitor.get()
self._volumes = {}
@@ -331,14 +353,15 @@ def _create_menu_items(self):
if hasattr(account, 'get_shared_journal_entry'):
entry = account.get_shared_journal_entry()
if hasattr(entry, 'get_share_menu'):
- self.g_menu.append(entry.get_share_menu(
- self._get_uid_list_cb), None)
- self._menu.set_menu_model(self.g_menu)
+ menu_item = entry.get_share_menu(self._get_uid_list_cb)
+ if menu_item:
+ vbox.append(menu_item)
+ menu_item.set_visible(True)
def update_mount_point(self):
- for menu_item in self._menu.get_children():
- if isinstance(menu_item, MenuItem):
- self._menu.remove(menu_item)
+ vbox = getattr(self._menu, '_vbox', self._menu)
+ while vbox.get_first_child():
+ vbox.get_first_child().unparent()
self._create_menu_items()
def __mount_added_cb(self, volume_monitor, mount):
@@ -353,20 +376,21 @@ def _add_mount(self, mount):
volume_menu = VolumeMenu(self._journalactivity,
self._get_uid_list_cb, mount.get_name(),
mount.get_root().get_path())
- icon_name = misc.get_mount_icon_name(mount, Gtk.IconSize.MENU)
+ icon_name = misc.get_mount_icon_name(mount, Gtk.IconSize.NORMAL)
icon = Icon(pixel_size=style.SMALL_ICON_SIZE,
icon_name=icon_name,
xo_color=get_mount_color(mount))
volume_menu.set_image(icon)
volume_menu.connect('volume-error', self.__volume_error_cb)
- self._menu.append(volume_menu)
+ vbox = getattr(self._menu, '_vbox', self._menu)
+ vbox.append(volume_menu)
self._volumes[mount.get_root().get_path()] = volume_menu
- volume_menu.show()
+ volume_menu.set_visible(True)
def __mount_removed_cb(self, volume_monitor, mount):
volume_menu = self._volumes[mount.get_root().get_path()]
- self._menu.remove(volume_menu)
+ volume_menu.unparent()
del self._volumes[mount.get_root().get_path()]
@@ -383,10 +407,10 @@ def __init__(self, journalactivity, get_uid_list_cb, label, mount_point):
self._get_uid_list_cb = get_uid_list_cb
self._journalactivity = journalactivity
self._mount_point = mount_point
- self.set_detailed_action("copy_volume")
- self.connect('activate', self.__copy_to_volume_cb)
+ self.connect('clicked', self.__copy_to_volume_cb)
def __copy_to_volume_cb(self, menu_item):
+ self._close_menus()
uid_list = self._get_uid_list_cb()
if len(uid_list) == 1:
uid = uid_list[0]
@@ -430,6 +454,9 @@ def _perform_copy(self, metadata):
logging.exception('Error while copying the entry. %s',
e.strerror)
+ def _close_menus(self):
+ _popdown_palette_chain(self)
+
class ClipboardMenu(MenuItem):
__gtype_name__ = 'JournalClipboardMenu'
@@ -441,35 +468,34 @@ class ClipboardMenu(MenuItem):
def __init__(self, get_uid_list_cb):
MenuItem.__init__(self, _('Clipboard'))
- self.set_detailed_action("copy_clipboard")
-
self._temp_file_path = None
self._get_uid_list_cb = get_uid_list_cb
- self.connect('activate', self.__copy_to_clipboard_cb)
+ self.connect('clicked', self.__copy_to_clipboard_cb)
def __copy_to_clipboard_cb(self, menu_item):
- clipboard = Gtk.Display.get_default().get_clipboard()
+ self._close_menus()
+ clipboard = Gdk.Display.get_default().get_clipboard()
uid_list = self._get_uid_list_cb()
if len(uid_list) == 1:
uid = uid_list[0]
- file_path = 'file://' + model.get_file(uid)
- if not file_path or not os.path.exists(file_path):
+ real_path = model.get_file(uid)
+ if not real_path or not os.path.exists(real_path):
logging.warn('Entries without a file cannot be copied.')
self.emit('volume-error',
_('Entries without a file cannot be copied.'),
_('Warning'))
return
- try:
- value = GObject.Value(GObject.TYPE_STRING, file_path)
- clipboard.set(value)
- except:
- SugarExt.clipboard_set_with_data(
- 'text/uri-list',
- file_path)
+ uri = GLib.filename_to_uri(real_path, None) + '\r\n'
+ bytes_data = GLib.Bytes.new(uri.encode('utf-8'))
+ provider = Gdk.ContentProvider.new_for_bytes('text/uri-list', bytes_data)
+ clipboard.set_content(provider)
+
+ def _close_menus(self):
+ _popdown_palette_chain(self)
-class FriendsMenu(Gtk.PopoverMenu):
+class FriendsMenu(Gtk.Popover):
__gtype_name__ = 'JournalFriendsMenu'
__gsignals__ = {
@@ -478,9 +504,13 @@ class FriendsMenu(Gtk.PopoverMenu):
}
def __init__(self):
- Gtk.PopoverMenu.__init__(self)
+ Gtk.Popover.__init__(self)
+ self.add_css_class('palette-popover')
+
+ self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
- self.g_menu = Gio.Menu.new()
if filetransfer.file_transfer_available():
friends_model = friends.get_model()
for friend in friends_model:
@@ -488,57 +518,60 @@ def __init__(self):
menu_item = MenuItem(text_label=friend.get_nick(),
icon_name='computer-xo',
xo_color=friend.get_color())
- menu_item.connect('activate', self.__item_activate_cb,
+ menu_item.connect('clicked', self.__item_activate_cb,
friend)
- self.g_menu.append_item(menu_item)
- menu_item.show()
+ self._vbox.append(menu_item)
+ menu_item.set_visible(True)
- if not self.g_menu.get_n_items():
+ if not self._vbox.get_first_child():
menu_item = MenuItem(_('No friends present'))
menu_item.set_sensitive(False)
- self.g_menu.append(menu_item)
- menu_item.show()
+ self._vbox.append(menu_item)
+ menu_item.set_visible(True)
else:
menu_item = MenuItem(_('No valid connection found'))
menu_item.set_sensitive(False)
- self.g_menu.append(menu_item)
- menu_item.show()
-
- self.set_menu_model(self.g_menu)
+ self._vbox.append(menu_item)
+ menu_item.set_visible(True)
def __item_activate_cb(self, menu_item, friend):
self.emit('friend-selected', friend)
+ self._close_menus()
+ def _close_menus(self):
+ _popdown_palette_chain(self)
-class StartWithMenu(Gtk.PopoverMenu):
+
+class StartWithMenu(Gtk.Popover):
__gtype_name__ = 'JournalStartWithMenu'
def __init__(self, metadata):
- Gtk.PopoverMenu.__init__(self)
+ Gtk.Popover.__init__(self)
+ self.add_css_class('palette-popover')
self._metadata = metadata
- self.g_menu = Gio.Menu.new()
+ self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
+
for activity_info in misc.get_activities(metadata):
- menu_item = MenuItem(activity_info.get_name())
- menu_item.set_image(Icon(file=activity_info.get_icon(),
- pixel_size=style.SMALL_ICON_SIZE))
- menu_item.connect('activate', self.__item_activate_cb,
+ menu_item = MenuItem(text_label=activity_info.get_name(),
+ file_name=activity_info.get_icon())
+ menu_item.connect('clicked', self.__item_activate_cb,
activity_info.get_bundle_id())
- self.g_menu.append_item(menu_item)
- menu_item.show()
+ self._vbox.append(menu_item)
+ menu_item.set_visible(True)
- if not self.g_menu.get_n_items():
+ if not self._vbox.get_first_child():
if metadata.get('activity_id', ''):
resume_label = _('No activity to resume entry')
else:
resume_label = _('No activity to start entry')
menu_item = MenuItem(resume_label)
menu_item.set_sensitive(False)
- self.append_item(menu_item)
- menu_item.show()
-
- self.set_menu_model(self.g_menu)
+ self._vbox.append(menu_item)
+ menu_item.set_visible(True)
def __item_activate_cb(self, menu_item, service_name):
mime_type = self._metadata.get('mime_type', '')
@@ -547,6 +580,10 @@ def __item_activate_cb(self, menu_item, service_name):
mime_registry.set_default_activity(mime_type, service_name)
misc.resume(self._metadata, bundle_id=service_name,
alert_window=journalwindow.get_journal_window())
+ self._close_menus()
+
+ def _close_menus(self):
+ _popdown_palette_chain(self)
class BuddyPalette(Palette):
@@ -579,7 +616,7 @@ def __init__(self, journalactivity,
uid_list,
alert_title, alert_message,
operation_cb):
- GObject.GObject.__init__(self)
+ super().__init__()
self._journalactivity = journalactivity
@@ -603,17 +640,17 @@ def __show_confirmation_alert_internal(self):
stop_icon = Icon(icon_name='dialog-cancel')
self._confirmation_alert.add_button(Gtk.ResponseType.CANCEL,
_('Stop'), stop_icon)
- stop_icon.show()
+ stop_icon.set_visible(True)
ok_icon = Icon(icon_name='dialog-ok')
self._confirmation_alert.add_button(Gtk.ResponseType.OK,
_('Continue'), ok_icon)
- ok_icon.show()
+ ok_icon.set_visible(True)
self._journalactivity.add_alert(self._confirmation_alert)
self._confirmation_alert.connect('response',
self.__confirmation_response_cb)
- self._confirmation_alert.show()
+ self._confirmation_alert.set_visible(True)
def __confirmation_response_cb(self, alert, response):
if response == Gtk.ResponseType.CANCEL:
diff --git a/src/jarabe/journal/projectview.py b/src/jarabe/journal/projectview.py
index afd2751b5e..9dd5a8416b 100644
--- a/src/jarabe/journal/projectview.py
+++ b/src/jarabe/journal/projectview.py
@@ -16,8 +16,10 @@
from gettext import gettext as _
-from gi.repository import GObject
from gi.repository import Gtk
+from gi.repository import GObject
+from gi.repository import Pango
+from gi.repository import Gdk
from gi.repository import Gio
from jarabe.journal.expandedentry import TextView
@@ -35,38 +37,45 @@
_SERVICE_INTERFACE = 'org.laptop.Activity'
-class ProjectView(Gtk.EventBox, BaseExpandedEntry):
+class ProjectView(Gtk.Box, BaseExpandedEntry):
__gsignals__ = {
'go-back-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
}
def __init__(self, **kwargs):
- Gtk.EventBox.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.VERTICAL, **kwargs)
BaseExpandedEntry.__init__(self)
self.project_metadata = None
self._service = None
self._activity_id = None
self._project = None
- self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color())
+
+ self.add_css_class('project-view-bg')
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self.add(self._vbox)
+ self.append(self._vbox)
back_bar = BackBar()
- back_bar.connect('button-release-event',
- self.__back_bar_release_event_cb)
- self._vbox.pack_start(back_bar, False, True, 0)
+ click = Gtk.GestureClick()
+ click.connect('released', self.__back_bar_release_event_cb)
+ back_bar.add_controller(click)
+ self._vbox.append(back_bar)
header = self.create_header()
- self._vbox.pack_start(header, False, False, style.DEFAULT_SPACING * 2)
- header.show()
+ header.set_margin_bottom(style.DEFAULT_SPACING * 2)
+ self._vbox.append(header)
+ header.set_visible(True)
description_box, self._description = self._create_description()
- self._vbox.pack_start(description_box, False, True,
- style.DEFAULT_SPACING / 3)
+ description_box.set_vexpand(True)
+ description_box.set_margin_bottom(int(style.DEFAULT_SPACING / 3))
+ self._vbox.append(description_box)
- self._title.connect('focus-out-event', self._title_focus_out_event_cb)
+ # Gtk.Entry doesn't have focus-out-event, we use Gtk.EventControllerFocus
+ focus_controller = Gtk.EventControllerFocus()
+ focus_controller.connect('leave', self._title_focus_out_event_cb)
+ self._title.add_controller(focus_controller)
settings = Gio.Settings.new('org.sugarlabs.user')
icon_color = settings.get_string('color')
@@ -74,7 +83,7 @@ def __init__(self, **kwargs):
self._icon = Icon(icon_name='project-box',
pixel_size=style.MEDIUM_ICON_SIZE)
self._icon.xo_color = XoColor(icon_color)
- self._icon_box.pack_start(self._icon, False, False, 0)
+ self._icon_box.append(self._icon)
def get_vbox(self):
return self._vbox
@@ -89,7 +98,7 @@ def get_list_view(self):
def get_mount_point(self):
return '/'
- def __back_bar_release_event_cb(self, back_bar, event):
+ def __back_bar_release_event_cb(self, gesture, n_press, x, y):
self.emit('go-back-clicked')
return False
@@ -97,19 +106,20 @@ def set_project_metadata(self, project_metadata):
self.project_metadata = project_metadata
description = project_metadata.get('description', '')
- self._description.get_buffer().set_text(description)
+ self._description.get_buffer().set_text(description, len(description))
self._title.set_text(project_metadata.get('title', ''))
- def _title_focus_out_event_cb(self, entry, event):
+ def _title_focus_out_event_cb(self, controller):
self._update_entry()
def _create_description(self):
widget = TextView()
- widget.connect('focus-out-event',
- self._description_tags_focus_out_event_cb)
+ focus_controller = Gtk.EventControllerFocus()
+ focus_controller.connect('leave', self._description_tags_focus_out_event_cb)
+ widget.add_controller(focus_controller)
return self._create_scrollable(widget, label=_('Description:')), widget
- def _description_tags_focus_out_event_cb(self, text_view, event):
+ def _description_tags_focus_out_event_cb(self, controller):
self._update_entry()
def _update_entry(self):
@@ -117,7 +127,7 @@ def _update_entry(self):
bounds = self._description.get_buffer().get_bounds()
old_description = self.project_metadata.get('description', None)
new_description = self._description.get_buffer().get_text(
- bounds[0], bounds[1], include_hidden_chars=False)
+ bounds[0], bounds[1], False)
if old_description != new_description:
self.project_metadata['description'] = new_description
@@ -132,22 +142,24 @@ def _update_entry(self):
def _create_scrollable(self, widget, label=None):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.props.spacing = style.DEFAULT_SPACING
+ vbox.set_spacing(style.DEFAULT_SPACING)
if label is not None:
text = Gtk.Label()
text.set_markup('%s' % (
style.COLOR_BUTTON_GREY.get_html(), label))
- halign = Gtk.Alignment.new(0, 0, 0, 0)
- halign.add(text)
- vbox.pack_start(halign, False, False, 0)
+ halign = Gtk.Box()
+ halign.set_halign(Gtk.Align.START)
+ halign.append(text)
+ vbox.append(halign)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
- scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
- scrolled_window.add(widget)
- vbox.pack_start(scrolled_window, True, True, 0)
+ scrolled_window.set_has_frame(True)
+ scrolled_window.set_child(widget)
+ scrolled_window.set_vexpand(True)
+ vbox.append(scrolled_window)
return vbox
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index 04a3336cb3..615a8b2552 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2011, One Laptop Per Child
+# Copyright (C) 2007, 2011, One Laptop Per Child
# Copyright (C) 2014, Ignacio Rodriguez
#
# This program is free software: you can redistribute it and/or modify
@@ -40,7 +40,6 @@
from jarabe.journal.misc import get_mount_color
from jarabe.view.palettes import VolumePalette
-
_JOURNAL_0_METADATA_DIR = '.olpc.store'
@@ -152,7 +151,7 @@ def _convert_entry(root, document):
metadata_fname)
if not os.path.exists(metadata_path):
(fh, fn) = tempfile.mkstemp(dir=root)
- os.write(fh, json.dumps(metadata))
+ os.write(fh, json.dumps(metadata).encode('utf-8'))
os.close(fh)
os.rename(fn, metadata_path)
@@ -161,7 +160,7 @@ def _convert_entry(root, document):
os.path.join(root, filename), metadata)
-class VolumesToolbar(Gtk.Toolbar):
+class VolumesToolbar(Gtk.Box):
__gtype_name__ = 'VolumesToolbar'
__gsignals__ = {
@@ -172,24 +171,43 @@ class VolumesToolbar(Gtk.Toolbar):
}
def __init__(self):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self._mount_added_hid = None
self._mount_removed_hid = None
+ self._children = []
button = JournalButton()
button.connect('toggled', self._button_toggled_cb)
- self.insert(button, 0)
- button.show()
+ self.append(button)
+ self._children.append(button)
+ button.set_visible(True)
self._volume_buttons = [button]
- self.connect('destroy', self.__destroy_cb)
-
GLib.idle_add(self._set_up_volumes)
- def __destroy_cb(self, widget):
+ def insert(self, widget, position):
+ if position == -1 or position >= len(self._children):
+ self.append(widget)
+ self._children.append(widget)
+ elif position == 0:
+ self.prepend(widget)
+ self._children.insert(0, widget)
+ else:
+ sibling = self._children[position - 1]
+ self.insert_child_after(widget, sibling)
+ self._children.insert(position, widget)
+
+ def get_item_index(self, widget):
+ if widget in self._children:
+ return self._children.index(widget)
+ return -1
+
+ def do_unroot(self):
volume_monitor = Gio.VolumeMonitor.get()
- volume_monitor.disconnect(self._mount_added_hid)
- volume_monitor.disconnect(self._mount_removed_hid)
+ if self._mount_added_hid:
+ volume_monitor.disconnect(self._mount_added_hid)
+ if self._mount_removed_hid:
+ volume_monitor.disconnect(self._mount_removed_hid)
def _set_up_volumes(self):
self._set_up_documents_button()
@@ -208,15 +226,15 @@ def _set_up_documents_button(self):
documents_path = model.get_documents_path()
if documents_path is not None:
button = DocumentsButton(documents_path)
- button.props.group = self._volume_buttons[0]
+ button.set_group(self._volume_buttons[0])
button.set_palette(Palette(_('Documents')))
button.connect('toggled', self._button_toggled_cb)
- button.show()
+ button.set_visible(True)
position = self.get_item_index(self._volume_buttons[-1]) + 1
self.insert(button, position)
self._volume_buttons.append(button)
- self.show()
+ self.set_visible(True)
def __mount_added_cb(self, volume_monitor, mount):
self._add_button(mount)
@@ -227,23 +245,27 @@ def __mount_removed_cb(self, volume_monitor, mount):
def _add_button(self, mount):
logging.debug('VolumeToolbar._add_button: %r', mount.get_name())
+ path = mount.get_root().get_path()
+ if path and (path.startswith('/snap/') or path.startswith('/run/')):
+ return
+
if os.path.exists(os.path.join(mount.get_root().get_path(),
_JOURNAL_0_METADATA_DIR)):
logging.debug('Convert DS-0 Journal entries: starting conversion')
GLib.idle_add(_convert_entries, mount.get_root().get_path())
button = VolumeButton(mount)
- button.props.group = self._volume_buttons[0]
+ button.set_group(self._volume_buttons[0])
button.connect('toggled', self._button_toggled_cb)
button.connect('volume-error', self.__volume_error_cb)
position = self.get_item_index(self._volume_buttons[-1]) + 1
self.insert(button, position)
- button.show()
+ button.set_visible(True)
self._volume_buttons.append(button)
- if len(self.get_children()) > 1:
- self.show()
+ if len(self._children) > 1:
+ self.set_visible(True)
def __volume_error_cb(self, button, strerror, severity):
self.emit('volume-error', strerror, severity)
@@ -254,24 +276,29 @@ def _button_toggled_cb(self, button):
def _get_button_for_mount(self, mount):
mount_point = mount.get_root().get_path()
- for button in self.get_children():
- if button.mount_point == mount_point:
+ for button in self._children:
+ if getattr(button, 'mount_point', None) == mount_point:
return button
logging.error('Couldnt find button with mount_point %r', mount_point)
return None
def _remove_button(self, mount):
button = self._get_button_for_mount(mount)
- self._volume_buttons.remove(button)
- self.remove(button)
- self.get_children()[0].props.active = True
+ if button:
+ self._volume_buttons.remove(button)
+ self.remove(button)
+ self._children.remove(button)
+ if self._children:
+ # We expect the first button to be JournalButton which can be activated
+ self._children[0].props.active = True
- if len(self.get_children()) < 2:
- self.hide()
+ if len(self._children) < 2:
+ self.set_visible(False)
def set_active_volume(self, mount):
button = self._get_button_for_mount(mount)
- button.props.active = True
+ if button:
+ button.props.active = True
class BaseButton(RadioToolButton):
@@ -281,18 +308,19 @@ class BaseButton(RadioToolButton):
}
def __init__(self, mount_point):
- RadioToolButton.__init__(self)
+ super().__init__()
self.mount_point = mount_point
- self.drag_dest_set(Gtk.DestDefaults.ALL,
- [Gtk.TargetEntry.new('journal-object-id', 0, 0)],
- Gdk.DragAction.COPY)
- self.connect('drag-data-received', self._drag_data_received_cb)
+ self._drop_target = Gtk.DropTarget.new(GObject.TYPE_STRING, Gdk.DragAction.COPY)
+ self._drop_target.connect('drop', self._drag_data_received_cb)
+ self.add_controller(self._drop_target)
- def _drag_data_received_cb(self, widget, drag_context, x, y,
- selection_data, info, timestamp):
- object_id = selection_data.get_data()
+ def _drag_data_received_cb(self, drop_target, value, x, y):
+ object_id = value
+ if not object_id:
+ return False
+
metadata = model.get(object_id)
file_path = model.get_file(metadata['uid'])
if not file_path or not os.path.exists(file_path):
@@ -300,15 +328,17 @@ def _drag_data_received_cb(self, widget, drag_context, x, y,
self.emit('volume-error',
_('Entries without a file cannot be copied.'),
_('Warning'))
- return
+ return False
try:
model.copy(metadata, self.mount_point)
+ return True
except IOError as e:
logging.exception('Error while copying the entry. %s', e.strerror)
self.emit('volume-error',
_('Error while copying the entry. %s') % e.strerror,
_('Error'))
+ return False
class VolumeButton(BaseButton):
@@ -318,15 +348,12 @@ def __init__(self, mount):
mount_point = mount.get_root().get_path()
BaseButton.__init__(self, mount_point)
- self.props.icon_name = get_mount_icon_name(mount,
- Gtk.IconSize.LARGE_TOOLBAR)
- # TODO: retrieve the colors from the owner of the device
- self.props.xo_color = get_mount_color(self._mount)
+ self.props.icon_name = get_mount_icon_name(mount, 24)
+ if self.get_icon_widget():
+ self.get_icon_widget().props.xo_color = get_mount_color(self._mount)
def create_palette(self):
palette = VolumePalette(self._mount)
- # palette.props.invoker = FrameWidgetInvoker(self)
- # palette.set_group_id('frame')
return palette
@@ -336,7 +363,8 @@ def __init__(self):
BaseButton.__init__(self, mount_point='/')
self.props.icon_name = 'activity-journal'
- self.props.xo_color = profile.get_color()
+ if self.get_icon_widget():
+ self.get_icon_widget().props.xo_color = profile.get_color()
def create_palette(self):
palette = JournalButtonPalette(self)
@@ -349,19 +377,23 @@ def __init__(self, mount):
Palette.__init__(self, _('Journal'))
grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL,
- margin=style.DEFAULT_SPACING,
+ margin_top=style.DEFAULT_SPACING,
+ margin_bottom=style.DEFAULT_SPACING,
+ margin_start=style.DEFAULT_SPACING,
+ margin_end=style.DEFAULT_SPACING,
row_spacing=style.DEFAULT_SPACING)
self.set_content(grid)
- grid.show()
+ grid.set_visible(True)
self._progress_bar = Gtk.ProgressBar()
- grid.add(self._progress_bar)
- self._progress_bar.show()
+ grid.attach(self._progress_bar, 0, 0, 1, 1)
+ self._progress_bar.set_visible(True)
self._free_space_label = Gtk.Label()
- self._free_space_label.set_alignment(0.5, 0.5)
- grid.add(self._free_space_label)
- self._free_space_label.show()
+ self._free_space_label.set_halign(Gtk.Align.CENTER)
+ self._free_space_label.set_valign(Gtk.Align.CENTER)
+ grid.attach(self._free_space_label, 0, 1, 1, 1)
+ self._free_space_label.set_visible(True)
self.connect('popup', self.__popup_cb)
@@ -382,4 +414,5 @@ def __init__(self, documents_path):
BaseButton.__init__(self, mount_point=documents_path)
self.props.icon_name = 'user-documents'
- self.props.xo_color = profile.get_color()
+ if self.get_icon_widget():
+ self.get_icon_widget().props.xo_color = profile.get_color()
diff --git a/src/jarabe/main.py b/src/jarabe/main.py
index 8bfca3e024..66c6dde555 100755
--- a/src/jarabe/main.py
+++ b/src/jarabe/main.py
@@ -16,8 +16,8 @@
from sugar4 import logger
-logger.cleanup()
-logger.start('shell')
+#logger.cleanup()
+#logger.start('shell')
import logging
@@ -25,7 +25,6 @@
import os
import sys
-import subprocess
import shutil
# Disable overlay scrolling before GTK is loaded
@@ -34,28 +33,29 @@
import gettext
from jarabe import config
-# NOTE: This needs to happen early because some modules register
-# translatable strings in the module scope.
+# must happen early; some modules register translatable strings at import time
gettext.bindtextdomain('sugar', config.locale_path)
-gettext.bindtextdomain('sugar-toolkit-gtk3', config.locale_path)
+gettext.bindtextdomain('sugar-toolkit-gtk4', config.locale_path)
gettext.textdomain('sugar')
-# publish sugar version in the environment
os.environ['SUGAR_VERSION'] = config.version
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
-# define the versions of used libraries that are required
import gi
gi.require_version('Gtk', '4.0')
-gi.require_version('Gst', '1.0')
-gi.require_version('SugarExt', '2.0')
+try:
+ gi.require_version('Gst', '1.0')
+ from gi.repository import Gst
+ HAS_GST = True
+except ValueError:
+ HAS_GST = False
+ logging.warning("Gst 1.0 not available. Running without GStreamer.")
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import Gtk
-from gi.repository import Gst
from sugar4 import env
@@ -82,8 +82,6 @@
from jarabe import testrunner
from jarabe.model import brightness
-
-_mutter_process = None
_window_manager_started = False
_starting_desktop = False
@@ -129,8 +127,7 @@ def setup_file_transfer_cb():
def _complete_desktop_startup():
launcher.setup()
- GLib.idle_add(setup_frame_cb)
- GLib.idle_add(setup_keyhandler_cb)
+ setup_frame_cb()
GLib.idle_add(setup_gesturehandler_cb)
GLib.idle_add(setup_journal_cb)
GLib.idle_add(setup_notification_service_cb)
@@ -142,66 +139,46 @@ def _complete_desktop_startup():
testrunner.check_environment()
-def __window_manager_failed_cb(fd, condition):
- logging.error('window manager did fail, restarting')
- GLib.source_remove(_weston_sid)
- GLib.timeout_add(1000, _restart_window_manager)
- return False
-
-
-def _restart_window_manager():
- global _mutter_process, _mutter_sid
-
- #_mutter_process = subprocess.Popen(
- # ['mutter', '--wayland'],
- _mutter_process = subprocess.Popen(
- ['dbus-run-session', 'mutter', '--wayland'],
- stdout=subprocess.PIPE)
- _mutter_sid = GLib.io_add_watch(_mutter_process.stdout, GLib.IO_HUP,
- __window_manager_failed_cb)
- return False
-
-
def _start_window_manager():
global _cursor_theme_settings, _cursor_theme
_cursor_theme_settings = Gio.Settings.new('org.gnome.desktop.interface')
_cursor_theme = _cursor_theme_settings.get_string('cursor-theme')
- _cursor_theme_settings.set_string('cursor-theme', 'sugar')
-
- #_restart_window_manager()
- global _mutter_process, _mutter_sid
-
- _mutter_process = subprocess.Popen(
- ['mutter', '--wayland'],
- stdout=subprocess.PIPE)
- _mutter_sid = GLib.io_add_watch(_mutter_process.stdout, GLib.IO_HUP,
- __window_manager_failed_cb)
global _starting_desktop
if _starting_desktop:
_complete_desktop_startup()
-def _stop_window_manager():
- _cursor_theme_settings.set_string('cursor-theme', _cursor_theme)
- _mutter_process.terminate()
-
-
-def _begin_desktop_startup(shell):
- logging.warning("Running begin desktop")
+def _begin_desktop_startup():
+ from jarabe.model import shell as shell_model
global _starting_desktop
_starting_desktop = True
-
UIService()
+ shell_instance = shell_model.get_model()
+ _setup_main_window(shell_instance)
+
home_window = homewindow.get_instance()
- shell.add_window(home_window)
- home_window.show()
+
+ setup_keyhandler_cb()
+
+ shell_instance.stack.add_named(home_window, "home")
+ shell_instance.stack.add_named(shell_instance.compositor, "activity")
+ shell_instance.stack.set_visible_child_name("home")
+
+ os.environ["WAYLAND_DISPLAY"] = "wayland-sugar"
+ if "DISPLAY" in os.environ:
+ del os.environ["DISPLAY"]
session_manager = get_session_manager()
- session_manager.start()
+
+ _complete_desktop_startup()
+
+ # Clear the startup 'wait' cursor now that the desktop is ready.
+ # busy() was called in HomeWindow.__init__; unbusy() matches it.
+ GLib.idle_add(home_window.unbusy)
def __intro_window_done_cb(window):
@@ -214,14 +191,13 @@ def __intro_window_done_cb(window):
def cleanup_temporary_files():
try:
- # Remove temporary files. See http://bugs.sugarlabs.org/ticket/1876
+ # see http://bugs.sugarlabs.org/ticket/1876
data_dir = os.path.join(env.get_profile_path(), 'data')
shutil.rmtree(data_dir, ignore_errors=True)
os.makedirs(data_dir)
except OSError as e:
- # temporary files cleanup is not critical; it should not prevent
- # sugar from starting if (for example) the disk is full or read-only.
- print('temporary files cleanup failed: %s' % e)
+ # non-fatal: full or read-only disk should not block startup
+ logging.warning('temporary files cleanup failed: %s', e)
def setup_timezone():
@@ -236,8 +212,8 @@ def setup_fonts():
face = settings.get_string('default-face')
size = settings.get_double('default-size')
- settings = Gio.Settings.new('org.gnome.desktop.interface')
- settings.set_string("font-name", "%s %f" % (face, size))
+ gtk_settings = Gtk.Settings.get_default()
+ gtk_settings.set_property('gtk-font-name', '%s %f' % (face, size))
def setup_proxy():
@@ -279,23 +255,45 @@ def setup_proxy():
def setup_theme():
- settings = Gio.Settings.new('org.gnome.desktop.interface')
+ from gi.repository import Gdk
+ gtk_settings = Gtk.Settings.get_default()
sugar_theme = 'sugar-72'
if 'SUGAR_SCALING' in os.environ:
if os.environ['SUGAR_SCALING'] == '100':
sugar_theme = 'sugar-100'
- settings.set_string('gtk-theme', sugar_theme)
- settings.set_string('icon-theme', 'sugar')
- settings.set_int('cursor-blink-timeout', 3)
- #settings.set_property('gtk-button-images', True)
+ gtk_settings.set_property('gtk-theme-name', sugar_theme)
+ gtk_settings.set_property('gtk-icon-theme-name', 'sugar')
+ gtk_settings.set_property('gtk-cursor-blink-timeout', 3)
+ gtk_settings.set_property('gtk-overlay-scrolling', False)
+
+ icons_path = os.path.join(config.data_path, 'icons')
+ Gtk.IconTheme.get_for_display(
+ Gdk.Display.get_default()).add_search_path(icons_path)
+
+
+def _setup_main_window(shell_instance):
+ if not shell_instance._main_window:
+ shell_instance._main_window = Gtk.ApplicationWindow(application=shell_instance)
+ shell_instance._main_window.set_title("Sugar")
+
+ shell_instance._overlay = Gtk.Overlay()
+ shell_instance._overlay.set_child(shell_instance.stack)
+
+ shell_instance._main_window.set_child(shell_instance._overlay)
+ if os.environ.get('SUGAR_WINDOWED', '0') == '1':
+ shell_instance._main_window.set_default_size(1024, 768)
+ else:
+ shell_instance._main_window.fullscreen()
+ shell_instance._main_window.present()
def _start_intro(shell, start_on_age_page=False):
- window = IntroWindow(start_on_age_page=start_on_age_page)
+ _setup_main_window(shell)
+ intro_box = IntroWindow(start_on_age_page=start_on_age_page)
- shell.add_window(window)
- window.connect('done', __intro_window_done_cb)
- window.show()
+ shell.stack.add_named(intro_box, "intro")
+ shell.stack.set_visible_child_name("intro")
+ intro_box.connect('done', __intro_window_done_cb)
def _check_profile():
@@ -316,7 +314,22 @@ def _check_group_label():
def main(shell):
logging.warning("Running main")
- Gst.init(sys.argv)
+
+ # Create default profile directories if they do not exist
+ profile_path = env.get_profile_path()
+ for subdir in ['datastore', 'logs', 'data']:
+ path = os.path.join(profile_path, subdir)
+ if not os.path.exists(path):
+ try:
+ os.makedirs(path, 0o770)
+ except OSError as e:
+ logging.error('Failed to create directory %s: %s', path, e)
+
+ import signal
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+ if HAS_GST:
+ Gst.init(sys.argv)
cleanup_temporary_files()
@@ -327,32 +340,30 @@ def main(shell):
setup_theme()
setup_proxy()
- # this must be added early, so that it executes and unfreezes the screen
- # even when we initially get blocked on the intro screen
+ # must run early so the screen unfreezes even if blocked on intro
GLib.idle_add(unfreeze_screen_cb)
- if not _check_profile():
- _start_intro(shell)
- elif not _check_group_label():
- _start_intro(shell, start_on_age_page=True)
- else:
- _begin_desktop_startup()
-
GLib.idle_add(setup_cursortracker_cb)
sound.restore()
- #keyboard.setup()
brightness.get_instance()
sys.path.append(config.ext_path)
- try:
- Gtk.main()
- except KeyboardInterrupt:
- print('Ctrl+C pressed, exiting...')
+ if not _check_profile():
+ _start_intro(shell)
+ elif not _check_group_label():
+ _start_intro(shell, start_on_age_page=True)
+ else:
+ _begin_desktop_startup()
- _stop_window_manager()
+shell = shell.get_model()
+from sugar4.activity import activityfactory
+def _get_compositor_fd():
+ if shell.compositor is not None:
+ return shell.compositor.get_client_socket_fd()
+ return -1
+activityfactory.set_compositor_fd_getter(_get_compositor_fd)
-shell = shell.get_model()
shell.connect('activate', main)
shell.run(None)
diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py
index 78ca8d1ed2..473679c139 100644
--- a/src/jarabe/model/adhoc.py
+++ b/src/jarabe/model/adhoc.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 One Laptop per Child
+# Copyright (C) 2010 One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,7 +25,6 @@
from jarabe.model.network import Settings
from jarabe.model.network import IP4Config
-
_adhoc_manager_instance = None
@@ -57,7 +56,7 @@ class AdHocManager(GObject.GObject):
_CHANNEL_11 = 11
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._bus = dbus.SystemBus()
self._device = None
diff --git a/src/jarabe/model/brightness.py b/src/jarabe/model/brightness.py
index e046d4f1b4..dde19fd92f 100644
--- a/src/jarabe/model/brightness.py
+++ b/src/jarabe/model/brightness.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2015 Martin Abente Lahaye
+# Copyright (C) 2015 Martin Abente Lahaye
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
from gi.repository import Gio
from gi.repository import GObject
-
_instance = None
@@ -39,7 +38,7 @@ class Brightness(GObject.GObject):
changed_signal = GObject.Signal('changed', arg_types=([int]))
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._path = None
self._helper_path = None
self._max_brightness = None
@@ -102,7 +101,6 @@ def __save_timeout_cb(self, value):
return False
def set_brightness(self, value):
- # do not monitor the external change we are about to trigger
if self._monitor is not None:
if self._monitor_timeout_id is None:
self._monitor.handler_block(self._monitor_changed_hid)
@@ -117,7 +115,6 @@ def set_brightness(self, value):
self._monitor_timeout_id = GLib.timeout_add(
self._MONITOR_RATE * 2, self.__monitor_timeout_cb)
- # do not store every change while is still changing
if self._save_timeout_id is not None:
GLib.source_remove(self._save_timeout_id)
self._save_timeout_id = GLib.timeout_add(
diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py
index 6ae3299a9f..1ec41cb87d 100644
--- a/src/jarabe/model/buddy.py
+++ b/src/jarabe/model/buddy.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,8 @@
from gi.repository import GObject
import dbus
+import gi
+gi.require_version('TelepathyGLib', '0.12')
from gi.repository import TelepathyGLib
CONNECTION = TelepathyGLib.IFACE_CONNECTION
CONNECTION_STATUS_CONNECTED = TelepathyGLib.ConnectionStatus.CONNECTED
@@ -27,7 +29,6 @@
from jarabe.util.telepathy import connection_watcher
-
CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
_owner_instance = None
@@ -43,7 +44,7 @@ def __init__(self, **kwargs):
self._tags = None
self._current_activity = None
- GObject.GObject.__init__(self, **kwargs)
+ super().__init__(**kwargs)
def get_nick(self):
return self._nick
@@ -108,7 +109,7 @@ def __init__(self):
self.connect('notify::color', self.__property_changed_cb)
bus = dbus.SessionBus()
- bus.add_signal_receiver(
+ self._name_owner_changed_handler = bus.add_signal_receiver(
self.__name_owner_changed_cb,
signal_name='NameOwnerChanged',
dbus_interface='org.freedesktop.DBus')
@@ -183,6 +184,12 @@ def __error_handler_cb(self, error):
def is_owner(self):
return True
+ def do_dispose(self):
+ if getattr(self, '_name_owner_changed_handler', None) is not None:
+ self._name_owner_changed_handler.remove()
+ self._name_owner_changed_handler = None
+ super().do_dispose()
+
def get_owner_instance():
global _owner_instance
diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py
index 57bd7ec358..453d79ed95 100644
--- a/src/jarabe/model/bundleregistry.py
+++ b/src/jarabe/model/bundleregistry.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2009 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
@@ -72,7 +72,7 @@ class BundleRegistry(GObject.GObject):
def __init__(self):
logging.debug('STARTUP: Loading the bundle registry')
- GObject.GObject.__init__(self)
+ super().__init__()
self._mime_defaults = self._load_mime_defaults()
@@ -109,8 +109,8 @@ def __init__(self):
try:
self._load_favorites()
- except Exception:
- logging.exception('Error while loading favorite_activities.')
+ except (ValueError, IOError, KeyError) as e:
+ logging.exception('Error while loading favorite_activities: %s', e)
self._hidden_activities = []
self._load_hidden_activities()
@@ -129,8 +129,8 @@ def __desktop_view_icons_changed_cb(self, model):
self._favorite_bundles.append({})
try:
self._load_favorites()
- except Exception:
- logging.exception('Error while loading favorite_activities.')
+ except (ValueError, IOError, KeyError) as e:
+ logging.exception('Error while loading favorite_activities: %s', e)
def __file_monitor_changed_cb(self, monitor, one_file, other_file,
event_type):
@@ -211,6 +211,8 @@ def _load_favorites(self):
def _load_hidden_activities(self):
path = os.environ.get('SUGAR_ACTIVITIES_HIDDEN', None)
+ if not path:
+ return
try:
with open(path) as file:
for line in file.readlines():
@@ -269,19 +271,18 @@ def _scan_directory(self, path):
bundle_dir = os.path.join(path, f)
if os.path.isdir(bundle_dir):
bundles[bundle_dir] = os.stat(bundle_dir).st_mtime
- except Exception:
+ except OSError as e:
logging.exception('Error while processing installed activity'
- ' bundle %s:', bundle_dir)
+ ' bundle %s: %s', bundle_dir, e)
bundle_dirs = list(bundles.keys())
bundle_dirs.sort(key=lambda x: bundles[x])
for folder in bundle_dirs:
try:
self.add_bundle(folder, emit_signals=False)
- except:
- # pylint: disable=W0702
+ except Exception as e:
logging.exception('Error while processing installed activity'
- ' bundle %s:', folder)
+ ' bundle %s: %s', folder, e)
def add_bundle(self, bundle_path, set_favorite=False, emit_signals=True,
force_downgrade=False):
@@ -490,7 +491,7 @@ def install(self, bundle, force_downgrade=False):
self.install_async(bundle, self._sync_install_cb, result,
force_downgrade)
while result[0] is None:
- Gtk.main_iteration()
+ GLib.MainContext.default().iteration(may_block=True)
if isinstance(result[0], Exception):
raise result[0]
@@ -676,9 +677,9 @@ def _do_work(self, task):
if act.is_user_activity():
try:
act.uninstall()
- except:
+ except Exception as e:
logging.exception('Uninstall failed, still trying to '
- 'install newer bundle')
+ 'install newer bundle: %s', e)
else:
logging.warning('Unable to uninstall system activity, '
'installing upgraded version in user '
diff --git a/src/jarabe/model/desktop.py b/src/jarabe/model/desktop.py
index 47c912bc4d..aa4ccfd688 100644
--- a/src/jarabe/model/desktop.py
+++ b/src/jarabe/model/desktop.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008-2013 Sugar Labs
+# Copyright (C) 2008-2013 Sugar Labs
# Copyright (C) 2013 Walter Bender
#
# This program is free software: you can redistribute it and/or modify
@@ -33,12 +33,14 @@ class DesktopViewModel(GObject.GObject):
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._number_of_views = 1
self._view_icons = None
self._favorite_icons = None
self._view_labels = None
+ self._backgrounds = None
+ self._activity_layouts = None
self._settings = Gio.Settings.new(_DESKTOP_CONF_DIR)
self._ensure_view_icons()
@@ -65,6 +67,16 @@ def get_view_labels(self):
view_labels = GObject.Property(type=object, getter=get_view_labels)
+ def get_backgrounds(self):
+ return self._backgrounds
+
+ backgrounds = GObject.Property(type=object, getter=get_backgrounds)
+
+ def get_activity_layouts(self):
+ return self._activity_layouts
+
+ activity_layouts = GObject.Property(type=object, getter=get_activity_layouts)
+
def _ensure_view_icons(self, update=False):
if self._view_icons and not update:
return
@@ -76,8 +88,10 @@ def _ensure_view_icons(self, update=False):
homeviews = self._settings.get_value(_HOMEVIEWS_KEY).unpack()
self._number_of_views = len(homeviews)
- self._view_icons = [view['view-icon'] for view in homeviews]
- self._favorite_icons = [view['favorite-icon'] for view in homeviews]
+ self._view_icons = [view.get('view-icon', '') for view in homeviews]
+ self._favorite_icons = [view.get('favorite-icon', '') for view in homeviews]
+ self._backgrounds = [view.get('background', '') for view in homeviews]
+ self._activity_layouts = [view.get('activity-layout', '') for view in homeviews]
self._view_labels = []
for view in homeviews:
if 'view-label' not in view:
@@ -115,3 +129,11 @@ def get_view_labels():
def get_number_of_views():
return get_model().get_number_of_views()
+
+
+def get_backgrounds():
+ return get_model().get_backgrounds()
+
+
+def get_activity_layouts():
+ return get_model().get_activity_layouts()
diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py
index aef04007de..a24125f6f3 100644
--- a/src/jarabe/model/filetransfer.py
+++ b/src/jarabe/model/filetransfer.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 Tomeu Vizoso
+# Copyright (C) 2008 Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -41,7 +41,6 @@
from jarabe.model import telepathyclient
from jarabe.model import neighborhood
-
FT_STATE_NONE = 0
FT_STATE_PENDING = 1
FT_STATE_ACCEPTED = 2
@@ -57,14 +56,13 @@
FT_REASON_LOCAL_ERROR = 5
FT_REASON_REMOTE_ERROR = 6
-
new_file_transfer = dispatch.Signal()
class BaseFileTransfer(GObject.GObject):
def __init__(self, connection):
- GObject.GObject.__init__(self)
+ super().__init__()
self._connection = connection
self._state = FT_STATE_NONE
self._transferred_bytes = 0
@@ -80,11 +78,11 @@ def __init__(self, connection):
def set_channel(self, channel):
self.channel = channel
- self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ self._state_changed_hid = self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
'FileTransferStateChanged', self.__state_changed_cb)
- self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ self._transferred_bytes_changed_hid = self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
'TransferredBytesChanged', self.__transferred_bytes_changed_cb)
- self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ self._initial_offset_defined_hid = self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
'InitialOffsetDefined', self.__initial_offset_defined_cb)
channel_properties = self.channel[dbus.PROPERTIES_IFACE]
@@ -131,6 +129,18 @@ def _get_state(self):
state = GObject.Property(type=int, getter=_get_state, setter=_set_state)
+ def do_dispose(self):
+ if getattr(self, '_state_changed_hid', None) is not None:
+ self._state_changed_hid.remove()
+ self._state_changed_hid = None
+ if getattr(self, '_transferred_bytes_changed_hid', None) is not None:
+ self._transferred_bytes_changed_hid.remove()
+ self._transferred_bytes_changed_hid = None
+ if getattr(self, '_initial_offset_defined_hid', None) is not None:
+ self._initial_offset_defined_hid.remove()
+ self._initial_offset_defined_hid = None
+ GObject.GObject.do_dispose(self)
+
def cancel(self):
self.channel[CHANNEL].Close()
@@ -287,12 +297,15 @@ def _new_channels_cb(connection, channels):
object_path, props)
new_file_transfer.send(None, file_transfer=incoming_file_transfer)
+_connection_handlers = {}
+
def _monitor_connection(connection):
logging.debug('connection added %r', connection)
- connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal(
+ handler = connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal(
'NewChannels',
lambda channels: _new_channels_cb(connection, channels))
+ _connection_handlers[id(connection)] = handler
def _connection_added_cb(conn_watcher, connection):
@@ -301,6 +314,9 @@ def _connection_added_cb(conn_watcher, connection):
def _connection_removed_cb(conn_watcher, connection):
logging.debug('connection removed %r', connection)
+ handler = _connection_handlers.pop(id(connection), None)
+ if handler is not None:
+ handler.remove()
def _got_dispatch_operation_cb(**kwargs):
@@ -360,7 +376,6 @@ def file_transfer_available():
return False
-
if __name__ == '__main__':
import tempfile
diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py
index 6cc48aa32e..4cba01cd7c 100644
--- a/src/jarabe/model/friends.py
+++ b/src/jarabe/model/friends.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -25,7 +25,6 @@
from jarabe.model.buddy import BuddyModel
from jarabe.model import neighborhood
-
_model = None
@@ -105,7 +104,7 @@ class Friends(GObject.GObject):
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._friends = {}
self._path = os.path.join(env.get_profile_path(), 'friends')
diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py
index a8f3d52ce5..4654e19749 100644
--- a/src/jarabe/model/invites.py
+++ b/src/jarabe/model/invites.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
@@ -38,7 +38,6 @@
from jarabe.model import neighborhood
from jarabe.journal import misc
-
CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
'org.laptop.Telepathy.ActivityProperties'
@@ -79,8 +78,18 @@ def _name_owner_changed_cb(self, name, old_owner, new_owner):
logging.debug('BaseInvite._name_owner_changed_cb %r %r %r', name,
new_owner, old_owner)
if name == self._handler and new_owner and not old_owner:
+ handler = getattr(self, '_name_owner_changed_handler', None)
+ if handler is not None:
+ handler.remove()
+ self._name_owner_changed_handler = None
self._call_handle_with()
+ def cleanup(self):
+ handler = getattr(self, '_name_owner_changed_handler', None)
+ if handler is not None:
+ handler.remove()
+ self._name_owner_changed_handler = None
+
class ActivityInvite(BaseInvite):
"""Invitation to a shared activity."""
@@ -115,10 +124,11 @@ def join(self):
return
bus = dbus.SessionBus()
- bus.add_signal_receiver(self._name_owner_changed_cb,
- 'NameOwnerChanged',
- 'org.freedesktop.DBus',
- arg0=self._handler)
+ self._name_owner_changed_handler = bus.add_signal_receiver(
+ self._name_owner_changed_cb,
+ 'NameOwnerChanged',
+ 'org.freedesktop.DBus',
+ arg0=self._handler)
model = neighborhood.get_model()
activity_id = model.get_activity_by_room(self._handle).activity_id
@@ -144,10 +154,11 @@ def join(self):
bundle = registry.get_bundle(bundle_id)
bus = dbus.SessionBus()
- bus.add_signal_receiver(self._name_owner_changed_cb,
- 'NameOwnerChanged',
- 'org.freedesktop.DBus',
- arg0=self._handler)
+ self._name_owner_changed_handler = bus.add_signal_receiver(
+ self._name_owner_changed_cb,
+ 'NameOwnerChanged',
+ 'org.freedesktop.DBus',
+ arg0=self._handler)
misc.launch(bundle, color=self.get_color(), invited=True,
uri=self._private_channel)
@@ -161,7 +172,7 @@ class Invites(GObject.GObject):
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._dispatch_operations = {}
@@ -287,6 +298,7 @@ def _add_private_invite(self, dispatch_operation_path, handle, handler,
self.emit('invite-added', invite)
def remove_invite(self, invite):
+ invite.cleanup()
del self._dispatch_operations[invite.dispatch_operation_path]
self.emit('invite-removed', invite)
diff --git a/src/jarabe/model/keyboard.py b/src/jarabe/model/keyboard.py
index 0bd1471b19..3f79912f3a 100755
--- a/src/jarabe/model/keyboard.py
+++ b/src/jarabe/model/keyboard.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2006, Red Hat, Inc.
# Copyright (C) 2009, One Laptop Per Child Association Inc
#
# This program is free software: you can redistribute it and/or modify
@@ -17,51 +17,50 @@
import logging
import gi
-gi.require_version('Xkl', '1.0')
+from gi.repository import Gdk
from gi.repository import Gio
-from gi.repository import GdkX11
-from gi.repository import Xkl
+from gi.repository import GLib
def setup():
settings = Gio.Settings.new('org.sugarlabs.peripherals.keyboard')
have_config = False
-
try:
- display = GdkX11.x11_get_default_xdisplay()
- if display is not None:
- engine = Xkl.Engine.get_instance(display)
- else:
- logging.debug('setup_keyboard_cb: Could not get default display.')
- return
-
- configrec = Xkl.ConfigRec()
- configrec.get_from_server(engine)
layouts = settings.get_strv('layouts')
layouts_list = []
variants_list = []
if layouts:
for layout in layouts:
- layouts_list.append(layout.split('(')[0])
- variants_list.append(layout.split('(')[1][:-1])
+ if '(' in layout and layout.endswith(')'):
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+ else:
+ layouts_list.append(layout)
+ variants_list.append('')
- if layouts_list and variants_list:
+ if layouts_list:
have_config = True
- configrec.set_layouts(layouts_list)
- configrec.set_variants(variants_list)
model = settings.get_string('model')
if model:
have_config = True
- configrec.set_model(model)
options = settings.get_strv('options')
if options:
have_config = True
- configrec.set_options(options)
if have_config:
- configrec.activate(engine)
- except Exception:
- logging.exception('Error during keyboard configuration')
+ bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+ proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
+ 'org.freedesktop.locale1',
+ '/org/freedesktop/locale1',
+ 'org.freedesktop.locale1', None)
+ layouts_str = ",".join(layouts_list)
+ variants_str = ",".join(variants_list)
+ options_str = ",".join(options) if options else ""
+ proxy.call_sync('SetX11Keyboard',
+ GLib.Variant('(ssssb)', (layouts_str, model if model else "", variants_str, options_str, True)),
+ Gio.DBusCallFlags.NONE, -1, None)
+ except (GLib.Error, ValueError, IndexError) as e:
+ logging.exception('Error during keyboard configuration: %s', e)
diff --git a/src/jarabe/model/mimeregistry.py b/src/jarabe/model/mimeregistry.py
index d506c26ae4..d6615041a8 100644
--- a/src/jarabe/model/mimeregistry.py
+++ b/src/jarabe/model/mimeregistry.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 Aleksey Lim
+# Copyright (C) 2009 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py
index fd8f4da8c5..bb07ce9e38 100644
--- a/src/jarabe/model/neighborhood.py
+++ b/src/jarabe/model/neighborhood.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Collabora Ltd.
+# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,6 +21,8 @@
from gi.repository import Gio
import dbus
from dbus import PROPERTIES_IFACE
+import gi
+gi.require_version('TelepathyGLib', '0.12')
from gi.repository import TelepathyGLib
ACCOUNT = TelepathyGLib.IFACE_ACCOUNT
ACCOUNT_MANAGER = TelepathyGLib.IFACE_ACCOUNT_MANAGER
@@ -53,7 +55,6 @@
from jarabe.model import bundleregistry
from jarabe.model import shell
-
ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher'
@@ -87,7 +88,7 @@ class ActivityModel(GObject.GObject):
}
def __init__(self, activity_id, room_handle):
- GObject.GObject.__init__(self)
+ super().__init__()
self.activity_id = activity_id
self.room_handle = room_handle
@@ -188,7 +189,7 @@ class _Account(GObject.GObject):
}
def __init__(self, account_path):
- GObject.GObject.__init__(self)
+ super().__init__()
self.object_path = account_path
@@ -754,7 +755,7 @@ class Neighborhood(GObject.GObject):
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._buddies = {None: get_owner_instance()}
self._activities = {}
@@ -781,10 +782,12 @@ def __init__(self):
def __got_accounts_cb(self, account_paths):
self._link_local_account = \
self._ensure_link_local_account(account_paths)
- self._connect_to_account(self._link_local_account)
+ if self._link_local_account:
+ self._connect_to_account(self._link_local_account)
self._server_account = self._ensure_server_account(account_paths)
- self._connect_to_account(self._server_account)
+ if self._server_account:
+ self._connect_to_account(self._server_account)
def __error_handler_cb(self, error):
raise RuntimeError(error)
@@ -806,12 +809,12 @@ def _connect_to_account(self, account):
def __account_connected_cb(self, account):
logging.debug('__account_connected_cb %s', account.object_path)
- if account == self._server_account:
+ if account == self._server_account and self._link_local_account:
self._link_local_account.disable()
def __account_disconnected_cb(self, account):
logging.debug('__account_disconnected_cb %s', account.object_path)
- if account == self._server_account:
+ if account == self._server_account and self._link_local_account:
self._link_local_account.enable()
def _get_published_name(self):
@@ -822,8 +825,9 @@ def _get_published_name(self):
the room name, the published name and the host name.
"""
+ pubkey = get_profile().pubkey or ''
public_key_hash = sha1(
- get_profile().pubkey.encode('utf-8')).hexdigest()
+ pubkey.encode('utf-8')).hexdigest()
return public_key_hash[:8]
def _ensure_link_local_account(self, account_paths):
@@ -852,13 +856,18 @@ def _ensure_link_local_account(self, account_paths):
ACCOUNT + '.ConnectAutomatically': True,
}
+
bus = dbus.Bus()
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
- account_path = account_manager.CreateAccount('salut', 'local-xmpp',
- 'salut', params,
- properties)
- return _Account(account_path)
+ try:
+ account_path = account_manager.CreateAccount('salut', 'local-xmpp',
+ 'salut', params,
+ properties)
+ return _Account(account_path)
+ except Exception as e:
+ logging.error("Failed to create Salut account: %s", e)
+ return None
def _ensure_server_account(self, account_paths):
for account_path in account_paths:
@@ -872,7 +881,7 @@ def _ensure_server_account(self, account_paths):
nick = self._settings_user.get_string('nick')
server = self._settings_collaboration.get_string('jabber-server')
- key_hash = get_profile().privkey_hash
+ key_hash = get_profile().privkey_hash or ''
params = {
'account': self._get_jabber_account_id(),
@@ -892,22 +901,30 @@ def _ensure_server_account(self, account_paths):
ACCOUNT + '.ConnectAutomatically': True,
}
+
bus = dbus.Bus()
obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
- account_path = account_manager.CreateAccount('gabble', 'jabber',
- 'jabber', params,
- properties)
- return _Account(account_path)
+ try:
+ account_path = account_manager.CreateAccount('gabble', 'jabber',
+ 'jabber', params,
+ properties)
+ return _Account(account_path)
+ except Exception as e:
+ logging.error("Failed to create Gabble account: %s", e)
+ return None
def _get_jabber_account_id(self):
+ pubkey = get_profile().pubkey or ''
public_key_hash = sha1(
- get_profile().pubkey.encode('utf-8')).hexdigest()
+ pubkey.encode('utf-8')).hexdigest()
server = self._settings_collaboration.get_string('jabber-server')
return '%s@%s' % (public_key_hash, server)
def __jabber_server_changed_cb(self, settings, key):
logging.debug('__jabber_server_changed_cb')
+ if not self._server_account:
+ return
bus = dbus.Bus()
account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
@@ -931,24 +948,28 @@ def __nick_changed_cb(self, settings, key):
nick = settings.get_string('nick')
bus = dbus.Bus()
- server_obj = bus.get_object(ACCOUNT_MANAGER_SERVICE,
- self._server_account.object_path)
- server_obj.Set(ACCOUNT, 'Nickname', nick,
- dbus_interface=PROPERTIES_IFACE)
-
- link_local_obj = bus.get_object(ACCOUNT_MANAGER_SERVICE,
- self._link_local_account.object_path)
- link_local_obj.Set(ACCOUNT, 'Nickname', nick,
+ if self._server_account:
+ server_obj = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._server_account.object_path)
+ server_obj.Set(ACCOUNT, 'Nickname', nick,
dbus_interface=PROPERTIES_IFACE)
- params_needing_reconnect = link_local_obj.UpdateParameters(
- {'nickname': nick, 'published-name': self._get_published_name()},
- dbus.Array([], 's'), dbus_interface=ACCOUNT)
- if params_needing_reconnect:
- link_local_obj.Reconnect()
+
+ if self._link_local_account:
+ link_local_obj = bus.get_object(ACCOUNT_MANAGER_SERVICE,
+ self._link_local_account.object_path)
+ link_local_obj.Set(ACCOUNT, 'Nickname', nick,
+ dbus_interface=PROPERTIES_IFACE)
+ params_needing_reconnect = link_local_obj.UpdateParameters(
+ {'nickname': nick, 'published-name': self._get_published_name()},
+ dbus.Array([], 's'), dbus_interface=ACCOUNT)
+ if params_needing_reconnect:
+ link_local_obj.Reconnect()
self._update_jid()
def _update_jid(self):
+ if not self._link_local_account:
+ return
bus = dbus.Bus()
account = bus.get_object(ACCOUNT_MANAGER_SERVICE,
self._link_local_account.object_path)
diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py
index 6e8227bf32..97a32c6d8b 100644
--- a/src/jarabe/model/network.py
+++ b/src/jarabe/model/network.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 Red Hat, Inc.
+# Copyright (C) 2008 Red Hat, Inc.
# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
# Copyright (C) 2009-2010 One Laptop per Child
# Copyright (C) 2009 Paraguay Educa, Martin Abente
@@ -327,7 +327,7 @@ def frequency_to_channel(frequency):
bg_table = {2412: 1, 2417: 2, 2422: 3, 2427: 4,
2432: 5, 2437: 6, 2442: 7, 2447: 8,
2452: 9, 2457: 10, 2462: 11, 2467: 12,
- 2472: 13, 14: 2484}
+ 2472: 13, 2484: 14}
a_table = {5035: 7, 5040: 8, 5045: 9, 5055: 11,
5060: 12, 5080: 16, 5170: 34,
@@ -639,7 +639,7 @@ class AccessPoint(GObject.GObject):
}
def __init__(self, device, model):
- GObject.GObject.__init__(self)
+ super().__init__()
self.device = device
self.model = model
@@ -787,7 +787,7 @@ class Connection(GObject.GObject):
}
def __init__(self, bus, path):
- GObject.GObject.__init__(self)
+ super().__init__()
obj = bus.get_object(NM_SERVICE, path)
self._connection = dbus.Interface(obj, NM_CONNECTION_IFACE)
self._removed_handle = self._connection.connect_to_signal(
@@ -1130,7 +1130,6 @@ def ssid_to_display_name(ssid):
return ':'.join(['%02x' % (ord(byte), ) for byte in ssid])
-
wifi_whitelist = ('Sugar Ad-hoc Network 1',
'Sugar Ad-hoc Network 6',
'Sugar Ad-hoc Network 11')
diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py
index 2f27381325..c4eba75a5e 100644
--- a/src/jarabe/model/notifications.py
+++ b/src/jarabe/model/notifications.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@
from jarabe import config
-
_DBUS_SERVICE = 'org.freedesktop.Notifications'
_DBUS_IFACE = 'org.freedesktop.Notifications'
_DBUS_PATH = '/org/freedesktop/Notifications'
diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py
index 911dc60ed5..ba94ad5c9a 100644
--- a/src/jarabe/model/olpcmesh.py
+++ b/src/jarabe/model/olpcmesh.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009, 2010 One Laptop per Child
+# Copyright (C) 2009, 2010 One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/model/screen.py b/src/jarabe/model/screen.py
index 913cfa1668..fcc612c2e1 100644
--- a/src/jarabe/model/screen.py
+++ b/src/jarabe/model/screen.py
@@ -13,9 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+
+
import os
+import logging
+
import dbus
+_logger = logging.getLogger('screen')
+
_HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore'
_HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm'
@@ -24,11 +30,25 @@
POWERD_FLAG_DIR = '/etc/powerd/flags'
+_SCREENSAVER_SERVICE = 'org.freedesktop.ScreenSaver'
+_SCREENSAVER_PATH = '/org/freedesktop/ScreenSaver'
+_SCREENSAVER_INTERFACE = 'org.freedesktop.ScreenSaver'
+
+_inhibit_cookie = None
+
+
def using_powerd():
+ """Returns True only on genuine OLPC hardware with powerd running.
+
+ On all modern systems (Ubuntu, Fedora, Debian with GNOME/KDE/Wayland),
+ this returns False and idle management is handled by the compositor
+ via org.freedesktop.ScreenSaver D-Bus inhibit instead.
+ """
return os.access(POWERD_FLAG_DIR, os.W_OK)
def _get_ohm():
+ """Get OHM (Open Hardware Manager) DBus proxy - OLPC XO hardware only."""
bus = dbus.SystemBus()
proxy = bus.get_object(_HARDWARE_MANAGER_SERVICE,
_HARDWARE_MANAGER_OBJECT_PATH,
@@ -37,10 +57,71 @@ def _get_ohm():
def unfreeze():
- if not using_powerd():
+ """Unfreeze the display or inhibit screensaver.
+
+ On OLPC hardware, sends the unfreeze command to ohmd. On standard
+ desktop systems, registers an idle inhibitor with the screensaver service.
+ """
+ if using_powerd():
+
+ try:
+ _get_ohm().SetKey('display.dcon_freeze', 0)
+ _logger.debug('OLPC display unfrozen via ohmd')
+ except dbus.DBusException as e:
+ _logger.warning('Failed to unfreeze OLPC display: %s', e)
+ return
+
+
+ _inhibit_screensaver()
+
+
+def _inhibit_screensaver():
+ """Register an idle inhibitor via org.freedesktop.ScreenSaver D-Bus.
+
+ This prevents screensaver/sleep while Sugar is the active desktop.
+ The inhibitor is automatically released when Sugar exits.
+
+ Supported by: Ubuntu, Fedora, Debian+GNOME, KDE, XFCE, LXQt.
+ On Wayland compositors, xdg-desktop-portal routes this to the native
+ zwp_idle_inhibit_unstable_v1 Wayland protocol.
+ """
+ global _inhibit_cookie
+ if _inhibit_cookie is not None:
+
+ return
+
+ try:
+ bus = dbus.SessionBus()
+ proxy = bus.get_object(_SCREENSAVER_SERVICE, _SCREENSAVER_PATH)
+ screensaver = dbus.Interface(proxy, _SCREENSAVER_INTERFACE)
+
+ _inhibit_cookie = screensaver.Inhibit(
+ 'org.sugarlabs.Shell',
+ 'Sugar is the active desktop session'
+ )
+ _logger.debug('Idle inhibitor registered, cookie=%s', _inhibit_cookie)
+ except dbus.DBusException as e:
+
+ _logger.info('Could not register idle inhibitor (no screensaver daemon): %s', e)
+
+
+def release_inhibitor():
+ """Release the idle inhibitor when Sugar is shutting down.
+
+ Call this in Sugar's shutdown sequence so the inhibitor is cleanly
+ released instead of timing out. Matched with unfreeze() in startup.
+ """
+ global _inhibit_cookie
+ if _inhibit_cookie is None:
return
try:
- _get_ohm().SetKey('display.dcon_freeze', 0)
- except dbus.DBusException:
- pass
+ bus = dbus.SessionBus()
+ proxy = bus.get_object(_SCREENSAVER_SERVICE, _SCREENSAVER_PATH)
+ screensaver = dbus.Interface(proxy, _SCREENSAVER_INTERFACE)
+ screensaver.UnInhibit(_inhibit_cookie)
+ _logger.debug('Idle inhibitor released')
+ except dbus.DBusException as e:
+ _logger.warning('Failed to release idle inhibitor: %s', e)
+ finally:
+ _inhibit_cookie = None
diff --git a/src/jarabe/model/screenshot.py b/src/jarabe/model/screenshot.py
index 75501655b5..3c0fa5d003 100644
--- a/src/jarabe/model/screenshot.py
+++ b/src/jarabe/model/screenshot.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2009 Simon Schampijer, James Zaki
#
# This program is free software: you can redistribute it and/or modify
@@ -19,9 +19,12 @@
from gettext import gettext as _
import io
import cairo
+import logging
+import subprocess
from gi.repository import Gdk
from gi.repository import Gio
+from gi.repository import GLib
import dbus
from sugar4.datastore import datastore
@@ -35,16 +38,58 @@ def take_screenshot():
fd, file_path = tempfile.mkstemp(dir=tmp_dir)
os.close(fd)
- window = Gdk.get_default_root_window()
- width, height = window.get_width(), window.get_height()
-
- screenshot_surface = Gdk.Window.create_similar_surface(
- window, cairo.CONTENT_COLOR, width, height)
-
- cr = cairo.Context(screenshot_surface)
- Gdk.cairo_set_source_window(cr, window, 0, 0)
- cr.paint()
- screenshot_surface.write_to_png(file_path)
+ width, height = 1024, 768
+ display = Gdk.Display.get_default()
+ if display:
+ monitors = display.get_monitors()
+ if monitors.get_n_items() > 0:
+ monitor = monitors.get_item(0)
+ if monitor is not None:
+ geometry = monitor.get_geometry()
+ width = geometry.width
+ height = geometry.height
+
+ screenshot_surface = None
+
+ # Try GNOME Shell DBus (Mutter)
+ try:
+ bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+ proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
+ 'org.gnome.Shell.Screenshot',
+ '/org/gnome/Shell/Screenshot',
+ 'org.gnome.Shell.Screenshot', None)
+ proxy.call_sync('Screenshot', GLib.Variant('(bbs)', (False, False, file_path)), Gio.DBusCallFlags.NONE, -1, None)
+ screenshot_surface = cairo.ImageSurface.create_from_png(file_path)
+ except (GLib.Error, cairo.Error) as e:
+ logging.warning("GNOME Shell screenshot failed: %s", e)
+
+ # Try grim (wlroots)
+ if not screenshot_surface:
+ try:
+ subprocess.run(['grim', file_path], check=True)
+ screenshot_surface = cairo.ImageSurface.create_from_png(file_path)
+ except (OSError, cairo.Error, subprocess.CalledProcessError) as e:
+ logging.warning("grim screenshot failed: %s", e)
+
+ # Try XDG Desktop Portal via dbus-send (Note: this is async and may require user interaction)
+ if not screenshot_surface:
+ try:
+ subprocess.run(['dbus-send', '--session', '--print-reply',
+ '--dest=org.freedesktop.portal.Desktop',
+ '/org/freedesktop/portal/desktop',
+ 'org.freedesktop.portal.Screenshot.Screenshot',
+ 'string:', 'dict:string:variant:'], check=True)
+ # Portal saves file to a uri, but handling the response requires listening to the Request signal.
+ # For this permanent fix, we assume Casilda will handle wlroots/gnome or we fallback to blank.
+ except (OSError, subprocess.CalledProcessError) as e:
+ logging.warning("XDG portal screenshot failed: %s", e)
+
+ if not screenshot_surface:
+ screenshot_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
+ cr = cairo.Context(screenshot_surface)
+ cr.set_source_rgb(1, 1, 1)
+ cr.paint()
+ screenshot_surface.write_to_png(file_path)
settings = Gio.Settings.new('org.sugarlabs.user')
color = settings.get_string('color')
diff --git a/src/jarabe/model/session.py b/src/jarabe/model/session.py
index 175655dac7..f07358c930 100644
--- a/src/jarabe/model/session.py
+++ b/src/jarabe/model/session.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, Red Hat, Inc.
+# Copyright (C) 2008, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,14 +16,12 @@
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
-#from gi.repository import SugarExt
-import dbus
import os
import logging
+import dbus
from jarabe.model import shell
-
_session_manager = None
@@ -43,7 +41,7 @@ class SessionManager(GObject.GObject):
MAX_SHUTDOWN_TRIES = 10
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
#address = SugarExt.xsmp_init()
#os.environ['SESSION_MANAGER'] = address
@@ -54,15 +52,14 @@ def __init__(self):
self._shutdown_tries = 0
self._logout_mode = None
- #def start(self):
- #self.session.start()
- #self.session.connect('shutdown-completed',
- # self.__shutdown_completed_cb)
-
def initiate_shutdown(self, logout_mode):
self._logout_mode = logout_mode
self.shutdown_signal.emit()
- #self.session.initiate_shutdown()
+ for activity in self._shell_model:
+ activity.stop()
+ self._shutdown_tries = 0
+ if self._try_shutdown():
+ GLib.timeout_add_seconds(self.SHUTDOWN_TIMEOUT, self._try_shutdown)
def cancel_shutdown(self):
#self.session.cancel_shutdown()
@@ -72,7 +69,7 @@ def cancel_shutdown(self):
def __shutdown_completed_cb(self, session):
if self._logout_mode is not None:
if self._try_shutdown():
- GLib.timeout_add(self.SHUTDOWN_TIMEOUT, self._try_shutdown)
+ GLib.timeout_add_seconds(self.SHUTDOWN_TIMEOUT, self._try_shutdown)
def _try_shutdown(self):
if len(self._shell_model) > 0:
@@ -96,41 +93,40 @@ def reboot(self):
def shutdown_completed(self):
if self._logout_mode != self.MODE_LOGOUT:
- bus = dbus.SystemBus()
- if have_systemd():
- try:
- proxy = bus.get_object('org.freedesktop.login1',
- '/org/freedesktop/login1')
- pm = dbus.Interface(proxy,
- 'org.freedesktop.login1.Manager')
-
- if self._logout_mode == self.MODE_SHUTDOWN:
- pm.PowerOff(False)
- elif self._logout_mode == self.MODE_REBOOT:
- pm.Reboot(True)
- except:
- logging.exception('Can not stop sugar')
- #self.session.cancel_shutdown()
- return
- else:
- CONSOLEKIT_DBUS_PATH = '/org/freedesktop/ConsoleKit/Manager'
- try:
- proxy = bus.get_object('org.freedesktop.ConsoleKit',
- CONSOLEKIT_DBUS_PATH)
- pm = dbus.Interface(proxy,
- 'org.freedesktop.ConsoleKit.Manager')
-
- if self._logout_mode == self.MODE_SHUTDOWN:
- pm.Stop()
- elif self._logout_mode == self.MODE_REBOOT:
- pm.Restart()
- except:
- logging.exception('Can not stop sugar')
- #self.session.cancel_shutdown()
- return
-
- #SugarExt.xsmp_shutdown()
- Gtk.main_quit()
+ try:
+ bus = dbus.SystemBus()
+ if have_systemd():
+ try:
+ proxy = bus.get_object('org.freedesktop.login1',
+ '/org/freedesktop/login1')
+ pm = dbus.Interface(proxy,
+ 'org.freedesktop.login1.Manager')
+
+ if self._logout_mode == self.MODE_SHUTDOWN:
+ pm.PowerOff(False)
+ elif self._logout_mode == self.MODE_REBOOT:
+ pm.Reboot(False)
+ except dbus.DBusException:
+ logging.exception('Can not stop sugar via systemd')
+ else:
+ CONSOLEKIT_DBUS_PATH = '/org/freedesktop/ConsoleKit/Manager'
+ try:
+ proxy = bus.get_object('org.freedesktop.ConsoleKit',
+ CONSOLEKIT_DBUS_PATH)
+ pm = dbus.Interface(proxy,
+ 'org.freedesktop.ConsoleKit.Manager')
+
+ if self._logout_mode == self.MODE_SHUTDOWN:
+ pm.Stop()
+ elif self._logout_mode == self.MODE_REBOOT:
+ pm.Restart()
+ except dbus.DBusException:
+ logging.exception('Can not stop sugar via ConsoleKit')
+ except dbus.DBusException:
+ logging.exception('Can not connect to SystemBus for shutdown')
+
+ # Always quit the shell model to ensure we exit
+ self._shell_model.quit()
def get_session_manager():
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index a0571cf97b..13acd1ba33 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -20,6 +20,7 @@
import gi
gi.require_version("Casilda", "1.0")
+from gi.repository import Casilda
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Gtk
@@ -72,7 +73,7 @@ def __init__(self, activity_info, activity_id, color, window=None):
_windows -- GtkWindows registered for the activity. The lowest
one in the stack is the main window.
"""
- GObject.GObject.__init__(self)
+ super().__init__()
self._windows = []
self._service = None
@@ -93,12 +94,11 @@ def __init__(self, activity_info, activity_id, color, window=None):
self._retrieve_service()
self._name_owner_changed_handler = None
- if not self._service:
- bus = dbus.SessionBus()
- self._name_owner_changed_handler = bus.add_signal_receiver(
- self._name_owner_changed_cb,
- signal_name='NameOwnerChanged',
- dbus_interface='org.freedesktop.DBus')
+ bus = dbus.SessionBus()
+ self._name_owner_changed_handler = bus.add_signal_receiver(
+ self._name_owner_changed_cb,
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus')
self._launch_completed_hid = \
get_model().connect('launch-completed',
@@ -144,19 +144,18 @@ def stop(self):
self.close_window()
def close_window(self):
- if self.get_window() is not None:
- self.get_window().destroy()
+ window = self.get_window()
+ if window is not None:
+ window.close()
for w in self._shell_windows:
- w.destroy()
+ w.close()
- def remove_window_by_bundle_id(self, bundle_id):
+ def remove_window(self, window):
"""Remove a window from the windows stack."""
- for wnd in self._windows:
- data = wnd.get_window().get_user_data()
- if getattr(data, 'bundle_id') == bundle_id:
- self._windows.remove(wnd)
- return True
+ if window in self._windows:
+ self._windows.remove(window)
+ return True
return False
def get_service(self):
@@ -172,20 +171,17 @@ def get_service(self):
def get_title(self):
"""Retrieve the application's root window's suggested title"""
if self._windows:
- return self._windows[0].get_name()
+ return self._windows[0].get_title()
return None
def get_icon_path(self):
"""Retrieve the activity's icon (file) name"""
if self.is_journal():
- icon_theme = Gtk.IconTheme.get_default()
- info = icon_theme.lookup_icon('activity-journal',
- Gtk.IconSize.SMALL_TOOLBAR, 0)
- if not info:
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
+ paintable = icon_theme.lookup_icon('activity-journal', None, 24, 1, Gtk.TextDirection.LTR, 0)
+ if not paintable or not paintable.get_file():
return None
- fname = info.get_filename()
- del info
- return fname
+ return paintable.get_file().get_path()
if self._activity_info:
return self._activity_info.get_icon()
return None
@@ -218,8 +214,8 @@ def has_bundle_id(self, bundle_id):
"""Check if a window with the given bundle id is in the windows stack"""
if self._windows:
for wnd in self._windows:
- data = wnd.get_window().get_user_data()
- wid = getattr(data, 'bundle_id')
+ data = wnd
+ wid = getattr(data, 'bundle_id', None)
if wid == bundle_id:
return True
return False
@@ -247,12 +243,17 @@ def get_type(self):
"""Retrieve the activity bundle id for future reference"""
if not self._windows:
return None
- data = self._windows[0].get_window().get_user_data()
- return getattr(data, 'bundle_id')
+ data = self._windows[0]
+ return getattr(data, 'bundle_id', None)
def is_journal(self):
"""Returns boolean if the activity is of type JournalActivity"""
- return self.get_type() == 'org.laptop.JournalActivity'
+ if self.get_bundle_id() == 'org.laptop.JournalActivity':
+ return True
+ from jarabe.journal.journalactivity import JournalActivity
+ if self._windows and isinstance(self._windows[0], JournalActivity):
+ return True
+ return False
def get_launch_time(self):
"""Return the time at which the activity was first launched
@@ -266,7 +267,9 @@ def get_pid(self):
"""Returns the activity's PID"""
if not self._windows:
return None
- return self._windows[0].get_pid()
+ if hasattr(self._windows[0], 'get_pid'):
+ return self._windows[0].get_pid()
+ return None
def get_bundle_path(self):
"""Returns the activity's bundle directory"""
@@ -283,8 +286,8 @@ def get_activity_name(self):
def equals(self, activity):
if self._activity_id and activity.get_activity_id():
return self._activity_id == activity.get_activity_id()
- data = self._windows[0].get_window().get_user_data()
- bundle_id = getattr(data, bundle_id)
+ data = self._windows[0]
+ bundle_id = getattr(data, 'bundle_id', None)
if bundle_id and activity.get_bundle_id():
return bundle_id == activity.get_bundle_id()
return False
@@ -301,7 +304,8 @@ def _retrieve_service(self):
try:
bus = dbus.SessionBus()
proxy = bus.get_object(self._get_service_name(),
- _SERVICE_PATH + '/' + self._activity_id)
+ _SERVICE_PATH + '/' + self._activity_id,
+ introspect=False)
self._service = dbus.Interface(proxy, _SERVICE_INTERFACE)
except dbus.DBusException:
self._service = None
@@ -314,11 +318,14 @@ def _name_owner_changed_cb(self, name, old, new):
self._name_owner_changed_handler.remove()
self._name_owner_changed_handler = None
self._service = None
+ get_model()._remove_activity(self)
elif not old and new:
logging.debug('Activity._name_owner_changed_cb: '
'activity %s started up', name)
self._retrieve_service()
- self.set_active(True)
+ if self.get_window() is not None:
+ self.set_active(True)
+ get_model().emit('launch-completed', self)
def set_active(self, state):
"""Propagate the current state to the activity object"""
@@ -349,29 +356,26 @@ def __launch_failed_cb(self, model, home_activity):
if home_activity is self:
self._set_launch_status(Activity.LAUNCH_FAILED)
- def _state_changed_cb(self, main_window, changed_mask, new_state):
- if changed_mask & Gdk.WindowState.ICONIFIED:
- if new_state & Gdk.WindowState.ICONIFIED:
+ def _state_changed_cb(self, main_window, *args):
+ # Track minimized state
+ surface = main_window.get_surface()
+ if surface is not None:
+ state = surface.get_state()
+ if state & Gdk.ToplevelState.MINIMIZED:
self.emit('pause')
else:
self.emit('resume')
class ShellModel(Gtk.Application):
- """Model of the shell (activity management)
-
- The ShellModel is basically the point of registration
- for all running activities within Sugar. It traps
- events that tell the system there is a new activity
- being created (generated by the activity factories),
- or removed, as well as those which tell us that the
- currently focused activity has changed.
-
- The HomeModel tracks a set of HomeActivity instances,
- which are tracking the window to activity mappings
- the activity factories have set up.
- """
+ """Model of the Shell, the point of registration for all running activities.
+ Traps 'window-added' / 'window-removed' events from Gtk.Application to
+ track which activity windows are alive, and emits signals consumed by the
+ home view and tray. A Casilda Wayland compositor (self.compositor) is
+ embedded as a child widget so activity processes render into the shell
+ rather than as independent top-level windows.
+ """
__gsignals__ = {
'activity-added': (GObject.SignalFlags.RUN_FIRST, None,
([GObject.TYPE_PYOBJECT])),
@@ -399,21 +403,17 @@ class ShellModel(Gtk.Application):
ZOOM_ACTIVITY = 3
def __init__(self, application_id="org.laptop.Shell"):
- Gtk.Application.__init__(
- self,
+ super().__init__(
application_id=application_id,
- flags=Gio.ApplicationFlags.IS_SERVICE)
+ flags=Gio.ApplicationFlags.NON_UNIQUE)
self.set_default()
- self.compositor = Casilda.Compositor()
- self.compositor.spawn_async(
- None,
- None,
- None,
- GLib.SpawnFlags.DEFAULT
- )
+ self.stack = Gtk.Stack()
+ self.stack.add_css_class('sugar-shell-stack')
+ self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
+ self._main_window = None
- logging.warning("shell initialized")
+ self.compositor = Casilda.Compositor(socket="wayland-sugar")
self.connect('window-added', self._window_added_cb)
self.connect('window-removed', self._window_removed_cb)
@@ -432,16 +432,16 @@ def __init__(self, application_id="org.laptop.Shell"):
self._launchers = {}
self._modal_dialogs_counter = 0
- #self._screen.toggle_showing_desktop(True)
-
settings = Gio.Settings.new('org.sugarlabs')
self._maximum_open_activities = settings.get_int(
'maximum-number-of-open-activities')
self._launch_timers = {}
+ self.zoom_level_changed.connect(self._zoom_level_changed_cb)
+ self.connect('active-activity-changed', self._active_activity_changed_cb)
+
def add_window(self, window):
- window.set_child(self.compositor)
super().add_window(window)
def get_launcher(self, activity_id):
@@ -455,12 +455,13 @@ def unregister_launcher(self, activity_id):
del self._launchers[activity_id]
def _update_zoom_level(self, window):
- if window.get_type_hint() == Gdk.WindowType.DIALOG:
+ if isinstance(window, Gtk.Dialog):
return
- if window.get_type_hint() == Gdk.WindowType.NORMAL:
- new_level = self.ZOOM_ACTIVITY
- else:
+
+ if window == self._main_window:
new_level = self._desktop_level
+ else:
+ new_level = self.ZOOM_ACTIVITY
if self._zoom_level != new_level:
old_level = self._zoom_level
@@ -473,6 +474,9 @@ def set_zoom_level(self, new_level, x_event_time=0):
if old_level == new_level:
return
+ if new_level == self.ZOOM_ACTIVITY and self._active_activity is None:
+ return
+
self._zoom_level = new_level
if new_level is not self.ZOOM_ACTIVITY:
self._desktop_level = new_level
@@ -480,21 +484,35 @@ def set_zoom_level(self, new_level, x_event_time=0):
self.zoom_level_changed.send(self, old_level=old_level,
new_level=new_level)
- show_desktop = new_level is not self.ZOOM_ACTIVITY
- #self._screen.toggle_showing_desktop(show_desktop)
-
if new_level is self.ZOOM_ACTIVITY:
- # activate the window, in case it was iconified
- # (e.g. during sugar launch, the Journal starts in this state)
- window = self._active_activity.get_window()
- if window:
- window.activate(x_event_time or Gtk.get_current_event_time())
+ if self._active_activity:
+ window = self._active_activity.get_window()
+ if window:
+ window.present()
def _get_zoom_level(self):
return self._zoom_level
zoom_level = property(_get_zoom_level)
+ def _zoom_level_changed_cb(self, signal=None, sender=None, **kwargs):
+ new_level = kwargs.get('new_level')
+ if new_level == self.ZOOM_ACTIVITY:
+ active_activity = self.get_active_activity()
+ if active_activity and active_activity.is_journal():
+ self.stack.set_visible_child_name("journal")
+ else:
+ self.stack.set_visible_child_name("activity")
+ else:
+ self.stack.set_visible_child_name("home")
+
+ def _active_activity_changed_cb(self, shell_model, activity):
+ if self.zoom_level == self.ZOOM_ACTIVITY:
+ if activity and activity.is_journal():
+ self.stack.set_visible_child_name("journal")
+ else:
+ self.stack.set_visible_child_name("activity")
+
def _get_activities_with_window(self):
ret = []
for i in self._activities:
@@ -537,11 +555,9 @@ def remove_shared_activity(self, activity_id):
del self._shared_activities[activity_id]
def get_tabbing_activity(self):
- """Returns the activity that is currently highlighted during tabbing"""
return self._tabbing_activity
def set_tabbing_activity(self, activity):
- """Sets the activity that is currently highlighted during tabbing"""
self._tabbing_activity = activity
self.emit('tabbing-activity-changed', self._tabbing_activity)
@@ -558,6 +574,20 @@ def _set_active_activity(self, home_activity):
self._active_activity = home_activity
self.emit('active-activity-changed', self._active_activity)
+ def activate_activity(self, activity):
+ if activity is None:
+ return
+
+ self._set_active_activity(activity)
+ self.set_zoom_level(self.ZOOM_ACTIVITY)
+
+ window = activity.get_window()
+ if window is not None:
+ if hasattr(window, 'present'):
+ window.present()
+ elif hasattr(window, 'activate'):
+ window.activate(0)
+
def __iter__(self):
return iter(self._activities)
@@ -571,29 +601,35 @@ def index(self, obj):
return self._activities.index(obj)
def _window_added_cb(self, application, window):
- """Handle the callback for the 'window opened' event.
-
- Most activities will register 2 windows during
- their lifetime: the launcher window, and the 'main'
- app window.
+ if not isinstance(window, Gtk.Dialog):
+ if type(window).__name__ in ('IntroWindow', 'HomeWindow', 'LaunchWindow'):
+ return
- When the main window appears, we send a signal to
- the launcher window to close.
- Some activities (notably non-native apps) open several
- windows during their lifetime, switching from one to
- the next as the 'main' window. We use a stack to track
- them.
+ if isinstance(window, Gtk.ApplicationWindow) and \
+ not getattr(window, 'activity_id', None):
+ logging.debug('_window_added_cb: ignoring non-activity '
+ 'ApplicationWindow %r', window)
+ return
- """
- logging.warning(f"adding window: {window.__name__}")
- if window.get_type_hint() == Gdk.WindowTypeHint.NORMAL or \
- window.get_type_hint() == Gdk.WindowTypeHint.SPLASHSCREEN:
home_activity = None
- data = window.get_window().get_user_data()
- activity_id = getattr(data, 'activity_id')
- service_name = getattr(data, 'bundle_id')
+ activity_id = getattr(window, 'activity_id', None)
+ service_name = getattr(window, 'bundle_id', None)
+
+ if not service_name:
+ if hasattr(window, 'get_app_id'):
+ service_name = window.get_app_id()
+ elif hasattr(window, 'app_id'):
+ service_name = getattr(window, 'app_id')
+
+ if not activity_id and service_name:
+ for a_id in list(self._launchers.keys()):
+ home_act = self.get_activity_by_id(a_id)
+ if home_act and home_act.get_bundle_id() == service_name:
+ if home_act.get_launch_status() == Activity.LAUNCHING:
+ activity_id = a_id
+ break
if service_name:
registry = get_registry()
@@ -604,31 +640,17 @@ def _window_added_cb(self, application, window):
if activity_id:
home_activity = self.get_activity_by_id(activity_id)
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- gdk_window = Gdk.Window.new(None, attributes, None)
- gdk_window.set_decorations(0)
-
+ window.set_decorated(False)
window.maximize()
def is_main_window(window, home_activity):
- # Check if window is the 'main' app window, not the
- # launcher window.
- return window.get_type_hint() != \
- Gdk.WindowTypeHint.SPLASHSCREEN and \
- home_activity.get_launch_status() == Activity.LAUNCHING
-
- if home_activity is None and \
- window.get_type_hint() == Gdk.WindowTypeHint.NORMAL:
- # This is a special case for the Journal
- # We check if is not a splash screen to avoid #4767
- logging.debug('first window registered for %s', activity_id)
+ return home_activity.get_launch_status() == Activity.LAUNCHING
+
+ if home_activity is None and not isinstance(window, Gtk.Dialog):
color = self._shared_activities.get(activity_id, None)
home_activity = Activity(activity_info, activity_id,
color, window)
-
self._add_activity(home_activity)
-
else:
logging.debug('window registered for %s', activity_id)
home_activity.add_window(window, is_main_window(window,
@@ -639,24 +661,43 @@ def is_main_window(window, home_activity):
startup_time = time.time() - home_activity.get_launch_time()
logging.debug('%s launched in %f seconds.',
activity_id, startup_time)
+ if self._active_activity == home_activity:
+ home_activity.set_active(True)
self.emit('active-window-changed', window)
if self._active_activity is None:
self._set_active_activity(home_activity)
def _window_removed_cb(self, application, window):
- if window.get__type_hint() == Gdk.WindowTypeHint.NORMAL or \
- window.get_type_hint() == Gdk.WindowTypeHint.SPLASHSCREEN:
- data = window.get_window().get_user_data()
- bundle_id = getattr(data, 'bundle_id')
- activity = self._get_activity_by_bundle_id(bundle_id)
+ activity_id = getattr(window, 'activity_id', None)
+ if not activity_id:
+ service_name = getattr(window, 'bundle_id', None)
+ if not service_name:
+ if hasattr(window, 'get_app_id'):
+ service_name = window.get_app_id()
+ elif hasattr(window, 'app_id'):
+ service_name = getattr(window, 'app_id')
+
+ if service_name:
+ for home_act in self._activities:
+ if home_act.get_bundle_id() == service_name and home_act.get_window() == window:
+ activity_id = home_act.get_activity_id()
+ break
+
+ if activity_id:
+ activity = self.get_activity_by_id(activity_id)
if activity is not None:
- activity.remove_window_by_bundle_id(bundle_id)
+ activity.remove_window(window)
if activity.get_window() is None:
logging.debug('last window gone - remove activity %s',
activity)
activity.close_window()
+ self.stack.set_transition_type(
+ Gtk.StackTransitionType.NONE)
self._remove_activity(activity)
+ GLib.idle_add(
+ lambda: self.stack.set_transition_type(
+ Gtk.StackTransitionType.CROSSFADE) or False)
def _get_activity_by_bundle_id(self, bundle_id):
for home_activity in self._activities:
@@ -670,16 +711,18 @@ def get_activity_by_id(self, activity_id):
return home_activity
return None
- def _active_window_changed_cb(self, window):
+ def _active_window_changed_cb(self, application, window):
if window is None:
return
- if window.get_type_hint() != Gdk.WindowTypeHint.DIALOG:
+ if not isinstance(window, Gtk.Dialog):
while window.get_transient_for() is not None:
window = window.get_transient_for()
- data = window.get_window().get_user_data()
- bundle_id = getattr(data, 'bundle_id')
+ data = window
+ bundle_id = getattr(data, 'bundle_id', None)
+ if not bundle_id:
+ return
act = self._get_activity_by_bundle_id(bundle_id)
if act is not None:
self._set_active_activity(act)
@@ -716,15 +759,17 @@ def _remove_activity(self, home_activity):
windows = self.get_windows()
windows.reverse()
for window in windows:
- data = window.get_window().get_user_data()
- bundle_id = getattr(data, 'bundle_id')
+ data = window
+ bundle_id = getattr(data, 'bundle_id', None)
+ if not bundle_id:
+ continue
new_activity = self._get_activity_by_bundle_id(bundle_id)
if new_activity is not None:
self._set_active_activity(new_activity)
break
else:
- logging.error('No activities are running')
self._set_active_activity(None)
+ self.set_zoom_level(self._desktop_level)
self.emit('activity-removed', home_activity)
self._activities.remove(home_activity)
@@ -741,7 +786,7 @@ def notify_launch(self, activity_id, service_name):
self._add_activity(home_activity)
self._set_active_activity(home_activity)
-
+ self.set_zoom_level(self.ZOOM_ACTIVITY)
self.emit('launch-started', home_activity)
if activity_id in self._launch_timers:
diff --git a/src/jarabe/model/sound.py b/src/jarabe/model/sound.py
index 3dc7d8841b..9d7f86b339 100644
--- a/src/jarabe/model/sound.py
+++ b/src/jarabe/model/sound.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2008 Red Hat, Inc.
+# Copyright (C) 2006-2008 Red Hat, Inc.
# Copyright (C) 2014 Emil Dudev
#
# This program is free software: you can redistribute it and/or modify
@@ -16,15 +16,16 @@
from gi.repository import Gio
from gi.repository import GLib
-
+import gi
+gi.require_version('SugarExt', '2.0')
from gi.repository import SugarExt
-from sugar4 import dispatch
+from sugar4 import dispatch
+import logging
_PLAYBACK = 0
_CAPTURE = 1
-
_SAVE_TIMEOUT = 500
@@ -91,6 +92,5 @@ def set_muted(self, new_state):
self._volume.set_mute(new_state)
self.muted_changed.send(None)
-
sound = PlaybackSound()
capture_sound = CaptureSound()
diff --git a/src/jarabe/model/speech.py b/src/jarabe/model/speech.py
index 825d8789d2..c4ea794092 100644
--- a/src/jarabe/model/speech.py
+++ b/src/jarabe/model/speech.py
@@ -1,8 +1,8 @@
-# Copyright (C) 2011 One Laptop Per Child
+# Copyright (C) 2011 One Laptop Per Child
#
-# This program is free software: you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
+# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
@@ -11,10 +11,400 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-from sugar4.speech import SpeechManager
+import os
+import logging
+from gettext import gettext as _
+import gi
+
+from gi.repository import Gio
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GLib
+from gi.repository import GObject
+
+_HAS_GST = True
+try:
+ gi.require_version('Gst', '1.0')
+ from gi.repository import Gst
+ Gst.init(None)
+ Gst.parse_launch('espeak')
+except Exception as e:
+ logging.error('Gst or the espeak plugin is not installed in the system: %s', e)
+ _HAS_GST = False
+
+try:
+ from sugar4 import power
+ _power_manager = power.get_power_manager()
+except (ImportError, AttributeError):
+ _power_manager = None
+
+DEFAULT_PITCH = 0
+DEFAULT_RATE = 0
+_SAVE_TIMEOUT = 500
+
+SPEECH_SCHEMA = 'org.sugarlabs.speech'
+
+translated_espeak_voices = {
+ 'af': _('Afrikaans'),
+ 'an': _('Aragonese'),
+ 'bg': _('Bulgarian'),
+ 'bs': _('Bosnian'),
+ 'ca': _('Catalan'),
+ 'cs': _('Czech'),
+ 'cy': _('Welsh'),
+ 'da': _('Danish'),
+ 'de': _('German'),
+ 'el': _('Greek'),
+ 'en': _('English'),
+ 'en_gb': _('English Britain'),
+ 'en_sc': _('English scottish'),
+ 'en_uk-north': _('English-north'),
+ 'en_uk-rp': _('English_rp'),
+ 'en_uk-wmids': _('English_wmids'),
+ 'en_us': _('English USA'),
+ 'en_wi': _('English West Indies'),
+ 'eo': _('Esperanto'),
+ 'es': _('Spanish'),
+ 'es_la': _('Spanish latin american'),
+ 'et': _('Estonian'),
+ 'fa': _('Farsi'),
+ 'fa_pin': _('Farsi-pinglish'),
+ 'fi': _('Finnish'),
+ 'fr_be': _('French belgium'),
+ 'fr_fr': _('French'),
+ 'ga': _('Irish-gaeilge'),
+ 'grc': _('Greek-ancient'),
+ 'hi': _('Hindi'),
+ 'hr': _('Croatian'),
+ 'hu': _('Hungarian'),
+ 'hy': _('Armenian'),
+ 'hy_west': _('Armenian (west)'),
+ 'id': _('Indonesian'),
+ 'is': _('Icelandic'),
+ 'it': _('Italian'),
+ 'jbo': _('Lojban'),
+ 'ka': _('Georgian'),
+ 'kn': _('Kannada'),
+ 'ku': _('Kurdish'),
+ 'la': _('Latin'),
+ 'lt': _('Lithuanian'),
+ 'lv': _('Latvian'),
+ 'mk': _('Macedonian'),
+ 'ml': _('Malayalam'),
+ 'ms': _('Malay'),
+ 'ne': _('Nepali'),
+ 'nl': _('Dutch'),
+ 'no': _('Norwegian'),
+ 'pa': _('Punjabi'),
+ 'pl': _('Polish'),
+ 'pt_br': _('Portuguese (Brazil)'),
+ 'pt_pt': _('Portuguese (Portugal)'),
+ 'ro': _('Romanian'),
+ 'ru': _('Russian'),
+ 'sk': _('Slovak'),
+ 'sq': _('Albanian'),
+ 'sr': _('Serbian'),
+ 'sv': _('Swedish'),
+ 'sw': _('Swahili'),
+ 'ta': _('Tamil'),
+ 'tr': _('Turkish'),
+ 'vi': _('Vietnam'),
+ 'vi_hue': _('Vietnam_hue'),
+ 'vi_sgn': _('Vietnam_sgn'),
+ 'zh': _('Mandarin'),
+ 'zh_yue': _('Cantonese')
+}
+
+
+class SpeechManager(GObject.GObject):
+
+ __gtype_name__ = 'SpeechManager'
+
+ __gsignals__ = {
+ 'play': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'pause': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'stop': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'mark': (GObject.SignalFlags.RUN_FIRST, None, [str])
+ }
+
+ MIN_PITCH = -100
+ MAX_PITCH = 100
+
+ MIN_RATE = -100
+ MAX_RATE = 100
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.player = None
+ if not self.enabled():
+ return
+
+ self.player = GstSpeechPlayer()
+ self.player.connect('play', self._update_state, 'play')
+ self.player.connect('stop', self._update_state, 'stop')
+ self.player.connect('pause', self._update_state, 'pause')
+ self.player.connect('mark', self._mark_cb)
+ self._default_voice_name = self.player.get_default_voice()
+ self._pitch = DEFAULT_PITCH
+ self._rate = DEFAULT_RATE
+ self._is_playing = False
+ self._is_paused = False
+ self._save_timeout_id = -1
+ self.restore()
+
+ def enabled(self):
+ return _HAS_GST
+
+ def _update_state(self, player, signal):
+ self._is_playing = (signal == 'play')
+ self._is_paused = (signal == 'pause')
+ self.emit(signal)
+
+ def _mark_cb(self, player, value):
+ self.emit('mark', value)
+
+ def get_is_playing(self):
+ return self._is_playing
+
+ is_playing = GObject.Property(type=bool, getter=get_is_playing,
+ setter=None, default=False)
+
+ def get_is_paused(self):
+ return self._is_paused
+
+ is_paused = GObject.Property(type=bool, getter=get_is_paused,
+ setter=None, default=False)
+
+ def get_pitch(self):
+ return self._pitch
+
+ def get_rate(self):
+ return self._rate
+
+ def set_pitch(self, pitch):
+ self._pitch = pitch
+ if self._save_timeout_id != -1:
+ GLib.source_remove(self._save_timeout_id)
+ self._save_timeout_id = GLib.timeout_add(_SAVE_TIMEOUT, self.save)
+
+ def set_rate(self, rate):
+ self._rate = rate
+ if self._save_timeout_id != -1:
+ GLib.source_remove(self._save_timeout_id)
+ self._save_timeout_id = GLib.timeout_add(_SAVE_TIMEOUT, self.save)
+
+ def say_text(self, text, pitch=None, rate=None, lang_code=None):
+ if pitch is None:
+ pitch = self._pitch
+ if rate is None:
+ rate = self._rate
+ if lang_code is None:
+ voice_name = self._default_voice_name
+ else:
+ voice_name = self.player.get_all_voices()[lang_code]
+ if text:
+ logging.debug(
+ 'PLAYING %r lang %r pitch %r rate %r',
+ text,
+ voice_name,
+ pitch,
+ rate)
+ self.player.speak(pitch, rate, voice_name, text)
+
+ def say_selected_text(self):
+ display = Gdk.Display.get_default()
+ if display:
+ clipboard = display.get_primary_clipboard()
+ clipboard.read_text_async(None, self.__primary_selection_cb)
+
+ def pause(self):
+ self.player.pause_sound_device()
+
+ def restart(self):
+ self.player.restart_sound_device()
+
+ def stop(self):
+ self.player.stop_sound_device()
+
+ def __primary_selection_cb(self, clipboard, result):
+ try:
+ text = clipboard.read_text_finish(result)
+ if text:
+ self.say_text(text)
+ except GLib.Error as e:
+ logging.error("Failed to read primary clipboard text: %s", e)
+
+ def save(self):
+ self._save_timeout_id = -1
+
+ schema_source = Gio.SettingsSchemaSource.get_default()
+ if schema_source.lookup(SPEECH_SCHEMA, True) is None:
+ return False
+
+ settings = Gio.Settings(SPEECH_SCHEMA)
+ settings.set_int('pitch', self._pitch)
+ settings.set_int('rate', self._rate)
+ logging.debug('saving speech configuration pitch %s rate %s',
+ self._pitch, self._rate)
+ return False
+
+ def restore(self):
+ schema_source = Gio.SettingsSchemaSource.get_default()
+ if schema_source.lookup(SPEECH_SCHEMA, True) is None:
+ return
+
+ settings = Gio.Settings(SPEECH_SCHEMA)
+ self._pitch = settings.get_int('pitch')
+ self._rate = settings.get_int('rate')
+ logging.debug('loading speech configuration pitch %s rate %s',
+ self._pitch, self._rate)
+
+ def get_all_voices(self):
+ if self.player:
+ return self.player.get_all_voices()
+ return None
+
+ def get_all_traslated_voices(self):
+ """ deprecated after 0.112, due to method name spelling error """
+ if self.player:
+ return self.player.get_all_translated_voices()
+ return None
+
+ def get_all_translated_voices(self):
+ if self.player:
+ return self.player.get_all_translated_voices()
+ return None
+
+
+class GstSpeechPlayer(GObject.GObject):
+
+ __gsignals__ = {
+ 'play': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'pause': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'stop': (GObject.SignalFlags.RUN_FIRST, None, []),
+ 'mark': (GObject.SignalFlags.RUN_FIRST, None, [str])
+ }
+
+ def __init__(self):
+ super().__init__()
+ self.pipeline = None
+ self._all_voices = None
+ self._all_translated_voices = None
+
+ def restart_sound_device(self):
+ if self.pipeline is None:
+ logging.debug('Trying to restart not initialized sound device')
+ return
+
+ if _power_manager:
+ _power_manager.inhibit_suspend()
+ self.pipeline.set_state(Gst.State.PLAYING)
+ self.emit('play')
+
+ def pause_sound_device(self):
+ if self.pipeline is None:
+ return
+
+ self.pipeline.set_state(Gst.State.PAUSED)
+ if _power_manager:
+ _power_manager.restore_suspend()
+ self.emit('pause')
+
+ def stop_sound_device(self):
+ if self.pipeline is None:
+ return
+
+ self.pipeline.set_state(Gst.State.NULL)
+ if _power_manager:
+ _power_manager.restore_suspend()
+ self.emit('stop')
+
+ def make_pipeline(self, command):
+ if self.pipeline is not None:
+ self.stop_sound_device()
+ del self.pipeline
+
+ self.pipeline = Gst.parse_launch(command)
+
+ bus = self.pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', self.__pipe_message_cb)
+
+ def __pipe_message_cb(self, bus, message):
+ if message.type in (Gst.MessageType.EOS, Gst.MessageType.ERROR):
+ self.pipeline.set_state(Gst.State.NULL)
+ self.pipeline = None
+ if _power_manager:
+ _power_manager.restore_suspend()
+ self.emit('stop')
+ elif message.type is Gst.MessageType.ELEMENT and \
+ message.get_structure().get_name() == 'espeak-mark':
+ mark_value = message.get_structure().get_value('mark')
+ self.emit('mark', mark_value)
+
+ def speak(self, pitch, rate, voice_name, text):
+ # TODO workaround for http://bugs.sugarlabs.org/ticket/1801
+ if not [i for i in text if i.isalnum()]:
+ return
+
+ self.make_pipeline('espeak name=espeak ! autoaudiosink')
+ src = self.pipeline.get_by_name('espeak')
+
+ src.props.text = text
+ src.props.pitch = pitch
+ src.props.rate = rate
+ src.props.voice = voice_name
+ src.props.track = 2 # track for marks
+
+ self.restart_sound_device()
+
+ def get_all_voices(self):
+ if self._all_voices is not None:
+ return self._all_voices
+ self._init_voices()
+ return self._all_voices
+
+ def get_all_translated_voices(self):
+ if self._all_translated_voices is not None:
+ return self._all_translated_voices
+ self._init_voices()
+ return self._all_translated_voices
+
+ def _init_voices(self):
+ self._all_voices = {}
+ self._all_translated_voices = {}
+
+ for voice in Gst.ElementFactory.make('espeak', None).props.voices:
+ name, language, dialect = voice
+ if dialect != 'none':
+ lang_code = language + '_' + dialect
+ else:
+ lang_code = language
+
+ self._all_voices[lang_code] = name
+ if lang_code in translated_espeak_voices:
+ self._all_translated_voices[lang_code] = \
+ translated_espeak_voices[lang_code]
+ else:
+ self._all_translated_voices[lang_code] = name
+
+ def get_default_voice(self):
+ voices = self.get_all_voices()
+
+ locale = os.environ.get('LANG', '')
+ language_location = locale.split('.', 1)[0].lower()
+ language = language_location.split('_')[0]
+ # if the language is es but not es_es default to es_la (latin voice)
+ if language == 'es' and language_location != 'es_es':
+ language_location = 'es_la'
+
+ best = voices.get(language_location) or voices.get(language) \
+ or 'english'
+ return best
_speech_manager = None
diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py
index 885beb4dd4..56448689e5 100644
--- a/src/jarabe/model/telepathyclient.py
+++ b/src/jarabe/model/telepathyclient.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Collabora Ltd.
+# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -32,7 +32,6 @@
from sugar4 import dispatch
-
SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar'
SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar'
@@ -46,7 +45,7 @@ def __init__(self):
CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE,
CLIENT_APPROVER])
- bus = dbus.Bus()
+ bus = dbus.SessionBus()
bus_name = service.BusName(SUGAR_CLIENT_SERVICE, bus=bus)
service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH)
diff --git a/src/jarabe/model/update/__init__.py b/src/jarabe/model/update/__init__.py
index 0dfa7a4d69..7efeab42f8 100644
--- a/src/jarabe/model/update/__init__.py
+++ b/src/jarabe/model/update/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013, Sugar Labs
+# Copyright (C) 2009-2013, Sugar Labs
# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
diff --git a/src/jarabe/model/update/aslo.py b/src/jarabe/model/update/aslo.py
index d741e03ed4..749682be64 100644
--- a/src/jarabe/model/update/aslo.py
+++ b/src/jarabe/model/update/aslo.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013 Sugar Labs
+# Copyright (C) 2009-2013 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
"""Activity information microformat parser.
Activity information is embedded in HTML/XHTML/XML pages using a
@@ -78,7 +77,7 @@ class _UpdateChecker(GObject.GObject):
_CHUNK_SIZE = 10240
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
self._bundle = None
def check(self, bundle):
diff --git a/src/jarabe/model/update/microformat.py b/src/jarabe/model/update/microformat.py
index 5786c214af..6c159ba9b1 100644
--- a/src/jarabe/model/update/microformat.py
+++ b/src/jarabe/model/update/microformat.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008-2013 One Laptop per Child
+# Copyright (C) 2008-2013 One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
"""
Activity information microformat parser.
@@ -356,7 +355,7 @@ class MetadataLookup(GObject.GObject):
}
def __init__(self, url):
- GObject.GObject.__init__(self)
+ super().__init__()
self._url = url
self._icon_file_name = None
self._size = None
@@ -436,14 +435,14 @@ def _locale_data_lookup(self):
if filename not in self._namelist:
continue
cp = ConfigParser()
- cp.readfp(StringIO(self._zf.read(filename)))
+ cp.read_string(self._zf.read(filename).decode('utf-8'))
return cp.get('Activity', 'name')
return None
def _activity_info_lookup(self, parameter):
filename = os.path.join(self._prefix, 'activity', 'activity.info')
cp = ConfigParser()
- cp.readfp(StringIO(self._zf.read(filename)))
+ cp.read_string(self._zf.read(filename).decode('utf-8'))
if cp.has_option('Activity', parameter):
return cp.get('Activity', parameter)
return ''
@@ -451,7 +450,7 @@ def _activity_info_lookup(self, parameter):
def _library_info_lookup(self, parameter):
filename = os.path.join(self._prefix, 'library', 'library.info')
cp = ConfigParser()
- cp.readfp(StringIO(self._zf.read(filename)))
+ cp.read_string(self._zf.read(filename).decode('utf-8'))
if cp.has_option('Library', parameter):
return cp.get('Library', parameter)
return ''
diff --git a/src/jarabe/model/update/updater.py b/src/jarabe/model/update/updater.py
index b43eb26b56..fbaa40766f 100644
--- a/src/jarabe/model/update/updater.py
+++ b/src/jarabe/model/update/updater.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013, Sugar Labs
+# Copyright (C) 2009-2013, Sugar Labs
# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
import os
import logging
import time
@@ -66,7 +65,7 @@ class Updater(GObject.GObject):
}
def __init__(self):
- GObject.GObject.__init__(self)
+ super().__init__()
settings = Gio.Settings.new(_UPDATE_KEYS_PATH)
backend = settings.get_string(_UPDATE_BACKEND_KEY)
diff --git a/src/jarabe/testrunner.py b/src/jarabe/testrunner.py
index 75100adc8c..33f9698262 100644
--- a/src/jarabe/testrunner.py
+++ b/src/jarabe/testrunner.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Daniel Narvaez
+# Copyright (C) 2013, Daniel Narvaez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/src/jarabe/util/downloader.py b/src/jarabe/util/downloader.py
index e5ebd75af7..130dcfffd5 100644
--- a/src/jarabe/util/downloader.py
+++ b/src/jarabe/util/downloader.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2013, Sugar Labs
+# Copyright (C) 2009-2013, Sugar Labs
# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software: you can redistribute it and/or modify
@@ -41,10 +41,10 @@ def get_soup_session():
global _session
if _session is None:
_session = Soup.Session()
- _session.set_timeout(60)
- _session.set_idle_timeout(60)
- _session.set_user_agent("Sugar/%s" % config.version)
- _session.set_proxy_resolver(Gio.ProxyResolver.get_default())
+ _session.props.timeout = 60
+ _session.props.idle_timeout = 60
+ _session.props.user_agent = "Sugar/%s" % config.version
+ _session.props.proxy_resolver = Gio.ProxyResolver.get_default()
return _session
@@ -62,7 +62,7 @@ class Downloader(GObject.GObject):
}
def __init__(self, url, session=None, request_headers=None):
- GObject.GObject.__init__(self)
+ super().__init__()
self._uri = GLib.Uri.parse(url, GLib.UriFlags.NONE)
self._session = session or get_soup_session()
self._pending_buffers = []
@@ -74,17 +74,18 @@ def __init__(self, url, session=None, request_headers=None):
self._output_stream = None
self._message = None
self._request_headers = request_headers
+ self._cancellable = Gio.Cancellable()
+ self._read_finished = False
+ self._accumulated_data = bytearray()
+ self._is_chunked = False
def _setup_message(self, method="GET"):
- self._message = Soup.Message.new(method=method, uri=self._uri)
- self._message.connect('got-body-data', self._got_body_data_cb)
- self._message.connect('got-headers', self._headers_cb, None)
+ self._message = Soup.Message.new(method, self._uri.to_string())
if self._request_headers is not None:
for header_key in list(self._request_headers.keys()):
self._message.get_request_headers().append(
header_key, self._request_headers[header_key])
-
def download_to_temp(self):
"""
Download the contents of the provided URL to temporary file storage.
@@ -106,8 +107,9 @@ def download_chunked(self):
signal. Upon completion, a successful download is indicated by a
reuslt of None in the complete signal parameters.
"""
+ self._is_chunked = True
self._setup_message()
- self._session.send_async(self._message, 0, None, self._message_cb, None)
+ self._session.send_async(self._message, GLib.PRIORITY_DEFAULT, self._cancellable, self._message_cb, None)
def download(self, start=None, end=None):
"""
@@ -117,10 +119,11 @@ def download(self, start=None, end=None):
The start and end parameters can optionally be set to perform a
partial read of the remote data.
"""
+ self._is_chunked = False
self._setup_message()
if start is not None:
self._message.get_request_headers().set_range(start, end)
- self._session.send_async(self._message, 0, None, self._message_cb, None)
+ self._session.send_async(self._message, GLib.PRIORITY_DEFAULT, self._cancellable, self._message_cb, None)
def get_size(self):
"""
@@ -128,52 +131,101 @@ def get_size(self):
The size is returned in the result parameter of the 'complete' signal.
"""
self._setup_message("HEAD")
- self._session.send_async(self._message, 0, None, self._message_cb, None)
+ self._session.send_async(self._message, GLib.PRIORITY_DEFAULT, self._cancellable, self._message_cb, None)
- def _message_cb(self, session, message, user_data):
- self._status_code = message.get_status()
- self._check_if_finished()
+ def _message_cb(self, session, res, user_data):
+ try:
+ stream = session.send_finish(res)
+ self._status_code = self._message.get_status()
+ if soup_status_is_successful(self._status_code):
+ self._total_size = self._message.get_response_headers().get_content_length()
- def cancel(self):
- self._cancelling = True
- self._session.cancel_message(self._message, SOUP_STATUS_CANCELLED)
+ if self._message.get_method() == "HEAD":
+ self._read_finished = True
+ self._check_if_finished()
+ else:
+ self._read_next_chunk(stream)
+ except Exception:
+ self._status_code = self._message.get_status()
+ if self._status_code == 0:
+ self._status_code = SOUP_STATUS_CANCELLED
+ self._read_finished = True
+ self._check_if_finished()
+
+ def _read_next_chunk(self, stream):
+ if self._cancelling or not soup_status_is_successful(self._status_code):
+ self._read_finished = True
+ self._check_if_finished()
+ return
+ stream.read_bytes_async(8192, GLib.PRIORITY_DEFAULT, self._cancellable, self._read_cb, stream)
- def _headers_cb(self, message, user_data):
- if soup_status_is_successful(message.get_status()):
- self._total_size = message.get_response_headers().get_content_length()
+ def _read_cb(self, stream, res, user_data):
+ if self._cancelling:
+ self._read_finished = True
+ self._check_if_finished()
+ return
+
+ try:
+ data = stream.read_bytes_finish(res)
+ except Exception:
+ self._status_code = SOUP_STATUS_CANCELLED
+ self._read_finished = True
+ self._check_if_finished()
+ return
- def _got_body_data_cb(self, message, chunk_size):
- if self._cancelling or \
- not soup_status_is_successful(message.get_status()):
+ if data is None or data.get_size() == 0:
+ self._read_finished = True
+ self._check_if_finished()
return
- data = Gio.InputStream.read_bytes(chunk_size, None)
self.emit('got-chunk', data)
+
+ if not self._is_chunked and not self._output_stream:
+ self._accumulated_data.extend(data.get_data())
+
if self._output_stream:
self._pending_buffers.append(data)
self._write_next_buffer()
+ else:
+ self._downloaded_size += data.get_size()
+ if self._total_size > 0:
+ progress = self._downloaded_size / float(self._total_size)
+ self.emit('progress', progress)
- def __write_async_cb(self, output_stream, result, user_data):
- count = output_stream.write_bytes_finish(result)
+ self._read_next_chunk(stream)
+
+ def cancel(self):
+ self._cancelling = True
+ self._cancellable.cancel()
- self._downloaded_size += count
- if self._total_size > 0:
- progress = self._downloaded_size / float(self._total_size)
- self.emit('progress', progress)
+ def __write_async_cb(self, output_stream, result, user_data):
+ try:
+ count = output_stream.write_bytes_finish(result)
+ self._downloaded_size += count
+ if self._total_size > 0:
+ progress = self._downloaded_size / float(self._total_size)
+ self.emit('progress', progress)
+ except Exception:
+ self._status_code = SOUP_STATUS_CANCELLED
+ self.cancel()
self._check_if_finished()
def _complete(self):
if self._output_stream:
- self._output_stream.close(None)
+ try:
+ self._output_stream.close(None)
+ except Exception:
+ pass
+ self._output_stream = None
result = None
if soup_status_is_successful(self._status_code):
if self._message.get_method() == "HEAD":
# this is a get_size request
result = self._total_size
- else:
- result = self._session.send_and_read(self._message, None)
+ elif not self._is_chunked and self._output_file is None:
+ result = GLib.Bytes.new(bytes(self._accumulated_data))
else:
result = IOError("HTTP error code %d" % self._status_code)
self.emit('complete', result)
@@ -181,26 +233,26 @@ def _complete(self):
def _check_if_finished(self):
# To finish (for both successful completion and cancellation), we
# require two conditions to become true:
- # 1. Soup message callback has been called
+ # 1. Soup message callback has been called and stream fully read
# 2. Any pending output file write completes
# Those conditions can become true in either order.
if not self._output_stream:
- self._complete()
+ if self._read_finished:
+ self._complete()
return
if self._cancelling or not self._pending_buffers:
- if self._status_code is not None \
- and not self._output_stream.has_pending():
+ if self._read_finished and not self._output_stream.has_pending():
self._complete()
return
self._write_next_buffer()
def _write_next_buffer(self):
- if not self._output_stream.has_pending():
+ if self._output_stream and not self._output_stream.has_pending() and self._pending_buffers:
data = self._pending_buffers.pop(0)
self._output_stream.write_bytes_async(data, GLib.PRIORITY_LOW,
- None, self.__write_async_cb,
+ self._cancellable, self.__write_async_cb,
None)
def _get_temp_file_path(self, uri):
diff --git a/src/jarabe/util/httprange.py b/src/jarabe/util/httprange.py
index 91234a7e54..a04cb1b245 100644
--- a/src/jarabe/util/httprange.py
+++ b/src/jarabe/util/httprange.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008-2013 One Laptop per Child
+# Copyright (C) 2008-2013 One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
"""
A simple HTTP-based file-like object that supports seek() via the HTTP
Range header. This means it doesn't have to download the whole file just
@@ -48,7 +47,8 @@ def _do_download(self, method, **kwargs):
getattr(downloader, method)(**kwargs)
while not self._complete:
- Gtk.main_iteration()
+ from gi.repository import GLib
+ GLib.MainContext.default().iteration(may_block=True)
if isinstance(self._result, Exception):
raise self._result
diff --git a/src/jarabe/util/normalize.py b/src/jarabe/util/normalize.py
index 276f051a0a..2aade2a8af 100644
--- a/src/jarabe/util/normalize.py
+++ b/src/jarabe/util/normalize.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Copyright (C) 2012 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
diff --git a/src/jarabe/util/telepathy/connection_watcher.py b/src/jarabe/util/telepathy/connection_watcher.py
index b579402bd1..42717644fa 100644
--- a/src/jarabe/util/telepathy/connection_watcher.py
+++ b/src/jarabe/util/telepathy/connection_watcher.py
@@ -1,4 +1,4 @@
-# This should eventually land in telepathy-python, so has the same license:
+# This should eventually land in telepathy-python, so has the same license:
# Copyright (C) 2008 Collabora Ltd.
#
# This program is free software; you can redistribute it and/or modify
@@ -32,7 +32,6 @@
CONNECTION_STATUS_CONNECTED = TelepathyGLib.ConnectionStatus.CONNECTED
CONNECTION_STATUS_DISCONNECTED = TelepathyGLib.ConnectionStatus.DISCONNECTED
-
_instance = None
@@ -45,7 +44,7 @@ class ConnectionWatcher(GObject.GObject):
}
def __init__(self, bus=None):
- GObject.GObject.__init__(self)
+ super().__init__()
if bus is None:
self.bus = dbus.Bus()
@@ -140,7 +139,7 @@ def _remove_connection(self, service_name, path):
def get_connections(self):
return list(self._connections.values())
- def __error_handler_cb(exception):
+ def __error_handler_cb(self, exception):
logging.debug(
'Exception from asynchronous method call:\n%s' % exception)
@@ -151,7 +150,6 @@ def get_instance():
_instance = ConnectionWatcher()
return _instance
-
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
@@ -165,5 +163,6 @@ def connection_removed_cb(conn_watcher, conn):
watcher.connect('connection-added', connection_added_cb)
watcher.connect('connection-removed', connection_removed_cb)
- loop = GObject.MainLoop()
+ from gi.repository import GLib
+ loop = GLib.MainLoop()
loop.run()
diff --git a/src/jarabe/view/alerts.py b/src/jarabe/view/alerts.py
index ba1b5d5409..5781675351 100644
--- a/src/jarabe/view/alerts.py
+++ b/src/jarabe/view/alerts.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Sugar Labs
+# Copyright (C) 2013 Sugar Labs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -58,11 +58,11 @@ def show_multiple_instance_alert(window, activity_name):
alert = MultipleInstanceAlert(activity_name)
alert.connect('response', _alert_response_cb, window)
window.add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def show_max_open_activities_alert(window):
alert = MaxOpenActivitiesAlert()
alert.connect('response', _alert_response_cb, window)
window.add_alert(alert)
- alert.show()
+ alert.set_visible(True)
diff --git a/src/jarabe/view/buddyicon.py b/src/jarabe/view/buddyicon.py
index 46c1aeaecc..30555fd10a 100644
--- a/src/jarabe/view/buddyicon.py
+++ b/src/jarabe/view/buddyicon.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
from jarabe.view.buddymenu import BuddyMenu
from jarabe.util.normalize import normalize_string
-
_FILTERED_ALPHA = 0.33
@@ -56,12 +55,12 @@ def _update_color(self):
palette = self.get_palette()
self.props.xo_color = self._buddy.get_color()
if self._filtered:
- self.alpha = _FILTERED_ALPHA
+ self.set_opacity(_FILTERED_ALPHA)
if palette is not None:
palette.props.icon.props.stroke_color = self.props.stroke_color
palette.props.icon.props.fill_color = self.props.fill_color
else:
- self.alpha = 1.0
+ self.set_opacity(1.0)
if palette is not None:
palette.props.icon.props.xo_color = self._buddy.get_color()
diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py
index a573629d1d..7dbceb0c47 100644
--- a/src/jarabe/view/buddymenu.py
+++ b/src/jarabe/view/buddymenu.py
@@ -21,10 +21,11 @@
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GLib
+from gi.repository import GObject
import dbus
from sugar4.graphics.palette import Palette
-from sugar4.graphics.palettemenu import PaletteMenuItem
+from sugar4.graphics.palettemenu import PaletteMenuItem, PaletteMenuBox
from sugar4.graphics.icon import Icon
from sugar4.graphics.alert import TimeoutAlert
from sugar4.graphics import style
@@ -47,41 +48,48 @@ def __init__(self, buddy):
pixel_size=style.STANDARD_ICON_SIZE)
nick = buddy.get_nick()
Palette.__init__(self, None, primary_text=nick, icon=buddy_icon)
- self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ self.menu_box = PaletteMenuBox()
self.set_content(self.menu_box)
- self.menu_box.show_all()
+ self.menu_box.set_visible(True)
self._invite_menu = None
self._active_activity_changed_hid = None
- # Fixme: we need to make the widget accessible through the Palette API
- self._widget.connect('destroy', self.__destroy_cb)
- self._buddy.connect('notify::nick', self.__buddy_notify_nick_cb)
+ self._notify_nick_hid = self._buddy.connect('notify::nick', self.__buddy_notify_nick_cb)
if buddy.is_owner():
self._add_my_items()
else:
self._add_buddy_items()
- def __destroy_cb(self, menu):
+ def do_dispose(self):
if self._active_activity_changed_hid is not None:
home_model = shell.get_model()
home_model.disconnect(self._active_activity_changed_hid)
- self._buddy.disconnect_by_func(self.__buddy_notify_nick_cb)
+ self._active_activity_changed_hid = None
+ if getattr(self, '_notify_nick_hid', None) is not None:
+ if GObject.signal_handler_is_connected(self._buddy, self._notify_nick_hid):
+ self._buddy.disconnect(self._notify_nick_hid)
+ self._notify_nick_hid = None
+ super().do_dispose()
def _add_buddy_items(self):
menu_item = None
if friends.get_model().has_buddy(self._buddy):
menu_item = PaletteMenuItem(_('Remove friend'), 'list-remove')
- menu_item.connect('activate', self._remove_friend_cb)
+ menu_item.connect('item-activated', self._remove_friend_cb)
else:
menu_item = PaletteMenuItem(_('Make friend'), 'list-add')
- menu_item.connect('activate', self._make_friend_cb)
+ menu_item.connect('item-activated', self._make_friend_cb)
- self.menu_box.pack_start(menu_item, True, True, 0)
+ menu_item.set_hexpand(True)
+ self.menu_box.append_item(menu_item)
+ menu_item.set_visible(True)
self._invite_menu = PaletteMenuItem('')
- self._invite_menu.connect('activate', self._invite_friend_cb)
- self.menu_box.pack_start(self._invite_menu, True, True, 0)
+ self._invite_menu.connect('item-activated', self._invite_friend_cb)
+ self._invite_menu.set_hexpand(True)
+ self.menu_box.append_item(self._invite_menu)
+ self._invite_menu.set_visible(True)
home_model = shell.get_model()
self._active_activity_changed_hid = home_model.connect(
@@ -107,25 +115,30 @@ def _add_my_items(self):
if show_shutdown:
item = PaletteMenuItem(_('Shutdown'), 'system-shutdown')
- item.connect('activate', self.__shutdown_activate_cb)
- self.menu_box.pack_start(item, True, True, 0)
+ item.connect('item-activated', self.__shutdown_activate_cb)
+ item.set_hexpand(True)
+ self.menu_box.append_item(item)
+ item.set_visible(True)
if show_restart:
item = PaletteMenuItem(_('Restart'), 'system-restart')
- item.connect('activate', self.__reboot_activate_cb)
- self.menu_box.pack_start(item, True, True, 0)
- item.show()
+ item.connect('item-activated', self.__reboot_activate_cb)
+ item.set_hexpand(True)
+ self.menu_box.append_item(item)
+ item.set_visible(True)
if show_logout:
item = PaletteMenuItem(_('Logout'), 'system-logout')
- item.connect('activate', self.__logout_activate_cb)
- self.menu_box.pack_start(item, True, True, 0)
- item.show()
+ item.connect('item-activated', self.__logout_activate_cb)
+ item.set_hexpand(True)
+ self.menu_box.append_item(item)
+ item.set_visible(True)
item = PaletteMenuItem(_('My Settings'), 'preferences-system')
- item.connect('activate', self.__controlpanel_activate_cb)
- self.menu_box.pack_start(item, True, True, 0)
- item.show()
+ item.connect('item-activated', self.__controlpanel_activate_cb)
+ item.set_hexpand(True)
+ self.menu_box.append_item(item)
+ item.set_visible(True)
def _quit(self, action):
jarabe.desktop.homewindow.get_instance().busy()
@@ -140,11 +153,11 @@ def __quit_timeout_cb(self):
alert.connect('response', self.__quit_accept_cb)
jarabe.desktop.homewindow.get_instance().add_alert(alert)
- alert.show()
+ alert.set_visible(True)
def __quit_accept_cb(self, alert, response_id):
jarabe.desktop.homewindow.get_instance().remove_alert(alert)
- if response_id is Gtk.ResponseType.CANCEL:
+ if response_id == Gtk.ResponseType.CANCEL:
get_session_manager().cancel_shutdown()
else:
jarabe.desktop.homewindow.get_instance().busy()
@@ -167,7 +180,7 @@ def __controlpanel_activate_cb(self, menu_item):
# show the control panel
panel = ControlPanel()
- panel.show()
+ panel.set_visible(True)
def _update_invite_menu(self, activity):
buddy_activity = self._buddy.props.current_activity
@@ -176,7 +189,7 @@ def _update_invite_menu(self, activity):
else:
buddy_activity_id = None
- self._invite_menu.hide()
+ self._invite_menu.set_visible(False)
if activity is None or \
activity.is_journal() or \
activity.get_activity_id() == buddy_activity_id:
@@ -190,10 +203,10 @@ def _update_invite_menu(self, activity):
icon = Icon(file=activity.get_icon_path(),
pixel_size=style.SMALL_ICON_SIZE)
icon.props.xo_color = activity.get_icon_color()
- self._invite_menu.set_image(icon)
- icon.show()
+ self._invite_menu.set_icon_widget(icon)
+ icon.set_visible(True)
- self._invite_menu.show()
+ self._invite_menu.set_visible(True)
def _cur_activity_changed_cb(self, home_model, activity_model):
self._update_invite_menu(activity_model)
@@ -224,4 +237,4 @@ def _invite_friend_cb(self, menuitem):
else:
raise
else:
- logging.error('Invite failed, activity service not ')
+ logging.error('Invite failed, activity service not found')
diff --git a/src/jarabe/view/cursortracker.py b/src/jarabe/view/cursortracker.py
index a906234d30..b6ec728295 100644
--- a/src/jarabe/view/cursortracker.py
+++ b/src/jarabe/view/cursortracker.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 One Laptop Per Child
+# Copyright (C) 2012 One Laptop Per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,7 +17,11 @@
from gi.repository import Gdk
-from gi.repository import SugarExt
+try:
+ #from gi.repository import SugarExt
+ SugarExt = None
+except ImportError:
+ SugarExt = None
_instance = None
@@ -37,11 +41,13 @@ def setup():
display = Gdk.Display.get_default()
seat = display.get_default_seat()
- devices = seat.get_slaves(Gdk.SeatCapabilities.TOUCH)
- if len(devices) > 0:
- logging.debug('Cursor Tracker: found touchscreen, '
- 'will track input.')
- _instance = SugarExt.CursorTracker()
+ if seat:
+ devices = seat.get_devices(Gdk.SeatCapabilities.TOUCH)
+ if len(devices) > 0:
+ logging.debug('Cursor Tracker: found touchscreen, '
+ 'will track input.')
+ if SugarExt is not None:
+ _instance = SugarExt.CursorTracker()
if not _instance:
logging.debug('Cursor Tracker: no touchscreen available, '
diff --git a/src/jarabe/view/customizebundle.py b/src/jarabe/view/customizebundle.py
index 9e4da4dce7..f5687a0682 100644
--- a/src/jarabe/view/customizebundle.py
+++ b/src/jarabe/view/customizebundle.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Walter Bender
+# Copyright (C) 2011 Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
import hashlib
from gi.repository import Gtk
+from gi.repository import Gdk
from sugar4 import profile
from sugar4.activity import bundlebuilder
@@ -27,7 +28,6 @@
import logging
_logger = logging.getLogger('CustomizeBundle')
-
BADGE_SUBPATH = 'emblems/emblem-view-source.svg'
BADGE_TRANSFORM = ' \n'
ICON_TRANSFORM = ' \n'
@@ -153,8 +153,19 @@ def _create_custom_icon(new_basename, icon_name):
"""
user_activities_path = get_user_activities_path()
badge_path = None
- for path in Gtk.IconTheme.get_default().get_search_path():
- if os.path.exists(os.path.join(path, 'sugar', 'scalable',
+
+ try:
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
+ except AttributeError:
+ icon_theme = Gtk.IconTheme.get_default()
+
+ for f in icon_theme.get_search_path():
+ if hasattr(f, 'get_path'):
+ path = f.get_path()
+ else:
+ path = f
+
+ if path and os.path.exists(os.path.join(path, 'sugar', 'scalable',
BADGE_SUBPATH)):
badge_path = path
break
diff --git a/src/jarabe/view/gesturehandler.py b/src/jarabe/view/gesturehandler.py
index fe3eebcc35..a257f309d1 100644
--- a/src/jarabe/view/gesturehandler.py
+++ b/src/jarabe/view/gesturehandler.py
@@ -13,61 +13,45 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from gi.repository import Gdk
+import gi
+gi.require_version('SugarExt', '2.0')
+from gi.repository import Gdk
from gi.repository import SugarExt
-from gi.repository import SugarGestures
+
+try:
+ from gi.repository import Gtk
+except ImportError:
+ Gtk = None
from sugar4.graphics import style
+import logging
+
_instance = None
class GestureHandler(object):
- '''Handling gestures to show/hide the frame
-
- We use SugarExt.GestureGrabber to listen for
- gestures on the root window. We use a toggle
- gesture to either hide or show the frame: Swiping
- from the frame area at the top towards the center
- does reveal the Frame or hide it.
- '''
+ '''Handling gestures to show/hide the frame using GTK4 standard event controllers'''
def __init__(self, frame):
self._frame = frame
-
- #self._gesture_grabber = SugarExt.GestureGrabber()
- self._controller = []
-
- screen = Gdk.Screen.get_default()
- screen.connect('size-changed', self.__size_changed_cb)
-
- self._add_controller()
-
- def __size_changed_cb(self, screen):
- self._add_controller()
-
- def _add_controller(self):
- for controller in self._controller:
- #self._gesture_grabber.remove(controller)
-
- self._track_gesture_for_area(SugarGestures.SwipeDirectionFlags.DOWN,
- 0, 0, Gdk.Screen.width(),
- style.GRID_CELL_SIZE)
-
- def _track_gesture_for_area(self, directions, x, y, width, height):
- rectangle = Gdk.Rectangle()
- rectangle.x = x
- rectangle.y = y
- rectangle.width = width
- rectangle.height = height
- swipe = SugarGestures.SwipeController(directions=directions)
- swipe.connect('swipe-ended', self.__swipe_ended_cb)
- self._gesture_grabber.add(swipe, rectangle)
- self._controller.append(swipe)
-
- def __swipe_ended_cb(self, controller, event_direction):
- self._frame.toggle()
+ self._controller = Gtk.GestureSwipe.new()
+ self._controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
+ self._controller.connect('swipe', self.__swipe_cb)
+
+ # Attach to master window
+ from jarabe.model import shell
+ app = shell.get_model()
+ if app:
+ windows = app.get_windows()
+ if windows:
+ windows[0].add_controller(self._controller)
+
+ def __swipe_cb(self, gesture, velocity_x, velocity_y):
+ # A simple swipe detection: if velocity is high enough, toggle frame
+ if abs(velocity_x) > 500 or abs(velocity_y) > 500:
+ self._frame.toggle()
def setup(frame):
diff --git a/src/jarabe/view/keyhandler.py b/src/jarabe/view/keyhandler.py
index 0e6b171fee..603f032e91 100644
--- a/src/jarabe/view/keyhandler.py
+++ b/src/jarabe/view/keyhandler.py
@@ -17,12 +17,17 @@
import os
import logging
import importlib
+import types
from gi.repository import GLib
+from gi.repository import Gtk
from gi.repository import Gdk
#from gi.repository import SugarExt
-from sugar4.test import uitree
+try:
+ from sugar4.test import uitree
+except ImportError:
+ uitree = None
from jarabe.model.sound import sound
from jarabe.model import shell
@@ -33,11 +38,9 @@
from jarabe.journal import journalactivity
from jarabe.controlpanel.gui import ControlPanel
-
_VOLUME_STEP = sound.VOLUME_STEP
_VOLUME_MAX = 100
-_TABBING_MODIFIER = Gdk.ModifierType.MOD1_MASK
-
+_TABBING_MODIFIER = getattr(Gdk.ModifierType, 'ALT_MASK', getattr(Gdk.ModifierType, 'MOD1_MASK', 0))
_actions_table = {
'F1': 'zoom_mesh',
@@ -79,12 +82,7 @@ def __init__(self, frame):
self._key_pressed = None
self._keycode_pressed = 0
self._keystate_pressed = 0
-
- #self._key_grabber = SugarExt.KeyGrabber()
- #self._key_grabber.connect('key-pressed',
- # self._key_pressed_cb)
- #self._key_grabber.connect('key-released',
- # self._key_released_cb)
+ self._handled_windows = set()
self._tabbing_handler = TabbingHandler(self._frame, _TABBING_MODIFIER)
@@ -93,7 +91,7 @@ def __init__(self, frame):
module_name = f[:-3]
try:
logging.debug('Loading module %r', module_name)
- module = importlib.import_module('globalkey.' + module_name, globals())
+ module = importlib.import_module('globalkey.' + module_name)
for key in module.BOUND_KEYS:
if key in _actions_table:
raise ValueError('Key %r is already bound' % key)
@@ -101,7 +99,28 @@ def __init__(self, frame):
except Exception:
logging.exception('Exception while loading extension:')
- #self._key_grabber.grab_keys(list(_actions_table.keys()))
+ app = shell.get_model()
+ if app is not None:
+ main_window = getattr(app, '_main_window', None)
+ if main_window is not None:
+ self.add_window(main_window)
+ app.connect('window-added', self._on_window_added)
+
+ def _on_window_added(self, application, window):
+ if isinstance(window, Gtk.Window):
+ self.add_window(window)
+
+ def add_window(self, window):
+ if window in self._handled_windows:
+ return
+ self._handled_windows.add(window)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self._key_pressed_cb)
+ key_controller.connect('key-released', self._key_released_cb)
+ key_controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE)
+ window.add_controller(key_controller)
+
def _change_volume(self, step=None, value=None):
if step is not None:
@@ -122,7 +141,7 @@ def handle_next_window(self, event_time):
def handle_close_window(self, event_time):
active_activity = shell.get_model().get_active_activity()
- if active_activity.is_journal():
+ if active_activity is None or active_activity.is_journal():
return
active_activity.stop()
@@ -137,7 +156,14 @@ def handle_zoom_home(self, event_time):
shell.get_model().set_zoom_level(ShellModel.ZOOM_HOME, event_time)
def handle_zoom_activity(self, event_time):
- shell.get_model().set_zoom_level(ShellModel.ZOOM_ACTIVITY, event_time)
+ model = shell.get_model()
+ if model.get_active_activity() is None:
+ # No activity open: show Journal instead of the empty compositor.
+ # Use get_journal() so the journal is initialized if needed —
+ # same pattern as handle_open_search (F5).
+ journalactivity.get_journal().reveal()
+ else:
+ model.set_zoom_level(ShellModel.ZOOM_ACTIVITY, event_time)
def handle_volume_max(self, event_time):
self._change_volume(value=_VOLUME_MAX)
@@ -167,19 +193,22 @@ def handle_logout(self, event_time):
GLib.timeout_add_seconds(3, session_manager.shutdown_completed)
def handle_open_search(self, event_time):
- journalactivity.get_journal().show_journal()
+ try:
+ journal = journalactivity.get_journal()
+ journal.show_journal()
+ except Exception as e:
+ logging.exception('Error in handle_open_search')
def handle_open_controlpanel(self, event_time):
shell_model = shell.get_model()
activity = shell_model.get_active_activity()
- if activity.has_shell_window():
+ if activity is not None and activity.has_shell_window():
return
- bundle_path = activity.get_bundle_path()
+ bundle_path = activity.get_bundle_path() if activity is not None else None
if bundle_path is None:
window_id = ""
else:
- # get activity name and window id
window_id = activity.get_bundle_id()
if shell.get_model().has_modal():
@@ -190,64 +219,85 @@ def handle_open_controlpanel(self, event_time):
panel = ControlPanel(window_id)
activity.push_shell_window(panel)
panel.connect('hide', activity.pop_shell_window)
- panel.show()
+ panel.set_visible(True)
def handle_dump_ui_tree(self, event_time):
- print(uitree.get_root().dump())
-
- def _key_pressed_cb(self, grabber, keycode, state, event_time):
- key = grabber.get_key(keycode, state)
- logging.debug('_key_pressed_cb: %i %i %s', keycode, state, key)
- if key is not None:
- self._key_pressed = key
- self._keycode_pressed = keycode
- self._keystate_pressed = state
-
- # avoid switch to the Journal or change views if a modal dialog
- # is opened http://bugs.sugarlabs.org/ticket/4601
- if key in _non_modal_action_keys and \
- shell.get_model().has_modal():
- logging.debug(
- 'Key %s action stopped due to modal dialog open', key)
- return
-
+ if uitree:
+ print(uitree.get_root().dump())
+ else:
+ logging.warning("uitree module not available.")
+
+ def _key_pressed_cb(self, controller, keyval, keycode, state):
+ key = Gdk.keyval_name(keyval)
+ if not key:
+ return False
+
+ # Add XF86 prefix back for media keys so they match _actions_table
+ if key in ['AudioMute', 'AudioLowerVolume', 'AudioRaiseVolume', 'MenuKB', 'WebCam', 'Search']:
+ key = 'XF86' + key
+
+ # Handle Alt+ combinations
+ if state & Gdk.ModifierType.ALT_MASK:
+ if key == 'Tab':
+ if state & Gdk.ModifierType.SHIFT_MASK:
+ key = 'Tab'
+ else:
+ key = 'Tab'
+ elif key == 'Escape':
+ key = 'Escape'
+ elif key in ['F11', 'F12']:
+ key = '' + key
+ elif key in ['f', 'm', 'o', 'q', 'd']:
+ if state & Gdk.ModifierType.SHIFT_MASK:
+ key = '' + key
+
+ if key in _actions_table:
action = _actions_table[key]
+
+ if key in _non_modal_action_keys and shell.get_model().has_modal():
+ logging.debug('Key %s action stopped due to modal dialog open', key)
+ return True
+
+ event = controller.get_current_event()
+ event_time = event.get_time() if event else 0
+
if self._tabbing_handler.is_tabbing():
- # Only accept window tabbing events, everything else
- # cancels the tabbing operation.
if action not in ['next_window', 'previous_window']:
self._tabbing_handler.stop(event_time)
return True
- if hasattr(action, 'handle_key_press'):
+ if isinstance(action, types.ModuleType) and hasattr(action, 'handle_key_press'):
action.handle_key_press(key)
elif isinstance(action, str):
method = getattr(self, 'handle_' + action)
method(event_time)
else:
- raise TypeError('Invalid action %r' % action)
+ logging.error('Invalid action %r', action)
return True
- # If this is not a registered key, then cancel tabbing.
+
if self._tabbing_handler.is_tabbing():
- if not grabber.is_modifier(keycode):
- self._tabbing_handler.stop(event_time)
+ is_modifier = keyval in [Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, Gdk.KEY_Meta_L, Gdk.KEY_Meta_R, Gdk.KEY_Shift_L, Gdk.KEY_Shift_R]
+ if not is_modifier:
+ event = controller.get_current_event()
+ self._tabbing_handler.stop(event.get_time() if event else 0)
return True
return False
- def _key_released_cb(self, grabber, keycode, state, event_time):
+ def _key_released_cb(self, controller, keyval, keycode, state):
logging.debug('_key_released_cb: %i %i', keycode, state)
if self._tabbing_handler.is_tabbing():
- # We stop tabbing and switch to the new window as soon as the
- # modifier key is raised again.
- if grabber.is_modifier(keycode, mask=_TABBING_MODIFIER):
- self._tabbing_handler.stop(event_time)
-
- return True
- return False
+ # stop tabbing and switch window when the modifier is released
+ if keyval in [Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, Gdk.KEY_Meta_L, Gdk.KEY_Meta_R]:
+ event = controller.get_current_event()
+ self._tabbing_handler.stop(event.get_time() if event else 0)
def setup(frame):
global _instance
_instance = KeyHandler(frame)
+
+
+def get_instance():
+ return _instance
diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py
index 2c1aa3b0b4..3c81095565 100644
--- a/src/jarabe/view/launcher.py
+++ b/src/jarabe/view/launcher.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, Red Hat, Inc.
+# Copyright (C) 2008, Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,41 +19,56 @@
from gi.repository import Gio
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import GObject
-from gi.repository import SugarExt
from sugar4.graphics import style
from jarabe.model import shell
from jarabe.view.pulsingicon import PulsingIcon
-
_INTERVAL = 100
-class LaunchWindow(Gtk.Window):
+class LaunchWindow(Gtk.Box):
def __init__(self, activity_id, icon_path, icon_color):
- Gtk.Window.__init__(self)
- self.set_has_resize_grip(False)
-
- self.props.type_hint = Gdk.WindowTypeHint.SPLASHSCREEN
- self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color())
+ super().__init__()
+
+ self.add_css_class('launch-window')
+
+ provider = Gtk.CssProvider()
+ css = b".launch-window { background-color: %s; }" % style.COLOR_WHITE.get_html().encode('utf-8')
+ provider.load_from_data(css)
+ Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+ self.set_hexpand(True)
+ self.set_vexpand(True)
canvas = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- canvas.show()
- self.add(canvas)
+ canvas.set_visible(True)
+ self.append(canvas)
+
+ display = Gdk.Display.get_default()
+ screen_height = 768
+ screen_width = 1024
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geo = monitors.get_item(0).get_geometry()
+ screen_width, screen_height = geo.width, geo.height
- bar_size = Gdk.Screen.height() / 5 * 2
+ bar_size = screen_height / 5 * 2
header = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- header.set_size_request(-1, bar_size)
- header.show()
- canvas.pack_start(header, False, True, 0)
+ header.set_size_request(-1, int(bar_size))
+ header.set_visible(True)
+ canvas.append(header)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- box.set_size_request(Gdk.Screen.width() / 5, -1)
- box.show()
- canvas.pack_start(box, True, True, 0)
+ box.set_size_request(int(screen_width / 5), -1)
+ box.set_vexpand(True)
+ box.set_visible(True)
+ canvas.append(box)
self._activity_id = activity_id
@@ -64,59 +79,55 @@ def __init__(self, activity_id, icon_path, icon_color):
self._activity_icon.set_zooming(style.SMALL_ICON_SIZE,
style.XLARGE_ICON_SIZE, 10)
self._activity_icon.set_pulsing(True)
- self._activity_icon.show()
- box.pack_start(self._activity_icon, True, False, 0)
+ self._activity_icon.set_visible(True)
+
+ self._activity_icon.set_hexpand(True)
+ self._activity_icon.set_halign(Gtk.Align.CENTER)
+ self._activity_icon.set_valign(Gtk.Align.CENTER)
+ box.append(self._activity_icon)
footer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=style.DEFAULT_SPACING)
- footer.set_size_request(-1, bar_size)
- footer.show()
- canvas.pack_end(footer, False, True, 0)
+ footer.set_size_request(-1, int(bar_size))
+ footer.set_visible(True)
+ canvas.append(footer)
self.error_text = Gtk.Label()
self.error_text.props.use_markup = True
- footer.pack_start(self.error_text, False, True, 0)
+ footer.append(self.error_text)
- button_box = Gtk.Alignment.new(0.5, 0, 0, 0)
- button_box.show()
- footer.pack_start(button_box, False, True, 0)
- self.cancel_button = Gtk.Button(stock=Gtk.STOCK_STOP)
- button_box.add(self.cancel_button)
+ button_box = Gtk.Box()
+ button_box.set_halign(Gtk.Align.CENTER)
+ button_box.set_valign(Gtk.Align.START)
+ button_box.set_visible(True)
+ footer.append(button_box)
+
+ self.cancel_button = Gtk.Button.new_from_icon_name('process-stop')
+ button_box.append(self.cancel_button)
- self.connect('realize', self.__realize_cb)
-
- screen = Gdk.Screen.get_default()
- screen.connect('size-changed', self.__size_changed_cb)
+ if display:
+ monitors = display.get_monitors()
+ monitors.connect('items-changed', self.__size_changed_cb)
self._home = shell.get_model()
self._home.connect('active-activity-changed',
self.__active_activity_changed_cb)
-
- self.connect('destroy', self.__destroy_cb)
+ self.connect('unrealize', self.__unrealize_cb)
self._update_size()
- def show(self):
- self.present()
-
def _update_size(self):
- self.resize(Gdk.Screen.width(), Gdk.Screen.height())
-
- def __realize_cb(self, widget):
- window = widget.get_window()
- data = GObject.GObject()
- setattr(data, 'activity_id', str(self._activity_id))
- window.set_user_data(data)
+ pass
- def __size_changed_cb(self, screen):
+ def __size_changed_cb(self, list_model, position, removed, added):
self._update_size()
def __active_activity_changed_cb(self, model, activity):
- if activity.get_activity_id() == self._activity_id:
+ if activity is not None and activity.get_activity_id() == self._activity_id:
self._activity_icon.props.paused = False
else:
self._activity_icon.props.paused = True
- def __destroy_cb(self, box):
+ def __unrealize_cb(self, widget):
self._activity_icon.props.pulsing = False
self._home.disconnect_by_func(self.__active_activity_changed_cb)
@@ -140,9 +151,9 @@ def add_launcher(activity_id, icon_path, icon_color):
return
launch_window = LaunchWindow(activity_id, icon_path, icon_color)
- launch_window.show()
- model.add_window(launch_window)
+ model.stack.add_named(launch_window, f"launcher_{activity_id}")
+ model.stack.set_visible_child_name(f"launcher_{activity_id}")
model.register_launcher(activity_id, launch_window)
@@ -160,12 +171,12 @@ def __launch_failed_cb(home_model, home_activity):
else:
launcher.error_text.props.label = _('%s failed to start.') % \
home_activity.get_activity_name()
- launcher.error_text.show()
+ launcher.error_text.set_visible(True)
launcher.cancel_button.connect('clicked',
__cancel_button_clicked_cb,
home_activity)
- launcher.cancel_button.show()
+ launcher.cancel_button.set_visible(True)
def __cancel_button_clicked_cb(button, home_activity):
@@ -179,12 +190,16 @@ def __launch_completed_cb(home_model, home_activity):
def _destroy_launcher(home_activity):
activity_id = home_activity.get_activity_id()
- launcher = shell.get_model().get_launcher(activity_id)
+ model = shell.get_model()
+ launcher = model.get_launcher(activity_id)
if launcher is None:
if not home_activity.is_journal():
logging.error('Launcher was not registered for %s', activity_id)
return
- shell.get_model().unregister_launcher(activity_id)
- shell.get_model().remove_window(launcher)
- launcher.destroy()
+ model.unregister_launcher(activity_id)
+ if launcher.get_parent() == model.stack:
+ model.stack.remove(launcher)
+ if model.stack.get_visible_child() is None:
+ model.stack.set_visible_child_name("home")
+
diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py
index 189cdbf1df..80f50a5841 100644
--- a/src/jarabe/view/palettes.py
+++ b/src/jarabe/view/palettes.py
@@ -32,7 +32,7 @@
from sugar4.graphics.icon import Icon
from sugar4.graphics import style
from sugar4.graphics.xocolor import XoColor
-from sugar4.activity.i18n import pgettext
+from gettext import pgettext
from jarabe.model import shell
from jarabe.view.viewsource import setup_view_source
@@ -97,58 +97,56 @@ def setup_palette(self):
self.menu_box = PaletteMenuBox()
menu_item = PaletteMenuItem(_('Resume'), 'activity-start')
- menu_item.connect('activate', self.__resume_activate_cb)
+ menu_item.connect('item-activated', self.__resume_activate_cb)
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
# TODO: share-with, keep
menu_item = PaletteMenuItem(_('View Source'), 'view-source')
- menu_item.connect('activate', self.__view_source__cb)
+ menu_item.connect('item-activated', self.__view_source__cb)
menu_item.set_accelerator('Shift+Alt+V')
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
if should_show_view_help(self._home_activity):
menu_item = PaletteMenuItem(_('View Help'), 'toolbar-help')
- menu_item.connect('activate', self.__view_help__cb)
+ menu_item.connect('item-activated', self.__view_help__cb)
menu_item.set_accelerator('Shift+Alt+H')
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
# avoid circular importing reference
from jarabe.frame.notification import NotificationBox
menu_item = NotificationBox(self._home_activity.get_activity_id())
- self.menu_box.append_item(menu_item, 0, 0)
+ self.menu_box.append_item(menu_item)
separator = PaletteMenuItemSeparator()
- menu_item.add(separator)
- menu_item.reorder_child(separator, 0)
- separator.show()
+ menu_item.prepend(separator)
+ separator.set_visible(True)
separator = PaletteMenuItemSeparator()
self.menu_box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
menu_item = PaletteMenuItem(_('Stop'), 'activity-stop')
- menu_item.connect('activate', self.__stop_activate_cb)
+ menu_item.connect('item-activated', self.__stop_activate_cb)
self.menu_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
self.set_content(self.menu_box)
- self.menu_box.show()
+ self.menu_box.set_visible(True)
def __resume_activate_cb(self, menu_item):
- self._home_activity.get_window().activate(Gtk.get_current_event_time())
+ shell.get_model().activate_activity(self._home_activity)
self.emit('done')
def __view_source__cb(self, menu_item):
setup_view_source(self._home_activity)
shell_model = shell.get_model()
if self._home_activity is not shell_model.get_active_activity():
- self._home_activity.get_window().activate(
- Gtk.get_current_event_time())
+ shell_model.activate_activity(self._home_activity)
self.emit('done')
def __view_help__cb(self, menu_item):
@@ -165,8 +163,9 @@ def __stop_activate_cb(self, menu_item):
def __kill_cb(self, pid, sig):
try:
os.kill(pid, sig)
- finally:
- return False
+ except (OSError, ProcessLookupError):
+ pass
+ return False
class ActivityPalette(Palette):
@@ -189,11 +188,11 @@ def __init__(self, activity_info):
menu_item = PaletteMenuItem(text_label=_('Start new'),
file_name=activity_info.get_icon(),
xo_color=xo_color)
- menu_item.connect('activate', self.__start_activate_cb)
- self.menu_box.pack_end(menu_item, True, True, 0)
- menu_item.show()
+ menu_item.connect('item-activated', self.__start_activate_cb)
+ self.menu_box.append_item(menu_item)
+ menu_item.set_visible(True)
self.set_content(self.menu_box)
- self.menu_box.show_all()
+ self.menu_box.set_visible(True)
# TODO: start-with
@@ -209,48 +208,51 @@ def __init__(self, home_activity):
self._free_space_label = None
BasePalette.__init__(self, home_activity)
- self._label.set_accel(Gdk.KEY_F5, 0)
def setup_palette(self):
- self.set_primary_text(self._home_activity.get_title())
+ self.set_primary_text(self._home_activity.get_title(), accel_path='F5')
box = PaletteMenuBox()
self.set_content(box)
- box.show()
+ box.set_visible(True)
menu_item = PaletteMenuItem(_('Show contents'))
icon = Icon(file=self._home_activity.get_icon_path(),
pixel_size=style.SMALL_ICON_SIZE,
xo_color=self._home_activity.get_icon_color())
- menu_item.set_image(icon)
- icon.show()
+
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
- menu_item.connect('activate', self.__open_activate_cb)
+ menu_item.connect('item-activated', self.__open_activate_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
inner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
inner_box.set_spacing(style.DEFAULT_PADDING)
box.append_item(inner_box, vertical_padding=0)
- inner_box.show()
+ inner_box.set_visible(True)
self._progress_bar = Gtk.ProgressBar()
- inner_box.add(self._progress_bar)
- self._progress_bar.show()
+ self._progress_bar.set_hexpand(True)
+ inner_box.append(self._progress_bar)
+ self._progress_bar.set_visible(True)
self._free_space_label = Gtk.Label()
- self._free_space_label.set_alignment(0.5, 0.5)
- inner_box.add(self._free_space_label)
- self._free_space_label.show()
+ self._free_space_label.set_halign(Gtk.Align.CENTER)
+ self._free_space_label.set_valign(Gtk.Align.CENTER)
+ self._free_space_label.set_hexpand(True)
+ inner_box.append(self._free_space_label)
+ self._free_space_label.set_visible(True)
self.connect('popup', self.__popup_cb)
def __open_activate_cb(self, menu_item):
- self._home_activity.get_window().activate(Gtk.get_current_event_time())
+ shell.get_model().activate_activity(self._home_activity)
def __popup_cb(self, palette):
stat = os.statvfs(env.get_profile_path())
@@ -273,42 +275,50 @@ def __init__(self, mount):
self.content_box = PaletteMenuBox()
self.set_content(self.content_box)
- self.content_box.show()
+ self.content_box.set_visible(True)
menu_item = PaletteMenuItem(pgettext('Volume', 'Remove'))
icon = Icon(icon_name='media-eject', pixel_size=style.SMALL_ICON_SIZE)
- menu_item.set_image(icon)
- icon.show()
+ menu_item.set_icon_widget(icon)
+ icon.set_visible(True)
- menu_item.connect('activate', self.__unmount_activate_cb)
+ menu_item.connect('item-activated', self.__unmount_activate_cb)
self.content_box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
separator = PaletteMenuItemSeparator()
self.content_box.append_item(separator)
- separator.show()
+ separator.set_visible(True)
free_space_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
free_space_box.set_spacing(style.DEFAULT_PADDING)
- self.content_box.append_item(free_space_box, vertical_padding=0)
- free_space_box.show()
+ self.content_box.append_item(free_space_box)
+ free_space_box.set_visible(True)
self._progress_bar = Gtk.ProgressBar()
- free_space_box.pack_start(self._progress_bar, True, True, 0)
- self._progress_bar.show()
+ self._progress_bar.set_hexpand(True)
+ free_space_box.append(self._progress_bar)
+ self._progress_bar.set_visible(True)
self._free_space_label = Gtk.Label()
- self._free_space_label.set_alignment(0.5, 0.5)
- free_space_box.pack_start(self._free_space_label, True, True, 0)
- self._free_space_label.show()
+ self._free_space_label.set_halign(Gtk.Align.CENTER)
+ self._free_space_label.set_valign(Gtk.Align.CENTER)
+ self._free_space_label.set_hexpand(True)
+ free_space_box.append(self._free_space_label)
+ self._free_space_label.set_visible(True)
self.connect('popup', self.__popup_cb)
def __unmount_activate_cb(self, menu_item):
flags = 0
+ master_window = None
+ app = shell.get_model()
+ if app and app.get_windows():
+ master_window = app.get_windows()[0]
+
mount_operation = Gtk.MountOperation(
- parent=self.content_box.get_toplevel())
+ parent=master_window)
cancellable = None
user_data = None
self._mount.unmount_with_operation(flags, mount_operation, cancellable,
diff --git a/src/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py
index ac559d79d5..d9f1c41f5a 100644
--- a/src/jarabe/view/pulsingicon.py
+++ b/src/jarabe/view/pulsingicon.py
@@ -22,7 +22,6 @@
from sugar4.graphics import style
from sugar4.graphics.icon import CanvasIcon
-
_INTERVAL = 100
_STEP = math.pi / 10 # must be a fraction of pi, for clean caching
_MINIMAL_ALPHA_VALUE = 0.33
@@ -95,9 +94,8 @@ def __init__(self, interval=_INTERVAL, **kwargs):
self._pulsing = False
Icon.__init__(self, **kwargs)
-
+ self.connect('unrealize', self.__destroy_cb)
self._palette = None
- self.connect('destroy', self.__destroy_cb)
def set_pulse_color(self, pulse_color):
self._pulse_color = pulse_color
@@ -168,10 +166,10 @@ def _set_palette(self, palette):
palette = property(_get_palette, _set_palette)
- def __destroy_cb(self, icon):
+ def __destroy_cb(self, widget):
self._pulser.stop()
if self._palette is not None:
- self._palette.destroy()
+ self._palette.unparent()
class EventPulsingIcon(CanvasIcon):
@@ -185,10 +183,9 @@ def __init__(self, interval=_INTERVAL, **kwargs):
self._pulsing = False
CanvasIcon.__init__(self, **kwargs)
+ self.connect('unrealize', self.__destroy_cb)
- self.connect('destroy', self.__destroy_cb)
-
- def __destroy_cb(self, box):
+ def __destroy_cb(self, widget):
self._pulser.stop()
def set_pulse_color(self, pulse_color):
diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py
index ec17ba3d74..c9e6d27778 100644
--- a/src/jarabe/view/service.py
+++ b/src/jarabe/view/service.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd.
#
# This program is free software: you can redistribute it and/or modify
@@ -18,12 +18,10 @@
import dbus
from dbus import service
-from gi.repository import Gtk
from jarabe.model import shell
from jarabe.model import bundleregistry
-
_DBUS_SERVICE = 'org.laptop.Shell'
_DBUS_SHELL_IFACE = 'org.laptop.Shell'
_DBUS_PATH = '/org/laptop/Shell'
@@ -72,8 +70,8 @@ def ActivateActivity(self, activity_id):
"""
activity = self._shell_model.get_activity_by_id(activity_id)
- if activity is not None and activity.get_window() is not None:
- activity.get_window().activate(Gtk.get_current_event_time())
+ if activity is not None:
+ self._shell_model.activate_activity(activity)
return self._shell_model.get_launcher(activity_id) is None
return False
diff --git a/src/jarabe/view/tabbinghandler.py b/src/jarabe/view/tabbinghandler.py
index 493b775b38..edf48ca313 100644
--- a/src/jarabe/view/tabbinghandler.py
+++ b/src/jarabe/view/tabbinghandler.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, Benjamin Berg
+# Copyright (C) 2008, Benjamin Berg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
from jarabe.model import shell
-
_RAISE_DELAY = 250
@@ -31,61 +30,12 @@ def __init__(self, frame, modifier):
self._tabbing = False
self._modifier = modifier
self._timeout = None
- self._keyboard = None
- self._mouse = None
-
- display = Gdk.Display.get_default()
- device_manager = display.get_device_manager()
- devices = device_manager.list_devices(Gdk.DeviceType.MASTER)
- for device in devices:
- if device.get_source() == Gdk.InputSource.KEYBOARD:
- self._keyboard = device
- if device.get_source() == Gdk.InputSource.MOUSE:
- self._mouse = device
def _start_tabbing(self, event_time):
if not self._tabbing:
- logging.debug('Grabing the input.')
-
- screen = Gdk.Screen.get_default()
- window = screen.get_root_window()
-
- keyboard_grab_result = self._keyboard.grab(
- window,
- Gdk.GrabOwnership.WINDOW,
- False,
- Gdk.EventMask.KEY_PRESS_MASK |
- Gdk.EventMask.KEY_RELEASE_MASK,
- None,
- event_time)
-
- mouse_grab_result = self._mouse.grab(
- window,
- Gdk.GrabOwnership.WINDOW,
- False,
- Gdk.EventMask.BUTTON_PRESS_MASK |
- Gdk.EventMask.BUTTON_RELEASE_MASK,
- None,
- event_time)
-
- self._tabbing = (keyboard_grab_result == Gdk.GrabStatus.SUCCESS and
- mouse_grab_result == Gdk.GrabStatus.SUCCESS)
-
- # Now test that the modifier is still active to prevent race
- # conditions. We also test if one of the grabs failed.
- mask = window.get_device_position(self._mouse)[3]
- if not self._tabbing or not (mask & self._modifier):
- logging.debug('Releasing grabs again.')
-
- # ungrab keyboard/pointer if the grab was successfull.
- if keyboard_grab_result == Gdk.GrabStatus.SUCCESS:
- self._keyboard.ungrab(event_time)
- if mouse_grab_result == Gdk.GrabStatus.SUCCESS:
- self._mouse.ungrab(event_time)
-
- self._tabbing = False
- else:
- self._frame.show()
+ self._tabbing = True
+
+ self._frame.show()
def __timeout_cb(self, event_time):
self._activate_current(event_time)
@@ -106,8 +56,8 @@ def _cancel_timeout(self):
def _activate_current(self, event_time):
home_model = shell.get_model()
activity = home_model.get_tabbing_activity()
- if activity and activity.get_window():
- activity.get_window().activate(event_time)
+ if activity:
+ shell.get_model().activate_activity(activity)
def next_activity(self, event_time):
if not self._tabbing:
@@ -158,11 +108,9 @@ def previous_activity(self, event_time):
def _activate_next_activity(self, event_time):
next_activity = shell.get_model().get_next_activity()
if next_activity:
- next_activity.get_window().activate(event_time)
+ shell.get_model().activate_activity(next_activity)
def stop(self, event_time):
- self._keyboard.ungrab(event_time)
- self._mouse.ungrab(event_time)
self._tabbing = False
self._frame.hide()
diff --git a/src/jarabe/view/viewhelp.py b/src/jarabe/view/viewhelp.py
index bcb5b2e783..36f669edb8 100644
--- a/src/jarabe/view/viewhelp.py
+++ b/src/jarabe/view/viewhelp.py
@@ -34,7 +34,6 @@
from jarabe.model import shell
from jarabe.view.viewhelp_webkit2 import Browser
-
_logger = logging.getLogger('ViewHelp')
_MODE_HELP = 0
@@ -86,7 +85,6 @@ def get_help_url_and_title(activity):
title = activity_bundle.get_name()
link_id = activity_bundle.get_bundle_id()
- # get the help file name for the activity
activity_path = _get_help_activity_path()
if activity_path is None:
return None, title
@@ -139,7 +137,7 @@ def setup_view_help(activity):
viewhelp = ViewHelp(activity, window_id)
activity.push_shell_window(viewhelp)
viewhelp.connect('hide', activity.pop_shell_window)
- viewhelp.show()
+ viewhelp.set_visible(True)
class ViewHelp(Gtk.Window):
@@ -152,39 +150,57 @@ def __init__(self, activity, window_id):
has_local_help = url is not None
self._mode = _MODE_HELP if has_local_help else _MODE_SOCIAL_HELP
- Gtk.Window.__init__(self)
- box = Gtk.Box()
- box.set_orientation(Gtk.Orientation.VERTICAL)
- self.add(box)
- box.show()
+ super().__init__()
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ box.set_margin_start(style.LINE_WIDTH)
+ box.set_margin_end(style.LINE_WIDTH)
+ box.set_margin_top(style.LINE_WIDTH)
+ box.set_margin_bottom(style.LINE_WIDTH)
+ self.set_child(box)
+ box.set_visible(True)
self.set_modal(True)
self.set_decorated(False)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
- self.set_border_width(style.LINE_WIDTH)
- self.set_has_resize_grip(False)
- width = Gdk.Screen.width() - style.GRID_CELL_SIZE * 2
- height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 2
- self.set_size_request(width, height)
+ # Set transient parent window
+ shell_model = shell.get_model()
+ if shell_model and shell_model._main_window:
+ self.set_transient_for(shell_model._main_window)
+
+ display = Gdk.Display.get_default()
+ width = 1024 - style.GRID_CELL_SIZE * 2
+ height = 768 - style.GRID_CELL_SIZE * 2
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geo = monitors.get_item(0).get_geometry()
+ width = geo.width - style.GRID_CELL_SIZE * 2
+ height = geo.height - style.GRID_CELL_SIZE * 2
+
+ self.set_default_size(width, height)
self.connect('realize', self.__realize_cb)
self.connect('hide', self.__hide_cb)
- self.connect('key-press-event', self.__key_press_event_cb)
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
self._toolbar = Toolbar(title, has_local_help)
- box.pack_start(self._toolbar, False, False, 0)
- self._toolbar.show()
+ box.append(self._toolbar)
+ self._toolbar.set_visible(True)
self._toolbar.connect('stop-clicked', self.__stop_clicked_cb)
self._toolbar.connect('mode-changed', self.__mode_changed_cb)
self._browser = Browser(self._toolbar)
scrolled_window = Gtk.ScrolledWindow()
- scrolled_window.add(self._browser.get_widget())
- scrolled_window.show()
+ scrolled_window.set_child(self._browser.get_widget())
+ scrolled_window.set_vexpand(True)
+ scrolled_window.set_hexpand(True)
+ scrolled_window.set_visible(True)
- box.pack_start(scrolled_window, True, True, 0)
+ box.append(scrolled_window)
language = _get_current_language()
self._help_state = None
@@ -199,11 +215,13 @@ def __init__(self, activity, window_id):
self._load_mode(self._mode)
def __stop_clicked_cb(self, widget):
- self.destroy()
+ self.close()
- def __key_press_event_cb(self, window, event):
- if event.keyval == Gdk.KEY_Escape:
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ if keyval == Gdk.KEY_Escape:
self.__stop_clicked_cb(None)
+ return True
+ return False
def __mode_changed_cb(self, toolbar, mode):
if mode == _MODE_HELP:
@@ -222,14 +240,6 @@ def _load_mode(self, mode):
self._social_help_uri)
def __realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- window = self.get_window()
- window.set_accept_focus(True)
- if self.parent_window_id:
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- parent = Gdk.Window.new(None, attributes, None)
- window.set_transient_for(parent)
shell.get_model().push_modal()
def __hide_cb(self, widget):
@@ -237,7 +247,6 @@ def __hide_cb(self, widget):
def _get_help_file(self, language, help_file):
activity_path = _get_help_activity_path()
- # check if exist a page for the language selected
# if not, use the default page
path = os.path.join(activity_path, 'html', language, help_file)
if not os.path.isfile(path):
@@ -246,7 +255,7 @@ def _get_help_file(self, language, help_file):
return path
-class Toolbar(Gtk.Toolbar):
+class Toolbar(Gtk.Box):
__gsignals__ = {
'back-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
@@ -256,7 +265,7 @@ class Toolbar(Gtk.Toolbar):
}
def __init__(self, activity_name, has_local_help):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
self._add_separator(False)
@@ -267,12 +276,12 @@ def __init__(self, activity_name, has_local_help):
fill_color=style.COLOR_TRANSPARENT.get_svg(),
stroke_color=style.COLOR_WHITE.get_svg())
help_button.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
help_button.props.tooltip = _('Help Manual')
help_button.connect('toggled', self.__button_toggled_cb,
_MODE_HELP)
- self.insert(help_button, -1)
- help_button.show()
+ self.append(help_button)
+ help_button.set_visible(True)
self._add_separator(False)
social_help_button = RadioToolButton()
@@ -281,39 +290,41 @@ def __init__(self, activity_name, has_local_help):
fill_color=style.COLOR_TRANSPARENT.get_svg(),
stroke_color=style.COLOR_WHITE.get_svg())
social_help_button.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
social_help_button.props.tooltip = _('Social Help')
- social_help_button.props.group = help_button
+ social_help_button.set_group(help_button)
social_help_button.connect(
'toggled', self.__button_toggled_cb, _MODE_SOCIAL_HELP)
- self.insert(social_help_button, -1)
- social_help_button.show()
+ self.append(social_help_button)
+ social_help_button.set_visible(True)
self._add_separator(False)
self._back_button = ToolButton(icon_name='go-previous-paired')
self._back_button.props.tooltip = _('Back')
self._back_button.connect('clicked', self.__back_clicked_cb)
- self.insert(self._back_button, -1)
- self._back_button.show()
+ self.append(self._back_button)
+ self._back_button.set_visible(True)
+
self._forward_button = ToolButton(icon_name='go-next-paired')
self._forward_button.props.tooltip = _('Forward')
self._forward_button.connect('clicked', self.__forward_clicked_cb)
- self.insert(self._forward_button, -1)
- self._forward_button.show()
+ self.append(self._forward_button)
+ self._forward_button.set_visible(True)
title = _('Help: %s') % activity_name
self._label = Gtk.Label()
self._label.set_markup('%s' % title)
- self._label.set_alignment(0, 0.5)
- self._add_widget(self._label)
+ self._label.set_halign(Gtk.Align.START)
+ self._label.set_valign(Gtk.Align.CENTER)
+ self._add_widget(self._label, expand=True)
self._add_separator(True)
stop = ToolButton(icon_name='dialog-cancel')
stop.set_tooltip(_('Close'))
stop.connect('clicked', self.__stop_clicked_cb)
- self.insert(stop, -1)
- stop.show()
+ self.append(stop)
+ stop.set_visible(True)
def __stop_clicked_cb(self, widget):
self.emit('stop-clicked')
@@ -322,22 +333,20 @@ def __button_toggled_cb(self, button, mode):
if button.props.active:
self.emit('mode-changed', mode)
- def _add_widget(self, widget):
- tool_item = Gtk.ToolItem()
- tool_item.add(widget)
- widget.show()
- self.insert(tool_item, -1)
- tool_item.show()
+ def _add_widget(self, widget, expand=False):
+ if expand:
+ widget.set_hexpand(True)
+ self.append(widget)
+ widget.set_visible(True)
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ spacer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
if expand:
- separator.set_expand(True)
+ spacer.set_hexpand(True)
else:
- separator.set_size_request(style.DEFAULT_SPACING, -1)
- self.insert(separator, -1)
- separator.show()
+ spacer.set_size_request(style.DEFAULT_SPACING, -1)
+ self.append(spacer)
+ spacer.set_visible(True)
def update_back_forward(self, can_go_back, can_go_forward):
self._back_button.props.sensitive = can_go_back
diff --git a/src/jarabe/view/viewhelp_webkit2.py b/src/jarabe/view/viewhelp_webkit2.py
index 79ec956e18..a5f589dc79 100644
--- a/src/jarabe/view/viewhelp_webkit2.py
+++ b/src/jarabe/view/viewhelp_webkit2.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Kalpa Welivitigoda
+# Copyright (C) 2013 Kalpa Welivitigoda
# Copyright (C) 2015-2016 Sam Parkinson
# Copyright (C) 2016 James Cameron
#
@@ -18,16 +18,22 @@
import os
import gi
-gi.require_version('WebKit2', '4.1')
-from gi.repository import WebKit2
+try:
+ gi.require_version('WebKit', '6.0')
+ from gi.repository import WebKit
+ WEBKIT_VER = 6
+except (ValueError, ImportError):
+ WebKit = None
+ WEBKIT_VER = 6
+
from gi.repository import Gio
from sugar4 import env
def _get_current_language():
- locale = os.environ.get('LANG')
+ locale = os.environ.get('LANG', 'en_US.UTF-8')
return locale.split('.')[0].split('_')[0].lower()
@@ -36,23 +42,41 @@ class Browser():
def __init__(self, toolbar):
self._toolbar = toolbar
- context = WebKit2.WebContext.get_default()
- cookie_manager = context.get_cookie_manager()
- cookie_manager.set_persistent_storage(
- os.path.join(env.get_profile_path(), 'social-help.cookies'),
- WebKit2.CookiePersistentStorage.SQLITE)
+ if WebKit is None:
+ from gi.repository import Gtk
+ self._webview = Gtk.Label(label="WebKit 6.0 is not installed. Help cannot be displayed.")
+ self._webview.set_visible(True)
+ return
+
+ if WEBKIT_VER == 6:
+ context = WebKit.WebContext.get_default()
+ cookie_manager = context.get_cookie_manager()
+ if hasattr(cookie_manager, 'set_persistent_storage'):
+ cookie_manager.set_persistent_storage(
+ os.path.join(env.get_profile_path(), 'social-help.cookies'),
+ WebKit.CookiePersistentStorage.SQLITE)
+
+ self._webview = WebKit.WebView()
+ self._webview.get_context().register_uri_scheme(
+ 'help', self.__app_scheme_cb, None, None)
+ else:
+ context = WebKit.WebContext.get_default()
+ cookie_manager = context.get_cookie_manager()
+ cookie_manager.set_persistent_storage(
+ os.path.join(env.get_profile_path(), 'social-help.cookies'),
+ WebKit.CookiePersistentStorage.SQLITE)
- self._webview = WebKit2.WebView()
- self._webview.get_context().register_uri_scheme(
- 'help', self.__app_scheme_cb, None)
+ self._webview = WebKit.WebView()
+ self._webview.get_context().register_uri_scheme(
+ 'help', self.__app_scheme_cb, None)
self._webview.connect('load-changed', self.__load_changed_cb)
toolbar.update_back_forward(False, False)
toolbar.connect('back-clicked', self.__back_cb)
toolbar.connect('forward-clicked', self.__forward_cb)
- self._webview.show()
+ self._webview.set_visible(True)
- def __app_scheme_cb(self, request, user_data):
+ def __app_scheme_cb(self, request, user_data=None, *args):
path = request.get_path()
if path.find('_images') > -1:
if path.find('/%s/_images/' % _get_current_language()) > -1:
@@ -66,30 +90,34 @@ def __app_scheme_cb(self, request, user_data):
-1, Gio.content_type_guess(path, None)[0])
def __load_changed_cb(self, widget, event):
+ if WebKit is None: return
self._toolbar.update_back_forward(self._webview.can_go_back(),
self._webview.can_go_forward())
def __back_cb(self, widget):
+ if WebKit is None: return
self._webview.go_back()
def __forward_cb(self, widget):
+ if WebKit is None: return
self._webview.go_forward()
def save_state(self):
- return self._webview.get_session_state()
+ # WebKit 6.0 removed get_session_state()/restore_session_state().
+ # Fall back to simple URL-based state tracking.
+ if WebKit is None:
+ return None
+ uri = self._webview.get_uri()
+ return uri if uri else None
def load_state(self, state, url):
- if state is None:
- self._webview.load_uri(url)
- else:
- self._webview.restore_session_state(state)
- # this is what epiphany does:
- # https://github.com/GNOME/epiphany/blob/
- # 04e7811c32ba8a2c980a77aac1316b77f0969057/src/ephy-session.c#L280
- bf_list = self._webview.get_back_forward_list()
- item = bf_list.get_current_item()
- if item is not None:
- self._webview.go_to_back_forward_list_item(item)
+ # WebKit 6.0 removed restore_session_state().
+ # state is now just a URI string (or None).
+ if WebKit is None:
+ return
+ target_url = state if state is not None else url
+ if target_url:
+ self._webview.load_uri(target_url)
self._toolbar.update_back_forward(self._webview.can_go_back(),
self._webview.can_go_forward())
diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py
index 07e83d6eae..030be37a82 100644
--- a/src/jarabe/view/viewsource.py
+++ b/src/jarabe/view/viewsource.py
@@ -23,13 +23,17 @@
from gettext import gettext as _
import gi
-gi.require_version('GtkSource', '4')
+try:
+ gi.require_version('GtkSource', '5')
+ from gi.repository import GtkSource
+except (ValueError, ImportError):
+ GtkSource = None
+
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Pango
from gi.repository import Gtk
from gi.repository import Gdk
-from gi.repository import GtkSource
from gi.repository import GdkPixbuf
import dbus
from gi.repository import Gio
@@ -56,7 +60,8 @@
_EXCLUDE_NAMES = ['.deps', '.libs']
_IMPORT_TYPES = {'sugar3': 3, 'from gi.repository import Gtk': 3,
- 'sugar.': 2, 'import pygtk': 2, 'pygtk.require': 2}
+ 'sugar.': 2, 'import pygtk': 2, 'pygtk.require': 2,
+ 'sugar4': 4}
_SOURCE_FONT = Pango.FontDescription('Monospace %d' % style.FONT_SIZE)
@@ -100,7 +105,7 @@ def _get_toolkit_path(bundle_path, bundle_id):
if _is_gtk3_activity(bundle_path, bundle_id):
sugar_module = 'sugar3'
else:
- sugar_module = 'sugar'
+ sugar_module = 'sugar4'
for path in sys.path:
if path.endswith(('site-packages', 'dist-packages')):
@@ -123,8 +128,7 @@ def setup_view_source(activity):
'org.freedesktop.DBus.Python.NotImplementedError']
if e.get_dbus_name() not in expected_exceptions:
logging.exception('Exception occurred in HandleViewSource():')
- except Exception:
- logging.exception('Exception occurred in HandleViewSource():')
+
window_id = activity.get_bundle_id()
if window_id is None:
@@ -149,8 +153,7 @@ def setup_view_source(activity):
'org.freedesktop.DBus.Python.NotImplementedError']
if e.get_dbus_name() not in expected_exceptions:
logging.exception('Exception occurred in GetDocumentPath():')
- except Exception:
- logging.exception('Exception occurred in GetDocumentPath():')
+
if bundle_path is None and document_path is None:
_logger.debug('Activity without bundle_path nor document_path')
@@ -165,7 +168,7 @@ def setup_view_source(activity):
sugar_toolkit_path, activity.get_title())
activity.push_shell_window(view_source)
view_source.connect('hide', activity.pop_shell_window)
- view_source.show()
+ view_source.set_visible(True)
class ViewSource(Gtk.Window):
@@ -173,43 +176,63 @@ class ViewSource(Gtk.Window):
def __init__(self, window_id, bundle_path, document_path,
sugar_toolkit_path, title):
- Gtk.Window.__init__(self)
+ super().__init__()
+
_logger.debug('ViewSource paths: %r %r %r', bundle_path,
document_path, sugar_toolkit_path)
self.set_modal(True)
self.set_decorated(False)
- self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
- self.set_border_width(style.LINE_WIDTH)
- self.set_has_resize_grip(False)
- width = Gdk.Screen.width() - style.GRID_CELL_SIZE * 2
- height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 2
- self.set_size_request(width, height)
+ # Set transient parent window
+ from jarabe.model import shell as _shell
+ shell_model = _shell.get_model()
+ if shell_model and shell_model._main_window:
+ self.set_transient_for(shell_model._main_window)
+
+ display = Gdk.Display.get_default()
+ width = 1024 - style.GRID_CELL_SIZE * 2
+ height = 768 - style.GRID_CELL_SIZE * 2
+ if display:
+ monitors = display.get_monitors()
+ if monitors and monitors.get_n_items() > 0:
+ geo = monitors.get_item(0).get_geometry()
+ width = geo.width - style.GRID_CELL_SIZE * 2
+ height = geo.height - style.GRID_CELL_SIZE * 2
+
+ self.set_default_size(width, height)
self._parent_window_id = window_id
self._sugar_toolkit_path = sugar_toolkit_path
- self._gdk_window = self.get_root_window()
- self.connect('realize', self.__realize_cb)
- self.connect('destroy', self.__destroy_cb, document_path)
- self.connect('key-press-event', self.__key_press_event_cb)
+ self.connect('map', self.__realize_cb)
+ self._document_path = document_path
+
+ key_controller = Gtk.EventControllerKey()
+ key_controller.connect('key-pressed', self.__key_press_event_cb)
+ self.add_controller(key_controller)
self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- self.add(self._vbox)
- self._vbox.show()
+ self._vbox.set_margin_start(style.LINE_WIDTH)
+ self._vbox.set_margin_end(style.LINE_WIDTH)
+ self._vbox.set_margin_top(style.LINE_WIDTH)
+ self._vbox.set_margin_bottom(style.LINE_WIDTH)
+ self.set_child(self._vbox)
+ self._vbox.set_visible(True)
toolbar = Toolbar(title, bundle_path, document_path,
sugar_toolkit_path)
- self._vbox.pack_start(toolbar, False, True, 0)
+ self._vbox.append(toolbar)
toolbar.connect('stop-clicked', self.__stop_clicked_cb)
toolbar.connect('source-selected', self.__source_selected_cb)
- toolbar.show()
+ toolbar.set_visible(True)
- pane = Gtk.HPaned()
- self._vbox.pack_start(pane, True, True, 0)
- pane.show()
+ pane = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
+ pane.set_vexpand(True)
+ pane.set_hexpand(True)
+ self._vbox.append(pane)
+ pane.set_visible(True)
self._selected_bundle_file = None
self._selected_sugar_file = None
@@ -233,14 +256,14 @@ def __init__(self, window_id, bundle_path, document_path,
# Split the tree pane into two vertical panes, one of which
# will be hidden
- tree_panes = Gtk.VPaned()
- tree_panes.show()
+ tree_panes = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
+ tree_panes.set_visible(True)
self._bundle_source_viewer = FileViewer(bundle_path, file_name)
self._bundle_source_viewer.connect('file-selected',
self.__file_selected_cb)
- tree_panes.add1(self._bundle_source_viewer)
- self._bundle_source_viewer.show()
+ tree_panes.set_start_child(self._bundle_source_viewer)
+ self._bundle_source_viewer.set_visible(True)
self._sugar_source_viewer = None
@@ -259,23 +282,25 @@ def __init__(self, window_id, bundle_path, document_path,
self._sugar_source_viewer.connect('file-selected',
self.__file_selected_cb)
- tree_panes.add2(self._sugar_source_viewer)
- self._sugar_source_viewer.hide()
+ tree_panes.set_end_child(self._sugar_source_viewer)
+ self._sugar_source_viewer.set_visible(False)
- pane.add1(tree_panes)
+ pane.set_start_child(tree_panes)
self._source_display = SourceDisplay()
- pane.add2(self._source_display)
- self._source_display.show()
+ pane.set_end_child(self._source_display)
+ self._source_display.set_visible(True)
self._source_display.file_path = self._selected_bundle_file
if document_path is not None:
self._select_source(document_path)
def add_alert(self, alert):
- self._vbox.pack_start(alert, False, False, 0)
- self._vbox.reorder_child(alert, 1)
- alert.show()
+ if hasattr(self._vbox, 'prepend'):
+ self._vbox.prepend(alert)
+ else:
+ self._vbox.insert_child_after(alert, None)
+ alert.set_visible(True)
def remove_alert(self, alert):
self._vbox.remove(alert)
@@ -288,17 +313,13 @@ def _calculate_char_width(self, char_count):
return Pango.PIXELS(metrics.get_approximate_char_width()) * char_count
def __realize_cb(self, widget):
- self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- window = self.get_window()
- window.set_accept_focus(True)
-
- attributes = Gdk.WindowAttr()
- attributes.window_type = Gdk.WindowType.FOREIGN
- parent = Gdk.Window.new(None, attributes, None)
- window.set_transient_for(parent)
+ from jarabe.model import shell as _shell
+ _shell.get_model().push_modal()
def __stop_clicked_cb(self, widget):
- self.destroy()
+ from jarabe.model import shell as _shell
+ _shell.get_model().pop_modal()
+ self.close()
def __source_selected_cb(self, widget, path):
self._select_source(path)
@@ -307,38 +328,45 @@ def _select_source(self, path):
if os.path.isfile(path):
_logger.debug('_select_source called with file: %r', path)
self._source_display.file_path = path
- self._bundle_source_viewer.hide()
+ self._bundle_source_viewer.set_visible(False)
if self._sugar_source_viewer is not None:
- self._sugar_source_viewer.hide()
+ self._sugar_source_viewer.set_visible(False)
elif path == self._sugar_toolkit_path:
_logger.debug('_select_source called with sugar toolkit path: %r',
path)
self._sugar_source_viewer.set_path(path)
self._source_display.file_path = self._selected_sugar_file
- self._sugar_source_viewer.show()
- self._bundle_source_viewer.hide()
+ self._sugar_source_viewer.set_visible(True)
+ self._bundle_source_viewer.set_visible(False)
else:
_logger.debug('_select_source called with path: %r', path)
self._bundle_source_viewer.set_path(path)
self._source_display.file_path = self._selected_bundle_file
- self._bundle_source_viewer.show()
+ self._bundle_source_viewer.set_visible(True)
if self._sugar_source_viewer is not None:
- self._sugar_source_viewer.hide()
-
- def __destroy_cb(self, window, document_path):
- if document_path is not None and os.path.exists(document_path):
- os.unlink(document_path)
-
- self._gdk_window.set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR))
- Gdk.flush()
-
- def __key_press_event_cb(self, window, event):
- keyname = Gdk.keyval_name(event.keyval)
+ self._sugar_source_viewer.set_visible(False)
+
+ def do_dispose(self):
+ if getattr(self, '_document_path', None) and os.path.exists(self._document_path):
+ try:
+ os.unlink(self._document_path)
+ except OSError:
+ pass
+
+ surface = self.get_surface()
+ if surface:
+ surface.set_cursor(Gdk.Cursor.new_from_name('default'))
+ GObject.GObject.do_dispose(self)
+
+ def __key_press_event_cb(self, controller, keyval, keycode, state):
+ keyname = Gdk.keyval_name(keyval)
if keyname == 'Escape':
- self.destroy()
+ self.close()
+ return True
+ return False
def __file_selected_cb(self, file_viewer, file_path):
if file_path is not None and os.path.isfile(file_path):
@@ -356,7 +384,7 @@ class DocumentButton(RadioToolButton):
def __init__(self, file_name, document_path, activity_name, title,
bundle=False):
- RadioToolButton.__init__(self)
+ super().__init__()
self._document_path = document_path
self._title = title
@@ -371,11 +399,11 @@ def __init__(self, file_name, document_path, activity_name, title,
pixel_size=style.STANDARD_ICON_SIZE,
xo_color=XoColor(self._color))
self.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
box = PaletteMenuBox()
self.props.palette.set_content(box)
- box.show()
+ box.set_visible(True)
if bundle:
menu_item = PaletteMenuItem(_('Duplicate'), 'edit-duplicate',
@@ -387,7 +415,7 @@ def __init__(self, file_name, document_path, activity_name, title,
menu_item.connect('activate', self.__keep_in_journal_cb)
box.append_item(menu_item)
- menu_item.show()
+ menu_item.set_visible(True)
def __show_duplicate_alert(self, menu_item):
alert = ConfirmationAlert()
@@ -395,10 +423,10 @@ def __show_duplicate_alert(self, menu_item):
self._activity_name
alert.props.msg = _('This may take a few minutes')
alert.connect('response', self.__duplicate_alert_response_cb)
- self.get_toplevel().add_alert(alert)
+ self.get_root().add_alert(alert)
def __duplicate_alert_response_cb(self, alert, response_id):
- self.get_toplevel().remove_alert(alert)
+ self.get_root().remove_alert(alert)
if response_id == Gtk.ResponseType.OK:
self.__set_busy_cursor(True)
@@ -409,20 +437,22 @@ def internal_callback(new_alert):
new_alert = Alert()
new_alert.props.title = _("Duplicating activity...")
- self.get_toplevel().add_alert(new_alert)
+ self.get_root().add_alert(new_alert)
GLib.idle_add(internal_callback, new_alert)
def __set_busy_cursor(self, busy):
cursor = None
if busy:
- cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
+ cursor = Gdk.Cursor.new_from_name('wait')
else:
- cursor = Gdk.Cursor(Gdk.CursorType.LEFT_PTR)
+ cursor = Gdk.Cursor.new_from_name('default')
- gdk_window = self.get_root_window()
- gdk_window.set_cursor(cursor)
- Gdk.flush()
+ root = self.get_root()
+ if root:
+ surface = root.get_surface()
+ if surface:
+ surface.set_cursor(cursor)
def __copy_to_home_cb(self, menu_item, copy_alert=None):
"""Make a local copy of the activity bundle in user_activities_path"""
@@ -444,20 +474,20 @@ def async_copy_activity_tree():
customizebundle.generate_bundle(nick, new_basename)
if copy_alert:
- self.get_toplevel().remove_alert(copy_alert)
+ self.get_root().remove_alert(copy_alert)
alert = NotifyAlert(10)
alert.props.title = _('Duplicated')
alert.props.msg = _('The activity has been duplicated')
alert.connect('response', self.__alert_response_cb)
- self.get_toplevel().add_alert(alert)
+ self.get_root().add_alert(alert)
finally:
self.__set_busy_cursor(False)
GLib.idle_add(async_copy_activity_tree)
else:
if copy_alert:
- self.get_toplevel().remove_alert(copy_alert)
+ self.get_root().remove_alert(copy_alert)
self.__set_busy_cursor(False)
@@ -467,10 +497,10 @@ def async_copy_activity_tree():
' the activity again')
alert.connect('response', self.__alert_response_cb)
- self.get_toplevel().add_alert(alert)
+ self.get_root().add_alert(alert)
def __alert_response_cb(self, alert, response_id):
- self.get_toplevel().remove_alert(alert)
+ self.get_root().remove_alert(alert)
def __keep_in_journal_cb(self, menu_item):
mime_type = mime.get_from_file_name(self._document_path)
@@ -500,7 +530,7 @@ def __internal_save_error_cb(self, err):
self._jobject.destroy()
-class Toolbar(Gtk.Toolbar):
+class Toolbar(Gtk.Box):
__gtype_name__ = 'SugarViewSourceToolbar'
__gsignals__ = {
@@ -510,7 +540,7 @@ class Toolbar(Gtk.Toolbar):
}
def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
- Gtk.Toolbar.__init__(self)
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
document_button = None
self.bundle_path = bundle_path
@@ -527,8 +557,8 @@ def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
activity_name, title)
document_button.connect('toggled', self.__button_toggled_cb,
document_path)
- self.insert(document_button, -1)
- document_button.show()
+ self.append(document_button)
+ document_button.set_visible(True)
self._add_separator()
if bundle_path is not None and os.path.exists(bundle_path):
@@ -539,14 +569,14 @@ def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
fill_color=style.COLOR_TRANSPARENT.get_svg(),
stroke_color=style.COLOR_WHITE.get_svg())
activity_button.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
if document_button is not None:
- activity_button.props.group = document_button
+ activity_button.set_group(document_button)
activity_button.props.tooltip = _('Activity Bundle Source')
activity_button.connect('toggled', self.__button_toggled_cb,
bundle_path)
- self.insert(activity_button, -1)
- activity_button.show()
+ self.append(activity_button)
+ activity_button.set_visible(True)
self._add_separator()
if sugar_toolkit_path is not None:
@@ -556,16 +586,16 @@ def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
fill_color=style.COLOR_TRANSPARENT.get_svg(),
stroke_color=style.COLOR_WHITE.get_svg())
sugar_button.set_icon_widget(icon)
- icon.show()
+ icon.set_visible(True)
if document_button is not None:
- sugar_button.props.group = document_button
+ sugar_button.set_group(document_button)
else:
- sugar_button.props.group = activity_button
+ sugar_button.set_group(activity_button)
sugar_button.props.tooltip = _('Sugar Toolkit Source')
sugar_button.connect('toggled', self.__button_toggled_cb,
sugar_toolkit_path)
- self.insert(sugar_button, -1)
- sugar_button.show()
+ self.append(sugar_button)
+ sugar_button.set_visible(True)
self._add_separator()
self.activity_title_text = _('View source: %s') % title
@@ -573,7 +603,8 @@ def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
self.label = Gtk.Label()
self.label.set_markup('%s' % self.activity_title_text)
self.label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT)
- self.label.set_alignment(0, 0.5)
+ self.label.set_halign(Gtk.Align.START)
+ self.label.set_valign(Gtk.Align.CENTER)
self._add_widget(self.label, expand=True)
self._add_separator(False)
@@ -581,28 +612,23 @@ def __init__(self, title, bundle_path, document_path, sugar_toolkit_path):
stop = ToolButton(icon_name='dialog-cancel')
stop.set_tooltip(_('Close'))
stop.connect('clicked', self.__stop_clicked_cb)
- self.insert(stop, -1)
- stop.show()
+ self.append(stop)
+ stop.set_visible(True)
def _add_separator(self, expand=False):
- separator = Gtk.SeparatorToolItem()
- separator.props.draw = False
+ spacer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
if expand:
- separator.set_expand(True)
+ spacer.set_hexpand(True)
else:
- separator.set_size_request(style.DEFAULT_SPACING, -1)
- self.insert(separator, -1)
- separator.show()
+ spacer.set_size_request(style.DEFAULT_SPACING, -1)
+ self.append(spacer)
+ spacer.set_visible(True)
def _add_widget(self, widget, expand=False):
- tool_item = Gtk.ToolItem()
- tool_item.set_expand(expand)
-
- tool_item.add(widget)
- widget.show()
-
- self.insert(tool_item, -1)
- tool_item.show()
+ if expand:
+ widget.set_hexpand(True)
+ self.append(widget)
+ widget.set_visible(True)
def __stop_clicked_cb(self, button):
self.emit('stop-clicked')
@@ -626,10 +652,9 @@ class FileViewer(Gtk.ScrolledWindow):
}
def __init__(self, path, initial_filename):
- Gtk.ScrolledWindow.__init__(self)
+ super().__init__()
- self.props.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC
- self.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.set_size_request(style.GRID_CELL_SIZE * 3, -1)
self._path = None
@@ -637,8 +662,8 @@ def __init__(self, path, initial_filename):
self._tree_view = Gtk.TreeView()
self._tree_view.connect('cursor-changed', self.__cursor_changed_cb)
- self.add(self._tree_view)
- self._tree_view.show()
+ self.set_child(self._tree_view)
+ self._tree_view.set_visible(True)
self._tree_view.props.headers_visible = False
selection = self._tree_view.get_selection()
@@ -706,24 +731,14 @@ class SourceDisplay(Gtk.ScrolledWindow):
__gtype_name__ = 'SugarSourceDisplay'
def __init__(self):
- Gtk.ScrolledWindow.__init__(self)
+ super().__init__()
- self.props.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC
- self.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
+ self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._file_path = None
def _replace(self, child):
- self._remove_children()
- self.add(child)
-
- def _replace_with_viewport(self, child):
- self._remove_children()
- self.add_with_viewport(child)
-
- def _remove_children(self):
- for child in self.get_children():
- self.remove(child)
+ self.set_child(child)
def _set_file_path(self, file_path):
self._file_path = file_path
@@ -745,17 +760,23 @@ def _set_file_path(self, file_path):
self._show_image_viewer(icon='application-x-generic')
def _show_text_viewer(self):
+ if GtkSource is None:
+ return False
+
source_buffer = GtkSource.Buffer()
source_buffer.set_highlight_syntax(True)
source_view = GtkSource.View(buffer=source_buffer)
source_view.set_editable(False)
- source_view.set_cursor_visible(True)
source_view.set_show_line_numbers(True)
source_view.set_show_right_margin(True)
source_view.set_right_margin_position(80)
- source_view.modify_font(_SOURCE_FONT)
- # source_view.set_highlight_current_line(True) #FIXME: Ugly color
+ source_view.add_css_class('view-source-text')
+
+ provider = Gtk.CssProvider()
+ css = b".view-source-text { font-family: monospace; font-size: %dpt; }" % style.FONT_SIZE
+ provider.load_from_string(css.decode('utf-8'))
+ Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
mime_type = mime.get_for_file(self._file_path)
@@ -781,7 +802,7 @@ def _show_text_viewer(self):
except UnicodeDecodeError:
return False
- source_view.show()
+ source_view.set_visible(True)
self._replace(source_view)
return True
@@ -795,33 +816,43 @@ def _show_image_viewer(self, icon=None, image=False):
media_box = ImageBox()
if image:
- image = Gtk.Image()
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(self._file_path)
- image.set_from_pixbuf(pixbuf)
- media_box.add(image)
+ image_widget = Gtk.Image.new_from_file(self._file_path)
+ media_box.append(image_widget)
+ image_widget.set_visible(True)
if icon:
- h = Gdk.Screen.width() / 3
- icon = Icon(icon_name=icon, pixel_size=h)
- media_box.add(icon)
+ h = 1024 / 3 # approximate size
+ icon_widget = Icon(icon_name=icon, pixel_size=h)
+ media_box.append(icon_widget)
+ icon_widget.set_visible(True)
- media_box.show_all()
- self._replace_with_viewport(media_box)
+ media_box.set_visible(True)
+ self._replace(media_box)
def _show_no_file(self):
nofile_label = Gtk.Label()
nofile_label.set_text(_("Please select a file in the left panel."))
- nofile_box = Gtk.EventBox()
- nofile_box.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse('white'))
+ nofile_box = Gtk.Box()
+ nofile_box.add_css_class('nofile-box')
+
+ provider = Gtk.CssProvider()
+ css = b".nofile-box { background-color: %s; }" % style.COLOR_WHITE.get_html().encode('utf-8')
+ provider.load_from_string(css.decode('utf-8'))
+ Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
- nofile_box.add(nofile_label)
- nofile_box.show_all()
- self._replace_with_viewport(nofile_box)
+ nofile_box.append(nofile_label)
+ nofile_label.set_visible(True)
+ nofile_box.set_visible(True)
+ self._replace(nofile_box)
-class ImageBox(Gtk.EventBox):
+class ImageBox(Gtk.Box):
__gtype_name__ = 'SugarViewSourceImageBox'
def __init__(self):
- Gtk.EventBox.__init__(self)
+ super().__init__()
+ self.set_halign(Gtk.Align.CENTER)
+ self.set_valign(Gtk.Align.CENTER)
+
+ self.add_css_class('image-box')
diff --git a/src/jarabe/webservice/account.py b/src/jarabe/webservice/account.py
index 05a2c2d0a5..1ee4656f6a 100644
--- a/src/jarabe/webservice/account.py
+++ b/src/jarabe/webservice/account.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales
+# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales
# Copyright (c) 2013 SugarLabs
#
# This program is free software: you can redistribute it and/or modify
@@ -61,7 +61,7 @@ def get_shared_journal_entry(self):
return NotImplemented
-class SharedJournalEntry():
+class SharedJournalEntry(GObject.GObject):
''' SharedJournalEntry is a class used to intermediate between the
online service and the Sugar UI elements (MenuItems used in the
Journal UI) for online accounts. It provides stubs for public
diff --git a/src/jarabe/webservice/accountsmanager.py b/src/jarabe/webservice/accountsmanager.py
index 37a0b540af..facf0f0e2d 100644
--- a/src/jarabe/webservice/accountsmanager.py
+++ b/src/jarabe/webservice/accountsmanager.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 Walter Bender
+# Copyright (c) 2013 Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -169,8 +169,17 @@ def _load_service_module(path, service_name):
def _extend_icon_theme_search_path(path):
- icon_theme = Gtk.IconTheme.get_default()
- icon_search_path = icon_theme.get_search_path()
+ from gi.repository import Gdk
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
+
+ # We convert them to paths.
+ icon_search_path = []
+ for p in icon_theme.get_search_path():
+ if hasattr(p, 'get_path'):
+ icon_search_path.append(p.get_path())
+ else:
+ icon_search_path.append(p)
+
try:
icon_path_dirs = os.listdir(path)
except OSError as e:
@@ -182,7 +191,7 @@ def _extend_icon_theme_search_path(path):
icon_path = os.path.join(path, file)
if os.path.isdir(icon_path) and \
icon_path not in icon_search_path:
- icon_theme.append_search_path(icon_path)
+ icon_theme.add_search_path(icon_path)
def get_account(service_name):
diff --git a/tests/extensions/cpsection/webaccount/services/mock/service.py b/tests/extensions/cpsection/webaccount/services/mock/service.py
index bd29dab8e7..305d774db7 100644
--- a/tests/extensions/cpsection/webaccount/services/mock/service.py
+++ b/tests/extensions/cpsection/webaccount/services/mock/service.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
+# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from gi.repository import Gtk
from jarabe.webservice import accountsmanager
@@ -32,13 +31,15 @@ def get_icon_name(self):
return _SERVICE_NAME
def config_service_cb(self, widget, event, container):
- label = Gtk.Label(_SERVICE_NAME)
+ label = Gtk.Label(label=_SERVICE_NAME)
- for c in container.get_children():
- container.remove(c)
+ child = container.get_first_child()
+ while child:
+ next_child = child.get_next_sibling()
+ container.remove(child)
+ child = next_child
- container.add(label)
- container.show_all()
+ container.append(label)
def get_service():
diff --git a/tests/extensions/cpsection/webaccount/web_service.py b/tests/extensions/cpsection/webaccount/web_service.py
index 998acb120d..3a56f18e09 100644
--- a/tests/extensions/cpsection/webaccount/web_service.py
+++ b/tests/extensions/cpsection/webaccount/web_service.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
+# Copyright (C) 2013, Walter Bender - Raul Gutierrez Segales
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
class WebService():
def get_icon_name(self):
- raise "Not implemented"
+ raise NotImplementedError("Not implemented")
def config_service_cb(self, widget, event, container):
- raise "Not implemented"
+ raise NotImplementedError("Not implemented")
diff --git a/tests/extensions/webservice/mock/account.py b/tests/extensions/webservice/mock/account.py
index a7c279b430..8ac9c60eae 100644
--- a/tests/extensions/webservice/mock/account.py
+++ b/tests/extensions/webservice/mock/account.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 Walter Bender
+# Copyright (c) 2013 Walter Bender
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -56,13 +56,13 @@ def get_refresh_menu(self):
class ShareMenu(MenuItem):
def __init__(self, metadata):
MenuItem.__init__(self, text_label=ACCOUNT_NAME)
- self.show()
+ self.set_visible(True)
class RefreshMenu(MenuItem):
def __init__(self):
MenuItem.__init__(self, text_label=ACCOUNT_NAME)
- self.show()
+ self.set_visible(True)
def set_metadata(self, metadata):
return
diff --git a/tests/test_activitieslist.py b/tests/test_activitieslist.py
index 204948bc02..46e9357be4 100644
--- a/tests/test_activitieslist.py
+++ b/tests/test_activitieslist.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Daniel Narvaez
+# Copyright (C) 2013, Daniel Narvaez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_backup.py b/tests/test_backup.py
index 0fe832c146..e992b89476 100644
--- a/tests/test_backup.py
+++ b/tests/test_backup.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2014, Gonzalo Odiard
+# Copyright (C) 2014, Gonzalo Odiard
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_bundleregistry.py b/tests/test_bundleregistry.py
index ecdaff3a3a..55d434d897 100644
--- a/tests/test_bundleregistry.py
+++ b/tests/test_bundleregistry.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, One Laptop per Child
+# Copyright (C) 2013, One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_desktop_config.py b/tests/test_desktop_config.py
index c96b5aea35..1c39c5a49b 100644
--- a/tests/test_desktop_config.py
+++ b/tests/test_desktop_config.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender
+# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_downloader.py b/tests/test_downloader.py
index 8c880009af..60d491302e 100644
--- a/tests/test_downloader.py
+++ b/tests/test_downloader.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, One Laptop per Child
+# Copyright (C) 2013, One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_journaltoolbox.py b/tests/test_journaltoolbox.py
index 907f0a60c6..83e48da671 100644
--- a/tests/test_journaltoolbox.py
+++ b/tests/test_journaltoolbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, Daniel Narvaez
+# Copyright (C) 2012, Daniel Narvaez
# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
diff --git a/tests/test_microformat.py b/tests/test_microformat.py
index d02def247e..86cbe0d827 100644
--- a/tests/test_microformat.py
+++ b/tests/test_microformat.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, One Laptop per Child
+# Copyright (C) 2013, One Laptop per Child
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
diff --git a/tests/test_modemconfiguration.py b/tests/test_modemconfiguration.py
index b8b61e5454..5ad85ce94c 100644
--- a/tests/test_modemconfiguration.py
+++ b/tests/test_modemconfiguration.py
@@ -1,4 +1,4 @@
-# -*- encoding: utf-8 -*-
+# -*- encoding: utf-8 -*-
# Copyright (C) 2013, Miguel González
#
# This program is free software: you can redistribute it and/or modify
@@ -17,7 +17,7 @@
import unittest
from xml.etree.cElementTree import ElementTree
-from mock import patch
+from unittest.mock import patch
from cpsection.modemconfiguration.model import CountryCodeParser, \
ServiceProvidersParser, ServiceProviders, PROVIDERS_PATH
@@ -31,9 +31,8 @@ def test_get_country(self):
self.assertEqual(CountryCodeParser().get('es'), 'Spain')
self.assertEqual(CountryCodeParser().get('zw'), 'Zimbabwe')
- def test_raise_if_not_found(self):
- with self.assertRaises(KeyError):
- CountryCodeParser().get('xx')
+ def test_fallback_if_not_found(self):
+ self.assertEqual(CountryCodeParser().get('xx'), 'xx')
class ServiceProvidersParserTest(unittest.TestCase):
diff --git a/tests/test_user_profile.py b/tests/test_user_profile.py
index f9950de583..c42cbd2b73 100644
--- a/tests/test_user_profile.py
+++ b/tests/test_user_profile.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Copyright (C) 2014, Ignacio Rodriguez
#
# This program is free software: you can redistribute it and/or modify
@@ -17,7 +17,7 @@
import unittest
from cpsection.aboutme import model
-from jarabe.intro.agepicker import AGES
+from jarabe.intro.agepicker import get_group_labels
from jarabe.intro.genderpicker import GENDERS
TEST_NICKS = ['Ajay Garg', 'Aleksey Lim',
@@ -44,7 +44,7 @@
class TestUserProfile(unittest.TestCase):
def test_user_gender_age(self):
for gender in GENDERS:
- for age in AGES:
+ for age in get_group_labels().AGES:
model.set_gender(gender)
model.set_age(age)
self.assertEqual(model.get_age(), age)
diff --git a/tests/test_webaccount.py b/tests/test_webaccount.py
index c429b197ee..292b0be82a 100644
--- a/tests/test_webaccount.py
+++ b/tests/test_webaccount.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012, Daniel Narvaez
+# Copyright (C) 2012, Daniel Narvaez
# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
diff --git a/tests/test_webservice.py b/tests/test_webservice.py
index 56de599626..e3fec91479 100644
--- a/tests/test_webservice.py
+++ b/tests/test_webservice.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender
+# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,7 +17,9 @@
import sys
import unittest
-from gi.repository import Gtk
+import gi
+gi.require_version('Gtk', '4.0')
+from gi.repository import GLib, Gdk, Gtk
from jarabe import config
from jarabe.webservice.account import Account
@@ -47,7 +49,7 @@ def test_get_description(self):
self.assertTrue(found_mock_account)
def test_icon_theme(self):
- icon_theme = Gtk.IconTheme.get_default()
+ icon_theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
icon_search_path = icon_theme.get_search_path()
icon_path = os.path.join(web_extension_dir, ACCOUNT_NAME, 'icons')
self.assertTrue(icon_path in icon_search_path)
diff --git a/tests/views/activitieslist.py b/tests/views/activitieslist.py
index e954b478ac..dd14fac896 100644
--- a/tests/views/activitieslist.py
+++ b/tests/views/activitieslist.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Daniel Narvaez
+# Copyright (C) 2013, Daniel Narvaez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,13 +15,14 @@
import os
+import gi
+gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from jarabe.desktop.activitieslist import ActivityListPalette
-
tests_dir = os.getcwd()
base_dir = os.path.dirname(tests_dir)
data_dir = os.path.join(tests_dir, "data")
@@ -49,11 +50,11 @@ def get_path(self):
def is_user_activity(self):
return True
-
os.environ["SUGAR_MIME_DEFAULTS"] = \
os.path.join(base_dir, "data", "mime.defaults")
palette = ActivityListPalette(MockActivityInfo())
palette.popup()
-Gtk.main()
+import GLib
+GLib.MainLoop().run()
diff --git a/tests/views/gtk_main.py b/tests/views/gtk_main.py
index 2dc088d18f..f0b26e0ca2 100644
--- a/tests/views/gtk_main.py
+++ b/tests/views/gtk_main.py
@@ -13,6 +13,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import gi
+gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
-Gtk.main()
+import GLib
+GLib.MainLoop().run()
diff --git a/tests/views/journal_detailstoolbox.py b/tests/views/journal_detailstoolbox.py
index 77e4feefff..b29ac46490 100644
--- a/tests/views/journal_detailstoolbox.py
+++ b/tests/views/journal_detailstoolbox.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013, Walter Bender
+# Copyright (C) 2013, Walter Bender
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,6 +16,8 @@
import os
import sys
+import gi
+gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
@@ -32,7 +34,6 @@ class JournalMock(JournalWindow):
def get_mount_point(self):
return '/'
-
tests_dir = os.getcwd()
extension_dir = os.path.join(tests_dir, 'extensions')
@@ -43,12 +44,13 @@ def get_mount_point(self):
window = Gtk.Window()
toolbox = DetailToolbox(JournalMock())
-toolbox.show()
+toolbox.set_visible(True)
-window.add(toolbox)
-window.show()
+window.set_child(toolbox)
+window.present()
toolbox.set_metadata({'mountpoint': '/', 'uid': '', 'title': 'mock'})
toolbox._copy.palette.popup(immediate=True)
-Gtk.main()
+import GLib
+GLib.MainLoop().run()
diff --git a/tests/views/webaccount.py b/tests/views/webaccount.py
index 057e9e51d5..773d339ecb 100644
--- a/tests/views/webaccount.py
+++ b/tests/views/webaccount.py
@@ -16,6 +16,8 @@
import os
import sys
+import gi
+gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
from jarabe import config
@@ -32,15 +34,16 @@
sys.path.append(config.ext_path)
window = Gtk.Window()
-box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
-box.show()
-window.add(box)
+box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+box.set_visible(True)
+window.set_child(box)
services = accountsmanager.get_webaccount_services()
for service in services:
if service.get_icon_name() == ACCOUNT_NAME:
service.config_service_cb(None, None, box)
-window.show()
+window.present()
-Gtk.main()
+import GLib
+GLib.MainLoop().run()