mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-06-25 00:02:51 -04:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
53062b7d3c | ||
|
9aaace82f7 | ||
|
bbb2ce2bf9 | ||
|
dd90c3fc2b | ||
|
8742945f3f | ||
|
06c62349a7 | ||
|
bc6163b1ac | ||
|
ac74e23fde | ||
|
2e29c016f7 | ||
|
b572a7eae3 | ||
|
f96d19ab64 | ||
|
680f9d3243 | ||
|
6fa7f22c9c | ||
|
6ac16ca8c0 | ||
|
e2dbb15a18 | ||
|
c57891a3c8 | ||
|
ccdf5d6ef0 | ||
|
bb39873cd5 | ||
|
9529bfaf76 | ||
|
13db1c20c2 | ||
|
963cee5eec | ||
|
08a73f9c28 | ||
|
02935555bf | ||
|
befb056d2c | ||
|
9cd6e05d5f | ||
|
57a3f3832a | ||
|
8c80fc5d3d |
@ -9,11 +9,6 @@ Cambalache used even/odd minor numbers to differentiate between stable and
|
|||||||
development releases.
|
development releases.
|
||||||
|
|
||||||
|
|
||||||
## 0.96.1
|
|
||||||
|
|
||||||
- Fix/improve GResource list model update
|
|
||||||
- Fix removing custom class data like styles
|
|
||||||
|
|
||||||
## 0.96.0
|
## 0.96.0
|
||||||
|
|
||||||
- Add GResource support
|
- Add GResource support
|
||||||
|
@ -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.2.0",
|
"tag" : "0.9.0",
|
||||||
"commit" : "99a0173f21345b85713198c1fa1fbb388d00182f"
|
"commit" : "4f7b1be321cf76832b12bda11fd91897257377e2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -43,9 +43,12 @@ from cambalache import (
|
|||||||
notification_center,
|
notification_center,
|
||||||
config,
|
config,
|
||||||
utils,
|
utils,
|
||||||
_
|
_,
|
||||||
|
N_
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from cambalache.cmb_blueprint import CmbBlueprintError
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
GObject.type_ensure(CmbGResourceEditor.__gtype__)
|
GObject.type_ensure(CmbGResourceEditor.__gtype__)
|
||||||
@ -64,6 +67,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
gtk4_filter = Gtk.Template.Child()
|
gtk4_filter = Gtk.Template.Child()
|
||||||
gtk3_filter = Gtk.Template.Child()
|
gtk3_filter = Gtk.Template.Child()
|
||||||
gtk_builder_filter = Gtk.Template.Child()
|
gtk_builder_filter = Gtk.Template.Child()
|
||||||
|
blueprint_filter = Gtk.Template.Child()
|
||||||
glade_filter = Gtk.Template.Child()
|
glade_filter = Gtk.Template.Child()
|
||||||
css_filter = Gtk.Template.Child()
|
css_filter = Gtk.Template.Child()
|
||||||
gresource_filter = Gtk.Template.Child()
|
gresource_filter = Gtk.Template.Child()
|
||||||
@ -136,7 +140,13 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
self.gtk4_import_filters = Gio.ListStore()
|
self.gtk4_import_filters = Gio.ListStore()
|
||||||
|
|
||||||
for filter in [self.gtk4_filter, self.gtk_builder_filter, self.css_filter, self.gresource_filter]:
|
for filter in [
|
||||||
|
self.gtk4_filter,
|
||||||
|
self.gtk_builder_filter,
|
||||||
|
self.blueprint_filter,
|
||||||
|
self.css_filter,
|
||||||
|
self.gresource_filter
|
||||||
|
]:
|
||||||
self.gtk4_import_filters.append(filter)
|
self.gtk4_import_filters.append(filter)
|
||||||
|
|
||||||
self.gtk3_import_filters = Gio.ListStore()
|
self.gtk3_import_filters = Gio.ListStore()
|
||||||
@ -1135,7 +1145,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
print("IMPORT", path, content_type)
|
print("IMPORT", path, content_type)
|
||||||
|
|
||||||
if content_type in ["application/x-gtk-builder", "application/x-glade"]:
|
if content_type in ["application/x-gtk-builder", "application/x-glade", "text/x-blueprint"]:
|
||||||
self.import_file(file.get_path())
|
self.import_file(file.get_path())
|
||||||
elif content_type == "text/css":
|
elif content_type == "text/css":
|
||||||
self.project.add_css(path)
|
self.project.add_css(path)
|
||||||
@ -1164,10 +1174,25 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.project.set_selection([gresource])
|
self.project.set_selection([gresource])
|
||||||
|
|
||||||
def __save(self):
|
def __save(self):
|
||||||
if self.project.save():
|
retval = False
|
||||||
self.__last_saved_index = self.project.history_index
|
|
||||||
self.__update_action_save()
|
try :
|
||||||
self.emit("project-saved", self.project)
|
retval = self.project.save()
|
||||||
|
except CmbBlueprintError as e:
|
||||||
|
self.present_message_to_user(
|
||||||
|
_("Error saving project"),
|
||||||
|
secondary_text=N_(
|
||||||
|
"blueprintcompiler encounter the following error:",
|
||||||
|
"blueprintcompiler encounter the following errors:",
|
||||||
|
len(e.errors)
|
||||||
|
),
|
||||||
|
details=[str(e)]
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if retval:
|
||||||
|
self.__last_saved_index = self.project.history_index
|
||||||
|
self.__update_action_save()
|
||||||
|
self.emit("project-saved", self.project)
|
||||||
|
|
||||||
def save_project(self):
|
def save_project(self):
|
||||||
if self.project is None:
|
if self.project is None:
|
||||||
|
@ -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.1 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_window.ui -->
|
<!-- interface-name cmb_window.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -836,6 +836,9 @@
|
|||||||
<object class="GtkFileFilter" id="glade_filter">
|
<object class="GtkFileFilter" id="glade_filter">
|
||||||
<property name="mime-types">application/x-glade</property>
|
<property name="mime-types">application/x-glade</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="blueprint_filter">
|
||||||
|
<property name="mime-types">text/x-blueprint</property>
|
||||||
|
</object>
|
||||||
<object class="GtkFileFilter" id="css_filter">
|
<object class="GtkFileFilter" id="css_filter">
|
||||||
<property name="mime-types">text/css</property>
|
<property name="mime-types">text/css</property>
|
||||||
</object>
|
</object>
|
||||||
@ -844,6 +847,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<object class="GtkFileFilter" id="gtk4_filter">
|
<object class="GtkFileFilter" id="gtk4_filter">
|
||||||
<property name="mime-types">application/x-gtk-builder
|
<property name="mime-types">application/x-gtk-builder
|
||||||
|
text/x-blueprint
|
||||||
text/css</property>
|
text/css</property>
|
||||||
<property name="name">All supported files</property>
|
<property name="name">All supported files</property>
|
||||||
<property name="suffixes">gresource.xml</property>
|
<property name="suffixes">gresource.xml</property>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?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.95.1 -->
|
<!-- Created with Cambalache 0.97.1 -->
|
||||||
<cambalache-project version="0.95.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="fdcf4cd517493f548aa4b4fe206ff7762cee9cdda7ec5a85a718b46eb1c4731b"/>
|
||||||
<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"/>
|
||||||
@ -222,8 +222,8 @@
|
|||||||
<ui template-class="CmbDBInspector" filename="cmb_db_inspector.ui" sha256="4451cdb08d24bd4a802ea692c0ebb4ef46af13152984c0b435d29bf4eb7dab55"/>
|
<ui template-class="CmbDBInspector" filename="cmb_db_inspector.ui" sha256="4451cdb08d24bd4a802ea692c0ebb4ef46af13152984c0b435d29bf4eb7dab55"/>
|
||||||
<ui filename="app/cmb_shortcuts.ui" sha256="d7ac37fd2430788a9e210ed4bc84dcfeba5609bdcc801afb192bfd900c7a8883"/>
|
<ui filename="app/cmb_shortcuts.ui" sha256="d7ac37fd2430788a9e210ed4bc84dcfeba5609bdcc801afb192bfd900c7a8883"/>
|
||||||
<ui template-class="CmbFileButton" filename="control/cmb_file_button.ui" sha256="f859b4f85d7c80c1fef69b68ebb9129423d9c72fdb38d304132784f7361cbbfd">
|
<ui template-class="CmbFileButton" filename="control/cmb_file_button.ui" sha256="f859b4f85d7c80c1fef69b68ebb9129423d9c72fdb38d304132784f7361cbbfd">
|
||||||
<property id="dialog_title" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
<property id="dialog-title" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||||
<property id="use_open" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
<property id="use-open" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||||
</ui>
|
</ui>
|
||||||
<ui template-class="CmbNotificationListView" filename="cmb_notification_list_view.ui" sha256="13622645038ef2aaa154f74cd300f9c0fa0dccf69d45d6c9376f9034e6ee57fb"/>
|
<ui template-class="CmbNotificationListView" filename="cmb_notification_list_view.ui" sha256="13622645038ef2aaa154f74cd300f9c0fa0dccf69d45d6c9376f9034e6ee57fb"/>
|
||||||
<ui template-class="CmbVersionNotificationView" filename="cmb_version_notification_view.ui" sha256="9a3ced46b90eb7e425d1c345853c4e8e908870c61c75475f7e20ce3c9ee8cec6"/>
|
<ui template-class="CmbVersionNotificationView" filename="cmb_version_notification_view.ui" sha256="9a3ced46b90eb7e425d1c345853c4e8e908870c61c75475f7e20ce3c9ee8cec6"/>
|
||||||
@ -256,7 +256,7 @@
|
|||||||
<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="46969468ae070bdd315ca4091869d0b8a9bcb24c10cde82208bfd7d36f39fdd0">
|
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="2050887ef1c45facb6ebff14500214bb035e6808ca61d2a7d661e696d79026ca">
|
||||||
<requires>CmbFileButton</requires>
|
<requires>CmbFileButton</requires>
|
||||||
<requires>CmbEntry</requires>
|
<requires>CmbEntry</requires>
|
||||||
</ui>
|
</ui>
|
||||||
@ -264,13 +264,13 @@
|
|||||||
<requires>CmbFileButton</requires>
|
<requires>CmbFileButton</requires>
|
||||||
<requires>CmbSourceView</requires>
|
<requires>CmbSourceView</requires>
|
||||||
</ui>
|
</ui>
|
||||||
<ui template-class="CmbUIEditor" filename="cmb_ui_editor.ui" sha256="0e4e205a3737fa207406ce74f4d8d3fbb4a477409b8a7b425b3d53c41309b306">
|
<ui template-class="CmbUIEditor" filename="cmb_ui_editor.ui" sha256="70e272e2c6c499a5424c6019154cd8338d9edde1fa111ad592a1c104019bb7ee">
|
||||||
<requires>CmbTextBuffer</requires>
|
<requires>CmbTextBuffer</requires>
|
||||||
<requires>CmbFileButton</requires>
|
<requires>CmbFileButton</requires>
|
||||||
<requires>CmbEntry</requires>
|
<requires>CmbEntry</requires>
|
||||||
<requires>CmbToplevelChooser</requires>
|
<requires>CmbToplevelChooser</requires>
|
||||||
</ui>
|
</ui>
|
||||||
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="20a192093a13209e0add725f15922e4f09689dda08fbf0b3a3eedf5d9adf2efc">
|
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="df07e3e03b88f9b097ad7d65efa15923d339db83b9d4a7c015a312a71f8c9685">
|
||||||
<requires>CmbNotificationListView</requires>
|
<requires>CmbNotificationListView</requires>
|
||||||
<requires>CmbScrolledWindow</requires>
|
<requires>CmbScrolledWindow</requires>
|
||||||
<requires>CmbObjectEditor</requires>
|
<requires>CmbObjectEditor</requires>
|
||||||
|
86
cambalache/cmb_blueprint.py
Normal file
86
cambalache/cmb_blueprint.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#
|
||||||
|
# Blueprint compiler integration functions
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
try:
|
||||||
|
import blueprintcompiler as bp
|
||||||
|
from blueprintcompiler import parser, tokenizer
|
||||||
|
from blueprintcompiler.decompiler import decompile_string
|
||||||
|
from blueprintcompiler.outputs import XmlOutput
|
||||||
|
except Exception:
|
||||||
|
bp = None
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBlueprintError(Exception):
|
||||||
|
def __init__(self, message, errors=[]):
|
||||||
|
super().__init__(message)
|
||||||
|
self.errors = errors
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBlueprintUnsupportedError(CmbBlueprintError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBlueprintMissingError(CmbBlueprintError):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("blueprintcompiler is not available")
|
||||||
|
|
||||||
|
|
||||||
|
def cmb_blueprint_decompile(data: str) -> str:
|
||||||
|
if bp is None:
|
||||||
|
raise CmbBlueprintMissingError()
|
||||||
|
|
||||||
|
try:
|
||||||
|
retval = decompile_string(data)
|
||||||
|
except bp.decompiler.UnsupportedError as e:
|
||||||
|
raise CmbBlueprintUnsupportedError(str(e))
|
||||||
|
except Exception as e:
|
||||||
|
raise CmbBlueprintError(str(e))
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
def cmb_blueprint_compile(data: str) -> str:
|
||||||
|
if bp is None:
|
||||||
|
raise CmbBlueprintMissingError()
|
||||||
|
|
||||||
|
tokens = tokenizer.tokenize(data)
|
||||||
|
ast, errors, warnings = parser.parse(tokens)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
f = io.StringIO("")
|
||||||
|
errors.pretty_print("temp", data, f)
|
||||||
|
f.seek(0)
|
||||||
|
raise CmbBlueprintError(f.read(), errors=errors)
|
||||||
|
|
||||||
|
if ast is None:
|
||||||
|
raise CmbBlueprintError("AST is None")
|
||||||
|
|
||||||
|
# Ignore warnings
|
||||||
|
|
||||||
|
retval = XmlOutput().emit(ast)
|
||||||
|
return retval.encode()
|
||||||
|
|
@ -82,6 +82,9 @@ class CmbDB(GObject.GObject):
|
|||||||
self.__history_commands = {}
|
self.__history_commands = {}
|
||||||
self.__table_column_mapping = {}
|
self.__table_column_mapping = {}
|
||||||
|
|
||||||
|
self._output_lowercase_boolean = False
|
||||||
|
self._output_use_enum_value = False
|
||||||
|
|
||||||
self.clipboard = []
|
self.clipboard = []
|
||||||
self.clipboard_ids = []
|
self.clipboard_ids = []
|
||||||
|
|
||||||
@ -2372,6 +2375,7 @@ class CmbDB(GObject.GObject):
|
|||||||
|
|
||||||
value = None
|
value = None
|
||||||
value_node = None
|
value_node = None
|
||||||
|
pinfo = self.type_info.get(property_type_id, None)
|
||||||
|
|
||||||
is_inline_object = not disable_inline_object and self.target_tk == "gtk-4.0"
|
is_inline_object = not disable_inline_object and self.target_tk == "gtk-4.0"
|
||||||
|
|
||||||
@ -2399,6 +2403,10 @@ class CmbDB(GObject.GObject):
|
|||||||
value = obj_name
|
value = obj_name
|
||||||
elif property_type_id == "GBytes":
|
elif property_type_id == "GBytes":
|
||||||
value = etree.CDATA(val)
|
value = etree.CDATA(val)
|
||||||
|
elif self._output_lowercase_boolean and property_type_id == "gboolean":
|
||||||
|
value = "true" if utils.bool_from_string(val) else "false"
|
||||||
|
elif self._output_use_enum_value and pinfo and pinfo.parent_id == "enum":
|
||||||
|
value = str(pinfo.enum_get_value_as_string(val, use_nick=False))
|
||||||
else:
|
else:
|
||||||
value = val
|
value = val
|
||||||
|
|
||||||
@ -2445,16 +2453,16 @@ class CmbDB(GObject.GObject):
|
|||||||
node = E.signal(name=name, handler=handler)
|
node = E.signal(name=name, handler=handler)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
utils.xml_node_set(node, "object", data)
|
|
||||||
|
|
||||||
# if object is set, swap defaults to True
|
# if object is set, swap defaults to True
|
||||||
if not swap:
|
if not swap:
|
||||||
utils.xml_node_set(node, "swapped", "no")
|
utils.xml_node_set(node, "swapped", "False")
|
||||||
|
|
||||||
|
utils.xml_node_set(node, "object", data)
|
||||||
elif swap:
|
elif swap:
|
||||||
utils.xml_node_set(node, "swapped", "yes")
|
utils.xml_node_set(node, "swapped", "True")
|
||||||
|
|
||||||
if after:
|
if after:
|
||||||
utils.xml_node_set(node, "after", "yes")
|
utils.xml_node_set(node, "after", "True")
|
||||||
obj.append(node)
|
obj.append(node)
|
||||||
self.__node_add_comment(node, comment)
|
self.__node_add_comment(node, comment)
|
||||||
|
|
||||||
@ -2518,6 +2526,7 @@ class CmbDB(GObject.GObject):
|
|||||||
owner_id,
|
owner_id,
|
||||||
) = row
|
) = row
|
||||||
|
|
||||||
|
pinfo = self.type_info.get(property_type_id, None)
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
# Ignore properties depending on metadata (Gtk4)
|
# Ignore properties depending on metadata (Gtk4)
|
||||||
@ -2537,6 +2546,15 @@ class CmbDB(GObject.GObject):
|
|||||||
if obj_name is None:
|
if obj_name is None:
|
||||||
continue
|
continue
|
||||||
value = obj_name
|
value = obj_name
|
||||||
|
elif self._output_lowercase_boolean and property_type_id == "gboolean":
|
||||||
|
value = "true" if utils.bool_from_string(val) else "false"
|
||||||
|
elif self._output_lowercase_boolean and property_type_id == "CmbBooleanUndefined":
|
||||||
|
if val == "undefined":
|
||||||
|
value = "undefined"
|
||||||
|
else:
|
||||||
|
value = "true" if utils.bool_from_string(val) else "false"
|
||||||
|
elif self._output_use_enum_value and pinfo and pinfo.parent_id == "enum":
|
||||||
|
value = str(pinfo.enum_get_value_as_string(val, use_nick=False))
|
||||||
else:
|
else:
|
||||||
value = val
|
value = val
|
||||||
|
|
||||||
@ -2746,7 +2764,7 @@ class CmbDB(GObject.GObject):
|
|||||||
for child in root:
|
for child in root:
|
||||||
node.append(child)
|
node.append(child)
|
||||||
|
|
||||||
def export_ui(self, ui_id, merengue=False, skip_version_comment=False):
|
def export_ui(self, ui_id, merengue=False):
|
||||||
c = self.conn.cursor()
|
c = self.conn.cursor()
|
||||||
|
|
||||||
c.execute("SELECT translation_domain, comment, template_id, custom_fragment FROM ui WHERE ui_id=?;", (ui_id,))
|
c.execute("SELECT translation_domain, comment, template_id, custom_fragment FROM ui WHERE ui_id=?;", (ui_id,))
|
||||||
@ -2759,8 +2777,7 @@ class CmbDB(GObject.GObject):
|
|||||||
|
|
||||||
node = E.interface()
|
node = E.interface()
|
||||||
|
|
||||||
if not skip_version_comment:
|
node.addprevious(etree.Comment(f" Created with Cambalache {config.VERSION} "))
|
||||||
node.addprevious(etree.Comment(f" Created with Cambalache {config.VERSION} "))
|
|
||||||
|
|
||||||
utils.xml_node_set(node, "domain", translation_domain)
|
utils.xml_node_set(node, "domain", translation_domain)
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ class CmbNotificationCenter(GObject.GObject):
|
|||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
# Reset retry interval
|
# Reset retry interval
|
||||||
self.retry_interval = 2
|
self.retry_interval = 8
|
||||||
|
|
||||||
data = response.read().decode()
|
data = response.read().decode()
|
||||||
|
|
||||||
@ -292,7 +292,7 @@ class CmbNotificationCenter(GObject.GObject):
|
|||||||
self.retry_interval *= 2
|
self.retry_interval *= 2
|
||||||
self.retry_interval = min(self.retry_interval, 256)
|
self.retry_interval = min(self.retry_interval, 256)
|
||||||
|
|
||||||
logger.warning(f"Request error {e}, retrying in {self.retry_interval}s")
|
logger.info(f"Request error {e}, retrying in {self.retry_interval}s")
|
||||||
GLib.timeout_add_seconds(self.retry_interval, self._get_notification)
|
GLib.timeout_add_seconds(self.retry_interval, self._get_notification)
|
||||||
|
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
|
@ -458,11 +458,17 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
def remove_data(self, data):
|
def remove_data(self, data):
|
||||||
try:
|
try:
|
||||||
assert data.get_id_string() in self.data_dict
|
assert data.get_id_string() in self.data_dict
|
||||||
|
|
||||||
|
self.project.history_push(
|
||||||
|
_("Remove {key} from {name}").format(key=data.info.key, name=self.display_name_type)
|
||||||
|
)
|
||||||
|
|
||||||
self.project.db.execute(
|
self.project.db.execute(
|
||||||
"DELETE FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=?;",
|
"DELETE FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=?;",
|
||||||
(self.ui_id, self.object_id, data.owner_id, data.data_id, data.id),
|
(self.ui_id, self.object_id, data.owner_id, data.data_id, data.id),
|
||||||
)
|
)
|
||||||
self.project.db.commit()
|
self.project.db.commit()
|
||||||
|
self.project.history_pop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||||
return False
|
return False
|
||||||
|
@ -27,7 +27,7 @@ from gi.repository import GObject
|
|||||||
|
|
||||||
from .cmb_objects_base import CmbBaseObjectData
|
from .cmb_objects_base import CmbBaseObjectData
|
||||||
from .cmb_type_info import CmbTypeDataInfo
|
from .cmb_type_info import CmbTypeDataInfo
|
||||||
from cambalache import getLogger
|
from cambalache import getLogger, _
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
@ -194,11 +194,17 @@ class CmbObjectData(CmbBaseObjectData):
|
|||||||
def remove_data(self, data):
|
def remove_data(self, data):
|
||||||
try:
|
try:
|
||||||
assert data in self.children
|
assert data in self.children
|
||||||
|
|
||||||
|
self.project.history_push(
|
||||||
|
_("Remove {key} from {name}").format(key=data.info.key, name=self.object.display_name_type)
|
||||||
|
)
|
||||||
|
|
||||||
self.project.db.execute(
|
self.project.db.execute(
|
||||||
"DELETE FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=?;",
|
"DELETE FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=?;",
|
||||||
(self.ui_id, self.object_id, data.owner_id, data.data_id, data.id),
|
(self.ui_id, self.object_id, data.owner_id, data.data_id, data.id),
|
||||||
)
|
)
|
||||||
self.project.db.commit()
|
self.project.db.commit()
|
||||||
|
self.project.history_pop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||||
return False
|
return False
|
||||||
|
@ -31,6 +31,12 @@ from .control import cmb_create_editor
|
|||||||
from cambalache import _
|
from cambalache import _
|
||||||
|
|
||||||
|
|
||||||
|
# Everyone knows that debugging is twice as hard as writing a program in the first place.
|
||||||
|
# So if you’re as clever as you can be when you write it, how will you ever debug it?
|
||||||
|
# -- Brian Kernighan, 1974
|
||||||
|
#
|
||||||
|
# TODO: rewrite this!
|
||||||
|
|
||||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_object_data_editor.ui")
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_object_data_editor.ui")
|
||||||
class CmbObjectDataEditor(Gtk.Box):
|
class CmbObjectDataEditor(Gtk.Box):
|
||||||
__gtype_name__ = "CmbObjectDataEditor"
|
__gtype_name__ = "CmbObjectDataEditor"
|
||||||
@ -69,11 +75,8 @@ class CmbObjectDataEditor(Gtk.Box):
|
|||||||
def __on_remove_clicked(self, button):
|
def __on_remove_clicked(self, button):
|
||||||
if self.info:
|
if self.info:
|
||||||
self.object.remove_data(self.__data)
|
self.object.remove_data(self.__data)
|
||||||
else:
|
elif self.__data:
|
||||||
if self.__data.parent:
|
self.__data.parent.remove_data(self.__data)
|
||||||
self.__data.parent.remove_data(self.__data)
|
|
||||||
else:
|
|
||||||
self.__data.object.remove_data(self.__data)
|
|
||||||
|
|
||||||
@GObject.Property(type=GObject.Object)
|
@GObject.Property(type=GObject.Object)
|
||||||
def object(self):
|
def object(self):
|
||||||
@ -136,8 +139,9 @@ class CmbObjectDataEditor(Gtk.Box):
|
|||||||
self.__update_arg(key)
|
self.__update_arg(key)
|
||||||
|
|
||||||
def __on_data_added(self, obj, data):
|
def __on_data_added(self, obj, data):
|
||||||
self.data = data
|
if self.info and self.data is None and self.info == data.info:
|
||||||
self.__update_view()
|
self.data = data
|
||||||
|
self.__update_view()
|
||||||
|
|
||||||
def __on_data_removed(self, obj, data):
|
def __on_data_removed(self, obj, data):
|
||||||
self.__remove_data_editor(data)
|
self.__remove_data_editor(data)
|
||||||
|
@ -260,7 +260,7 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
|||||||
hexpand=True,
|
hexpand=True,
|
||||||
object=obj,
|
object=obj,
|
||||||
data=data,
|
data=data,
|
||||||
info=None if data else info.data[data_key],
|
info=info.data[data_key],
|
||||||
)
|
)
|
||||||
|
|
||||||
grid.attach(editor, 0, i, 2, 1)
|
grid.attach(editor, 0, i, 2, 1)
|
||||||
|
@ -58,6 +58,9 @@ class CmbPath(CmbBase, Gio.ListModel):
|
|||||||
return self.__path_items.get(directory, None)
|
return self.__path_items.get(directory, None)
|
||||||
|
|
||||||
def add_item(self, item):
|
def add_item(self, item):
|
||||||
|
if item in self.__items:
|
||||||
|
return
|
||||||
|
|
||||||
display_name = item.display_name
|
display_name = item.display_name
|
||||||
is_path = isinstance(item, CmbPath)
|
is_path = isinstance(item, CmbPath)
|
||||||
|
|
||||||
@ -85,9 +88,13 @@ class CmbPath(CmbBase, Gio.ListModel):
|
|||||||
self.notify("display-name")
|
self.notify("display-name")
|
||||||
|
|
||||||
def remove_item(self, item):
|
def remove_item(self, item):
|
||||||
|
if item not in self.__items:
|
||||||
|
return
|
||||||
|
|
||||||
if isinstance(item, CmbPath) and item.path in self.__path_items:
|
if isinstance(item, CmbPath) and item.path in self.__path_items:
|
||||||
del self.__path_items[item.path]
|
del self.__path_items[item.path]
|
||||||
|
|
||||||
|
item.path_parent = None
|
||||||
i = self.__items.index(item)
|
i = self.__items.index(item)
|
||||||
self.__items.pop(i)
|
self.__items.pop(i)
|
||||||
self.items_changed(i, 1, 0)
|
self.items_changed(i, 1, 0)
|
||||||
|
@ -27,9 +27,10 @@ import os
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from gi.repository import GObject, Gio
|
from gi.repository import GObject, Gio, GLib
|
||||||
from graphlib import TopologicalSorter, CycleError
|
from graphlib import TopologicalSorter, CycleError
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
@ -49,6 +50,7 @@ from .cmb_layout_property import CmbLayoutProperty
|
|||||||
from .cmb_library_info import CmbLibraryInfo
|
from .cmb_library_info import CmbLibraryInfo
|
||||||
from .cmb_type_info import CmbTypeInfo
|
from .cmb_type_info import CmbTypeInfo
|
||||||
from .cmb_objects_base import CmbSignal
|
from .cmb_objects_base import CmbSignal
|
||||||
|
from .cmb_blueprint import cmb_blueprint_decompile, cmb_blueprint_compile
|
||||||
from .utils import FileHash
|
from .utils import FileHash
|
||||||
from . import constants, utils
|
from . import constants, utils
|
||||||
from cambalache import config, getLogger, _, N_
|
from cambalache import config, getLogger, _, N_
|
||||||
@ -276,7 +278,23 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
|
|
||||||
return root, relpath, hexdigest
|
return root, relpath, hexdigest
|
||||||
|
|
||||||
return None, None
|
return None, None, None
|
||||||
|
|
||||||
|
def __parse_blp_file(self, filename):
|
||||||
|
fullpath, relpath = self.__get_abs_path(filename)
|
||||||
|
|
||||||
|
with open(fullpath, "rb") as fd:
|
||||||
|
blueprint_decompiled = fd.read()
|
||||||
|
m = hashlib.sha256()
|
||||||
|
m.update(blueprint_decompiled)
|
||||||
|
hexdigest = m.hexdigest()
|
||||||
|
|
||||||
|
blueprint_compiled = cmb_blueprint_compile(blueprint_decompiled.decode())
|
||||||
|
root = etree.fromstring(blueprint_compiled)
|
||||||
|
|
||||||
|
return root, relpath, hexdigest
|
||||||
|
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
def __get_version_comment_from_root(self, root):
|
def __get_version_comment_from_root(self, root):
|
||||||
comment = root.getprevious()
|
comment = root.getprevious()
|
||||||
@ -287,7 +305,10 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
def __load_ui_from_node(self, node):
|
def __load_ui_from_node(self, node):
|
||||||
filename, sha256 = utils.xml_node_get(node, ["filename", "sha256"])
|
filename, sha256 = utils.xml_node_get(node, ["filename", "sha256"])
|
||||||
if filename:
|
if filename:
|
||||||
root, relpath, hexdigest = self.__parse_xml_file(filename)
|
if filename.endswith(".blp"):
|
||||||
|
root, relpath, hexdigest = self.__parse_blp_file(filename)
|
||||||
|
else:
|
||||||
|
root, relpath, hexdigest = self.__parse_xml_file(filename)
|
||||||
|
|
||||||
if sha256 != hexdigest:
|
if sha256 != hexdigest:
|
||||||
logger.warning(f"{filename} hash mismatch, file was modified")
|
logger.warning(f"{filename} hash mismatch, file was modified")
|
||||||
@ -525,36 +546,49 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
else:
|
else:
|
||||||
fullpath = filename
|
fullpath = filename
|
||||||
|
|
||||||
dirty = True
|
interface = root.getroot()
|
||||||
|
hexdigest = None
|
||||||
|
blueprint_decompiled = None
|
||||||
|
use_blp = filename.endswith(".blp")
|
||||||
|
|
||||||
|
if use_blp:
|
||||||
|
str_exported = etree.tostring(interface, pretty_print=True, encoding="UTF-8").decode("UTF-8")
|
||||||
|
blueprint_decompiled = cmb_blueprint_decompile(str_exported)
|
||||||
|
|
||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
os.makedirs(os.path.dirname(fullpath), exist_ok=True)
|
os.makedirs(os.path.dirname(fullpath), exist_ok=True)
|
||||||
|
|
||||||
original_comment, original_hash = self.__file_state.get(filename, (None, None))
|
original_comment, original_hash = self.__file_state.get(filename, (None, None))
|
||||||
if original_comment is not None:
|
if original_comment is not None:
|
||||||
interface = root.getroot()
|
if use_blp:
|
||||||
|
m = hashlib.sha256()
|
||||||
|
m.update(blueprint_decompiled.encode())
|
||||||
|
hexdigest = m.hexdigest()
|
||||||
|
else:
|
||||||
|
comment = self.__get_version_comment_from_root(interface)
|
||||||
|
new_comment = comment.text
|
||||||
|
comment.text = original_comment.text
|
||||||
|
|
||||||
comment = self.__get_version_comment_from_root(interface)
|
# Calculate hash
|
||||||
new_comment = comment.text
|
hash_file = FileHash()
|
||||||
comment.text = original_comment.text
|
|
||||||
|
|
||||||
# Calculate hash
|
|
||||||
hash_file = FileHash()
|
|
||||||
root.write(hash_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
|
|
||||||
hexdigest = hash_file.hexdigest()
|
|
||||||
hash_file.close()
|
|
||||||
|
|
||||||
comment.text = new_comment
|
|
||||||
dirty = original_hash != hexdigest
|
|
||||||
|
|
||||||
if dirty:
|
|
||||||
# Dump xml to file
|
|
||||||
with open(fullpath, "wb") as fd:
|
|
||||||
hash_file = FileHash(fd)
|
|
||||||
root.write(hash_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
|
root.write(hash_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
|
||||||
hexdigest = hash_file.hexdigest()
|
hexdigest = hash_file.hexdigest()
|
||||||
hash_file.close()
|
hash_file.close()
|
||||||
|
|
||||||
|
comment.text = new_comment
|
||||||
|
|
||||||
|
if original_hash is None or original_hash != hexdigest:
|
||||||
|
if use_blp:
|
||||||
|
with open(fullpath, "wb") as fd:
|
||||||
|
fd.write(blueprint_decompiled.encode())
|
||||||
|
else:
|
||||||
|
# Dump xml to file
|
||||||
|
with open(fullpath, "wb") as fd:
|
||||||
|
hash_file = FileHash(fd)
|
||||||
|
root.write(hash_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
|
||||||
|
hexdigest = hash_file.hexdigest()
|
||||||
|
hash_file.close()
|
||||||
|
|
||||||
# Store filename and hash in node
|
# Store filename and hash in node
|
||||||
utils.xml_node_set(node, "filename", filename)
|
utils.xml_node_set(node, "filename", filename)
|
||||||
utils.xml_node_set(node, "sha256", hexdigest)
|
utils.xml_node_set(node, "sha256", hexdigest)
|
||||||
@ -631,8 +665,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
self.__save_xml_and_update_node(ui, root, filename)
|
self.__save_xml_and_update_node(ui, root, filename)
|
||||||
else:
|
else:
|
||||||
# Embed UI content in project as CDATA
|
# Embed UI content in project as CDATA
|
||||||
root = self.db.export_ui(ui_id, skip_version_comment=True)
|
root = self.db.export_ui(ui_id)
|
||||||
self.__save_xml_in_node(ui, root)
|
self.__save_xml_in_node(ui, root.getroot())
|
||||||
|
|
||||||
return ui
|
return ui
|
||||||
|
|
||||||
@ -655,8 +689,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
self.__save_xml_and_update_node(gresources, root, filename)
|
self.__save_xml_and_update_node(gresources, root, filename)
|
||||||
else:
|
else:
|
||||||
# Embed file contents in project as CDATA
|
# Embed file contents in project as CDATA
|
||||||
root = self.db.export_gresource(gresource_id, skip_version_comment=True)
|
root = self.db.export_gresource(gresource_id)
|
||||||
self.__save_xml_in_node(gresources, root)
|
self.__save_xml_in_node(gresources, root.getroot())
|
||||||
|
|
||||||
return gresources
|
return gresources
|
||||||
|
|
||||||
@ -774,7 +808,12 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
|
|
||||||
# Import file
|
# Import file
|
||||||
self.foreign_keys = False
|
self.foreign_keys = False
|
||||||
root, relpath, hexdigest = self.__parse_xml_file(filename)
|
|
||||||
|
if filename.endswith(".blp"):
|
||||||
|
root, relpath, hexdigest = self.__parse_blp_file(filename)
|
||||||
|
else:
|
||||||
|
root, relpath, hexdigest = self.__parse_xml_file(filename)
|
||||||
|
|
||||||
ui_id = self.db.import_from_node(root, relpath)
|
ui_id = self.db.import_from_node(root, relpath)
|
||||||
self.foreign_keys = True
|
self.foreign_keys = True
|
||||||
|
|
||||||
@ -2241,6 +2280,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
item.path_parent = None
|
||||||
self.__items.insert(i, item)
|
self.__items.insert(i, item)
|
||||||
self.items_changed(i, 0, 1)
|
self.items_changed(i, 0, 1)
|
||||||
|
|
||||||
@ -2296,23 +2336,45 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
|
|
||||||
path_parent = item.path_parent
|
path_parent = item.path_parent
|
||||||
|
|
||||||
|
# Do not do anything if the path is the same
|
||||||
|
if path_parent and path_parent.path and path_parent.path == os.path.dirname(filename):
|
||||||
|
return
|
||||||
|
|
||||||
# Remove item
|
# Remove item
|
||||||
self.__remove_item(item)
|
self.__remove_item(item)
|
||||||
# add it again
|
# add it again
|
||||||
self.__add_item(item, filename)
|
self.__add_item(item, filename)
|
||||||
|
|
||||||
if in_selection:
|
if in_selection:
|
||||||
self.set_selection([item])
|
GLib.idle_add(self.__set_selection_idle, item)
|
||||||
|
|
||||||
# Clear unused paths
|
# Clear unused paths
|
||||||
if path_parent.n_items == 0:
|
if path_parent and path_parent.n_items == 0:
|
||||||
while path_parent is not None:
|
GLib.idle_add(self.__clear_unused_paths_idle, path_parent)
|
||||||
next_parent = path_parent.path_parent
|
|
||||||
|
|
||||||
if path_parent.n_items <= 1:
|
def __set_selection_idle(self, item):
|
||||||
logger.warning(path_parent)
|
self.set_selection([item])
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
path_parent = next_parent
|
def __clear_unused_paths_idle(self, path_parent):
|
||||||
|
if path_parent.n_items:
|
||||||
|
return
|
||||||
|
|
||||||
|
while path_parent is not None:
|
||||||
|
next_parent = path_parent.path_parent
|
||||||
|
|
||||||
|
if path_parent.n_items != 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
path_parent = next_parent
|
||||||
|
|
||||||
|
if path_parent:
|
||||||
|
if path_parent.path_parent:
|
||||||
|
path_parent.path_parent.remove_item(path_parent)
|
||||||
|
else:
|
||||||
|
self.__remove_item(path_parent)
|
||||||
|
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
def do_ui_added(self, ui):
|
def do_ui_added(self, ui):
|
||||||
self.__add_item(ui, ui.filename)
|
self.__add_item(ui, ui.filename)
|
||||||
|
@ -331,7 +331,7 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def enum_get_value_as_string(self, value):
|
def enum_get_value_as_string(self, value, use_nick=True):
|
||||||
if self.parent_id != "enum":
|
if self.parent_id != "enum":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
|||||||
|
|
||||||
# Always use nick as value
|
# Always use nick as value
|
||||||
if value == enum_name or value == enum_nick or value == str(enum_value):
|
if value == enum_name or value == enum_nick or value == str(enum_value):
|
||||||
return enum_nick
|
return enum_nick if use_nick else enum_value
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -65,6 +65,10 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
|||||||
def __on_notify(self, obj, pspec):
|
def __on_notify(self, obj, pspec):
|
||||||
self.project._ui_changed(self, pspec.name)
|
self.project._ui_changed(self, pspec.name)
|
||||||
|
|
||||||
|
# Update display name if one of the following properties changed
|
||||||
|
if pspec.name in ["filename", "template-id"]:
|
||||||
|
self.notify("display-name")
|
||||||
|
|
||||||
def list_libraries(self):
|
def list_libraries(self):
|
||||||
retval = {}
|
retval = {}
|
||||||
|
|
||||||
|
@ -23,8 +23,11 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from gi.repository import GObject, Gtk
|
from gi.repository import GObject, Gtk
|
||||||
|
|
||||||
|
from cambalache import _
|
||||||
from .cmb_ui import CmbUI
|
from .cmb_ui import CmbUI
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +36,7 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
__gtype_name__ = "CmbUIEditor"
|
__gtype_name__ = "CmbUIEditor"
|
||||||
|
|
||||||
filename = Gtk.Template.Child()
|
filename = Gtk.Template.Child()
|
||||||
|
format = Gtk.Template.Child()
|
||||||
template_id = Gtk.Template.Child()
|
template_id = Gtk.Template.Child()
|
||||||
description = Gtk.Template.Child()
|
description = Gtk.Template.Child()
|
||||||
copyright = Gtk.Template.Child()
|
copyright = Gtk.Template.Child()
|
||||||
@ -54,9 +58,6 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
|
|
||||||
@object.setter
|
@object.setter
|
||||||
def _set_object(self, obj):
|
def _set_object(self, obj):
|
||||||
if obj == self._object:
|
|
||||||
return
|
|
||||||
|
|
||||||
for binding in self._bindings:
|
for binding in self._bindings:
|
||||||
binding.unbind()
|
binding.unbind()
|
||||||
|
|
||||||
@ -79,9 +80,15 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
self.template_id.object = obj
|
self.template_id.object = obj
|
||||||
self.filename.dirname = obj.project.dirname
|
self.filename.dirname = obj.project.dirname
|
||||||
|
|
||||||
|
# Set some default name
|
||||||
|
self.filename.unnamed_filename = _("unnamed.ui")
|
||||||
|
if not obj.filename and obj.template_id:
|
||||||
|
template = obj.project.get_object_by_id(obj.ui_id, obj.template_id)
|
||||||
|
if template:
|
||||||
|
self.filename.unnamed_filename = f"{template.name}.ui".lower()
|
||||||
|
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
binding = GObject.Object.bind_property(
|
binding = obj.bind_property(
|
||||||
obj,
|
|
||||||
field,
|
field,
|
||||||
getattr(self, field),
|
getattr(self, field),
|
||||||
"cmb-value",
|
"cmb-value",
|
||||||
@ -89,5 +96,42 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
)
|
)
|
||||||
self._bindings.append(binding)
|
self._bindings.append(binding)
|
||||||
|
|
||||||
|
if obj.project.target_tk == "gtk-4.0":
|
||||||
|
self.filename.mime_types = "application/x-gtk-builder;text/x-blueprint"
|
||||||
|
|
||||||
|
# filename -> format
|
||||||
|
binding = obj.bind_property(
|
||||||
|
"filename",
|
||||||
|
self.format,
|
||||||
|
"selected",
|
||||||
|
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||||
|
transform_to=self.__filename_to_format,
|
||||||
|
transform_from=self.__format_to_filename,
|
||||||
|
user_data=obj
|
||||||
|
)
|
||||||
|
self._bindings.append(binding)
|
||||||
|
|
||||||
|
self.format.show()
|
||||||
|
self.format.set_sensitive(bool(obj.filename))
|
||||||
|
else:
|
||||||
|
self.filename.mime_types = "application/x-gtk-builder;application/x-glade"
|
||||||
|
self.format.hide()
|
||||||
|
|
||||||
|
def __filename_to_format(self, binding, source_value, ui):
|
||||||
|
if not source_value:
|
||||||
|
self.format.props.sensitive = False
|
||||||
|
return 0
|
||||||
|
self.format.props.sensitive = True
|
||||||
|
|
||||||
|
return 1 if source_value.endswith(".blp") else 0
|
||||||
|
|
||||||
|
def __format_to_filename(self, binding, target_value, ui):
|
||||||
|
if not ui.filename:
|
||||||
|
self.format.props.sensitive = False
|
||||||
|
return None
|
||||||
|
self.format.props.sensitive = True
|
||||||
|
|
||||||
|
return os.path.splitext(ui.filename)[0] + (".blp" if target_value == 1 else ".ui")
|
||||||
|
|
||||||
|
|
||||||
Gtk.WidgetClass.set_css_name(CmbUIEditor, "CmbUIEditor")
|
Gtk.WidgetClass.set_css_name(CmbUIEditor, "CmbUIEditor")
|
||||||
|
@ -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.1 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_ui_editor.ui -->
|
<!-- interface-name cmb_ui_editor.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<property name="label" translatable="yes">Description:</property>
|
<property name="label" translatable="yes">Description:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">2</property>
|
<property name="row">3</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<property name="label" translatable="yes">Copyright:</property>
|
<property name="label" translatable="yes">Copyright:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">3</property>
|
<property name="row">4</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
<property name="label" translatable="yes">Authors:</property>
|
<property name="label" translatable="yes">Authors:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">4</property>
|
<property name="row">5</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<property name="label" translatable="yes">Domain:</property>
|
<property name="label" translatable="yes">Domain:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">5</property>
|
<property name="row">6</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -78,7 +78,7 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">5</property>
|
<property name="row">6</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -95,7 +95,7 @@
|
|||||||
<property name="min-content-height">96</property>
|
<property name="min-content-height">96</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">2</property>
|
<property name="row">3</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
<property name="min-content-height">96</property>
|
<property name="min-content-height">96</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">4</property>
|
<property name="row">5</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -124,7 +124,7 @@
|
|||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">1</property>
|
<property name="row">2</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -134,7 +134,7 @@
|
|||||||
<property name="label" translatable="yes">Template:</property>
|
<property name="label" translatable="yes">Template:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">1</property>
|
<property name="row">2</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -151,7 +151,7 @@
|
|||||||
<property name="min-content-height">96</property>
|
<property name="min-content-height">96</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">3</property>
|
<property name="row">4</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -161,7 +161,7 @@
|
|||||||
<property name="label" translatable="yes">Comment:</property>
|
<property name="label" translatable="yes">Comment:</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">0</property>
|
<property name="column">0</property>
|
||||||
<property name="row">6</property>
|
<property name="row">7</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -178,7 +178,34 @@
|
|||||||
<property name="min-content-height">96</property>
|
<property name="min-content-height">96</property>
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">6</property>
|
<property name="row">7</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Format:</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="row">1</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkDropDown" id="format">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="model">
|
||||||
|
<object class="GtkStringList">
|
||||||
|
<property name="strings">Gtk Builder
|
||||||
|
Blueprint</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="column-span">1</property>
|
||||||
|
<property name="row">1</property>
|
||||||
|
<property name="row-span">1</property>
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
@ -39,8 +39,9 @@ from . import config
|
|||||||
from .cmb_ui import CmbUI
|
from .cmb_ui import CmbUI
|
||||||
from .cmb_object import CmbObject
|
from .cmb_object import CmbObject
|
||||||
from .cmb_context_menu import CmbContextMenu
|
from .cmb_context_menu import CmbContextMenu
|
||||||
|
from cambalache.cmb_blueprint import cmb_blueprint_decompile
|
||||||
from . import utils
|
from . import utils
|
||||||
from cambalache import getLogger, _
|
from cambalache import getLogger, _, N_
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
@ -167,7 +168,6 @@ class CmbMerengueProcess(GObject.Object):
|
|||||||
env = json.loads(os.environ.get("MERENGUE_DEV_ENV", "{}"))
|
env = json.loads(os.environ.get("MERENGUE_DEV_ENV", "{}"))
|
||||||
env = env | {
|
env = env | {
|
||||||
"GDK_BACKEND": "wayland",
|
"GDK_BACKEND": "wayland",
|
||||||
"GSK_RENDERER": "cairo",
|
|
||||||
"WAYLAND_DISPLAY": self.wayland_display,
|
"WAYLAND_DISPLAY": self.wayland_display,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +279,7 @@ class CmbView(Gtk.Box):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__project = None
|
self.__project = None
|
||||||
self.__ui_id = 0
|
self.__ui = None
|
||||||
self.__theme = None
|
self.__theme = None
|
||||||
|
|
||||||
self.menu = self.__create_context_menu()
|
self.menu = self.__create_context_menu()
|
||||||
@ -337,14 +337,34 @@ class CmbView(Gtk.Box):
|
|||||||
return self.__project.db.tostring(ui_id, merengue=merengue)
|
return self.__project.db.tostring(ui_id, merengue=merengue)
|
||||||
|
|
||||||
def __update_view(self):
|
def __update_view(self):
|
||||||
if self.__project and self.__ui_id > 0:
|
if self.__project and self.__ui:
|
||||||
if self.stack.props.visible_child_name == "ui_xml":
|
if self.stack.props.visible_child_name == "ui_xml":
|
||||||
ui = self.__get_ui_xml(self.__ui_id)
|
ui_source = self.__get_ui_xml(self.__ui.ui_id)
|
||||||
self.text_view.buffer.set_text(ui)
|
|
||||||
|
if self.__ui.filename and self.__ui.filename.endswith(".blp"):
|
||||||
|
try:
|
||||||
|
ui_source = cmb_blueprint_decompile(ui_source)
|
||||||
|
self.text_view.lang = "blueprint"
|
||||||
|
except Exception as e:
|
||||||
|
ui_source = _("Error exporting project")
|
||||||
|
ui_source += "\n"
|
||||||
|
ui_source += N_(
|
||||||
|
"blueprintcompiler encounter the following error:",
|
||||||
|
"blueprintcompiler encounter the following errors:",
|
||||||
|
len(e.errors)
|
||||||
|
)
|
||||||
|
ui_source += "\n"
|
||||||
|
ui_source += str(e)
|
||||||
|
self.text_view.lang = ""
|
||||||
|
# TODO: forward error to parent to show to user
|
||||||
|
else:
|
||||||
|
self.text_view.lang = "xml"
|
||||||
|
|
||||||
|
self.text_view.buffer.set_text(ui_source)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.text_view.buffer.set_text("")
|
self.text_view.buffer.set_text("")
|
||||||
self.__ui_id = 0
|
self.__ui = None
|
||||||
|
|
||||||
def __get_ui_dirname(self, ui_id):
|
def __get_ui_dirname(self, ui_id):
|
||||||
dirname = GLib.get_home_dir()
|
dirname = GLib.get_home_dir()
|
||||||
@ -448,19 +468,23 @@ class CmbView(Gtk.Box):
|
|||||||
if len(selection) > 0:
|
if len(selection) > 0:
|
||||||
obj = selection[0]
|
obj = selection[0]
|
||||||
|
|
||||||
if type(obj) not in [CmbUI, CmbObject]:
|
if isinstance(obj, CmbUI):
|
||||||
|
ui = obj
|
||||||
|
elif isinstance(obj, CmbObject):
|
||||||
|
ui = obj.ui
|
||||||
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
ui_id = obj.ui_id
|
ui_id = obj.ui_id
|
||||||
|
|
||||||
if self.__ui_id != ui_id:
|
if self.__ui != ui:
|
||||||
self.__ui_id = ui_id
|
self.__ui = ui
|
||||||
self.__merengue_update_ui(ui_id)
|
self.__merengue_update_ui(ui.ui_id)
|
||||||
|
|
||||||
objects = self.__get_selection_objects(selection, ui_id)
|
objects = self.__get_selection_objects(selection, ui.ui_id)
|
||||||
self.__merengue_command("selection_changed", args={"ui_id": ui_id, "selection": objects})
|
self.__merengue_command("selection_changed", args={"ui_id": ui_id, "selection": objects})
|
||||||
else:
|
else:
|
||||||
self.__ui_id = 0
|
self.__ui = None
|
||||||
self.__merengue_update_ui(0)
|
self.__merengue_update_ui(0)
|
||||||
|
|
||||||
self.__update_view()
|
self.__update_view()
|
||||||
@ -632,7 +656,7 @@ class CmbView(Gtk.Box):
|
|||||||
self.__merengue_last_exit = None
|
self.__merengue_last_exit = None
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__ui_id = 0
|
self.__ui = None
|
||||||
self.__merengue.start()
|
self.__merengue.start()
|
||||||
|
|
||||||
def __command_selection_changed(self, selection):
|
def __command_selection_changed(self, selection):
|
||||||
@ -691,7 +715,7 @@ class CmbView(Gtk.Box):
|
|||||||
|
|
||||||
self.__load_css_providers()
|
self.__load_css_providers()
|
||||||
|
|
||||||
self.__ui_id = 0
|
self.__ui = None
|
||||||
self.__on_project_selection_changed(self.__project)
|
self.__on_project_selection_changed(self.__project)
|
||||||
elif command == "placeholder_selected":
|
elif command == "placeholder_selected":
|
||||||
self.emit(
|
self.emit(
|
||||||
|
@ -35,6 +35,8 @@ class CmbFileButton(Gtk.Button):
|
|||||||
|
|
||||||
dirname = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
dirname = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
dialog_title = GObject.Property(type=str, default=_("Select filename"), flags=GObject.ParamFlags.READWRITE)
|
dialog_title = GObject.Property(type=str, default=_("Select filename"), flags=GObject.ParamFlags.READWRITE)
|
||||||
|
accept_label = GObject.Property(type=str, default=_("Select"), flags=GObject.ParamFlags.READWRITE)
|
||||||
|
unnamed_filename = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
use_open = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
use_open = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
label = Gtk.Template.Child()
|
label = Gtk.Template.Child()
|
||||||
@ -42,21 +44,35 @@ class CmbFileButton(Gtk.Button):
|
|||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.__filename = None
|
self.__filename = None
|
||||||
|
self.__filters = None
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_button_clicked")
|
@Gtk.Template.Callback("on_button_clicked")
|
||||||
def __on_button_clicked(self, button):
|
def __on_button_clicked(self, button):
|
||||||
dialog = Gtk.FileDialog(
|
dialog = Gtk.FileDialog(
|
||||||
modal=True,
|
modal=True,
|
||||||
title=self.dialog_title
|
filters=self.__filters,
|
||||||
|
title=self.dialog_title,
|
||||||
|
accept_label=self.accept_label
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.dirname is not None:
|
if self.dirname is not None:
|
||||||
if self.__filename is not None:
|
if self.__filename:
|
||||||
fullpath = os.path.join(self.dirname, self.__filename)
|
fullpath = os.path.join(self.dirname, self.__filename)
|
||||||
dialog.set_initial_file(Gio.File.new_for_path(fullpath))
|
|
||||||
|
file = Gio.File.new_for_path(fullpath)
|
||||||
|
dialog.set_initial_file(file)
|
||||||
|
|
||||||
|
# See which filter matches the file info and use it as default
|
||||||
|
if file.query_exists(None):
|
||||||
|
info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, Gio.FileQueryInfoFlags.NONE, None)
|
||||||
|
for filter in self.__filters:
|
||||||
|
if filter.match(info):
|
||||||
|
dialog.set_default_filter(filter)
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
dialog.set_initial_folder(Gio.File.new_for_path(self.dirname))
|
dialog.set_initial_folder(Gio.File.new_for_path(self.dirname))
|
||||||
# dialog.set_initial_name("unnamed.ui")
|
if self.unnamed_filename:
|
||||||
|
dialog.set_initial_name(self.unnamed_filename)
|
||||||
|
|
||||||
def dialog_callback(dialog, res):
|
def dialog_callback(dialog, res):
|
||||||
try:
|
try:
|
||||||
@ -81,3 +97,18 @@ class CmbFileButton(Gtk.Button):
|
|||||||
|
|
||||||
self.__filename = value if value is not None else ""
|
self.__filename = value if value is not None else ""
|
||||||
self.label.set_label(self.__filename)
|
self.label.set_label(self.__filename)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def mime_types(self):
|
||||||
|
if self.__filters:
|
||||||
|
return ";".join([f.props.mime_types for f in self.__filters])
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@mime_types.setter
|
||||||
|
def _set_mime_types(self, value):
|
||||||
|
if value:
|
||||||
|
self.__filters = Gio.ListStore()
|
||||||
|
for mime in value.split(';'):
|
||||||
|
self.__filters.append(Gtk.FileFilter(mime_types=[mime]))
|
||||||
|
else:
|
||||||
|
self.__filters = None
|
||||||
|
@ -40,7 +40,8 @@ class CmbSourceView(GtkSource.View):
|
|||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def lang(self):
|
def lang(self):
|
||||||
return self.buffer.get_language()
|
language = self.buffer.get_language()
|
||||||
|
return language.get_id() if language else ""
|
||||||
|
|
||||||
@lang.setter
|
@lang.setter
|
||||||
def _set_lang(self, value):
|
def _set_lang(self, value):
|
||||||
|
@ -26,6 +26,7 @@ configure_file(
|
|||||||
install_data([
|
install_data([
|
||||||
'cmb_accessible_editor.py',
|
'cmb_accessible_editor.py',
|
||||||
'cmb_base.py',
|
'cmb_base.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',
|
||||||
|
@ -14,15 +14,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</description>
|
</description>
|
||||||
<releases>
|
<releases>
|
||||||
<release date="2025-05-16" version="0.96.1">
|
|
||||||
<description>
|
|
||||||
<p>GResource First Bugfix Release!</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fix/improve GResource list model update</li>
|
|
||||||
<li>Fix removing custom class data like styles</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release date="2025-04-20" version="0.96.0">
|
<release date="2025-04-20" version="0.96.0">
|
||||||
<description>
|
<description>
|
||||||
<p>GResource Release!</p>
|
<p>GResource Release!</p>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'cambalache', 'c',
|
'cambalache', 'c',
|
||||||
version: '0.96.1',
|
version: '0.97.3',
|
||||||
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.2.0', fallback: ['casilda', 'casilda_dep'])
|
casilda_dep = dependency('casilda-0.1', version: '>= 0.9.0', 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')
|
||||||
|
|
||||||
|
@ -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.2.0
|
revision = 0.9.0
|
||||||
depth = 1
|
depth = 1
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
<signal name="activate-default" handler="on_activate_default"/>
|
<signal name="activate-default" handler="on_activate_default"/>
|
||||||
<signal name="activate-default" handler="on_activate_default2"/>
|
<signal name="activate-default" handler="on_activate_default2"/>
|
||||||
<signal name="add" handler="on_add"/>
|
<signal name="add" handler="on_add"/>
|
||||||
<signal name="focus" handler="on_focus" swapped="yes"/>
|
<signal name="focus" handler="on_focus" swapped="True"/>
|
||||||
<signal name="focus-in-event" handler="on_focus_in_event" after="yes"/>
|
<signal name="focus-in-event" handler="on_focus_in_event" after="True"/>
|
||||||
<signal name="focus-out-event" handler="on_focus_out_event" swapped="yes" after="yes"/>
|
<signal name="focus-out-event" handler="on_focus_out_event" swapped="True" after="True"/>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkDialog" id="dialog1"/>
|
<object class="GtkDialog" id="dialog1"/>
|
||||||
</interface>
|
</interface>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="label">
|
<object class="GtkLabel" id="label">
|
||||||
<accessibility>
|
<accessibility>
|
||||||
<property name="help-text">help text</property>
|
<property name="description">help text</property>
|
||||||
<property name="label">a label</property>
|
<property name="label">a label</property>
|
||||||
<relation name="described-by">a11y3</relation>
|
<relation name="described-by">a11y3</relation>
|
||||||
<relation name="details">a11y2</relation>
|
<relation name="details">a11y2</relation>
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<signal name="activate" handler="on_button_activate"/>
|
<signal name="activate" handler="on_button_activate"/>
|
||||||
<signal name="clicked" handler="on_button_clicked" swapped="yes"/>
|
<signal name="clicked" handler="on_button_clicked" swapped="True"/>
|
||||||
<signal name="clicked" handler="on_button_clicked2" after="yes"/>
|
<signal name="clicked" handler="on_button_clicked2" after="True"/>
|
||||||
<signal name="clicked" handler="on_button_clicked3" swapped="yes" after="yes"/>
|
<signal name="clicked" handler="on_button_clicked3" swapped="True" after="True"/>
|
||||||
<signal name="clicked" handler="on_button_clicked4" object="win1"/>
|
<signal name="clicked" handler="on_button_clicked4" object="win1"/>
|
||||||
<signal name="clicked" handler="on_button_clicked5" object="win1" swapped="no"/>
|
<signal name="clicked" handler="on_button_clicked5" swapped="False" object="win1"/>
|
||||||
<signal name="notify::label" handler="on_notify"/>
|
<signal name="notify::label" handler="on_notify"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name string_list.ui -->
|
<!-- interface-name string_list.ui -->
|
||||||
<requires lib="gtk" version="4.8"/>
|
<requires lib="gtk" version="4.10"/>
|
||||||
<object class="GtkStringList" id="list1">
|
<object class="GtkStringList" id="list1">
|
||||||
<property name="strings">a
|
<property name="strings">a
|
||||||
b
|
b
|
||||||
|
109
tests/test_cmb_blueprint.py
Normal file
109
tests/test_cmb_blueprint.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/pytest
|
||||||
|
|
||||||
|
"""
|
||||||
|
import .ui files into cambalache and compare it to blueprint compiler output
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
from cambalache import CmbProject
|
||||||
|
from cambalache.cmb_blueprint import cmb_blueprint_decompile, cmb_blueprint_compile, CmbBlueprintUnsupportedError
|
||||||
|
|
||||||
|
|
||||||
|
def tostring(ui):
|
||||||
|
db = ui.project.db
|
||||||
|
|
||||||
|
# Internal API to ensure Cambalache outputs the same as blueprint compiler
|
||||||
|
db._output_lowercase_boolean = True
|
||||||
|
db._output_use_enum_value = True
|
||||||
|
|
||||||
|
tree = db.export_ui(ui.ui_id)
|
||||||
|
|
||||||
|
if tree is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Remove all XML comments since they are not supported by blueprint
|
||||||
|
for node in root.xpath("//comment()"):
|
||||||
|
parent = node.getparent()
|
||||||
|
if parent is not None:
|
||||||
|
parent.remove(node)
|
||||||
|
|
||||||
|
for node in root.iterfind("requires"):
|
||||||
|
lib = node.get("lib", None)
|
||||||
|
if lib != "gtk":
|
||||||
|
root.remove(node)
|
||||||
|
|
||||||
|
return etree.tostring(root, pretty_print=True, encoding="UTF-8").decode("UTF-8")
|
||||||
|
|
||||||
|
|
||||||
|
def get_exported_and_blueprint(filename):
|
||||||
|
"""
|
||||||
|
import .ui file and compare it with the exported version
|
||||||
|
"""
|
||||||
|
path = os.path.join(os.path.dirname(__file__), "gtk-4.0", filename)
|
||||||
|
project = CmbProject(target_tk="gtk-4.0")
|
||||||
|
ui, msgs, detail_msg = project.import_file(path)
|
||||||
|
|
||||||
|
assert (ui)
|
||||||
|
assert (msgs is None)
|
||||||
|
assert (detail_msg is None)
|
||||||
|
|
||||||
|
# Export Cambalache UI
|
||||||
|
str_exported = tostring(ui)
|
||||||
|
|
||||||
|
# Decompile and recompile UI to Blueprint
|
||||||
|
blueprint_decompiled = cmb_blueprint_decompile(str_exported)
|
||||||
|
blueprint_compiled = cmb_blueprint_compile(blueprint_decompiled)
|
||||||
|
assert blueprint_compiled is not None
|
||||||
|
|
||||||
|
# Remove blueprint DO NOT EDIT comment
|
||||||
|
root = etree.fromstring(blueprint_compiled)
|
||||||
|
blueprint_compiled = etree.tostring(root, pretty_print=True, encoding="UTF-8").decode("UTF-8")
|
||||||
|
|
||||||
|
return str_exported, blueprint_compiled
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("filename", [
|
||||||
|
"window.ui",
|
||||||
|
"children.ui",
|
||||||
|
"layout.ui",
|
||||||
|
"signals.ui",
|
||||||
|
"template.ui",
|
||||||
|
"inline_object.ui",
|
||||||
|
"stack_page.ui",
|
||||||
|
"comboboxtext.ui",
|
||||||
|
"style.ui",
|
||||||
|
"filefilter.ui",
|
||||||
|
"menu.ui",
|
||||||
|
|
||||||
|
# help-text is not an accessibility property
|
||||||
|
"accessibility.ui",
|
||||||
|
|
||||||
|
# bind-flags missing
|
||||||
|
# "bindings.ui",
|
||||||
|
|
||||||
|
# Translation comment missing
|
||||||
|
# "string_list.ui",
|
||||||
|
])
|
||||||
|
def test_assert_exported_and_compiled(filename):
|
||||||
|
"""
|
||||||
|
import .ui file and compare it with the exported version
|
||||||
|
"""
|
||||||
|
str_exported, blueprint_compiled = get_exported_and_blueprint(filename)
|
||||||
|
|
||||||
|
# Compare blueprint generated string with cambalache
|
||||||
|
assert blueprint_compiled.strip() == str_exported.strip()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("filename", [
|
||||||
|
"liststore.ui",
|
||||||
|
"treestore.ui",
|
||||||
|
"label.ui",
|
||||||
|
])
|
||||||
|
def test_assert_exported_and_compiled_unsupported(filename):
|
||||||
|
with pytest.raises(CmbBlueprintUnsupportedError):
|
||||||
|
str_exported, blueprint_compiled = get_exported_and_blueprint(filename)
|
||||||
|
|
@ -4,11 +4,52 @@
|
|||||||
import .ui files into cambalache and export to compare results
|
import .ui files into cambalache and export to compare results
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
from cambalache import CmbProject, config
|
from cambalache import CmbProject, config
|
||||||
|
|
||||||
|
|
||||||
def assert_original_and_exported(target_tk, filename):
|
@pytest.mark.parametrize("target_tk,filename", [
|
||||||
|
("gtk+-3.0", "window.ui"),
|
||||||
|
("gtk+-3.0", "children.ui"),
|
||||||
|
("gtk+-3.0", "packing.ui"),
|
||||||
|
("gtk+-3.0", "signals.ui"),
|
||||||
|
("gtk+-3.0", "template.ui"),
|
||||||
|
("gtk+-3.0", "comboboxtext.ui"),
|
||||||
|
("gtk+-3.0", "dialog.ui"),
|
||||||
|
("gtk+-3.0", "label.ui"),
|
||||||
|
("gtk+-3.0", "levelbar.ui"),
|
||||||
|
("gtk+-3.0", "liststore.ui"),
|
||||||
|
("gtk+-3.0", "scale.ui"),
|
||||||
|
("gtk+-3.0", "sizegroup.ui"),
|
||||||
|
("gtk+-3.0", "style.ui"),
|
||||||
|
("gtk+-3.0", "treestore.ui"),
|
||||||
|
("gtk+-3.0", "filefilter.ui"),
|
||||||
|
("gtk+-3.0", "custom_fragment.ui"),
|
||||||
|
("gtk+-3.0", "bindings.ui"),
|
||||||
|
("gtk+-3.0", "menu.ui"),
|
||||||
|
("gtk+-3.0", "accessibility.ui"),
|
||||||
|
("gtk-4.0", "window.ui"),
|
||||||
|
("gtk-4.0", "children.ui"),
|
||||||
|
("gtk-4.0", "layout.ui"),
|
||||||
|
("gtk-4.0", "signals.ui"),
|
||||||
|
("gtk-4.0", "template.ui"),
|
||||||
|
("gtk-4.0", "inline_object.ui"),
|
||||||
|
("gtk-4.0", "stack_page.ui"),
|
||||||
|
("gtk-4.0", "liststore.ui"),
|
||||||
|
("gtk-4.0", "treestore.ui"),
|
||||||
|
("gtk-4.0", "comboboxtext.ui"),
|
||||||
|
("gtk-4.0", "style.ui"),
|
||||||
|
("gtk-4.0", "label.ui"),
|
||||||
|
("gtk-4.0", "filefilter.ui"),
|
||||||
|
("gtk-4.0", "custom_fragment.ui"),
|
||||||
|
("gtk-4.0", "bindings.ui"),
|
||||||
|
("gtk-4.0", "menu.ui"),
|
||||||
|
("gtk-4.0", "string_list.ui"),
|
||||||
|
("gtk-4.0", "accessibility.ui"),
|
||||||
|
("gtk-4.0", "ui_comments.ui")
|
||||||
|
])
|
||||||
|
def test_(target_tk, filename):
|
||||||
"""
|
"""
|
||||||
import .ui file and compare it with the exported version
|
import .ui file and compare it with the exported version
|
||||||
"""
|
"""
|
||||||
@ -29,160 +70,3 @@ def assert_original_and_exported(target_tk, filename):
|
|||||||
|
|
||||||
assert str_exported == str_original
|
assert str_exported == str_original
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Gtk+ 3.0 Tests
|
|
||||||
#
|
|
||||||
def test_gtk3_window():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "window.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_children():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "children.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_packing():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "packing.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_signals():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "signals.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_template():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "template.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_comboboxtext():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "comboboxtext.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_dialog():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "dialog.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_label():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "label.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_levelbar():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "levelbar.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_liststore():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "liststore.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_scale():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "scale.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_sizegroup():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "sizegroup.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_style():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "style.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_treestore():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "treestore.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_filefilter():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "filefilter.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_custom_fragment():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "custom_fragment.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_bindings():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "bindings.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_menu():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "menu.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk3_accessibility():
|
|
||||||
assert_original_and_exported("gtk+-3.0", "accessibility.ui")
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Gtk 4.0 Tests
|
|
||||||
#
|
|
||||||
def test_gtk4_window():
|
|
||||||
assert_original_and_exported("gtk-4.0", "window.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_children():
|
|
||||||
assert_original_and_exported("gtk-4.0", "children.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_layout():
|
|
||||||
assert_original_and_exported("gtk-4.0", "layout.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_signals():
|
|
||||||
assert_original_and_exported("gtk-4.0", "signals.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_template():
|
|
||||||
assert_original_and_exported("gtk-4.0", "template.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_inline_object():
|
|
||||||
assert_original_and_exported("gtk-4.0", "inline_object.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_stack_page():
|
|
||||||
assert_original_and_exported("gtk-4.0", "stack_page.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_liststore():
|
|
||||||
assert_original_and_exported("gtk-4.0", "liststore.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_treestore():
|
|
||||||
assert_original_and_exported("gtk-4.0", "treestore.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_comboboxtext():
|
|
||||||
assert_original_and_exported("gtk-4.0", "comboboxtext.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_style():
|
|
||||||
assert_original_and_exported("gtk-4.0", "style.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_label():
|
|
||||||
assert_original_and_exported("gtk-4.0", "label.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_filefilter():
|
|
||||||
assert_original_and_exported("gtk-4.0", "filefilter.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_custom_fragment():
|
|
||||||
assert_original_and_exported("gtk-4.0", "custom_fragment.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_bindings():
|
|
||||||
assert_original_and_exported("gtk-4.0", "bindings.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_menu():
|
|
||||||
assert_original_and_exported("gtk-4.0", "menu.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_string_list():
|
|
||||||
assert_original_and_exported("gtk-4.0", "string_list.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_accessibility():
|
|
||||||
assert_original_and_exported("gtk-4.0", "accessibility.ui")
|
|
||||||
|
|
||||||
|
|
||||||
def test_gtk4_ui_comments():
|
|
||||||
assert_original_and_exported("gtk-4.0", "ui_comments.ui")
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Cambalache UI Maker developer mode
|
# Cambalache UI Maker developer mode
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
# Copyright (C) 2021-2025 Juan Pablo Ugarte
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as
|
# it under the terms of the GNU General Public License as
|
||||||
@ -22,6 +22,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import gi
|
||||||
import sys
|
import sys
|
||||||
import stat
|
import stat
|
||||||
import signal
|
import signal
|
||||||
@ -35,9 +36,19 @@ localpkgdatadir = os.path.join(localdir, "share", "cambalache")
|
|||||||
catalogsdir = os.path.join(localpkgdatadir, "catalogs")
|
catalogsdir = os.path.join(localpkgdatadir, "catalogs")
|
||||||
localbindir = os.path.join(localdir, "bin")
|
localbindir = os.path.join(localdir, "bin")
|
||||||
|
|
||||||
|
repository = gi.Repository.get_default()
|
||||||
|
|
||||||
|
LD_LIBRARY_PATH = [locallibdir, f"{locallibdir}/cambalache", f"{locallibdir}/cmb_catalog_gen"]
|
||||||
|
for path in LD_LIBRARY_PATH:
|
||||||
|
repository.prepend_library_path(path)
|
||||||
|
|
||||||
|
GI_TYPELIB_PATH = [f"{locallibdir}/girepository-1.0", f"{locallibdir}/cambalache", f"{locallibdir}/cmb_catalog_gen"]
|
||||||
|
for path in GI_TYPELIB_PATH:
|
||||||
|
repository.prepend_search_path(path)
|
||||||
|
|
||||||
for var, value in [
|
for var, value in [
|
||||||
("LD_LIBRARY_PATH", f"{locallibdir}:{locallibdir}/cambalache:{locallibdir}/cmb_catalog_gen"),
|
("LD_LIBRARY_PATH", ":".join(LD_LIBRARY_PATH)),
|
||||||
("GI_TYPELIB_PATH", f"{locallibdir}/girepository-1.0:{locallibdir}/cambalache:{locallibdir}/cmb_catalog_gen"),
|
("GI_TYPELIB_PATH", ":".join(GI_TYPELIB_PATH)),
|
||||||
("PKG_CONFIG_PATH", os.path.join(locallibdir, "pkgconfig")),
|
("PKG_CONFIG_PATH", os.path.join(locallibdir, "pkgconfig")),
|
||||||
("GSETTINGS_SCHEMA_DIR", os.path.join(localdir, "share", "glib-2.0", "schemas")),
|
("GSETTINGS_SCHEMA_DIR", os.path.join(localdir, "share", "glib-2.0", "schemas")),
|
||||||
("XDG_DATA_DIRS", os.path.join(localdir, "share")),
|
("XDG_DATA_DIRS", os.path.join(localdir, "share")),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user