Compare commits

...

51 Commits
0.97.4 ... 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
Juan Pablo Ugarte
3b76a4f89e
Rolling 0.97.6 2025-07-27 11:06:56 -04:00
Juan Pablo Ugarte
55f9b8d1de
CmbView: Make CasildaCompositor scrollable
- Add scrolled window around compositor
 - Remove dark mode hack
2025-07-27 11:01:35 -04:00
Juan Pablo Ugarte
9d76bac374
CmbWindow: remove dark mode hack
New CasildaCompositor does not render a background
2025-07-27 10:59:59 -04:00
Juan Pablo Ugarte
eaf1a58879
Bump Casilda dependency 2025-07-27 10:59:21 -04:00
Juan Pablo Ugarte
8f8cdb8b53
Tests: add dummy backend URL to make sure notifications are disabled during tests. 2025-07-27 10:27:45 -04:00
Juan Pablo Ugarte
2c614dda5f
CmbProject: select parent after deleting object 2025-07-27 10:26:07 -04:00
Juan Pablo Ugarte
4c763b0970
CmbObject: notify display-name 2025-07-27 10:25:42 -04:00
Juan Pablo Ugarte
e6de9fa586
MrgApplication: fix icontheme search path for gtk 3 projects 2025-07-24 18:42:08 -04:00
Juan Pablo Ugarte
002385e4ed
CmbNotification: always enable when backed is in localhost 2025-07-24 18:41:34 -04:00
Juan Pablo Ugarte
12d637064a
Rolling 0.97.5 2025-07-19 09:34:56 -04:00
Juan Pablo Ugarte
d3805e3be9
Makefile: add flatpak build target for arm 2025-07-19 09:33:58 -04:00
Juan Pablo Ugarte
10deecaa10
CmbApplication: add non unique flag from environment 2025-07-19 09:33:29 -04:00
Juan Pablo Ugarte
a3d952db1b
CmbWindow: add import directory action.
Add support to import all supported files in a directory.
2025-07-09 20:44:26 -04:00
Juan Pablo Ugarte
4d1faeeaea
CmbProject: add _on_import_directory_activate()
Function to get a list of files sorted in topological order for import
2025-07-09 20:44:26 -04:00
Juan Pablo Ugarte
3caee79fd7
MrgAdwAdialog: make parent window big enough to show popup 2025-07-09 20:44:26 -04:00
Juan Pablo Ugarte
f97524ebe8
CmbView: add icontheme search path support
This sends icon theme search paths on project load to make those icon names
available to merengue.
2025-07-09 20:42:59 -04:00
Juan Pablo Ugarte
5377633cff
MrgApplication: add set_icontheme_search_paths command. 2025-07-09 20:42:59 -04:00
Juan Pablo Ugarte
d663223cee
CmbProject: add icontheme search path support
Load <icontheme-search-path> nodes and store them in icontheme_search_paths list.
2025-07-09 20:42:59 -04:00
Juan Pablo Ugarte
52b3bbcef1
CmbView: close merengue fd after passing to child process. 2025-07-02 22:30:08 -04:00
63 changed files with 1914 additions and 1100 deletions

View File

@ -1,11 +1,22 @@
cambalache.flatpak: repo
flatpak build-bundle repo $@ ar.xjuan.Cambalache
repo: ar.xjuan.Cambalache.json .git/objects repo: ar.xjuan.Cambalache.json .git/objects
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --noninteractive --user flathub org.gnome.Sdk//48 flatpak install --noninteractive --user flathub org.gnome.Sdk//48
flatpak install --noninteractive --user flathub org.gnome.Platform//48 flatpak install --noninteractive --user flathub org.gnome.Platform//48
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
cambalache.flatpak: repo
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache cambalache_arm.flatpak: repo_arm
flatpak build-bundle --arch=aarch64 repo $@ ar.xjuan.Cambalache
repo_arm: ar.xjuan.Cambalache.json .git/objects
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --noninteractive --user flathub org.gnome.Sdk/aarch64/48
flatpak install --noninteractive --user flathub org.gnome.Platform/aarch64/48
flatpak-builder --arch=aarch64 --force-clean --repo=repo build ar.xjuan.Cambalache.json
.PHONY: install clean veryclean .PHONY: install clean veryclean

View File

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

View File

@ -38,7 +38,12 @@ basedir = os.path.dirname(__file__) or "."
class CmbApplication(Adw.Application): class CmbApplication(Adw.Application):
def __init__(self): def __init__(self):
super().__init__(application_id="ar.xjuan.Cambalache", flags=Gio.ApplicationFlags.HANDLES_OPEN) flags = Gio.ApplicationFlags.HANDLES_OPEN
if "CMB_APPLICATION_NON_UNIQUE" in os.environ:
flags |= Gio.ApplicationFlags.NON_UNIQUE
super().__init__(application_id="ar.xjuan.Cambalache", flags=flags)
self.add_main_option("version", b"v", GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _("Print version"), None) self.add_main_option("version", b"v", GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _("Print version"), None)

View File

@ -27,7 +27,7 @@ import os
import locale import locale
import tempfile 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 .cmb_tutor import CmbTutor, CmbTutorState
from . import cmb_tutorial from . import cmb_tutorial
@ -120,6 +120,9 @@ class CmbWindow(Adw.ApplicationWindow):
intro_button = Gtk.Template.Child() intro_button = Gtk.Template.Child()
menu_button = Gtk.Template.Child() menu_button = Gtk.Template.Child()
# Properties
source_style = GObject.Property(type=GtkSource.StyleScheme, flags=GObject.ParamFlags.READWRITE)
# Settings # Settings
completed_intro = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE) completed_intro = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
@ -178,6 +181,7 @@ class CmbWindow(Adw.ApplicationWindow):
"delete", "delete",
"donate", "donate",
"import", "import",
"import_directory",
"inspect", "inspect",
"intro", "intro",
"liberapay", "liberapay",
@ -301,6 +305,7 @@ class CmbWindow(Adw.ApplicationWindow):
self.__load_window_state() self.__load_window_state()
self.__update_actions() 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)) 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) self.__update_dark_mode(app.props.style_manager)
@ -476,11 +481,11 @@ class CmbWindow(Adw.ApplicationWindow):
def __update_dark_mode(self, style_manager): def __update_dark_mode(self, style_manager):
if style_manager.props.dark: if style_manager.props.dark:
self.source_style = self.source_style_manager.get_scheme("Adwaita-dark")
self.add_css_class("dark") self.add_css_class("dark")
self.view._set_dark_mode(True)
else: else:
self.remove_css_class("dark") self.remove_css_class("dark")
self.view._set_dark_mode(False) self.source_style = self.source_style_manager.get_scheme("Adwaita")
def __np_name_to_ui(self, binding, value): def __np_name_to_ui(self, binding, value):
if len(value): if len(value):
@ -732,7 +737,7 @@ class CmbWindow(Adw.ApplicationWindow):
return filename.startswith(f"/run/user/{os.getuid()}/doc/") return filename.startswith(f"/run/user/{os.getuid()}/doc/")
def import_file(self, filename, target_tk=None): def import_ui(self, filename, target_tk=None, autoselect=True):
if self.project is None: if self.project is None:
dirname = os.path.dirname(filename) dirname = os.path.dirname(filename)
basename = os.path.basename(filename) basename = os.path.basename(filename)
@ -753,7 +758,8 @@ class CmbWindow(Adw.ApplicationWindow):
ui = self.project.get_ui_by_filename(filename) ui = self.project.get_ui_by_filename(filename)
if ui: if ui:
self.project.set_selection([ui]) if autoselect:
self.project.set_selection([ui])
return return
try: try:
@ -762,7 +768,8 @@ class CmbWindow(Adw.ApplicationWindow):
ui, msg, detail = self.project.import_file(filename) ui, msg, detail = self.project.import_file(filename)
self.project.set_selection([ui]) if autoselect:
self.project.set_selection([ui])
if msg: if msg:
details = "\n".join(detail) details = "\n".join(detail)
@ -819,7 +826,7 @@ class CmbWindow(Adw.ApplicationWindow):
def on_ask_gtk_version_response(d, r): def on_ask_gtk_version_response(d, r):
target_tk = "gtk-4.0" if r == 4 else "gtk+-3.0" target_tk = "gtk-4.0" if r == 4 else "gtk+-3.0"
self.import_file(filename, target_tk=target_tk) self.import_ui(filename, target_tk=target_tk)
d.destroy() d.destroy()
dialog.connect("response", on_ask_gtk_version_response) dialog.connect("response", on_ask_gtk_version_response)
@ -852,7 +859,7 @@ class CmbWindow(Adw.ApplicationWindow):
self.ask_gtk_version(filename) self.ask_gtk_version(filename)
return return
self.import_file(filename, target_tk=target_tk) self.import_ui(filename, target_tk=target_tk)
elif content_type != "application/x-cambalache-project": elif content_type != "application/x-cambalache-project":
raise Exception(_("Unknown file type {content_type}").format(content_type=content_type)) raise Exception(_("Unknown file type {content_type}").format(content_type=content_type))
@ -1133,6 +1140,16 @@ class CmbWindow(Adw.ApplicationWindow):
details=unsupported_features_list, details=unsupported_features_list,
) )
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"]:
self.import_ui(path, autoselect=autoselect)
elif content_type == "text/css":
self.project.add_css(path)
elif content_type == "application/xml" and path.endswith("gresource.xml"):
self.project.import_gresource(path)
def _on_import_activate(self, action, data): def _on_import_activate(self, action, data):
if self.project is None: if self.project is None:
return return
@ -1140,17 +1157,7 @@ class CmbWindow(Adw.ApplicationWindow):
def dialog_callback(dialog, res): def dialog_callback(dialog, res):
try: try:
for file in dialog.open_multiple_finish(res): for file in dialog.open_multiple_finish(res):
path = file.get_path() self.import_file(file.get_path())
content_type = utils.content_type_guess(path)
print("IMPORT", path, content_type)
if content_type in ["application/x-gtk-builder", "application/x-glade", "text/x-blueprint"]:
self.import_file(file.get_path())
elif content_type == "text/css":
self.project.add_css(path)
elif content_type == "application/xml" and path.endswith("gresource.xml"):
self.project.import_gresource(path)
except Exception as e: except Exception as e:
logger.warning(f"Error {e}") logger.warning(f"Error {e}")
@ -1166,6 +1173,73 @@ class CmbWindow(Adw.ApplicationWindow):
) )
dialog.open_multiple(self, None, dialog_callback) dialog.open_multiple(self, None, dialog_callback)
def _on_import_directory_activate(self, action, data):
if self.project is None:
return
def progress_dialog_new():
dialog = Gtk.Window(
title="Importing directory",
transient_for=self,
default_width=640,
modal=True
)
progressbar = Gtk.ProgressBar(
text=_("Loading directory contents"),
ellipsize=Pango.EllipsizeMode.START,
show_text=True,
vexpand=True,
margin_top=32,
margin_bottom=32,
margin_start=16,
margin_end=16,
)
dialog.set_child(progressbar)
dialog.present()
return dialog, progressbar
def dialog_callback(dialog, res):
main_loop = GLib.MainContext.default()
progress, progressbar = progress_dialog_new()
def pulse():
progressbar.pulse()
while main_loop.pending():
main_loop.iteration(False)
try:
dir = dialog.select_folder_finish(res)
dirpath = dir.get_path()
files = self.project._list_supported_files(dirpath, pulse)
n_files = len(files)
self.project.history_push(_('Import {n} directory "{dirpath}"').format(dirpath=dirpath, n=n_files))
basedir = self.project.dirname + "/"
for i, path in enumerate(files):
progressbar.set_text(path.removeprefix(basedir))
progressbar.set_fraction(i/n_files)
self.import_file(path, autoselect=False)
while main_loop.pending():
main_loop.iteration(False)
self.project.history_pop()
self._show_message(_("Imported {n} files").format(n=n_files))
progress.close()
except Exception as e:
logger.warning(f"Error {e}")
dialog = self.__file_open_dialog_new(_("Choose directory to import"), accept_label=_("Import directory"))
dialog.set_initial_folder(Gio.File.new_for_path(self.project.dirname))
dialog.select_folder(self, None, dialog_callback)
def _on_add_gresource_activate(self, action, data): def _on_add_gresource_activate(self, action, data):
if self.project is None: if self.project is None:
return return

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.97.1 --> <!-- Created with Cambalache 0.97.4 -->
<interface> <interface>
<!-- interface-name cmb_window.ui --> <!-- interface-name cmb_window.ui -->
<!-- interface-copyright Juan Pablo Ugarte --> <!-- interface-copyright Juan Pablo Ugarte -->
@ -31,6 +31,10 @@
<attribute name="action">win.import</attribute> <attribute name="action">win.import</attribute>
<attribute name="label" translatable="yes">Import file</attribute> <attribute name="label" translatable="yes">Import file</attribute>
</item> </item>
<item>
<attribute name="action">win.import_directory</attribute>
<attribute name="label">Import directory</attribute>
</item>
</section> </section>
<section> <section>
<item> <item>

