Compare commits

...

32 Commits
0.97.6 ... main

Author SHA1 Message Date
Juan Pablo Ugarte
8ce0ccf64b
CmbSourceView: bind buffer style with CmbWindow::source-style 2025-08-11 21:50:42 -04:00
Juan Pablo Ugarte
27f3054601
CmbWindow: add source_style property
Add property to change GtkSourceView style
2025-08-11 21:50:00 -04:00
Juan Pablo Ugarte
2b5cbff98c
CmbWindow: re enable dark mode logo 2025-08-09 08:59:41 -04:00
Juan Pablo Ugarte
b86df04dbf
Rolling 0.97.7 2025-08-08 21:33:19 -04:00
Juan Pablo Ugarte
dcac9ba37c
CmbDB: fix workspace default 2025-08-08 21:31:53 -04:00
Juan Pablo Ugarte
a903a27d60
MrgAdwViewStack: add basic support
Derive MrgControllers from GtkStack and GtkStackPage
2025-08-08 19:54:18 -04:00
Juan Pablo Ugarte
daeabaf319
CmbObjectEditor: do not include ID for GtkExpression 2025-08-08 19:54:18 -04:00
Juan Pablo Ugarte
0a3131f16a
mrg_webkit: remove WebKitWebView workspace proxy types
Webkit works fine under Casilda compositor.
2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
fd53a459c1
CmbProperty: add binding expression support
Refactor all setters and getters
2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
4753bf8233
MrgApplication: catch exception in namespace version checking 2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
abb7e2ffb6
CmbPropertyLabel: use new CmbBindingPopover widget
This enables basic support for gtk expression property binding

Closes issue #257 "Add GtkExpression support"
2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
6fc3cbd0dd
Update build system with new types 2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
e56e745b92
cmb_create_editor() use GtkSuggestionEntry for gtype properties 2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
b27891ba79
CmbTypeChooserWidget: support GtkExpression
Special case GtkExpression derived types when used to create an inline object
2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
d0da2f0016
CmbBindingPopover: implement binding popover with Cambalache
- Add basic support for gtk expression
2025-08-08 19:46:31 -04:00
Juan Pablo Ugarte
fee3885bf8
CmbSuggestionEntry: new control widget 2025-08-07 18:49:06 -04:00
Juan Pablo Ugarte
83d50f05ce
CmbPropertyChooser: move class to its own file 2025-08-07 18:49:06 -04:00
Juan Pablo Ugarte
f85ab89f75
CmbObject: add binding_expression_property_id property 2025-08-07 18:49:06 -04:00
Juan Pablo Ugarte
e5066582cd
CmbProject: add gtk expression support 2025-08-07 18:49:06 -04:00
Juan Pablo Ugarte
3ce5f4f13f
CmbDB: add gtk expression support 2025-08-07 18:49:06 -04:00
Juan Pablo Ugarte
08507597f2
Data Model: add binding_expression_id and binding_expression_object_id to object_property table 2025-08-07 18:57:38 -04:00
Juan Pablo Ugarte
beeea0fa16
CmbObjectChooser: remove contruct only from properties 2025-08-07 18:57:37 -04:00
Juan Pablo Ugarte
98fa039783
CmbFlagsEntry: remove contruct only from properties 2025-08-07 18:57:37 -04:00
Juan Pablo Ugarte
38a57c7f64
Tests: add gtk expression import/export test 2025-08-07 18:57:37 -04:00
Juan Pablo Ugarte
3621463d3d
cmb-catalog-gen: add support for GtkExpression param spec
- Update Gtk catalogs for gtk expression support
2025-08-07 18:57:37 -04:00
Juan Pablo Ugarte
4375049d89
CmbWindow: expose import_file() 2025-07-28 18:24:08 -04:00
Juan Pablo Ugarte
a73d2610f4
Fix build 2025-07-28 18:24:08 -04:00
Juan Pablo Ugarte
cbe5a6ab11
CmbView: require Casilda 0.2 2025-07-28 17:57:44 -04:00
Juan Pablo Ugarte
d223cbc2b1
CmbDB: fix signal object references
Fix issue #276 "Signal loses reference to object if defined later in the XML"
2025-07-28 17:57:09 -04:00
Juan Pablo Ugarte
32f50abb29
CmbProject: remove old project support 2025-07-27 20:22:47 -04:00
Juan Pablo Ugarte
6a22474768
CmbProject: fix add_parent() for inline objects 2025-07-27 19:07:15 -04:00
Juan Pablo Ugarte
6c406ab960
CmbObject: implement inline_property_id as property 2025-07-27 19:06:32 -04:00
55 changed files with 1659 additions and 1056 deletions

View File

@ -83,8 +83,8 @@
{
"type" : "git",
"url" : "https://gitlab.gnome.org/jpu/casilda.git",
"tag" : "0.9.1",
"commit" : "fbc3c034061c5f81a27151d94db33a0a91ed1b0c"
"tag" : "0.9.2",
"commit" : "4f39e9d5f22f35b16583490a874ecf43db07869e"
}
]
},

View File

@ -27,7 +27,7 @@ import os
import locale
import tempfile
from gi.repository import GLib, GObject, Gio, Gdk, Gtk, Pango, Adw
from gi.repository import GLib, GObject, Gio, Gdk, Gtk, Pango, Adw, GtkSource
from .cmb_tutor import CmbTutor, CmbTutorState
from . import cmb_tutorial
@ -120,6 +120,9 @@ class CmbWindow(Adw.ApplicationWindow):
intro_button = Gtk.Template.Child()
menu_button = Gtk.Template.Child()
# Properties
source_style = GObject.Property(type=GtkSource.StyleScheme, flags=GObject.ParamFlags.READWRITE)
# Settings
completed_intro = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
@ -302,6 +305,10 @@ class CmbWindow(Adw.ApplicationWindow):
self.__load_window_state()
self.__update_actions()
self.source_style_manager = GtkSource.StyleSchemeManager.get_default()
app.props.style_manager.connect("notify::dark", lambda o, p: self.__update_dark_mode(app.props.style_manager))
self.__update_dark_mode(app.props.style_manager)
# Bind preview
hide_placeholders_button = Gtk.ToggleButton(tooltip_text=_("Hide placeholders"), icon_name="view-conceal-symbolic")
self.type_chooser.content.append(hide_placeholders_button)
@ -472,6 +479,14 @@ class CmbWindow(Adw.ApplicationWindow):
self.np_ui_entry.set_sensitive(sensitive)
self.__update_action_new()
def __update_dark_mode(self, style_manager):
if style_manager.props.dark:
self.source_style = self.source_style_manager.get_scheme("Adwaita-dark")
self.add_css_class("dark")
else:
self.remove_css_class("dark")
self.source_style = self.source_style_manager.get_scheme("Adwaita")
def __np_name_to_ui(self, binding, value):
if len(value):
return value.lower().rsplit(".", 1)[0] + ".ui"
@ -1125,7 +1140,7 @@ class CmbWindow(Adw.ApplicationWindow):
details=unsupported_features_list,
)
def __import_file(self, path, autoselect=True):
def import_file(self, path, autoselect=True):
content_type = utils.content_type_guess(path)
if content_type in ["application/x-gtk-builder", "application/x-glade", "text/x-blueprint"]:
@ -1142,7 +1157,7 @@ class CmbWindow(Adw.ApplicationWindow):
def dialog_callback(dialog, res):
try:
for file in dialog.open_multiple_finish(res):
self.__import_file(file.get_path())
self.import_file(file.get_path())
except Exception as e:
logger.warning(f"Error {e}")
@ -1208,7 +1223,7 @@ class CmbWindow(Adw.ApplicationWindow):
for i, path in enumerate(files):
progressbar.set_text(path.removeprefix(basedir))
progressbar.set_fraction(i/n_files)
self.__import_file(path, autoselect=False)
self.import_file(path, autoselect=False)
while main_loop.pending():
main_loop.iteration(False)

