diff --git a/README.md b/README.md index c074e93cc..dc952ef03 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ desktop](https://github.com/sugarlabs/sugar). To install Sugar Toolkit alone without Sugar desktop, ``` -sudo apt install python-sugar3 +sudo apt install python3-sugar3 ``` Installing on Fedora diff --git a/configure.ac b/configure.ac index ade9073cc..ce649f631 100644 --- a/configure.ac +++ b/configure.ac @@ -27,7 +27,7 @@ AS_IF([test "x$with_python3" != xno], [AM_PATH_PYTHON([3])], [AS_IF([test "x$with_python2" = xyes], [AM_PATH_PYTHON([2.7])],)]) -PKG_CHECK_MODULES(EXT, gtk+-3.0 gdk-3.0 gdk-pixbuf-2.0 sm ice alsa +PKG_CHECK_MODULES(EXT, gtk4 gdk-pixbuf-2.0 sm ice alsa librsvg-2.0 xfixes xi x11) GLIB_MKENUMS=`$PKG_CONFIG glib-2.0 --variable=glib_mkenums` diff --git a/portingGTK4.md b/portingGTK4.md new file mode 100644 index 000000000..3103d1a8b --- /dev/null +++ b/portingGTK4.md @@ -0,0 +1,111 @@ +# Setting Up a Virtual Environment and Running from Source + +## Creating a Virtual Environment + +1. Open a terminal. +2. Navigate to your project directory: +3. Create a virtual environment: + ```sh + python3 -m venv venv + ``` +4. Activate the virtual environment: + - On Linux/macOS: + ```sh + source venv/bin/activate + ``` + - On Windows: + ```sh + .\venv\Scripts\activate + ``` + +## Installing Dependencies + +1. Ensure you have `pip` installed. If not, install it: + ```sh + python3 -m ensurepip --upgrade + ``` + +## Setting the PYTHONPATH + +1. Set the `PYTHONPATH` to include the `src` directory: + ```sh + export PYTHONPATH=$(pwd)/src + ``` + +## Running from Source + +1. Ensure the virtual environment is activated. +2. Run the application: + ```sh + python path/to/your/main_script.py + ``` + +## Deactivating the Virtual Environment + +1. When you are done, deactivate the virtual environment: + ```sh + deactivate + ``` + + + + +## Porting to GTK 4.0 + +### Changes Made + +1. **Updated `gi.require_version` Calls:** + - Changed `'Gtk', '3.0'` and `'Gdk', '3.0'` to `'Gtk', '4.0'` and `'Gdk', '4.0'` respectively. + +2. **Refactored Deprecated GTK 3.0 APIs:** + - Replaced `Gtk.VBox` and `Gtk.HBox` with `Gtk.Box` using orientation. + - Removed `Gtk.EventBox` and implemented event controllers. + +3. **Refactored Icon and EventIcon Classes:** + - Updated the `Icon` and `EventIcon` classes to be compatible with GTK 4.0. + +4. **Refactored Alert and TimeoutIcon Classes:** + - Modified the `Alert` and `TimeoutIcon` classes to use `Gtk.Box` and updated them to GTK 4.0. + +5. **Updated Version Requirements:** + - Changed the GDK and GTK version requirements to 4.0 in the project configuration files. + +6. **Refactored Activity Class Initialization:** + - Updated the initialization process of the `Activity` class to be compatible with GTK 4.0. + + +## Installing Build Dependencies +### On Debian/Ubuntu: +```bash +sudo apt-get install \ + build-essential \ + python3-dev \ + libgtk-4-dev \ + libgdk-pixbuf-2.0-dev \ + gobject-introspection \ + libgirepository1.0-dev \ + gir1.2-gtk-4.0 \ + python3-gi \ + libx11-dev \ + libxi-dev \ + libxext-dev \ + libxrandr-dev \ + libxrender-dev \ + libxtst-dev \ + autoconf \ + automake \ + libtool \ + libasound2-dev \ + librsvg2-dev \ +``` + +- If using Debian 11 (Bullseye): +- Add backports to /etc/apt/sources.list: +```bash +sudo echo "deb http://deb.debian.org/debian bullseye-backports main" >> /etc/apt/sources.list +``` +-Update and install from backports: +``` +sudo apt update +sudo apt -t bullseye-backports install libgtk-4-dev +``` diff --git a/src/sugar3/Makefile.am b/src/sugar3/Makefile.am index addb15bc4..f318192be 100644 --- a/src/sugar3/Makefile.am +++ b/src/sugar3/Makefile.am @@ -125,8 +125,8 @@ SugarExt_1_0_gir_FILES = \ sugar-wm.h SugarExt_1_0_gir: libsugarext.la -SugarExt_1_0_gir_INCLUDES = Gtk-3.0 Gdk-3.0 -SugarExt_1_0_gir_PACKAGES = gtk+-3.0 gdk-3.0 +SugarExt_1_0_gir_INCLUDES = Gtk-4.0 +SugarExt_1_0_gir_PACKAGES = gtk4 SugarExt_1_0_gir_EXPORT_PACKAGES = SugarExt-1.0 girdir = $(datadir)/gir-1.0 diff --git a/src/sugar3/activity/activity.py b/src/sugar3/activity/activity.py index 4f7c9a222..78375a945 100644 --- a/src/sugar3/activity/activity.py +++ b/src/sugar3/activity/activity.py @@ -170,20 +170,21 @@ class MySpecialToolbar(Gtk.Toolbar): import json import gi -gi.require_version('Gtk', '3.0') -gi.require_version('Gdk', '3.0') +gi.require_version('Gtk', '4.0') +gi.require_version('Gdk', '4.0') gi.require_version('TelepathyGLib', '0.12') gi.require_version('SugarExt', '1.0') +gi.require_version('GdkX11', '4.0') from gi.repository import GLib from gi.repository import GObject from gi.repository import Gdk from gi.repository import Gtk from gi.repository import TelepathyGLib +from gi.repository import GdkX11 import dbus import dbus.service from dbus import PROPERTIES_IFACE - from sugar3 import util from sugar3 import power from sugar3.profile import get_color, get_save_as @@ -243,6 +244,7 @@ class _ActivitySession(GObject.GObject): def __init__(self): GObject.GObject.__init__(self) + self._main_loop = GLib.MainLoop() self._xsmp_client = SugarExt.ClientXSMP() self._xsmp_client.connect('quit-requested', @@ -261,8 +263,8 @@ def unregister(self, activity): if len(self._activities) == 0: logging.debug('Quitting the activity process.') - Gtk.main_quit() - + self._main_loop.quit() + def will_quit(self, activity, will_quit): if will_quit: self._will_quit.append(activity) @@ -284,7 +286,7 @@ def __sm_quit_cb(self, client): self.emit('quit') -class Activity(Window, Gtk.Container): +class Activity(Gtk.Window): """ Initialise an Activity. @@ -315,10 +317,6 @@ class Activity(Window, Gtk.Container): * creates a base Gtk.Window within this window. * creates an ActivityService (self._bus) servicing this application. - - When your activity implements :func:`__init__`, it must call the - :class:`Activity` class :func:`__init__` before any - :class:`Activity` specific code. """ __gtype_name__ = 'SugarActivity' @@ -332,13 +330,16 @@ class Activity(Window, Gtk.Container): } def __init__(self, handle, create_jobject=True): + Gtk.Window.__init__(self) + if hasattr(GLib, 'unix_signal_add'): GLib.unix_signal_add( GLib.PRIORITY_DEFAULT, signal.SIGINT, self.close) # Stuff that needs to be done early icons_path = os.path.join(get_bundle_path(), 'icons') - Gtk.IconTheme.get_default().append_search_path(icons_path) + display = Gdk.Display.get_default() + Gtk.IconTheme.get_for_display(display).add_search_path(icons_path) sugar_theme = 'sugar-72' if 'SUGAR_SCALING' in os.environ: @@ -350,20 +351,17 @@ def __init__(self, handle, create_jobject=True): settings = Gtk.Settings.get_default() settings.set_property('gtk-theme-name', sugar_theme) settings.set_property('gtk-icon-theme-name', 'sugar') - settings.set_property('gtk-button-images', True) settings.set_property('gtk-font-name', - '%s %f' % (style.FONT_FACE, style.FONT_SIZE)) + '%s %f' % (style.FONT_FACE, style.FONT_SIZE)) - Window.__init__(self) + self.set_titlebar(Gtk.HeaderBar()) if 'SUGAR_ACTIVITY_ROOT' in os.environ: # If this activity runs inside Sugar, we want it to take all the # screen. Would be better if it was the shell to do this, but we # haven't found yet a good way to do it there. See #1263. - self.connect('window-state-event', self.__window_state_event_cb) - screen = Gdk.Screen.get_default() - screen.connect('size-changed', self.__screen_size_changed_cb) - self._adapt_window_to_screen() + self.connect('notify::window-state', self.__window_state_event_cb) + self.connect('realize', self.__on_realize) # process titles will only show 15 characters # but they get truncated anyway so if more characters @@ -373,7 +371,7 @@ def __init__(self, handle, create_jobject=True): util.set_proc_title(proc_title) self.connect('realize', self.__realize_cb) - self.connect('delete-event', self.__delete_event_cb) + self.connect('close-request', self.__delete_event_cb) self._in_main = False self._iconify = False @@ -396,13 +394,9 @@ def __init__(self, handle, create_jobject=True): self._session = _get_session() self._session.register(self) - self._session.connect('quit-requested', - self.__session_quit_requested_cb) - self._session.connect('quit', self.__session_quit_cb) - accel_group = Gtk.AccelGroup() - self.sugar_accel_group = accel_group - self.add_accel_group(accel_group) + self.shortcut_controller = Gtk.ShortcutController() + self.add_controller(self.shortcut_controller) self._bus = ActivityService(self) self._owns_file = False @@ -442,9 +436,6 @@ def __init__(self, handle, create_jobject=True): self._client_handler = _ClientHandler( self.get_bundle_id(), partial(self.__got_channel_cb, wait_loop)) - # FIXME: The current API requires that self.shared_activity is set - # before exiting from __init__, so we wait until we have got the - # shared activity. http://bugs.sugarlabs.org/ticket/2168 wait_loop.run() else: pservice = presenceservice.get_instance() @@ -468,12 +459,19 @@ def __init__(self, handle, create_jobject=True): self._stop_buttons = [] if self._is_resumed and get_save_as(): - # preserve original and use a copy for editing self._jobject_old = self._jobject self._jobject = datastore.copy(self._jobject, '/') self._original_title = self._jobject.metadata['title'] + def __on_realize(self, window): + display = Gdk.Display.get_default() + surface = self.get_surface() + if surface: + monitor = display.get_monitor_at_surface(surface) + monitor.connect('notify::geometry', self.__screen_size_changed_cb) + self._adapt_window_to_screen() + def add_stop_button(self, button): """ Register an extra stop button. Normally not required. Use only @@ -492,32 +490,33 @@ def iconify(self): def run_main_loop(self): if self._iconify: - Window.iconify(self) + self.iconify() self._in_main = True - Gtk.main() + self._session._main_loop.run() def _initialize_journal_object(self): - title = _('%s Activity') % get_bundle_name() - - icon_color = get_color().to_string() - - jobject = datastore.create() - jobject.metadata['title'] = title - jobject.metadata['title_set_by_user'] = '0' - jobject.metadata['activity'] = self.get_bundle_id() - jobject.metadata['activity_id'] = self.get_id() - jobject.metadata['keep'] = '0' - jobject.metadata['preview'] = '' - jobject.metadata['share-scope'] = SCOPE_PRIVATE - jobject.metadata['icon-color'] = icon_color - jobject.metadata['launch-times'] = str(int(time.time())) - jobject.metadata['spent-times'] = '0' - jobject.file_path = '' - - # FIXME: We should be able to get an ID synchronously from the DS, - # then call async the actual create. - # http://bugs.sugarlabs.org/ticket/2169 - datastore.write(jobject) + if self._jobject is not None: + return self._jobject + + if self._object_id is None: + # Create a new activity instance + jobject = datastore.create() + else: + # Resume an activity instance + try: + jobject = datastore.get(self._object_id) + except (TypeError, dbus.exceptions.DBusException) as e: + logging.warning('Error getting object from datastore: %s', e) + jobject = datastore.create() + self._object_id = None + + try: + jobject.metadata['activity'] = self.get_bundle_id() + jobject.metadata['activity_id'] = self.get_id() + jobject.metadata['keep'] = '0' + jobject.file_path = '' + except (AttributeError, TypeError) as e: + logging.warning('Error setting metadata: %s', e) return jobject @@ -531,7 +530,7 @@ def _set_up_sharing(self, mesh_instance, share_scope): logging.debug('*** Act %s, mesh instance %r, scope %s' % (self._activity_id, mesh_instance, share_scope)) if mesh_instance is not None: - # There's already an instance on the mesh, join it + # There's already an instance on the mesh, join its logging.debug('*** Act %s joining existing mesh instance %r' % (self._activity_id, mesh_instance)) self.shared_activity = mesh_instance @@ -712,20 +711,12 @@ def __window_state_event_cb(self, window, event): self.move(0, 0) def _adapt_window_to_screen(self): - screen = Gdk.Screen.get_default() - rect = screen.get_monitor_geometry(screen.get_number()) - 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) - self.set_geometry_hints(None, geometry, hints) + display = Gdk.Display.get_default() + monitor = display.get_monitor_at_surface(self.get_surface()) + rect = monitor.get_geometry() + + self.set_default_size(rect.width, rect.height) + self.set_size_request(rect.width, rect.height) def __session_quit_requested_cb(self, session): self._quit_requested = True @@ -872,12 +863,15 @@ def get_preview(self): return None alloc = self.canvas.get_allocation() - + width, height = alloc.width, alloc.height + if width == 0 or height == 0: + return None + dummy_cr = Gdk.cairo_create(window) target = dummy_cr.get_target() canvas_width, canvas_height = alloc.width, alloc.height screenshot_surface = target.create_similar(cairo.CONTENT_COLOR, - canvas_width, canvas_height) + canvas_width, canvas_height) del dummy_cr, target cr = cairo.Context(screenshot_surface) @@ -889,7 +883,7 @@ def get_preview(self): preview_width, preview_height = PREVIEW_SIZE preview_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - preview_width, preview_height) + preview_width, preview_height) cr = cairo.Context(preview_surface) scale_w = preview_width * 1.0 / canvas_width @@ -986,13 +980,15 @@ def set_last_value(values_list, new_value): # Cannot call datastore.write async for creates: # https://dev.laptop.org/ticket/3071 if self._jobject.object_id is None: - datastore.write(self._jobject, transfer_ownership=True) + datastore.write(self._jobject) else: self._updating_jobject = True - datastore.write(self._jobject, - transfer_ownership=True, - reply_handler=self.__save_cb, - error_handler=self.__save_error_cb) + try: + datastore.write(self._jobject) + except Exception as exc: + self.__save_error_cb(exc) + else: + self.__save_cb() def copy(self): ''' @@ -1336,18 +1332,20 @@ def close(self, skip_save=False): self._do_close(skip_save) def __realize_cb(self, window): - display_name = Gdk.Display.get_default().get_name() - if ':' in display_name: - # X11 for sure; this only works in X11 - xid = window.get_window().get_xid() - SugarExt.wm_set_bundle_id(xid, self.get_bundle_id()) - SugarExt.wm_set_activity_id(xid, str(self._activity_id)) - elif display_name == 'Broadway': - # GTK3's HTML5 backend - # This is needed so that the window takes the whole browser window + display = Gdk.Display.get_default() + surface = self.get_surface() + if surface and display.__class__.__name__ == 'X11Display': + # Use X11Surface.get_xid(surface) in GTK4: + # GTK4 no longer supports SugarExt.wm_set_bundle_id() + # or SugarExt.wm_set_activity_id() + # Remove or comment them out entirely: + # # SugarExt.wm_set_bundle_id(xid, self.get_bundle_id()) + # SugarExt.wm_set_activity_id(xid, str(self._activity_id)) + pass + elif display.get_name() == 'Broadway': self.maximize() - def __delete_event_cb(self, widget, event): + def __delete_event_cb(self, *args): self.close() return True @@ -1401,38 +1399,29 @@ def get_document_path(self, async_cb, async_err_cb): def busy(self): ''' - Show that the activity is busy. If used, must be called once - before a lengthy operation, and :meth:`unbusy` must be called - after the operation completes. - - .. code-block:: python - - self.busy() - self.long_operation() - self.unbusy() + Show that the activity is busy. + In GTK4 the busy indicator is implemented without changing the cursor. ''' - if self._busy_count == 0: - self._old_cursor = self.get_window().get_cursor() - self._set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) self._busy_count += 1 + # Optionally, you could add a CSS class or overlay here for a busy indicator. def unbusy(self): ''' - Show that the activity is not busy. An equal number of calls - to :meth:`unbusy` are required to balance the calls to - :meth:`busy`. - + Show that the activity is not busy. Returns: - int: a count of further calls to :meth:`unbusy` expected + int: the updated busy counter. ''' - self._busy_count -= 1 - if self._busy_count == 0: - self._set_cursor(self._old_cursor) + self._busy_count = max(0, self._busy_count - 1) return self._busy_count def _set_cursor(self, cursor): - self.get_window().set_cursor(cursor) - Gdk.flush() + # In GTK4, setting a cursor on a window is not supported. + # This method is now a no-op. + pass + + def reveal(self): + """Bring the activity window to the front.""" + self.present() class _ClientHandler(dbus.service.Object): @@ -1511,6 +1500,8 @@ def GetAll(self, interface_name): return r else: logging.debug('InvalidArgument') + + _session = None diff --git a/src/sugar3/activity/bundlebuilder.py b/src/sugar3/activity/bundlebuilder.py index 82bf1f9bb..51ccf8ea0 100644 --- a/src/sugar3/activity/bundlebuilder.py +++ b/src/sugar3/activity/bundlebuilder.py @@ -130,7 +130,7 @@ def build_locale(self): po_dir = os.path.join(self.config.source_dir, 'po') if not self.config.bundle.is_dir(po_dir): - logging.warn('Missing po/ dir, cannot build_locale') + logging.warning('Missing po/ dir, cannot build_locale') return if os.path.exists(self.locale_dir): @@ -163,7 +163,7 @@ def build_locale(self): translated_summary = '' if translated_summary.find('\n') > -1: translated_summary = translated_summary.replace('\n', '') - logging.warn( + logging.warning( 'Translation of summary on file %s have \\n chars. ' 'Should be removed' % file_name) linfo_file = os.path.join(localedir, 'activity.linfo') @@ -195,14 +195,14 @@ def get_files_in_git(self, root=None): stdout=subprocess.PIPE, cwd=root) except OSError: - logging.warn('Packager: git is not installed, ' + logging.warning('Packager: git is not installed, ' 'fall back to filtered list') if git_ls is not None: stdout, _ = git_ls.communicate() if git_ls.returncode: # Fall back to filtered list - logging.warn('Packager: this is not a git repository, ' + logging.warning('Packager: this is not a git repository, ' 'fall back to filtered list') elif stdout: # pylint: disable=E1103 diff --git a/src/sugar3/activity/webactivity.py b/src/sugar3/activity/webactivity.py index 9fadaee57..cdb2a3858 100644 --- a/src/sugar3/activity/webactivity.py +++ b/src/sugar3/activity/webactivity.py @@ -20,8 +20,8 @@ import logging import gi -gi.require_version('Gdk', '3.0') -gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '4.0') +gi.require_version('Gtk', '4.0') gi.require_version('WebKit2', '4.1') from gi.repository import Gdk diff --git a/src/sugar3/activity/widgets.py b/src/sugar3/activity/widgets.py index f5097f7da..b1b2dcc1f 100755 --- a/src/sugar3/activity/widgets.py +++ b/src/sugar3/activity/widgets.py @@ -47,7 +47,6 @@ def _create_activity_icon(metadata): from sugar3.activity.activity import get_bundle_path bundle = get_bundle_instance(get_bundle_path()) icon = Icon(file=bundle.get_icon(), xo_color=color) - return icon @@ -55,18 +54,20 @@ class ActivityButton(ToolButton): def __init__(self, activity, **kwargs): ToolButton.__init__(self, **kwargs) - icon = _create_activity_icon(activity.metadata) - self.set_icon_widget(icon) - icon.show() - + self.set_child(icon) + icon.set_visible(True) self.props.hide_tooltip_on_click = False self.palette_invoker.props.toggle_palette = True - self.props.tooltip = activity.metadata['title'] + # Use safe get for title + self.props.tooltip_text = activity.metadata.get('title', '') activity.metadata.connect('updated', self.__jobject_updated_cb) def __jobject_updated_cb(self, jobject): - self.props.tooltip = jobject['title'] + # Use get('title', '') to avoid KeyError + self.props.tooltip_text = jobject.get('title', '') + + class ActivityToolbarButton(ToolbarButton): @@ -74,19 +75,22 @@ class ActivityToolbarButton(ToolbarButton): def __init__(self, activity, **kwargs): toolbar = ActivityToolbar(activity, orientation_left=True) toolbar.connect('enter-key-press', lambda widget: self.emit('clicked')) - + # Initialize with the toolbar as page. ToolbarButton.__init__(self, page=toolbar, **kwargs) - icon = _create_activity_icon(activity.metadata) - self.set_icon_widget(icon) - icon.show() + self.set_child(icon) + icon.set_visible(True) + + def do_snapshot(self, snapshot): + # Override do_snapshot to bypass ToolbarBox’s super() call that fails when self isn’t a ToolbarBox. + return None class StopButton(ToolButton): def __init__(self, activity, **kwargs): ToolButton.__init__(self, 'activity-stop', **kwargs) - self.props.tooltip = _('Stop') + self.props.tooltip_text = _('Stop') self.props.accelerator = 'Q' self.connect('clicked', self.__stop_button_clicked_cb, activity) activity.add_stop_button(self) @@ -94,12 +98,14 @@ def __init__(self, activity, **kwargs): def __stop_button_clicked_cb(self, button, activity): activity.close() + def get_toplevel(self): + return self.get_ancestor(Gtk.Window) class UndoButton(ToolButton): def __init__(self, **kwargs): ToolButton.__init__(self, 'edit-undo', **kwargs) - self.props.tooltip = _('Undo') + self.props.tooltip_text = _('Undo') self.props.accelerator = 'Z' @@ -107,7 +113,7 @@ class RedoButton(ToolButton): def __init__(self, **kwargs): ToolButton.__init__(self, 'edit-redo', **kwargs) - self.props.tooltip = _('Redo') + self.props.tooltip_text = _('Redo') self.props.accelerator = 'Y' @@ -115,7 +121,7 @@ class CopyButton(ToolButton): def __init__(self, **kwargs): ToolButton.__init__(self, 'edit-copy', **kwargs) - self.props.tooltip = _('Copy') + self.props.tooltip_text = _('Copy') self.props.accelerator = 'C' @@ -123,7 +129,7 @@ class PasteButton(ToolButton): def __init__(self, **kwargs): ToolButton.__init__(self, 'edit-paste', **kwargs) - self.props.tooltip = _('Paste') + self.props.tooltip_text = _('Paste') self.props.accelerator = 'V' @@ -170,75 +176,72 @@ def __update_share_cb(self, activity): self.neighborhood.handler_unblock(self._neighborhood_handle) -class TitleEntry(Gtk.ToolItem): +class TitleEntry(Gtk.Box): __gsignals__ = { - 'enter-key-press': (GObject.SignalFlags.RUN_FIRST, None, ([])), + 'enter-key-press': (GObject.SignalFlags.RUN_FIRST, None, ()) } - + def __init__(self, activity, **kwargs): - Gtk.ToolItem.__init__(self) - self.set_expand(False) - + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) + self.set_hexpand(False) + self.set_vexpand(False) self.entry = Gtk.Entry(**kwargs) - self.entry.set_size_request(int(Gdk.Screen.width() / 3), -1) - self.entry.set_text(activity.metadata['title']) - self.entry.connect( - 'focus-out-event', self.__focus_out_event_cb, activity) + + # Update screen dimension handling + display = Gdk.Display.get_default() + monitors = display.get_monitors() + monitor = monitors.get_item(0) + geometry = monitor.get_geometry() + self.entry.set_size_request(int(geometry.width / 3), -1) + + # Safe retrieval of title + self.entry.set_text(activity.metadata.get('title', '')) + self.entry.connect('notify::has-focus', self.__focus_changed_cb, activity) self.entry.connect('activate', self.__activate_cb, activity) - self.entry.connect('button-press-event', self.__button_press_event_cb) - self.entry.show() - self.add(self.entry) - - activity.metadata.connect('updated', self.__jobject_updated_cb) - activity.connect('closing', self.__closing_cb) - + self.entry.set_visible(True) + # Use a GestureClick on the entry + click_controller = Gtk.GestureClick() + click_controller.connect("pressed", self.__on_entry_clicked) + self.entry.add_controller(click_controller) + + self.append(self.entry) + + def __on_entry_clicked(self, gesture, n_press, x, y): + if not self.entry.is_focus(): + self.entry.grab_focus() + self.entry.select_region(0, -1) + return True + + def __focus_changed_cb(self, widget, param_spec, activity): + if not widget.has_focus(): + widget.select_region(0, 0) + self.save_title(activity) + def __activate_cb(self, entry, activity): self.save_title(activity) entry.select_region(0, 0) - entry.hide() - entry.show() + entry.set_visible(False) + entry.set_visible(True) self.emit('enter-key-press') return False - def modify_bg(self, state, color): - Gtk.ToolItem.modify_bg(self, state, color) - self.entry.modify_bg(state, color) - def __jobject_updated_cb(self, jobject): + # Use get() to avoid KeyError while updating title + new_title = jobject.get('title', '') if self.entry.has_focus(): return - if self.entry.get_text() == jobject['title']: + if self.entry.get_text() == new_title: return - self.entry.set_text(jobject['title']) - - def __closing_cb(self, activity): - self.save_title(activity) - return False - - def __focus_out_event_cb(self, widget, event, activity): - widget.select_region(0, 0) - self.save_title(activity) - return False - - def __button_press_event_cb(self, widget, event): - if widget.is_focus(): - return False - else: - widget.grab_focus() - widget.select_region(0, -1) - return True + self.entry.set_text(new_title) def save_title(self, activity): title = self.entry.get_text() - if title == activity.metadata['title']: + if title == activity.metadata.get('title', ''): return - activity.metadata['title'] = title activity.metadata['title_set_by_user'] = '1' activity.save() - activity.set_title(title) - shared_activity = activity.get_shared_activity() if shared_activity is not None: shared_activity.props.name = title @@ -256,49 +259,48 @@ def __init__(self, activity, **kwargs): description_box = PaletteMenuBox() sw = Gtk.ScrolledWindow() - sw.set_size_request(int(Gdk.Screen.width() / 2), - 2 * style.GRID_CELL_SIZE) + display = Gdk.Display.get_default() + monitor = display.get_monitors().get_item(0) + geometry = monitor.get_geometry() + sw.set_size_request(int(geometry.width / 2), 2 * style.GRID_CELL_SIZE) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self._text_view = Gtk.TextView() self._text_view.set_cursor_visible(True) - self._text_view.set_left_margin(style.DEFAULT_PADDING) - self._text_view.set_right_margin(style.DEFAULT_PADDING) + self._text_view.set_margin_start(style.DEFAULT_PADDING) + self._text_view.set_margin_end(style.DEFAULT_PADDING) self._text_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) text_buffer = Gtk.TextBuffer() if 'description' in activity.metadata: text_buffer.set_text(activity.metadata['description']) self._text_view.set_buffer(text_buffer) - self._text_view.connect('focus-out-event', - self.__description_changed_cb, activity) - sw.add(self._text_view) + self._text_view.connect('notify::has-focus', self.__focus_changed_cb, activity) + sw.set_child(self._text_view) description_box.append_item(sw, vertical_padding=0) self._palette.set_content(description_box) - description_box.show_all() + description_box.set_visible(True) activity.metadata.connect('updated', self.__jobject_updated_cb) - def set_expanded(self, expanded): - box = self.toolbar_box - if not box: - return + def __focus_changed_cb(self, widget, pspec, activity): + if not widget.has_focus(): + self.__description_changed_cb(widget, None, activity) - if not expanded: - self.palette_invoker.notify_popdown() + def __jobject_updated_cb(self, jobject): + if self._text_view.has_focus(): return + descr = jobject.get('description', '') + if self._get_text_from_buffer() == descr: + return + buf = self._text_view.get_buffer() + buf.set_text(descr) - if box.expanded_button is not None: - box.expanded_button.queue_draw() - if box.expanded_button != self: - box.expanded_button.set_expanded(False) - box.expanded_button = self - - def get_toolbar_box(self): - parent = self.get_parent() - if not hasattr(parent, 'owner'): - return None - return parent.owner - - toolbar_box = property(get_toolbar_box) + def __description_changed_cb(self, widget, event, activity): + description = self._get_text_from_buffer() + if 'description' in activity.metadata and description == activity.metadata['description']: + return + activity.metadata['description'] = description + activity.save() + return False def _get_text_from_buffer(self): buf = self._text_view.get_buffer() @@ -306,6 +308,7 @@ def _get_text_from_buffer(self): end_iter = buf.get_end_iter() return buf.get_text(start_iter, end_iter, False) + def __jobject_updated_cb(self, jobject): if self._text_view.has_focus(): return @@ -327,43 +330,46 @@ def __description_changed_cb(self, widget, event, activity): return False -class ActivityToolbar(Gtk.Toolbar): +class ActivityToolbar(Gtk.Box): """The Activity toolbar with the Journal entry title and sharing button""" __gsignals__ = { 'enter-key-press': (GObject.SignalFlags.RUN_FIRST, None, ([])), } def __init__(self, activity, orientation_left=False): - Gtk.Toolbar.__init__(self) - + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) self._activity = activity + # Helper: remove child from existing parent before appending. + def safe_append(box, widget): + if widget.get_parent() is not None: + widget.get_parent().remove(widget) + box.append(widget) + if activity.metadata: title_button = TitleEntry(activity) - title_button.connect('enter-key-press', - lambda widget: self.emit('enter-key-press')) - title_button.show() - self.insert(title_button, -1) - self.title = title_button.entry + title_button.connect('enter-key-press', lambda widget: self.emit('enter-key-press')) + title_button.set_visible(True) + safe_append(self, title_button) if not orientation_left: - separator = Gtk.SeparatorToolItem() - separator.props.draw = False + separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + separator.set_visible(True) separator.set_expand(True) - self.insert(separator, -1) - separator.show() + safe_append(self, separator) if activity.metadata: description_item = DescriptionItem(activity) - description_item.show() - self.insert(description_item, -1) + description_item.set_visible(True) + safe_append(self, description_item) self.share = ShareButton(activity) - self.share.show() - self.insert(self.share, -1) + self.share.set_visible(True) + safe_append(self, self.share) + -class EditToolbar(Gtk.Toolbar): +class EditToolbar(Gtk.Box): """Provides the standard edit toolbar for Activities. Members: @@ -394,29 +400,28 @@ class EditToolbar(Gtk.Toolbar): # Add the edit toolbar: toolbox.add_toolbar(_('Edit'), self._edit_toolbar) # And make it visible: - self._edit_toolbar.show() + self._edit_toolbar.set_visible(True) """ def __init__(self): - Gtk.Toolbar.__init__(self) + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) self.undo = UndoButton() - self.insert(self.undo, -1) - self.undo.show() + self.append(self.undo) + self.undo.set_visible(True) self.redo = RedoButton() - self.insert(self.redo, -1) - self.redo.show() + self.append(self.redo) + self.redo.set_visible(True) - self.separator = Gtk.SeparatorToolItem() - self.separator.set_draw(True) - self.insert(self.separator, -1) - self.separator.show() + self.separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) + self.separator.set_visible(True) + self.append(self.separator) self.copy = CopyButton() - self.insert(self.copy, -1) - self.copy.show() + self.append(self.copy) + self.copy.set_visible(True) self.paste = PasteButton() - self.insert(self.paste, -1) - self.paste.show() + self.append(self.paste) + self.paste.set_visible(True) diff --git a/src/sugar3/bundle/activitybundle.py b/src/sugar3/bundle/activitybundle.py index 87aa79c71..c683e21ef 100644 --- a/src/sugar3/bundle/activitybundle.py +++ b/src/sugar3/bundle/activitybundle.py @@ -130,10 +130,18 @@ def __init__(self, path, translated=True): def _parse_info(self, info_file): cp = ConfigParser() - if six.PY2: - cp.readfp(info_file) - else: - cp.read_string(info_file.read().decode()) + + content = info_file.read() + if isinstance(content, bytes): + content = content.decode("utf-8") + try: + if content.startswith("b'") or content.startswith('b"'): + import ast + content = ast.literal_eval(content) + except Exception: + pass + + cp.read_string(content, source=getattr(info_file, 'name', '')) section = 'Activity' @@ -258,7 +266,7 @@ def _parse_linfo(self, linfo_file): if six.PY2: cp.readfp(linfo_file) else: - cp.read_string(linfo_file.read().decode()) + cp.read_file(linfo_file) except ParsingError as e: logging.exception('Exception reading linfo file: %s', e) return diff --git a/src/sugar3/bundle/contentbundle.py b/src/sugar3/bundle/contentbundle.py index 0e61ade08..6cd8eedb0 100644 --- a/src/sugar3/bundle/contentbundle.py +++ b/src/sugar3/bundle/contentbundle.py @@ -68,7 +68,7 @@ def __init__(self, path): def _parse_info(self, info_file): cp = ConfigParser() - cp.readfp(info_file) + cp.read_file(info_file) section = 'Library' diff --git a/src/sugar3/datastore/datastore.py b/src/sugar3/datastore/datastore.py index 3dfb81445..abea601a6 100644 --- a/src/sugar3/datastore/datastore.py +++ b/src/sugar3/datastore/datastore.py @@ -29,6 +29,8 @@ from gi.repository import GObject from gi.repository import Gio import dbus +_bus = dbus.SessionBus() +import uuid from sugar3 import env from sugar3 import mime @@ -44,16 +46,14 @@ def _get_data_store(): global _data_store - - if not _data_store: - _bus = dbus.SessionBus() - _data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, - DS_DBUS_PATH), - DS_DBUS_INTERFACE) - _data_store.connect_to_signal('Created', __datastore_created_cb) - _data_store.connect_to_signal('Deleted', __datastore_deleted_cb) - _data_store.connect_to_signal('Updated', __datastore_updated_cb) - + if _data_store is None: + try: + _data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, + DS_DBUS_PATH), + DS_DBUS_INTERFACE) + except dbus.exceptions.DBusException as e: + logging.warning('Could not connect to DataStore service: %s', e) + return None return _data_store @@ -95,8 +95,8 @@ def __init__(self, properties=None): pass self._properties = properties - default_keys = ['activity', 'activity_id', - 'mime_type', 'title_set_by_user'] + default_keys = ['activity', 'activity_id', 'mime_type', 'title', + 'title_set_by_user', 'spent-times'] for key in default_keys: if key not in self._properties: self._properties[key] = '' @@ -151,16 +151,18 @@ class DSObject(object): def __init__(self, object_id, metadata=None, file_path=None): self._update_signal_match = None self._object_id = None - self.set_object_id(object_id) - - self._metadata = metadata + from sugar3.datastore.datastore import DSMetadata + self._metadata = metadata if metadata is not None else DSMetadata() self._file_path = file_path self._destroyed = False self._owns_file = False - + def get_object_id(self): return self._object_id + + def get_properties(self): + return self.metadata def set_object_id(self, object_id): if self._update_signal_match is not None: @@ -226,6 +228,12 @@ def __del__(self): def copy(self): return DSObject(None, self._metadata.copy(), self._file_path) + + def get_preview(self): + return self.metadata.get('preview', '') + + def get_icon(self): + return self.metadata.get('icon', '') class RawObject(object): @@ -346,8 +354,7 @@ def _create_ds_entry(properties, filename, transfer_ownership=False): return object_id -def write(ds_object, update_mtime=True, transfer_ownership=False, - reply_handler=None, error_handler=None, timeout=-1): +def write(jobject): """Write the DSObject given to the datastore. Creates a new entry if the entry does not exist yet. @@ -364,37 +371,18 @@ def write(ds_object, update_mtime=True, transfer_ownership=False, timeout -- dbus timeout for the caller to wait (default -1) """ - logging.debug('datastore.write') - - properties = ds_object.metadata.get_dictionary().copy() - - if update_mtime: - properties['mtime'] = datetime.now().isoformat() - properties['timestamp'] = int(time.time()) - - file_path = ds_object.get_file_path(fetch=False) - if file_path is None: - file_path = '' - - # FIXME: this func will be sync for creates regardless of the handlers - # supplied. This is very bad API, need to decide what to do here. - if ds_object.object_id: - _update_ds_entry(ds_object.object_id, - properties, - file_path, - transfer_ownership, - reply_handler=reply_handler, - error_handler=error_handler, - timeout=timeout) + if jobject.object_id is None: + try: + jobject.object_id = _create_ds_entry(jobject.get_properties(), + jobject.get_file_path()) + except (TypeError, dbus.exceptions.DBusException) as e: + logging.warning('Error writing to datastore: %s', e) + # Create a temporary object ID if DataStore is not available + jobject.object_id = 'temp_' + str(uuid.uuid4()) else: - if reply_handler or error_handler: - logging.warning('datastore.write() cannot currently be called' - 'async for creates, see ticket 3071') - ds_object.object_id = _create_ds_entry(properties, file_path, - transfer_ownership) - ds_object.metadata['uid'] = ds_object.object_id - # TODO: register the object for updates - logging.debug('Written object %s to the datastore.', ds_object.object_id) + _update_ds_entry(jobject.object_id, + jobject.get_properties(), + jobject.get_file_path()) def delete(object_id): diff --git a/src/sugar3/dispatch/dispatcher.py b/src/sugar3/dispatch/dispatcher.py index 222fc7f50..e2a65866b 100644 --- a/src/sugar3/dispatch/dispatcher.py +++ b/src/sugar3/dispatch/dispatcher.py @@ -1,11 +1,6 @@ import weakref import six -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - from sugar3.dispatch import saferef WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref) diff --git a/src/sugar3/eggaccelerators.c b/src/sugar3/eggaccelerators.c index 952cd3e08..1da48dcc1 100644 --- a/src/sugar3/eggaccelerators.c +++ b/src/sugar3/eggaccelerators.c @@ -18,169 +18,467 @@ * Boston, MA 02111-1307, USA. */ +#include // Ensure GdkKeymap is declared #include "eggaccelerators.h" #include #include -#include + +#include #include enum { - EGG_MODMAP_ENTRY_SHIFT = 0, - EGG_MODMAP_ENTRY_LOCK = 1, + EGG_MODMAP_ENTRY_SHIFT = 0, + EGG_MODMAP_ENTRY_LOCK = 1, EGG_MODMAP_ENTRY_CONTROL = 2, - EGG_MODMAP_ENTRY_MOD1 = 3, - EGG_MODMAP_ENTRY_MOD2 = 4, - EGG_MODMAP_ENTRY_MOD3 = 5, - EGG_MODMAP_ENTRY_MOD4 = 6, - EGG_MODMAP_ENTRY_MOD5 = 7, - EGG_MODMAP_ENTRY_LAST = 8 + EGG_MODMAP_ENTRY_MOD1 = 3, + EGG_MODMAP_ENTRY_MOD2 = 4, + EGG_MODMAP_ENTRY_MOD3 = 5, + EGG_MODMAP_ENTRY_MOD4 = 6, + EGG_MODMAP_ENTRY_MOD5 = 7, + EGG_MODMAP_ENTRY_LAST = 8 }; #define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x)) -typedef struct +typedef struct { + EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST]; +} EggModmap; + +/* Helper functions - these stay outside the version check */ +static inline gboolean +is_alt(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'a' || string[1] == 'A') && + (string[2] == 'l' || string[2] == 'L') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == '>')); +} + +static inline gboolean +is_ctl(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == '>')); +} + +static inline gboolean +is_modx(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'd' || string[3] == 'D') && + (string[4] >= '1' && string[4] <= '5') && + (string[5] == '>')); +} + +static inline gboolean +is_ctrl(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'r' || string[3] == 'R') && + (string[4] == 'l' || string[4] == 'L') && + (string[5] == '>')); +} + +static inline gboolean +is_shft(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'f' || string[3] == 'F') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == '>')); +} + +static inline gboolean +is_shift(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'i' || string[3] == 'I') && + (string[4] == 'f' || string[4] == 'F') && + (string[5] == 't' || string[5] == 'T') && + (string[6] == '>')); +} + +static inline gboolean +is_control(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'n' || string[3] == 'N') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == 'o' || string[6] == 'O') && + (string[7] == 'l' || string[7] == 'L') && + (string[8] == '>')); +} + +static inline gboolean +is_release(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'r' || string[1] == 'R') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'a' || string[5] == 'A') && + (string[6] == 's' || string[6] == 'S') && + (string[7] == 'e' || string[7] == 'E') && + (string[8] == '>')); +} + +static inline gboolean +is_meta(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == 'a' || string[4] == 'A') && + (string[5] == '>')); +} + +static inline gboolean +is_super(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'u' || string[2] == 'U') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); +} + +static inline gboolean +is_hyper(const gchar *string) { + return ((string[0] == '<') && + (string[1] == 'h' || string[1] == 'H') && + (string[2] == 'y' || string[2] == 'Y') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); +} + +static inline gboolean +is_keycode(const gchar *string) { + return ((string[0] == '0') && + (string[1] == 'x')); +} + +/* Main parse function - outside version check */ +gboolean +egg_accelerator_parse_virtual(const gchar *accelerator, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods) { - EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST]; + guint keyval; + GdkModifierType mods; + gint len; + gboolean bad_keyval; + + if (accelerator_key) + *accelerator_key = 0; + if (accelerator_mods) + *accelerator_mods = 0; + if (keycode) + *keycode = 0; + + g_return_val_if_fail(accelerator != NULL, FALSE); + + bad_keyval = FALSE; + keyval = 0; + mods = 0; + len = strlen(accelerator); + + while (len) { + if (*accelerator == '<') { + if (len >= 9 && is_release(accelerator)) { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_RELEASE_MASK; + } else if (len >= 9 && is_control(accelerator)) { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } else if (len >= 7 && is_shift(accelerator)) { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } else if (len >= 6 && is_shft(accelerator)) { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } else if (len >= 6 && is_ctrl(accelerator)) { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } else if (len >= 6 && is_modx(accelerator)) { + static const guint mod_vals[] = { + EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, + EGG_VIRTUAL_MOD3_MASK, EGG_VIRTUAL_MOD4_MASK, + EGG_VIRTUAL_MOD5_MASK + }; + + len -= 6; + accelerator += 4; + mods |= mod_vals[*accelerator - '1']; + accelerator += 2; + } else if (len >= 5 && is_ctl(accelerator)) { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } else if (len >= 5 && is_alt(accelerator)) { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_ALT_MASK; + } else if (len >= 6 && is_meta(accelerator)) { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_META_MASK; + } else if (len >= 7 && is_hyper(accelerator)) { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_HYPER_MASK; + } else if (len >= 7 && is_super(accelerator)) { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SUPER_MASK; + } else { + gchar last_ch; + last_ch = *accelerator; + while (last_ch && last_ch != '>') { + last_ch = *accelerator; + accelerator += 1; + len -= 1; + } + } + } else { + keyval = gdk_keyval_from_name(accelerator); + + if (keyval == 0) { + if (len >= 4 && is_keycode(accelerator)) { + char keystring[5]; + gchar *endptr; + gint tmp_keycode; + + memcpy(keystring, accelerator, 4); + keystring[4] = '\0'; + + tmp_keycode = strtol(keystring, &endptr, 16); + + if (endptr == NULL || *endptr != '\0') { + bad_keyval = TRUE; + } else if (keycode != NULL) { + *keycode = tmp_keycode; + if (*keycode == 0) + bad_keyval = TRUE; + } + } + } else if (keycode != NULL) { + GdkDisplay *display = gdk_display_get_default(); + if (display) { + *keycode = XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(display), + keyval); + } + } + accelerator += len; + len -= len; + } + } -} EggModmap; + if (accelerator_key) + *accelerator_key = gdk_keyval_to_lower(keyval); + if (accelerator_mods) + *accelerator_mods = mods; + + return !bad_keyval; +} -const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap); +#if GTK_CHECK_VERSION(4,0,0) +/* GTK4 implementations */ +const EggModmap * +egg_keymap_get_modmap(GdkKeymap *keymap) +{ + static EggModmap default_modmap = { + { EGG_VIRTUAL_SHIFT_MASK, + EGG_VIRTUAL_LOCK_MASK, + EGG_VIRTUAL_CONTROL_MASK, + EGG_VIRTUAL_ALT_MASK, + EGG_VIRTUAL_MOD2_MASK, + EGG_VIRTUAL_MOD3_MASK, + EGG_VIRTUAL_MOD4_MASK, + EGG_VIRTUAL_MOD5_MASK } + }; + return &default_modmap; +} + +void +egg_keymap_resolve_virtual_modifiers(GdkKeymap *keymap, + EggVirtualModifierType virtual_mods, + GdkModifierType *concrete_mods) +{ + *concrete_mods = (GdkModifierType)virtual_mods; +} + +void +egg_keymap_virtualize_modifiers(GdkKeymap *keymap, + GdkModifierType concrete_mods, + EggVirtualModifierType *virtual_mods) +{ + *virtual_mods = (EggVirtualModifierType)concrete_mods; +} + +static void +reload_modmap(GdkKeymap *keymap, + EggModmap *modmap) +{ + /* Nothing to do in GTK4 */ +} + +#else +/* GTK3 implementations unchanged */ + +const EggModmap *egg_keymap_get_modmap(GdkKeymap *keymap); static inline gboolean -is_alt (const gchar *string) +is_alt(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'a' || string[1] == 'A') && - (string[2] == 'l' || string[2] == 'L') && - (string[3] == 't' || string[3] == 'T') && - (string[4] == '>')); + (string[1] == 'a' || string[1] == 'A') && + (string[2] == 'l' || string[2] == 'L') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == '>')); } static inline gboolean -is_ctl (const gchar *string) +is_ctl(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'c' || string[1] == 'C') && - (string[2] == 't' || string[2] == 'T') && - (string[3] == 'l' || string[3] == 'L') && - (string[4] == '>')); + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == '>')); } static inline gboolean -is_modx (const gchar *string) +is_modx(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'm' || string[1] == 'M') && - (string[2] == 'o' || string[2] == 'O') && - (string[3] == 'd' || string[3] == 'D') && - (string[4] >= '1' && string[4] <= '5') && - (string[5] == '>')); + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'd' || string[3] == 'D') && + (string[4] >= '1' && string[4] <= '5') && + (string[5] == '>')); } static inline gboolean -is_ctrl (const gchar *string) +is_ctrl(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'c' || string[1] == 'C') && - (string[2] == 't' || string[2] == 'T') && - (string[3] == 'r' || string[3] == 'R') && - (string[4] == 'l' || string[4] == 'L') && - (string[5] == '>')); + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'r' || string[3] == 'R') && + (string[4] == 'l' || string[4] == 'L') && + (string[5] == '>')); } static inline gboolean -is_shft (const gchar *string) +is_shft(const gchar *string) { return ((string[0] == '<') && - (string[1] == 's' || string[1] == 'S') && - (string[2] == 'h' || string[2] == 'H') && - (string[3] == 'f' || string[3] == 'F') && - (string[4] == 't' || string[4] == 'T') && - (string[5] == '>')); + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'f' || string[3] == 'F') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == '>')); } static inline gboolean -is_shift (const gchar *string) +is_shift(const gchar *string) { return ((string[0] == '<') && - (string[1] == 's' || string[1] == 'S') && - (string[2] == 'h' || string[2] == 'H') && - (string[3] == 'i' || string[3] == 'I') && - (string[4] == 'f' || string[4] == 'F') && - (string[5] == 't' || string[5] == 'T') && - (string[6] == '>')); + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'i' || string[3] == 'I') && + (string[4] == 'f' || string[4] == 'F') && + (string[5] == 't' || string[5] == 'T') && + (string[6] == '>')); } static inline gboolean -is_control (const gchar *string) +is_control(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'c' || string[1] == 'C') && - (string[2] == 'o' || string[2] == 'O') && - (string[3] == 'n' || string[3] == 'N') && - (string[4] == 't' || string[4] == 'T') && - (string[5] == 'r' || string[5] == 'R') && - (string[6] == 'o' || string[6] == 'O') && - (string[7] == 'l' || string[7] == 'L') && - (string[8] == '>')); + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'n' || string[3] == 'N') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == 'o' || string[6] == 'O') && + (string[7] == 'l' || string[7] == 'L') && + (string[8] == '>')); } static inline gboolean -is_release (const gchar *string) +is_release(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'r' || string[1] == 'R') && - (string[2] == 'e' || string[2] == 'E') && - (string[3] == 'l' || string[3] == 'L') && - (string[4] == 'e' || string[4] == 'E') && - (string[5] == 'a' || string[5] == 'A') && - (string[6] == 's' || string[6] == 'S') && - (string[7] == 'e' || string[7] == 'E') && - (string[8] == '>')); + (string[1] == 'r' || string[1] == 'R') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'a' || string[5] == 'A') && + (string[6] == 's' || string[6] == 'S') && + (string[7] == 'e' || string[7] == 'E') && + (string[8] == '>')); } static inline gboolean -is_meta (const gchar *string) +is_meta(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'm' || string[1] == 'M') && - (string[2] == 'e' || string[2] == 'E') && - (string[3] == 't' || string[3] == 'T') && - (string[4] == 'a' || string[4] == 'A') && - (string[5] == '>')); + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == 'a' || string[4] == 'A') && + (string[5] == '>')); } static inline gboolean -is_super (const gchar *string) +is_super(const gchar *string) { return ((string[0] == '<') && - (string[1] == 's' || string[1] == 'S') && - (string[2] == 'u' || string[2] == 'U') && - (string[3] == 'p' || string[3] == 'P') && - (string[4] == 'e' || string[4] == 'E') && - (string[5] == 'r' || string[5] == 'R') && - (string[6] == '>')); + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'u' || string[2] == 'U') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); } static inline gboolean -is_hyper (const gchar *string) +is_hyper(const gchar *string) { return ((string[0] == '<') && - (string[1] == 'h' || string[1] == 'H') && - (string[2] == 'y' || string[2] == 'Y') && - (string[3] == 'p' || string[3] == 'P') && - (string[4] == 'e' || string[4] == 'E') && - (string[5] == 'r' || string[5] == 'R') && - (string[6] == '>')); + (string[1] == 'h' || string[1] == 'H') && + (string[2] == 'y' || string[2] == 'Y') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); } static inline gboolean -is_keycode (const gchar *string) +is_keycode(const gchar *string) { return ((string[0] == '0') && - (string[1] == 'x')); + (string[1] == 'x')); } /** @@ -208,11 +506,12 @@ is_keycode (const gchar *string) * * Returns: %TRUE on success. */ + gboolean -egg_accelerator_parse_virtual (const gchar *accelerator, - guint *accelerator_key, - guint *keycode, - EggVirtualModifierType *accelerator_mods) +egg_accelerator_parse_virtual(const gchar *accelerator, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods) { guint keyval; GdkModifierType mods; @@ -226,376 +525,235 @@ egg_accelerator_parse_virtual (const gchar *accelerator, if (keycode) *keycode = 0; - g_return_val_if_fail (accelerator != NULL, FALSE); + g_return_val_if_fail(accelerator != NULL, FALSE); bad_keyval = FALSE; keyval = 0; mods = 0; - len = strlen (accelerator); + len = strlen(accelerator); while (len) + { + if (*accelerator == '<') { - if (*accelerator == '<') - { - if (len >= 9 && is_release (accelerator)) - { - accelerator += 9; - len -= 9; - mods |= EGG_VIRTUAL_RELEASE_MASK; - } - else if (len >= 9 && is_control (accelerator)) - { - accelerator += 9; - len -= 9; - mods |= EGG_VIRTUAL_CONTROL_MASK; - } - else if (len >= 7 && is_shift (accelerator)) - { - accelerator += 7; - len -= 7; - mods |= EGG_VIRTUAL_SHIFT_MASK; - } - else if (len >= 6 && is_shft (accelerator)) - { - accelerator += 6; - len -= 6; - mods |= EGG_VIRTUAL_SHIFT_MASK; - } - else if (len >= 6 && is_ctrl (accelerator)) - { - accelerator += 6; - len -= 6; - mods |= EGG_VIRTUAL_CONTROL_MASK; - } - else if (len >= 6 && is_modx (accelerator)) - { - static const guint mod_vals[] = { - EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK, - EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK - }; - - len -= 6; - accelerator += 4; - mods |= mod_vals[*accelerator - '1']; - accelerator += 2; - } - else if (len >= 5 && is_ctl (accelerator)) - { - accelerator += 5; - len -= 5; - mods |= EGG_VIRTUAL_CONTROL_MASK; - } - else if (len >= 5 && is_alt (accelerator)) - { - accelerator += 5; - len -= 5; - mods |= EGG_VIRTUAL_ALT_MASK; - } - else if (len >= 6 && is_meta (accelerator)) - { - accelerator += 6; - len -= 6; - mods |= EGG_VIRTUAL_META_MASK; - } - else if (len >= 7 && is_hyper (accelerator)) - { - accelerator += 7; - len -= 7; - mods |= EGG_VIRTUAL_HYPER_MASK; - } - else if (len >= 7 && is_super (accelerator)) - { - accelerator += 7; - len -= 7; - mods |= EGG_VIRTUAL_SUPER_MASK; - } - else - { - gchar last_ch; - - last_ch = *accelerator; - while (last_ch && last_ch != '>') - { - last_ch = *accelerator; - accelerator += 1; - len -= 1; - } - } - } + if (len >= 9 && is_release(accelerator)) + { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_RELEASE_MASK; + } + else if (len >= 9 && is_control(accelerator)) + { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 7 && is_shift(accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } + else if (len >= 6 && is_shft(accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } + else if (len >= 6 && is_ctrl(accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 6 && is_modx(accelerator)) + { + static const guint mod_vals[] = { + EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK, + EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK}; + + len -= 6; + accelerator += 4; + mods |= mod_vals[*accelerator - '1']; + accelerator += 2; + } + else if (len >= 5 && is_ctl(accelerator)) + { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 5 && is_alt(accelerator)) + { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_ALT_MASK; + } + else if (len >= 6 && is_meta(accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_META_MASK; + } + else if (len >= 7 && is_hyper(accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_HYPER_MASK; + } + else if (len >= 7 && is_super(accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SUPER_MASK; + } else - { - keyval = gdk_keyval_from_name (accelerator); - - if (keyval == 0) - { - /* If keyval is 0, than maybe it's a keycode. Check for 0x## */ - if (len >= 4 && is_keycode (accelerator)) - { - char keystring[5]; - gchar *endptr; - gint tmp_keycode; - - memcpy (keystring, accelerator, 4); - keystring [4] = '\000'; - - tmp_keycode = strtol (keystring, &endptr, 16); - - if (endptr == NULL || *endptr != '\000') - { - bad_keyval = TRUE; - } - else if (keycode != NULL) - { - *keycode = tmp_keycode; - /* 0x00 is an invalid keycode too. */ - if (*keycode == 0) - bad_keyval = TRUE; - } - } - } else if (keycode != NULL) { - GdkDisplay *display = gdk_display_get_default (); - *keycode = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY(display), - keyval); - } - accelerator += len; - len -= len; - } - } + { + gchar last_ch; - if (accelerator_key) - *accelerator_key = gdk_keyval_to_lower (keyval); - if (accelerator_mods) - *accelerator_mods = mods; - - return !bad_keyval; -} - - -/** - * egg_virtual_accelerator_name: - * @accelerator_key: accelerator keyval - * @accelerator_mods: accelerator modifier mask - * @returns: a newly-allocated accelerator name - * - * Converts an accelerator keyval and modifier mask - * into a string parseable by egg_accelerator_parse_virtual(). - * For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK, - * this function returns "<Control>q". - * - * The caller of this function must free the returned string. - */ -gchar* -egg_virtual_accelerator_name (guint accelerator_key, - guint keycode, - EggVirtualModifierType accelerator_mods) -{ - static const gchar text_release[] = ""; - static const gchar text_shift[] = ""; - static const gchar text_control[] = ""; - static const gchar text_mod1[] = ""; - static const gchar text_mod2[] = ""; - static const gchar text_mod3[] = ""; - static const gchar text_mod4[] = ""; - static const gchar text_mod5[] = ""; - static const gchar text_meta[] = ""; - static const gchar text_super[] = ""; - static const gchar text_hyper[] = ""; - guint l; - gchar *keyval_name; - gchar *accelerator; - - accelerator_mods &= EGG_VIRTUAL_MODIFIER_MASK; - - if (!accelerator_key) - { - keyval_name = g_strdup_printf ("0x%02x", keycode); + last_ch = *accelerator; + while (last_ch && last_ch != '>') + { + last_ch = *accelerator; + accelerator += 1; + len -= 1; + } + } } - else + else { - keyval_name = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key)); - if (!keyval_name) - keyval_name = ""; - } + keyval = gdk_keyval_from_name(accelerator); - l = 0; - if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK) - l += sizeof (text_release) - 1; - if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK) - l += sizeof (text_shift) - 1; - if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK) - l += sizeof (text_control) - 1; - if (accelerator_mods & EGG_VIRTUAL_ALT_MASK) - l += sizeof (text_mod1) - 1; - if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK) - l += sizeof (text_mod2) - 1; - if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK) - l += sizeof (text_mod3) - 1; - if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK) - l += sizeof (text_mod4) - 1; - if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK) - l += sizeof (text_mod5) - 1; - if (accelerator_mods & EGG_VIRTUAL_META_MASK) - l += sizeof (text_meta) - 1; - if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK) - l += sizeof (text_hyper) - 1; - if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK) - l += sizeof (text_super) - 1; - l += strlen (keyval_name); - - accelerator = g_new (gchar, l + 1); - - l = 0; - accelerator[l] = 0; - if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK) - { - strcpy (accelerator + l, text_release); - l += sizeof (text_release) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK) - { - strcpy (accelerator + l, text_shift); - l += sizeof (text_shift) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK) - { - strcpy (accelerator + l, text_control); - l += sizeof (text_control) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_ALT_MASK) - { - strcpy (accelerator + l, text_mod1); - l += sizeof (text_mod1) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK) - { - strcpy (accelerator + l, text_mod2); - l += sizeof (text_mod2) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK) - { - strcpy (accelerator + l, text_mod3); - l += sizeof (text_mod3) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK) - { - strcpy (accelerator + l, text_mod4); - l += sizeof (text_mod4) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK) - { - strcpy (accelerator + l, text_mod5); - l += sizeof (text_mod5) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_META_MASK) - { - strcpy (accelerator + l, text_meta); - l += sizeof (text_meta) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK) - { - strcpy (accelerator + l, text_hyper); - l += sizeof (text_hyper) - 1; - } - if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK) - { - strcpy (accelerator + l, text_super); - l += sizeof (text_super) - 1; + if (keyval == 0) + { + /* If keyval is 0, then maybe it's a keycode. Check for 0x## */ + if (len >= 4 && is_keycode(accelerator)) + { + char keystring[5]; + gchar *endptr; + gint tmp_keycode; + + memcpy(keystring, accelerator, 4); + keystring[4] = '\000'; + + tmp_keycode = strtol(keystring, &endptr, 16); + + if (endptr == NULL || *endptr != '\000') + { + bad_keyval = TRUE; + } + else if (keycode != NULL) + { + *keycode = tmp_keycode; + /* 0x00 is an invalid keycode too. */ + if (*keycode == 0) + bad_keyval = TRUE; + } + } + } + else if (keycode != NULL) + { + GdkDisplay *display = gdk_display_get_default(); + *keycode = XKeysymToKeycode(GDK_DISPLAY_XDISPLAY(display), + keyval); + } + accelerator += len; + len -= len; } + } - strcpy (accelerator + l, keyval_name); + if (accelerator_key) + *accelerator_key = gdk_keyval_to_lower(keyval); + if (accelerator_mods) + *accelerator_mods = mods; - return accelerator; + return !bad_keyval; } -void -egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, - EggVirtualModifierType virtual_mods, - GdkModifierType *concrete_mods) +void egg_keymap_resolve_virtual_modifiers(GdkKeymap *keymap, + EggVirtualModifierType virtual_mods, + GdkModifierType *concrete_mods) { GdkModifierType concrete; int i; const EggModmap *modmap; - g_return_if_fail (GDK_IS_KEYMAP (keymap)); - g_return_if_fail (concrete_mods != NULL); + g_return_if_fail(GDK_IS_KEYMAP(keymap)); + g_return_if_fail(concrete_mods != NULL); - modmap = egg_keymap_get_modmap (keymap); + modmap = egg_keymap_get_modmap(keymap); /* Not so sure about this algorithm. */ - concrete = 0; i = 0; while (i < EGG_MODMAP_ENTRY_LAST) - { - if (modmap->mapping[i] & virtual_mods) - concrete |= (1 << i); + { + if (modmap->mapping[i] & virtual_mods) + concrete |= (1 << i); - ++i; - } + ++i; + } *concrete_mods = concrete; } -void -egg_keymap_virtualize_modifiers (GdkKeymap *keymap, - GdkModifierType concrete_mods, - EggVirtualModifierType *virtual_mods) +void egg_keymap_virtualize_modifiers(GdkKeymap *keymap, + GdkModifierType concrete_mods, + EggVirtualModifierType *virtual_mods) { GdkModifierType virtual; int i; const EggModmap *modmap; - g_return_if_fail (GDK_IS_KEYMAP (keymap)); - g_return_if_fail (virtual_mods != NULL); + g_return_if_fail(GDK_IS_KEYMAP(keymap)); + g_return_if_fail(virtual_mods != NULL); - modmap = egg_keymap_get_modmap (keymap); + modmap = egg_keymap_get_modmap(keymap); /* Not so sure about this algorithm. */ - virtual = 0; i = 0; while (i < EGG_MODMAP_ENTRY_LAST) + { + if ((1 << i) & concrete_mods) { - if ((1 << i) & concrete_mods) - { - EggVirtualModifierType cleaned; - - cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK | - EGG_VIRTUAL_MOD3_MASK | - EGG_VIRTUAL_MOD4_MASK | - EGG_VIRTUAL_MOD5_MASK); + EggVirtualModifierType cleaned; - if (cleaned != 0) - { - virtual |= cleaned; - } - else - { - /* Rather than dropping mod2->mod5 if not bound, - * go ahead and use the concrete names - */ - virtual |= modmap->mapping[i]; - } - } + cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK | + EGG_VIRTUAL_MOD3_MASK | + EGG_VIRTUAL_MOD4_MASK | + EGG_VIRTUAL_MOD5_MASK); - ++i; + if (cleaned != 0) + { + virtual |= cleaned; + } + else + { + /* Rather than dropping mod2->mod5 if not bound, + * go ahead and use the concrete names + */ + virtual |= modmap->mapping[i]; + } } + ++i; + } *virtual_mods = virtual; } static void -reload_modmap (GdkKeymap *keymap, - EggModmap *modmap) +reload_modmap(GdkKeymap *keymap, + EggModmap *modmap) { XModifierKeymap *xmodmap; int map_size; int i; /* FIXME multihead */ - xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ()); + xmodmap = XGetModifierMapping(gdk_x11_get_default_xdisplay()); - memset (modmap->mapping, 0, sizeof (modmap->mapping)); + memset(modmap->mapping, 0, sizeof(modmap->mapping)); /* there are 8 modifiers, and the first 3 are shift, shift lock, * and control @@ -603,59 +761,48 @@ reload_modmap (GdkKeymap *keymap, map_size = 8 * xmodmap->max_keypermod; i = 3 * xmodmap->max_keypermod; while (i < map_size) + { + int keycode = xmodmap->modifiermap[i]; + GdkKeymapKey *keys = NULL; + guint *keyvals = NULL; + int n_entries = 0; + int j; + EggVirtualModifierType mask; + + gdk_keymap_get_entries_for_keycode(keymap, + keycode, + &keys, &keyvals, &n_entries); + + mask = 0; + j = 0; + while (j < n_entries) { - /* get the key code at this point in the map, - * see if its keysym is one we're interested in - */ - int keycode = xmodmap->modifiermap[i]; - GdkKeymapKey *keys; - guint *keyvals; - int n_entries; - int j; - EggVirtualModifierType mask; - - keys = NULL; - keyvals = NULL; - n_entries = 0; - - gdk_keymap_get_entries_for_keycode (keymap, - keycode, - &keys, &keyvals, &n_entries); - - mask = 0; - j = 0; - while (j < n_entries) - { - if (keyvals[j] == GDK_KEY_Num_Lock) - mask |= EGG_VIRTUAL_NUM_LOCK_MASK; - else if (keyvals[j] == GDK_KEY_Scroll_Lock) - mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK; - else if (keyvals[j] == GDK_KEY_Meta_L || - keyvals[j] == GDK_KEY_Meta_R) - mask |= EGG_VIRTUAL_META_MASK; - else if (keyvals[j] == GDK_KEY_Hyper_L || - keyvals[j] == GDK_KEY_Hyper_R) - mask |= EGG_VIRTUAL_HYPER_MASK; - else if (keyvals[j] == GDK_KEY_Super_L || - keyvals[j] == GDK_KEY_Super_R) - mask |= EGG_VIRTUAL_SUPER_MASK; - else if (keyvals[j] == GDK_KEY_Mode_switch) - mask |= EGG_VIRTUAL_MODE_SWITCH_MASK; - - ++j; - } + if (keyvals[j] == GDK_KEY_Num_Lock) + mask |= EGG_VIRTUAL_NUM_LOCK_MASK; + else if (keyvals[j] == GDK_KEY_Scroll_Lock) + mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK; + else if (keyvals[j] == GDK_KEY_Meta_L || + keyvals[j] == GDK_KEY_Meta_R) + mask |= EGG_VIRTUAL_META_MASK; + else if (keyvals[j] == GDK_KEY_Hyper_L || + keyvals[j] == GDK_KEY_Hyper_R) + mask |= EGG_VIRTUAL_HYPER_MASK; + else if (keyvals[j] == GDK_KEY_Super_L || + keyvals[j] == GDK_KEY_Super_R) + mask |= EGG_VIRTUAL_SUPER_MASK; + else if (keyvals[j] == GDK_KEY_Mode_switch) + mask |= EGG_VIRTUAL_MODE_SWITCH_MASK; + + ++j; + } - /* Mod1Mask is 1 << 3 for example, i.e. the - * fourth modifier, i / keyspermod is the modifier - * index - */ - modmap->mapping[i/xmodmap->max_keypermod] |= mask; + modmap->mapping[i / xmodmap->max_keypermod] |= mask; - g_free (keyvals); - g_free (keys); + g_free(keyvals); + g_free(keys); - ++i; - } + ++i; + } /* Add in the not-really-virtual fixed entries */ modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK; @@ -667,38 +814,30 @@ reload_modmap (GdkKeymap *keymap, modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK; modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK; - XFreeModifiermap (xmodmap); + XFreeModifiermap(xmodmap); } -const EggModmap* -egg_keymap_get_modmap (GdkKeymap *keymap) +const EggModmap * +egg_keymap_get_modmap(GdkKeymap *keymap) { EggModmap *modmap; - /* This is all a hack, much simpler when we can just - * modify GDK directly. - */ - - modmap = g_object_get_data (G_OBJECT (keymap), - "egg-modmap"); + modmap = g_object_get_data(G_OBJECT(keymap), + "egg-modmap"); if (modmap == NULL) - { - modmap = g_new0 (EggModmap, 1); - - /* FIXME modify keymap change events with an event filter - * and force a reload if we get one - */ + { + modmap = g_new0(EggModmap, 1); + reload_modmap(keymap, modmap); - reload_modmap (keymap, modmap); - - g_object_set_data_full (G_OBJECT (keymap), - "egg-modmap", - modmap, - g_free); - } + g_object_set_data_full(G_OBJECT(keymap), + "egg-modmap", + modmap, + g_free); + } - g_assert (modmap != NULL); + g_assert(modmap != NULL); return modmap; } +#endif diff --git a/src/sugar3/eggaccelerators.h b/src/sugar3/eggaccelerators.h index 423b274eb..baf9e0cbf 100644 --- a/src/sugar3/eggaccelerators.h +++ b/src/sugar3/eggaccelerators.h @@ -24,6 +24,11 @@ #include #include +#if GTK_CHECK_VERSION(4,0,0) +// In GTK4 the keymap API has been removed; provide a dummy type. +typedef void GdkKeymap; +#endif + G_BEGIN_DECLS /* Where a value is also in GdkModifierType we coincide, @@ -32,15 +37,15 @@ G_BEGIN_DECLS typedef enum { EGG_VIRTUAL_SHIFT_MASK = 1 << 0, - EGG_VIRTUAL_LOCK_MASK = 1 << 1, + EGG_VIRTUAL_LOCK_MASK = 1 << 1, EGG_VIRTUAL_CONTROL_MASK = 1 << 2, EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */ - EGG_VIRTUAL_MOD2_MASK = 1 << 4, - EGG_VIRTUAL_MOD3_MASK = 1 << 5, - EGG_VIRTUAL_MOD4_MASK = 1 << 6, - EGG_VIRTUAL_MOD5_MASK = 1 << 7, + EGG_VIRTUAL_MOD2_MASK = 1 << 4, + EGG_VIRTUAL_MOD3_MASK = 1 << 5, + EGG_VIRTUAL_MOD4_MASK = 1 << 6, + EGG_VIRTUAL_MOD5_MASK = 1 << 7, #if 0 GDK_BUTTON1_MASK = 1 << 8, @@ -70,7 +75,7 @@ typedef enum gboolean egg_accelerator_parse_virtual (const gchar *accelerator, guint *accelerator_key, - guint *keycode, + guint *keycode, EggVirtualModifierType *accelerator_mods); void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, EggVirtualModifierType virtual_mods, @@ -80,10 +85,9 @@ void egg_keymap_virtualize_modifiers (GdkKeymap *keymap, EggVirtualModifierType *virtual_mods); gchar* egg_virtual_accelerator_name (guint accelerator_key, - guint keycode, + guint keycode, EggVirtualModifierType accelerator_mods); G_END_DECLS - #endif /* __EGG_ACCELERATORS_H__ */ diff --git a/src/sugar3/eggdesktopfile.c b/src/sugar3/eggdesktopfile.c index ce1feb194..21aa43e44 100644 --- a/src/sugar3/eggdesktopfile.c +++ b/src/sugar3/eggdesktopfile.c @@ -1028,27 +1028,30 @@ array_putenv (GPtrArray *env, char *variable) static gboolean egg_desktop_file_launchv (EggDesktopFile *desktop_file, - GSList *documents, va_list args, - GError **error) + GSList *documents, va_list args, + GError **error) { EggDesktopFileLaunchOption option; GSList *translated_documents = NULL, *docs; char *command, **argv; - int argc, i, screen_num; + int argc, i, screen_num = 0; gboolean success, current_success; GdkDisplay *display; char *startup_id; - GPtrArray *env = NULL; char **variables = NULL; - GdkScreen *screen = NULL; +#if !GTK_CHECK_VERSION(4,0,0) + GdkScreen *screen = NULL; +#else + /* In GTK4, ignore screen options; declare a dummy variable */ + void *screen = NULL; +#endif int workspace = -1; const char *directory = NULL; guint32 launch_time = (guint32)-1; GSpawnFlags flags = G_SPAWN_SEARCH_PATH; GSpawnChildSetupFunc setup_func = NULL; gpointer setup_data = NULL; - GPid *ret_pid = NULL; int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL; char **ret_startup_id = NULL; @@ -1071,24 +1074,38 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, while ((option = va_arg (args, EggDesktopFileLaunchOption))) { switch (option) - { - case EGG_DESKTOP_FILE_LAUNCH_CLEARENV: - if (env) - g_ptr_array_free (env, TRUE); - env = g_ptr_array_new (); - break; - case EGG_DESKTOP_FILE_LAUNCH_PUTENV: - variables = va_arg (args, char **); - for (i = 0; variables[i]; i++) - env = array_putenv (env, variables[i]); - break; - - case EGG_DESKTOP_FILE_LAUNCH_SCREEN: - screen = va_arg (args, GdkScreen *); - break; - case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: - workspace = va_arg (args, int); - break; + { + case EGG_DESKTOP_FILE_LAUNCH_SCREEN: +#if !GTK_CHECK_VERSION(4,0,0) + screen = va_arg (args, GdkScreen *); +#else + /* Discard screen option in GTK4 */ + (void)va_arg(args, void*); +#endif + break; + case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: +#if !GTK_CHECK_VERSION(4,0,0) + if (screen) + { + char *display_name = gdk_screen_make_display_name (screen); + char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); + env = array_putenv (env, display_env); + g_free (display_name); + g_free (display_env); + display = gdk_screen_get_display (screen); + } + else + { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + screen_num = gdk_screen_get_number (screen); +#else + /* In GTK4, ignore screen settings */ + display = gdk_display_get_default (); + screen_num = 0; +#endif + break; case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: directory = va_arg (args, const char *); @@ -1132,22 +1149,26 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, } } - if (screen) - { - char *display_name = gdk_screen_make_display_name (screen); - char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); - env = array_putenv (env, display_env); - g_free (display_name); - g_free (display_env); - - display = gdk_screen_get_display (screen); - } - else - { + #if !GTK_CHECK_VERSION(4,0,0) + if (screen) + { + char *display_name = gdk_screen_make_display_name (screen); + char *display_env = g_strdup_printf ("DISPLAY=%s", display_name); + env = array_putenv (env, display_env); + g_free (display_name); + g_free (display_env); + display = gdk_screen_get_display (screen); + } + else + { + display = gdk_display_get_default (); + screen = gdk_display_get_default_screen (display); + } + screen_num = gdk_screen_get_number (screen); + #else display = gdk_display_get_default (); - screen = gdk_display_get_default_screen (display); - } - screen_num = gdk_screen_get_number (screen); + screen_num = 0; + #endif translated_documents = translate_document_list (desktop_file, documents); docs = translated_documents; @@ -1389,30 +1410,30 @@ void egg_set_desktop_file (const char *desktop_file_path) { GError *error = NULL; - G_LOCK (egg_desktop_file); if (egg_desktop_file) egg_desktop_file_free (egg_desktop_file); - egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error); if (error) { g_warning ("Could not load desktop file '%s': %s", - desktop_file_path, error->message); + desktop_file_path, error->message); g_error_free (error); } - /* Set localized application name and default window icon */ if (egg_desktop_file->name) g_set_application_name (egg_desktop_file->name); if (egg_desktop_file->icon) { +#if !GTK_CHECK_VERSION(4,0,0) if (g_path_is_absolute (egg_desktop_file->icon)) - gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); + gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); else - gtk_window_set_default_icon_name (egg_desktop_file->icon); + gtk_window_set_default_icon_name (egg_desktop_file->icon); +#else + gtk_window_set_default_icon_name (egg_desktop_file->icon); +#endif } - G_UNLOCK (egg_desktop_file); } diff --git a/src/sugar3/eggsmclient-xsmp.c b/src/sugar3/eggsmclient-xsmp.c index 2fd674290..cde1224c1 100644 --- a/src/sugar3/eggsmclient-xsmp.c +++ b/src/sugar3/eggsmclient-xsmp.c @@ -24,6 +24,7 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include #include "eggsmclient.h" #include "eggsmclient-xsmp.h" @@ -39,7 +40,7 @@ #include #include -#include +#include static const char *state_names[] = { "start", @@ -203,9 +204,13 @@ sm_client_xsmp_connect (gpointer user_data) xsmp->client_id = g_strdup (client_id); free (client_id); - gdk_threads_enter (); - gdk_x11_set_sm_client_id (xsmp->client_id); - gdk_threads_leave (); + #if GTK_CHECK_VERSION(4,0,0) + g_debug("GTK4: gdk_x11_set_sm_client_id is not available; skipping session id setup."); + #else + gdk_threads_enter (); + gdk_x11_set_sm_client_id (xsmp->client_id); + gdk_threads_leave (); + #endif g_debug ("Got client ID \"%s\"", xsmp->client_id); } @@ -473,8 +478,10 @@ idle_do_pending_events (gpointer data) { EggSMClientXSMP *xsmp = data; EggSMClient *client = data; - - gdk_threads_enter (); + + #if !GTK_CHECK_VERSION(4,0,0) + gdk_threads_enter (); + #endif xsmp->idle = 0; @@ -497,9 +504,11 @@ idle_do_pending_events (gpointer data) xsmp->waiting_to_save_myself = FALSE; do_save_yourself (xsmp); } - - out: - gdk_threads_leave (); + + out: + #if !GTK_CHECK_VERSION(4,0,0) + gdk_threads_leave (); + #endif return FALSE; } @@ -1210,9 +1219,13 @@ process_ice_messages (IceConn ice_conn) { IceProcessMessagesStatus status; - gdk_threads_enter (); - status = IceProcessMessages (ice_conn, NULL, NULL); - gdk_threads_leave (); + #if !GTK_CHECK_VERSION(4,0,0) + gdk_threads_enter (); + #endif + status = IceProcessMessages (ice_conn, NULL, NULL); + #if !GTK_CHECK_VERSION(4,0,0) + gdk_threads_leave (); + #endif switch (status) { diff --git a/src/sugar3/event-controller/Makefile.am b/src/sugar3/event-controller/Makefile.am index ef8c04d37..0715797f2 100644 --- a/src/sugar3/event-controller/Makefile.am +++ b/src/sugar3/event-controller/Makefile.am @@ -59,8 +59,8 @@ SugarGestures_1_0_gir_CFLAGS = \ -DSUGAR_TOOLKIT_COMPILATION --warn-all SugarGestures-1.0.gir: libsugar-eventcontroller.la -SugarGestures_1_0_gir_INCLUDES = Gtk-3.0 Gdk-3.0 -SugarGestures_1_0_gir_PACKAGES = gtk+-3.0 gdk-3.0 +SugarGestures_1_0_gir_INCLUDES = Gtk-4.0 +SugarGestures_1_0_gir_PACKAGES = gtk4 SugarGestures_1_0_gir_EXPORT_PACKAGES = SugarGestures-1.0 girdir = $(datadir)/gir-1.0 diff --git a/src/sugar3/event-controller/sugar-event-controller.c b/src/sugar3/event-controller/sugar-event-controller.c index 70e10cdfc..d71e3823c 100644 --- a/src/sugar3/event-controller/sugar-event-controller.c +++ b/src/sugar3/event-controller/sugar-event-controller.c @@ -171,17 +171,15 @@ sugar_event_controller_init (SugarEventController *controller) } static gboolean -_sugar_event_controller_widget_event (GtkWidget *widget, - GdkEvent *event, - gpointer user_data) +_sugar_event_controller_widget_event (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) { SugarControllerWidgetData *data; gboolean handled = FALSE; guint i; - data = g_object_get_qdata (G_OBJECT (widget), - quark_widget_controller_data); - + data = g_object_get_qdata (G_OBJECT (widget), quark_widget_controller_data); if (!data || !data->controllers || data->controllers->len == 0) return FALSE; @@ -196,21 +194,14 @@ _sugar_event_controller_widget_event (GtkWidget *widget, data->current_exclusive != item->controller) continue; - if (event->type == GDK_GRAB_BROKEN && !event->grab_broken.keyboard) - sugar_event_controller_reset (item->controller); - else - { - if (!sugar_event_controller_handle_event (item->controller, event)) - continue; + if (!sugar_event_controller_handle_event (item->controller, event)) + continue; - state = sugar_event_controller_get_state (item->controller); + state = sugar_event_controller_get_state (item->controller); - /* Consider events handled once the - * controller recognizes the action - */ - if (state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) - handled = TRUE; - } + /* Consider events handled once the controller recognizes the action */ + if (state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) + handled = TRUE; } return handled; diff --git a/src/sugar3/event-controller/sugar-long-press-controller.c b/src/sugar3/event-controller/sugar-long-press-controller.c index 0fd10055c..dec0d8028 100644 --- a/src/sugar3/event-controller/sugar-long-press-controller.c +++ b/src/sugar3/event-controller/sugar-long-press-controller.c @@ -192,6 +192,53 @@ sugar_long_press_controller_reset (SugarEventController *controller) g_object_notify (G_OBJECT (controller), "state"); } +static gboolean +_sugar_swipe_controller_store_event (GdkEvent *event) +{ + gdouble x, y; +#if GTK_CHECK_VERSION(4,0,0) + // For GTK4 + if (!gdk_event_get_position(event, &x, &y)) +#else + // For GTK3 + if (!gdk_event_get_coords(event, &x, &y)) +#endif + return FALSE; + + /* store x, y, etc. */ + return TRUE; +} + +static gboolean +sugar_swipe_controller_handle_event (SugarEventController *controller, + GdkEvent *event) +{ + GdkEventType type; +#if GTK_CHECK_VERSION(4,0,0) + type = gdk_event_get_event_type (event); +#else + type = event->type; +#endif + + switch (type) + { + case GDK_TOUCH_BEGIN: + // Handle GDK_TOUCH_BEGIN + break; + case GDK_TOUCH_UPDATE: + // Handle GDK_TOUCH_UPDATE + break; + case GDK_TOUCH_END: + // Handle GDK_TOUCH_END + break; + default: + return FALSE; + } + + return TRUE; +} + + static gboolean sugar_long_press_controller_handle_event (SugarEventController *controller, GdkEvent *event) @@ -200,11 +247,20 @@ sugar_long_press_controller_handle_event (SugarEventController *controller, GdkEventSequence *sequence; gboolean handled = TRUE; GdkDevice *device; + GdkEventType type; priv = SUGAR_LONG_PRESS_CONTROLLER (controller)->priv; device = gdk_event_get_device (event); sequence = gdk_event_get_event_sequence (event); +#if GTK_CHECK_VERSION(4,0,0) + // For GTK4, use accessor + type = gdk_event_get_event_type (event); +#else + // For GTK3, can access event->type (deprecated usage otherwise) + type = event->type; +#endif + if (priv->device) { if (priv->device != device) @@ -212,40 +268,66 @@ sugar_long_press_controller_handle_event (SugarEventController *controller, if (sequence && priv->sequence != sequence) { - /* Another touch is simultaneously operating, - * give up on recognizing a long press. - */ + // Another touch is simultaneously operating; give up on recognizing a long press. _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); - return FALSE; } } - switch (event->type) + switch (type) { case GDK_TOUCH_BEGIN: - priv->device = g_object_ref (device); - priv->start_time = g_get_monotonic_time (); - priv->x = event->touch.x; - priv->y = event->touch.y; - priv->root_x = event->touch.x_root; - priv->root_y = event->touch.y_root; - priv->sequence = sequence; - - priv->timeout_id = - gdk_threads_add_timeout (priv->delay, - _sugar_long_press_controller_timeout, - controller); - g_object_notify (G_OBJECT (controller), "state"); + { +#if GTK_CHECK_VERSION(4,0,0) + gdouble x, y; + gdk_event_get_position (event, &x, &y); + priv->x = x; + priv->y = y; + // GTK4 does not provide separate “root” values; reuse same values + priv->root_x = x; + priv->root_y = y; +#else + priv->x = event->touch.x; + priv->y = event->touch.y; + priv->root_x = event->touch.x_root; + priv->root_y = event->touch.y_root; +#endif + priv->device = g_object_ref (device); + priv->start_time = g_get_monotonic_time (); + priv->sequence = sequence; + +#if GTK_CHECK_VERSION(4,0,0) + priv->timeout_id = g_timeout_add (priv->delay, + _sugar_long_press_controller_timeout, + controller); +#else + priv->timeout_id = gdk_threads_add_timeout (priv->delay, + _sugar_long_press_controller_timeout, + controller); +#endif + g_object_notify (G_OBJECT (controller), "state"); + } break; + case GDK_TOUCH_UPDATE: - if (ABS (priv->x - event->touch.x) > priv->threshold || - ABS (priv->y - event->touch.y) > priv->threshold) - _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); + { +#if GTK_CHECK_VERSION(4,0,0) + gdouble x, y; + gdk_event_get_position (event, &x, &y); +#else + gdouble x = event->touch.x; + gdouble y = event->touch.y; +#endif + if (ABS (priv->x - x) > priv->threshold || + ABS (priv->y - y) > priv->threshold) + _sugar_long_press_controller_cancel (SUGAR_LONG_PRESS_CONTROLLER (controller)); + } break; + case GDK_TOUCH_END: sugar_event_controller_reset (controller); break; + default: handled = FALSE; break; diff --git a/src/sugar3/event-controller/sugar-swipe-controller.c b/src/sugar3/event-controller/sugar-swipe-controller.c index 124b528bd..e51344d74 100644 --- a/src/sugar3/event-controller/sugar-swipe-controller.c +++ b/src/sugar3/event-controller/sugar-swipe-controller.c @@ -128,7 +128,11 @@ _sugar_swipe_controller_store_event (SugarSwipeController *controller, priv = controller->priv; +#if GTK_CHECK_VERSION(4,0,0) + if (!gdk_event_get_position (event, &x, &y)) +#else if (!gdk_event_get_coords (event, &x, &y)) +#endif return; time = gdk_event_get_time (event); @@ -262,7 +266,8 @@ sugar_swipe_controller_handle_event (SugarEventController *controller, GdkEventSequence *sequence; gboolean handled = TRUE; GdkDevice *device; - + GdkEventType type; + device = gdk_event_get_device (event); sequence = gdk_event_get_event_sequence (event); @@ -276,7 +281,13 @@ sugar_swipe_controller_handle_event (SugarEventController *controller, (priv->sequence && priv->sequence != sequence)) return FALSE; - switch (event->type) +#if GTK_CHECK_VERSION(4,0,0) + type = gdk_event_get_event_type (event); +#else + type = event->type; +#endif + + switch (type) { case GDK_TOUCH_BEGIN: priv->device = g_object_ref (device); @@ -285,6 +296,7 @@ sugar_swipe_controller_handle_event (SugarEventController *controller, _sugar_swipe_controller_store_event (swipe, event); g_object_notify (G_OBJECT (controller), "state"); break; + case GDK_TOUCH_END: if (priv->device) g_object_unref (priv->device); @@ -295,6 +307,7 @@ sugar_swipe_controller_handle_event (SugarEventController *controller, _sugar_swipe_controller_clear_events (swipe); g_object_notify (G_OBJECT (controller), "state"); break; + case GDK_TOUCH_UPDATE: _sugar_swipe_controller_store_event (swipe, event); @@ -305,6 +318,7 @@ sugar_swipe_controller_handle_event (SugarEventController *controller, g_object_notify (G_OBJECT (controller), "state"); } break; + default: handled = FALSE; break; diff --git a/src/sugar3/event-controller/sugar-touch-controller.c b/src/sugar3/event-controller/sugar-touch-controller.c index a62b3bcdf..cace1a282 100644 --- a/src/sugar3/event-controller/sugar-touch-controller.c +++ b/src/sugar3/event-controller/sugar-touch-controller.c @@ -20,6 +20,19 @@ */ #include "sugar-touch-controller.h" +#include + +/* + * If not provided by the system, define a local GdkPoint structure. + */ +#ifndef GDK_POINT_DEFINED +typedef struct { + gint x; + gint y; +} GdkPoint; +#define GDK_POINT_DEFINED +#endif + #define TOUCHES_IN_RANGE(t,p) ((t) >= (p)->min_touches && (t) <= (p)->max_touches) typedef struct _SugarTouch SugarTouch; @@ -94,64 +107,86 @@ static gboolean sugar_touch_controller_handle_event (SugarEventController *controller, GdkEvent *event) { - SugarTouchControllerPrivate *priv; - GdkEventSequence *sequence; - gboolean handled = TRUE; - GdkPoint *point; - gint n_touches, prev_n_touches; - gboolean is_in_range, was_in_range; - - priv = SUGAR_TOUCH_CONTROLLER (controller)->priv; - sequence = gdk_event_get_event_sequence (event); - prev_n_touches = g_hash_table_size (priv->touches); - was_in_range = TOUCHES_IN_RANGE (prev_n_touches, priv); - - if (!sequence) - return FALSE; - - switch (event->type) + SugarTouchControllerPrivate *priv; + GdkEventSequence *sequence; + gboolean handled = TRUE; + GdkPoint *point; + gint n_touches, prev_n_touches; + gboolean is_in_range, was_in_range; + GdkEventType type; + gdouble x = 0, y = 0; + + priv = SUGAR_TOUCH_CONTROLLER (controller)->priv; + sequence = gdk_event_get_event_sequence (event); + prev_n_touches = g_hash_table_size (priv->touches); + was_in_range = TOUCHES_IN_RANGE (prev_n_touches, priv); + + if (!sequence) + return FALSE; + + type = gdk_event_get_event_type (event); + switch (type) { case GDK_TOUCH_BEGIN: - point = g_new0 (GdkPoint, 1); - point->x = event->touch.x; - point->y = event->touch.y; - g_hash_table_insert (priv->touches, sequence, point); - break; + { +#if GTK_CHECK_VERSION(4,0,0) + if (!gdk_event_get_position (event, &x, &y)) + return FALSE; +#else + GdkEventTouch *tevent = (GdkEventTouch *) event; + x = tevent->x; + y = tevent->y; +#endif + point = g_new0 (GdkPoint, 1); + point->x = (gint) x; + point->y = (gint) y; + g_hash_table_insert (priv->touches, sequence, point); + break; + } case GDK_TOUCH_END: - g_hash_table_remove (priv->touches, sequence); - break; + g_hash_table_remove (priv->touches, sequence); + break; case GDK_TOUCH_UPDATE: - point = g_hash_table_lookup (priv->touches, sequence); - - if (point) { - point->x = event->touch.x; - point->y = event->touch.y; +#if GTK_CHECK_VERSION(4,0,0) + if (!gdk_event_get_position (event, &x, &y)) + return FALSE; +#else + GdkEventTouch *tevent = (GdkEventTouch *) event; + x = tevent->x; + y = tevent->y; +#endif + point = g_hash_table_lookup (priv->touches, sequence); + if (point) + { + point->x = (gint) x; + point->y = (gint) y; + } + else + handled = FALSE; + break; } - else - handled = FALSE; - break; default: - handled = FALSE; + handled = FALSE; } - n_touches = g_hash_table_size (priv->touches); - is_in_range = TOUCHES_IN_RANGE (n_touches, priv); + n_touches = g_hash_table_size (priv->touches); + is_in_range = TOUCHES_IN_RANGE (n_touches, priv); - if (handled) + if (handled) { - if (is_in_range) + if (is_in_range) { - if (!was_in_range) - g_signal_emit_by_name (controller, "began"); - else - g_signal_emit_by_name (controller, "updated"); + if (!was_in_range) + g_signal_emit_by_name (controller, "began"); + else + g_signal_emit_by_name (controller, "updated"); } - else if (was_in_range) - g_signal_emit_by_name (controller, "ended"); + else if (was_in_range) + g_signal_emit_by_name (controller, "ended"); } - return handled; + return handled; } static void @@ -316,7 +351,7 @@ sugar_touch_controller_get_sequences (SugarTouchController *controller) * @controller: a #SugarTouchController * @sequence: a #GdkEventSequence * @x: (out) (transfer none): Return location for the X coordinate of the touch - * @y: (out) (transfer none): Return location for the X coordinate of the touch + * @y: (out) (transfer none): Return location for the Y coordinate of the touch * * If @sequence is operating on @controller, this function returns %TRUE and * fills in @x and @y with the latest coordinates for that @sequence. diff --git a/src/sugar3/graphics/alert.py b/src/sugar3/graphics/alert.py index f05f00fcf..95cba6d68 100644 --- a/src/sugar3/graphics/alert.py +++ b/src/sugar3/graphics/alert.py @@ -51,7 +51,9 @@ # Boston, MA 02111-1307, USA. import gettext +import gi +gi.require_version('Gtk', '4.0') from gi.repository import Gtk from gi.repository import GObject from gi.repository import GLib @@ -70,7 +72,7 @@ def _(msg): GObject.ParamFlags.READABLE -class Alert(Gtk.EventBox): +class Alert(Gtk.Box): """ Alerts are inside the activity window instead of being a separate popup window. They do not hide the canvas. @@ -107,37 +109,42 @@ def __init__(self, **kwargs): self._icon = None self._buttons = {} - self._hbox = Gtk.HBox() - self._hbox.set_border_width(style.DEFAULT_SPACING) + self._hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self._hbox.set_margin_start(style.DEFAULT_SPACING) + self._hbox.set_margin_end(style.DEFAULT_SPACING) + self._hbox.set_margin_top(style.DEFAULT_SPACING) + self._hbox.set_margin_bottom(style.DEFAULT_SPACING) self._hbox.set_spacing(style.DEFAULT_SPACING) - self._msg_box = Gtk.VBox() + self._msg_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self._title_label = Gtk.Label() - self._title_label.set_alignment(0, 0.5) + self._title_label.set_halign(Gtk.Align.START) + self._title_label.set_valign(Gtk.Align.CENTER) self._title_label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT) - self._msg_box.pack_start(self._title_label, False, False, 0) + self._msg_box.append(self._title_label) self._msg_label = Gtk.Label() - self._msg_label.set_alignment(0, 0.5) + self._msg_label.set_halign(Gtk.Align.START) + self._msg_label.set_valign(Gtk.Align.CENTER) self._msg_label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT) - self._msg_box.pack_start(self._msg_label, False, False, 0) - self._hbox.pack_start(self._msg_box, False, False, 0) + self._msg_box.append(self._msg_label) + self._hbox.append(self._msg_box) - self._buttons_box = Gtk.HButtonBox() - self._buttons_box.set_layout(Gtk.ButtonBoxStyle.END) + self._buttons_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self._buttons_box.set_halign(Gtk.Align.END) self._buttons_box.set_spacing(style.DEFAULT_SPACING) - self._hbox.pack_end(self._buttons_box, True, True, 0) + self._hbox.append(self._buttons_box) GObject.GObject.__init__(self, **kwargs) - self.set_visible_window(True) - self.add(self._hbox) - self._title_label.show() - self._msg_label.show() - self._buttons_box.show() - self._msg_box.show() - self._hbox.show() - self.show() + self.set_visible(True) + self.append(self._hbox) + self._title_label.set_visible(True) + self._msg_label.set_visible(True) + self._buttons_box.set_visible(True) + self._msg_box.set_visible(True) + self._hbox.set_visible(True) + self.set_visible(True) def do_set_property(self, pspec, value): """ @@ -154,11 +161,11 @@ def do_set_property(self, pspec, value): if self._msg != value: self._msg = value self._msg_label.set_markup(self._msg) - self._msg_label.set_line_wrap(True) + self._msg_label.set_wrap(True) elif pspec.name == 'icon': if self._icon != value: self._icon = value - self._hbox.pack_start(self._icon, False, False, 0) + self._hbox.append(self._icon) self._hbox.reorder_child(self._icon, 0) def do_get_property(self, pspec): @@ -187,8 +194,8 @@ def add_entry(self): :class:`Gtk.Entry`: the entry added to the alert """ entry = Gtk.Entry() - self._hbox.pack_start(entry, True, True, 0) - entry.show() + self._hbox.append(entry) + entry.set_visible(True) self._hbox.set_child_packing(self._buttons_box, False, False, 0, Gtk.PackType.END) @@ -222,10 +229,10 @@ def add_button(self, response_id, label, icon=None, position=-1): button = Gtk.Button() self._buttons[response_id] = button if icon is not None: - button.set_image(icon) + button.set_child(icon) button.set_label(label) - self._buttons_box.pack_start(button, True, True, 0) - button.show() + self._buttons_box.append(button) + button.set_visible(True) button.connect('clicked', self.__button_clicked_cb, response_id) if position != -1: self._buttons_box.reorder_child(button, position) @@ -307,11 +314,11 @@ def __init__(self, **kwargs): icon = Icon(icon_name='dialog-cancel') self.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon) - icon.show() + icon.set_visible(True) icon = Icon(icon_name='dialog-ok') self.add_button(Gtk.ResponseType.OK, _('Ok'), icon) - icon.show() + icon.set_visible(True) class ErrorAlert(Alert): @@ -352,19 +359,24 @@ def __init__(self, **kwargs): icon = Icon(icon_name='dialog-ok') self.add_button(Gtk.ResponseType.OK, _('Ok'), icon) - icon.show() + icon.set_visible(True) -class _TimeoutIcon(Gtk.Alignment): +class _TimeoutIcon(Gtk.Box): __gtype_name__ = 'SugarTimeoutIcon' def __init__(self): - Gtk.Alignment.__init__(self, xalign=0, yalign=0, xscale=1, yscale=1) + super().__init__() + self.set_orientation(Gtk.Orientation.HORIZONTAL) + self.set_halign(Gtk.Align.CENTER) + self.set_valign(Gtk.Align.CENTER) self.set_app_paintable(True) + self._text = Gtk.Label() - self._text.set_alignment(0.5, 0.5) - self.add(self._text) - self._text.show() + self._text.set_halign(Gtk.Align.CENTER) + self._text.set_valign(Gtk.Align.CENTER) + self.append(self._text) + self.connect('draw', self.__draw_cb) def __draw_cb(self, widget, context): @@ -408,7 +420,7 @@ def __init__(self, timeout=5, label=_('Ok'), **kwargs): self._timeout_text = _TimeoutIcon() self._timeout_text.set_text(self._timeout) self.add_button(Gtk.ResponseType.OK, label, self._timeout_text) - self._timeout_text.show() + self._timeout_text.set_visible(True) self._timeout_sid = GLib.timeout_add(1000, self.__timeout_cb) @@ -475,7 +487,7 @@ def __init__(self, timeout=5, **kwargs): icon = Icon(icon_name='dialog-cancel') self.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon) - icon.show() + icon.set_visible(True) class NotifyAlert(_TimeoutAlert): diff --git a/src/sugar3/graphics/icon.py b/src/sugar3/graphics/icon.py index 342e802ab..598c64075 100644 --- a/src/sugar3/graphics/icon.py +++ b/src/sugar3/graphics/icon.py @@ -97,7 +97,7 @@ import gi gi.require_version('Rsvg', '2.0') -gi.require_version('Gtk', '3.0') +gi.require_version('Gtk', '4.0') from gi.repository import GLib from gi.repository import GObject from gi.repository import Gtk @@ -214,7 +214,7 @@ def _get_attach_points(self, info, size_request): try: with open(icon_filename) as config_file: cp = ConfigParser() - cp.readfp(config_file) + cp.read_file(config_file) attach_points_str = cp.get('Icon Data', 'AttachPoints') attach_points = attach_points_str.split(',') attach_x = float(attach_points[0].strip()) / 1000 @@ -434,6 +434,7 @@ def get_surface(self, sensitive=True, widget=None): xo_color = property(_get_xo_color, _set_xo_color) + class Icon(Gtk.Image): ''' The most basic Sugar icon class. Displays the icon given. @@ -472,9 +473,6 @@ class Icon(Gtk.Image): __gtype_name__ = 'SugarIcon' - _MENU_SIZES = (Gtk.IconSize.MENU, Gtk.IconSize.DND, - Gtk.IconSize.SMALL_TOOLBAR, Gtk.IconSize.BUTTON) - def __init__(self, **kwargs): self._buffer = _IconBuffer() # HACK: need to keep a reference to the path so it doesn't get garbage @@ -534,10 +532,7 @@ def _sync_image_properties(self): pixel_size = None if self.props.pixel_size == -1: - if self.props.icon_size in self._MENU_SIZES: - pixel_size = style.SMALL_ICON_SIZE - else: - pixel_size = style.STANDARD_ICON_SIZE + pixel_size = style.STANDARD_ICON_SIZE else: pixel_size = self.props.pixel_size @@ -759,7 +754,7 @@ def set_scale(self, value): ''' -class EventIcon(Gtk.EventBox): +class EventIcon(Gtk.Box): ''' An Icon class that provides access to mouse events and that can act as a cursor-positioned palette invoker. @@ -794,7 +789,7 @@ def __init__(self, **kwargs): self._buffer = _IconBuffer() self._alpha = 1.0 - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) self.set_visible_window(False) self.set_above_child(True) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | diff --git a/src/sugar3/graphics/menuitem.py b/src/sugar3/graphics/menuitem.py index c6c3324e0..22e0b7bcf 100644 --- a/src/sugar3/graphics/menuitem.py +++ b/src/sugar3/graphics/menuitem.py @@ -36,7 +36,7 @@ def __init__(self, text_label=None, icon_name=None, GObject.GObject.__init__(self) self._accelerator = None - label = Gtk.AccelLabel(label=text_label) + label = Gtk.Label(label=text_label) label.set_alignment(0.0, 0.5) label.set_accel_widget(self) if text_maxlen > 0: diff --git a/src/sugar3/graphics/notebook.py b/src/sugar3/graphics/notebook.py index 088733b31..40d32a3e8 100644 --- a/src/sugar3/graphics/notebook.py +++ b/src/sugar3/graphics/notebook.py @@ -59,7 +59,7 @@ def __init__(self, **kwargs): GObject.GObject.__init__(self, **kwargs) self.set_scrollable(True) - self.show() + self.set_visible(True) def do_set_property(self, pspec, value): ''' @@ -71,24 +71,21 @@ def do_set_property(self, pspec, value): raise AssertionError def _add_icon_to_button(self, button): - icon_box = Gtk.HBox() - image = Gtk.Image() - image.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU) + icon_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + image = Gtk.Image.new_from_icon_name("window-close", Gtk.IconSize.MENU) Gtk.Button.set_relief(button, Gtk.ReliefStyle.NONE) settings = Gtk.Widget.get_settings(button) valid_, w, h = Gtk.icon_size_lookup_for_settings(settings, Gtk.IconSize.MENU) Gtk.Widget.set_size_request(button, w + 4, h + 4) - image.show() - icon_box.pack_start(image, True, False, 0) - button.add(icon_box) - icon_box.show() + image.set_visible(True) + icon_box.append(image) + button.set_child(icon_box) + icon_box.set_visible(True) def _create_custom_tab(self, text, child): - event_box = Gtk.EventBox() - - tab_box = Gtk.HBox(False, 2) + tab_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) tab_label = Gtk.Label(label=text) tab_button = Gtk.Button() @@ -97,17 +94,15 @@ def _create_custom_tab(self, text, child): # Add a picture on a button self._add_icon_to_button(tab_button) - event_box.show() - tab_button.show() - tab_label.show() + tab_label.set_visible(True) + tab_button.set_visible(True) - tab_box.pack_start(tab_label, True, False, 0) - tab_box.pack_start(tab_button, True, False, 0) + tab_box.append(tab_label) + tab_box.append(tab_button) - tab_box.show_all() - event_box.add(tab_box) + tab_box.set_visible(True) - return event_box + return tab_box def add_page(self, text_label, widget): ''' @@ -123,8 +118,8 @@ def add_page(self, text_label, widget): ''' # Add a new page to the notebook if self._can_close_tabs: - eventbox = self._create_custom_tab(text_label, widget) - self.append_page(widget, eventbox) + tab_box = self._create_custom_tab(text_label, widget) + self.append_page(widget, tab_box) else: self.append_page(widget, Gtk.Label(label=text_label)) @@ -132,7 +127,7 @@ def add_page(self, text_label, widget): # Set the new page self.set_current_page(pages - 1) - self.show_all() + self.set_visible(True) return True diff --git a/src/sugar3/graphics/palette.py b/src/sugar3/graphics/palette.py index 85c0de7c4..737c9ef06 100644 --- a/src/sugar3/graphics/palette.py +++ b/src/sugar3/graphics/palette.py @@ -47,18 +47,16 @@ assert TreeViewInvoker -class _HeaderItem(Gtk.MenuItem): - """A MenuItem with a custom child widget that gets all the - available space. +class _HeaderItem(Gtk.ListBoxRow): + """A ListBoxRow with a custom child widget that gets all the + available space. """ __gtype_name__ = 'SugarPaletteHeader' def __init__(self, widget): - Gtk.MenuItem.__init__(self) - if self.get_child() is not None: - self.remove(self.get_child()) + super().__init__() self.add(widget) # FIXME we have to mark it as insensitive again to make it an # informational element, when we realize how to get the icon @@ -71,7 +69,7 @@ def do_size_allocate(self, allocation): def do_draw(self, cr): # force state normal, we don't want change color when hover - self.set_state(Gtk.StateType.NORMAL) + self.set_state_flags(Gtk.StateFlags.NORMAL, clear=True) # draw separator allocation = self.get_allocation() @@ -85,10 +83,11 @@ def do_draw(self, cr): cr.restore() # draw content - Gtk.MenuItem.do_draw(self, cr) + super().do_draw(cr) return False + class Palette(PaletteWindow): """Floating palette implementation. @@ -123,47 +122,51 @@ def __init__(self, label=None, accel_path=None, self._icon = None self._icon_visible = True - self._primary_event_box = Gtk.EventBox() - self._primary_event_box.show() - self._primary_box = Gtk.HBox() - self._primary_event_box.add(self._primary_box) - self._primary_box.show() + self._primary_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self._primary_box.set_visible(True) - self._icon_box = Gtk.HBox() + self._icon_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self._icon_box.set_size_request(style.GRID_CELL_SIZE, -1) - self._primary_box.pack_start(self._icon_box, False, True, 0) - - labels_box = Gtk.VBox() - self._label_alignment = Gtk.Alignment(xalign=0, yalign=0.5, xscale=1, - yscale=0.33) - self._label_alignment.set_padding( - style.DEFAULT_SPACING, style.DEFAULT_SPACING, - style.DEFAULT_SPACING, style.DEFAULT_SPACING) - self._label_alignment.add(labels_box) - self._label_alignment.show() - self._primary_box.pack_start(self._label_alignment, True, True, 0) - labels_box.show() - - self._label = Gtk.AccelLabel(label='') - self._label.set_alignment(0, 0.5) + self._primary_box.append(self._icon_box) + + labels_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self._label_alignment = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self._label_alignment.set_margin_start(style.DEFAULT_SPACING) + self._label_alignment.set_margin_end(style.DEFAULT_SPACING) + self._label_alignment.set_margin_top(style.DEFAULT_SPACING) + self._label_alignment.set_margin_bottom(style.DEFAULT_SPACING) + self._label_alignment.append(labels_box) + self._label_alignment.set_visible(True) + self._primary_box.append(self._label_alignment) + labels_box.set_visible(True) + + self._label = Gtk.Label(label='') + self._label.set_xalign(0) + self._label.set_yalign(0.5) if text_maxlen > 0: self._label.set_max_width_chars(text_maxlen) self._label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT) - labels_box.pack_start(self._label, True, True, 0) - self._primary_event_box.connect('button-release-event', - self.__button_release_event_cb) - self._primary_event_box.set_events(Gdk.EventMask.BUTTON_RELEASE_MASK) + labels_box.append(self._label) + + # self._primary_box.connect('button-release-event', + # self.__button_release_event_cb) + # self._primary_box.set_events(Gdk.EventMask.BUTTON_RELEASE_MASK) + + self._click_controller = Gtk.GestureClick.new() + self._click_controller.connect('released', self.__button_release_event_cb) + self._primary_box.add_controller(self._click_controller) self._secondary_label = Gtk.Label() - self._secondary_label.set_alignment(0, 0.5) - labels_box.pack_start(self._secondary_label, True, True, 0) + self._secondary_label.set_xalign(0) + self._secondary_label.set_yalign(0.5) + labels_box.append(self._secondary_label) - self._secondary_box = Gtk.VBox() + self._secondary_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self._separator = Gtk.HSeparator() - self._secondary_box.pack_start(self._separator, True, True, 0) - self._secondary_box.show() + self._separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + self._secondary_box.append(self._separator) + self._secondary_box.set_visible(True) # we init after initializing all of our containers PaletteWindow.__init__(self, **kwargs) @@ -178,8 +181,8 @@ def __init__(self, label=None, accel_path=None, self._add_content() self.action_bar = PaletteActionBar() - self._secondary_box.pack_start(self.action_bar, True, True, 0) - self.action_bar.show() + self._secondary_box.append(self.action_bar) + self.action_bar.set_visible(True) self.connect('notify::invoker', self.__notify_invoker_cb) @@ -245,20 +248,21 @@ def on_enter(self): def _add_content(self): # The content is not shown until a widget is added - self._content = Gtk.VBox() - self._secondary_box.pack_start(self._content, True, True, - style.DEFAULT_SPACING) + self._content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self._secondary_box.append(self._content) + self._content.set_margin_top(style.DEFAULT_SPACING) + self._content.set_visible(True) def _update_accel_widget(self): - assert self.props.invoker is not None - self._label.props.accel_widget = self.props.invoker.props.widget + if hasattr(self._label.props, 'accel_widget'): + self._label.props.accel_widget = self.props.invoker.props.widget def set_primary_text(self, label, accel_path=None): self._primary_text = label if label is not None: label = GLib.markup_escape_text(label) self._label.set_markup('%s' % label) - self._label.show() + self._label.set_visible(True) def get_primary_text(self): return self._primary_text @@ -270,10 +274,11 @@ def get_primary_text(self): def __button_release_event_cb(self, widget, event): if self.props.invoker is not None: self.props.invoker.primary_text_clicked() + return False def set_secondary_text(self, label): if label is None: - self._secondary_label.hide() + self._secondary_label.set_visible(False) else: NO_OF_LINES = 3 ELLIPSIS_LENGTH = 6 @@ -301,7 +306,7 @@ def set_secondary_text(self, label): self._secondary_text = label self._secondary_label.set_text(label) - self._secondary_label.show() + self._secondary_label.set_visible(True) def get_secondary_text(self): return self._secondary_text @@ -310,16 +315,12 @@ def get_secondary_text(self): setter=set_secondary_text) def _show_icon(self): - self._label_alignment.set_padding( - style.DEFAULT_SPACING, style.DEFAULT_SPACING, - 0, style.DEFAULT_SPACING) - self._icon_box.show() + self._label_alignment.set_margin_start(0) + self._icon_box.set_visible(True) def _hide_icon(self): - self._icon_box.hide() - self._label_alignment.set_padding( - style.DEFAULT_SPACING, style.DEFAULT_SPACING, - style.DEFAULT_SPACING, style.DEFAULT_SPACING) + self._icon_box.set_visible(False) + self._label_alignment.set_margin_start(style.DEFAULT_SPACING) def set_icon(self, icon): if icon is None: @@ -329,16 +330,16 @@ def set_icon(self, icon): if self._icon: self._icon_box.remove(self._icon_box.get_children()[0]) - event_box = Gtk.EventBox() + event_box = Gtk.Box() event_box.connect('button-release-event', self.__icon_button_release_event_cb) - self._icon_box.pack_start(event_box, True, True, 0) - event_box.show() + self._icon_box.append(event_box) + event_box.set_visible(True) self._icon = icon self._icon.props.pixel_size = style.STANDARD_ICON_SIZE - event_box.add(self._icon) - self._icon.show() + event_box.append(self._icon) + self._icon.set_visible(True) self._show_icon() def get_icon(self): @@ -358,7 +359,7 @@ def set_icon_visible(self, visible): self._hide_icon() def get_icon_visible(self): - return self._icon_visilbe + return self._icon_visible icon_visible = GObject.Property(type=bool, default=True, @@ -373,26 +374,27 @@ def set_content(self, widget): self._widget = _PaletteWindowWidget(self) self._setup_widget() - self._palette_box = Gtk.VBox() - self._palette_box.pack_start(self._primary_event_box, False, True, - 0) - self._palette_box.pack_start(self._secondary_box, True, True, 0) + self._palette_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self._palette_box.append(self._primary_box) + self._palette_box.append(self._secondary_box) - self._widget.add(self._palette_box) - self._palette_box.show() - height = style.GRID_CELL_SIZE - 2 * self._widget.get_border_width() - self._primary_event_box.set_size_request(-1, height) + self._widget.set_child(self._palette_box) + self._palette_box.set_visible(True) + border_width = max(self._widget.get_margin_top(), self._widget.get_margin_bottom()) + height = style.GRID_CELL_SIZE - 2 * border_width + self._primary_box.set_size_request(-1, height) - if self._content.get_children(): - self._content.remove(self._content.get_children()[0]) + if self._content.get_first_child(): + self._content.remove_child(self._content.get_first_child()[0]) if widget is not None: - widget.connect('button-release-event', - self.__widget_button_release_cb) - self._content.add(widget) - self._content.show() + gesture = Gtk.GestureClick.new() + gesture.connect("released", self.__widget_button_release_cb) + widget.add_controller(gesture) + self._content.append(widget) + self._content.set_visible(True) else: - self._content.hide() + self._content.set_visible(False) self._content_widget = widget @@ -412,16 +414,16 @@ def get_label_width(self): return label_width def _update_separators(self): - visible = self._content.get_children() + visible = self._content.get_first_child() is not None self._separator.props.visible = visible def _update_accept_focus(self): - accept_focus = len(self._content.get_children()) + accept_focus = self._content.get_first_child() is not None self._widget.set_accept_focus(accept_focus) def _update_full_request(self): if self._widget is not None: - self._full_request = self._widget.size_request() + self._full_request = self._widget.get_preferred_size() def get_menu(self): assert self._content_widget is None @@ -429,15 +431,15 @@ def get_menu(self): if self._widget is None \ or not isinstance(self._widget, _PaletteMenuWidget): if self._widget is not None: - self._palette_box.remove(self._primary_event_box) + self._palette_box.remove(self._primary_box) self._palette_box.remove(self._secondary_box) self._teardown_widget() self._widget.destroy() self._widget = _PaletteMenuWidget() - self._label_menuitem = _HeaderItem(self._primary_event_box) - self._label_menuitem.show() + self._label_menuitem = _HeaderItem(self._primary_box) + self._label_menuitem.set_visible(True) self._widget.append(self._label_menuitem) self._setup_widget() @@ -450,15 +452,18 @@ def _invoker_right_click_cb(self, invoker): self.popup(immediate=True) -class PaletteActionBar(Gtk.HButtonBox): +class PaletteActionBar(Gtk.Box): + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.HORIZONTAL) def add_action(self, label, icon_name=None): button = Gtk.Button(label) if icon_name: icon = Icon(icon_name) - button.set_image(icon) - icon.show() + button.set_child(icon) + icon.set_visible(True) - self.pack_start(button, True, True, 0) - button.show() + self.append(button) + button.set_visible(True) diff --git a/src/sugar3/graphics/palettemenu.py b/src/sugar3/graphics/palettemenu.py index ddc871164..6a16bbfaf 100644 --- a/src/sugar3/graphics/palettemenu.py +++ b/src/sugar3/graphics/palettemenu.py @@ -15,11 +15,11 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ''' The palettemenu module is the main port of call for making palettes. It -covers creating menu items, seperators and placing them in a box. +covers creating menu items, separators and placing them in a box. Example: - Create a palette menu with 2 items with a seperator in the middle. + Create a palette menu with 2 items with a separator in the middle. .. code-block:: python @@ -38,22 +38,22 @@ def __init__(self): self, primary_text='List Item') box = PaletteMenuBox() self.set_content(box) - box.show() + box.set_visible(True) menu_item = PaletteMenuItem( _('Edit'), icon_name='toolbar-edit') menu_item.connect('activate', self.__edit_cb) box.append_item(menu_item) - menu_item.show() + menu_item.set_visible(True) sep = PaletteMenuItemSeparator() box.append_item(sep) - sep.show() + sep.set_visible(True) menu_item = PaletteMenuItem( _('Delete'), icon_name='edit-delete') box.append_item(menu_item) - menu_item.show() + menu_item.set_visible(True) def __edit_cb(self, menu_item): print('Edit...') @@ -75,37 +75,40 @@ def __edit_cb(self, menu_item): palette = image.get_palette() box = PaletteMenuBox() palette.set_content(box) - box.show() + box.set_visible(True) menu_item = PaletteMenuItem(_('Floating')) menu_item.connect('activate', self.__image_cb, True) box.append_item(menu_item) - menu_item.show() + menu_item.set_visible(True) ''' from gi.repository import GObject from gi.repository import Gtk +from gi.repository import Gdk from sugar3.graphics.icon import Icon from sugar3.graphics import style - -class PaletteMenuBox(Gtk.VBox): +class PaletteMenuBox(Gtk.Box): ''' The PaletteMenuBox is a box that is useful for making palettes. It supports adding :class:`sugar3.graphics.palettemenu.PaletteMenuItem`, :class:`sugar3.graphics.palettemenu.PaletteMenuItemSeparator` and it automatically adds padding to other widgets. ''' + __gsignals__ = { + "button-release-event": (GObject.SignalFlags.RUN_LAST, None, (Gdk.Event,)), + } def __init__(self): - Gtk.VBox.__init__(self) + super().__init__(orientation=Gtk.Orientation.VERTICAL) def append_item(self, item_or_widget, horizontal_padding=None, vertical_padding=None): ''' - Add a menu item, seperator or other widget to the end of the palette - (simmilar to `Gtk.Box.pack_start`). + Add a menu item, separator or other widget to the end of the palette + (similar to `Gtk.Box.pack_start`). If an item is appended (a :class:`sugar3.graphics.palettemenu.PaletteMenuItem` or a @@ -114,8 +117,8 @@ def append_item(self, item_or_widget, horizontal_padding=None, appended (:class:`Gtk.Widget` subclass) padding will be added. Args: - item_or_widget (:class:`Gtk.Widget` or menu item or seperator): - item or widget to add the the palette + item_or_widget (:class:`Gtk.Widget` or menu item or separator): + item or widget to add the palette horizontal_padding (int): by default, :class:`sugar3.graphics.style.DEFAULT_SPACING` is applied vertical_padding (int): by default, @@ -124,50 +127,52 @@ def append_item(self, item_or_widget, horizontal_padding=None, Returns: None ''' - item = None if (isinstance(item_or_widget, PaletteMenuItem) or isinstance(item_or_widget, PaletteMenuItemSeparator)): - item = item_or_widget + self.append(item_or_widget) else: item = self._wrap_widget(item_or_widget, horizontal_padding, vertical_padding) - - self.pack_start(item, False, False, 0) + self.append(item) def _wrap_widget(self, widget, horizontal_padding, vertical_padding): - vbox = Gtk.VBox() - vbox.show() + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + vbox.set_visible(True) if horizontal_padding is None: horizontal_padding = style.DEFAULT_SPACING - if vertical_padding is None: vertical_padding = style.DEFAULT_SPACING - hbox = Gtk.HBox() - vbox.pack_start(hbox, True, True, vertical_padding) - hbox.show() - - hbox.pack_start(widget, True, True, horizontal_padding) + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + hbox.set_visible(True) + + widget.set_margin_start(horizontal_padding) + widget.set_margin_end(horizontal_padding) + hbox.set_margin_top(vertical_padding) + hbox.set_margin_bottom(vertical_padding) + + hbox.append(widget) + vbox.append(hbox) + return vbox -class PaletteMenuItemSeparator(Gtk.EventBox): +class PaletteMenuItemSeparator(Gtk.Separator): ''' - Horizontal seperator to put in a palette + Horizontal separator to put in a palette ''' __gtype_name__ = 'SugarPaletteMenuItemSeparator' def __init__(self): - Gtk.EventBox.__init__(self) - separator = Gtk.HSeparator() - self.add(separator) - separator.show() - self.set_size_request(-1, style.DEFAULT_SPACING * 2) + super().__init__(orientation=Gtk.Orientation.HORIZONTAL) + self.set_margin_top(style.DEFAULT_SPACING) + self.set_margin_bottom(style.DEFAULT_SPACING) + self.set_visible(True) -class PaletteMenuItem(Gtk.EventBox): +class PaletteMenuItem(Gtk.Box): ''' A palette menu item is a line of text, and optionally an icon, that the user can activate. @@ -178,7 +183,7 @@ class PaletteMenuItem(Gtk.EventBox): Args: text_label (str): a text to display in the menu - icon_name (str): the name of a sugar icon to be displayed. Takse + icon_name (str): the name of a sugar icon to be displayed. Takes precedence over file_name text_maxlen (int): the desired maximum width of the label, in @@ -201,51 +206,47 @@ class PaletteMenuItem(Gtk.EventBox): def __init__(self, text_label=None, icon_name=None, text_maxlen=60, xo_color=None, file_name=None, accelerator=None): - Gtk.EventBox.__init__(self) + super().__init__(orientation=Gtk.Orientation.HORIZONTAL) self.set_above_child(True) self.icon = None - self._hbox = Gtk.HBox() + self._hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - vbox = Gtk.VBox() - self.add(vbox) - vbox.show() + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.append(vbox) + vbox.set_visible(True) - hbox = Gtk.HBox() - vbox.pack_start(hbox, True, True, style.DEFAULT_PADDING) - hbox.show() + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + vbox.append(hbox) + hbox.set_margin_top(style.DEFAULT_PADDING) + hbox.set_margin_bottom(style.DEFAULT_PADDING) + hbox.set_visible(True) - hbox.pack_start(self._hbox, True, True, style.DEFAULT_PADDING) + hbox.append(self._hbox) if icon_name is not None: self.icon = Icon(icon_name=icon_name, pixel_size=style.SMALL_ICON_SIZE) if xo_color is not None: self.icon.props.xo_color = xo_color - self._hbox.pack_start(self.icon, expand=False, fill=False, - padding=style.DEFAULT_PADDING) + self._hbox.append(self.icon) elif file_name is not None: self.icon = Icon(file=file_name, pixel_size=style.SMALL_ICON_SIZE) if xo_color is not None: self.icon.props.xo_color = xo_color - self._hbox.pack_start(self.icon, expand=False, fill=False, - padding=style.DEFAULT_PADDING) + self._hbox.append(self.icon) - align = Gtk.Alignment(xalign=0.0, yalign=0.5, xscale=0.0, yscale=0.0) - self.label = Gtk.Label(text_label) + self.label = Gtk.Label(label=text_label) if text_maxlen > 0: self.label.set_max_width_chars(text_maxlen) self.label.set_ellipsize(style.ELLIPSIZE_MODE_DEFAULT) - align.add(self.label) - self._hbox.pack_start(align, expand=True, fill=True, - padding=style.DEFAULT_PADDING) + self._hbox.append(self.label) - self._accelerator_label = Gtk.AccelLabel('') + self._accelerator_label = Gtk.Label(label='') if accelerator is not None: self._accelerator_label.set_text(accelerator) - self._hbox.pack_start(self._accelerator_label, expand=False, - fill=False, padding=style.DEFAULT_PADDING) + self._hbox.append(self._accelerator_label) self.id_bt_release_cb = self.connect('button-release-event', self.__button_release_cb) @@ -254,7 +255,7 @@ def __init__(self, text_label=None, icon_name=None, text_maxlen=60, self.id_leave_notify_cb = self.connect('leave-notify-event', self.__leave_notify_cb) - self.show_all() + self.set_visible(True) def __button_release_cb(self, widget, event): alloc = self.get_allocation() @@ -262,12 +263,10 @@ def __button_release_cb(self, widget, event): self.emit('activate') def __enter_notify_cb(self, widget, event): - self.modify_bg(Gtk.StateType.NORMAL, - style.COLOR_BUTTON_GREY.get_gdk_color()) + self.set_state_flags(Gtk.StateFlags.PRELIGHT, clear=False) def __leave_notify_cb(self, widget, event): - self.modify_bg(Gtk.StateType.NORMAL, - style.COLOR_BLACK.get_gdk_color()) + self.unset_state_flags(Gtk.StateFlags.PRELIGHT) def set_label(self, text_label): ''' @@ -288,8 +287,7 @@ def set_image(self, icon): Args: icon (:class:`Gtk.Widget`): icon widget ''' - self._hbox.pack_start(icon, expand=False, fill=False, - padding=style.DEFAULT_PADDING) + self._hbox.append(icon) self._hbox.reorder_child(icon, 0) def set_accelerator(self, text): @@ -304,12 +302,12 @@ def set_accelerator(self, text): def set_sensitive(self, sensitive): ''' - Sets whether the widget should be activateable by the user and changes - the widget's appearence to the appropriate state. + Sets whether the widget should be activatable by the user and changes + the widget's appearance to the appropriate state. Args: - sensitive (bool): if `True`, the widget will be activateable by - the user. Otherwise, it will not be activateable + sensitive (bool): if `True`, the widget will be activatable by + the user. Otherwise, it will not be activatable ''' is_sensitive = bool(not self.get_state_flags() & Gtk.StateFlags.INSENSITIVE) @@ -325,6 +323,4 @@ def set_sensitive(self, sensitive): self.handler_block(self.id_bt_release_cb) self.handler_block(self.id_enter_notify_cb) self.handler_block(self.id_leave_notify_cb) - self.set_state_flags(self.get_state_flags() | - Gtk.StateFlags.INSENSITIVE, - clear=True) + self.set_state_flags(Gtk.StateFlags.INSENSITIVE) diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py index dbaf71741..a38370184 100644 --- a/src/sugar3/graphics/palettewindow.py +++ b/src/sugar3/graphics/palettewindow.py @@ -45,15 +45,19 @@ def _get_pointer_position(widget): - global _pointer - - if _pointer is None: - display = widget.get_display() - manager = display.get_device_manager() - _pointer = manager.get_client_pointer() - screen, x, y = _pointer.get_position() - return (x, y) - + current = widget + while current is not None: + if hasattr(current, 'get_window'): + window = current.get_window() + if window is not None: + success, x, y = window.get_pointer() + if success: + return (x, y) + if hasattr(current, 'get_parent'): + current = current.get_parent() + else: + break + return (0, 0) def _calculate_gap(a, b): """Helper function to find the gap position and size of widget a""" @@ -90,29 +94,35 @@ def _calculate_gap(a, b): return False -class _PaletteMenuWidget(Gtk.Menu): - +class _PaletteMenuWidget(Gtk.PopoverMenu): __gtype_name__ = "SugarPaletteMenuWidget" __gsignals__ = { 'enter-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])), - 'leave-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])), + 'leave-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])) } def __init__(self): - Gtk.Menu.__init__(self) - - accel_group = Gtk.AccelGroup() - self.sugar_accel_group = accel_group - self.get_toplevel().add_accel_group(accel_group) - + super().__init__() + + # gtk4 specific + self.set_has_arrow(False) + self.set_autohide(False) + + self.menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.set_child(self.menu_box) + self._popup_position = (0, 0) self._entered = False self._mouse_in_palette = False self._mouse_in_invoker = False self._up = False self._invoker = None - self._menus = [] + + self._motion_controller = Gtk.EventControllerMotion.new() + self._motion_controller.connect("enter", self._enter_notify_cb) + self._motion_controller.connect("leave", self._leave_notify_cb) + self.add_controller(self._motion_controller) def set_accept_focus(self, focus): pass @@ -122,7 +132,9 @@ def get_origin(self): return x, y def move(self, x, y): + """Position the palette menu""" self._popup_position = (x, y) + self.set_pointing_to(Gdk.Rectangle().new(x, y, 1, 1)) def set_transient_for(self, window): pass @@ -133,7 +145,14 @@ def set_invoker(self, invoker): def _position(self, widget, *data): return self._popup_position[0], self._popup_position[1], False + def add_item(self, item): + while item.get_parent() is not None: + item.get_parent().remove(item) + self.menu_box.append(item) + + def popup(self, invoker): + """Show the palette menu""" if self._up: return @@ -168,33 +187,16 @@ def popup(self, invoker): # invoker, and this lets us track enter/leave for the invoker widget. self._invoker = invoker - self._find_all_menus(self) - self.realize() - for menu in self._menus: - if self._invoker: - menu.connect('motion-notify-event', self._motion_notify_cb) - menu.connect('enter-notify-event', self._enter_notify_cb) - menu.connect('leave-notify-event', self._leave_notify_cb) - menu.connect('button-release-event', self._button_release_event_cb) - self._entered = False - self._mouse_in_palette = False - self._mouse_in_invoker = False - Gtk.Menu.popup(self, None, None, self._position, None, 0, 0) + self.set_parent(invoker) + self.popup() self._up = True def popdown(self): + """Hide the palette menu""" if not self._up: return - Gtk.Menu.popdown(self) - - for menu in self._menus: - menu.disconnect_by_func(self._motion_notify_cb) - menu.disconnect_by_func(self._enter_notify_cb) - menu.disconnect_by_func(self._leave_notify_cb) - + self.popdown() self._up = False - self._menus = [] - self._invoker = None def _find_all_menus(self, menu): """ @@ -286,22 +288,37 @@ class _PaletteWindowWidget(Gtk.Window): 'leave-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])), } - def __init__(self, palette=None): - Gtk.Window.__init__(self) + def __init__(self, palette, **kwargs): + super().__init__(**kwargs) + self._palette = palette self.set_decorated(False) self.set_resizable(False) - self.set_position(Gtk.WindowPosition.NONE) + # gtk4 replacement doesn't exist + # self.set_position(Gtk.WindowPosition.NONE) context = self.get_style_context() # Just assume all borders are the same - border = context.get_border(Gtk.StateFlags.ACTIVE).right - self.set_border_width(border) + border = context.get_border() + # self.set_border_width(border_width) + border_width = border.right - accel_group = Gtk.AccelGroup() - self.sugar_accel_group = accel_group - self.add_accel_group(accel_group) + # In GTK 4, use margins (or CSS) instead of set_border_width: + self.set_margin_top(border_width) + self.set_margin_bottom(border_width) + self.set_margin_start(border_width) + self.set_margin_end(border_width) + + + # Remove references to Gtk.AccelGroup: + # accel_group = Gtk.AccelGroup() + # self.sugar_accel_group = accel_group + # self.add_accel_group(accel_group) + + # Replace with a GTK 4 ShortcutController: + shortcut_controller = Gtk.ShortcutController() + self.add_controller(shortcut_controller) self._old_alloc = None self._invoker = None @@ -309,8 +326,8 @@ def __init__(self, palette=None): def set_accept_focus(self, focus): self._should_accept_focus = focus - if self.get_window() is not None: - self.get_window().set_accept_focus(focus) + if self.get_surface() is not None: + self.set_focusable(focus) def get_origin(self): res_, x, y = self.get_window().get_origin() @@ -527,8 +544,13 @@ def _setup_widget(self): self._widget.connect('destroy', self.__destroy_cb) self._widget.connect('enter-notify', self.__enter_notify_cb) self._widget.connect('leave-notify', self.__leave_notify_cb) - self._widget.connect('key-press-event', self.__key_press_event_cb) + # self._widget.connect('key-press-event', self.__key_press_event_cb) + # Replace it with a key controller: + key_controller = Gtk.EventControllerKey() + key_controller.connect('key-pressed', self.__key_press_event_cb) + self._widget.add_controller(key_controller) + self._set_effective_group_id(self._group_id) self._widget.set_invoker(self._invoker) @@ -811,8 +833,17 @@ def __init__(self): self._screen_area = Gdk.Rectangle() self._screen_area.x = self._screen_area.y = 0 - self._screen_area.width = Gdk.Screen.width() - self._screen_area.height = Gdk.Screen.height() + display = Gdk.Display.get_default() + monitors = display.get_monitors() + monitor = monitors.get_item(0) + if monitor is not None: + geometry = monitor.get_geometry() + self._screen_area.width = geometry.width + self._screen_area.height = geometry.height + else: + # Fallback to default values or handle the absence of a primary monitor + self._screen_area.width = 1280 + self._screen_area.height = 800 self._position_hint = self.ANCHORED self._cursor_x = -1 self._cursor_y = -1 @@ -1114,6 +1145,14 @@ def __init__(self, parent=None, widget=None): self._long_pressed_hid = None self._long_pressed_controller = SugarGestures.LongPressController() + if self._widget is not None: + self._long_pressed_controller.attach( + self._widget, SugarGestures.EventControllerFlags.NONE) + else: + logging.debug('Skipping attach() due to missing "event" signal') + + + if parent or widget: self.attach_widget(parent, widget) @@ -1127,25 +1166,52 @@ def attach_widget(self, parent, widget=None): self.notify('widget') - self._enter_hid = self._widget.connect('enter-notify-event', - self.__enter_notify_event_cb) - self._leave_hid = self._widget.connect('leave-notify-event', - self.__leave_notify_event_cb) - if GObject.signal_lookup('clicked', self._widget) != 0: + # Only connect 'enter-notify-event'/'leave-notify-event' if supported + if GObject.signal_lookup('enter-notify-event', self._widget.__class__): + self._enter_hid = self._widget.connect('enter-notify-event', + self.__enter_notify_event_cb) + self._leave_hid = self._widget.connect('leave-notify-event', + self.__leave_notify_event_cb) + else: + self._enter_hid = None + self._leave_hid = None + + if GObject.signal_lookup('clicked', self._widget.__class__): self._click_hid = self._widget.connect('clicked', - self.__click_event_cb) - self._touch_hid = self._widget.connect('touch-event', - self.__touch_event_cb) - self._release_hid = \ - self._widget.connect('button-release-event', - self.__button_release_event_cb) - self._draw_hid = self._widget.connect_after('draw', self.__drawing_cb) + self.__clicked_cb) + else: + self._click_hid = None - self._long_pressed_hid = self._long_pressed_controller.connect( - 'pressed', self.__long_pressed_event_cb, self._widget) - self._long_pressed_controller.attach( - self._widget, - SugarGestures.EventControllerFlags.NONE) + if GObject.signal_lookup('touch-event', self._widget.__class__): + self._touch_hid = self._widget.connect('touch-event', + self.__touch_event_cb) + else: + self._touch_hid = None + + if GObject.signal_lookup('button-release-event', self._widget.__class__): + self._release_hid = self._widget.connect('button-release-event', + self.__button_release_event_cb) + else: + self._release_hid = None + + + if GObject.signal_lookup('draw', self._widget.__class__): + self._draw_hid = self._widget.connect_after('draw', self.__drawing_cb) + else: + self._draw_hid = None + + if hasattr(self._long_pressed_controller, 'connect') and \ + GObject.signal_lookup('event', self._widget.__class__): + self._long_pressed_hid = self._long_pressed_controller.connect( + 'pressed', self.__long_pressed_event_cb, self._widget) + if self._widget.get_parent() is None: + self._long_pressed_controller.attach( + self._widget, SugarGestures.EventControllerFlags.NONE) + else: + logging.debug('Widget already has a parent; skipping long-press attach to avoid gtk_box_append assertion') + else: + self._long_pressed_hid = None + logging.debug('Skipping attach() due to missing "event" signal') self.attach(parent) @@ -1218,17 +1284,21 @@ def __touch_event_cb(self, button, event): return True return False - def __click_event_cb(self, button): - event = Gtk.get_current_event() + def __clicked_cb(self, widget): + # For non-event driven activate (e.g. clicked) simply do: + if self.props.lock_palette and not self.locked: + self.locked = True + if hasattr(self.parent, 'set_expanded'): + self.parent.set_expanded(True) + if self.props.toggle_palette: + self.notify_toggle_state() + + # Retain __click_event_cb for event-driven signals: + def __click_event_cb(self, widget, event): if not event: - # not an event from a user interaction, this can be when - # the clicked event is emitted on a 'active' property - # change of ToggleToolButton for example + # This callback requires an event from a user interaction. return - if event and button != Gtk.get_event_widget(event): - # another special case for the ToggleToolButton: this handles - # the case where we select an item and the active property - # of the other one changes to 'False' + if widget != Gtk.get_event_widget(event): return if self.props.lock_palette and not self.locked: @@ -1314,12 +1384,14 @@ def attach(self, parent): self._pointer_position = _get_pointer_position(self.parent) - self._enter_hid = self._item.connect('enter-notify-event', - self.__enter_notify_event_cb) - self._leave_hid = self._item.connect('leave-notify-event', - self.__leave_notify_event_cb) - self._release_hid = self._item.connect('button-release-event', - self.__button_release_event_cb) + if (self._widget is not None and + GObject.signal_lookup('enter-notify-event', self._widget.__class__)): + self._enter_hid = self._widget.connect('enter-notify-event', + self.__enter_notify_event_cb) + if (self._widget is not None and + GObject.signal_lookup('leave-notify-event', self._widget.__class__)): + self._leave_hid = self._widget.connect('leave-notify-event', + self.__leave_notify_event_cb) self._long_pressed_hid = self._long_pressed_controller.connect( 'pressed', self.__long_pressed_event_cb, self._item) diff --git a/src/sugar3/graphics/radiopalette.py b/src/sugar3/graphics/radiopalette.py index b1e46794c..405037511 100644 --- a/src/sugar3/graphics/radiopalette.py +++ b/src/sugar3/graphics/radiopalette.py @@ -51,33 +51,42 @@ def do_clicked(self): return self.selected_button.emit('clicked') +def _get_box_children(box): + children = [] + child = box.get_first_child() + while child is not None: + children.append(child) + child = child.get_next_sibling() + return children class RadioPalette(Palette): def __init__(self, **kwargs): Palette.__init__(self, **kwargs) - self.button_box = Gtk.HBox() - self.button_box.show() + self.button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.button_box.set_visible(True) self.set_content(self.button_box) def append(self, button, label): - children = self.button_box.get_children() + children = _get_box_children(self.button_box) if button.palette is not None: raise RuntimeError("Palette's button should not have sub-palettes") - - button.show() + + self.button_box.append(button) + button.set_visible(True) button.connect('clicked', self.__clicked_cb) - self.button_box.pack_start(button, True, False, 0) + self.button_box.append(button) button.palette_label = label if not children: self.__clicked_cb(button) def update_button(self): - for i in self.button_box.get_children(): + for i in _get_box_children(self.button_box): self.__clicked_cb(i) + def __clicked_cb(self, button): if not button.get_active(): diff --git a/src/sugar3/graphics/radiotoolbutton.py b/src/sugar3/graphics/radiotoolbutton.py index 97ebcc007..7232dc7e1 100644 --- a/src/sugar3/graphics/radiotoolbutton.py +++ b/src/sugar3/graphics/radiotoolbutton.py @@ -38,9 +38,9 @@ from sugar3.graphics import toolbutton -class RadioToolButton(Gtk.RadioToolButton): +class RadioToolButton(Gtk.ToggleButton): ''' - The RadioToolButton class manages a Gtk.RadioToolButton styled for + The RadioToolButton class manages a Gtk.ToggleButton styled for Sugar. Args: @@ -62,7 +62,7 @@ class RadioToolButton(Gtk.RadioToolButton): __gtype_name__ = 'SugarRadioToolButton' - def __init__(self, icon_name=None, **kwargs): + def __init__(self, icon_name=None, group=None, **kwargs): self._accelerator = None self._tooltip = None self._xo_color = None @@ -77,16 +77,28 @@ def __init__(self, icon_name=None, **kwargs): if icon_name: self.set_icon_name(icon_name) - # HACK: stop Gtk from adding a label and expanding the size of - # the button. This happen when set_icon_widget is called - # if label_widget is None - self.props.label_widget = Gtk.Box() + self._group = group + if self._group is not None: + if isinstance(self._group, RadioToolButton): + self._group = self._group._group + self._group.append(self) + else: + self._group = [self] + self.connect('toggled', self.__toggled_cb) self.connect('destroy', self.__destroy_cb) def __destroy_cb(self, icon): if self._palette_invoker is not None: self._palette_invoker.detach() + if self in self._group: + self._group.remove(self) + + def __toggled_cb(self, button): + if button.get_active(): + for btn in self._group: + if btn != button: + btn.set_active(False) def set_tooltip(self, tooltip): ''' @@ -103,7 +115,7 @@ def set_tooltip(self, tooltip): self._tooltip = tooltip # Set label, shows up when toolbar overflows - Gtk.RadioToolButton.set_label(self, tooltip) + Gtk.ToggleButton.set_label(self, tooltip) def get_tooltip(self): ''' @@ -143,15 +155,15 @@ def set_icon_name(self, icon_name): ''' icon = Icon(icon_name=icon_name, xo_color=self._xo_color) - self.set_icon_widget(icon) - icon.show() + self.set_child(icon) + icon.set_visible(True) def get_icon_name(self): ''' Return icon name, or None if there is no icon name. ''' - if self.props.icon_widget is not None: - return self.props.icon_widget.props.icon_name + if self.get_child() is not None: + return self.get_child().props.icon_name else: return None @@ -167,8 +179,8 @@ def set_xo_color(self, xo_color): ''' if self._xo_color != xo_color: self._xo_color = xo_color - if self.props.icon_widget is not None: - self.props.icon_widget.props.xo_color = xo_color + if self.get_child() is not None: + self.get_child().props.xo_color = xo_color def get_xo_color(self): ''' @@ -212,7 +224,7 @@ def do_draw(self, cr): cr.rectangle(0, 0, allocation.width, allocation.height) cr.paint() - Gtk.RadioToolButton.do_draw(self, cr) + Gtk.ToggleButton.do_draw(self, cr) if self.palette and self.palette.is_up(): invoker = self.palette.props.invoker diff --git a/src/sugar3/graphics/toolbarbox.py b/src/sugar3/graphics/toolbarbox.py index 804e2b24f..58a3e4fce 100644 --- a/src/sugar3/graphics/toolbarbox.py +++ b/src/sugar3/graphics/toolbarbox.py @@ -33,21 +33,24 @@ def __init__(self, page=None, **kwargs): ToolButton.__init__(self, **kwargs) self.page_widget = None + self._last_parent = None # track previous parent + self.set_page(page) self.connect('clicked', lambda widget: self.set_expanded(not self.is_expanded())) - self.connect_after('draw', self.__drawing_cb) - self.connect('hierarchy-changed', self.__hierarchy_changed_cb) + # self.connect_after('draw', self.__drawing_cb) + self.connect('notify::parent', self.__hierarchy_changed_cb) def __hierarchy_changed_cb(self, tool_button, previous_toplevel): - parent = self.get_parent() - if hasattr(parent, 'owner'): - if self.page_widget and previous_toplevel is None: - self._unparent() - parent.owner.pack_start(self.page_widget, True, True, 0) - self.set_expanded(False) + new_parent = self.get_parent() + if self._last_parent is None and new_parent is not None and \ + hasattr(new_parent, 'owner') and self.page_widget: + self._unparent() + new_parent.owner.append(self.page_widget) + self.set_expanded(False) + self._last_parent = new_parent def get_toolbar_box(self): parent = self.get_parent() @@ -107,9 +110,9 @@ def set_expanded(self, expanded): self._unparent() - self.modify_bg(Gtk.StateType.NORMAL, box.background) + self.override_background_color(Gtk.StateFlags.NORMAL, box.background) _setup_page(self.page_widget, box.background, box.props.padding) - box.pack_start(self.page_widget, True, True, 0) + box.append(self.page_widget) def _move_page_to_palette(self): if self.is_in_palette(): @@ -118,13 +121,12 @@ def _move_page_to_palette(self): self._unparent() if isinstance(self.props.palette, _ToolbarPalette): - self.props.palette._widget.add(self.page_widget) - + self.props.palette._widget.set_child(self.page_widget) + def _unparent(self): - page_parent = self.page_widget.get_parent() - if page_parent is None: - return - page_parent.remove(self.page_widget) + if self.page_widget.get_parent() is not None: + self.page_widget.unparent() + def __drawing_cb(self, button, cr): alloc = self.get_allocation() @@ -140,9 +142,13 @@ def __drawing_cb(self, button, cr): Gtk.PositionType.BOTTOM, 0, alloc.width) _paint_arrow(self, cr, 0) return False - - -class ToolbarBox(Gtk.VBox): + def do_snapshot(self, snapshot): + # Add default style classes for consistency. + self.get_style_context().add_class('toolitem') + self.get_style_context().add_class('toolbar-down') + # TODO: Implement custom arrow drawing using GTK4 snapshot APIs, if desired. + return super(ToolbarBox, self).do_snapshot(snapshot) +class ToolbarBox(Gtk.Box): __gtype_name__ = 'SugarToolbarBox' @@ -151,17 +157,16 @@ def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING): self._expanded_button_index = -1 self.background = None - self._toolbar = Gtk.Toolbar() + self._toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self._toolbar.owner = self - self._toolbar.connect('remove', self.__remove_cb) self._toolbar_widget, self._toolbar_alignment = \ - _embed_page(Gtk.EventBox(), self._toolbar) - self.pack_start(self._toolbar_widget, True, True, 0) + _embed_page(Gtk.Box(), self._toolbar) + self.append(self._toolbar_widget) self.props.padding = padding - self.modify_bg(Gtk.StateType.NORMAL, - style.COLOR_TOOLBAR_GREY.get_gdk_color()) + self.override_background_color(Gtk.StateFlags.NORMAL, + style.COLOR_TOOLBAR_GREY) def get_toolbar(self): return self._toolbar @@ -171,32 +176,33 @@ def get_toolbar(self): def get_expanded_button(self): if self._expanded_button_index == -1: return None - return self.toolbar.get_nth_item(self._expanded_button_index) + return self.toolbar.get_child_at_index(self._expanded_button_index) def set_expanded_button(self, button): - if button not in self.toolbar: + if button not in self.toolbar.get_children(): self._expanded_button_index = -1 return - self._expanded_button_index = self.toolbar.get_item_index(button) + self._expanded_button_index = self.toolbar.get_children().index(button) expanded_button = property(get_expanded_button, set_expanded_button) def get_padding(self): - return self._toolbar_alignment.props.left_padding + return self._toolbar_alignment.get_margin_start() def set_padding(self, pad): - self._toolbar_alignment.set_padding(0, 0, pad, pad) + self._toolbar_alignment.set_margin_start(pad) + self._toolbar_alignment.set_margin_end(pad) - padding = GObject.Property(type=object, + padding = GObject.Property(type=int, getter=get_padding, setter=set_padding) - def modify_bg(self, state, color): - if state == Gtk.StateType.NORMAL: + def override_background_color(self, state, color): + if state == Gtk.StateFlags.NORMAL: self.background = color - self._toolbar_widget.modify_bg(state, color) - self.toolbar.modify_bg(state, color) + _override_background_color(self._toolbar_widget, state, color) + _override_background_color(self.toolbar, state, color) - def __remove_cb(self, sender, button): + def remove_button(self, button): if not isinstance(button, ToolbarButton): return button.popdown() @@ -204,6 +210,10 @@ def __remove_cb(self, sender, button): self.remove(button.page_widget) self._expanded_button_index = -1 + def remove(self, widget): + self.remove_button(widget) + super().remove(widget) + if hasattr(ToolbarBox, 'set_css_name'): ToolbarBox.set_css_name('toolbarbox') @@ -219,8 +229,13 @@ def __init__(self, **kwargs): group.connect('popdown', self.__group_popdown_cb) self.set_group_id('toolbarbox') - self._widget = _PaletteWindowWidget() - self._widget.set_border_width(0) + self._widget = _PaletteWindowWidget(self) + # self._widget.set_border_width(0) + self._widget.set_margin_top(0) + self._widget.set_margin_bottom(0) + self._widget.set_margin_start(0) + self._widget.set_margin_end(0) + self._setup_widget() self._widget.connect('realize', self._realize_cb) @@ -263,8 +278,7 @@ def popup(self, immediate=False): if button.is_expanded(): return box = button.toolbar_box - _setup_page(button.page_widget, style.COLOR_BLACK.get_gdk_color(), - box.props.padding) + _setup_page(button.page_widget, style.COLOR_BLACK, box.props.padding) PaletteWindow.popup(self, immediate) def __group_popdown_cb(self, group): @@ -272,7 +286,7 @@ def __group_popdown_cb(self, group): self.popdown(immediate=True) -class _Box(Gtk.EventBox): +class _Box(Gtk.Box): def __init__(self, toolbar_button): GObject.GObject.__init__(self) @@ -294,36 +308,45 @@ def do_draw(self, cr): def _setup_page(page_widget, color, hpad): - page_widget.get_child().set_padding(0, 0, hpad, hpad) + page_widget.get_child().set_margin_start(hpad) + page_widget.get_child().set_margin_end(hpad) page = _get_embedded_page(page_widget) - page.modify_bg(Gtk.StateType.NORMAL, color) + _override_background_color(page, Gtk.StateFlags.NORMAL, color) if isinstance(page, Gtk.Container): for i in page.get_children(): - i.modify_bg(Gtk.StateType.INSENSITIVE, color) + _override_background_color(i, Gtk.StateFlags.INSENSITIVE, color) - page_widget.modify_bg(Gtk.StateType.NORMAL, color) - page_widget.modify_bg(Gtk.StateType.PRELIGHT, color) + _override_background_color(page_widget, Gtk.StateFlags.NORMAL, color) + _override_background_color(page_widget, Gtk.StateFlags.PRELIGHT, color) def _embed_page(page_widget, page): page.show() - alignment = Gtk.Alignment(xscale=1.0, yscale=1.0) - alignment.add(page) - alignment.show() + alignment = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + alignment.append(page) + alignment.set_visible(True) - page_widget.modify_bg(Gtk.StateType.ACTIVE, - style.COLOR_BUTTON_GREY.get_gdk_color()) - page_widget.add(alignment) - page_widget.show() + _override_background_color(page_widget, Gtk.StateFlags.ACTIVE, + style.COLOR_BUTTON_GREY) + page_widget.append(alignment) + page_widget.set_visible(True) return (page_widget, alignment) def _get_embedded_page(page_widget): - return page_widget.get_child().get_child() - + if hasattr(page_widget, "get_first_child"): + first = page_widget.get_first_child() + elif hasattr(page_widget, "get_child"): + first = page_widget.get_child() + else: + first = None + + if first is not None and hasattr(first, "get_first_child"): + return first.get_first_child() + return first def _paint_arrow(widget, cr, angle): alloc = widget.get_allocation() @@ -336,3 +359,32 @@ def _paint_arrow(widget, cr, angle): context.add_class('toolitem') Gtk.render_arrow(context, cr, angle, x, y, arrow_size) + + +def _parse_rgba(color): + # If it's already a Gdk.RGBA, just return its components + if hasattr(color, 'red') and hasattr(color, 'green') and hasattr(color, 'blue') and hasattr(color, 'alpha'): + return (color.red, color.green, color.blue, color.alpha) + + # If it's a tuple/list, assume up to 4 channels + if isinstance(color, (tuple, list)): + r, g, b = color[:3] + a = color[3] if len(color) > 3 else 1.0 + return (r, g, b, a) + + # Fallback to white + return (1.0, 1.0, 1.0, 1.0) + +def _override_background_color(widget, state, color): + r, g, b, a = _parse_rgba(color) + css_color = f"rgba({int(r * 255)}, {int(g * 255)}, {int(b * 255)}, {a})" + style_context = widget.get_style_context() + style_context.add_class('custom-bg') + css_provider = Gtk.CssProvider() + css_provider.load_from_data(f""" + .custom-bg {{ + background-color: {css_color}; + }} + """.encode('utf-8')) + style_context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + diff --git a/src/sugar3/graphics/toolbox.py b/src/sugar3/graphics/toolbox.py index daf651665..5b6beee03 100644 --- a/src/sugar3/graphics/toolbox.py +++ b/src/sugar3/graphics/toolbox.py @@ -27,7 +27,7 @@ from sugar3.graphics import style -class Toolbox(Gtk.VBox): +class Toolbox(Gtk.Box): ''' Class to represent the toolbox of an activity. Groups a number of toolbars vertically, which can be accessed using their @@ -46,7 +46,7 @@ class Toolbox(Gtk.VBox): } def __init__(self): - GObject.GObject.__init__(self) + super().__init__(orientation=Gtk.Orientation.VERTICAL) self._notebook = Gtk.Notebook() self._notebook.set_tab_pos(Gtk.PositionType.BOTTOM) @@ -54,14 +54,15 @@ def __init__(self): self._notebook.set_show_tabs(False) self._notebook.props.tab_vborder = style.TOOLBOX_TAB_VBORDER self._notebook.props.tab_hborder = style.TOOLBOX_TAB_HBORDER - self.pack_start(self._notebook, True, True, 0) - self._notebook.show() + self.append(self._notebook) + self._notebook.set_visible(True) - self._separator = Gtk.HSeparator() - self._separator.modify_bg(Gtk.StateType.NORMAL, - style.COLOR_PANEL_GREY.get_gdk_color()) + self._separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + self._separator.override_background_color(Gtk.StateFlags.NORMAL, + style.COLOR_PANEL_GREY) self._separator.set_size_request(1, style.TOOLBOX_SEPARATOR_HEIGHT) - self.pack_start(self._separator, False, False, 0) + self.append(self._separator) + self._separator.set_visible(False) self._notebook.connect('notify::page', self._notify_page_cb) @@ -82,27 +83,28 @@ def add_toolbar(self, name, toolbar): Gtk.Toolbar to be appended to this toolbox ''' label = Gtk.Label(label=name) - req = label.size_request() - label.set_size_request(max(req.width, style.TOOLBOX_TAB_LABEL_WIDTH), + req = label.get_preferred_size() + label.set_size_request(max(req[1].width, style.TOOLBOX_TAB_LABEL_WIDTH), -1) - label.set_alignment(0.0, 0.5) + label.set_xalign(0.0) + label.set_yalign(0.5) - event_box = Gtk.EventBox() + event_box = Gtk.Box() - alignment = Gtk.Alignment(xscale=1.0, yscale=1.0) - alignment.set_padding(0, 0, style.TOOLBOX_HORIZONTAL_PADDING, - style.TOOLBOX_HORIZONTAL_PADDING) + alignment = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + alignment.set_margin_start(style.TOOLBOX_HORIZONTAL_PADDING) + alignment.set_margin_end(style.TOOLBOX_HORIZONTAL_PADDING) - alignment.add(toolbar) - event_box.add(alignment) - alignment.show() - event_box.show() + alignment.append(toolbar) + event_box.append(alignment) + alignment.set_visible(True) + event_box.set_visible(True) self._notebook.append_page(event_box, label) if self._notebook.get_n_pages() > 1: self._notebook.set_show_tabs(True) - self._separator.show() + self._separator.set_visible(True) def remove_toolbar(self, index): ''' @@ -115,7 +117,7 @@ def remove_toolbar(self, index): if self._notebook.get_n_pages() < 2: self._notebook.set_show_tabs(False) - self._separator.hide() + self._separator.set_visible(False) def set_current_toolbar(self, index): ''' diff --git a/src/sugar3/graphics/toolbutton.py b/src/sugar3/graphics/toolbutton.py index 8b0031f77..633ea2f1e 100644 --- a/src/sugar3/graphics/toolbutton.py +++ b/src/sugar3/graphics/toolbutton.py @@ -18,7 +18,7 @@ """ The toolbutton module provides the ToolButton class, which is a -Gtk.ToolButton with icon and tooltip styled for Sugar. +Gtk.Button with icon and tooltip styled for Sugar. Example: Add a tool button to a window @@ -70,17 +70,42 @@ def _add_accelerator(tool_button): def _hierarchy_changed_cb(tool_button, previous_toplevel): - _add_accelerator(tool_button) + _add_accelerator_gtk4(tool_button) + # In GTK4, property notifications for "accelerator" can be handled by re-adding controllers, + # but for now we remove any obsolete signal connections. + +def _add_accelerator_gtk4(tool_button): + child = tool_button.get_child() + if not child: + return + + accel = tool_button.get_property("accelerator") + if not accel: + return + try: + trigger = Gtk.ShortcutTrigger.parse(accel) + except Exception as e: + logging.error("Error parsing accelerator '%s': %s", accel, e) + return + + # Create an action that activates the tool_button when the accelerator fires. + action = Gtk.CallbackAction.new(lambda *args: tool_button.activate()) + shortcut = Gtk.Shortcut.new(trigger, action) + # Create a new ShortcutController and add the accelerator shortcut to the child. + controller = Gtk.ShortcutController.new() + controller.add_shortcut(shortcut) + child.add_controller(controller) + def setup_accelerator(tool_button): _add_accelerator(tool_button) - tool_button.connect('hierarchy-changed', _hierarchy_changed_cb) - + tool_button.connect('notify::accelerator', _add_accelerator) + -class ToolButton(Gtk.ToolButton): +class ToolButton(Gtk.Button): ''' - The ToolButton class manages a Gtk.ToolButton styled for Sugar. + The ToolButton class manages a Gtk.Button styled for Sugar. Keyword Args: icon_name(string): name of themed icon. @@ -97,6 +122,15 @@ class ToolButton(Gtk.ToolButton): ''' __gtype_name__ = 'SugarToolButton' + + __gsignals__ = { + 'can-activate-accel': ( + GObject.SignalFlags.RUN_FIRST, + GObject.TYPE_BOOLEAN, + () + ) + } + def __init__(self, icon_name=None, **kwargs): self._accelerator = None @@ -111,9 +145,7 @@ def __init__(self, icon_name=None, **kwargs): if icon_name: self.set_icon_name(icon_name) - self.get_child().connect('can-activate-accel', - self.__button_can_activate_accel_cb) - + self.connect('can-activate-accel', self.__button_can_activate_accel_cb) self.connect('destroy', self.__destroy_cb) def __destroy_cb(self, icon): @@ -139,7 +171,7 @@ def set_tooltip(self, tooltip): self._tooltip = tooltip # Set label, shows up when toolbar overflows - Gtk.ToolButton.set_label(self, tooltip) + self.set_tooltip_text(tooltip) def get_tooltip(self): ''' @@ -200,15 +232,15 @@ def set_icon_name(self, icon_name): icon_name (string): name of icon ''' icon = Icon(icon_name=icon_name) - self.set_icon_widget(icon) - icon.show() + self.set_child(icon) + icon.set_visible(True) def get_icon_name(self): ''' Return icon name, or None if there is no icon name. ''' - if self.props.icon_widget is not None: - return self.props.icon_widget.props.icon_name + if self.get_child() is not None: + return self.get_child().props.icon_name else: return None @@ -248,7 +280,7 @@ def do_draw(self, cr): cr.rectangle(0, 0, allocation.width, allocation.height) cr.paint() - Gtk.ToolButton.do_draw(self, cr) + Gtk.Button.do_draw(self, cr) if self.palette and self.palette.is_up(): invoker = self.palette.props.invoker diff --git a/src/sugar3/graphics/toolcombobox.py b/src/sugar3/graphics/toolcombobox.py index 0e9f8b84a..5abdfb7e3 100644 --- a/src/sugar3/graphics/toolcombobox.py +++ b/src/sugar3/graphics/toolcombobox.py @@ -40,22 +40,22 @@ def __init__(self, combo=None, **kwargs): self.set_border_width(style.DEFAULT_PADDING) - hbox = Gtk.HBox(False, style.DEFAULT_SPACING) + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=style.DEFAULT_SPACING) self.label = Gtk.Label(label=self._label_text) - hbox.pack_start(self.label, False, False, 0) - self.label.show() + hbox.append(self.label) + self.label.set_visible(True) if combo: self.combo = combo else: self.combo = ComboBox() - hbox.pack_start(self.combo, True, True, 0) - self.combo.show() + hbox.append(self.combo) + self.combo.set_visible(True) self.add(hbox) - hbox.show() + hbox.set_visible(True) def do_set_property(self, pspec, value): if pspec.name == 'label-text': diff --git a/src/sugar3/graphics/tray.py b/src/sugar3/graphics/tray.py index 3cef6de0b..13da021c4 100644 --- a/src/sugar3/graphics/tray.py +++ b/src/sugar3/graphics/tray.py @@ -62,7 +62,7 @@ def __init__(self, orientation): self.traybar.set_orientation(orientation) self.traybar.set_show_arrow(False) self.add(self.traybar) - self.traybar.show() + self.traybar.set_visible(True) self.connect('size-allocate', self._size_allocate_cb) @@ -196,12 +196,8 @@ def __init__(self, icon_name, scroll_direction): self.icon = Icon(icon_name=icon_name, pixel_size=style.SMALL_ICON_SIZE) - # The alignment is a hack to work around Gtk.ToolButton code - # that sets the icon_size when the icon_widget is a Gtk.Image - alignment = Gtk.Alignment(xalign=0.5, yalign=0.5) - alignment.add(self.icon) - self.set_icon_widget(alignment) - alignment.show_all() + self.set_child(self.icon) + self.icon.set_visible(True) self.connect('clicked', self._clicked_cb) @@ -240,7 +236,7 @@ def _clicked_cb(self, button): ALIGN_TO_END = 1 -class HTray(Gtk.EventBox): +class HTray(Gtk.Box): __gtype_name__ = 'SugarHTray' @@ -255,32 +251,26 @@ def __init__(self, **kwargs): self._drag_active = False self.align = ALIGN_TO_START - Gtk.EventBox.__init__(self, **kwargs) - - self._box = Gtk.HBox() - self.add(self._box) - self._box.show() + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, **kwargs) scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE) - self._box.pack_start(scroll_left, False, False, 0) + self.append(scroll_left) self._viewport = _TrayViewport(Gtk.Orientation.HORIZONTAL) - self._box.pack_start(self._viewport, True, True, 0) - self._viewport.show() + self.append(self._viewport) + self._viewport.set_visible(True) scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE) - self._box.pack_start(scroll_right, False, False, 0) + self.append(scroll_right) scroll_left.viewport = self._viewport scroll_right.viewport = self._viewport if self.align == ALIGN_TO_END: - spacer = Gtk.SeparatorToolItem() + spacer = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL) spacer.set_size_request(0, 0) - spacer.props.draw = False - spacer.set_expand(True) self._viewport.traybar.insert(spacer, 0) - spacer.show() + spacer.set_visible(True) def do_set_property(self, pspec, value): if pspec.name == 'align': @@ -302,11 +292,11 @@ def _set_drag_active(self, active): if self._drag_active != active: self._drag_active = active if self._drag_active: - self._viewport.traybar.modify_bg( - Gtk.StateType.NORMAL, - style.COLOR_BLACK.get_gdk_color()) + self._viewport.traybar.override_background_color( + Gtk.StateFlags.NORMAL, + style.COLOR_BLACK) else: - self._viewport.traybar.modify_bg(Gtk.StateType.NORMAL, None) + self._viewport.traybar.override_background_color(Gtk.StateFlags.NORMAL, None) def get_children(self): children = self._viewport.traybar.get_children()[:] @@ -336,7 +326,7 @@ def scroll_to_item(self, item): HTray.set_css_name('htray') -class VTray(Gtk.EventBox): +class VTray(Gtk.Box): __gtype_name__ = 'SugarVTray' @@ -351,32 +341,26 @@ def __init__(self, **kwargs): self._drag_active = False self.align = ALIGN_TO_START - Gtk.EventBox.__init__(self, **kwargs) - - self._box = Gtk.VBox() - self.add(self._box) - self._box.show() + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, **kwargs) scroll_up = _TrayScrollButton('go-up', _PREVIOUS_PAGE) - self._box.pack_start(scroll_up, False, False, 0) + self.append(scroll_up) self._viewport = _TrayViewport(Gtk.Orientation.VERTICAL) - self._box.pack_start(self._viewport, True, True, 0) - self._viewport.show() + self.append(self._viewport) + self._viewport.set_visible(True) scroll_down = _TrayScrollButton('go-down', _NEXT_PAGE) - self._box.pack_start(scroll_down, False, False, 0) + self.append(scroll_down) scroll_up.viewport = self._viewport scroll_down.viewport = self._viewport if self.align == ALIGN_TO_END: - spacer = Gtk.SeparatorToolItem() + spacer = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) spacer.set_size_request(0, 0) - spacer.props.draw = False - spacer.set_expand(True) self._viewport.traybar.insert(spacer, 0) - spacer.show() + spacer.set_visible(True) def do_set_property(self, pspec, value): if pspec.name == 'align': @@ -398,11 +382,11 @@ def _set_drag_active(self, active): if self._drag_active != active: self._drag_active = active if self._drag_active: - self._viewport.traybar.modify_bg( - Gtk.StateType.NORMAL, - style.COLOR_BLACK.get_gdk_color()) + self._viewport.traybar.override_background_color( + Gtk.StateFlags.NORMAL, + style.COLOR_BLACK) else: - self._viewport.traybar.modify_bg(Gtk.StateType.NORMAL, None) + self._viewport.traybar.override_background_color(Gtk.StateFlags.NORMAL, None) def get_children(self): children = self._viewport.traybar.get_children()[:] @@ -438,12 +422,12 @@ def __init__(self, **kwargs): ToolButton.__init__(self, **kwargs) -class _IconWidget(Gtk.EventBox): +class _IconWidget(Gtk.Box): __gtype_name__ = 'SugarTrayIconWidget' def __init__(self, icon_name=None, xo_color=None): - Gtk.EventBox.__init__(self) + Gtk.Box.__init__(self) self.set_app_paintable(True) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | @@ -452,8 +436,8 @@ def __init__(self, icon_name=None, xo_color=None): self._icon = Icon(icon_name=icon_name, xo_color=xo_color, pixel_size=style.STANDARD_ICON_SIZE) - self.add(self._icon) - self._icon.show() + self.append(self._icon) + self._icon.set_visible(True) def do_draw(self, cr): palette = self.get_parent().palette @@ -465,7 +449,7 @@ def do_draw(self, cr): cr.rectangle(0, 0, allocation.width, allocation.height) cr.paint() - Gtk.EventBox.do_draw(self, cr) + Gtk.Box.do_draw(self, cr) if palette and palette.is_up(): invoker = palette.props.invoker @@ -486,7 +470,7 @@ def __init__(self, icon_name=None, xo_color=None): self._icon_widget = _IconWidget(icon_name, xo_color) self.add(self._icon_widget) - self._icon_widget.show() + self._icon_widget.set_visible(True) self._palette_invoker = ToolInvoker(self) diff --git a/src/sugar3/graphics/window.py b/src/sugar3/graphics/window.py index 81211eec5..1d50c8394 100644 --- a/src/sugar3/graphics/window.py +++ b/src/sugar3/graphics/window.py @@ -21,7 +21,7 @@ """ import gi -gi.require_version('GdkX11', '3.0') +gi.require_version('GdkX11', '4.0') from gi.repository import GObject from gi.repository import GLib @@ -141,9 +141,9 @@ def __init__(self, **args): self._canvas = None self.tray = None - self.__vbox = Gtk.VBox() - self.__hbox = Gtk.HBox() - self.__vbox.pack_start(self.__hbox, True, True, 0) + self.__vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.__hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.__vbox.append(self.__hbox) self.__hbox.show() self.add_events(Gdk.EventMask.POINTER_MOTION_HINT_MASK | @@ -246,10 +246,15 @@ def set_canvas(self, canvas): self.__hbox.remove(self._canvas) if canvas: - self.__hbox.pack_start(canvas, True, True, 0) + self.__hbox.append(canvas) self._canvas = canvas - self.__vbox.set_focus_child(self._canvas) + + if hasattr(self, "_Window__vbox"): + self.__vbox.set_focus_child(self._canvas) + else: + self.__hbox.grab_focus() + def get_canvas(self): """ @@ -288,7 +293,7 @@ def set_toolbar_box(self, toolbar_box): self.__vbox.remove(self._toolbar_box) if toolbar_box: - self.__vbox.pack_start(toolbar_box, False, False, 0) + self.__vbox.append(toolbar_box) self.__vbox.reorder_child(toolbar_box, 0) self._toolbar_box = toolbar_box @@ -313,11 +318,11 @@ def set_tray(self, tray, position): box.remove(self.tray) if position == Gtk.PositionType.LEFT: - self.__hbox.pack_start(tray, False, False, 0) + self.__hbox.append(tray) elif position == Gtk.PositionType.RIGHT: - self.__hbox.pack_end(tray, False, False, 0) + self.__hbox.append(tray) elif position == Gtk.PositionType.BOTTOM: - self.__vbox.pack_end(tray, False, False, 0) + self.__vbox.append(tray) self.tray = tray @@ -334,7 +339,7 @@ def add_alert(self, alert): """ self._alerts.append(alert) if len(self._alerts) == 1: - self.__vbox.pack_start(alert, False, False, 0) + self.__vbox.append(alert) if self._toolbar_box is not None: self.__vbox.reorder_child(alert, 1) else: @@ -354,7 +359,7 @@ def remove_alert(self, alert): if alert.get_parent() is not None: self.__vbox.remove(alert) if len(self._alerts) >= 1: - self.__vbox.pack_start(self._alerts[0], False, False, 0) + self.__vbox.append(self._alerts[0]) if self._toolbar_box is not None: self.__vbox.reorder_child(self._alerts[0], 1) else: diff --git a/src/sugar3/speech.py b/src/sugar3/speech.py index 6c0b0273f..fb217ea3e 100644 --- a/src/sugar3/speech.py +++ b/src/sugar3/speech.py @@ -19,7 +19,7 @@ from gettext import gettext as _ import gi -gi.require_version('Gtk', '3.0') +gi.require_version('Gtk', '4.0') from gi.repository import Gio from gi.repository import Gtk diff --git a/src/sugar3/sugar-clipboard.c b/src/sugar3/sugar-clipboard.c index 5685790bb..c6eddca1e 100644 --- a/src/sugar3/sugar-clipboard.c +++ b/src/sugar3/sugar-clipboard.c @@ -17,36 +17,48 @@ * Boston, MA 02111-1307, USA. */ +#include #include "sugar-clipboard.h" /** * sugar_clipboard_set_with_data: - * @clipboard: a #GtkClipboard - * @targets: (array length=n_targets): array containing information - * about the available forms for the clipboard data - * @n_targets: number of elements in @targets - * @get_func: (closure user_data) (scope notified): function to call to get the - * actual clipboard data - * @clear_func: (closure user_data) (scope async): when the clipboard - * contents are set again, this function will be called, and @get_func - * will not be subsequently called. + * @clipboard: a clipboard object. In GTK3, this is a #GtkClipboard. In GTK4, it is a #GdkClipboard. + * @targets: (array length=n_targets): array containing information about the available clipboard formats. + * @n_targets: number of elements in @targets. + * @get_func: a callback to retrieve the clipboard data. + * @clear_func: a callback to clear the clipboard data when it is updated. * @user_data: user data to pass to @get_func and @clear_func. * - * Virtually sets the contents of the specified clipboard by providing - * a list of supported formats for the clipboard data and a function - * to call to get the actual data when it is requested. + * Sets the contents of the specified clipboard by providing a list of supported + * formats and callbacks to retrieve or clear the data. * * Return value: %TRUE if setting the clipboard data succeeded. - * If setting the clipboard data failed the provided callback - * functions will be ignored. - **/ + */ +#if GTK_CHECK_VERSION(4, 0, 0) +gboolean +sugar_clipboard_set_with_data (GdkClipboard *clipboard, + const GtkTargetEntry *targets, + guint n_targets, + GtkClipboardGetFunc get_func, + GtkClipboardClearFunc clear_func, + gpointer user_data) +{ + /* + * In GTK4 the clipboard API has changed. + * Replace this stub with an implementation using the new GTK4 clipboard API, + * such as using gdk_clipboard_set_content() with a GdkContentProvider. + */ + g_warning ("sugar_clipboard_set_with_data is not yet implemented for GTK4."); + return FALSE; +} +#else gboolean sugar_clipboard_set_with_data (GtkClipboard *clipboard, - const GtkTargetEntry *targets, - guint n_targets, - GtkClipboardGetFunc get_func, - GtkClipboardClearFunc clear_func, - gpointer user_data) + const GtkTargetEntry *targets, + guint n_targets, + GtkClipboardGetFunc get_func, + GtkClipboardClearFunc clear_func, + gpointer user_data) { return gtk_clipboard_set_with_data (clipboard, targets, @@ -55,3 +67,4 @@ sugar_clipboard_set_with_data (GtkClipboard *clipboard, clear_func, user_data); } +#endif diff --git a/src/sugar3/sugar-clipboard.h b/src/sugar3/sugar-clipboard.h index e5d84dbd6..7157f5813 100644 --- a/src/sugar3/sugar-clipboard.h +++ b/src/sugar3/sugar-clipboard.h @@ -22,8 +22,35 @@ #include +#if GTK_CHECK_VERSION(4, 0, 0) +/* Define dummy types and function pointer types for GTK4, + * since the old GTK3 clipboard types are no longer available. + */ +typedef struct { + const gchar *target; + guint flags; + guint info; +} GtkTargetEntry; + +typedef void (*GtkClipboardGetFunc)(GdkClipboard *clipboard, + const GtkTargetEntry *target, + gpointer user_data); + +typedef void (*GtkClipboardClearFunc)(GdkClipboard *clipboard, + gpointer user_data); +#endif + G_BEGIN_DECLS +#if GTK_CHECK_VERSION(4, 0, 0) +gboolean +sugar_clipboard_set_with_data (GdkClipboard *clipboard, + const GtkTargetEntry *targets, + guint n_targets, + GtkClipboardGetFunc get_func, + GtkClipboardClearFunc clear_func, + gpointer user_data); +#else gboolean sugar_clipboard_set_with_data (GtkClipboard *clipboard, const GtkTargetEntry *targets, @@ -31,8 +58,8 @@ sugar_clipboard_set_with_data (GtkClipboard *clipboard, GtkClipboardGetFunc get_func, GtkClipboardClearFunc clear_func, gpointer user_data); +#endif G_END_DECLS #endif /* __SUGAR_CLIPBOARD_H__ */ - diff --git a/src/sugar3/sugar-cursor-tracker.c b/src/sugar3/sugar-cursor-tracker.c index 49314f679..5d9e8e446 100644 --- a/src/sugar3/sugar-cursor-tracker.c +++ b/src/sugar3/sugar-cursor-tracker.c @@ -19,8 +19,13 @@ * Author: Simon Schampijer */ -#include +#include +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif #include +#include #include "sugar-cursor-tracker.h" G_DEFINE_TYPE_WITH_PRIVATE (SugarCursorTracker, sugar_cursor_tracker, G_TYPE_OBJECT) @@ -28,135 +33,170 @@ G_DEFINE_TYPE_WITH_PRIVATE (SugarCursorTracker, sugar_cursor_tracker, G_TYPE_OBJ static void sugar_cursor_tracker_finalize (GObject *object) { - SugarCursorTrackerPrivate *priv = SUGAR_CURSOR_TRACKER (object)->priv; - - G_OBJECT_CLASS (sugar_cursor_tracker_parent_class)->finalize (object); + G_OBJECT_CLASS (sugar_cursor_tracker_parent_class)->finalize (object); } static void sugar_cursor_tracker_class_init (SugarCursorTrackerClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sugar_cursor_tracker_finalize; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = sugar_cursor_tracker_finalize; } -static GdkWindow * -_get_default_root_window (void) +static GdkSurface * +_get_default_root_surface (void) { - GdkDisplay *display; - GdkScreen *screen; - - display = gdk_display_get_default (); - screen = gdk_display_get_default_screen (display); + GdkDisplay *display; + GListModel *monitors; + GdkMonitor *monitor; + GdkRectangle geometry; + GtkWindow *window; + GdkSurface *surface = NULL; + + display = gdk_display_get_default (); + if (!display) + return NULL; + + /* Get monitors list */ + monitors = gdk_display_get_monitors (display); + if (!monitors || g_list_model_get_n_items (monitors) == 0) + return NULL; + + /* Get the first monitor */ + monitor = g_list_model_get_item (monitors, 0); + if (!monitor) + return NULL; + + /* Get the monitor geometry */ + gdk_monitor_get_geometry (monitor, &geometry); + + /* Create a new toplevel window */ + window = GTK_WINDOW(gtk_window_new()); + if (window) { + GtkWidget *widget = GTK_WIDGET(window); + + /* Set window properties */ + gtk_window_set_default_size(window, geometry.width, geometry.height); + gtk_window_set_decorated(window, FALSE); + + /* Position the window using GTK4 methods */ + gtk_widget_set_size_request(widget, geometry.width, geometry.height); + + /* Show the window */ + gtk_widget_set_visible(widget, TRUE); + gtk_window_present(GTK_WINDOW(window)); + + /* Get the window's surface and position it using X11 */ + surface = gtk_native_get_surface(GTK_NATIVE(window)); + if (surface) { + Display *xdisplay = gdk_x11_display_get_xdisplay(gtk_widget_get_display(widget)); + Window xwindow = gdk_x11_surface_get_xid(surface); + XMoveWindow(xdisplay, xwindow, geometry.x, geometry.y); + } + + /* We keep a reference to the window */ + g_object_set_data_full(G_OBJECT(surface), "window", + window, + (GDestroyNotify)gtk_window_destroy); + } + + g_object_unref(monitor); + + return surface; +} - return gdk_screen_get_root_window (screen); +static void +_set_cursor_visibility (SugarCursorTracker *tracker, gboolean visible) +{ + GdkDisplay *display; + Display *xdisplay; + SugarCursorTrackerPrivate *priv; + GdkSurface *root_surface; + + priv = sugar_cursor_tracker_get_instance_private(tracker); + root_surface = priv->root_surface; + if (!root_surface) + return; + + display = gdk_surface_get_display (root_surface); + xdisplay = gdk_x11_display_get_xdisplay (display); + + gdk_x11_display_error_trap_push (display); + + if (visible == TRUE) { + if (priv->cursor_shown == FALSE) { + XFixesShowCursor (xdisplay, + gdk_x11_surface_get_xid (root_surface)); + priv->cursor_shown = TRUE; + } + } else { + if (priv->cursor_shown == TRUE) { + XFixesHideCursor (xdisplay, + gdk_x11_surface_get_xid (root_surface)); + priv->cursor_shown = FALSE; + } + } + + if (gdk_x11_display_error_trap_pop (display)) { + g_warning ("An error occurred trying to %s the cursor", + visible ? "show" : "hide"); + } } static void -_track_raw_events (GdkWindow *window) +handle_touch_begin (SugarCursorTracker *tracker) { - XIEventMask mask; - guchar *evmask; - GdkDisplay *display; - - evmask = g_new0 (guchar, XIMaskLen (XI_LASTEVENT)); - XISetMask (evmask, XI_RawTouchBegin); - XISetMask (evmask, XI_RawTouchEnd); - XISetMask (evmask, XI_RawTouchUpdate); - XISetMask (evmask, XI_RawMotion); - XISetMask (evmask, XI_RawButtonPress); - - mask.deviceid = XIAllMasterDevices; - mask.mask_len = sizeof (evmask); - mask.mask = evmask; - - display = gdk_window_get_display (window); - XISelectEvents (gdk_x11_display_get_xdisplay (display), - gdk_x11_window_get_xid (window), - &mask, 1); + _set_cursor_visibility (tracker, FALSE); } static void -_set_cursor_visibility (SugarCursorTracker *tracker, - gboolean visible) +handle_motion (SugarCursorTracker *tracker) { - GdkDisplay *display; - Display *xdisplay; - SugarCursorTrackerPrivate *priv; - - priv = tracker->priv; - display = gdk_display_get_default (); - xdisplay = GDK_DISPLAY_XDISPLAY (display); - - gdk_x11_display_error_trap_push (display); - - if (visible == TRUE) { - if (priv->cursor_shown == FALSE) { - XFixesShowCursor (xdisplay, GDK_WINDOW_XID (_get_default_root_window ())); - priv->cursor_shown = TRUE; - } - } - else { - if (priv->cursor_shown == TRUE) { - XFixesHideCursor (xdisplay, GDK_WINDOW_XID (_get_default_root_window ())); - priv->cursor_shown = False; - } - } - - if (gdk_x11_display_error_trap_pop (display)) { - g_warning ("An error occurred trying to %s the cursor", - FALSE ? "show" : "hide"); - } + _set_cursor_visibility (tracker, TRUE); } -static GdkFilterReturn -filter_function (GdkXEvent *xevent, - GdkEvent *gdkevent, - gpointer user_data) +static void +handle_button_press (SugarCursorTracker *tracker) { - XEvent *ev = xevent; - XIEvent *xiev; - SugarCursorTracker *tracker; - - if (ev->type != GenericEvent) - return GDK_FILTER_CONTINUE; - - tracker = user_data; - - xiev = ev->xcookie.data; - - switch (xiev->evtype) { - case XI_RawTouchBegin: - _set_cursor_visibility (tracker, FALSE); - return GDK_FILTER_REMOVE; - case XI_RawMotion: - _set_cursor_visibility (tracker, TRUE); - return GDK_FILTER_REMOVE; - case XI_RawButtonPress: - _set_cursor_visibility (tracker, TRUE); - return GDK_FILTER_REMOVE; - default: - return GDK_FILTER_CONTINUE; - } + _set_cursor_visibility (tracker, TRUE); } static void sugar_cursor_tracker_init (SugarCursorTracker *tracker) { - SugarCursorTrackerPrivate *priv; - tracker->priv = priv = sugar_cursor_tracker_get_instance_private (tracker); - priv->root_window = _get_default_root_window (); - priv->cursor_shown = True; - - tracker->priv = priv = sugar_cursor_tracker_get_instance_private (tracker); - priv->root_window = _get_default_root_window (); - _track_raw_events (priv->root_window); - gdk_window_add_filter (NULL, filter_function, tracker); + SugarCursorTrackerPrivate *priv; + GdkSeat *seat; + + priv = sugar_cursor_tracker_get_instance_private (tracker); + priv->root_surface = _get_default_root_surface (); + priv->cursor_shown = TRUE; + + /* Set up event controllers for GTK4 */ + seat = gdk_display_get_default_seat (gdk_display_get_default ()); + if (seat) { + GdkDevice *pointer = gdk_seat_get_pointer (seat); + if (pointer) { + GtkEventController *touch, *motion; + GtkGesture *gesture; + + /* Touch events */ + touch = gtk_event_controller_legacy_new (); + gtk_event_controller_set_propagation_phase (touch, GTK_PHASE_CAPTURE); + g_signal_connect_swapped (touch, "event", G_CALLBACK (handle_touch_begin), tracker); + + /* Motion events */ + motion = gtk_event_controller_motion_new (); + g_signal_connect_swapped (motion, "motion", G_CALLBACK (handle_motion), tracker); + + /* Button press events */ + gesture = gtk_gesture_click_new (); + g_signal_connect_swapped (gesture, "pressed", G_CALLBACK (handle_button_press), tracker); + } + } } SugarCursorTracker * sugar_cursor_tracker_new (void) { - return g_object_new (SUGAR_TYPE_CURSOR_TRACKER, NULL); + return g_object_new (SUGAR_TYPE_CURSOR_TRACKER, NULL); } diff --git a/src/sugar3/sugar-cursor-tracker.h b/src/sugar3/sugar-cursor-tracker.h index 58b9f04cf..2ffb0b6a0 100644 --- a/src/sugar3/sugar-cursor-tracker.h +++ b/src/sugar3/sugar-cursor-tracker.h @@ -23,39 +23,26 @@ #define __SUGAR_CURSOR_TRACKER_H__ #include -#include -#include "event-controller/sugar-event-controllers.h" G_BEGIN_DECLS typedef struct _SugarCursorTracker SugarCursorTracker; typedef struct _SugarCursorTrackerClass SugarCursorTrackerClass; -typedef struct _SugarCursorTrackerPrivate SugarCursorTrackerPrivate; - -#define SUGAR_TYPE_CURSOR_TRACKER (sugar_cursor_tracker_get_type()) -#define SUGAR_CURSOR_TRACKER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_CURSOR_TRACKER, SugarCursorTracker)) -#define SUGAR_CURSOR_TRACKER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_CURSOR_TRACKER, SugarCursorTrackerClass)) -#define SUGAR_IS_CURSOR_TRACKER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_CURSOR_TRACKER)) -#define SUGAR_IS_CURSOR_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_CURSOR_TRACKER)) -#define SUGAR_CURSOR_TRACKER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_CURSOR_TRACKER, SugarCursorTrackerClass)) - -struct _SugarCursorTracker { - GObject parent_instance; - SugarCursorTrackerPrivate *priv; -}; + +#define SUGAR_TYPE_CURSOR_TRACKER (sugar_cursor_tracker_get_type()) +G_DECLARE_DERIVABLE_TYPE(SugarCursorTracker, sugar_cursor_tracker, SUGAR, CURSOR_TRACKER, GObject) struct _SugarCursorTrackerClass { - GObjectClass parent_class; + GObjectClass parent_class; }; -struct _SugarCursorTrackerPrivate -{ - GdkWindow *root_window; - gboolean cursor_shown; -}; +/* Private structure */ +typedef struct { + GdkSurface *root_surface; + gboolean cursor_shown; +} SugarCursorTrackerPrivate; -GType sugar_cursor_tracker_get_type (void); -SugarCursorTracker * sugar_cursor_tracker_new (void); +SugarCursorTracker *sugar_cursor_tracker_new(void); G_END_DECLS diff --git a/src/sugar3/sugar-gesture-grabber.c b/src/sugar3/sugar-gesture-grabber.c index acee9caa3..93c11b971 100644 --- a/src/sugar3/sugar-gesture-grabber.c +++ b/src/sugar3/sugar-gesture-grabber.c @@ -18,8 +18,12 @@ * * Author: Carlos Garnacho */ +#include +#include +#include -#include +#include +#include #include #include "sugar-gesture-grabber.h" @@ -41,17 +45,23 @@ struct _ControllerData G_DEFINE_TYPE_WITH_PRIVATE (SugarGestureGrabber, sugar_gesture_grabber, G_TYPE_OBJECT) +static void handle_touch_event(GtkGesture *gesture, + GdkEventSequence *sequence, + gpointer user_data); + + static void _sugar_gesture_grabber_notify_touch (SugarGestureGrabber *grabber, - GdkDevice *device, - GdkEventSequence *sequence, - gboolean handled) + GdkDevice *device, + GdkEventSequence *sequence, + gboolean handled) { SugarGestureGrabberPrivate *priv = grabber->priv; GdkDisplay *display; guint i; - display = gdk_window_get_display (priv->root_window); + /* Use root_surface instead of root_window */ + display = gdk_surface_get_display (priv->root_surface); for (i = 0; i < priv->touches->len; i++) { TouchData *data; @@ -69,11 +79,10 @@ _sugar_gesture_grabber_notify_touch (SugarGestureGrabber *grabber, gdk_x11_display_error_trap_push (display); XIAllowTouchEvents (gdk_x11_display_get_xdisplay (display), - gdk_x11_device_get_id (data->device), - GPOINTER_TO_INT (data->sequence), - gdk_x11_window_get_xid (priv->root_window), - (handled) ? XIAcceptTouch : XIRejectTouch); - + gdk_x11_device_get_id (data->device), + GPOINTER_TO_INT (data->sequence), + gdk_x11_surface_get_xid (priv->root_surface), + (handled) ? XIAcceptTouch : XIRejectTouch); gdk_x11_display_error_trap_pop_ignored (display); data->consumed = TRUE; } @@ -81,8 +90,8 @@ _sugar_gesture_grabber_notify_touch (SugarGestureGrabber *grabber, static void _sugar_gesture_grabber_add_touch (SugarGestureGrabber *grabber, - GdkDevice *device, - GdkEventSequence *sequence) + GdkDevice *device, + GdkEventSequence *sequence) { SugarGestureGrabberPrivate *priv = grabber->priv; TouchData data; @@ -95,8 +104,8 @@ _sugar_gesture_grabber_add_touch (SugarGestureGrabber *grabber, static void _sugar_gesture_grabber_remove_touch (SugarGestureGrabber *grabber, - GdkDevice *device, - GdkEventSequence *sequence) + GdkDevice *device, + GdkEventSequence *sequence) { SugarGestureGrabberPrivate *priv = grabber->priv; guint i; @@ -107,7 +116,7 @@ _sugar_gesture_grabber_remove_touch (SugarGestureGrabber *grabber, data = &g_array_index (priv->touches, TouchData, i); if (data->device == device && - data->sequence == sequence) { + data->sequence == sequence) { g_array_remove_index_fast (priv->touches, i); break; } @@ -137,7 +146,7 @@ sugar_gesture_grabber_finalize (GObject *object) } _sugar_gesture_grabber_notify_touch (SUGAR_GESTURE_GRABBER (object), - NULL, NULL, FALSE); + NULL, NULL, FALSE); for (i = 0; i < priv->controllers->len; i++) { ControllerData *data; @@ -160,181 +169,200 @@ sugar_gesture_grabber_class_init (SugarGestureGrabberClass *klass) object_class->finalize = sugar_gesture_grabber_finalize; } +/* Update _grab_touch_events to accept a GdkSurface instead of GdkWindow */ static void -_grab_touch_events (GdkWindow *window) +_grab_touch_events (GdkSurface *surface) { - XIGrabModifiers mods = { 1 }; - unsigned char mask[4] = { 0 }; - GdkDisplay *display; - XIEventMask evmask; - - XISetMask (mask, XI_TouchBegin); - XISetMask (mask, XI_TouchUpdate); - XISetMask (mask, XI_TouchEnd); - - evmask.deviceid = XIAllMasterDevices; - evmask.mask_len = sizeof (mask); - evmask.mask = mask; - - mods.modifiers = XIAnyModifier; - display = gdk_window_get_display (window); - - XIGrabTouchBegin (gdk_x11_display_get_xdisplay (display), - XIAllMasterDevices, - gdk_x11_window_get_xid (window), - XINoOwnerEvents, &evmask, 1, &mods); + XIGrabModifiers mods = { 1 }; + unsigned char mask[4] = { 0 }; + GdkDisplay *display; + XIEventMask evmask; + + XISetMask (mask, XI_TouchBegin); + XISetMask (mask, XI_TouchUpdate); + XISetMask (mask, XI_TouchEnd); + + evmask.deviceid = XIAllMasterDevices; + evmask.mask_len = sizeof (mask); + evmask.mask = mask; + + mods.modifiers = XIAnyModifier; + display = gdk_surface_get_display (surface); + + XIGrabTouchBegin (gdk_x11_display_get_xdisplay (display), + XIAllMasterDevices, + gdk_x11_surface_get_xid (surface), + XINoOwnerEvents, &evmask, 1, &mods); } -static GdkWindow * -_get_default_root_window (void) +static GdkSurface * +_get_default_root_surface (void) { - GdkDisplay *display; - GdkScreen *screen; - - display = gdk_display_get_default (); - screen = gdk_display_get_default_screen (display); - - return gdk_screen_get_root_window (screen); + GdkDisplay *display; + GdkSurface *surface = NULL; + + display = gdk_display_get_default(); + if (!display) + return NULL; + + /* Get the default seat for the display */ + GdkSeat *seat = gdk_display_get_default_seat(display); + if (!seat) + return NULL; + + /* Create fullscreen window on the primary monitor */ + GtkWindow *window = GTK_WINDOW(gtk_window_new()); + if (window) { + /* Make it fullscreen */ + gtk_window_fullscreen(window); + gtk_window_set_decorated(window, FALSE); + + /* Show the window */ + gtk_widget_set_visible(GTK_WIDGET(window), TRUE); + gtk_window_present(window); + + /* Get the native surface */ + surface = gtk_native_get_surface(GTK_NATIVE(window)); + } + + return surface; } static gboolean _sugar_gesture_grabber_run_controllers (SugarGestureGrabber *grabber, - GdkEvent *event) + GdkEvent *event) { - SugarGestureGrabberPrivate *priv = grabber->priv; - gboolean handled = FALSE; - guint i; - - for (i = 0; i < priv->controllers->len; i++) { - ControllerData *data; - - data = &g_array_index (priv->controllers, ControllerData, i); - - if (event->type == GDK_TOUCH_BEGIN && - (event->touch.x_root < data->rect.x || - event->touch.x_root > data->rect.x + data->rect.width || - event->touch.y_root < data->rect.y || - event->touch.y_root > data->rect.y + data->rect.height)) - continue; - - handled = sugar_event_controller_handle_event (data->controller, - event); + SugarGestureGrabberPrivate *priv = grabber->priv; + gboolean handled = FALSE; + double x, y; + + if (!gdk_event_get_position(event, &x, &y)) + return FALSE; + + GdkSurface *surface = gdk_event_get_surface(event); + if (!surface) + return FALSE; + + /* Get surface position relative to root */ + graphene_point_t pos = GRAPHENE_POINT_INIT (0, 0); + + /* Get the surface's position in the root coordinate system */ + GdkDisplay *display = gdk_surface_get_display(surface); + if (display) { + GdkMonitor *monitor = gdk_display_get_monitor_at_surface(display, surface); + if (monitor) { + GdkRectangle geometry; + gdk_monitor_get_geometry(monitor, &geometry); + pos.x = geometry.x; + pos.y = geometry.y; + } + } - if (handled) { - guint state; + double x_root = x + pos.x; + double y_root = y + pos.y; - state = sugar_event_controller_get_state (data->controller); + for (guint i = 0; i < priv->controllers->len; i++) { + ControllerData *data = &g_array_index(priv->controllers, ControllerData, i); - if (state == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) { - _sugar_gesture_grabber_notify_touch (grabber, - event->touch.device, - event->touch.sequence, - TRUE); - } - } - } + if (gdk_event_get_event_type(event) == GDK_TOUCH_BEGIN) { + if (x_root < data->rect.x || + x_root > data->rect.x + data->rect.width || + y_root < data->rect.y || + y_root > data->rect.y + data->rect.height) + continue; + } - return handled; -} + handled = sugar_event_controller_handle_event(data->controller, event); -static GdkFilterReturn -filter_function (GdkXEvent *xevent, - GdkEvent *gdkevent, - gpointer user_data) -{ - XGenericEventCookie *xge = xevent; - SugarGestureGrabber *grabber; - SugarGestureGrabberPrivate *priv; - gboolean handled = FALSE; - XIDeviceEvent *ev; - GdkDisplay *display; - GdkEvent *event; - - if (xge->type != GenericEvent) - return GDK_FILTER_CONTINUE; - - grabber = user_data; - priv = grabber->priv; - - display = gdk_window_get_display (priv->root_window); - ev = (XIDeviceEvent *) xge->data; - - switch (ev->evtype) { - case XI_TouchBegin: - event = gdk_event_new (GDK_TOUCH_BEGIN); - break; - case XI_TouchEnd: - event = gdk_event_new (GDK_TOUCH_END); - break; - case XI_TouchUpdate: - event = gdk_event_new (GDK_TOUCH_UPDATE); - break; - default: - return GDK_FILTER_CONTINUE; + if (handled && + sugar_event_controller_get_state(data->controller) == SUGAR_EVENT_CONTROLLER_STATE_RECOGNIZED) { + _sugar_gesture_grabber_notify_touch(grabber, + gdk_event_get_device(event), + gdk_event_get_event_sequence(event), + TRUE); } + } - if (ev->event != gdk_x11_window_get_xid (priv->root_window)) - return GDK_FILTER_CONTINUE; - - event->touch.window = g_object_ref (priv->root_window); - event->touch.time = ev->time; - event->touch.x = ev->event_x; - event->touch.y = ev->event_y; - event->touch.x_root = ev->root_x; - event->touch.y_root = ev->root_y; - event->touch.sequence = GINT_TO_POINTER (ev->detail); - event->touch.emulating_pointer = (ev->flags & XITouchEmulatingPointer); - - handled = _sugar_gesture_grabber_run_controllers (grabber, event); - - if (!handled) { - gdk_x11_display_error_trap_push (display); - XIAllowTouchEvents (gdk_x11_display_get_xdisplay (display), - ev->deviceid, ev->detail, - gdk_x11_window_get_xid (priv->root_window), - XIRejectTouch); - gdk_x11_display_error_trap_pop_ignored (display); - } else if (event->type == GDK_TOUCH_BEGIN) { - _sugar_gesture_grabber_add_touch (grabber, - event->touch.device, - event->touch.sequence); - } else if (event->type == GDK_TOUCH_END) { - _sugar_gesture_grabber_notify_touch (grabber, - event->touch.device, - event->touch.sequence, - FALSE); - _sugar_gesture_grabber_remove_touch (grabber, - event->touch.device, - event->touch.sequence); - } + return handled; +} - if (handled) { - if (priv->cancel_timeout_id) - g_source_remove (priv->cancel_timeout_id); - priv->cancel_timeout_id = - gdk_threads_add_timeout (150, - (GSourceFunc) _sugar_gesture_grabber_cancel_timeout, - grabber); - } +static void +gesture_controller_event_cb (GtkEventController *controller, + GdkEvent *event, + SugarGestureGrabber *grabber) +{ + SugarGestureGrabberPrivate *priv = grabber->priv; + gboolean handled = FALSE; + + if (!priv->root_surface) + return; + + handled = _sugar_gesture_grabber_run_controllers(grabber, event); + + if (!handled) { + GdkDevice *device = gdk_event_get_device(event); + GdkEventSequence *sequence = gdk_event_get_event_sequence(event); + _sugar_gesture_grabber_notify_touch(grabber, device, sequence, FALSE); + } else if (gdk_event_get_event_type(event) == GDK_TOUCH_BEGIN) { + GdkDevice *device = gdk_event_get_device(event); + GdkEventSequence *sequence = gdk_event_get_event_sequence(event); + _sugar_gesture_grabber_add_touch(grabber, device, sequence); + } else if (gdk_event_get_event_type(event) == GDK_TOUCH_END) { + GdkDevice *device = gdk_event_get_device(event); + GdkEventSequence *sequence = gdk_event_get_event_sequence(event); + _sugar_gesture_grabber_notify_touch(grabber, device, sequence, FALSE); + _sugar_gesture_grabber_remove_touch(grabber, device, sequence); + } +} - gdk_event_free (event); - return GDK_FILTER_REMOVE; +static void +handle_touch_event (GtkGesture *gesture, + GdkEventSequence *sequence, + gpointer user_data) +{ + SugarGestureGrabber *grabber = SUGAR_GESTURE_GRABBER(user_data); + GdkEvent *event; + + event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(gesture)); + if (!event) + return; + + gboolean handled = _sugar_gesture_grabber_run_controllers(grabber, event); + + if (!handled) { + GdkDevice *device = gdk_event_get_device(event); + GdkEventSequence *seq = gdk_event_get_event_sequence(event); + _sugar_gesture_grabber_notify_touch(grabber, device, seq, FALSE); + } } static void sugar_gesture_grabber_init (SugarGestureGrabber *grabber) { - SugarGestureGrabberPrivate *priv; - - grabber->priv = priv = sugar_gesture_grabber_get_instance_private (grabber); - priv->root_window = _get_default_root_window (); - _grab_touch_events (priv->root_window); - gdk_window_add_filter (NULL, filter_function, grabber); + SugarGestureGrabberPrivate *priv; + + grabber->priv = priv = sugar_gesture_grabber_get_instance_private(grabber); + priv->root_surface = _get_default_root_surface(); + + if (priv->root_surface) { + GtkWidget *widget = gtk_widget_get_ancestor(GTK_WIDGET(priv->root_surface), + GTK_TYPE_WINDOW); + if (widget) { + /* Add touch gesture controller */ + GtkGesture *gesture = gtk_gesture_click_new(); + gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(gesture), TRUE); + gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(gesture)); + + /* Connect to touch event */ + g_signal_connect(gesture, "begin", + G_CALLBACK(handle_touch_event), grabber); + } + } - priv->touches = g_array_new (FALSE, FALSE, sizeof (TouchData)); - priv->controllers = g_array_new (FALSE, FALSE, sizeof (ControllerData)); + priv->touches = g_array_new(FALSE, FALSE, sizeof(TouchData)); + priv->controllers = g_array_new(FALSE, FALSE, sizeof(ControllerData)); } SugarGestureGrabber * @@ -371,8 +399,8 @@ _sugar_gesture_grabber_find_controller (SugarGestureGrabber *grabber, void sugar_gesture_grabber_add (SugarGestureGrabber *grabber, - SugarEventController *controller, - const GdkRectangle *rect) + SugarEventController *controller, + const GdkRectangle *rect) { SugarGestureGrabberPrivate *priv; ControllerData data; @@ -382,7 +410,7 @@ sugar_gesture_grabber_add (SugarGestureGrabber *grabber, if (_sugar_gesture_grabber_find_controller (grabber, controller, NULL)) { g_warning ("Controller is already on the gesture grabber" - " list. Controllers can only be added once."); + " list. Controllers can only be added once."); return; } @@ -395,7 +423,7 @@ sugar_gesture_grabber_add (SugarGestureGrabber *grabber, void sugar_gesture_grabber_remove (SugarGestureGrabber *grabber, - SugarEventController *controller) + SugarEventController *controller) { SugarGestureGrabberPrivate *priv; ControllerData *data; diff --git a/src/sugar3/sugar-gesture-grabber.h b/src/sugar3/sugar-gesture-grabber.h index 74be86383..8225c52f7 100644 --- a/src/sugar3/sugar-gesture-grabber.h +++ b/src/sugar3/sugar-gesture-grabber.h @@ -23,7 +23,7 @@ #define __SUGAR_GESTURE_GRABBER_H__ #include -#include +#include #include "event-controller/sugar-event-controllers.h" G_BEGIN_DECLS @@ -34,7 +34,7 @@ typedef struct _SugarGestureGrabberPrivate SugarGestureGrabberPrivate; #define SUGAR_TYPE_GESTURE_GRABBER (sugar_gesture_grabber_get_type()) #define SUGAR_GESTURE_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabber)) -#define SUGAR_GESTURE_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabberClass)) +#define SUGAR_GESTURE_GRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabberClass)) #define SUGAR_IS_GESTURE_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_GESTURE_GRABBER)) #define SUGAR_IS_GESTURE_GRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_GESTURE_GRABBER)) #define SUGAR_GESTURE_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_GESTURE_GRABBER, SugarGestureGrabberClass)) @@ -50,19 +50,20 @@ struct _SugarGestureGrabberClass { struct _SugarGestureGrabberPrivate { - GdkWindow *root_window; - GArray *controllers; - GArray *touches; - guint cancel_timeout_id; + /* Replace the old root window with a root surface */ + GdkSurface *root_surface; + GArray *controllers; + GArray *touches; + guint cancel_timeout_id; }; GType sugar_gesture_grabber_get_type (void); SugarGestureGrabber * sugar_gesture_grabber_new (void); void sugar_gesture_grabber_add (SugarGestureGrabber *grabber, - SugarEventController *controller, - const GdkRectangle *rect); + SugarEventController *controller, + const GdkRectangle *rect); void sugar_gesture_grabber_remove (SugarGestureGrabber *grabber, - SugarEventController *controller); + SugarEventController *controller); G_END_DECLS diff --git a/src/sugar3/sugar-key-grabber.c b/src/sugar3/sugar-key-grabber.c index a4c00968f..6cb089dea 100644 --- a/src/sugar3/sugar-key-grabber.c +++ b/src/sugar3/sugar-key-grabber.c @@ -21,32 +21,33 @@ #include #include #include -#include +#include #include "sugar-key-grabber.h" #include "eggaccelerators.h" #include "sugar-marshal.h" /* we exclude shift, GDK_CONTROL_MASK and GDK_MOD1_MASK since we know what - these modifiers mean - these are the mods whose combinations are bound by the keygrabbing code */ +these modifiers mean +these are the mods whose combinations are bound by the keygrabbing code */ #define IGNORED_MODS (0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | \ - GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK) + GDK_ALT_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK) + /* these are the ones we actually use for global keys, we always only check - * for these set */ -#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK) +* for these set */ +#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_ALT_MASK) enum { - KEY_PRESSED, - KEY_RELEASED, - N_SIGNALS + KEY_PRESSED, + KEY_RELEASED, + N_SIGNALS }; typedef struct { - char *key; - guint keysym; - guint state; - guint keycode; + char *key; + guint keysym; + guint state; + guint keycode; } Key; G_DEFINE_TYPE(SugarKeyGrabber, sugar_key_grabber, G_TYPE_OBJECT) @@ -56,260 +57,254 @@ static guint signals[N_SIGNALS]; static void free_key_info(Key *key_info) { - g_free(key_info->key); - g_free(key_info); + g_free(key_info->key); + g_free(key_info); } static void -sugar_key_grabber_dispose (GObject *object) +sugar_key_grabber_dispose(GObject *object) { - SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(object); + SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(object); + + if (grabber->keys) { + g_list_foreach(grabber->keys, (GFunc)free_key_info, NULL); + g_list_free(grabber->keys); + grabber->keys = NULL; + } - if (grabber->keys) { - g_list_foreach(grabber->keys, (GFunc)free_key_info, NULL); - g_list_free(grabber->keys); - grabber->keys = NULL; - } + G_OBJECT_CLASS(sugar_key_grabber_parent_class)->dispose(object); } static void sugar_key_grabber_class_init(SugarKeyGrabberClass *grabber_class) { - GObjectClass *g_object_class = G_OBJECT_CLASS (grabber_class); - - g_object_class->dispose = sugar_key_grabber_dispose; - - signals[KEY_PRESSED] = g_signal_new ("key-pressed", - G_TYPE_FROM_CLASS (grabber_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (SugarKeyGrabberClass, key_pressed), - NULL, NULL, - sugar_marshal_BOOLEAN__UINT_UINT_UINT, - G_TYPE_BOOLEAN, 3, - G_TYPE_UINT, - G_TYPE_UINT, - G_TYPE_UINT); - signals[KEY_RELEASED] = g_signal_new ("key-released", - G_TYPE_FROM_CLASS (grabber_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (SugarKeyGrabberClass, key_released), - NULL, NULL, - sugar_marshal_BOOLEAN__UINT_UINT_UINT, - G_TYPE_BOOLEAN, 3, - G_TYPE_UINT, - G_TYPE_UINT, - G_TYPE_UINT); + GObjectClass *g_object_class = G_OBJECT_CLASS(grabber_class); + + g_object_class->dispose = sugar_key_grabber_dispose; + + signals[KEY_PRESSED] = g_signal_new("key-pressed", + G_TYPE_FROM_CLASS(grabber_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(SugarKeyGrabberClass, key_pressed), + NULL, NULL, + sugar_marshal_BOOLEAN__UINT_UINT_UINT, + G_TYPE_BOOLEAN, 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); + + signals[KEY_RELEASED] = g_signal_new("key-released", + G_TYPE_FROM_CLASS(grabber_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(SugarKeyGrabberClass, key_released), + NULL, NULL, + sugar_marshal_BOOLEAN__UINT_UINT_UINT, + G_TYPE_BOOLEAN, 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); } char * sugar_key_grabber_get_key(SugarKeyGrabber *grabber, guint keycode, guint state) { - GList *l; + GList *l; - for (l = grabber->keys; l != NULL; l = l->next) { - Key *keyinfo = (Key *)l->data; - if ((keyinfo->keycode == keycode) && - ((state & USED_MODS) == keyinfo->state)) { - return g_strdup(keyinfo->key); - } - } + for (l = grabber->keys; l != NULL; l = l->next) { + Key *keyinfo = (Key *)l->data; + if ((keyinfo->keycode == keycode) && + ((state & USED_MODS) == keyinfo->state)) { + return g_strdup(keyinfo->key); + } + } - return NULL; + return NULL; } -static GdkFilterReturn -filter_events(GdkXEvent *xevent, GdkEvent *event, gpointer data) +static gboolean +key_event_handler(GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer user_data) { - SugarKeyGrabber *grabber = (SugarKeyGrabber *)data; - XEvent *xev = (XEvent *)xevent; - - if (xev->type == KeyRelease) { - int return_value; - g_signal_emit (grabber, signals[KEY_RELEASED], 0, xev->xkey.keycode, - xev->xkey.state, xev->xkey.time, &return_value); - if(return_value) - return GDK_FILTER_REMOVE; - } - - if (xev->type == KeyPress) { - int return_value; - g_signal_emit (grabber, signals[KEY_PRESSED], 0, xev->xkey.keycode, - xev->xkey.state, xev->xkey.time, &return_value); - if(return_value) - return GDK_FILTER_REMOVE; - } - - if (xev->type == GenericEvent) { - XIDeviceEvent *ev; - int return_value = FALSE; - - ev = (XIDeviceEvent *) ((XGenericEventCookie *) xev)->data; - - if (ev->evtype == XI_KeyPress) { - g_signal_emit (grabber, signals[KEY_PRESSED], 0, - ev->detail, ev->mods.effective, ev->time, &return_value); - } else if (ev->evtype == XI_KeyRelease) { - g_signal_emit (grabber, signals[KEY_RELEASED], 0, - ev->detail, ev->mods.effective, ev->time, &return_value); - } - - if (return_value) - return GDK_FILTER_REMOVE; - } - - - return GDK_FILTER_CONTINUE; + SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(user_data); + gboolean handled = FALSE; + guint32 time = g_get_monotonic_time() / 1000; // Convert to milliseconds + + // Determine if this is a press or release + GdkEvent *event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); + if (gdk_event_get_event_type(event) == GDK_KEY_PRESS) { + g_signal_emit(grabber, signals[KEY_PRESSED], 0, + keycode, state, time, &handled); + } else if (gdk_event_get_event_type(event) == GDK_KEY_RELEASE) { + g_signal_emit(grabber, signals[KEY_RELEASED], 0, + keycode, state, time, &handled); + } + + return handled; } static void sugar_key_grabber_init(SugarKeyGrabber *grabber) { - GdkScreen *screen; - - screen = gdk_screen_get_default(); - grabber->root = gdk_screen_get_root_window(screen); - grabber->keys = NULL; + GdkDisplay *display; + + display = gdk_display_get_default(); + if (!display) + return; + + // Create a fullscreen window to act as root + GtkWindow *window = GTK_WINDOW(gtk_window_new()); + if (window) { + gtk_window_fullscreen(window); + gtk_window_set_decorated(window, FALSE); + gtk_widget_set_visible(GTK_WIDGET(window), TRUE); + + // Add key event controller + GtkEventController *key_controller = gtk_event_controller_key_new(); + g_signal_connect(key_controller, "key-pressed", + G_CALLBACK(key_event_handler), grabber); + g_signal_connect(key_controller, "key-released", + G_CALLBACK(key_event_handler), grabber); + gtk_widget_add_controller(GTK_WIDGET(window), key_controller); + + grabber->root = gtk_native_get_surface(GTK_NATIVE(window)); + } - gdk_window_add_filter(grabber->root, filter_events, grabber); + grabber->keys = NULL; } -/* grab_key and grab_key_real are from - * gnome-control-center/gnome-settings-daemon/gnome-settings-multimedia-keys.c - */ - static void -grab_key_real (Key *key, GdkWindow *root, gboolean grab, int result) +grab_key_real(Key *key, GdkSurface *root, gboolean grab, int result) { - Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default ()); - if (grab) - XGrabKey (display, key->keycode, (result | key->state), - GDK_WINDOW_XID (root), True, GrabModeAsync, GrabModeAsync); - else - XUngrabKey(display, key->keycode, (result | key->state), - GDK_WINDOW_XID (root)); + Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + if (grab) + XGrabKey(display, key->keycode, (result | key->state), + gdk_x11_surface_get_xid(root), True, + GrabModeAsync, GrabModeAsync); + else + XUngrabKey(display, key->keycode, (result | key->state), + gdk_x11_surface_get_xid(root)); } #define N_BITS 32 static void -grab_key (SugarKeyGrabber *grabber, Key *key, gboolean grab) +grab_key(SugarKeyGrabber *grabber, Key *key, gboolean grab) { - int indexes[N_BITS];/*indexes of bits we need to flip*/ - int i, bit, bits_set_cnt; - int uppervalue; - guint mask_to_traverse = IGNORED_MODS & ~key->state & GDK_MODIFIER_MASK; - - bit = 0; - for (i = 0; i < N_BITS; i++) { - if (mask_to_traverse & (1<state & GDK_MODIFIER_MASK; + + bit = 0; + for (i = 0; i < N_BITS; i++) { + if (mask_to_traverse & (1<root, grab, result); + for (j = 0; j < bits_set_cnt; j++) { + if (i & (1<root, grab, result); + } } -/** - * sugar_key_grabber_grab_keys: - * @grabber: a #SugarKeyGrabber - * @keys: (array length=n_elements) (element-type utf8): array of - * keys the grabber will listen to - * @n_elements: number of elements in @keys. - * - * Pass to the key grabber the keys it should listen to. - **/ void sugar_key_grabber_grab_keys(SugarKeyGrabber *grabber, - const gchar *keys[], - gint n_elements) + const gchar *keys[], + gint n_elements) { gint i; - const char *key; Key *keyinfo = NULL; gint min_keycodes, max_keycodes; + GdkDisplay *display = gdk_display_get_default(); - XDisplayKeycodes(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), - &min_keycodes, &max_keycodes); + XDisplayKeycodes(GDK_DISPLAY_XDISPLAY(display), + &min_keycodes, &max_keycodes); - for (i = 0; i < n_elements; i++){ - keyinfo = g_new0 (Key, 1); - keyinfo->key = g_strdup(keys[i]); + for (i = 0; i < n_elements; i++) { + keyinfo = g_new0(Key, 1); + keyinfo->key = g_strdup(keys[i]); - if (!egg_accelerator_parse_virtual (keys[i], &keyinfo->keysym, - &keyinfo->keycode, - &keyinfo->state)) { - g_warning ("Invalid key specified: %s", keys[i]); + if (!egg_accelerator_parse_virtual(keys[i], &keyinfo->keysym, + &keyinfo->keycode, + &keyinfo->state)) { + g_warning("Invalid key specified: %s", keys[i]); + free_key_info(keyinfo); continue; } - if (keyinfo->keycode < min_keycodes || keyinfo->keycode > max_keycodes) { - g_warning ("Keycode out of bounds: %d for key %s", keyinfo->keycode, keys[i]); + if (keyinfo->keycode < min_keycodes || + keyinfo->keycode > max_keycodes) { + g_warning("Keycode out of bounds: %d for key %s", + keyinfo->keycode, keys[i]); + free_key_info(keyinfo); continue; } - gdk_error_trap_push(); - + gdk_x11_display_error_trap_push(display); grab_key(grabber, keyinfo, TRUE); - - gdk_flush(); - gint error_code = gdk_error_trap_pop (); - if(!error_code) + gdk_display_sync(display); + + gint error_code = gdk_x11_display_error_trap_pop(display); + + if (!error_code) grabber->keys = g_list_append(grabber->keys, keyinfo); - else if(error_code == BadAccess) - g_warning ("Grab failed, another application may already have access to key '%s'", keys[i]); - else if(error_code == BadValue) - g_warning ("Grab failed, invalid key %s specified. keysym: %u keycode: %u state: %u", - keys[i], keyinfo->keysym, keyinfo->keycode, keyinfo->state); + else if (error_code == BadAccess) + g_warning("Grab failed, another application may already have access to key '%s'", + keys[i]); + else if (error_code == BadValue) + g_warning("Grab failed, invalid key %s specified. keysym: %u keycode: %u state: %u", + keys[i], keyinfo->keysym, keyinfo->keycode, keyinfo->state); else - g_warning ("Grab failed for key '%s' for unknown reason '%d'", keys[i], error_code); - + g_warning("Grab failed for key '%s' for unknown reason '%d'", + keys[i], error_code); } } gboolean sugar_key_grabber_is_modifier(SugarKeyGrabber *grabber, guint keycode, guint mask) { - Display *xdisplay; - XModifierKeymap *modmap; - gint start, end, i, mod_index; - gboolean is_modifier = FALSE; - - xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default ()); - - modmap = XGetModifierMapping(xdisplay); - - if (mask != -1) { - mod_index = 0; - mask = mask >> 1; - while (mask != 0) { - mask = mask >> 1; - mod_index += 1; - } - start = mod_index * modmap->max_keypermod; - end = (mod_index + 1) * modmap->max_keypermod; - } else { - start = 0; - end = 8 * modmap->max_keypermod; - } - - for (i = start; i < end; i++) { - if (keycode == modmap->modifiermap[i]) { - is_modifier = TRUE; - break; - } - } - - XFreeModifiermap (modmap); - - return is_modifier; + Display *xdisplay; + XModifierKeymap *modmap; + gint start, end, i, mod_index; + gboolean is_modifier = FALSE; + + xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + + modmap = XGetModifierMapping(xdisplay); + + if (mask != -1) { + mod_index = 0; + mask = mask >> 1; + while (mask != 0) { + mask = mask >> 1; + mod_index += 1; + } + start = mod_index * modmap->max_keypermod; + end = (mod_index + 1) * modmap->max_keypermod; + } else { + start = 0; + end = 8 * modmap->max_keypermod; + } + + for (i = start; i < end; i++) { + if (keycode == modmap->modifiermap[i]) { + is_modifier = TRUE; + break; + } + } + + XFreeModifiermap(modmap); + + return is_modifier; } diff --git a/src/sugar3/sugar-key-grabber.h b/src/sugar3/sugar-key-grabber.h index 2afebd43d..b4938d206 100644 --- a/src/sugar3/sugar-key-grabber.h +++ b/src/sugar3/sugar-key-grabber.h @@ -29,17 +29,18 @@ typedef struct _SugarKeyGrabber SugarKeyGrabber; typedef struct _SugarKeyGrabberClass SugarKeyGrabberClass; typedef struct _SugarKeyGrabberPrivate SugarKeyGrabberPrivate; -#define SUGAR_TYPE_KEY_GRABBER (sugar_key_grabber_get_type()) -#define SUGAR_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabber)) -#define SUGAR_KEY_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass)) -#define SUGAR_IS_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_KEY_GRABBER)) -#define SUGAR_IS_KEYGRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_KEY_GRABBER)) -#define SUGAR_KEY_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass)) +#define SUGAR_TYPE_KEY_GRABBER (sugar_key_grabber_get_type()) +#define SUGAR_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabber)) +#define SUGAR_KEY_GRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass)) +#define SUGAR_IS_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_KEY_GRABBER)) +#define SUGAR_IS_KEYGRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_KEY_GRABBER)) +#define SUGAR_KEY_GRABBER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass)) struct _SugarKeyGrabber { GObject base_instance; - GdkWindow *root; + /* Updated: use GdkSurface instead of GdkWindow for GTK4 */ + GdkSurface *root; GList *keys; }; @@ -47,23 +48,23 @@ struct _SugarKeyGrabberClass { GObjectClass base_class; gboolean (* key_pressed) (SugarKeyGrabber *grabber, - guint keycode, - guint state); + guint keycode, + guint state); gboolean (* key_released) (SugarKeyGrabber *grabber, - guint keycode, - guint state); + guint keycode, + guint state); }; GType sugar_key_grabber_get_type (void); void sugar_key_grabber_grab_keys (SugarKeyGrabber *grabber, - const gchar *keys[], - gint n_elements); + const gchar *keys[], + gint n_elements); char *sugar_key_grabber_get_key (SugarKeyGrabber *grabber, - guint keycode, - guint state); + guint keycode, + guint state); gboolean sugar_key_grabber_is_modifier (SugarKeyGrabber *grabber, - guint keycode, - guint mask); + guint keycode, + guint mask); G_END_DECLS diff --git a/src/sugar3/sugar-wm.c b/src/sugar3/sugar-wm.c index f13cfb661..486b94d58 100644 --- a/src/sugar3/sugar-wm.c +++ b/src/sugar3/sugar-wm.c @@ -19,8 +19,9 @@ #include #include -#include +#include +#include #include "sugar-wm.h" #define MAX_PROPERTY_LEN 1024 @@ -36,12 +37,19 @@ get_property(Window window, const char *name) unsigned long bytes_after; unsigned char *data; - display = gdk_x11_get_default_xdisplay(); + GdkDisplay *gdk_display = gdk_display_get_default(); + if (!gdk_display) + return NULL; + + display = gdk_x11_display_get_xdisplay(gdk_display); + if (!display) + return NULL; + property = XInternAtom(display, name, False); if (XGetWindowProperty(display, window, property, 0, MAX_PROPERTY_LEN, - False, XA_STRING, &actual_type, &actual_format, - &n_items, &bytes_after, &data) != Success) { + False, XA_STRING, &actual_type, &actual_format, + &n_items, &bytes_after, &data) != Success) { return NULL; } @@ -54,11 +62,18 @@ set_property(Window window, const char *name, const char *value) Display *display; Atom property; - display = gdk_x11_get_default_xdisplay(); + GdkDisplay *gdk_display = gdk_display_get_default(); + if (!gdk_display) + return; + + display = gdk_x11_display_get_xdisplay(gdk_display); + if (!display) + return; + property = XInternAtom(display, name, False); - XChangeProperty (display, window, property, XA_STRING, 8, PropModeReplace, - (unsigned char *)value, strlen(value)); + XChangeProperty(display, window, property, XA_STRING, 8, PropModeReplace, + (unsigned char *)value, strlen(value)); } char *