View File

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?> <?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd"> <!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<!-- Created with Cambalache 0.97.1 --> <!-- Created with Cambalache 0.97.6 -->
<cambalache-project version="0.96.0" target_tk="gtk-4.0"> <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"/> <gresources filename="app/app.gresource.xml" sha256="3684aa78fce08d8e81d0907317214aeb179c5aea091dd0df405476b43e286941"/>
<css filename="cambalache.css" priority="400" is_global="1"/> <css filename="cambalache.css" priority="400" is_global="1"/>
<css filename="app/cambalache.css" is_global="0"/> <css filename="app/cambalache.css" is_global="0"/>
@ -145,6 +145,7 @@
</ui> </ui>
<ui template-class="CmbTranslatableWidget" filename="control/cmb_translatable_widget.ui" sha256="b3178157210f93308b92d99f933cb8d04f2de0278ad75680c7460e2c520b1684"/> <ui template-class="CmbTranslatableWidget" filename="control/cmb_translatable_widget.ui" sha256="b3178157210f93308b92d99f933cb8d04f2de0278ad75680c7460e2c520b1684"/>
<ui template-class="CasildaCompositor"> <ui template-class="CasildaCompositor">
<property id="scrollable" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
<signal id="context-menu"/> <signal id="context-menu"/>
<content><![CDATA[<interface> <content><![CDATA[<interface>
<!-- interface-name CmbCompositor --> <!-- interface-name CmbCompositor -->
@ -231,6 +232,13 @@
<ui template-class="CmbPollNotificationView" filename="cmb_poll_notification_view.ui" sha256="8f47a1e503b85eb5ac3ac54962a40fc2237588e1216afba859a3016a1dcfc121"/> <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="CmbPollOptionCheck" filename="cmb_poll_option_check.ui" sha256="aa433f201dc1863f3727e1baa2c4cc239192a1ae4c53553de69d529ba2cc6fed"/>
<ui template-class="CmbNotificationListRow" filename="cmb_notification_list_row.ui" sha256="5ef66fcc24e10d40a91ff0eada84f6aa8a595e368961364d98fde1800755edfc"/> <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"> <ui template-class="CmbPixbufEntry">
<requires>CmbFileEntry</requires> <requires>CmbFileEntry</requires>
<content><![CDATA[<interface> <content><![CDATA[<interface>
@ -249,14 +257,14 @@
<signal id="chooser-popup"/> <signal id="chooser-popup"/>
<signal id="type-selected"/> <signal id="type-selected"/>
</ui> </ui>
<ui template-class="CmbView" filename="cmb_view.ui" sha256="6b9251d30d6e4751b8b04b2af923eec9a8f00d590a322d96bf080fb200b58424"> <ui template-class="CmbView" filename="cmb_view.ui" sha256="fc4098342eb1f7a4aba9b3df5f20ba5a279f760b7ce94452cdbfd1248fc9bd02">
<requires>CasildaCompositor</requires> <requires>CasildaCompositor</requires>
<requires>CmbSourceView</requires> <requires>CmbSourceView</requires>
<requires>CmbDBInspector</requires> <requires>CmbDBInspector</requires>
<signal id="placeholder-activated"/> <signal id="placeholder-activated"/>
<signal id="placeholder-selected"/> <signal id="placeholder-selected"/>
</ui> </ui>
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="2050887ef1c45facb6ebff14500214bb035e6808ca61d2a7d661e696d79026ca"> <ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="982c596dbe038edac9f4f20a72062b9d8b78b08be7e731395dde1c701d92bcb1">
<requires>CmbFileButton</requires> <requires>CmbFileButton</requires>
<requires>CmbEntry</requires> <requires>CmbEntry</requires>
</ui> </ui>
@ -270,7 +278,12 @@
<requires>CmbEntry</requires> <requires>CmbEntry</requires>
<requires>CmbToplevelChooser</requires> <requires>CmbToplevelChooser</requires>
</ui> </ui>
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="df07e3e03b88f9b097ad7d65efa15923d339db83b9d4a7c015a312a71f8c9685"> <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>CmbNotificationListView</requires>
<requires>CmbScrolledWindow</requires> <requires>CmbScrolledWindow</requires>
<requires>CmbObjectEditor</requires> <requires>CmbObjectEditor</requires>

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 --> <!-- Created with Cambalache 0.97.6 -->
<gresources> <gresources>
<gresource prefix="/ar/xjuan/Cambalache"> <gresource prefix="/ar/xjuan/Cambalache">
<file>cambalache.css</file> <file>cambalache.css</file>
@ -27,5 +27,6 @@
<file>icons/scalable/actions/binded-symbolic.svg</file> <file>icons/scalable/actions/binded-symbolic.svg</file>
<file>icons/scalable/actions/bind-symbolic.svg</file> <file>icons/scalable/actions/bind-symbolic.svg</file>
<file>cmb_notification_list_row.ui</file> <file>cmb_notification_list_row.ui</file>
<file>cmb_binding_popover.ui</file>
</gresource> </gresource>
</gresources> </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 graphlib import TopologicalSorter, CycleError
from gi.repository import GLib, Gio, GObject from gi.repository import GLib, Gio, GObject
from cambalache import config, getLogger, _ 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 .constants import EXTERNAL_TYPE, GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
from .cmb_db_profile import CmbProfileConnection from .cmb_db_profile import CmbProfileConnection
@ -318,6 +318,9 @@ class CmbDB(GObject.GObject):
if column in unique_constraints_flat: if column in unique_constraints_flat:
continue continue
# Get column index
column_index = all_columns.index(column)
c.execute( c.execute(
f""" f"""
CREATE TRIGGER on_{table}_update_{column} AFTER UPDATE OF {column} ON {table} 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}) (SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
IS NOT ('UPDATE', '{table}', json_array('{column}')) IS NOT ('UPDATE', '{table}', json_array('{column}'))
AND AND
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq}) (SELECT command, table_name, columns, new_values ->> {column_index} IS NULL FROM history WHERE history_id = {history_seq})
IS NOT ('INSERT', '{table}', NULL) IS NOT ('INSERT', '{table}', NULL, 0)
) )
) )
BEGIN BEGIN
@ -364,8 +367,8 @@ class CmbDB(GObject.GObject):
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND 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}) (SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
AND AND
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq}) (SELECT command, table_name, columns, new_values ->> {column_index} IS NULL FROM history WHERE history_id = {history_seq})
IS ('INSERT', '{table}', NULL) IS ('INSERT', '{table}', NULL, 0)
BEGIN BEGIN
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq}; UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
END; END;
@ -609,46 +612,6 @@ class CmbDB(GObject.GObject):
return utils.parse_version(version) 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): def __load_table_from_tuples(self, c, table, tuples, version=None):
data = ast.literal_eval(f"[{tuples}]") if tuples else [] data = ast.literal_eval(f"[{tuples}]") if tuples else []
if len(data) == 0: if len(data) == 0:
@ -670,35 +633,12 @@ class CmbDB(GObject.GObject):
# Load table data # Load table data
c.executemany(f"INSERT INTO temp.{table} VALUES ({cols})", 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 # Copy data from temp table
c.execute(f"INSERT INTO main.{table} SELECT * FROM temp.{table};") c.execute(f"INSERT INTO main.{table} SELECT * FROM temp.{table};")
# Drop temp table # Drop temp table
c.execute(f"DROP TABLE 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): def __load_accessibility_metadata(self, node):
data = json.loads(node.text) data = json.loads(node.text)
@ -947,6 +887,7 @@ class CmbDB(GObject.GObject):
layout=None, layout=None,
position=None, position=None,
inline_property=None, inline_property=None,
inline_binding_expression=False,
): ):
c = self.conn.cursor() c = self.conn.cursor()
@ -1017,18 +958,20 @@ class CmbDB(GObject.GObject):
) )
count = c.fetchone()[0] count = c.fetchone()[0]
inline_object_property = "binding_expression_id" if inline_binding_expression else "inline_object_id"
if count: if count:
c.execute( c.execute(
""" f"""
UPDATE object_property SET inline_object_id=? UPDATE object_property SET {inline_object_property}=?
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id; WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id;
""", """,
(object_id, ui_id, parent_id, pinfo.owner_id, inline_property), (object_id, ui_id, parent_id, pinfo.owner_id, inline_property),
) )
else: else:
c.execute( c.execute(
""" f"""
INSERT INTO object_property (ui_id, object_id, owner_id, property_id, inline_object_id) INSERT INTO object_property (ui_id, object_id, owner_id, property_id, {inline_object_property})
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
""", """,
(ui_id, parent_id, pinfo.owner_id, inline_property, object_id), (ui_id, parent_id, pinfo.owner_id, inline_property, object_id),
@ -1124,15 +1067,15 @@ class CmbDB(GObject.GObject):
# Initialize to null # Initialize to null
inline_object_id = None inline_object_id = None
# GtkBuilder in Gtk4 supports defining an object in a property if self.target_tk == "gtk-4.0" and pinfo.is_object and len(prop) >= 1:
obj_node = prop.find("object")
if self.target_tk == "gtk-4.0" and pinfo.is_object and obj_node is not None:
if pinfo.disable_inline_object: if pinfo.disable_inline_object:
self.__collect_error("not-inline-object", prop, f"{info.type_id}:{property_id}") self.__collect_error("not-inline-object", prop, f"{info.type_id}:{property_id}")
return return
inline_object_id = self.__import_object(ui_id, obj_node, object_id) if prop[0].tag == "object" or \
value = None (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( self.__upsert_object_property(
c, c,
@ -1153,6 +1096,33 @@ class CmbDB(GObject.GObject):
inline_object_id=inline_object_id 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( def __upsert_object_property(
self, self,
c, c,
@ -1170,7 +1140,9 @@ class CmbDB(GObject.GObject):
bind_source_id=None, bind_source_id=None,
bind_property_id=None, bind_property_id=None,
bind_flags=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) comment = self.__node_get_comment(prop)
@ -1191,8 +1163,9 @@ class CmbDB(GObject.GObject):
""" """
INSERT OR REPLACE INTO object_property INSERT OR REPLACE INTO object_property
(ui_id, object_id, owner_id, property_id, value, translatable, comment, translation_context, (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) translation_comments, inline_object_id, bind_source_id, bind_property_id, bind_flags,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); binding_expression_id, binding_expression_object_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""", """,
( (
ui_id, ui_id,
@ -1208,6 +1181,8 @@ class CmbDB(GObject.GObject):
bind_source_id, bind_source_id,
bind_property_id, bind_property_id,
bind_flags, bind_flags,
binding_expression_id,
binding_expression_object_id,
), ),
) )
except Exception as e: except Exception as e:
@ -1330,9 +1305,9 @@ class CmbDB(GObject.GObject):
""" """
INSERT INTO object_signal INSERT INTO object_signal
(ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after, comment) (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: except Exception as e:
raise Exception(f"XML:{signal.sourceline} - Can not import object {object_id} {owner_id}:{signal_id} signal: {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: else:
self.__collect_error("unknown-tag", node, child.tag) 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( def __import_object(
self, ui_id, node, parent_id, internal_child=None, child_type=None, is_template=False, object_id_map=None 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": if node.tag == "menu":
return self.__import_menu(ui_id, node, parent_id, object_id_map=object_id_map) 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" is_template = node.tag == "template"
@ -1665,6 +1707,8 @@ class CmbDB(GObject.GObject):
for child in node.iterchildren(): for child in node.iterchildren():
if child.tag == "property": if child.tag == "property":
self.__import_property(c, info, ui_id, object_id, child, object_id_map=object_id_map) 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": elif child.tag == "signal":
self.__import_signal(c, info, ui_id, object_id, child, object_id_map=object_id_map) self.__import_signal(c, info, ui_id, object_id, child, object_id_map=object_id_map)
elif child.tag == "child": elif child.tag == "child":
@ -1782,6 +1826,16 @@ class CmbDB(GObject.GObject):
(ui_id,), (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 # Fix bind source and set bind owner to the object type
self.conn.execute( self.conn.execute(
""" """
@ -1793,6 +1847,52 @@ class CmbDB(GObject.GObject):
(ui_id,), (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 # Fix a11y CmbAccessibleList references
for row in self.conn.execute( for row in self.conn.execute(
""" """
@ -2143,6 +2243,100 @@ class CmbDB(GObject.GObject):
return obj 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): def __get_object_name(self, ui_id, object_id, merengue=False):
if object_id is None: if object_id is None:
return 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 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): 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 = self.conn.cursor()
c.execute("SELECT type_id, name, custom_fragment FROM object WHERE ui_id=? AND object_id=?;", (ui_id, object_id)) 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() 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) info = self.type_info.get(type_id, None)
if info is None: if info is None:
@ -2275,6 +2467,14 @@ class CmbDB(GObject.GObject):
c.close() c.close()
return None 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() cc = self.conn.cursor()
merengue_template = merengue and info.library_id is None and info.parent_id is not None 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, 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.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.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 NULL, NULL, p.type_id
FROM object_property AS op, property AS p, type AS t FROM object_property AS op, property AS p, type AS t
WHERE op.owner_id NOT IN WHERE op.owner_id NOT IN
@ -2345,7 +2546,7 @@ class CmbDB(GObject.GObject):
{template_check} {template_check}
UNION UNION
SELECT p.default_value, p.property_id, NULL, NULL, NULL, NULL, NULL, p.is_object, p.disable_inline_object, 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 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 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=?) 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_owner_id,
bind_property_id, bind_property_id,
bind_flags, bind_flags,
binding_expression_id,
binding_expression_object_id,
required, required,
workspace_default, workspace_default,
property_type_id, property_type_id,
@ -2377,13 +2580,10 @@ class CmbDB(GObject.GObject):
value_node = None value_node = None
pinfo = self.type_info.get(property_type_id, 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 binding_expression_id:
if is_object and is_inline_object: value_node = self.__export_expression(ui_id, binding_expression_id, merengue=merengue)
value_node = etree.fromstring(workspace_default)
else:
value = workspace_default
elif is_object: elif is_object:
# Ignore object properties with 0/null ID or unknown object references # Ignore object properties with 0/null ID or unknown object references
if val is not None and val.isnumeric() and int(val) == 0: if val is not None and val.isnumeric() and int(val) == 0:
@ -2394,7 +2594,7 @@ class CmbDB(GObject.GObject):
elif ignore_id: elif ignore_id:
# Ignore references to object in template mode since the object could not exists in this UI # Ignore references to object in template mode since the object could not exists in this UI
continue continue
else: elif val:
obj_name = self.__get_object_name(ui_id, val, merengue=merengue) obj_name = self.__get_object_name(ui_id, val, merengue=merengue)
# Ignore properties that reference an unknown object # Ignore properties that reference an unknown object
@ -2410,7 +2610,24 @@ class CmbDB(GObject.GObject):
else: else:
value = val 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: if value is not None:
node.text = value node.text = value
elif value_node is not None: elif value_node is not None:
@ -2476,7 +2693,7 @@ class CmbDB(GObject.GObject):
accessible_role = None accessible_role = None
a11y_data = {} a11y_data = {}
if self.target_tk == "gtk+-3.0": if target_gtk3:
atk_object = E.object() atk_object = E.object()
atk_object.set("class", "AtkObject") atk_object.set("class", "AtkObject")
else: else:
@ -2660,6 +2877,7 @@ class CmbDB(GObject.GObject):
SELECT object_id, internal, type, comment, position, custom_child_fragment SELECT object_id, internal, type, comment, position, custom_child_fragment
FROM object FROM object
WHERE ui_id=? AND parent_id=? AND 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 object_id NOT IN (SELECT inline_object_id FROM object_property
WHERE inline_object_id IS NOT NULL AND ui_id=? AND object_id=?) WHERE inline_object_id IS NOT NULL AND ui_id=? AND object_id=?)
ORDER BY position; ORDER BY position;
@ -2674,7 +2892,7 @@ class CmbDB(GObject.GObject):
if merengue and is_box: if merengue and is_box:
# FIXME: On Gtk 3 we get the position from the layout property # 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( r = cc.execute(
""" """
SELECT value SELECT value
@ -2706,7 +2924,7 @@ class CmbDB(GObject.GObject):
if linfo is not None: if linfo is not None:
# Packing / Layout # 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( for prop in cc.execute(
f""" f"""
SELECT value, property_id, comment SELECT value, property_id, comment
@ -2727,7 +2945,7 @@ class CmbDB(GObject.GObject):
self.__node_add_comment(node, comment) self.__node_add_comment(node, comment)
if len(layout) > 0: if len(layout) > 0:
if self.target_tk == "gtk+-3.0": if target_gtk3:
child.append(layout) child.append(layout)
else: else:
child_obj.append(layout) 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'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 --> <!-- Created with Cambalache 0.97.6 -->
<interface> <interface>
<!-- interface-name cmb_gresource_editor.ui --> <!-- interface-name cmb_gresource_editor.ui -->
<!-- interface-copyright Juan Pablo Ugarte --> <!-- interface-copyright Juan Pablo Ugarte -->

View File

@ -139,16 +139,20 @@ class CmbNotificationCenter(GObject.GObject):
for prop in ["enabled", "uuid", "next-request", "notifications"]: for prop in ["enabled", "uuid", "next-request", "notifications"]:
self.settings.bind(prop, self, prop.replace("-", "_"), Gio.SettingsBindFlags.DEFAULT) self.settings.bind(prop, self, prop.replace("-", "_"), Gio.SettingsBindFlags.DEFAULT)
# Disable notifications if settings backend is ephemeral backend = urlparse(os.environ.get("CMB_NOTIFICATION_URL", "https://xjuan.ar:1934"))
if GObject.type_name(Gio.SettingsBackend.get_default()) == "GMemorySettingsBackend": if backend.hostname == "localhost":
logger.info("Disabling notifications") self.REQUEST_INTERVAL = 4
self.enabled = False self.enabled = True
else:
self.REQUEST_INTERVAL = 24 * 60 * 60
# Disable notifications if settings backend is ephemeral
if GObject.type_name(Gio.SettingsBackend.get_default()) == "GMemorySettingsBackend":
logger.info("Disabling notifications")
self.enabled = False
self.__load_notifications() self.__load_notifications()
backend = urlparse(os.environ.get("CMB_NOTIFICATION_URL", "https://xjuan.ar:1934"))
self.REQUEST_INTERVAL = 4 if backend.hostname == "localhost" else 24 * 60 * 60
if backend.scheme == "https": if backend.scheme == "https":
logger.info(f"Backend: {backend.scheme}://{backend.hostname}:{backend.port}") logger.info(f"Backend: {backend.scheme}://{backend.hostname}:{backend.port}")
self.connection = http.client.HTTPSConnection(backend.hostname, backend.port, timeout=8) self.connection = http.client.HTTPSConnection(backend.hostname, backend.port, timeout=8)

View File

@ -62,7 +62,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
self.__signals_dict = None self.__signals_dict = None
self.__data = None self.__data = None
self.__data_dict = None self.__data_dict = None
self.inline_property_id = None
self.version_warning = None self.version_warning = None
self.__is_template = False self.__is_template = False
@ -75,8 +74,9 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
if self.project is None: if self.project is None:
return return
self.__update_inline_property_id()
self.__update_version_warning() self.__update_version_warning()
self.connect("notify", self._on_notify)
self.ui.connect("notify", self._on_ui_notify) self.ui.connect("notify", self._on_ui_notify)
self.ui.connect("library-changed", self._on_ui_library_changed) self.ui.connect("library-changed", self._on_ui_library_changed)
@ -127,7 +127,8 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
self.__populate_data() self.__populate_data()
return self.__data_dict return self.__data_dict
def __update_inline_property_id(self): @property
def inline_property_id(self):
ui_id = self.ui_id ui_id = self.ui_id
object_id = self.object_id object_id = self.object_id
parent_id = self.parent_id parent_id = self.parent_id
@ -135,9 +136,28 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
if parent_id: if parent_id:
# Set which parent property makes a reference to this inline object # Set which parent property makes a reference to this inline object
row = self.project.db.execute( 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() ).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): def __populate_type_properties(self, name):
property_info = self.project.get_type_properties(name) property_info = self.project.get_type_properties(name)
@ -589,11 +609,19 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
name = self.name or "" name = self.name or ""
type_id = self.type_id type_id = self.type_id
parent_id = self.parent_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]: if type_id in [GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]:
prop = self.properties_dict["label"] prop = self.properties_dict["label"]
label = prop.value or "" label = prop.value or ""
display_name = f"{type_id} <i>{label}</i>" 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: elif not parent_id and self.ui.template_id == self.object_id:
# Translators: This is used for Template classes in the object tree # Translators: This is used for Template classes in the object tree
display_name = _("{name} (template)").format(name=name) display_name = _("{name} (template)").format(name=name)
@ -616,6 +644,13 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
target = self.ui.get_target(self.info.library_id) target = self.ui.get_target(self.info.library_id)
self.version_warning = utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.type_id) self.version_warning = utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.type_id)
def _on_notify(self, obj, pspec):
property_id = pspec.name
if property_id in ["name", "type-id"]:
self.notify("display-name")
self.notify("display-name-type")
def _on_ui_notify(self, obj, pspec): def _on_ui_notify(self, obj, pspec):
property_id = pspec.name property_id = pspec.name

View File

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

View File

@ -573,6 +573,8 @@ class CmbBaseProperty(CmbBase):
bind_owner_id, bind_owner_id,
bind_property_id, bind_property_id,
bind_flags, 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) 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, 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): class CmbBaseLayoutProperty(CmbBase):
__gtype_name__ = "CmbBaseLayoutProperty" __gtype_name__ = "CmbBaseLayoutProperty"

View File

@ -132,6 +132,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
self.__filename = None self.__filename = None
self.icontheme_search_paths = []
super().__init__(**kwargs) super().__init__(**kwargs)
self.target_tk = target_tk self.target_tk = target_tk
@ -227,6 +229,12 @@ class CmbProject(GObject.Object, Gio.ListModel):
return {"names": columns, "types": types, "pks": pks} 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): def __init_type_info(self, c):
for row in c.execute("SELECT * FROM type WHERE parent_id IS NOT NULL ORDER BY type_id;"): for row in c.execute("SELECT * FROM type WHERE parent_id IS NOT NULL ORDER BY type_id;"):
type_id = row[0] type_id = row[0]
@ -442,62 +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." 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): if version <= (0, 94, 0):
ui_graph = {} raise Exception(
ui_node_template = {} f"Project format {version} is not supported, Open/save with Cambalache 0.96.0 to migrate to the new format."
)
css_list = [] ui_graph = {}
gresourses_list = [] ui_node_template = {}
for child in root.getchildren(): css_list = []
if child.tag == "ui": gresourses_list = []
# Collect template class <-> node relation
template = child.get("template-class", None)
if template:
ui_node_template[template] = child
# Collect node dependencies for child in root.getchildren():
dependencies = [] if child.tag == "ui":
for requires in child.findall("requires"): # Collect template class <-> node relation
dependencies.append(requires.text) template = child.get("template-class", None)
if template:
ui_node_template[template] = child
ui_graph[child] = dependencies # Collect node dependencies
elif child.tag == "css": dependencies = []
css_list.append(child) for requires in child.findall("requires"):
elif child.tag == "gresources": dependencies.append(requires.text)
gresourses_list.append(child)
else:
raise Exception(f"Unknown tag {child.tag} in project file.")
for node in css_list: ui_graph[child] = dependencies
self.__load_css_from_node(node) 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: for node in css_list:
self.__load_gresource_from_node(node) self.__load_css_from_node(node)
# Replace dependencies with nodes for node in gresourses_list:
ui_node_graph = {} self.__load_gresource_from_node(node)
for node, dependencies in ui_graph.items():
ui_node_graph[node] = [ui_node_template[key] for key in dependencies]
try: # Replace dependencies with nodes
ts = TopologicalSorter(ui_node_graph) ui_node_graph = {}
sorted_ui_nodes = tuple(ts.static_order()) for node, dependencies in ui_graph.items():
except CycleError as e: ui_node_graph[node] = [ui_node_template[key] for key in dependencies]
raise Exception(f"Could not load project because of dependency cycle {e}")
# Load UI in topological order try:
for node in sorted_ui_nodes: ts = TopologicalSorter(ui_node_graph)
self.__load_ui_from_node(node) sorted_ui_nodes = tuple(ts.static_order())
else: except CycleError as e:
self.db.load_old_format(root, version) raise Exception(f"Could not load project because of dependency cycle {e}")
for row in self.db.execute("SELECT * FROM ui;"): # Load UI in topological order
ui = self.__add_ui(True, *row) for node in sorted_ui_nodes:
ui.notify("n-items") self.__load_ui_from_node(node)
for row in self.db.execute("SELECT * FROM css;"):
self.__add_css(True, *row)
self.history_enabled = True self.history_enabled = True
@ -706,6 +711,9 @@ class CmbProject(GObject.Object, Gio.ListModel):
project.addprevious(etree.Comment(f" Created with Cambalache {config.VERSION} ")) project.addprevious(etree.Comment(f" Created with Cambalache {config.VERSION} "))
for path in self.icontheme_search_paths:
project.append(E("icontheme-search-path", path))
# Save GResources # Save GResources
for row in c.execute("SELECT gresource_id, gresources_filename FROM gresource WHERE resource_type='gresources';"): for row in c.execute("SELECT gresource_id, gresources_filename FROM gresource WHERE resource_type='gresources';"):
gresource_id, gresources_filename = row gresource_id, gresources_filename = row
@ -835,6 +843,73 @@ class CmbProject(GObject.Object, Gio.ListModel):
return (ui, msgs, detail_msg) return (ui, msgs, detail_msg)
def _list_supported_files(self, dirpath, step_cb=None):
def read_dir(path):
retval = []
with os.scandir(path) as it:
for entry in it:
root, ext = os.path.splitext(entry.name)
if ext in [".ui", ".blp", ".css", ".xml"] and entry.is_file():
retval.append(entry.path)
if step_cb:
step_cb()
elif entry.is_dir():
retval += read_dir(entry.path)
return retval
ui_graph = {}
ui_node_template = {}
for filename in read_dir(dirpath):
try:
if filename.endswith(".blp"):
root, relpath, hexdigest = self.__parse_blp_file(filename)
elif filename.endswith(".ui"):
root, relpath, hexdigest = self.__parse_xml_file(filename)
elif filename.endswith(".css") or filename.endswith(".gresource.xml"):
ui_graph[filename] = []
if step_cb:
step_cb()
continue
else:
continue
except Exception as e:
print(e)
continue
template = root.find("template")
if template is not None:
ui_node_template[template.get("class")] = filename
dependencies = set()
for node in root.iterfind(".//object"):
klass = node.get("class", None)
if klass in self.type_info:
continue
dependencies.add(klass)
ui_graph[filename] = list(dependencies)
if step_cb:
step_cb()
# Replace dependencies with nodes
ui_node_graph = {}
for filename, dependencies in ui_graph.items():
ui_node_graph[filename] = [ui_node_template[key] for key in dependencies if key in ui_node_template]
try:
ts = TopologicalSorter(ui_node_graph)
sorted_ui_nodes = list(ts.static_order())
except CycleError as e:
raise Exception(f"Could not load project because of dependency cycle {e}")
return sorted_ui_nodes
def import_gresource(self, filename): def import_gresource(self, filename):
self.history_push(_('Import GResource "{filename}"').format(filename=filename)) self.history_push(_('Import GResource "{filename}"').format(filename=filename))
@ -1135,6 +1210,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
child_type=None, child_type=None,
inline_property=None, inline_property=None,
internal=None, internal=None,
inline_binding_expression=False,
): ):
if parent_id: if parent_id:
parent = self.get_object_by_id(ui_id, parent_id) parent = self.get_object_by_id(ui_id, parent_id)
@ -1158,6 +1234,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
inline_property=inline_property, inline_property=inline_property,
child_type=child_type, child_type=child_type,
internal=internal, internal=internal,
inline_binding_expression=inline_binding_expression,
) )
self.history_pop() self.history_pop()
self.db.commit() self.db.commit()
@ -1192,6 +1269,9 @@ class CmbProject(GObject.Object, Gio.ListModel):
raise Exception("Internal objects can not be removed") raise Exception("Internal objects can not be removed")
try: try:
was_selected = obj in self.__selection
parent = obj.parent
template_ui = None template_ui = None
template_instances = None template_instances = None
@ -1232,6 +1312,10 @@ class CmbProject(GObject.Object, Gio.ListModel):
self.__remove_object(obj, template_ui, template_instances) self.__remove_object(obj, template_ui, template_instances)
obj._remove_from_old_parent() obj._remove_from_old_parent()
# Select parent if removed object was selected
if was_selected and parent:
self.set_selection([parent])
def get_selection(self): def get_selection(self):
return self.__selection return self.__selection
@ -1323,11 +1407,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
p = properties.get(property_id, None) p = properties.get(property_id, None)
if p and p.owner_id == owner_id and p.property_id == property_id: if p and p.owner_id == owner_id and p.property_id == property_id:
print("AAAAAAAAA", p, prop, property_id)
p.notify(prop) 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 __undo_redo_do(self, undo, update_objects=None):
def get_object_position(table, row): def get_object_position(table, row):
@ -2130,6 +2211,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
position = obj.position position = obj.position
list_position = obj.list_position list_position = obj.list_position
child_type = obj.type 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.ignore_check_constraints = True
self.db.execute("UPDATE object SET position=-1 WHERE ui_id=? AND object_id=?;", (ui_id, object_id)) self.db.execute("UPDATE object SET position=-1 WHERE ui_id=? AND object_id=?;", (ui_id, object_id))
@ -2146,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 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)) 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 self.db.ignore_check_constraints = False
# Move all layout properties from obj to parent # Move all layout properties from obj to parent