View File

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<!-- Created with Cambalache 0.97.5 -->
<!-- Created with Cambalache 0.97.6 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
<gresources filename="cambalache.gresource.xml" sha256="fdcf4cd517493f548aa4b4fe206ff7762cee9cdda7ec5a85a718b46eb1c4731b"/>
<gresources filename="cambalache.gresource.xml" sha256="f5e5356b0a796d96885a8bb0b363d18339457ea6bfe26e56b6db3f7e4a618b16"/>
<gresources filename="app/app.gresource.xml" sha256="3684aa78fce08d8e81d0907317214aeb179c5aea091dd0df405476b43e286941"/>
<css filename="cambalache.css" priority="400" is_global="1"/>
<css filename="app/cambalache.css" is_global="0"/>
@ -232,6 +232,13 @@
<ui template-class="CmbPollNotificationView" filename="cmb_poll_notification_view.ui" sha256="8f47a1e503b85eb5ac3ac54962a40fc2237588e1216afba859a3016a1dcfc121"/>
<ui template-class="CmbPollOptionCheck" filename="cmb_poll_option_check.ui" sha256="aa433f201dc1863f3727e1baa2c4cc239192a1ae4c53553de69d529ba2cc6fed"/>
<ui template-class="CmbNotificationListRow" filename="cmb_notification_list_row.ui" sha256="5ef66fcc24e10d40a91ff0eada84f6aa8a595e368961364d98fde1800755edfc"/>
<ui template-class="CmbPropertyChooser">
<content><![CDATA[<interface>
<requires lib="gtk" version="4.0"/>
<template class="CmbPropertyChooser" parent="GtkComboBox"/>
</interface>
]]></content>
</ui>
<ui template-class="CmbPixbufEntry">
<requires>CmbFileEntry</requires>
<content><![CDATA[<interface>
@ -257,7 +264,7 @@
<signal id="placeholder-activated"/>
<signal id="placeholder-selected"/>
</ui>
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="6a0563d827973e129cca2c41e90d931c6d63ec23422e459101e0c120c0937f07">
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="982c596dbe038edac9f4f20a72062b9d8b78b08be7e731395dde1c701d92bcb1">
<requires>CmbFileButton</requires>
<requires>CmbEntry</requires>
</ui>
@ -271,6 +278,11 @@
<requires>CmbEntry</requires>
<requires>CmbToplevelChooser</requires>
</ui>
<ui template-class="CmbBindingPopover" filename="cmb_binding_popover.ui" sha256="e3138c75d2c45534c1b41acbc3724a760b3a390effebcf0f54df1e04f3e748cc">
<requires>CmbObjectChooser</requires>
<requires>CmbPropertyChooser</requires>
<requires>CmbFlagsEntry</requires>
</ui>
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="784143e77643d8b03c5149768f9def9020c34aafd323a7073d601f2c1d2e5648">
<requires>CmbNotificationListView</requires>
<requires>CmbScrolledWindow</requires>

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<!-- Created with Cambalache 0.97.6 -->
<gresources>
<gresource prefix="/ar/xjuan/Cambalache">
<file>cambalache.css</file>
@ -27,5 +27,6 @@
<file>icons/scalable/actions/binded-symbolic.svg</file>
<file>icons/scalable/actions/bind-symbolic.svg</file>
<file>cmb_notification_list_row.ui</file>
<file>cmb_binding_popover.ui</file>
</gresource>
</gresources>

View File

@ -0,0 +1,142 @@
#
# CmbBindingPopover
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GLib, GObject, Gtk
from .cmb_property import CmbProperty
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_binding_popover.ui")
class CmbBindingPopover(Gtk.Popover):
__gtype_name__ = "CmbBindingPopover"
prop = GObject.Property(type=CmbProperty, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
object_chooser = Gtk.Template.Child()
property_chooser = Gtk.Template.Child()
flags_entry = Gtk.Template.Child()
# Expression
expression_dropdown = Gtk.Template.Child()
expression_object_chooser = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.prop is None:
return
# Get bind property to initialize inputs
bind_source, bind_property = self.__find_bind_source_property(self.prop.bind_source_id, self.prop.bind_property_id)
# Object editor (it does not set the object directly to CmbProperty, just choose the object in the prop chooser)
self.object_chooser.parent = self.prop.object
self.object_chooser.cmb_value = bind_source.object_id if bind_source else 0
# Update Property editor
self.property_chooser.object = bind_source
self.property_chooser.target_info = self.prop.info
# Update active_id after letting the object populate the properties
if bind_property:
self.property_chooser.props.active_id = bind_property.property_id
# Flags editor
binding_flags_info = self.prop.project.type_info.get("GBindingFlags", None)
self.flags_entry.info = binding_flags_info
GObject.Object.bind_property(
self.prop,
"bind_flags",
self.flags_entry,
"cmb-value",
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
)
self.__updating = True
# Expression stuff
if self.prop.binding_expression_id:
expression_source = self.prop.project.get_object_by_id(self.prop.ui_id, self.prop.binding_expression_id)
item_index = self.expression_dropdown.props.model.find(expression_source.type_id)
self.expression_dropdown.set_selected(item_index if item_index < GLib.MAXUINT else 0)
else:
self.expression_dropdown.set_selected(0)
self.expression_object_chooser.parent = self.prop.object
self.expression_object_chooser.cmb_value = self.prop.binding_expression_object_id
self.__updating = False
@Gtk.Template.Callback("on_clear_clicked")
def __on_clear_clicked(self, button):
if self.prop:
self.prop.clear_binding()
self.popdown()
@Gtk.Template.Callback("on_close_clicked")
def __on_close_clicked(self, button):
self.popdown()
@Gtk.Template.Callback("on_object_editor_notify")
def __on_object_editor_notify(self, object_editor, pspec):
object_id = object_editor.cmb_value
if object_id:
obj = self.prop.project.get_object_by_id(self.prop.ui_id, int(object_id)) if self.prop else None
self.property_chooser.object = obj
else:
self.property_chooser.object = None
@Gtk.Template.Callback("on_property_editor_changed")
def __on_property_editor_changed(self, combo):
if self.prop is None:
return
if combo.object:
bind_source, bind_property = self.__find_bind_source_property(combo.object.object_id, combo.props.active_id)
self.prop.bind_property = bind_property
else:
self.prop.bind_property = None
def __find_bind_source_property(self, bind_source_id, bind_property_id):
bind_source = self.prop.project.get_object_by_id(self.prop.ui_id, bind_source_id) if bind_source_id else None
bind_property = bind_source.properties_dict.get(bind_property_id, None) if bind_source else None
return bind_source, bind_property
@Gtk.Template.Callback("on_popover_show")
def __on_popover_show(self, combo):
if self.prop.binding_expression_id:
self.expression_dropdown.grab_focus()
else:
self.object_chooser.grab_focus()
# GtkExpression support
@Gtk.Template.Callback("on_expression_dropdown_notify")
def __on_expression_dropdown_notify(self, dropdown, pspec):
if self.prop is None or self.__updating:
return
self.prop.set_binding_expression_type(dropdown.props.selected_item.get_string())

View File

@ -0,0 +1,188 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.97.6 -->
<interface>
<!-- interface-name cmb_property_binding_widget.ui -->
<requires lib="gtk" version="4.18"/>
<template class="CmbBindingPopover" parent="GtkPopover">
<property name="css-classes">cmb-binding-popover</property>
<property name="position">left</property>
<signal name="show" handler="on_popover_show"/>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkGrid">
<property name="column-spacing">4</property>
<property name="row-spacing">4</property>
<property name="vexpand">True</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Type</property>
<property name="margin-start">16</property>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">5</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Object</property>
<property name="margin-start">16</property>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">6</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="CmbObjectChooser" id="expression_object_chooser">
<property name="placeholder-text" translatable="yes">&lt;expression object&gt;</property>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">6</property>
</layout>
</object>
</child>
<child>
<object class="GtkDropDown" id="expression_dropdown">
<property name="model">
<object class="GtkStringList">
<items>
<item translatable="yes">None</item>
<item>GtkConstantExpression</item>
<item>GtkPropertyExpression</item>
<item>GtkClosureExpression</item>
</items>
</object>
</property>
<signal name="notify::selected-item" handler="on_expression_dropdown_notify"/>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">5</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Source</property>
<property name="margin-start">16</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Property</property>
<property name="margin-start">16</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Flags</property>
<property name="margin-start">16</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="CmbObjectChooser" id="object_chooser">
<signal name="notify::cmb-value" handler="on_object_editor_notify"/>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="CmbPropertyChooser" id="property_chooser">
<signal name="changed" handler="on_property_editor_changed"/>
<layout>
<property name="column">1</property>
<property name="row">2</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="CmbFlagsEntry" id="flags_entry">
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">3</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">&lt;b&gt;Gtk Expression&lt;/b&gt;</property>
<property name="margin-top">8</property>
<property name="use-markup">True</property>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">&lt;b&gt;Property Binding&lt;/b&gt;</property>
<property name="use-markup">True</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="css-classes">close</property>
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="icon-name">window-close-symbolic</property>
<signal name="clicked" handler="on_close_clicked"/>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="halign">end</property>
<property name="label">Clear</property>
<signal name="clicked" handler="on_clear_clicked"/>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -34,7 +34,7 @@ from lxml.builder import E
from graphlib import TopologicalSorter, CycleError
from gi.repository import GLib, Gio, GObject
from cambalache import config, getLogger, _
from . import cmb_db_migration, utils
from . import utils
from .constants import EXTERNAL_TYPE, GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
from .cmb_db_profile import CmbProfileConnection
@ -318,6 +318,9 @@ class CmbDB(GObject.GObject):
if column in unique_constraints_flat:
continue
# Get column index
column_index = all_columns.index(column)
c.execute(
f"""
CREATE TRIGGER on_{table}_update_{column} AFTER UPDATE OF {column} ON {table}
@ -330,8 +333,8 @@ class CmbDB(GObject.GObject):
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
IS NOT ('UPDATE', '{table}', json_array('{column}'))
AND
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
IS NOT ('INSERT', '{table}', NULL)
(SELECT command, table_name, columns, new_values ->> {column_index} IS NULL FROM history WHERE history_id = {history_seq})
IS NOT ('INSERT', '{table}', NULL, 0)
)
)
BEGIN
@ -364,8 +367,8 @@ class CmbDB(GObject.GObject):
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
AND
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
IS ('INSERT', '{table}', NULL)
(SELECT command, table_name, columns, new_values ->> {column_index} IS NULL FROM history WHERE history_id = {history_seq})
IS ('INSERT', '{table}', NULL, 0)
BEGIN
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
END;
@ -609,46 +612,6 @@ class CmbDB(GObject.GObject):
return utils.parse_version(version)
def __ensure_table_data_columns(self, version, table, data):
if version is None:
return data
if version < (0, 7, 5):
data = cmb_db_migration.ensure_columns_for_0_7_5(table, data)
if version < (0, 9, 0):
data = cmb_db_migration.ensure_columns_for_0_9_0(table, data)
if version < (0, 11, 2):
data = cmb_db_migration.ensure_columns_for_0_11_2(table, data)
if version < (0, 11, 4):
data = cmb_db_migration.ensure_columns_for_0_11_4(table, data)
if version < (0, 13, 1):
data = cmb_db_migration.ensure_columns_for_0_13_1(table, data)
if version < (0, 17, 3):
data = cmb_db_migration.ensure_columns_for_0_17_3(table, data)
return data
def __migrate_table_data(self, c, version, table, data):
if version is None:
return
if version < (0, 7, 5):
cmb_db_migration.migrate_table_data_to_0_7_5(c, table, data)
if version < (0, 9, 0):
cmb_db_migration.migrate_table_data_to_0_9_0(c, table, data)
if version < (0, 17, 3):
cmb_db_migration.migrate_table_data_to_0_17_3(c, table, data)
if version < (0, 91, 3):
cmb_db_migration.migrate_table_data_to_0_91_3(c, table, data)
def __load_table_from_tuples(self, c, table, tuples, version=None):
data = ast.literal_eval(f"[{tuples}]") if tuples else []
if len(data) == 0:
@ -670,35 +633,12 @@ class CmbDB(GObject.GObject):
# Load table data
c.executemany(f"INSERT INTO temp.{table} VALUES ({cols})", data)
# Migrate data to current format
self.__migrate_table_data(c, version, table, data)
# Copy data from temp table
c.execute(f"INSERT INTO main.{table} SELECT * FROM temp.{table};")
# Drop temp table
c.execute(f"DROP TABLE temp.{table};")
def load_old_format(self, root, version):
c = self.conn.cursor()
# Avoid circular dependencies errors
self.foreign_keys = False
self.ignore_check_constraints = True
# Support old format
all_tables = self.__tables + ["property", "signal"]
for child in root.getchildren():
if child.tag in all_tables:
self.__load_table_from_tuples(c, child.tag, child.text, version)
else:
raise Exception(f"Unknown tag {child.tag} in project file.")
self.foreign_keys = True
self.ignore_check_constraints = False
c.close()
def __load_accessibility_metadata(self, node):
data = json.loads(node.text)
@ -947,6 +887,7 @@ class CmbDB(GObject.GObject):
layout=None,
position=None,
inline_property=None,
inline_binding_expression=False,
):
c = self.conn.cursor()
@ -1017,18 +958,20 @@ class CmbDB(GObject.GObject):
)
count = c.fetchone()[0]
inline_object_property = "binding_expression_id" if inline_binding_expression else "inline_object_id"
if count:
c.execute(
"""
UPDATE object_property SET inline_object_id=?
f"""
UPDATE object_property SET {inline_object_property}=?
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id;
""",
(object_id, ui_id, parent_id, pinfo.owner_id, inline_property),
)
else:
c.execute(
"""
INSERT INTO object_property (ui_id, object_id, owner_id, property_id, inline_object_id)
f"""
INSERT INTO object_property (ui_id, object_id, owner_id, property_id, {inline_object_property})
VALUES (?, ?, ?, ?, ?)
""",
(ui_id, parent_id, pinfo.owner_id, inline_property, object_id),
@ -1124,15 +1067,15 @@ class CmbDB(GObject.GObject):
# Initialize to null
inline_object_id = None
# GtkBuilder in Gtk4 supports defining an object in a property
obj_node = prop.find("object")
if self.target_tk == "gtk-4.0" and pinfo.is_object and obj_node is not None:
if self.target_tk == "gtk-4.0" and pinfo.is_object and len(prop) >= 1:
if pinfo.disable_inline_object:
self.__collect_error("not-inline-object", prop, f"{info.type_id}:{property_id}")
return
inline_object_id = self.__import_object(ui_id, obj_node, object_id)
value = None
if prop[0].tag == "object" or \
(pinfo.type_id == "GtkExpression" and prop[0].tag in ["lookup", "constant", "closure"]):
inline_object_id = self.__import_object(ui_id, prop[0], object_id)
value = None
self.__upsert_object_property(
c,
@ -1153,6 +1096,33 @@ class CmbDB(GObject.GObject):
inline_object_id=inline_object_id
)
def __import_binding(self, c, info, ui_id, object_id, prop, object_id_map=None):
name, object = self.__node_get(prop, "name", ["object"])
property_id = name.replace("_", "-")
pinfo = self.__get_property_info(info, property_id)
if pinfo is None:
self.__collect_error("unknown-property", prop, f"{info.type_id}:{property_id}")
return
# Get expression object
binding_expression_id = self.__import_expression(ui_id, prop[0], object_id)
self.__upsert_object_property(
c,
info,
pinfo,
ui_id,
object_id,
prop,
property_id,
None,
object_id_map=object_id_map,
binding_expression_id=binding_expression_id,
binding_expression_object_id=object
)
def __upsert_object_property(
self,
c,
@ -1170,7 +1140,9 @@ class CmbDB(GObject.GObject):
bind_source_id=None,
bind_property_id=None,
bind_flags=None,
inline_object_id=None
inline_object_id=None,
binding_expression_id=None,
binding_expression_object_id=None
):
comment = self.__node_get_comment(prop)
@ -1191,8 +1163,9 @@ class CmbDB(GObject.GObject):
"""
INSERT OR REPLACE INTO object_property
(ui_id, object_id, owner_id, property_id, value, translatable, comment, translation_context,
translation_comments, inline_object_id, bind_source_id, bind_property_id, bind_flags)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
translation_comments, inline_object_id, bind_source_id, bind_property_id, bind_flags,
binding_expression_id, binding_expression_object_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
(
ui_id,
@ -1208,6 +1181,8 @@ class CmbDB(GObject.GObject):
bind_source_id,
bind_property_id,
bind_flags,
binding_expression_id,
binding_expression_object_id,
),
)
except Exception as e:
@ -1330,9 +1305,9 @@ class CmbDB(GObject.GObject):
"""
INSERT INTO object_signal
(ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after, comment)
VALUES (?, ?, ?, ?, ?, ?, (SELECT object_id FROM object WHERE ui_id=? AND name=?), ?, ?, ?);
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
(ui_id, object_id, owner_id, signal_id, handler, detail, ui_id, user_data, swap, after, comment),
(ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after, comment),
)
except Exception as e:
raise Exception(f"XML:{signal.sourceline} - Can not import object {object_id} {owner_id}:{signal_id} signal: {e}")
@ -1607,6 +1582,71 @@ class CmbDB(GObject.GObject):
else:
self.__collect_error("unknown-tag", node, child.tag)
def __import_expression(self, ui_id, node, parent_id):
comment = self.__node_get_comment(node)
tag = node.tag
if tag == "constant":
klass = "GtkConstantExpression"
elif tag == "lookup":
klass = "GtkPropertyExpression"
elif tag == "closure":
klass = "GtkClosureExpression"
else:
self.__collect_error("unknown-tag", node, tag)
return
info = self.type_info.get(klass, None)
if not info:
logger.warning(f"Error importing expression: {klass} not found")
return None
# Insert menu
try:
expression_id = self.add_object(ui_id, klass, None, parent_id, None, None, comment)
except Exception:
logger.warning(f"XML:{node.sourceline} - Error importing expression")
return None
# Get a list of attributes and their values
properties = node.attrib.items()
# Append text as value attribute
if klass != "GtkClosureExpression" and node.text:
properties.append(("value", node.text))
c = self.conn.cursor()
# Import attributes as properties
for property_id, value in properties:
if property_id not in info.properties:
logger.warning(f"XML:{node.sourceline} - Error importing expression, {property_id} attribute is not valid")
continue
c.execute(
"""
INSERT OR REPLACE INTO object_property
(ui_id, object_id, owner_id, property_id, value)
VALUES (?, ?, ?, ?, ?);
""",
(
ui_id,
expression_id,
klass,
property_id,
value,
),
)
c.close()
for child in node:
self.__import_expression(ui_id, child, expression_id)
return expression_id
def __import_object(
self, ui_id, node, parent_id, internal_child=None, child_type=None, is_template=False, object_id_map=None
):
@ -1614,6 +1654,8 @@ class CmbDB(GObject.GObject):
if node.tag == "menu":
return self.__import_menu(ui_id, node, parent_id, object_id_map=object_id_map)
if node.tag in ["lookup", "constant", "closure"]:
return self.__import_expression(ui_id, node, parent_id)
is_template = node.tag == "template"
@ -1665,6 +1707,8 @@ class CmbDB(GObject.GObject):
for child in node.iterchildren():
if child.tag == "property":
self.__import_property(c, info, ui_id, object_id, child, object_id_map=object_id_map)
elif child.tag == "binding" and self.target_tk == "gtk-4.0":
self.__import_binding(c, info, ui_id, object_id, child, object_id_map=object_id_map)
elif child.tag == "signal":
self.__import_signal(c, info, ui_id, object_id, child, object_id_map=object_id_map)
elif child.tag == "child":
@ -1782,6 +1826,16 @@ class CmbDB(GObject.GObject):
(ui_id,),
)
# Fix signal user_data that refer to an object
self.conn.execute(
"""
UPDATE object_signal AS os SET user_data=o.object_id
FROM object AS o
WHERE os.ui_id=? AND os.ui_id == o.ui_id AND os.user_data IS NOT NULL AND os.user_data == o.name;
""",
(ui_id,),
)
# Fix bind source and set bind owner to the object type
self.conn.execute(
"""
@ -1793,6 +1847,52 @@ class CmbDB(GObject.GObject):
(ui_id,),
)
# Fix binding expression object references
self.conn.execute(
"""
UPDATE object_property AS op
SET binding_expression_object_id=o.object_id
FROM object AS o
WHERE op.ui_id=? AND binding_expression_object_id IS NOT NULL AND
o.ui_id = op.ui_id AND o.name = op.binding_expression_object_id;
""",
(ui_id,),
)
# Fix GtkPropertyExpression and GtkConstantExpression value properties
self.conn.execute(
"""
UPDATE object_property AS op
SET value=o.object_id
FROM object AS o
WHERE op.ui_id=? AND
op.ui_id=o.ui_id AND
op.property_id='value' AND
op.owner_id IN ('GtkPropertyExpression', 'GtkConstantExpression') AND
op.value=o.name AND
(
(op.ui_id, op.object_id) IN (
SELECT op2.ui_id, op2.object_id
FROM object_property AS op2, type AS t
WHERE op2.ui_id=? AND
t.derivable AND
op2.owner_id IN ('GtkPropertyExpression', 'GtkConstantExpression') AND
op2.property_id='type' AND
op2.value = t.type_id
)
OR
(op.ui_id, op.object_id) NOT IN (
SELECT op3.ui_id, op3.object_id
FROM object_property AS op3
WHERE op3.ui_id=? AND
op3.owner_id IN ('GtkPropertyExpression', 'GtkConstantExpression') AND
op3.property_id='type'
)
)
""",
(ui_id, ui_id, ui_id),
)
# Fix a11y CmbAccessibleList references
for row in self.conn.execute(
"""
@ -2143,6 +2243,100 @@ class CmbDB(GObject.GObject):
return obj
def __export_expression(self, ui_id, object_id, merengue=False):
c = self.conn.cursor()
c.execute("SELECT type_id FROM object WHERE ui_id=? AND object_id=?;", (ui_id, object_id))
type_id, = c.fetchone()
# Collect properties
props = {}
for row in c.execute(
"""
SELECT value, property_id
FROM object_property
WHERE ui_id=? AND object_id=? AND value IS NOT NULL ORDER BY property_id;
""",
(ui_id, object_id),
):
value, property_id = row
props[property_id] = value
if type_id == "GtkConstantExpression":
# Do not export incomplete expressions
if "value" not in props:
return None
node = E.constant()
elif type_id == "GtkPropertyExpression":
node = E.lookup()
elif type_id == "GtkClosureExpression":
# Dont export closure expressions since the function most likely does not exists
if merengue:
return None
# Do not export incomplete expressions
if "function" not in props:
return None
node = E.closure()
else:
logger.warning(f"Ignoring object type {type_id} while exporting expression.")
return None
has_children = False
# Children
for row in c.execute(
"SELECT object_id, comment FROM object WHERE ui_id=? AND parent_id=? ORDER BY position;",
(ui_id, object_id),
):
child_id, comment = row
child_node = self.__export_expression(ui_id, child_id, merengue=merengue)
if child_node is not None:
self.__node_add_comment(child_node, comment)
node.append(child_node)
has_children = True
# Do not export incomplete expressions
if type_id == "GtkPropertyExpression" and ("value" not in props or not has_children):
return None
# Check if type is an object type
is_object = type_id in ["GtkPropertyExpression", "GtkConstantExpression"]
if "type" in props:
# Check if type is an object type
info = self.type_info.get(props["type"])
is_object = info.is_object if info is not None else False
# Set attributes from properties
for property_id, value in props.items():
if property_id == "value":
if has_children:
continue
if is_object:
row = self.conn.execute(
"SELECT object_id FROM object WHERE ui_id=? AND name=?", (ui_id, value.strip())
).fetchone()
if row is None:
continue
if merengue:
node.text = f"__cmb__{ui_id}.{row[0]}"
else:
node.text = value
else:
node.text = value
else:
utils.xml_node_set(node, property_id, value)
c.close()
return node
def __get_object_name(self, ui_id, object_id, merengue=False):
if object_id is None:
return None
@ -2258,16 +2452,14 @@ class CmbDB(GObject.GObject):
return n_objs == 0 and n_children == 0 and n_props == 0 and n_layout_props == 0 and n_signals == 0 and n_data == 0
def __export_object(self, ui_id, object_id, merengue=False, template_id=None, ignore_id=False):
target_gtk4 = self.target_tk == "gtk-4.0"
target_gtk3 = not target_gtk4
c = self.conn.cursor()
c.execute("SELECT type_id, name, custom_fragment FROM object WHERE ui_id=? AND object_id=?;", (ui_id, object_id))
type_id, name, custom_fragment = c.fetchone()
# Special case <menu>
if type_id == GMENU_TYPE:
c.close()
return self.__export_menu(ui_id, object_id, merengue=merengue, ignore_id=ignore_id)
info = self.type_info.get(type_id, None)
if info is None:
@ -2275,6 +2467,14 @@ class CmbDB(GObject.GObject):
c.close()
return None
# Special case <menu>
if type_id == GMENU_TYPE:
c.close()
return self.__export_menu(ui_id, object_id, merengue=merengue, ignore_id=ignore_id)
elif info.is_a("GtkExpression"):
c.close()
return self.__export_expression(ui_id, object_id, merengue=merengue)
cc = self.conn.cursor()
merengue_template = merengue and info.library_id is None and info.parent_id is not None
@ -2336,6 +2536,7 @@ class CmbDB(GObject.GObject):
SELECT op.value, op.property_id, op.inline_object_id, op.comment, op.translatable, op.translation_context,
op.translation_comments, p.is_object, p.disable_inline_object,
op.bind_source_id, op.bind_owner_id, op.bind_property_id, op.bind_flags,
op.binding_expression_id, op.binding_expression_object_id,
NULL, NULL, p.type_id
FROM object_property AS op, property AS p, type AS t
WHERE op.owner_id NOT IN
@ -2345,7 +2546,7 @@ class CmbDB(GObject.GObject):
{template_check}
UNION
SELECT p.default_value, p.property_id, NULL, NULL, NULL, NULL, NULL, p.is_object, p.disable_inline_object,
NULL, NULL, NULL, NULL, p.required, p.workspace_default, p.type_id
NULL, NULL, NULL, NULL, NULL, NULL, p.required, p.workspace_default, p.type_id
FROM property AS p, type AS t
WHERE p.owner_id == t.type_id AND (required=1 OR save_always=1) AND owner_id IN ({placeholders}) AND
property_id NOT IN (SELECT property_id FROM object_property WHERE ui_id=? AND object_id=?)
@ -2368,6 +2569,8 @@ class CmbDB(GObject.GObject):
bind_owner_id,
bind_property_id,
bind_flags,
binding_expression_id,
binding_expression_object_id,
required,
workspace_default,
property_type_id,
@ -2377,13 +2580,10 @@ class CmbDB(GObject.GObject):
value_node = None
pinfo = self.type_info.get(property_type_id, None)
is_inline_object = not disable_inline_object and self.target_tk == "gtk-4.0"
is_inline_object = not disable_inline_object and target_gtk4
if required and workspace_default:
if is_object and is_inline_object:
value_node = etree.fromstring(workspace_default)
else:
value = workspace_default
if binding_expression_id:
value_node = self.__export_expression(ui_id, binding_expression_id, merengue=merengue)
elif is_object:
# Ignore object properties with 0/null ID or unknown object references
if val is not None and val.isnumeric() and int(val) == 0:
@ -2394,7 +2594,7 @@ class CmbDB(GObject.GObject):
elif ignore_id:
# Ignore references to object in template mode since the object could not exists in this UI
continue
else:
elif val:
obj_name = self.__get_object_name(ui_id, val, merengue=merengue)
# Ignore properties that reference an unknown object
@ -2410,7 +2610,24 @@ class CmbDB(GObject.GObject):
else:
value = val
node = E.property(name=property_id)
if value is None and value_node is None and required and workspace_default:
if is_object and is_inline_object:
value_node = etree.fromstring(workspace_default)
else:
value = workspace_default
if target_gtk4 and binding_expression_id:
if value_node is None:
continue
node = E.binding(name=property_id)
if binding_expression_object_id:
object_name = self.__get_object_name(ui_id, binding_expression_object_id, merengue=merengue)
utils.xml_node_set(node, "object", object_name)
else:
node = E.property(name=property_id)
if value is not None:
node.text = value
elif value_node is not None:
@ -2476,7 +2693,7 @@ class CmbDB(GObject.GObject):
accessible_role = None
a11y_data = {}
if self.target_tk == "gtk+-3.0":
if target_gtk3:
atk_object = E.object()
atk_object.set("class", "AtkObject")
else:
@ -2660,6 +2877,7 @@ class CmbDB(GObject.GObject):
SELECT object_id, internal, type, comment, position, custom_child_fragment
FROM object
WHERE ui_id=? AND parent_id=? AND
type_id NOT IN ('GtkPropertyExpression', 'GtkConstantExpression', 'GtkClosureExpression') AND
object_id NOT IN (SELECT inline_object_id FROM object_property
WHERE inline_object_id IS NOT NULL AND ui_id=? AND object_id=?)
ORDER BY position;
@ -2674,7 +2892,7 @@ class CmbDB(GObject.GObject):
if merengue and is_box:
# FIXME: On Gtk 3 we get the position from the layout property
if self.target_tk == "gtk+-3.0":
if target_gtk3:
r = cc.execute(
"""
SELECT value
@ -2706,7 +2924,7 @@ class CmbDB(GObject.GObject):
if linfo is not None:
# Packing / Layout
layout = E("packing" if self.target_tk == "gtk+-3.0" else "layout")
layout = E("packing" if target_gtk3 else "layout")
for prop in cc.execute(
f"""
SELECT value, property_id, comment
@ -2727,7 +2945,7 @@ class CmbDB(GObject.GObject):
self.__node_add_comment(node, comment)
if len(layout) > 0:
if self.target_tk == "gtk+-3.0":
if target_gtk3:
child.append(layout)
else:
child_obj.append(layout)

View File

@ -1,163 +0,0 @@
#
# CmbDBmigration - Cambalache DataBase Migration functions
#
# Copyright (C) 2021-2023 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
def ensure_columns_for_0_7_5(table, data):
if table == "object":
# Append position column
return [row + (None,) for row in data]
elif table in ["object_property", "object_layout_property"]:
# Append translation_context, translation_comments columns
return [row + (None, None) for row in data]
return data
def migrate_table_data_to_0_7_5(c, table, data):
if table == "object":
c.execute(
"""
UPDATE temp.object SET position=new.position - 1
FROM (
SELECT row_number() OVER (PARTITION BY parent_id ORDER BY object_id) position, ui_id, object_id
FROM temp.object
WHERE parent_id IS NOT NULL
) AS new
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
"""
)
c.execute(
"""
UPDATE temp.object SET position=new.position - 1
FROM (
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
FROM temp.object
WHERE parent_id IS NULL
) AS new
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
"""
)
def ensure_columns_for_0_9_0(table, data):
if table == "object_property":
# Append inline_object_id column
return [row + (None,) for row in data]
return data
def migrate_table_data_to_0_9_0(c, table, data):
if table == "object_property":
# Remove all object properties with a 0 as value
c.execute(
"""
DELETE FROM temp.object_property AS op
WHERE value = 0 AND
(SELECT property_id FROM temp.property WHERE owner_id=op.owner_id AND property_id=op.property_id AND is_object)
IS NOT NULL;
"""
)
def ensure_columns_for_0_11_2(table, data):
if table in ["object", "ui"]:
# Append custom_text column
return [row + (None,) for row in data]
return data
def ensure_columns_for_0_11_4(table, data):
if table == "object_property":
# Append bind_[source_id owner_id property_id flags] column
return [row + (None, None, None, None) for row in data]
return data
def ensure_columns_for_0_13_1(table, data):
if table == "object_data":
# Append translatable, translation_context, translation_comments columns
return [row + (None, None, None) for row in data]
return data
def ensure_columns_for_0_17_3(table, data):
if table == "object":
# Append custom_child_fragment column
return [row + (None,) for row in data]
return data
def migrate_table_data_to_0_17_3(c, table, data):
if table in ["object_property", "object_layout_property", "object_data"]:
c.executescript(
f"""
UPDATE temp.{table} SET translatable=1
WHERE translatable IS NOT NULL AND lower(translatable) IN (1, 'y', 'yes', 't', 'true');
UPDATE temp.{table} SET translatable=NULL
WHERE translatable IS NOT NULL AND translatable != 1;
"""
)
if table == "object_signal":
for prop in ["swap", "after"]:
c.executescript(
f"""
UPDATE temp.object_signal SET {prop}=1
WHERE {prop} IS NOT NULL AND lower({prop}) IN (1, 'y', 'yes', 't', 'true');
UPDATE temp.object_signal SET {prop}=NULL WHERE {prop} IS NOT NULL AND after != 1;
"""
)
def migrate_table_data_to_0_91_3(c, table, data):
# Ensure every object has a position
if table == "object":
c.execute(
"""
UPDATE temp.object SET position=new.position - 1
FROM (
SELECT row_number() OVER (PARTITION BY ui_id, parent_id ORDER BY position, object_id) position, ui_id, object_id
FROM temp.object
WHERE parent_id IS NOT NULL
) AS new
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
"""
)
c.execute(
"""
UPDATE temp.object SET position=new.position - 1
FROM (
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
FROM temp.object
WHERE parent_id IS NULL
) AS new
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
"""
)

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<!-- Created with Cambalache 0.97.6 -->
<interface>
<!-- interface-name cmb_gresource_editor.ui -->
<!-- interface-copyright Juan Pablo Ugarte -->

View File

@ -62,7 +62,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
self.__signals_dict = None
self.__data = None
self.__data_dict = None
self.inline_property_id = None
self.version_warning = None
self.__is_template = False
@ -75,7 +74,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
if self.project is None:
return
self.__update_inline_property_id()
self.__update_version_warning()
self.connect("notify", self._on_notify)
@ -129,7 +127,8 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
self.__populate_data()
return self.__data_dict
def __update_inline_property_id(self):
@property
def inline_property_id(self):
ui_id = self.ui_id
object_id = self.object_id
parent_id = self.parent_id
@ -137,9 +136,28 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
if parent_id:
# Set which parent property makes a reference to this inline object
row = self.project.db.execute(
"SELECT property_id FROM object_property WHERE ui_id=? AND inline_object_id=?;", (ui_id, object_id)
"SELECT property_id FROM object_property WHERE ui_id=? AND object_id=? AND inline_object_id=?;",
(ui_id, parent_id, object_id)
).fetchone()
self.inline_property_id = row[0] if row else None
return row[0] if row else None
return None
@property
def binding_expression_property_id(self):
ui_id = self.ui_id
object_id = self.object_id
parent_id = self.parent_id
if parent_id:
# Set which parent property makes a reference to this binding expression object
row = self.project.db.execute(
"SELECT property_id FROM object_property WHERE ui_id=? AND object_id=? AND binding_expression_id=?;",
(ui_id, parent_id, object_id)
).fetchone()
return row[0] if row else None
return None
def __populate_type_properties(self, name):
property_info = self.project.get_type_properties(name)
@ -591,11 +609,19 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
name = self.name or ""
type_id = self.type_id
parent_id = self.parent_id
expression_property = self.binding_expression_property_id
if type_id in [GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]:
prop = self.properties_dict["label"]
label = prop.value or ""
display_name = f"{type_id} <i>{label}</i>"
elif expression_property and type_id in ["GtkPropertyExpression", "GtkConstantExpression", "GtkClosureExpression"]:
expression_type = {
"GtkPropertyExpression": "lookup",
"GtkConstantExpression": "constant",
"GtkClosureExpression": "closure"
}.get(type_id)
display_name = f"<b>{expression_property}</b> ({expression_type} bind)"
elif not parent_id and self.ui.template_id == self.object_id:
# Translators: This is used for Template classes in the object tree
display_name = _("{name} (template)").format(name=name)

View File

@ -165,7 +165,7 @@ class CmbObjectEditor(Gtk.Box):
# Child Type input
if parent.info.has_child_types():
self.append(self.__create_child_type_editor())
else:
elif not obj.info.is_a("GtkExpression"):
# ID
self.append(self.__create_id_editor())

View File

@ -573,6 +573,8 @@ class CmbBaseProperty(CmbBase):
bind_owner_id,
bind_property_id,
bind_flags,
binding_expression_id,
binding_expression_object_id,
):
return cls(project=project, ui_id=ui_id, object_id=object_id, owner_id=owner_id, property_id=property_id)
@ -826,6 +828,56 @@ class CmbBaseProperty(CmbBase):
value,
)
@GObject.Property(type=int)
def binding_expression_id(self):
return self.db_get(
"SELECT binding_expression_id FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
)
@binding_expression_id.setter
def _set_binding_expression_id(self, value):
self.db_set(
"UPDATE object_property SET binding_expression_id=? WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
value,
)
@GObject.Property(type=int)
def binding_expression_object_id(self):
return self.db_get(
"SELECT binding_expression_object_id FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
)
@binding_expression_object_id.setter
def _set_binding_expression_object_id(self, value):
self.db_set(
"UPDATE object_property SET binding_expression_object_id=? WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
value,
)
class CmbBaseLayoutProperty(CmbBase):
__gtype_name__ = "CmbBaseLayoutProperty"

View File

@ -229,6 +229,12 @@ class CmbProject(GObject.Object, Gio.ListModel):
return {"names": columns, "types": types, "pks": pks}
def _get_types(self):
retval = []
for row in self.db.execute("SELECT type_id FROM type ORDER BY type_id;"):
retval.append(row[0])
return retval
def __init_type_info(self, c):
for row in c.execute("SELECT * FROM type WHERE parent_id IS NOT NULL ORDER BY type_id;"):
type_id = row[0]
@ -444,64 +450,59 @@ class CmbProject(GObject.Object, Gio.ListModel):
f"File format {version} is not supported by this release,\nplease update to a newer version to open this file."
)
if version > (0, 94, 0):
ui_graph = {}
ui_node_template = {}
if version <= (0, 94, 0):
raise Exception(
f"Project format {version} is not supported, Open/save with Cambalache 0.96.0 to migrate to the new format."
)
css_list = []
gresourses_list = []
ui_graph = {}
ui_node_template = {}
for child in root.getchildren():
if child.tag == "ui":
# Collect template class <-> node relation
template = child.get("template-class", None)
if template:
ui_node_template[template] = child
css_list = []
gresourses_list = []
# Collect node dependencies
dependencies = []
for requires in child.findall("requires"):
dependencies.append(requires.text)
for child in root.getchildren():
if child.tag == "ui":
# Collect template class <-> node relation
template = child.get("template-class", None)
if template:
ui_node_template[template] = child
ui_graph[child] = dependencies
elif child.tag == "css":
css_list.append(child)
elif child.tag == "gresources":
gresourses_list.append(child)
elif child.tag == "icontheme-search-path":
self.icontheme_search_paths.append(child.text)
else:
raise Exception(f"Unknown tag {child.tag} in project file.")
# Collect node dependencies
dependencies = []
for requires in child.findall("requires"):
dependencies.append(requires.text)
for node in css_list:
self.__load_css_from_node(node)
ui_graph[child] = dependencies
elif child.tag == "css":
css_list.append(child)
elif child.tag == "gresources":
gresourses_list.append(child)
elif child.tag == "icontheme-search-path":
self.icontheme_search_paths.append(child.text)
else:
raise Exception(f"Unknown tag {child.tag} in project file.")
for node in gresourses_list:
self.__load_gresource_from_node(node)
for node in css_list:
self.__load_css_from_node(node)
# Replace dependencies with nodes
ui_node_graph = {}
for node, dependencies in ui_graph.items():
ui_node_graph[node] = [ui_node_template[key] for key in dependencies]
for node in gresourses_list:
self.__load_gresource_from_node(node)
try:
ts = TopologicalSorter(ui_node_graph)
sorted_ui_nodes = tuple(ts.static_order())
except CycleError as e:
raise Exception(f"Could not load project because of dependency cycle {e}")
# Replace dependencies with nodes
ui_node_graph = {}
for node, dependencies in ui_graph.items():
ui_node_graph[node] = [ui_node_template[key] for key in dependencies]
# Load UI in topological order
for node in sorted_ui_nodes:
self.__load_ui_from_node(node)
else:
self.db.load_old_format(root, version)
try:
ts = TopologicalSorter(ui_node_graph)
sorted_ui_nodes = tuple(ts.static_order())
except CycleError as e:
raise Exception(f"Could not load project because of dependency cycle {e}")
for row in self.db.execute("SELECT * FROM ui;"):
ui = self.__add_ui(True, *row)
ui.notify("n-items")
for row in self.db.execute("SELECT * FROM css;"):
self.__add_css(True, *row)
# Load UI in topological order
for node in sorted_ui_nodes:
self.__load_ui_from_node(node)
self.history_enabled = True
@ -1209,6 +1210,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
child_type=None,
inline_property=None,
internal=None,
inline_binding_expression=False,
):
if parent_id:
parent = self.get_object_by_id(ui_id, parent_id)
@ -1232,6 +1234,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
inline_property=inline_property,
child_type=child_type,
internal=internal,
inline_binding_expression=inline_binding_expression,
)
self.history_pop()
self.db.commit()
@ -1404,11 +1407,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
p = properties.get(property_id, None)
if p and p.owner_id == owner_id and p.property_id == property_id:
print("AAAAAAAAA", p, prop, property_id)
p.notify(prop)
if layout:
obj._layout_property_changed(p)
else:
obj._property_changed(p)
def __undo_redo_do(self, undo, update_objects=None):
def get_object_position(table, row):
@ -2211,6 +2211,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
position = obj.position
list_position = obj.list_position
child_type = obj.type
inline_property_id = obj.inline_property_id
binding_expression_property_id = obj.binding_expression_property_id
self.db.ignore_check_constraints = True
self.db.execute("UPDATE object SET position=-1 WHERE ui_id=? AND object_id=?;", (ui_id, object_id))
@ -2227,6 +2229,19 @@ class CmbProject(GObject.Object, Gio.ListModel):
self.db.execute("UPDATE object SET parent_id=? WHERE ui_id=? AND object_id=?;", (new_parent_id, ui_id, object_id))
self.db.execute("UPDATE object SET position=0 WHERE ui_id=? AND object_id=?;", (ui_id, object_id))
if inline_property_id:
self.db.execute(
"UPDATE object_property SET inline_object_id=? WHERE ui_id=? AND object_id=? AND property_id=?;",
(new_parent_id, ui_id, grand_parent_id, inline_property_id)
)
if binding_expression_property_id:
self.db.execute(
"UPDATE object_property SET binding_expression_id=? WHERE ui_id=? AND object_id=? AND property_id=?;",
(new_parent_id, ui_id, grand_parent_id, binding_expression_property_id)
)
self.db.ignore_check_constraints = False
# Move all layout properties from obj to parent

View File

@ -25,7 +25,7 @@
from gi.repository import GObject
from .cmb_objects_base import CmbBaseProperty
from .cmb_objects_base import CmbBaseProperty, CmbBaseObject
from .cmb_property_info import CmbPropertyInfo
from . import utils
from cambalache import _, getLogger
@ -50,63 +50,94 @@ class CmbProperty(CmbBaseProperty):
self.connect("notify", self.__on_notify)
def __str__(self):
return f"CmbProperty<{self.object.type_id} {self.info.owner_id}:{self.property_id}>"
return f"CmbProperty<{self.object.type_id} {self.info.owner_id}::{self.property_id}>"
def __on_notify(self, obj, pspec):
self.project._object_property_changed(self.object, self)
@GObject.Property(type=str)
def value(self):
c = self.project.db.execute(
"SELECT value FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
def __db_get(self, column):
row = self.project.db.execute(
f"SELECT {column} FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
(self.ui_id, self.object_id, self.owner_id, self.property_id),
).fetchone()
return row[0] if row is not None else None
def __db_set(self, **kwargs):
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
columns = tuple(kwargs.keys())
values = tuple(kwargs.values())
placeholders = ",".join((["?"] * len(values)))
count = self.db_get(
"SELECT count(ui_id) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
(self.ui_id, self.object_id, self.owner_id, self.property_id),
)
row = c.fetchone()
return row[0] if row is not None else self.info.default_value
# Ensure row exists
if count:
# Execute update statement and return row values
statement = ",".join([f"{col}=?" for col in columns])
row = self.project.db.execute(
f"""
UPDATE object_property SET {statement} WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?
RETURNING
value, translatable, comment, translation_context, translation_comments,
inline_object_id,
bind_source_id, bind_owner_id, bind_property_id, bind_flags,
binding_expression_id, binding_expression_object_id;
""",
values + (self.ui_id, self.object_id, self.owner_id, self.property_id)
).fetchone()
# If all values are none/false we can remove the row
if all(not val for val in row):
self.reset()
else:
self.project.db.execute(
f"""
INSERT INTO object_property (ui_id, object_id, owner_id, property_id, {",".join(columns)})
VALUES (?, ?, ?, ?, {placeholders});
""",
(self.ui_id, self.object_id, self.owner_id, self.property_id) + values,
)
self.__update_internal_child()
if self._init is False:
self.object._property_changed(self)
@GObject.Property(type=str)
def value(self):
return self.__db_get("value") or self.info.default_value
@value.setter
def _set_value(self, value):
self.__update_values(value, self.translatable, self.translation_context, self.translation_comments, self.bind_property)
self.__db_set(value=value)
@GObject.Property(type=bool, default=False)
def translatable(self):
return super().translatable
return self.__db_get("translatable")
@translatable.setter
def _set_translatable(self, value):
self.__update_values(self.value, value, self.translation_context, self.translation_comments, self.bind_property)
self.__db_set(translatable=value)
@GObject.Property(type=str)
def translation_context(self):
return self.db_get(
"SELECT translation_context FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
)
return self.__db_get("translation_context")
@translation_context.setter
def _set_translation_context(self, value):
self.__update_values(self.value, self.translatable, value, self.translation_comments, self.bind_property)
self.__db_set(translation_context=value)
@GObject.Property(type=str)
def translation_comments(self):
return self.db_get(
"SELECT translation_comments FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
)
return self.__db_get("translation_comments")
@translation_comments.setter
def _set_translation_comments(self, value):
self.__update_values(self.value, self.translatable, self.translation_context, value, self.bind_property)
self.__db_set(translation_comments=value)
def reset(self):
if self.info.internal_child:
@ -155,122 +186,12 @@ class CmbProperty(CmbBaseProperty):
"value": str(value)
}
def __update_values(self, value, translatable, translation_context, translation_comments, bind_property):
c = self.project.db.cursor()
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
if bind_property:
bind_source_id = bind_property.object.object_id
bind_owner_id = bind_property.owner_id
bind_property_id = bind_property.property_id
if (
(value is None or value == self.info.default_value or (self.info.is_object and value == 0)) and
bind_property is None and
not translatable and
translation_context is None and
translation_comments is None
):
self.reset()
else:
if (
value == self.value and
translatable == self.translatable and
translation_context == self.translation_context and
translation_comments == self.translation_comments and
bind_source_id == self.bind_source_id and
bind_owner_id == self.bind_owner_id and
bind_property_id == self.bind_property_id
):
return
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
count = self.db_get(
"SELECT count(ui_id) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
(self.ui_id, self.object_id, self.owner_id, self.property_id),
)
if count:
if self.info.internal_child:
self.project.history_push(_("Update {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
c.execute(
"""
UPDATE object_property
SET
value=?,
translatable=?, translation_context=?, translation_comments=?,
bind_source_id=?, bind_owner_id=?, bind_property_id=?
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
""",
(
value,
translatable,
translation_context,
translation_comments,
bind_source_id,
bind_owner_id,
bind_property_id,
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
),
)
else:
if self.info.internal_child:
self.project.history_push(_("Set {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
c.execute(
"""
INSERT INTO object_property
(
ui_id, object_id, owner_id, property_id,
value,
translatable, translation_context, translation_comments,
bind_source_id, bind_owner_id, bind_property_id
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
(
self.ui_id,
self.object_id,
self.owner_id,
self.property_id,
value,
translatable,
translation_context,
translation_comments,
bind_source_id,
bind_owner_id,
bind_property_id,
),
)
self.__update_internal_child()
if self.info.internal_child:
self.project.history_pop()
if self._init is False:
self.object._property_changed(self)
c.close()
@GObject.Property(type=CmbBaseProperty)
def bind_property(self):
c = self.project.db.cursor()
row = c.execute(
"""
SELECT bind_source_id, bind_property_id
FROM object_property
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
""",
(self.ui_id, self.object_id, self.owner_id, self.property_id),
).fetchone()
bind_source_id = self.bind_source_id
bind_property_id = self.bind_property_id
if row:
bind_source_id, bind_property_id = row
if bind_source_id and bind_property_id:
source = self.project.get_object_by_id(self.ui_id, bind_source_id) if bind_property_id else None
return source.properties_dict.get(bind_property_id, None) if source else None
@ -278,7 +199,113 @@ class CmbProperty(CmbBaseProperty):
@bind_property.setter
def _set_bind_property(self, bind_property):
self.__update_values(self.value, self.translatable, self.translation_context, self.translation_comments, bind_property)
bind_source_id, bind_owner_id, bind_property_id, bind_flags = (None, None, None, None)
if bind_property:
obj = bind_property.object
bind_source_id = obj.object_id
bind_owner_id = bind_property.owner_id
bind_property_id = bind_property.property_id
bind_flags = bind_property.bind_flags
if obj.ui_id == self.ui_id and obj.object_id == self.bind_source_id and \
self.bind_property_id == bind_property.property_id:
return
self.project.history_push(
_("Bind {object}::{property} to {bind_object}::{bind_property}").format(
object=self.object.display_name_type,
property=self.property_id,
bind_object=obj.display_name_type,
bind_property=bind_property.property_id
)
)
else:
if not self.bind_source_id and not self.bind_property_id:
return
self.project.history_push(
_("Clear {object}::{property} binding").format(
object=self.object.display_name_type,
property=self.property_id
)
)
self.__db_set(
bind_source_id=bind_source_id,
bind_owner_id=bind_owner_id,
bind_property_id=bind_property_id,
bind_flags=bind_flags
)
self.project.history_pop()
self.project._object_property_binding_changed(self.object, self)
@GObject.Property(type=CmbBaseObject)
def binding_expression(self):
return self.project.get_object_by_id(self.ui_id, self.binding_expression_id)
@binding_expression.setter
def _set_binding_expression(self, binding_expression):
if binding_expression:
if binding_expression.ui_id == self.ui_id and binding_expression.object_id == self.binding_expression_id:
return
self.project.history_push(
_("Bind {object}::{property} to {expression_object}").format(
object=self.object.display_name_type,
property=self.property_id,
expression_object=binding_expression.display_name_type
)
)
else:
if not self.binding_expression_id:
return
self.project.history_push(
_("Clear {object}::{property} binding expression").format(
object=self.object.display_name_type,
property=self.property_id
)
)
self.__db_set(binding_expression_id=binding_expression.object_id)
self.project.history_pop()
self.project._object_property_binding_changed(self.object, self)
@GObject.Property(type=CmbBaseObject)
def binding_expression_object(self):
return self.project.get_object_by_id(self.ui_id, self.binding_expression_id)
@binding_expression.setter
def _set_binding_expression_object(self, expression_object):
if expression_object:
if expression_object.ui_id == self.ui_id and expression_object.object_id == self.expression_object_id:
return
self.project.history_push(
_("Bind {object}::{property} to {expression_object}").format(
object=self.object.display_name_type,
property=self.property_id,
expression_object=expression_object.display_name_type
)
)
else:
if not self.binding_expression_id:
return
self.project.history_push(
_("Clear {object}::{property} binding expression object").format(
object=self.object.display_name_type,
property=self.property_id
)
)
self.__db_set(binding_expression_id=expression_object.object_id)
self.project.history_pop()
self.project._object_property_binding_changed(self.object, self)
def _update_version_warning(self):
@ -297,3 +324,82 @@ class CmbProperty(CmbBaseProperty):
warning += _("Warning: GFile uri needs to be absolute for Gtk < 4.16")
self.version_warning = warning if len(warning) else None
def __clear_expression_inline_object(self):
binding_expression_id = self.binding_expression_id
if binding_expression_id:
expression_source = self.project.get_object_by_id(self.ui_id, binding_expression_id)
self.project.remove_object(expression_source)
def clear_binding(self):
if self.bind_property is None and self.bind_flags is None and self.binding_expression_id is None and \
self.binding_expression_object_id is None:
return
self.project.history_push(
_("Clear {object}::{property} binding").format(
object=self.object.display_name_type,
property=self.property_id
)
)
self.__clear_expression_inline_object()
bind_source_id = self.bind_source_id != 0
bind_owner_id = self.bind_owner_id is not None
bind_property_id = self.bind_property_id is not None
bind_flags = self.bind_flags is not None
binding_expression_id = self.binding_expression_id != 0
binding_expression_object_id = self.binding_expression_object_id != 0
self.__db_set(
bind_source_id=None,
bind_owner_id=None,
bind_property_id=None,
bind_flags=None,
binding_expression_id=None,
binding_expression_object_id=None
)
if bind_source_id:
self.notify("bind-source-id")
if bind_owner_id:
self.notify("bind-owner-id")
if bind_property_id:
self.notify("bind-property-id")
if bind_flags:
self.notify("bind-flags")
if binding_expression_id:
self.notify("binding-expression-id")
if binding_expression_object_id:
self.notify("binding-expression-object-id")
self.project.history_pop()
def set_binding_expression_type(self, expression_type):
info = self.project.type_info.get(expression_type)
if info is None:
return
self.project.history_push(
_("Bind {object}::{property} to {expression_type}").format(
object=self.object.display_name_type,
property=self.property_id,
expression_type=expression_type
)
)
self.__clear_expression_inline_object()
self.project.add_object(
self.ui_id,
info.type_id,
parent_id=self.object.object_id,
inline_property=self.property_id,
inline_binding_expression=True
)
self.notify("binding-expression-id")
self.project.history_pop()

View File

@ -25,12 +25,9 @@
from gi.repository import GObject, Gtk
from .cmb_object import CmbObject
from .cmb_property import CmbProperty
from .cmb_layout_property import CmbLayoutProperty
from .cmb_property_info import CmbPropertyInfo
from .control import CmbObjectChooser, CmbFlagsEntry
from cambalache import _
from .cmb_binding_popover import CmbBindingPopover
class CmbPropertyLabel(Gtk.Button):
@ -62,12 +59,11 @@ class CmbPropertyLabel(Gtk.Button):
self.label.props.label = self.prop.info.a11y_property_id if self.prop.info.is_a11y else self.prop.property_id
self.__update_property_label()
self.prop.connect("notify::value", lambda o, p: self.__update_property_label())
self.prop.connect("notify", self.__on_notify)
if self.bindable:
self.connect("clicked", self.__on_bind_button_clicked)
if self.layout_prop:
elif self.layout_prop:
self.bind_icon = None
self.label.props.label = self.layout_prop.property_id
self.__update_layout_property_label()
@ -76,6 +72,18 @@ class CmbPropertyLabel(Gtk.Button):
box.append(self.label)
self.set_child(box)
def __on_notify(self, prop, pspec):
if pspec.name in {
"value",
"bind-source-id",
"bind-owner-id",
"bind-property-id",
"bind-flags",
"binding-expression-id",
"binding-expression-object-id"
}:
self.__update_property_label()
def __update_label(self, prop):
if prop.value != prop.info.default_value:
self.add_css_class("modified")
@ -99,172 +107,20 @@ class CmbPropertyLabel(Gtk.Button):
if not self.bindable:
return
if self.prop.bind_property_id:
if self.prop.bind_property_id or self.prop.binding_expression_id:
self.bind_icon.props.icon_name = "binded-symbolic"
self.remove_css_class("hidden")
else:
self.bind_icon.props.icon_name = "bind-symbolic"
self.add_css_class("hidden")
def __on_object_editor_notify(self, object_editor, pspec, property_editor):
object_id = object_editor.cmb_value
if object_id:
property_editor.object = self.prop.project.get_object_by_id(self.prop.ui_id, int(object_id))
def __on_property_editor_changed(self, combo):
bind_source, bind_property = self.__find_bind_source_property(combo.object.object_id, combo.props.active_id)
self.prop.bind_property = bind_property
self.__update_property_label()
def __find_bind_source_property(self, bind_source_id, bind_property_id):
bind_source = self.prop.project.get_object_by_id(self.prop.ui_id, bind_source_id) if bind_source_id else None
bind_property = bind_source.properties_dict.get(bind_property_id, None) if bind_source else None
return bind_source, bind_property
def __on_clear_clicked(self, button, popover):
self.prop.bind_property = None
self.prop.bind_flags = None
self.__update_property_label()
popover.popdown()
def __on_close_clicked(self, button, popover):
popover.popdown()
def __on_bind_button_clicked(self, button):
popover = Gtk.Popover(position=Gtk.PositionType.LEFT, css_classes=["cmb-binding-popover"])
popover = CmbBindingPopover(prop=self.prop)
popover.set_parent(self)
label = Gtk.Label(label="<b>Property Binding</b>", use_markup=True, halign=Gtk.Align.START, hexpand=True)
close = Gtk.Button(icon_name="window-close-symbolic", halign=Gtk.Align.END, css_classes=["close"])
close.connect("clicked", self.__on_close_clicked, popover)
box = Gtk.Box(hexpand=True)
box.append(label)
box.append(close)
grid = Gtk.Grid(hexpand=True, row_spacing=6, column_spacing=6)
grid.attach(box, 0, 0, 2, 1)
# Get bind property to initialize inputs
bind_source, bind_property = self.__find_bind_source_property(self.prop.bind_source_id, self.prop.bind_property_id)
# Create Property editor
property_editor = CmbPropertyChooser(object=bind_source, target_info=self.prop.info)
property_editor.connect("changed", self.__on_property_editor_changed)
# Update active_id after letting the object populate the properties
if bind_property:
property_editor.props.active_id = bind_property.property_id
# Object editor (it does not set the object directly to CmbProperty, just choose the object in the prop chooser)
object_editor = CmbObjectChooser(parent=self.prop.object, cmb_value=bind_source.object_id if bind_source else 0)
object_editor.connect("notify::cmb-value", self.__on_object_editor_notify, property_editor)
# Flags editor
binding_flags_info = self.prop.project.type_info.get("GBindingFlags", None)
flags_editor = CmbFlagsEntry(info=binding_flags_info)
GObject.Object.bind_property(
self.prop,
"bind_flags",
flags_editor,
"cmb-value",
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
)
i = 1
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
label = Gtk.Label(label=prop_label, xalign=0)
grid.attach(label, 0, i, 1, 1)
grid.attach(editor, 1, i, 1, 1)
i += 1
clear = Gtk.Button(label=_("Clear"), halign=Gtk.Align.END)
clear.connect("clicked", self.__on_clear_clicked, popover)
grid.attach(clear, 0, i, 2, 1)
object_editor.grab_focus()
popover.set_child(grid)
# Destroy popup on close
popover.connect("closed", lambda p: p.unparent())
popover.popup()
Gtk.WidgetClass.set_css_name(CmbPropertyLabel, "CmbPropertyLabel")
class CmbPropertyChooser(Gtk.ComboBoxText):
__gtype_name__ = "CmbPropertyChooser"
object = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE)
target_info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__populate()
self.connect("notify::object", self.__on_object_notify)
def __on_object_notify(self, obj, pspec):
self.__populate()
def __populate(self):
self.remove_all()
if self.object is None:
return
target_info = self.target_info
target_type = target_info.type_id
target_type_info = self.object.project.type_info.get(target_type, None)
target_is_object = target_info.is_object
target_is_iface = target_type_info.parent_id == "interface" if target_type_info else False
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
info = prop.info
if info.is_a11y:
continue
# Ignore construct only properties
if info.construct_only:
continue
source_type_info = self.object.project.type_info.get(info.type_id, None)
source_is_object = info.is_object
source_is_iface = source_type_info.parent_id == "interface" if source_type_info else False
if target_is_object or target_is_iface:
# Ignore non object properties
if not source_is_object and not source_is_iface:
continue
# Ignore object properties of a different type
if source_type_info and not source_type_info.is_a(target_info.type_id):
continue
elif source_is_object or source_is_iface:
continue
# Enums and Flags has to be the same type
if target_type_info and target_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
continue
if source_type_info and source_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
continue
compatible = info.type_id == target_type
if not compatible:
try:
gtype_id = GObject.type_from_name(info.type_id)
gtarget_id = GObject.type_from_name(target_type)
if gtype_id and gtarget_id:
compatible = GObject.Value.type_compatible(gtype_id, gtarget_id)
if not compatible:
compatible = GObject.Value.type_transformable(gtype_id, gtarget_id)
except Exception as e: # noqa F841
self.append(prop.property_id, prop.property_id + "*")
continue
if compatible:
self.append(prop.property_id, prop.property_id)

View File

@ -73,6 +73,11 @@ class CmbTypeChooserWidget(Gtk.Box):
def __type_info_should_append(self, info):
retval = False
# Special case GtkExpression they are not instantiable but can be created as part of
# a GtkExpression property as an inline object that Cambalache will serialize as a builder expression
if self.derived_type_id == "GtkExpression" and info.parent_id == "GtkExpression":
return True
if not info.instantiable or info.layout not in [None, "container"]:
return False

View File

@ -30,7 +30,7 @@ import time
import atexit
import socket
gi.require_version('Casilda', '0.1')
gi.require_version('Casilda', '0.2')
from gi.repository import GObject, GLib, Gio, Gdk, Gtk, Casilda
from . import config

View File

@ -29,6 +29,7 @@ from .cmb_file_button import CmbFileButton
from .cmb_file_entry import CmbFileEntry
from .cmb_icon_name_entry import CmbIconNameEntry
from .cmb_pixbuf_entry import CmbPixbufEntry
from .cmb_property_chooser import CmbPropertyChooser
from .cmb_spin_button import CmbSpinButton
from .cmb_text_buffer import CmbTextBuffer
from .cmb_toplevel_chooser import CmbToplevelChooser
@ -41,4 +42,5 @@ from .cmb_source_view import CmbSourceView
from .cmb_switch import CmbSwitch
from .cmb_text_view import CmbTextView
from .cmb_translatable_popover import CmbTranslatablePopover
from .cmb_suggestion_entry import CmbSuggestionEntry
from .cmb_create_editor import cmb_create_editor

View File

@ -39,6 +39,7 @@ from .cmb_object_chooser import CmbObjectChooser
from .cmb_object_list_editor import CmbObjectListEditor
from .cmb_switch import CmbSwitch
from .cmb_text_view import CmbTextView
from .cmb_suggestion_entry import CmbSuggestionEntry
def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
@ -141,17 +142,18 @@ def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
parent=prop.object if prop else parent,
type_id="GtkAccessible",
)
elif type_id == "gtype":
editor = CmbSuggestionEntry()
editor.set_suggestions(project._get_types())
elif info:
if info.is_object or info.parent_id == "interface":
if info.is_object or info.parent_id == "interface" or type_id == "GtkExpression":
if prop is None:
editor = CmbObjectChooser(
project=project,
parent=parent,
type_id=type_id,
)
else:
editor = CmbObjectChooser(
project=project,
parent=prop.object,
is_inline=project.target_tk == "gtk-4.0" and not prop.info.disable_inline_object,
inline_object_id=prop.inline_object_id,

View File

@ -30,15 +30,16 @@ from ..cmb_type_info import CmbTypeInfo
class CmbFlagsEntry(Gtk.Entry):
__gtype_name__ = "CmbFlagsEntry"
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
id_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
text_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
value_column = GObject.Property(type=int, default=2, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
separator = GObject.Property(type=str, default="|", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
id_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
text_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
value_column = GObject.Property(type=int, default=2, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
separator = GObject.Property(type=str, default="|", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
def __init__(self, **kwargs):
self.flags = {}
self._checks = {}
self._popover = None
super().__init__(**kwargs)
@ -47,9 +48,10 @@ class CmbFlagsEntry(Gtk.Entry):
self.connect("icon-release", self.__on_icon_release)
self.__init_popover()
def __init_popover(self):
if self._popover:
return
self._popover = Gtk.Popover()
self._popover.set_parent(self)
@ -78,6 +80,7 @@ class CmbFlagsEntry(Gtk.Entry):
self.notify("cmb-value")
def __on_icon_release(self, obj, pos):
self.__init_popover()
self._popover.popup()
def __to_string(self):

View File

@ -27,33 +27,30 @@ from cambalache import _
from gi.repository import GObject, Gdk, Gtk
from ..cmb_object import CmbObject
from ..cmb_project import CmbProject
from ..cmb_type_chooser_popover import CmbTypeChooserPopover
class CmbObjectChooser(Gtk.Entry):
__gtype_name__ = "CmbObjectChooser"
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
parent = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
is_inline = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
parent = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
is_inline = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
inline_object_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
inline_property_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
type_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
inline_property_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
type_id = GObject.Property(type=str, default="GObject", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
def __init__(self, **kwargs):
self.__object_id = None
self.__updating = None
super().__init__(**kwargs)
self.connect("notify::text", self.__on_text_notify)
if self.type_id is None:
self.props.placeholder_text = "<GObject>"
return
self.connect("notify::parent", self.__on_parent_notify)
self.connect("notify::text", self.__on_text_notify)
if self.is_inline:
self.connect("icon-press", self.__on_icon_pressed)
self.parent.connect("property-changed", self.__on_parent_property_changed)
if self.parent:
self.parent.connect("property-changed", self.__on_parent_property_changed)
self.__update_icons()
else:
self.props.placeholder_text = f"<{self.type_id}>"
@ -66,8 +63,27 @@ class CmbObjectChooser(Gtk.Entry):
drop_target.connect("drop", self.__on_drop_drop)
self.add_controller(drop_target)
def __on_parent_notify(self, obj, pspec):
if self.parent:
self.parent.connect("property-changed", self.__on_parent_property_changed)
def __on_text_notify(self, obj, pspec):
if self.inline_object_id:
return
if self.__updating:
self.__updating = False
return
obj = self.parent.project.get_object_by_name(self.parent.ui_id, self.props.text)
value = obj.object_id if obj else None
if self.__object_id != value:
self.__object_id = value
self.notify("cmb-value")
def __on_parent_property_changed(self, parent, prop):
if prop.property_id != self.inline_property_id:
if not self.is_inline or prop.property_id != self.inline_property_id:
return
self.inline_object_id = prop.inline_object_id
@ -112,17 +128,6 @@ class CmbObjectChooser(Gtk.Entry):
# Select dragged object id
self.cmb_value = origin_item.object_id
def __on_text_notify(self, obj, pspec):
if self.__updating or self.inline_object_id:
return
obj = self.parent.project.get_object_by_name(self.parent.ui_id, self.props.text)
value = obj.object_id if obj else None
if self.__object_id != value:
self.__object_id = value
self.notify("cmb-value")
@GObject.Property(type=str)
def cmb_value(self):
return str(self.__object_id) if self.__object_id else None
@ -143,7 +148,6 @@ class CmbObjectChooser(Gtk.Entry):
self.props.text = obj.name if obj else ""
else:
self.props.text = ""
self.__updating = False
def __update_icons(self):
if not self.is_inline:
@ -174,6 +178,9 @@ class CmbObjectChooser(Gtk.Entry):
self.__update_icons()
def __on_icon_pressed(self, widget, icon_pos):
if not self.is_inline:
return
parent = self.parent
project = parent.project

View File

@ -0,0 +1,107 @@
#
# CmbPropertyChooser
#
# Copyright (C) 2023-2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk
from ..cmb_object import CmbObject
from ..cmb_property_info import CmbPropertyInfo
class CmbPropertyChooser(Gtk.ComboBoxText):
__gtype_name__ = "CmbPropertyChooser"
object = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE)
target_info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__populate()
self.connect("notify", self.__on_notify)
def __on_notify(self, obj, pspec):
if pspec.name in ["object", "target-info"]:
self.__populate()
def __populate(self):
self.remove_all()
if self.object is None or self.target_info is None:
return
target_info = self.target_info
target_type = target_info.type_id
target_type_info = self.object.project.type_info.get(target_type, None)
target_is_object = target_info.is_object
target_is_iface = target_type_info.parent_id == "interface" if target_type_info else False
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
info = prop.info
if info.is_a11y:
continue
# Ignore construct only properties
if info.construct_only:
continue
source_type_info = self.object.project.type_info.get(info.type_id, None)
source_is_object = info.is_object
source_is_iface = source_type_info.parent_id == "interface" if source_type_info else False
if target_is_object or target_is_iface:
# Ignore non object properties
if not source_is_object and not source_is_iface:
continue
# Ignore object properties of a different type
if source_type_info and not source_type_info.is_a(target_info.type_id):
continue
elif source_is_object or source_is_iface:
continue
# Enums and Flags has to be the same type
if target_type_info and target_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
continue
if source_type_info and source_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
continue
compatible = info.type_id == target_type
if not compatible:
try:
gtype_id = GObject.type_from_name(info.type_id)
gtarget_id = GObject.type_from_name(target_type)
if gtype_id and gtarget_id:
compatible = GObject.Value.type_compatible(gtype_id, gtarget_id)
if not compatible:
compatible = GObject.Value.type_transformable(gtype_id, gtarget_id)
except Exception as e: # noqa F841
self.append(prop.property_id, prop.property_id + "*")
continue
if compatible:
self.append(prop.property_id, prop.property_id)

View File

@ -23,7 +23,7 @@
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, GtkSource
from gi.repository import GObject, Gtk, GtkSource
class CmbSourceView(GtkSource.View):
@ -38,6 +38,27 @@ class CmbSourceView(GtkSource.View):
self.props.buffer = self.buffer
self.buffer.connect("changed", self.__on_buffer_changed)
self.connect("notify::root", self.__on_parent_notify)
self.__source_style_binding = None
def __on_parent_notify(self, obj, pspec):
if self.__source_style_binding:
self.__source_style_binding.unbind()
self.__source_style_binding = None
root = self.props.root
if root is None:
return
if isinstance(root, Gtk.ApplicationWindow) and hasattr(root, "source_style"):
self.__source_style_binding = GObject.Object.bind_property(
root,
"source-style",
self.buffer,
"style-scheme",
GObject.BindingFlags.SYNC_CREATE,
)
@GObject.Property(type=str)
def lang(self):
language = self.buffer.get_language()

View File

@ -0,0 +1,64 @@
#
# CmbSuggestionEntry
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk
from .cmb_entry import CmbEntry
class CmbSuggestionEntry(CmbEntry):
__gtype_name__ = "CmbSuggestionEntry"
def __init__(self, **kwargs):
self._filters = {}
super().__init__(**kwargs)
# GObject.Object.bind_property(self, "cmb-value", self.props.buffer, "text", GObject.BindingFlags.SYNC_CREATE)
self.type_model = Gtk.ListStore(str)
# Completion
self.__completion = Gtk.EntryCompletion()
self.__completion.props.model = self.type_model
self.__completion.props.text_column = 0
self.__completion.props.inline_completion = True
self.__completion.props.inline_selection = True
self.__completion.props.popup_set_width = True
self.props.completion = self.__completion
# self.__completion.set_match_func(lambda o, key, iter, d: key in self.type_model[iter][0], None)
# String
renderer_text = Gtk.CellRendererText()
self.__completion.pack_start(renderer_text, False)
self.__completion.add_attribute(renderer_text, "text", 0)
def set_suggestions(self, suggestions):
self.type_model.clear()
for suggestion in suggestions:
self.type_model.append([suggestion])
def __model_filter_func(self, model, iter, data):
return model[iter][0] == data

View File

@ -15,8 +15,10 @@ install_data([
'cmb_object_chooser.py',
'cmb_object_list_editor.py',
'cmb_pixbuf_entry.py',
'cmb_property_chooser.py',
'cmb_source_view.py',
'cmb_spin_button.py',
'cmb_suggestion_entry.py',
'cmb_switch.py',
'cmb_text_buffer.py',
'cmb_text_view.py',

View File

@ -262,9 +262,13 @@ CREATE TABLE object_property (
bind_owner_id TEXT REFERENCES type ON UPDATE CASCADE,
bind_property_id TEXT,
bind_flags TEXT,
binding_expression_id INTEGER,
binding_expression_object_id INTEGER,
PRIMARY KEY(ui_id, object_id, owner_id, property_id),
FOREIGN KEY(ui_id, object_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
FOREIGN KEY(ui_id, inline_object_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
FOREIGN KEY(ui_id, binding_expression_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
FOREIGN KEY(ui_id, binding_expression_object_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
FOREIGN KEY(owner_id, property_id) REFERENCES property,
FOREIGN KEY(ui_id, bind_source_id) REFERENCES object(ui_id, object_id),
FOREIGN KEY(bind_owner_id, bind_property_id) REFERENCES property(owner_id, property_id) ON DELETE SET NULL

View File

@ -93,6 +93,8 @@ install_data([
'mrg_adw/mrg_adw_bin.py',
'mrg_adw/mrg_adw_carousel.py',
'mrg_adw/mrg_adw_dialog.py',
'mrg_adw/mrg_adw_view_stack.py',
'mrg_adw/mrg_adw_view_stack_page.py',
'mrg_adw/mrg_adw_window.py',
],
install_dir: join_paths(moduledir, 'mrg_adw')

View File

@ -34,4 +34,6 @@ from .mrg_adw_bin import MrgAdwBin
from .mrg_adw_carousel import MrgAdwCarousel
from .mrg_adw_window import MrgAdwWindow
from .mrg_adw_dialog import MrgAdwDialog
from .mrg_adw_view_stack import MrgAdwViewStack
from .mrg_adw_view_stack_page import MrgAdwViewStackPage

View File

@ -0,0 +1,40 @@
# MrgAdwViewStack Controller
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Adw
from merengue.mrg_gtk import MrgGtkStack
class MrgAdwViewStack(MrgGtkStack):
object = GObject.Property(type=Adw.ViewStack, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def add(self, child):
if self.object is None:
return
self.object.add(child)

View File

@ -0,0 +1,33 @@
# MrgAdwViewStack Controller
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Adw
from merengue.mrg_gtk import MrgGtkStackPage
class MrgAdwViewStackPage(MrgGtkStackPage):
object = GObject.Property(type=Adw.ViewStackPage, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)

View File

@ -258,10 +258,10 @@ class MrgApplication(Gtk.Application):
controller.remove_placeholder(modifier)
def load_namespace(self, namespace, version, object_types):
if version:
gi.require_version(namespace, version)
try:
if version:
gi.require_version(namespace, version)
mod = importlib.import_module(f"gi.repository.{namespace}")
except Exception as e:
logger.warning(e)

View File

@ -22,10 +22,4 @@
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject
from .mrg_webkit_web_view import MrgWebKitWebView
from .mrg_webkit_web_view import MrgDummyWebView
from .mrg_webkit_web_view import MrgDummyWebViewProxy
GObject.type_ensure(MrgDummyWebViewProxy.__gtype__)

View File

@ -22,7 +22,7 @@
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk, WebKit
from gi.repository import GObject, WebKit
from merengue.mrg_gtk import MrgGtkWidget
@ -36,7 +36,6 @@ class MrgWebKitWebView(MrgGtkWidget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
logger.warning("MrgWebKitWebView __init__")
def object_changed(self, old, new):
super().object_changed(old, new)
@ -53,20 +52,21 @@ class MrgWebKitWebView(MrgGtkWidget):
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: table;
}
div.content {
display: table-cell;
text-align: center;
vertical-align: middle;
border: 3px groove lightgray;
border: 3px solid lightgray;
border-radius: 1em;
padding: 1em;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
min-height: calc(100vh - 2em);
}
</style>
@ -85,14 +85,15 @@ function open_url() {
<div class="content">
<h3>WebKit Test Page</h3>
<span>URL:</span>
<input type="text" id="url_entry" />
<input type="text" id="url_entry" placeholder="Enter a URL" />
<input type="button" value="Open" onclick="open_url()" />
<br/>
<br/>
<a href="https://gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org/">WebKitGtk</a>
<a href="https://gitlab.gnome.org/jpu/cambalache" title="gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org" title="webkitgtk.org">WebKitGtk</a>
<a href="https://browserbench.org/Speedometer3.1" title="browserbench.org/Speedometer3.1">Speedometer</a>
</div>
</body>
</html>
@ -100,47 +101,3 @@ function open_url() {
".",
)
class MrgDummyWebViewProxy(Gtk.Label):
__gtype_name__ = "MrgDummyWebViewProxy"
automation_presentation_type = GObject.Property(
type=WebKit.AutomationBrowsingContextPresentation,
default=WebKit.AutomationBrowsingContextPresentation.WINDOW,
flags=GObject.ParamFlags.READWRITE,
)
camera_capture_state = GObject.Property(
type=WebKit.MediaCaptureState, default=WebKit.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
default_content_security_policy = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
display_capture_state = GObject.Property(
type=WebKit.MediaCaptureState, default=WebKit.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
editable = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_controlled_by_automation = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_ephemeral = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_muted = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
microphone_capture_state = GObject.Property(
type=WebKit.MediaCaptureState, default=WebKit.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
related_view = GObject.Property(type=WebKit.WebView, flags=GObject.ParamFlags.READWRITE)
settings = GObject.Property(type=WebKit.Settings, flags=GObject.ParamFlags.READWRITE)
user_content_manager = GObject.Property(type=WebKit.UserContentManager, flags=GObject.ParamFlags.READWRITE)
web_context = GObject.Property(type=WebKit.WebContext, flags=GObject.ParamFlags.READWRITE)
web_extension_mode = GObject.Property(
type=WebKit.WebExtensionMode, default=WebKit.WebExtensionMode.NONE, flags=GObject.ParamFlags.READWRITE
)
website_policies = GObject.Property(type=WebKit.WebsitePolicies, flags=GObject.ParamFlags.READWRITE)
zoom_level = GObject.Property(type=float, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.props.label = "WebKit.WebView\nplaceholder"
self.props.justify = Gtk.Justification.CENTER
class MrgDummyWebView(MrgGtkWidget):
object = GObject.Property(type=MrgDummyWebViewProxy, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)

View File

@ -22,10 +22,4 @@
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject
from .mrg_webkit_web_view import MrgWebKitWebView
from .mrg_webkit_web_view import MrgDummyWebView
from .mrg_webkit_web_view import MrgDummyWebViewProxy
GObject.type_ensure(MrgDummyWebViewProxy.__gtype__)

View File

@ -22,7 +22,7 @@
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk, WebKit2
from gi.repository import GObject, WebKit2
from merengue.mrg_gtk import MrgGtkWidget
@ -36,7 +36,6 @@ class MrgWebKitWebView(MrgGtkWidget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
logger.warning("MrgWebKitWebView __init__")
def object_changed(self, old, new):
super().object_changed(old, new)
@ -53,20 +52,21 @@ class MrgWebKitWebView(MrgGtkWidget):
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: table;
}
div.content {
display: table-cell;
text-align: center;
vertical-align: middle;
border: 3px groove lightgray;
border: 3px solid lightgray;
border-radius: 1em;
padding: 1em;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
min-height: calc(100vh - 2em);
}
</style>
@ -85,14 +85,15 @@ function open_url() {
<div class="content">
<h3>WebKit Test Page</h3>
<span>URL:</span>
<input type="text" id="url_entry" />
<input type="text" id="url_entry" placeholder="Enter a URL" />
<input type="button" value="Open" onclick="open_url()" />
<br/>
<br/>
<a href="https://gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org/">WebKitGtk</a>
<a href="https://gitlab.gnome.org/jpu/cambalache" title="gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org" title="webkitgtk.org">WebKitGtk</a>
<a href="https://browserbench.org/Speedometer3.1" title="browserbench.org/Speedometer3.1">Speedometer</a>
</div>
</body>
</html>
@ -100,47 +101,3 @@ function open_url() {
".",
)
class MrgDummyWebViewProxy(Gtk.Label):
__gtype_name__ = "MrgDummyWebViewProxy"
automation_presentation_type = GObject.Property(
type=WebKit2.AutomationBrowsingContextPresentation,
default=WebKit2.AutomationBrowsingContextPresentation.WINDOW,
flags=GObject.ParamFlags.READWRITE,
)
camera_capture_state = GObject.Property(
type=WebKit2.MediaCaptureState, default=WebKit2.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
default_content_security_policy = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
display_capture_state = GObject.Property(
type=WebKit2.MediaCaptureState, default=WebKit2.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
editable = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_controlled_by_automation = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_ephemeral = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
is_muted = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
microphone_capture_state = GObject.Property(
type=WebKit2.MediaCaptureState, default=WebKit2.MediaCaptureState.NONE, flags=GObject.ParamFlags.READWRITE
)
related_view = GObject.Property(type=WebKit2.WebView, flags=GObject.ParamFlags.READWRITE)
settings = GObject.Property(type=WebKit2.Settings, flags=GObject.ParamFlags.READWRITE)
user_content_manager = GObject.Property(type=WebKit2.UserContentManager, flags=GObject.ParamFlags.READWRITE)
web_context = GObject.Property(type=WebKit2.WebContext, flags=GObject.ParamFlags.READWRITE)
web_extension_mode = GObject.Property(
type=WebKit2.WebExtensionMode, default=WebKit2.WebExtensionMode.NONE, flags=GObject.ParamFlags.READWRITE
)
website_policies = GObject.Property(type=WebKit2.WebsitePolicies, flags=GObject.ParamFlags.READWRITE)
zoom_level = GObject.Property(type=float, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.props.label = "WebKit2.WebView\nplaceholder"
self.props.justify = Gtk.Justification.CENTER
class MrgDummyWebView(MrgGtkWidget):
object = GObject.Property(type=MrgDummyWebViewProxy, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)

View File

@ -26,13 +26,13 @@ configure_file(
install_data([
'cmb_accessible_editor.py',
'cmb_base.py',
'cmb_binding_popover.py',
'cmb_blueprint.py',
'cmb_context_menu.py',
'cmb_css.py',
'cmb_css_editor.py',
'cmb_db.py',
'cmb_db_inspector.py',
'cmb_db_migration.py',
'cmb_db_profile.py',
'cmb_fragment_editor.py',
'cmb_gresource.py',

View File

@ -2,8 +2,6 @@ CMB_CATALOG_GEN=flatpak run --devel \
--command=cmb-catalog-gen \
ar.xjuan.Cambalache
# CMB_CATALOG_GEN=../run-cmb-catalog-gen.sh
BASE_CATALOG_FILES = \
glib/gobject-2.0.xml \
glib/gio-2.0.xml \

View File

@ -492,11 +492,11 @@
("GResourceFlags","G_RESOURCE_FLAGS_NONE","none",None,"No flags set."),
("GResourceLookupFlags","G_RESOURCE_LOOKUP_FLAGS_NONE","none",None,"No flags set."),
("GSettingsBindFlags","G_SETTINGS_BIND_DEFAULT","default",None,"Equivalent to `G_SETTINGS_BIND_GET|G_SETTINGS_BIND_SET`"),
("GSettingsBindFlags","G_SETTINGS_BIND_GET","get",1,"Update the #GObject property when the setting changes. It is an error to use this flag if the property is not writable."),
("GSettingsBindFlags","G_SETTINGS_BIND_GET_NO_CHANGES","get-no-changes",8,"When set in addition to %G_SETTINGS_BIND_GET, set the #GObject property value initially from the setting, but do not listen for changes of the setting"),
("GSettingsBindFlags","G_SETTINGS_BIND_INVERT_BOOLEAN","invert-boolean",16,"When passed to g_settings_bind(), uses a pair of mapping functions that invert the boolean value when mapping between the setting and the property. The setting and property must both be booleans. You cannot pass this flag to g_settings_bind_with_mapping()."),
("GSettingsBindFlags","G_SETTINGS_BIND_NO_SENSITIVITY","no-sensitivity",4,"Do not try to bind a \"sensitivity\" property to the writability of the setting"),
("GSettingsBindFlags","G_SETTINGS_BIND_SET","set",2,"Update the setting when the #GObject property changes. It is an error to use this flag if the property is not readable."),
("GSettingsBindFlags","G_SETTINGS_BIND_GET","get",1,"Update the [class@GObject.Object] property when the setting changes. It is an error to use this flag if the property is not writable."),
("GSettingsBindFlags","G_SETTINGS_BIND_GET_NO_CHANGES","get-no-changes",8,"When set in addition to [flags@Gio.SettingsBindFlags.GET], set the [class@GObject.Object] property value initially from the setting, but do not listen for changes of the setting"),
("GSettingsBindFlags","G_SETTINGS_BIND_INVERT_BOOLEAN","invert-boolean",16,"When passed to [method@Gio.Settings.bind], uses a pair of mapping functions that invert the boolean value when mapping between the setting and the property. The setting and property must both be booleans. You cannot pass this flag to [method@Gio.Settings.bind_with_mapping]."),
("GSettingsBindFlags","G_SETTINGS_BIND_NO_SENSITIVITY","no-sensitivity",4,"Do not try to bind a sensitivity property to the writability of the setting"),
("GSettingsBindFlags","G_SETTINGS_BIND_SET","set",2,"Update the setting when the [class@GObject.Object] property changes. It is an error to use this flag if the property is not readable."),
("GSocketMsgFlags","G_SOCKET_MSG_DONTROUTE","dontroute",4,"Don't use a gateway to send out the packet, only send to hosts on directly connected networks."),
("GSocketMsgFlags","G_SOCKET_MSG_NONE","none",None,"No flags."),
("GSocketMsgFlags","G_SOCKET_MSG_OOB","oob",1,"Request to send/receive out of band data."),

View File

@ -5,10 +5,6 @@
Copyright (C) 2022 Juan Pablo Ugarte
-->
<meta>
<types>
<WebKitWebView workspace-type="MrgDummyWebViewProxy"/>
</types>
<categories>
<category name="hidden">

View File

@ -610,6 +610,7 @@
("AdwClampScrollable","unit","AdwLengthUnit",None,None,None,"sp",None,None,"1.4",None,None,None,None,None,None,None,None),
("AdwComboRow","accessible-role","GtkAccessibleRole",None,1,None,"combo-box",None,None,None,None,None,None,None,None,None,"GtkAccessible",None),
("AdwComboRow","enable-search","gboolean",None,None,None,"False",None,None,"1.4",None,None,None,None,None,None,None,None),
("AdwComboRow","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("AdwComboRow","factory","GtkListItemFactory",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("AdwComboRow","header-factory","GtkListItemFactory",1,None,None,None,None,None,"1.6",None,None,None,None,None,None,None,None),
("AdwComboRow","list-factory","GtkListItemFactory",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),

View File

@ -50,7 +50,7 @@
("WebKitWebExtensionMatchPatternOptions","flags","webkit2gtk",None,None,None,None,None,None,None),
("WebKitWebExtensionMode","enum","webkit2gtk",None,None,None,None,None,None,None),
("WebKitWebProcessTerminationReason","enum","webkit2gtk",None,None,None,None,None,None,None),
("WebKitWebView","WebKitWebViewBase","webkit2gtk",None,None,None,1,"container",None,"MrgDummyWebViewProxy"),
("WebKitWebView","WebKitWebViewBase","webkit2gtk",None,None,None,1,"container",None,None),
("WebKitWebViewBase","GtkContainer","webkit2gtk",None,None,None,1,"container","hidden",None),
("WebKitWebsiteDataTypes","flags","webkit2gtk",None,None,None,None,None,None,None)
</type>

View File

@ -49,7 +49,7 @@
("WebKitWebExtensionMatchPatternOptions","flags","webkitgtk",None,None,None,None,None,None,None),
("WebKitWebExtensionMode","enum","webkitgtk",None,None,None,None,None,None,None),
("WebKitWebProcessTerminationReason","enum","webkitgtk",None,None,None,None,None,None,None),
("WebKitWebView","WebKitWebViewBase","webkitgtk",None,None,None,1,"container",None,"MrgDummyWebViewProxy"),
("WebKitWebView","WebKitWebViewBase","webkitgtk",None,None,None,1,"container",None,None),
("WebKitWebViewBase","GtkWidget","webkitgtk",None,None,None,1,None,"hidden",None),
("WebKitWebsiteDataTypes","flags","webkitgtk",None,None,None,None,None,None,None)
</type>

View File

@ -18,6 +18,22 @@
</properties>
</GtkWidget>
<GtkPropertyExpression target='Gtk-4.0'>
<children-constraints>
<constraint shortcut="True">GtkConstantExpression</constraint>
<constraint shortcut="True">GtkPropertyExpression</constraint>
<constraint shortcut="True">GtkClosureExpression</constraint>
</children-constraints>
</GtkPropertyExpression>
<GtkClosureExpression target='Gtk-4.0'>
<children-constraints>
<constraint shortcut="True">GtkConstantExpression</constraint>
<constraint shortcut="True">GtkPropertyExpression</constraint>
<constraint shortcut="True">GtkClosureExpression</constraint>
</children-constraints>
</GtkClosureExpression>
<GtkAccessible>
<properties>
<property id="accessible-role" construct-only="True"/>

View File

@ -2278,7 +2278,7 @@
("GtkSettings","gtk-modules","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkSettings","gtk-overlay-scrolling","gboolean",None,None,None,"True",None,None,"3.24.9",None,None,None,None,None,None,None,None),
("GtkSettings","gtk-primary-button-warps-slider","gboolean",None,None,None,"True",None,None,"3.6",None,None,None,None,None,None,None,None),
("GtkSettings","gtk-print-backends","gchararray",None,None,None,"file,lpr,cups",None,None,None,None,None,None,None,None,None,None,None),
("GtkSettings","gtk-print-backends","gchararray",None,None,None,"file,cups",None,None,None,None,None,None,None,None,None,None,None),
("GtkSettings","gtk-print-preview-command","gchararray",None,None,None,"evince --unlink-tempfile --preview --print-settings %s %f",None,None,None,None,None,None,None,None,None,None,None),
("GtkSettings","gtk-recent-files-enabled","gboolean",None,None,None,"True",None,None,"3.8",None,None,None,None,None,None,None,None),
("GtkSettings","gtk-recent-files-limit","gint",None,None,None,"50","-1","2147483647",None,"3.10",None,None,None,None,None,None,None),

View File

@ -63,7 +63,6 @@
("GtkBuilderScope","interface","gtk",None,None,None,None,None,None,None),
("GtkButton","GtkWidget","gtk",None,None,None,1,"container","control",None),
("GtkButtonsType","enum","gtk",None,None,None,None,None,None,None),
("GtkCClosureExpression","GtkExpression","gtk",None,None,None,None,None,None,None),
("GtkCalendar","GtkWidget","gtk",None,None,None,None,"container","display",None),
("GtkCallbackAction","GtkShortcutAction","gtk",None,None,None,1,None,None,None),
("GtkCellArea","GObject","gtk",None,"4.10",1,1,"container",None,None),
@ -261,7 +260,6 @@
("GtkNothingAction","GtkShortcutAction","gtk",None,None,None,1,None,None,None),
("GtkNumberUpLayout","enum","gtk",None,None,None,None,None,None,None),
("GtkNumericSorter","GtkSorter","gtk",None,None,None,1,None,None,None),
("GtkObjectExpression","GtkExpression","gtk",None,None,None,None,None,None,None),
("GtkOrdering","enum","gtk",None,None,None,None,None,None,None),
("GtkOrientable","interface","gtk",None,None,None,None,None,None,None),
("GtkOrientation","enum","gtk",None,None,None,None,None,None,None),
@ -278,7 +276,6 @@
("GtkPageSetupUnixDialog","GtkDialog","gtk",None,None,None,None,"container",None,None),
("GtkPanDirection","enum","gtk",None,None,None,None,None,None,None),
("GtkPaned","GtkWidget","gtk",None,None,None,None,"container","layout",None),
("GtkParamSpecExpression","GObject","gtk",None,None,None,None,None,None,None),
("GtkPasswordEntry","GtkWidget","gtk",None,None,None,1,"container",None,None),
("GtkPasswordEntryBuffer","GtkEntryBuffer","gtk",None,None,None,1,None,None,None),
("GtkPickFlags","flags","gtk",None,None,None,None,None,None,None),
@ -1378,7 +1375,7 @@
("GtkPrintError","GTK_PRINT_ERROR_NOMEM","nomem",2,"A memory allocation failed."),
("GtkPrintOperationAction","GTK_PRINT_OPERATION_ACTION_EXPORT","export",3,"Export to a file. This requires the export-filename property to be set."),
("GtkPrintOperationAction","GTK_PRINT_OPERATION_ACTION_PREVIEW","preview",2,"Show the print preview."),
("GtkPrintOperationAction","GTK_PRINT_OPERATION_ACTION_PRINT","print",1,"Start to print without showing the print dialog, based on the current print settings."),
("GtkPrintOperationAction","GTK_PRINT_OPERATION_ACTION_PRINT","print",1,"Start to print without showing the print dialog, based on the current print settings, if possible. Depending on the platform, a print dialog might appear anyway."),
("GtkPrintOperationAction","GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG","print-dialog",None,"Show the print dialog."),
("GtkPrintOperationResult","GTK_PRINT_OPERATION_RESULT_APPLY","apply",1,"The print settings should be stored."),
("GtkPrintOperationResult","GTK_PRINT_OPERATION_RESULT_CANCEL","cancel",2,"The print operation has been canceled, the print settings should not be stored."),
@ -1755,6 +1752,12 @@
("GtkWindow","titlebar",1,None)
</type_child_type>
<type_child_constraint>
("GtkClosureExpression","GtkClosureExpression",1,1),
("GtkClosureExpression","GtkConstantExpression",1,1),
("GtkClosureExpression","GtkPropertyExpression",1,1),
("GtkPropertyExpression","GtkClosureExpression",1,1),
("GtkPropertyExpression","GtkConstantExpression",1,1),
("GtkPropertyExpression","GtkPropertyExpression",1,1),
("GtkStack","GtkStackPage",1,1)
</type_child_constraint>
<type_internal_child>
@ -1885,6 +1888,7 @@
("GtkBookmarkList","attributes","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkBookmarkList","filename","gchararray",None,1,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkBookmarkList","io-priority","gint",None,None,None,"0","-2147483647","2147483647",None,None,None,None,None,None,None,None,None),
("GtkBoolFilter","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkBoolFilter","invert","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkBox","baseline-child","gint",None,None,None,"-1","-1","2147483647","4.12",None,None,None,None,None,None,None,None),
("GtkBox","baseline-position","GtkBaselinePosition",None,None,None,"center",None,None,None,None,None,None,None,None,None,None,None),
@ -2038,6 +2042,8 @@
("GtkCheckButton","inconsistent","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkCheckButton","label","gchararray",None,None,None,None,None,None,None,None,1,None,None,None,None,None,None),
("GtkCheckButton","use-underline","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkClosureExpression","function","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkClosureExpression","type","gtype",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkColorButton","css-name","gchararray",None,1,None,"colorbutton",None,None,None,None,None,None,None,None,None,"GtkWidget",None),
("GtkColorButton","modal","gboolean",None,None,None,"True",None,None,None,None,None,None,None,None,None,None,None),
("GtkColorButton","show-editor","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
@ -2099,6 +2105,8 @@
("GtkComboBox","popup-fixed-width","gboolean",None,None,None,"True",None,None,None,None,None,None,None,None,None,None,None),
("GtkComboBoxText","entry-text-column","gint",None,None,None,"0","-1","2147483647",None,None,None,None,None,None,None,"GtkComboBox",None),
("GtkComboBoxText","id-column","gint",None,None,None,"1","-1","2147483647",None,None,None,None,None,None,None,"GtkComboBox",None),
("GtkConstantExpression","type","gtype",None,None,None,"GObject",None,None,None,None,None,None,None,None,None,None,None),
("GtkConstantExpression","value","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkConstraint","constant","gdouble",None,1,None,"0.0","-1.79769313486232e+308","1.79769313486232e+308",None,None,None,None,None,None,None,None,None),
("GtkConstraint","multiplier","gdouble",None,1,None,"1.0","-1.79769313486232e+308","1.79769313486232e+308",None,None,None,None,None,None,None,None,None),
("GtkConstraint","relation","GtkConstraintRelation",None,1,None,"eq",None,None,None,None,None,None,None,None,None,None,None),
@ -2131,6 +2139,7 @@
("GtkDropDown","accessible-role","GtkAccessibleRole",None,None,None,"combo-box",None,None,None,None,None,None,None,None,None,"GtkAccessible",None),
("GtkDropDown","css-name","gchararray",None,1,None,"dropdown",None,None,None,None,None,None,None,None,None,"GtkWidget",None),
("GtkDropDown","enable-search","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkDropDown","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkDropDown","factory","GtkListItemFactory",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkDropDown","header-factory","GtkListItemFactory",1,None,None,None,None,None,"4.12",None,None,None,None,None,None,None,None),
("GtkDropDown","list-factory","GtkListItemFactory",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
@ -2469,7 +2478,6 @@
("GtkMediaStream","loop","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkMediaStream","muted","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkMediaStream","playing","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkMediaStream","prepared","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkMediaStream","volume","gdouble",None,None,None,"1.0","0.0","1.0",None,None,None,None,None,None,None,None,None),
("GtkMenuButton","accessible-role","GtkAccessibleRole",None,None,None,"button",None,None,None,None,None,None,None,None,None,"GtkAccessible",None),
("GtkMenuButton","active","gboolean",None,None,None,"False",None,None,"4.10",None,None,None,None,None,None,None,None),
@ -2522,6 +2530,7 @@
("GtkNotebookPage","tab-expand","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkNotebookPage","tab-fill","gboolean",None,None,None,"True",None,None,None,None,None,None,None,None,None,None,None),
("GtkNotebookPage","tab-label","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkNumericSorter","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkNumericSorter","sort-order","GtkSortType",None,None,None,"ascending",None,None,None,None,None,None,None,None,None,None,None),
("GtkOrientable","orientation","GtkOrientation",None,None,None,"horizontal",None,None,None,None,None,None,None,None,None,None,None),
("GtkOverlay","child","GtkWidget",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
@ -2620,6 +2629,9 @@
("GtkProgressBar","pulse-step","gdouble",None,None,None,"0.1","0.0","1.0",None,None,None,None,None,None,None,None,None),
("GtkProgressBar","show-text","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),
("GtkProgressBar","text","gchararray",None,None,None,None,None,None,None,None,1,None,None,None,None,None,None),
("GtkPropertyExpression","name","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkPropertyExpression","type","gtype",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkPropertyExpression","value","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkRange","adjustment","GtkAdjustment",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkRange","css-name","gchararray",None,1,None,"range",None,None,None,None,None,None,None,None,None,"GtkWidget",None),
("GtkRange","fill-level","gdouble",None,None,None,"1.79769313486232e+308","-1.79769313486232e+308","1.79769313486232e+308",None,None,None,None,None,None,None,None,None),
@ -2830,11 +2842,13 @@
("GtkStackSwitcher","css-name","gchararray",None,1,None,"stackswitcher",None,None,None,None,None,None,None,None,None,"GtkWidget",None),
("GtkStackSwitcher","stack","GtkStack",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkStatusbar","css-name","gchararray",None,1,None,"statusbar",None,None,None,None,None,None,None,None,None,"GtkWidget",None),
("GtkStringFilter","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkStringFilter","ignore-case","gboolean",None,None,None,"True",None,None,None,None,None,None,None,None,None,None,None),
("GtkStringFilter","match-mode","GtkStringFilterMatchMode",None,None,None,"substring",None,None,None,None,None,None,None,None,None,None,None),
("GtkStringFilter","search","gchararray",None,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkStringList","strings","GStrv",None,1,None,None,None,None,"4.10",None,None,None,None,None,None,None,None),
("GtkStringSorter","collation","GtkCollation",None,None,None,"unicode",None,None,"4.10",None,None,None,None,None,None,None,None),
("GtkStringSorter","expression","GtkExpression",1,None,None,None,None,None,None,None,None,None,None,None,None,None,None),
("GtkStringSorter","ignore-case","gboolean",None,None,None,"True",None,None,None,None,None,None,None,None,None,None,None),
("GtkSwitch","accessible-role","GtkAccessibleRole",None,None,None,"switch",None,None,None,None,None,None,None,None,None,"GtkAccessible",None),
("GtkSwitch","active","gboolean",None,None,None,"False",None,None,None,None,None,None,None,None,None,None,None),

View File

@ -1,6 +1,6 @@
project(
'cambalache', 'c',
version: '0.97.6',
version: '0.97.7',
meson_version: '>= 1.1.0',
default_options: [
'c_std=c11',

View File

@ -1,22 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.13.1" target_tk="gtk+-3.0">
<ui>
(1,None,None,"test_0.17.3.ui",None,None,None,None,None,None,None)
</ui>
<ui_library>
(1,"gtk","3.24",None)
</ui_library>
<object>
(1,1,"GtkWindow",None,None,None,None,None,-1,None),
(1,2,"GtkButton",None,1,None,None,None,-1,None),
</object>
<object_property>
(1,1,"GtkWindow","title","nice translatable title",1,None,None,None,None,None,None,None,None),
(1,2,"GtkButton","label","another tranlatable string",1,None,None,None,None,None,None,None,None),
</object_property>
<object_signal>
(1,1,1,"GtkWindow","activate-default","test_swap",None,None,1,None,None),
(2,1,1,"GtkWindow","activate-focus","test_after",None,None,None,1,None)
</object_signal>
</cambalache-project>

View File

@ -1,39 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.10.3" target_tk="gtk+-3.0">
<ui>
(1,None,"test_format.ui","test_format.ui",None,None,None,None,None,None)
</ui>
<object>
(1,1,"GtkWindow",None,None,None,None,None,None),
(1,2,"GtkGrid",None,1,None,None,None,-1),
(1,3,"GtkLabel",None,2,None,None,None,None),
(1,4,"GtkEntry",None,2,None,None,None,1),
(1,5,"GtkButton",None,2,None,None,None,4),
(1,6,"GtkEntryBuffer","abuffer",None,None,None,None,None)
</object>
<object_property>
(1,3,"GtkLabel","label","hello world",None,None,None,None,None),
(1,4,"GtkEntry","max-width-chars","21",None,None,None,None,None),
(1,4,"GtkEntry","placeholder-text","Hola Mundo",None,None,None,None,None),
(1,5,"GtkButton","label","button",None,None,None,None,None),
(1,6,"GtkEntryBuffer","text","lorem ipsum",None,None,None,None,None)
</object_property>
<object_layout_property>
(1,2,3,"GtkGridLayoutChild","height","1",None,None,None,None),
(1,2,3,"GtkGridLayoutChild","left-attach","0",None,None,None,None),
(1,2,3,"GtkGridLayoutChild","top-attach","0",None,None,None,None),
(1,2,3,"GtkGridLayoutChild","width","1",None,None,None,None),
(1,2,4,"GtkGridLayoutChild","height","1",None,None,None,None),
(1,2,4,"GtkGridLayoutChild","left-attach","1",None,None,None,None),
(1,2,4,"GtkGridLayoutChild","top-attach","1",None,None,None,None),
(1,2,4,"GtkGridLayoutChild","width","1",None,None,None,None),
(1,2,5,"GtkGridLayoutChild","height","1",None,None,None,None),
(1,2,5,"GtkGridLayoutChild","left-attach","2",None,None,None,None),
(1,2,5,"GtkGridLayoutChild","top-attach","2",None,None,None,None),
(1,2,5,"GtkGridLayoutChild","width","1",None,None,None,None)
</object_layout_property>
<object_signal>
(1,1,5,"GtkButton","clicked","on_button_clicked",None,None,None,None,None)
</object_signal>
</cambalache-project>

View File

@ -0,0 +1,42 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.97.6 -->
<interface>
<!-- interface-name expression.ui -->
<requires lib="gio" version="2.0"/>
<requires lib="gtk" version="4.0"/>
<object class="GtkWindow" id="win1">
<property name="title">Window Title</property>
<property name="tooltip-text">hola test</property>
</object>
<object class="GtkDropDown" id="drop1">
<property name="expression">
<constant type="gchararray">A simple constant expression</constant>
</property>
</object>
<object class="GtkDropDown" id="drop2">
<property name="expression">
<lookup name="title" type="GtkWindow">win1</lookup>
</property>
</object>
<object class="GtkDropDown" id="drop3">
<property name="expression">
<closure function="combine_args_somehow" type="gchararray">
<constant type="gchararray">search term: </constant>
<lookup name="expression" type="GtkDropDown">drop1</lookup>
</closure>
</property>
</object>
<object class="GtkWindow" id="win2">
<property name="title">Another Window Title</property>
</object>
<object class="GtkWindow" id="win3">
<binding name="title">
<lookup name="title">win1</lookup>
</binding>
</object>
<object class="GtkWindow" id="win4">
<binding name="title" object="win2">
<lookup name="title">win1</lookup>
</binding>
</object>
</interface>

View File

@ -1,19 +1,37 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.12.0" target_tk="gtk-4.0">
<!-- Created with Cambalache 0.96.1 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
<ui>
(1,None,"template_inline_object.ui","template_inline_object.ui",None,None,None,None,None,None,None),
(2,1,None,"my-window.ui",None,None,None,None,None,None,None)
</ui>
<object>
(1,1,"MyWindow",None,None,None,None,None,None,None),
(2,1,"GtkWindow","MyWindow",None,None,None,None,None,None),
(2,2,"GtkBox",None,1,None,None,None,None,None),
(2,3,"GtkLabel",None,2,None,None,None,1,None)
<requires>MyWindow</requires>
<content><![CDATA[<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.96.1 -->
<interface>
<!-- interface-name template_inline_object.ui -->
<requires lib="gtk" version="4.6"/>
<object class="MyWindow">
<property name="title">This window should have a label inside a inline box as child</property>
</object>
<object_property>
(1,1,"GtkWindow","title","This window should have a label inside a inline box as child",None,None,None,None,None,None,None,None,None),
(2,1,"GtkWindow","child",None,None,None,None,None,2,None,None,None,None),
(2,3,"GtkLabel","label","a label inside an inline object",None,None,None,None,None,None,None,None,None)
</object_property>
</interface>
]]></content>
</ui>
<ui template-class="MyWindow">
<content><![CDATA[<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.96.1 -->
<interface>
<requires lib="gtk" version="4.6"/>
<template class="MyWindow" parent="GtkWindow">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="label">a label inside an inline object</property>
</object>
</child>
</object>
</property>
</template>
</interface>
]]></content>
</ui>
</cambalache-project>

View File

@ -1,29 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.13.1" target_tk="gtk-4.0">
<ui>
(1,None,None,"test_0.17.3.ui",None,None,None,None,None,None,None)
</ui>
<ui_library>
(1,"gtk","4.12",None)
</ui_library>
<object>
(1,1,"GtkWindow",None,None,None,None,None,-1,None),
(1,2,"GtkButton",None,1,None,None,None,-1,None),
(1,3,"GtkStringList",None,None,None,None,None,-1,None)
</object>
<object_property>
(1,1,"GtkWindow","title","nice translatable title",1,None,None,None,None,None,None,None,None),
(1,2,"GtkButton","label","another tranlatable string",1,None,None,None,None,None,None,None,None),
(1,3,"GtkStringList","strings","lorem\nipsum",None,None,None,None,None,None,None,None,None)
</object_property>
<object_signal>
(1,1,1,"GtkWindow","activate-default","test_swap",None,None,1,None,None),
(2,1,1,"GtkWindow","activate-focus","test_after",None,None,None,1,None)
</object_signal>
<object_data>
(1,1,"GtkWidget",2,2,None,1,None,None,None,None),
(1,3,"GtkStringList",1,1,None,None,None,None,None,None),
(1,3,"GtkStringList",2,2,"translatable item",1,None,1,None,None)
</object_data>
</cambalache-project>

View File

@ -1,87 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.10.3" target_tk="gtk-4.0">
<ui>
(1,1,"main.ui","main.ui",None,None,None,None,None,None)
</ui>
<object>
(1,1,"GtkApplicationWindow","EfibootsMainWindow",None,None,None,None,None),
(1,2,"GtkHeaderBar",None,1,None,"titlebar",None,1),
(1,3,"GtkBox",None,1,None,None,None,None),
(1,4,"GtkButton","refresh_button",2,None,"end",None,None),
(1,5,"GtkButton","save_button",2,None,"end",None,None),
(1,6,"GtkColumnView","column_view",3,None,None,None,None),
(1,7,"GtkBox",None,3,None,None,None,1),
(1,8,"GtkBox",None,3,None,None,None,2),
(1,9,"GtkButton","up",7,None,None,None,None),
(1,10,"GtkButton","down",7,None,None,None,1),
(1,11,"GtkButton","add",7,None,None,None,2),
(1,12,"GtkButton","remove",7,None,None,None,3),
(1,13,"GtkLabel",None,8,None,None,None,None),
(1,14,"GtkSpinButton","timeout_spin",8,None,None,None,1),
(1,15,"GtkColumnViewColumn","column_current",6,None,None,None,None),
(1,16,"GtkColumnViewColumn","column_number",6,None,None,None,1),
(1,17,"GtkColumnViewColumn","column_label",6,None,None,None,2),
(1,18,"GtkColumnViewColumn","column_path",6,None,None,None,3),
(1,20,"GtkColumnViewColumn","column_parameters",6,None,None,None,4),
(1,21,"GtkColumnViewColumn","column_active",6,None,None,None,5),
(1,22,"GtkColumnViewColumn","column_next",6,None,None,None,6),
(1,23,"GtkSortListModel",None,None,None,None,None,None),
(1,24,"GtkSorter",None,None,None,None,None,None)
</object>
<object_property>
(1,1,"GtkWindow","default-height","260",None,None,None,None,None),
(1,1,"GtkWindow","default-width","300",None,None,None,None,None),
(1,3,"GtkBox","spacing","12",None,None,None,None,None),
(1,3,"GtkOrientable","orientation","vertical",None,None,None,None,None),
(1,3,"GtkWidget","margin-bottom","10",None,None,None,None,None),
(1,3,"GtkWidget","margin-end","10",None,None,None,None,None),
(1,3,"GtkWidget","margin-start","10",None,None,None,None,None),
(1,3,"GtkWidget","margin-top","10",None,None,None,None,None),
(1,4,"GtkButton","icon-name","edit-clear-all-symbolic",None,None,None,None,None),
(1,4,"GtkWidget","tooltip-text","Reset",None,None,None,None,None),
(1,5,"GtkButton","icon-name","document-save-symbolic",None,None,None,None,None),
(1,5,"GtkWidget","tooltip-text","Save",None,None,None,None,None),
(1,6,"GtkColumnView","show-column-separators","True",None,None,None,None,None),
(1,6,"GtkWidget","margin-bottom","10",None,None,None,None,None),
(1,7,"GtkBox","homogeneous","True",None,None,None,None,None),
(1,7,"GtkWidget","css-classes","linked",None,None,None,None,None),
(1,8,"GtkBox","spacing","8",None,None,None,None,None),
(1,9,"GtkButton","icon-name","go-up-symbolic",None,None,None,None,None),
(1,9,"GtkWidget","tooltip-text","Move up",None,None,None,None,None),
(1,10,"GtkButton","icon-name","go-down-symbolic",None,None,None,None,None),
(1,10,"GtkWidget","tooltip-text","Move down",None,None,None,None,None),
(1,11,"GtkButton","icon-name","list-add-symbolic",None,None,None,None,None),
(1,11,"GtkWidget","tooltip-text","Add new entry",None,None,None,None,None),
(1,12,"GtkButton","icon-name","list-remove-symbolic",None,None,None,None,None),
(1,12,"GtkWidget","tooltip-text","Remove entry",None,None,None,None,None),
(1,13,"GtkLabel","label","Boot manager timeout in seconds:",None,None,None,None,None),
(1,14,"GtkSpinButton","climb-rate","1.0",None,None,None,None,None),
(1,14,"GtkSpinButton","numeric","True",None,None,None,None,None),
(1,14,"GtkSpinButton","update-policy","if-valid",None,None,None,None,None),
(1,15,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,15,"GtkColumnViewColumn","title","Current",None,None,None,None,None),
(1,16,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,16,"GtkColumnViewColumn","title","Number",None,None,None,None,None),
(1,17,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,17,"GtkColumnViewColumn","title","Label",None,None,None,None,None),
(1,18,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,18,"GtkColumnViewColumn","title","Path",None,None,None,None,None),
(1,20,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,20,"GtkColumnViewColumn","title","Parameters",None,None,None,None,None),
(1,21,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,21,"GtkColumnViewColumn","title","Active",None,None,None,None,None),
(1,22,"GtkColumnViewColumn","resizable","True",None,None,None,None,None),
(1,22,"GtkColumnViewColumn","title","Boot Next",None,None,None,None,None)
</object_property>
<object_signal>
(1,1,5,"GtkButton","clicked","on_clicked_save",None,None,None,None,None),
(2,1,4,"GtkButton","clicked","on_clicked_reset",None,None,None,None,None),
(3,1,9,"GtkButton","clicked","on_clicked_up",None,None,None,None,None),
(4,1,10,"GtkButton","clicked","on_clicked_down",None,None,None,None,None),
(5,1,11,"GtkButton","clicked","on_clicked_add",None,None,None,None,None),
(6,1,12,"GtkButton","clicked","on_clicked_remove",None,None,None,None,None),
(7,1,14,"GtkSpinButton","value-changed","on_value_changed_timeout",None,None,None,None,None),
(8,1,1,"GtkWindow","close-request","on_close_request",None,None,None,None,None)
</object_signal>
</cambalache-project>

View File

@ -1,33 +0,0 @@
#!/usr/bin/pytest
"""
Test Old format loading
"""
import os
from cambalache import CmbProject
basedir = os.path.dirname(__file__)
def migration_test(target, filename):
project_path = os.path.join(basedir, target, filename)
project = CmbProject(filename=project_path)
assert project is not None
def test_gtk3_format_0_10_3():
migration_test("gtk+-3.0", "test_project_0.10.3.cmb")
def test_gtk4_format_0_10_3():
migration_test("gtk-4.0", "test_project_0.10.3.cmb")
def test_gtk3_column_constraint_changes_0_17_3():
migration_test("gtk+-3.0", "test_column_constraint_changes_0.17.3.cmb")
def test_gtk4_column_constraint_changes_0_17_3():
migration_test("gtk-4.0", "test_column_constraint_changes_0.17.3.cmb")

View File

@ -47,7 +47,8 @@ from cambalache import CmbProject, config
("gtk-4.0", "menu.ui"),
("gtk-4.0", "string_list.ui"),
("gtk-4.0", "accessibility.ui"),
("gtk-4.0", "ui_comments.ui")
("gtk-4.0", "ui_comments.ui"),
("gtk-4.0", "expression.ui"),
])
def test_(target_tk, filename):
"""

View File

@ -76,6 +76,7 @@ class CmbGirData:
"GParamGType": "gtype",
"GParamBoxed": "boxed",
"GParamVariant": "variant",
"GtkParamSpecExpression": "object",
}
# GtkBuilder native object types
@ -585,6 +586,64 @@ class CmbGirData:
)
])
# Add GtkExpression type properties
# To create a constant expression, use the <constant> element. If the type attribute is specified, the element content
# is interpreted as a value of that type. Otherwise, it is assumed to be an object. For instance:
#
# <constant>string_filter</constant>
# <constant type='gchararray'>Hello, world</constant>
#
self.__add_type("GtkConstantExpression", "GtkExpression", [
("type", "gtype", "GObject"),
("value", "gchararray", None),
])
# To create a property expression, use the <lookup> element. It can have a type attribute to specify the object type,
# and a name attribute to specify the property to look up.
# The content of <lookup> can either be a string that specifies the name of the object to use, an element specifying and
# expression to provide an object, or empty to use the this object.
#
# <lookup name='search'>string_filter</lookup>
#
self.__add_type("GtkPropertyExpression", "GtkExpression", [
("type", "gtype", None),
("value", "gchararray", None),
("name", "gchararray", None),
])
# To create a closure expression, use the <closure> element. The function attribute specifies what function to use for
# the closure, and the type attribute specifies its return type.
# The content of the element contains the expressions for the parameters. For instance:
#
# <closure type='gchararray' function='combine_args_somehow'>
# <constant type='gchararray'>File size:</constant>
# <lookup type='GFile' name='size'>myfile</lookup>
# </closure>
#
self.__add_type("GtkClosureExpression", "GtkExpression", [
("type", "gtype", None),
("function", "gchararray", None),
])
def __add_type(self, type_name, parent_type, properties):
self.types.update({
type_name: {
"parent": parent_type,
"properties": {
prop: {
"type": type_name,
"default_value": default,
"is_object": False,
"version": None,
"deprecated_version": None,
"construct": None,
"translatable": False,
} for (prop, type_name, default) in properties
}
}
})
def __a11y_add_ifaces_from_enum(self, accessible_ifaces):
accessible_types = {}
@ -727,10 +786,14 @@ class CmbGirData:
def _get_type_data(self, element, name, use_instance=True, skip_types=[]):
parent = element.get("parent")
if parent and parent.find(".") < 0:
if parent == "Expression" and name not in ["GtkConstantExpression", "GtkPropertyExpression", "GtkClosureExpression"]:
return None
elif parent and parent.find(".") < 0:
parent = self.prefix + parent
elif parent is None:
parent = "object"
elif parent == "GObject.ParamSpec":
return None
else:
parent = self.external_types.get(parent, "GObject")