View File

@ -25,7 +25,7 @@
from gi.repository import GObject 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 .cmb_property_info import CmbPropertyInfo
from . import utils from . import utils
from cambalache import _, getLogger from cambalache import _, getLogger
@ -50,63 +50,94 @@ class CmbProperty(CmbBaseProperty):
self.connect("notify", self.__on_notify) self.connect("notify", self.__on_notify)
def __str__(self): 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): def __on_notify(self, obj, pspec):
self.project._object_property_changed(self.object, self) self.project._object_property_changed(self.object, self)
@GObject.Property(type=str) def __db_get(self, column):
def value(self): row = self.project.db.execute(
c = self.project.db.execute( f"SELECT {column} FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
"SELECT value 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), (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 @value.setter
def _set_value(self, value): 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) @GObject.Property(type=bool, default=False)
def translatable(self): def translatable(self):
return super().translatable return self.__db_get("translatable")
@translatable.setter @translatable.setter
def _set_translatable(self, value): 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) @GObject.Property(type=str)
def translation_context(self): def translation_context(self):
return self.db_get( return self.__db_get("translation_context")
"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,
),
)
@translation_context.setter @translation_context.setter
def _set_translation_context(self, value): 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) @GObject.Property(type=str)
def translation_comments(self): def translation_comments(self):
return self.db_get( return self.__db_get("translation_comments")
"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,
),
)
@translation_comments.setter @translation_comments.setter
def _set_translation_comments(self, value): 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): def reset(self):
if self.info.internal_child: if self.info.internal_child:
@ -155,122 +186,12 @@ class CmbProperty(CmbBaseProperty):
"value": str(value) "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) @GObject.Property(type=CmbBaseProperty)
def bind_property(self): def bind_property(self):
c = self.project.db.cursor() bind_source_id = self.bind_source_id
row = c.execute( bind_property_id = self.bind_property_id
"""
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()
if row: if bind_source_id and bind_property_id:
bind_source_id, bind_property_id = row
source = self.project.get_object_by_id(self.ui_id, bind_source_id) if bind_property_id else None 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 return source.properties_dict.get(bind_property_id, None) if source else None
@ -278,7 +199,113 @@ class CmbProperty(CmbBaseProperty):
@bind_property.setter @bind_property.setter
def _set_bind_property(self, bind_property): 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) self.project._object_property_binding_changed(self.object, self)
def _update_version_warning(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") warning += _("Warning: GFile uri needs to be absolute for Gtk < 4.16")
self.version_warning = warning if len(warning) else None 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 gi.repository import GObject, Gtk
from .cmb_object import CmbObject
from .cmb_property import CmbProperty from .cmb_property import CmbProperty
from .cmb_layout_property import CmbLayoutProperty from .cmb_layout_property import CmbLayoutProperty
from .cmb_property_info import CmbPropertyInfo from .cmb_binding_popover import CmbBindingPopover
from .control import CmbObjectChooser, CmbFlagsEntry
from cambalache import _
class CmbPropertyLabel(Gtk.Button): 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.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.__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: if self.bindable:
self.connect("clicked", self.__on_bind_button_clicked) self.connect("clicked", self.__on_bind_button_clicked)
elif self.layout_prop:
if self.layout_prop:
self.bind_icon = None self.bind_icon = None
self.label.props.label = self.layout_prop.property_id self.label.props.label = self.layout_prop.property_id
self.__update_layout_property_label() self.__update_layout_property_label()
@ -76,6 +72,18 @@ class CmbPropertyLabel(Gtk.Button):
box.append(self.label) box.append(self.label)
self.set_child(box) 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): def __update_label(self, prop):
if prop.value != prop.info.default_value: if prop.value != prop.info.default_value:
self.add_css_class("modified") self.add_css_class("modified")
@ -99,172 +107,20 @@ class CmbPropertyLabel(Gtk.Button):
if not self.bindable: if not self.bindable:
return 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.bind_icon.props.icon_name = "binded-symbolic"
self.remove_css_class("hidden") self.remove_css_class("hidden")
else: else:
self.bind_icon.props.icon_name = "bind-symbolic" self.bind_icon.props.icon_name = "bind-symbolic"
self.add_css_class("hidden") 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): 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) popover.set_parent(self)
label = Gtk.Label(label="<b>Property Binding</b>", use_markup=True, halign=Gtk.Align.START, hexpand=True) # Destroy popup on close
close = Gtk.Button(icon_name="window-close-symbolic", halign=Gtk.Align.END, css_classes=["close"]) popover.connect("closed", lambda p: p.unparent())
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)
popover.popup() popover.popup()
Gtk.WidgetClass.set_css_name(CmbPropertyLabel, "CmbPropertyLabel") 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): def __type_info_should_append(self, info):
retval = False 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"]: if not info.instantiable or info.layout not in [None, "container"]:
return False return False

View File

@ -30,7 +30,7 @@ import time
import atexit import atexit
import socket 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 gi.repository import GObject, GLib, Gio, Gdk, Gtk, Casilda
from . import config from . import config
@ -131,6 +131,11 @@ class CmbMerengueProcess(GObject.Object):
[wayland_socket, client.fileno()] [wayland_socket, client.fileno()]
) )
# Close file descriptors passed to the client.
# If we do not close wayland_socket then the client wont be destroyed if its process is killed.
os.close(wayland_socket)
client.close()
if valid: if valid:
self.__pid = pid self.__pid = pid
GLib.child_watch_add(GLib.PRIORITY_DEFAULT_IDLE, pid, self.__on_exit, None) GLib.child_watch_add(GLib.PRIORITY_DEFAULT_IDLE, pid, self.__on_exit, None)
@ -248,17 +253,6 @@ class CmbView(Gtk.Box):
self.__merengue_command("quit") self.__merengue_command("quit")
self.__merengue.cleanup() self.__merengue.cleanup()
def __set_dark_mode(self, dark):
valid, bg_color = self.get_style_context().lookup_color('theme_bg_color')
if valid:
self.compositor.props.bg_color = bg_color
return GLib.SOURCE_REMOVE
def _set_dark_mode(self, dark):
# This needs to be called in an idle because theme_bg_color has not changed at this point
GLib.idle_add(self.__set_dark_mode, dark)
def __merengue_command(self, command, payload=None, args=None): def __merengue_command(self, command, payload=None, args=None):
if not self.__merengue_started: if not self.__merengue_started:
return return
@ -624,6 +618,17 @@ class CmbView(Gtk.Box):
}, },
) )
def __set_icontheme_search_paths(self):
if self.project is None or self.project.filename is None:
return
dirname = os.path.dirname(self.project.filename)
self.__merengue_command(
"set_icontheme_search_paths",
args={"paths": [os.path.join(dirname, path) for path in self.project.icontheme_search_paths]}
)
def __on_preview_notify(self, obj, pspec): def __on_preview_notify(self, obj, pspec):
self.__merengue_command("set_app_property", args={"property": "preview", "value": self.preview}) self.__merengue_command("set_app_property", args={"property": "preview", "value": self.preview})
@ -649,6 +654,8 @@ class CmbView(Gtk.Box):
self.__merengue_started = True self.__merengue_started = True
self.__merengue_command("gtk_settings_get", args={"property": "gtk-theme-name"}) self.__merengue_command("gtk_settings_get", args={"property": "gtk-theme-name"})
self.__set_icontheme_search_paths()
self.__load_namespaces() self.__load_namespaces()
self.__load_css_providers() self.__load_css_providers()

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 --> <!-- Created with Cambalache 0.97.5 -->
<interface> <interface>
<!-- interface-name cmb_view.ui --> <!-- interface-name cmb_view.ui -->
<!-- interface-copyright Juan Pablo Ugarte --> <!-- interface-copyright Juan Pablo Ugarte -->
@ -17,9 +17,14 @@
<child> <child>
<object class="GtkGraphicsOffload" id="compositor_offload"> <object class="GtkGraphicsOffload" id="compositor_offload">
<property name="child"> <property name="child">
<object class="CasildaCompositor" id="compositor"> <object class="GtkScrolledWindow">
<property name="hexpand">True</property> <child>
<property name="vexpand">True</property> <object class="CasildaCompositor" id="compositor">
<property name="hexpand">True</property>
<property name="scrollable">True</property>
<property name="vexpand">True</property>
</object>
</child>
</object> </object>
</property> </property>
<property name="hexpand">True</property> <property name="hexpand">True</property>

View File

@ -29,6 +29,7 @@ from .cmb_file_button import CmbFileButton
from .cmb_file_entry import CmbFileEntry from .cmb_file_entry import CmbFileEntry
from .cmb_icon_name_entry import CmbIconNameEntry from .cmb_icon_name_entry import CmbIconNameEntry
from .cmb_pixbuf_entry import CmbPixbufEntry from .cmb_pixbuf_entry import CmbPixbufEntry
from .cmb_property_chooser import CmbPropertyChooser
from .cmb_spin_button import CmbSpinButton from .cmb_spin_button import CmbSpinButton
from .cmb_text_buffer import CmbTextBuffer from .cmb_text_buffer import CmbTextBuffer
from .cmb_toplevel_chooser import CmbToplevelChooser from .cmb_toplevel_chooser import CmbToplevelChooser
@ -41,4 +42,5 @@ from .cmb_source_view import CmbSourceView
from .cmb_switch import CmbSwitch from .cmb_switch import CmbSwitch
from .cmb_text_view import CmbTextView from .cmb_text_view import CmbTextView
from .cmb_translatable_popover import CmbTranslatablePopover from .cmb_translatable_popover import CmbTranslatablePopover
from .cmb_suggestion_entry import CmbSuggestionEntry
from .cmb_create_editor import cmb_create_editor 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_object_list_editor import CmbObjectListEditor
from .cmb_switch import CmbSwitch from .cmb_switch import CmbSwitch
from .cmb_text_view import CmbTextView 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): 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, parent=prop.object if prop else parent,
type_id="GtkAccessible", type_id="GtkAccessible",
) )
elif type_id == "gtype":
editor = CmbSuggestionEntry()
editor.set_suggestions(project._get_types())
elif info: 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: if prop is None:
editor = CmbObjectChooser( editor = CmbObjectChooser(
project=project,
parent=parent, parent=parent,
type_id=type_id, type_id=type_id,
) )
else: else:
editor = CmbObjectChooser( editor = CmbObjectChooser(
project=project,
parent=prop.object, parent=prop.object,
is_inline=project.target_tk == "gtk-4.0" and not prop.info.disable_inline_object, is_inline=project.target_tk == "gtk-4.0" and not prop.info.disable_inline_object,
inline_object_id=prop.inline_object_id, inline_object_id=prop.inline_object_id,

View File

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

View File

@ -27,33 +27,30 @@ from cambalache import _
from gi.repository import GObject, Gdk, Gtk from gi.repository import GObject, Gdk, Gtk
from ..cmb_object import CmbObject from ..cmb_object import CmbObject
from ..cmb_project import CmbProject
from ..cmb_type_chooser_popover import CmbTypeChooserPopover from ..cmb_type_chooser_popover import CmbTypeChooserPopover
class CmbObjectChooser(Gtk.Entry): class CmbObjectChooser(Gtk.Entry):
__gtype_name__ = "CmbObjectChooser" __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)
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)
is_inline = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
inline_object_id = GObject.Property(type=str, default=None, 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) inline_property_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
type_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY) type_id = GObject.Property(type=str, default="GObject", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.__object_id = None self.__object_id = None
self.__updating = None self.__updating = None
super().__init__(**kwargs) super().__init__(**kwargs)
self.connect("notify::text", self.__on_text_notify)
if self.type_id is None: self.connect("notify::parent", self.__on_parent_notify)
self.props.placeholder_text = "<GObject>" self.connect("notify::text", self.__on_text_notify)
return
if self.is_inline: if self.is_inline:
self.connect("icon-press", self.__on_icon_pressed) 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() self.__update_icons()
else: else:
self.props.placeholder_text = f"<{self.type_id}>" self.props.placeholder_text = f"<{self.type_id}>"
@ -66,8 +63,27 @@ class CmbObjectChooser(Gtk.Entry):
drop_target.connect("drop", self.__on_drop_drop) drop_target.connect("drop", self.__on_drop_drop)
self.add_controller(drop_target) 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): 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 return
self.inline_object_id = prop.inline_object_id self.inline_object_id = prop.inline_object_id
@ -112,17 +128,6 @@ class CmbObjectChooser(Gtk.Entry):
# Select dragged object id # Select dragged object id
self.cmb_value = origin_item.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) @GObject.Property(type=str)
def cmb_value(self): def cmb_value(self):
return str(self.__object_id) if self.__object_id else None 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 "" self.props.text = obj.name if obj else ""
else: else:
self.props.text = "" self.props.text = ""
self.__updating = False
def __update_icons(self): def __update_icons(self):
if not self.is_inline: if not self.is_inline:
@ -174,6 +178,9 @@ class CmbObjectChooser(Gtk.Entry):
self.__update_icons() self.__update_icons()
def __on_icon_pressed(self, widget, icon_pos): def __on_icon_pressed(self, widget, icon_pos):
if not self.is_inline:
return
parent = self.parent parent = self.parent
project = parent.project 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 # SPDX-License-Identifier: LGPL-2.1-only
# #
from gi.repository import GObject, GtkSource from gi.repository import GObject, Gtk, GtkSource
class CmbSourceView(GtkSource.View): class CmbSourceView(GtkSource.View):
@ -38,6 +38,27 @@ class CmbSourceView(GtkSource.View):
self.props.buffer = self.buffer self.props.buffer = self.buffer
self.buffer.connect("changed", self.__on_buffer_changed) 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) @GObject.Property(type=str)
def lang(self): def lang(self):
language = self.buffer.get_language() 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_chooser.py',
'cmb_object_list_editor.py', 'cmb_object_list_editor.py',
'cmb_pixbuf_entry.py', 'cmb_pixbuf_entry.py',
'cmb_property_chooser.py',
'cmb_source_view.py', 'cmb_source_view.py',
'cmb_spin_button.py', 'cmb_spin_button.py',
'cmb_suggestion_entry.py',
'cmb_switch.py', 'cmb_switch.py',
'cmb_text_buffer.py', 'cmb_text_buffer.py',
'cmb_text_view.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_owner_id TEXT REFERENCES type ON UPDATE CASCADE,
bind_property_id TEXT, bind_property_id TEXT,
bind_flags TEXT, bind_flags TEXT,
binding_expression_id INTEGER,
binding_expression_object_id INTEGER,
PRIMARY KEY(ui_id, object_id, owner_id, property_id), 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, 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, 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(owner_id, property_id) REFERENCES property,
FOREIGN KEY(ui_id, bind_source_id) REFERENCES object(ui_id, object_id), 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 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_bin.py',
'mrg_adw/mrg_adw_carousel.py', 'mrg_adw/mrg_adw_carousel.py',
'mrg_adw/mrg_adw_dialog.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', 'mrg_adw/mrg_adw_window.py',
], ],
install_dir: join_paths(moduledir, 'mrg_adw') 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_carousel import MrgAdwCarousel
from .mrg_adw_window import MrgAdwWindow from .mrg_adw_window import MrgAdwWindow
from .mrg_adw_dialog import MrgAdwDialog from .mrg_adw_dialog import MrgAdwDialog
from .mrg_adw_view_stack import MrgAdwViewStack
from .mrg_adw_view_stack_page import MrgAdwViewStackPage

View File

@ -31,6 +31,7 @@ class MrgAdwDialog(MrgGtkWidget):
object = GObject.Property(type=Adw.Dialog, flags=GObject.ParamFlags.READWRITE) object = GObject.Property(type=Adw.Dialog, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.__headerbar_height = 0
super().__init__(**kwargs) super().__init__(**kwargs)
def object_changed(self, old, new): def object_changed(self, old, new):
@ -52,6 +53,11 @@ class MrgAdwDialog(MrgGtkWidget):
self.object.present(self.window) self.object.present(self.window)
self.window.present() self.window.present()
minimun, natural = self.object.get_preferred_size()
h_minimun, h_natural = self.__headerbar.get_preferred_size()
self.window.set_default_size(natural.width + 64, natural.height + h_natural.height * 2)
self.window.set_title(GObject.type_name(self.object.__gtype__)) self.window.set_title(GObject.type_name(self.object.__gtype__))
CambalachePrivate.widget_set_application_id(self.window, f"Casilda:{self.ui_id}.{self.object_id}") CambalachePrivate.widget_set_application_id(self.window, f"Casilda:{self.ui_id}.{self.object_id}")
self.__update_placeholder() self.__update_placeholder()
@ -59,10 +65,17 @@ class MrgAdwDialog(MrgGtkWidget):
def __window_new(self): def __window_new(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
headerbar = Gtk.HeaderBar(valign=Gtk.Align.START, vexpand=False) headerbar = Gtk.HeaderBar(valign=Gtk.Align.START, vexpand=False)
self.__headerbar = headerbar
button = Gtk.Button(label="Open", valign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER, vexpand=True) button = Gtk.Button(label="Open", valign=Gtk.Align.CENTER, halign=Gtk.Align.CENTER, vexpand=True)
button.connect("clicked", self.__on_open_button_clicked) button.connect("clicked", self.__on_open_button_clicked)
label = Gtk.Label(label="This window is created automatically to be able to present the AdwDialog.", vexpand=True)
headerbar.pack_end(button)
box.append(headerbar) box.append(headerbar)
box.append(button) box.append(label)
return Adw.Window(deletable=False, content=box) return Adw.Window(deletable=False, content=box)
def __on_open_button_clicked(self, button): def __on_open_button_clicked(self, button):

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) controller.remove_placeholder(modifier)
def load_namespace(self, namespace, version, object_types): def load_namespace(self, namespace, version, object_types):
if version:
gi.require_version(namespace, version)
try: try:
if version:
gi.require_version(namespace, version)
mod = importlib.import_module(f"gi.repository.{namespace}") mod = importlib.import_module(f"gi.repository.{namespace}")
except Exception as e: except Exception as e:
logger.warning(e) logger.warning(e)
@ -312,6 +312,15 @@ class MrgApplication(Gtk.Application):
if css: if css:
css.set_property(field, value) css.set_property(field, value)
def set_icontheme_search_paths(self, paths):
if Gtk.MAJOR_VERSION == 4:
theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
else:
theme = Gtk.IconTheme.get_for_screen(Gdk.Screen.get_default())
for path in paths:
theme.add_search_path(path)
def run_command(self, command, args, payload): def run_command(self, command, args, payload):
logger.debug(f"{command} {args} payload={len(payload) if payload else -1}") logger.debug(f"{command} {args} payload={len(payload) if payload else -1}")
@ -343,6 +352,8 @@ class MrgApplication(Gtk.Application):
self.remove_css_provider(**args) self.remove_css_provider(**args)
elif command == "update_css_provider": elif command == "update_css_provider":
self.update_css_provider(**args) self.update_css_provider(**args)
elif command == "set_icontheme_search_paths":
self.set_icontheme_search_paths(**args)
elif command == "quit": elif command == "quit":
self.quit() self.quit()
else: else:

View File

@ -22,10 +22,4 @@
# SPDX-License-Identifier: LGPL-2.1-only # 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 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 # 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 from merengue.mrg_gtk import MrgGtkWidget
@ -36,7 +36,6 @@ class MrgWebKitWebView(MrgGtkWidget):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
logger.warning("MrgWebKitWebView __init__")
def object_changed(self, old, new): def object_changed(self, old, new):
super().object_changed(old, new) super().object_changed(old, new)
@ -53,20 +52,21 @@ class MrgWebKitWebView(MrgGtkWidget):
<style> <style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: table;
}
div.content { div.content {
display: table-cell;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
border: 3px groove lightgray; border: 3px solid lightgray;
border-radius: 1em; 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> </style>
@ -85,14 +85,15 @@ function open_url() {
<div class="content"> <div class="content">
<h3>WebKit Test Page</h3> <h3>WebKit Test Page</h3>
<span>URL:</span> <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()" /> <input type="button" value="Open" onclick="open_url()" />
<br/> <br/>
<br/> <br/>
<a href="https://gitlab.gnome.org/jpu/cambalache">Cambalache</a> <a href="https://gitlab.gnome.org/jpu/cambalache" title="gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org/">WebKitGtk</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> </div>
</body> </body>
</html> </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 # 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 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 # 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 from merengue.mrg_gtk import MrgGtkWidget
@ -36,7 +36,6 @@ class MrgWebKitWebView(MrgGtkWidget):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
logger.warning("MrgWebKitWebView __init__")
def object_changed(self, old, new): def object_changed(self, old, new):
super().object_changed(old, new) super().object_changed(old, new)
@ -53,20 +52,21 @@ class MrgWebKitWebView(MrgGtkWidget):
<style> <style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: table;
}
div.content { div.content {
display: table-cell;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
border: 3px groove lightgray; border: 3px solid lightgray;
border-radius: 1em; 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> </style>
@ -85,14 +85,15 @@ function open_url() {
<div class="content"> <div class="content">
<h3>WebKit Test Page</h3> <h3>WebKit Test Page</h3>
<span>URL:</span> <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()" /> <input type="button" value="Open" onclick="open_url()" />
<br/> <br/>
<br/> <br/>
<a href="https://gitlab.gnome.org/jpu/cambalache">Cambalache</a> <a href="https://gitlab.gnome.org/jpu/cambalache" title="gitlab.gnome.org/jpu/cambalache">Cambalache</a>
<a href="https://webkitgtk.org/">WebKitGtk</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> </div>
</body> </body>
</html> </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([ install_data([
'cmb_accessible_editor.py', 'cmb_accessible_editor.py',
'cmb_base.py', 'cmb_base.py',
'cmb_binding_popover.py',
'cmb_blueprint.py', 'cmb_blueprint.py',
'cmb_context_menu.py', 'cmb_context_menu.py',
'cmb_css.py', 'cmb_css.py',
'cmb_css_editor.py', 'cmb_css_editor.py',
'cmb_db.py', 'cmb_db.py',
'cmb_db_inspector.py', 'cmb_db_inspector.py',
'cmb_db_migration.py',
'cmb_db_profile.py', 'cmb_db_profile.py',
'cmb_fragment_editor.py', 'cmb_fragment_editor.py',
'cmb_gresource.py', 'cmb_gresource.py',

View File

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

View File

@ -492,11 +492,11 @@
("GResourceFlags","G_RESOURCE_FLAGS_NONE","none",None,"No flags set."), ("GResourceFlags","G_RESOURCE_FLAGS_NONE","none",None,"No flags set."),
("GResourceLookupFlags","G_RESOURCE_LOOKUP_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_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","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 %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_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 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_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_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_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_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_NONE","none",None,"No flags."),
("GSocketMsgFlags","G_SOCKET_MSG_OOB","oob",1,"Request to send/receive out of band data."), ("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 Copyright (C) 2022 Juan Pablo Ugarte
--> -->
<meta> <meta>
<types>
<WebKitWebView workspace-type="MrgDummyWebViewProxy"/>
</types>
<categories> <categories>
<category name="hidden"> <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), ("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","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","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","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","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), ("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), ("WebKitWebExtensionMatchPatternOptions","flags","webkit2gtk",None,None,None,None,None,None,None),
("WebKitWebExtensionMode","enum","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), ("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), ("WebKitWebViewBase","GtkContainer","webkit2gtk",None,None,None,1,"container","hidden",None),
("WebKitWebsiteDataTypes","flags","webkit2gtk",None,None,None,None,None,None,None) ("WebKitWebsiteDataTypes","flags","webkit2gtk",None,None,None,None,None,None,None)
</type> </type>

View File

@ -49,7 +49,7 @@
("WebKitWebExtensionMatchPatternOptions","flags","webkitgtk",None,None,None,None,None,None,None), ("WebKitWebExtensionMatchPatternOptions","flags","webkitgtk",None,None,None,None,None,None,None),
("WebKitWebExtensionMode","enum","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), ("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), ("WebKitWebViewBase","GtkWidget","webkitgtk",None,None,None,1,None,"hidden",None),
("WebKitWebsiteDataTypes","flags","webkitgtk",None,None,None,None,None,None,None) ("WebKitWebsiteDataTypes","flags","webkitgtk",None,None,None,None,None,None,None)
</type> </type>

View File

@ -18,6 +18,22 @@
</properties> </properties>
</GtkWidget> </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> <GtkAccessible>
<properties> <properties>
<property id="accessible-role" construct-only="True"/> <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-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-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-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-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-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), ("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), ("GtkBuilderScope","interface","gtk",None,None,None,None,None,None,None),
("GtkButton","GtkWidget","gtk",None,None,None,1,"container","control",None), ("GtkButton","GtkWidget","gtk",None,None,None,1,"container","control",None),
("GtkButtonsType","enum","gtk",None,None,None,None,None,None,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), ("GtkCalendar","GtkWidget","gtk",None,None,None,None,"container","display",None),
("GtkCallbackAction","GtkShortcutAction","gtk",None,None,None,1,None,None,None), ("GtkCallbackAction","GtkShortcutAction","gtk",None,None,None,1,None,None,None),
("GtkCellArea","GObject","gtk",None,"4.10",1,1,"container",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), ("GtkNothingAction","GtkShortcutAction","gtk",None,None,None,1,None,None,None),
("GtkNumberUpLayout","enum","gtk",None,None,None,None,None,None,None), ("GtkNumberUpLayout","enum","gtk",None,None,None,None,None,None,None),
("GtkNumericSorter","GtkSorter","gtk",None,None,None,1,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), ("GtkOrdering","enum","gtk",None,None,None,None,None,None,None),
("GtkOrientable","interface","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), ("GtkOrientation","enum","gtk",None,None,None,None,None,None,None),
@ -278,7 +276,6 @@
("GtkPageSetupUnixDialog","GtkDialog","gtk",None,None,None,None,"container",None,None), ("GtkPageSetupUnixDialog","GtkDialog","gtk",None,None,None,None,"container",None,None),
("GtkPanDirection","enum","gtk",None,None,None,None,None,None,None), ("GtkPanDirection","enum","gtk",None,None,None,None,None,None,None),
("GtkPaned","GtkWidget","gtk",None,None,None,None,"container","layout",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), ("GtkPasswordEntry","GtkWidget","gtk",None,None,None,1,"container",None,None),
("GtkPasswordEntryBuffer","GtkEntryBuffer","gtk",None,None,None,1,None,None,None), ("GtkPasswordEntryBuffer","GtkEntryBuffer","gtk",None,None,None,1,None,None,None),
("GtkPickFlags","flags","gtk",None,None,None,None,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."), ("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_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_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."), ("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_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."), ("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) ("GtkWindow","titlebar",1,None)
</type_child_type> </type_child_type>
<type_child_constraint> <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) ("GtkStack","GtkStackPage",1,1)
</type_child_constraint> </type_child_constraint>
<type_internal_child> <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","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","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), ("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), ("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-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), ("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","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","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), ("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","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","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), ("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), ("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","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), ("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","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","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), ("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","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","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","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","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","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), ("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","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","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","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), ("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","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), ("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-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-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), ("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), ("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), ("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), ("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","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","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), ("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","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","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), ("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","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), ("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), ("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","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","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), ("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), ("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","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), ("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","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), ("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( project(
'cambalache', 'c', 'cambalache', 'c',
version: '0.97.4', version: '0.97.7',
meson_version: '>= 1.1.0', meson_version: '>= 1.1.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
@ -29,7 +29,7 @@ privatecmb_catalog_gendir = join_paths(get_option('prefix'), get_option('libdir'
libxml2_dep = dependency('libxml-2.0', version: '>= 2.9.0') libxml2_dep = dependency('libxml-2.0', version: '>= 2.9.0')
pygobject_dep = dependency('pygobject-3.0', version: '>= 3.52.0') pygobject_dep = dependency('pygobject-3.0', version: '>= 3.52.0')
gtk4_dep = dependency('gtk4', version: '>= 4.18.0') gtk4_dep = dependency('gtk4', version: '>= 4.18.0')
casilda_dep = dependency('casilda-0.1', version: '>= 0.9.1', fallback: ['casilda', 'casilda_dep']) casilda_dep = dependency('casilda-0.2', version: '>= 0.9.2', fallback: ['casilda', 'casilda_dep'])
adw_dep = dependency('libadwaita-1', version: '>= 1.7.0') adw_dep = dependency('libadwaita-1', version: '>= 1.7.0')
gtksource_dep = dependency('gtksourceview-5', version: '>= 5.16.0') gtksource_dep = dependency('gtksourceview-5', version: '>= 5.16.0')

View File

@ -1,5 +1,5 @@
[wrap-git] [wrap-git]
directory = casilda directory = casilda
url = https://gitlab.gnome.org/jpu/casilda.git url = https://gitlab.gnome.org/jpu/casilda.git
revision = 0.9.1 revision = 0.9.2
depth = 1 depth = 1

View File

@ -7,6 +7,7 @@ basedir = os.path.join(os.path.split(os.path.dirname(__file__))[0])
homedir = os.path.join(basedir, ".local", "home") homedir = os.path.join(basedir, ".local", "home")
os.makedirs(os.path.join(homedir, "Projects"), exist_ok=True) os.makedirs(os.path.join(homedir, "Projects"), exist_ok=True)
os.environ["CMB_NOTIFICATION_URL"] = "https://dummybackend.local:2000"
os.environ["GSETTINGS_BACKEND"] = "memory" os.environ["GSETTINGS_BACKEND"] = "memory"
os.environ["HOME"] = homedir os.environ["HOME"] = homedir

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'?> <?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd"> <!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> <ui>
(1,None,"template_inline_object.ui","template_inline_object.ui",None,None,None,None,None,None,None), <requires>MyWindow</requires>
(2,1,None,"my-window.ui",None,None,None,None,None,None,None) <content><![CDATA[<?xml version='1.0' encoding='UTF-8'?>
</ui> <!-- Created with Cambalache 0.96.1 -->
<object> <interface>
(1,1,"MyWindow",None,None,None,None,None,None,None), <!-- interface-name template_inline_object.ui -->
(2,1,"GtkWindow","MyWindow",None,None,None,None,None,None), <requires lib="gtk" version="4.6"/>
(2,2,"GtkBox",None,1,None,None,None,None,None), <object class="MyWindow">
(2,3,"GtkLabel",None,2,None,None,None,1,None) <property name="title">This window should have a label inside a inline box as child</property>
</object> </object>
<object_property> </interface>
(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), ]]></content>
(2,1,"GtkWindow","child",None,None,None,None,None,2,None,None,None,None), </ui>
(2,3,"GtkLabel","label","a label inside an inline object",None,None,None,None,None,None,None,None,None) <ui template-class="MyWindow">
</object_property> <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> </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", "menu.ui"),
("gtk-4.0", "string_list.ui"), ("gtk-4.0", "string_list.ui"),
("gtk-4.0", "accessibility.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): def test_(target_tk, filename):
""" """

View File

@ -76,6 +76,7 @@ class CmbGirData:
"GParamGType": "gtype", "GParamGType": "gtype",
"GParamBoxed": "boxed", "GParamBoxed": "boxed",
"GParamVariant": "variant", "GParamVariant": "variant",
"GtkParamSpecExpression": "object",
} }
# GtkBuilder native object types # 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): def __a11y_add_ifaces_from_enum(self, accessible_ifaces):
accessible_types = {} accessible_types = {}
@ -727,10 +786,14 @@ class CmbGirData:
def _get_type_data(self, element, name, use_instance=True, skip_types=[]): def _get_type_data(self, element, name, use_instance=True, skip_types=[]):
parent = element.get("parent") 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 parent = self.prefix + parent
elif parent is None: elif parent is None:
parent = "object" parent = "object"
elif parent == "GObject.ParamSpec":
return None
else: else:
parent = self.external_types.get(parent, "GObject") parent = self.external_types.get(parent, "GObject")