mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-06-25 00:02:51 -04:00
Compare commits
218 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 | ||
|
7baf191c0b | ||
|
8d170bab39 | ||
|
b592367640 | ||
|
d41c08b68a | ||
|
7de36972dd | ||
|
6b1ae2ad75 | ||
|
296d7767f9 | ||
|
e6e55da821 | ||
|
530c211262 | ||
|
175d1d1b61 | ||
|
1401c0c480 | ||
|
acadc33f02 | ||
|
9d8794cfb0 | ||
|
011c6dc082 | ||
|
258f04825b | ||
|
61893998dd | ||
|
7baa2cfbbe | ||
|
60f3e49672 | ||
|
7213b0351f | ||
|
0709c8487b | ||
|
b2d95c576d | ||
|
211d13b14a | ||
|
5e16c964ee | ||
|
bda30ef76e | ||
|
8858257f6d | ||
|
14d0b3dd39 | ||
|
05bdb5d98e | ||
|
af6258fe8a | ||
|
5de6214da2 | ||
|
4c8cbda88d | ||
|
04ec6758f0 | ||
|
ecf21c544d | ||
|
a9c05ef202 | ||
|
df1dd90146 | ||
|
477c45a32c | ||
|
99d58df208 | ||
|
5f69f152a1 | ||
|
45f6cf920b | ||
|
425aed1872 | ||
|
0d93f151bf | ||
|
a2945f2de2 | ||
|
34bdc5e530 | ||
|
dcc4b0a199 | ||
|
3f23b4a9e7 | ||
|
4db249bf28 | ||
|
8b02d13e01 | ||
|
31b93cef82 | ||
|
c7885f5ab6 | ||
|
9a0db6c2f3 | ||
|
cb1aed17b8 | ||
|
09d118dbc0 | ||
|
829dc50bae | ||
|
94cce882aa | ||
|
f0c1543982 | ||
|
fb930b36da | ||
|
48c004a1e6 | ||
|
f3662b8095 | ||
|
986e3705b4 | ||
|
e30b93506b | ||
|
907c4938a0 | ||
|
874a506f47 | ||
|
2623583f3a | ||
|
1f709586fb | ||
|
6e1935d9fb | ||
|
bc74332ecf | ||
|
7aaabca392 | ||
|
53854f6615 | ||
|
edee67cc72 | ||
|
937211565c | ||
|
c6c278444f | ||
|
56b8af7f97 | ||
|
526b880187 | ||
|
e8cf3eb578 | ||
|
0e4d55eaa2 | ||
|
627c7a4fa9 | ||
|
7f6cd3c005 | ||
|
ffdeb0c6c0 | ||
|
d3e52c3b3b | ||
|
bfe13f87d4 | ||
|
e4a6ae3e36 | ||
|
e242742a29 | ||
|
458e93abce | ||
|
1c0d1a686a | ||
|
0ce1286acc | ||
|
f6ffc64988 | ||
|
5ca4aa2377 | ||
|
2468a22178 | ||
|
3b6b8023af | ||
|
afd241b7ea | ||
|
23c6576514 | ||
|
4c219cd58a | ||
|
39b47123dd | ||
|
f941661e49 | ||
|
572bf8f1ac | ||
|
097ccce2f7 | ||
|
530ed75c79 | ||
|
be19adfba0 | ||
|
cf7a91ceb4 | ||
|
c88712db76 | ||
|
9ea01bdec7 | ||
|
6b323e4475 | ||
|
ee660f396e | ||
|
92cc81894a | ||
|
db70b1c2b8 | ||
|
9a91a2a085 | ||
|
f4c662263d | ||
|
f5886a1727 | ||
|
38bbf4e15f | ||
|
654e8739e9 | ||
|
bbb5c1b6ed | ||
|
15f08c470c | ||
|
8a253fcf67 | ||
|
1477b33c7b | ||
|
69875d1fdb | ||
|
6b7ebb71d1 | ||
|
8621dbf1e3 | ||
|
d210ec2193 | ||
|
c197d14f6f | ||
|
42a21f8b49 | ||
|
d474122539 | ||
|
168d6a09cf | ||
|
731c47903c | ||
|
fb37f3d831 | ||
|
9b21bc15ab | ||
|
9fa28bebd4 | ||
|
d494ffeeec | ||
|
27e7f75606 | ||
|
ed77ab5bc9 | ||
|
2e981adcaf | ||
|
4b7aadb056 | ||
|
5461d4030b | ||
|
4842a23663 | ||
|
162b4a0730 | ||
|
9afe9a289d | ||
|
142df82ff4 | ||
|
f9b90d9fdc | ||
|
483b7e201e | ||
|
38a50780e0 | ||
|
1af92433e1 | ||
|
1a60f42a77 | ||
|
e6acd909c4 | ||
|
af0bdfe8f9 | ||
|
6771499cad | ||
|
e5edade840 | ||
|
70dd8f311d | ||
|
4b366b67fe | ||
|
7f100a1bfd | ||
|
7883ac4c51 | ||
|
9605d7aecf | ||
|
6561a6d9f1 | ||
|
fb8dd9422a | ||
|
f3d3858d9d | ||
|
ef4cacca8c | ||
|
ddde4c5b4a | ||
|
6a5923d98b | ||
|
78e9c945d5 | ||
|
14f45babb8 | ||
|
473d8d1d58 | ||
|
5d4ad6acff | ||
|
bdda000998 | ||
|
5e52ee3b6b | ||
|
be2f8e6332 | ||
|
5179476882 | ||
|
546992a157 | ||
|
42dbfe4bbc | ||
|
17000fcf09 | ||
|
9b64282ff9 | ||
|
5853f94bac | ||
|
0bbb0004dc | ||
|
b09e64efd2 | ||
|
6c06483261 | ||
|
0a51869abc | ||
|
34608d0347 | ||
|
6cfba4725b | ||
|
1176ba4c4f | ||
|
49b6aac7f3 | ||
|
d21afce466 | ||
|
8aacff8862 | ||
|
7d810921c4 | ||
|
39571d9a8e | ||
|
0f38117095 | ||
|
b7d19e6e89 | ||
|
588fdc99c0 | ||
|
8ce6694883 | ||
|
062782eeb6 | ||
|
b2ab6cbe51 | ||
|
5268517b72 | ||
|
28e53a270e | ||
|
942d1a2c52 | ||
|
91a4e2ddee | ||
|
c76b5df13e |
12
.local.env
12
.local.env
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/bash
|
|
||||||
|
|
||||||
SCRIPT=$(readlink -f $0)
|
|
||||||
DIRNAME=$(dirname $SCRIPT)
|
|
||||||
ARCH_TRIPLET=$(cc -dumpmachine)
|
|
||||||
export LD_LIBRARY_PATH=$DIRNAME/.local/lib/$ARCH_TRIPLET:$LD_LIBRARY_PATH
|
|
||||||
export GI_TYPELIB_PATH=$DIRNAME/.local/lib/$ARCH_TRIPLET/girepository-1.0:$GI_TYPELIB_PATH
|
|
||||||
export PKG_CONFIG_PATH=$DIRNAME/.local/lib/$ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH
|
|
||||||
export GSETTINGS_SCHEMA_DIR=$DIRNAME/.local/share/glib-2.0/schemas:$GSETTINGS_SCHEMA_DIR
|
|
||||||
export XDG_DATA_DIRS=$DIRNAME/.local/share:$XDG_DATA_DIRS
|
|
||||||
export PYTHONPATH=$DIRNAME/.local/lib/python3/dist-packages:$PYTHONPATH
|
|
||||||
export PATH=$DIRNAME/.local/bin:$PATH
|
|
64
CHANGELOG.md
64
CHANGELOG.md
@ -9,18 +9,68 @@ Cambalache used even/odd minor numbers to differentiate between stable and
|
|||||||
development releases.
|
development releases.
|
||||||
|
|
||||||
|
|
||||||
## 0.92.1
|
## 0.96.0
|
||||||
|
|
||||||
2024-10-11 - First Bugfix release in the series
|
- Add GResource support
|
||||||
|
- Add internal children support
|
||||||
|
- New project format
|
||||||
|
- Save directly to .ui files
|
||||||
|
- Show directory structure in navigation
|
||||||
|
- Unified import dialog for all file types
|
||||||
|
- Add Finnish translation. Erwinjitsu
|
||||||
|
- Use AdwAboutDialog lo-vely
|
||||||
|
- Add action child type to GtkDialog
|
||||||
|
|
||||||
- Fix window state saving
|
### Packaging changes
|
||||||
- Show layout properties from parent classes
|
|
||||||
- Improve template export for merengue
|
- pygobject-3.0 dependency bumped to 3.52 which depends on the new gi repository from GLib
|
||||||
- Fix object model regression on cut and paste
|
- libcambalacheprivate-[3|4] and its typelib are now installed under libdir/cambalache
|
||||||
- Fix merengue process restart
|
- libcmbcatalogutils-[3|4] and its typelib are now installed under libdir/cmb_catalog_gen
|
||||||
|
- Gtk 3, Handy, webkit2gtk and webkitgtk are now optional dependencies
|
||||||
|
|
||||||
### Issues
|
### Issues
|
||||||
|
|
||||||
|
- #253 "Error updating UI 1: gtk-builder-error-quark: .:8:1 Invalid object type 'AdwApplicationWindow' (6)"
|
||||||
|
- #145 "Consider Cambalache to manage resource description file for building the resource bundle"
|
||||||
|
- #54 "Add support for internal children"
|
||||||
|
- #255 "Unable to open files via the UI in a KDE Plasma session"
|
||||||
|
- #260 "Wrong default for Swap setting in signals"
|
||||||
|
- #259 "Install private shared libraries in sub directories of the main library path"
|
||||||
|
- #263 "Translatable setting resets when label field is empty"
|
||||||
|
- #264 "Error undoing removal of parent GtkGrid"
|
||||||
|
- #266 "Error "Unknown internal child: entry (6)" with particular GTK 3 UI file"
|
||||||
|
- #265 "GtkButtonBox shows too many buttons"
|
||||||
|
- #267 "Make drag'n'drop of top-level more intuitive"
|
||||||
|
- #269 "Failed to display some element of a validated ui file"
|
||||||
|
- #272 "Background of compositor does not change colors, when adwaita colors are changed"
|
||||||
|
- #273 "GtkComboBoxText items gets their translatable property removed"
|
||||||
|
|
||||||
|
## 0.94.0
|
||||||
|
|
||||||
|
2024-11-25 - Accessibility release
|
||||||
|
|
||||||
|
- Gtk 4 and Gtk 3 accessibility support
|
||||||
|
- Support property subclass override defaults
|
||||||
|
- AdwDialog placeholder support
|
||||||
|
- Improved object description in hierarchy
|
||||||
|
- Lots of bug fixes and minor UI improvements
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
- #252 "Workspace process error / "Error updating UI 1: gtk-builder-error-quark: .:185:38 Object with ID reset not found (13)" with specific UI file"
|
||||||
|
- #251 "GTK 3 message dialog from specific .ui file rendered incorrectly"
|
||||||
|
- #250 "Error trying to import specific (LibreOffice) GTK 3 .ui file: "'NoneType has no attribute 'type_id'""
|
||||||
|
- #240 "Do not show cryptic paths for imported ui files (flatpak)"
|
||||||
|
- #202 "cambalache crashes when using"
|
||||||
|
- #203 "AdwActionRow : wrong default for activatable property"
|
||||||
|
- #241 "Handle adding widgets in empty workspace"
|
||||||
|
- #234 "Hold <alt> to create object in place is not clear"
|
||||||
|
- #242 "Support quit via Ctrl + Q"
|
||||||
|
- #239 "Preview feature is not clear"
|
||||||
|
- #235 "Remember last saved / open location"
|
||||||
|
- #236 "`Import` menu operation is not clear"
|
||||||
|
- #233 "Widget tree is confusing"
|
||||||
|
- #137 "Add accessibility support"
|
||||||
- #232 "Crashes when restarting workspace"
|
- #232 "Crashes when restarting workspace"
|
||||||
|
|
||||||
## 0.92.0
|
## 0.92.0
|
||||||
|
4
Makefile
4
Makefile
@ -1,7 +1,7 @@
|
|||||||
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//47
|
flatpak install --noninteractive --user flathub org.gnome.Sdk//48
|
||||||
flatpak install --noninteractive --user flathub org.gnome.Platform//47
|
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
|
cambalache.flatpak: repo
|
||||||
|
@ -152,7 +152,7 @@ cambalache under .local directoy and set up all environment variables needed to
|
|||||||
run the app from the source directory. (Follow manual installation to ensure
|
run the app from the source directory. (Follow manual installation to ensure
|
||||||
you have everything needed)
|
you have everything needed)
|
||||||
|
|
||||||
`./run-dev.sh`
|
`./run-dev.py`
|
||||||
|
|
||||||
This is meant for Cambalache development only.
|
This is meant for Cambalache development only.
|
||||||
|
|
||||||
|
@ -1,104 +1,110 @@
|
|||||||
{
|
{
|
||||||
"app-id": "ar.xjuan.Cambalache",
|
"app-id" : "ar.xjuan.Cambalache",
|
||||||
"runtime": "org.gnome.Platform",
|
"runtime" : "org.gnome.Platform",
|
||||||
"runtime-version": "47",
|
"runtime-version" : "48",
|
||||||
"sdk": "org.gnome.Sdk",
|
"sdk" : "org.gnome.Sdk",
|
||||||
"separate-locales": false,
|
"separate-locales" : false,
|
||||||
"command": "cambalache",
|
"command" : "cambalache",
|
||||||
"finish-args": [
|
"finish-args" : [
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--share=network",
|
"--share=network",
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--filesystem=home",
|
"--filesystem=home",
|
||||||
"--device=dri"
|
"--device=dri"
|
||||||
],
|
],
|
||||||
"cleanup": [
|
"cleanup" : [
|
||||||
"/include",
|
"/include",
|
||||||
"/lib/pkgconfig",
|
"/lib/pkgconfig",
|
||||||
"/man",
|
"/man",
|
||||||
"/share/doc",
|
"/share/doc",
|
||||||
"/share/gtk-doc",
|
"/share/gtk-doc",
|
||||||
"/share/man",
|
"/share/man",
|
||||||
"/share/pkgconfig",
|
"/share/pkgconfig",
|
||||||
"*.la",
|
"*.la",
|
||||||
"*.a"
|
"*.a"
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules" : [
|
||||||
{
|
|
||||||
"name": "python3-lxml",
|
|
||||||
"buildsystem": "simple",
|
|
||||||
"build-commands": [
|
|
||||||
"pip3 install --exists-action=i --ignore-installed --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"lxml\" --no-build-isolation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
{
|
||||||
"type": "file",
|
"name" : "python3-lxml",
|
||||||
"url": "https://files.pythonhosted.org/packages/63/f7/ffbb6d2eb67b80a45b8a0834baa5557a14a5ffce0979439e7cd7f0c4055b/lxml-5.2.2.tar.gz",
|
"buildsystem" : "simple",
|
||||||
"sha256": "bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"
|
"build-commands" : [
|
||||||
}
|
"pip3 install --exists-action=i --ignore-installed --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"lxml\" --no-build-isolation"
|
||||||
]
|
],
|
||||||
},
|
"sources" : [
|
||||||
{
|
{
|
||||||
"name": "libseat",
|
"type" : "file",
|
||||||
"buildsystem": "meson",
|
"url" : "https://files.pythonhosted.org/packages/80/61/d3dc048cd6c7be6fe45b80cedcbdd4326ba4d550375f266d9f4246d0f4bc/lxml-5.3.2.tar.gz",
|
||||||
"config-opts": [
|
"sha256" : "773947d0ed809ddad824b7b14467e1a481b8976e87278ac4a730c2f7c7fcddc1"
|
||||||
"-Dserver=disabled",
|
}
|
||||||
"-Dman-pages=disabled"
|
]
|
||||||
],
|
},
|
||||||
"sources": [
|
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"name" : "libseat",
|
||||||
"url": "https://git.sr.ht/~kennylevinsen/seatd/archive/0.8.0.tar.gz",
|
"buildsystem" : "meson",
|
||||||
"sha256": "a562a44ee33ccb20954a1c1ec9a90ecb2db7a07ad6b18d0ac904328efbcf65a0",
|
"config-opts" : [
|
||||||
"x-checker-data": {
|
"-Dserver=disabled",
|
||||||
"type": "anitya",
|
"-Dman-pages=disabled"
|
||||||
"project-id": 234932,
|
],
|
||||||
"stable-only": true,
|
"sources" : [
|
||||||
"url-template": "https://git.sr.ht/~kennylevinsen/seatd/archive/$version.tar.gz"
|
{
|
||||||
}
|
"type" : "archive",
|
||||||
}
|
"url" : "https://git.sr.ht/~kennylevinsen/seatd/archive/0.8.0.tar.gz",
|
||||||
]
|
"sha256" : "a562a44ee33ccb20954a1c1ec9a90ecb2db7a07ad6b18d0ac904328efbcf65a0",
|
||||||
},
|
"x-checker-data" : {
|
||||||
{
|
"type" : "anitya",
|
||||||
"name": "wlroots",
|
"project-id" : 234932,
|
||||||
"builddir": true,
|
"stable-only" : true,
|
||||||
"buildsystem": "meson",
|
"url-template" : "https://git.sr.ht/~kennylevinsen/seatd/archive/$version.tar.gz"
|
||||||
"config-opts": [],
|
}
|
||||||
"sources": [
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "git",
|
"name" : "wlroots",
|
||||||
"url": "https://gitlab.freedesktop.org/wlroots/wlroots.git",
|
"builddir" : true,
|
||||||
"tag": "0.18.1",
|
"buildsystem" : "meson",
|
||||||
"commit": "5bc39071d173301eb8b2cd652c711075526dfbd9"
|
"config-opts" : [],
|
||||||
}
|
"sources" : [
|
||||||
]
|
{
|
||||||
},
|
"type" : "git",
|
||||||
{
|
"url" : "https://gitlab.freedesktop.org/wlroots/wlroots.git",
|
||||||
"name": "casilda",
|
"tag" : "0.18.1",
|
||||||
"builddir": true,
|
"commit" : "5bc39071d173301eb8b2cd652c711075526dfbd9"
|
||||||
"buildsystem": "meson",
|
}
|
||||||
"config-opts": [],
|
]
|
||||||
"sources": [
|
},
|
||||||
{
|
{
|
||||||
"type": "git",
|
"name" : "casilda",
|
||||||
"url": "https://gitlab.gnome.org/jpu/casilda.git",
|
"builddir" : true,
|
||||||
"tag": "0.2.0",
|
"buildsystem" : "meson",
|
||||||
"commit": "99a0173f21345b85713198c1fa1fbb388d00182f"
|
"config-opts" : [],
|
||||||
}
|
"sources" : [
|
||||||
]
|
{
|
||||||
},
|
"type" : "git",
|
||||||
{
|
"url" : "https://gitlab.gnome.org/jpu/casilda.git",
|
||||||
"name": "cambalache",
|
"tag" : "0.9.0",
|
||||||
"builddir": true,
|
"commit" : "4f7b1be321cf76832b12bda11fd91897257377e2"
|
||||||
"buildsystem": "meson",
|
}
|
||||||
"sources": [
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "git",
|
"name" : "cambalache",
|
||||||
"path": ".",
|
"builddir" : true,
|
||||||
"branch": "HEAD"
|
"buildsystem" : "meson",
|
||||||
|
"sources" : [
|
||||||
|
{
|
||||||
|
"type" : "git",
|
||||||
|
"path" : ".",
|
||||||
|
"branch" : "HEAD"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"config-opts" : [
|
||||||
|
"--libdir=lib"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"build-options" : {
|
||||||
|
"env" : { }
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,12 @@ import builtins
|
|||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
|
gi.require_version("GIRepository", "3.0")
|
||||||
gi.require_version("Gdk", "4.0")
|
gi.require_version("Gdk", "4.0")
|
||||||
gi.require_version("Gtk", "4.0")
|
gi.require_version("Gtk", "4.0")
|
||||||
gi.require_version("GtkSource", "5")
|
gi.require_version("GtkSource", "5")
|
||||||
gi.require_version("WebKit", "6.0")
|
gi.require_version("WebKit", "6.0")
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
|
||||||
# Ensure _() builtin
|
# Ensure _() builtin
|
||||||
if "_" not in builtins.__dict__:
|
if "_" not in builtins.__dict__:
|
||||||
@ -55,7 +57,7 @@ resource._register()
|
|||||||
provider = Gtk.CssProvider()
|
provider = Gtk.CssProvider()
|
||||||
provider.load_from_resource("/ar/xjuan/Cambalache/cambalache.css")
|
provider.load_from_resource("/ar/xjuan/Cambalache/cambalache.css")
|
||||||
display = Gdk.Display.get_default()
|
display = Gdk.Display.get_default()
|
||||||
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - 1)
|
||||||
|
|
||||||
# FIXME: this is needed in flatpak for icons to work
|
# FIXME: this is needed in flatpak for icons to work
|
||||||
Gtk.IconTheme.get_for_display(display).add_search_path("/app/share/icons")
|
Gtk.IconTheme.get_for_display(display).add_search_path("/app/share/icons")
|
||||||
@ -78,6 +80,7 @@ from .cmb_objects_base import CmbBaseObject
|
|||||||
from .cmb_css import CmbCSS
|
from .cmb_css import CmbCSS
|
||||||
from .cmb_ui import CmbUI
|
from .cmb_ui import CmbUI
|
||||||
from .cmb_object import CmbObject
|
from .cmb_object import CmbObject
|
||||||
|
from .cmb_gresource import CmbGResource
|
||||||
|
|
||||||
# from .cmb_object_data import CmbObjectData
|
# from .cmb_object_data import CmbObjectData
|
||||||
from .cmb_property import CmbProperty
|
from .cmb_property import CmbProperty
|
||||||
@ -88,13 +91,17 @@ from .cmb_project import CmbProject
|
|||||||
|
|
||||||
from .cmb_db_inspector import CmbDBInspector
|
from .cmb_db_inspector import CmbDBInspector
|
||||||
from .cmb_view import CmbView
|
from .cmb_view import CmbView
|
||||||
from .cmb_column_view import CmbColumnView
|
from .cmb_list_view import CmbListView
|
||||||
|
from .cmb_notification import notification_center, CmbNotification, CmbNotificationCenter
|
||||||
|
from .cmb_notification_list_view import CmbNotificationListView
|
||||||
from .cmb_object_editor import CmbObjectEditor
|
from .cmb_object_editor import CmbObjectEditor
|
||||||
from .cmb_signal_editor import CmbSignalEditor
|
from .cmb_signal_editor import CmbSignalEditor
|
||||||
from .cmb_ui_editor import CmbUIEditor
|
from .cmb_ui_editor import CmbUIEditor
|
||||||
from .cmb_ui_requires_editor import CmbUIRequiresEditor
|
from .cmb_ui_requires_editor import CmbUIRequiresEditor
|
||||||
from .cmb_css_editor import CmbCSSEditor
|
from .cmb_css_editor import CmbCSSEditor
|
||||||
|
from .cmb_gresource_editor import CmbGResourceEditor
|
||||||
from .cmb_fragment_editor import CmbFragmentEditor
|
from .cmb_fragment_editor import CmbFragmentEditor
|
||||||
|
from .cmb_accessible_editor import CmbAccessibleEditor
|
||||||
from .cmb_type_chooser import CmbTypeChooser
|
from .cmb_type_chooser import CmbTypeChooser
|
||||||
from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
||||||
from .cmb_type_chooser_popover import CmbTypeChooserPopover
|
from .cmb_type_chooser_popover import CmbTypeChooserPopover
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/ar/xjuan/Cambalache/app">
|
<gresource prefix="/ar/xjuan/Cambalache/app">
|
||||||
<file>metainfo.xml</file>
|
<file>metainfo.xml</file>
|
||||||
|
@ -38,7 +38,25 @@ window.cmb-window label.message {
|
|||||||
background-color: rgba(0, 0, 0, .6);
|
background-color: rgba(0, 0, 0, .6);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.cmb-window.dark label.message {
|
window.cmb-window list.notifications {
|
||||||
|
margin: 1em 2em;
|
||||||
|
border-radius: 1em;
|
||||||
|
border: 1px solid var(--secondary-sidebar-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cmb-window list.notifications > row {
|
||||||
|
padding: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cmb-window list.notifications > row:last-child {
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cmb-window list.notifications row > revealer > box > box:last-child {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cmb-window.dark .message {
|
||||||
color: black;
|
color: black;
|
||||||
background-color: rgba(255, 255, 255, .6);
|
background-color: rgba(255, 255, 255, .6);
|
||||||
}
|
}
|
||||||
@ -89,11 +107,14 @@ CmbTypeChooser {
|
|||||||
|
|
||||||
CmbUIEditor,
|
CmbUIEditor,
|
||||||
CmbFragmentEditor,
|
CmbFragmentEditor,
|
||||||
CmbObjectEditor {
|
CmbObjectEditor,
|
||||||
|
CmbAccessibleEditor {
|
||||||
padding: 0 4px 4px 4px;
|
padding: 0 4px 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
CmbCSSEditor,
|
CmbCSSEditor,
|
||||||
|
CmbGResourceEditor,
|
||||||
|
CmbGResourceFileEditor,
|
||||||
stackswitcher.property-pane {
|
stackswitcher.property-pane {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
@ -42,13 +42,14 @@ class CmbApplication(Adw.Application):
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
self.add_main_option("export-all", b"E", GLib.OptionFlags.NONE, GLib.OptionArg.FILENAME, _("Export project"), None)
|
self.add_main_option(
|
||||||
|
"export-all", b"E", GLib.OptionFlags.NONE, GLib.OptionArg.FILENAME, _("Deprecated: Export project"), None
|
||||||
|
)
|
||||||
|
|
||||||
def __add_window(self):
|
def add_new_window(self):
|
||||||
window = CmbWindow(application=self)
|
window = CmbWindow(application=self)
|
||||||
window.connect("open-project", self.__on_open_project)
|
|
||||||
|
|
||||||
window.connect("close-request", self.__on_window_close_request)
|
window.connect("close-request", self.__on_window_close_request)
|
||||||
|
window.connect("project-closed", self.__on_window_project_closed)
|
||||||
self.add_window(window)
|
self.add_window(window)
|
||||||
return window
|
return window
|
||||||
|
|
||||||
@ -60,50 +61,18 @@ class CmbApplication(Adw.Application):
|
|||||||
window = win
|
window = win
|
||||||
|
|
||||||
if window is None:
|
if window is None:
|
||||||
window = self.__add_window()
|
window = self.add_new_window()
|
||||||
if path is not None:
|
if path is not None:
|
||||||
window.open_project(path, target_tk=target_tk)
|
window.open_project(path, target_tk=target_tk)
|
||||||
|
|
||||||
window.present()
|
window.present()
|
||||||
|
|
||||||
def import_file(self, path):
|
def import_file(self, path):
|
||||||
window = self.__add_window() if self.props.active_window is None else self.props.active_window
|
window = self.add_new_window() if self.props.active_window is None else self.props.active_window
|
||||||
window.import_file(path)
|
window.import_file(path)
|
||||||
window.present()
|
window.present()
|
||||||
|
|
||||||
def do_open(self, files, nfiles, hint):
|
def check_can_quit(self, window=None):
|
||||||
for file in files:
|
|
||||||
path = file.get_path()
|
|
||||||
content_type = utils.content_type_guess(path)
|
|
||||||
|
|
||||||
if content_type == "application/x-cambalache-project":
|
|
||||||
self.open_project(path)
|
|
||||||
elif content_type in ["application/x-gtk-builder", "application/x-glade"]:
|
|
||||||
self.import_file(path)
|
|
||||||
|
|
||||||
def do_startup(self):
|
|
||||||
Adw.Application.do_startup(self)
|
|
||||||
|
|
||||||
for action in ["quit"]:
|
|
||||||
gaction = Gio.SimpleAction.new(action, None)
|
|
||||||
gaction.connect("activate", getattr(self, f"_on_{action}_activate"))
|
|
||||||
self.add_action(gaction)
|
|
||||||
|
|
||||||
provider = Gtk.CssProvider()
|
|
||||||
provider.load_from_resource("/ar/xjuan/Cambalache/app/cambalache.css")
|
|
||||||
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
|
||||||
|
|
||||||
def do_activate(self):
|
|
||||||
if self.props.active_window is None:
|
|
||||||
self.open_project(None)
|
|
||||||
|
|
||||||
def __on_open_project(self, window, filename, target_tk):
|
|
||||||
if window.project is None:
|
|
||||||
window.open_project(filename, target_tk)
|
|
||||||
else:
|
|
||||||
self.open_project(filename, target_tk)
|
|
||||||
|
|
||||||
def __check_can_quit(self, window=None):
|
|
||||||
windows = self.__get_windows() if window is None else [window]
|
windows = self.__get_windows() if window is None else [window]
|
||||||
unsaved_windows = []
|
unsaved_windows = []
|
||||||
windows2save = []
|
windows2save = []
|
||||||
@ -195,28 +164,96 @@ class CmbApplication(Adw.Application):
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
def __on_window_close_request(self, window):
|
def __on_window_close_request(self, window):
|
||||||
self.__check_can_quit(window)
|
self.check_can_quit(window)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def __on_window_project_closed(self, window):
|
||||||
|
windows = self.__get_windows()
|
||||||
|
|
||||||
|
if len(windows) > 1:
|
||||||
|
self.remove_window(window)
|
||||||
|
window.destroy()
|
||||||
|
|
||||||
|
# Action handlers
|
||||||
|
def _on_quit_activate(self, action, data):
|
||||||
|
self.check_can_quit()
|
||||||
|
|
||||||
|
def _on_open_activate(self, action, data):
|
||||||
|
filename, target_tk = data.unpack()
|
||||||
|
|
||||||
|
# FIXME: use nullable parameter
|
||||||
|
target_tk = target_tk if target_tk else None
|
||||||
|
filename = filename if filename else None
|
||||||
|
|
||||||
|
window = self.props.active_window
|
||||||
|
|
||||||
|
if window and window.project is None:
|
||||||
|
window.open_project(filename, target_tk)
|
||||||
|
else:
|
||||||
|
self.open_project(filename, target_tk)
|
||||||
|
|
||||||
|
def _on_new_activate(self, action, data):
|
||||||
|
target_tk, filename, uipath = data.unpack()
|
||||||
|
|
||||||
|
# FIXME: use nullable parameter
|
||||||
|
target_tk = target_tk if target_tk else None
|
||||||
|
filename = filename if filename else None
|
||||||
|
uipath = uipath if uipath else None
|
||||||
|
|
||||||
|
window = self.props.active_window
|
||||||
|
|
||||||
|
if window is None or window.project is not None:
|
||||||
|
window = self.add_new_window()
|
||||||
|
|
||||||
|
window.create_project(target_tk, filename, uipath)
|
||||||
|
window.present()
|
||||||
|
|
||||||
|
# GApplication interface
|
||||||
|
def do_open(self, files, nfiles, hint):
|
||||||
|
for file in files:
|
||||||
|
path = file.get_path()
|
||||||
|
content_type = utils.content_type_guess(path)
|
||||||
|
|
||||||
|
if content_type == "application/x-cambalache-project":
|
||||||
|
self.open_project(path)
|
||||||
|
elif content_type in ["application/x-gtk-builder", "application/x-glade"]:
|
||||||
|
self.import_file(path)
|
||||||
|
|
||||||
|
def do_startup(self):
|
||||||
|
Adw.Application.do_startup(self)
|
||||||
|
|
||||||
|
for action, accelerators, parameter_type in [
|
||||||
|
("quit", ["<Primary>q"], None),
|
||||||
|
("open", None, "(ss)"),
|
||||||
|
("new", None, "(sss)"),
|
||||||
|
]:
|
||||||
|
gaction = Gio.SimpleAction.new(action, GLib.VariantType.new(parameter_type) if parameter_type else None)
|
||||||
|
gaction.connect("activate", getattr(self, f"_on_{action}_activate"))
|
||||||
|
self.add_action(gaction)
|
||||||
|
if accelerators:
|
||||||
|
self.set_accels_for_action(f"app.{action}", accelerators)
|
||||||
|
|
||||||
|
provider = Gtk.CssProvider()
|
||||||
|
provider.load_from_resource("/ar/xjuan/Cambalache/app/cambalache.css")
|
||||||
|
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
|
||||||
|
def do_activate(self):
|
||||||
|
if self.props.active_window is None:
|
||||||
|
self.open_project(None)
|
||||||
|
|
||||||
def do_window_removed(self, window):
|
def do_window_removed(self, window):
|
||||||
windows = self.__get_windows()
|
windows = self.__get_windows()
|
||||||
|
|
||||||
if len(windows) == 0:
|
if len(windows) == 0:
|
||||||
self.activate_action("quit")
|
self.activate_action("quit")
|
||||||
|
|
||||||
def _on_quit_activate(self, action, data):
|
|
||||||
self.__check_can_quit()
|
|
||||||
|
|
||||||
def do_handle_local_options(self, options):
|
def do_handle_local_options(self, options):
|
||||||
if options.contains("version"):
|
if options.contains("version"):
|
||||||
print(config.VERSION)
|
print(config.VERSION)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if options.contains("export-all"):
|
if options.contains("export-all"):
|
||||||
filename = options.lookup_value("export-all")
|
print("Export has been deprecated and does nothing. Every UI file is updated on project save.")
|
||||||
filename = "".join([chr(c) for c in filename.unpack()])
|
|
||||||
project = CmbProject(filename=filename)
|
|
||||||
project.export()
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_shortcuts.ui -->
|
<!-- interface-name cmb_shortcuts.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -15,32 +15,56 @@
|
|||||||
<property name="view">shortcuts</property>
|
<property name="view">shortcuts</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>n</property>
|
<property name="accelerator"><primary>n</property>
|
||||||
<property name="title" translatable="yes">Create new project</property>
|
<property name="title" translatable="yes">Create new project</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>o</property>
|
<property name="accelerator"><primary>o</property>
|
||||||
<property name="title" translatable="yes">Open a project</property>
|
<property name="title" translatable="yes">Open a project</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>w</property>
|
<property name="accelerator"><primary>i</property>
|
||||||
|
<property name="title" translatable="yes">Import file</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator"><primary>s</property>
|
||||||
|
<property name="title" translatable="yes">Save project</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator"><primary><shift>s</property>
|
||||||
|
<property name="title" translatable="yes">Save project as</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator"><primary>w</property>
|
||||||
<property name="title" translatable="yes">Close the project</property>
|
<property name="title" translatable="yes">Close the project</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsGroup">
|
||||||
|
<property name="title" translatable="yes">General</property>
|
||||||
|
<property name="view">general</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>s</property>
|
<property name="accelerator"><primary>question</property>
|
||||||
<property name="title" translatable="yes">Save the project</property>
|
<property name="title" translatable="yes">Keyboard Shortcuts</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>e</property>
|
<property name="accelerator"><primary>q</property>
|
||||||
<property name="title" translatable="yes">Save and Export</property>
|
<property name="title" translatable="yes">Quit application</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -51,25 +75,31 @@
|
|||||||
<property name="view">shortcuts</property>
|
<property name="view">shortcuts</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>Insert</property>
|
<property name="accelerator">Delete</property>
|
||||||
|
<property name="title" translatable="yes">Delete object</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator"><primary>Insert</property>
|
||||||
<property name="title" translatable="yes">Add slot/column</property>
|
<property name="title" translatable="yes">Add slot/column</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control>Delete</property>
|
<property name="accelerator"><primary>Delete</property>
|
||||||
<property name="title" translatable="yes">Remove slot/column</property>
|
<property name="title" translatable="yes">Remove slot/column</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control><shift>Insert</property>
|
<property name="accelerator"><primary><shift>Insert</property>
|
||||||
<property name="title" translatable="yes">Add row</property>
|
<property name="title" translatable="yes">Add row</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><Control><shift>Delete</property>
|
<property name="accelerator"><primary><shift>Delete</property>
|
||||||
<property name="title" translatable="yes">Remove row</property>
|
<property name="title" translatable="yes">Remove row</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
@ -72,13 +72,6 @@ intro = [
|
|||||||
(_("Try adding a grid"), "intro_button", 3, "add-grid"),
|
(_("Try adding a grid"), "intro_button", 3, "add-grid"),
|
||||||
(_("and a button"), "intro_button", 3, "add-button"),
|
(_("and a button"), "intro_button", 3, "add-button"),
|
||||||
(_("Quite easy! Isn't it?"), "intro_button", 3),
|
(_("Quite easy! Isn't it?"), "intro_button", 3),
|
||||||
(
|
|
||||||
_("Once you finish, you can export all UI files to xml here"),
|
|
||||||
_("Export all"),
|
|
||||||
5,
|
|
||||||
None,
|
|
||||||
CmbTutorPosition.LEFT,
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
_("If you have any question, contact us on Matrix!"),
|
_("If you have any question, contact us on Matrix!"),
|
||||||
_("Contact"),
|
_("Contact"),
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import traceback
|
|
||||||
import locale
|
import locale
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
@ -32,22 +31,46 @@ from gi.repository import GLib, GObject, Gio, Gdk, Gtk, Pango, Adw
|
|||||||
from .cmb_tutor import CmbTutor, CmbTutorState
|
from .cmb_tutor import CmbTutor, CmbTutorState
|
||||||
from . import cmb_tutorial
|
from . import cmb_tutorial
|
||||||
|
|
||||||
from cambalache import CmbProject, CmbUI, CmbCSS, CmbObject, CmbTypeChooserPopover, getLogger, config, utils, _
|
from cambalache import (
|
||||||
|
CmbProject,
|
||||||
|
CmbUI,
|
||||||
|
CmbCSS,
|
||||||
|
CmbObject,
|
||||||
|
CmbGResource,
|
||||||
|
CmbGResourceEditor,
|
||||||
|
CmbTypeChooserPopover,
|
||||||
|
getLogger,
|
||||||
|
notification_center,
|
||||||
|
config,
|
||||||
|
utils,
|
||||||
|
_,
|
||||||
|
N_
|
||||||
|
)
|
||||||
|
|
||||||
|
from cambalache.cmb_blueprint import CmbBlueprintError
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
GObject.type_ensure(CmbGResourceEditor.__gtype__)
|
||||||
|
|
||||||
|
|
||||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/app/cmb_window.ui")
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/app/cmb_window.ui")
|
||||||
class CmbWindow(Adw.ApplicationWindow):
|
class CmbWindow(Adw.ApplicationWindow):
|
||||||
__gtype_name__ = "CmbWindow"
|
__gtype_name__ = "CmbWindow"
|
||||||
|
|
||||||
__gsignals__ = {
|
__gsignals__ = {
|
||||||
"open-project": (GObject.SignalFlags.RUN_FIRST, None, (str, str)),
|
"project-closed": (GObject.SignalFlags.RUN_FIRST, None, ()),
|
||||||
"project-saved": (GObject.SignalFlags.RUN_FIRST, None, (CmbProject,)),
|
"project-saved": (GObject.SignalFlags.RUN_FIRST, None, (CmbProject,)),
|
||||||
}
|
}
|
||||||
|
|
||||||
open_filter = Gtk.Template.Child()
|
open_filter = Gtk.Template.Child()
|
||||||
import_filter = Gtk.Template.Child()
|
gtk4_filter = Gtk.Template.Child()
|
||||||
|
gtk3_filter = Gtk.Template.Child()
|
||||||
|
gtk_builder_filter = Gtk.Template.Child()
|
||||||
|
blueprint_filter = Gtk.Template.Child()
|
||||||
|
glade_filter = Gtk.Template.Child()
|
||||||
|
css_filter = Gtk.Template.Child()
|
||||||
|
gresource_filter = Gtk.Template.Child()
|
||||||
|
|
||||||
headerbar = Gtk.Template.Child()
|
headerbar = Gtk.Template.Child()
|
||||||
title = Gtk.Template.Child()
|
title = Gtk.Template.Child()
|
||||||
@ -59,6 +82,9 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
# Start screen
|
# Start screen
|
||||||
version_label = Gtk.Template.Child()
|
version_label = Gtk.Template.Child()
|
||||||
|
front_notification_list_view = Gtk.Template.Child()
|
||||||
|
notification_dialog = Gtk.Template.Child()
|
||||||
|
notification_list_view = Gtk.Template.Child()
|
||||||
|
|
||||||
# New Project
|
# New Project
|
||||||
np_name_entry = Gtk.Template.Child()
|
np_name_entry = Gtk.Template.Child()
|
||||||
@ -73,8 +99,10 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
message_label = Gtk.Template.Child()
|
message_label = Gtk.Template.Child()
|
||||||
|
|
||||||
# Workspace
|
# Workspace
|
||||||
|
workspace_stack = Gtk.Template.Child()
|
||||||
view = Gtk.Template.Child()
|
view = Gtk.Template.Child()
|
||||||
column_view = Gtk.Template.Child()
|
source_view = Gtk.Template.Child()
|
||||||
|
list_view = Gtk.Template.Child()
|
||||||
type_chooser = Gtk.Template.Child()
|
type_chooser = Gtk.Template.Child()
|
||||||
editor_stack = Gtk.Template.Child()
|
editor_stack = Gtk.Template.Child()
|
||||||
ui_editor = Gtk.Template.Child()
|
ui_editor = Gtk.Template.Child()
|
||||||
@ -83,8 +111,10 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
fragment_editor = Gtk.Template.Child()
|
fragment_editor = Gtk.Template.Child()
|
||||||
object_editor = Gtk.Template.Child()
|
object_editor = Gtk.Template.Child()
|
||||||
object_layout_editor = Gtk.Template.Child()
|
object_layout_editor = Gtk.Template.Child()
|
||||||
|
accessible_editor = Gtk.Template.Child()
|
||||||
signal_editor = Gtk.Template.Child()
|
signal_editor = Gtk.Template.Child()
|
||||||
css_editor = Gtk.Template.Child()
|
css_editor = Gtk.Template.Child()
|
||||||
|
gresource_editor = Gtk.Template.Child()
|
||||||
|
|
||||||
# Tutor widgets
|
# Tutor widgets
|
||||||
intro_button = Gtk.Template.Child()
|
intro_button = Gtk.Template.Child()
|
||||||
@ -96,6 +126,11 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
MAXIMIZED = 1 << 2
|
MAXIMIZED = 1 << 2
|
||||||
FULLSCREEN = 1 << 4
|
FULLSCREEN = 1 << 4
|
||||||
|
|
||||||
|
__portal_access_msg = _(
|
||||||
|
"Cambalache will not have access to the file for save because it is outside of your home directory. "
|
||||||
|
"Consider using Flatseal or flatpak to give permissions to the host directory."
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__project = None
|
self.__project = None
|
||||||
self.__last_saved_index = None
|
self.__last_saved_index = None
|
||||||
@ -103,6 +138,21 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.gtk4_import_filters = Gio.ListStore()
|
||||||
|
|
||||||
|
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.gtk3_import_filters = Gio.ListStore()
|
||||||
|
for filter in [self.gtk3_filter, self.gtk_builder_filter, self.glade_filter, self.css_filter, self.gresource_filter]:
|
||||||
|
self.gtk3_import_filters.append(filter)
|
||||||
|
|
||||||
self.__recent_manager = self.__get_recent_manager()
|
self.__recent_manager = self.__get_recent_manager()
|
||||||
|
|
||||||
self.editor_stack.set_size_request(420, -1)
|
self.editor_stack.set_size_request(420, -1)
|
||||||
@ -112,6 +162,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
for action in [
|
for action in [
|
||||||
"about",
|
"about",
|
||||||
"add_css",
|
"add_css",
|
||||||
|
"add_gresource",
|
||||||
"add_object",
|
"add_object",
|
||||||
"add_object_toplevel",
|
"add_object_toplevel",
|
||||||
"add_placeholder",
|
"add_placeholder",
|
||||||
@ -126,12 +177,12 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
"debug",
|
"debug",
|
||||||
"delete",
|
"delete",
|
||||||
"donate",
|
"donate",
|
||||||
"export",
|
|
||||||
"import",
|
"import",
|
||||||
"inspect",
|
"inspect",
|
||||||
"intro",
|
"intro",
|
||||||
"liberapay",
|
"liberapay",
|
||||||
"new",
|
"new",
|
||||||
|
"notification",
|
||||||
"open",
|
"open",
|
||||||
"paste",
|
"paste",
|
||||||
"patreon",
|
"patreon",
|
||||||
@ -172,7 +223,6 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
("win.save", ["<Primary>s"]),
|
("win.save", ["<Primary>s"]),
|
||||||
("win.save_as", ["<Shift><Primary>s"]),
|
("win.save_as", ["<Shift><Primary>s"]),
|
||||||
("win.import", ["<Primary>i"]),
|
("win.import", ["<Primary>i"]),
|
||||||
("win.export", ["<Primary>e"]),
|
|
||||||
("win.close", ["<Primary>w"]),
|
("win.close", ["<Primary>w"]),
|
||||||
("win.undo", ["<Primary>z"]),
|
("win.undo", ["<Primary>z"]),
|
||||||
("win.redo", ["<Primary><shift>z"]),
|
("win.redo", ["<Primary><shift>z"]),
|
||||||
@ -209,7 +259,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
("win.debug", ["<Shift><Primary>d"]),
|
("win.debug", ["<Shift><Primary>d"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
app = Gio.Application.get_default()
|
app = self.props.application
|
||||||
for action, accelerators in action_map:
|
for action, accelerators in action_map:
|
||||||
app.set_accels_for_action(action, accelerators)
|
app.set_accels_for_action(action, accelerators)
|
||||||
|
|
||||||
@ -255,11 +305,11 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.__update_dark_mode(app.props.style_manager)
|
self.__update_dark_mode(app.props.style_manager)
|
||||||
|
|
||||||
# Bind preview
|
# Bind preview
|
||||||
preview_button = Gtk.ToggleButton(tooltip_text=_("Preview mode"), icon_name="system-run-symbolic")
|
hide_placeholders_button = Gtk.ToggleButton(tooltip_text=_("Hide placeholders"), icon_name="view-conceal-symbolic")
|
||||||
self.type_chooser.content.append(preview_button)
|
self.type_chooser.content.append(hide_placeholders_button)
|
||||||
|
|
||||||
GObject.Object.bind_property(
|
GObject.Object.bind_property(
|
||||||
preview_button,
|
hide_placeholders_button,
|
||||||
"active",
|
"active",
|
||||||
self.view,
|
self.view,
|
||||||
"preview",
|
"preview",
|
||||||
@ -273,6 +323,10 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.__recent_manager.connect("changed", lambda rm: self.__update_recent_menu())
|
self.__recent_manager.connect("changed", lambda rm: self.__update_recent_menu())
|
||||||
self.__update_recent_menu()
|
self.__update_recent_menu()
|
||||||
|
|
||||||
|
self.notification_list_view.notification_center = notification_center
|
||||||
|
self.front_notification_list_view.notification_center = notification_center
|
||||||
|
notification_center.connect("new-notification", self.__on_new_notification)
|
||||||
|
|
||||||
def __get_recent_manager(self):
|
def __get_recent_manager(self):
|
||||||
# Load the user host recently used file
|
# Load the user host recently used file
|
||||||
if os.environ.get("container", None) == "flatpak":
|
if os.environ.get("container", None) == "flatpak":
|
||||||
@ -295,12 +349,13 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
if self.__project:
|
if self.__project:
|
||||||
self.__project.disconnect_by_func(self.__on_project_filename_notify)
|
self.__project.disconnect_by_func(self.__on_project_filename_notify)
|
||||||
self.__project.disconnect_by_func(self.__on_project_selection_changed)
|
self.__project.disconnect_by_func(self.__on_project_selection_changed)
|
||||||
|
self.__project.disconnect_by_func(self.__on_project_gresource_changed)
|
||||||
self.__project.disconnect_by_func(self.__on_project_changed)
|
self.__project.disconnect_by_func(self.__on_project_changed)
|
||||||
|
|
||||||
self.__project = project
|
self.__project = project
|
||||||
self.view.project = project
|
self.view.project = project
|
||||||
self.type_chooser.project = project
|
self.type_chooser.project = project
|
||||||
self.column_view.project = project
|
self.list_view.project = project
|
||||||
|
|
||||||
# Clear Editors
|
# Clear Editors
|
||||||
self.ui_editor.object = None
|
self.ui_editor.object = None
|
||||||
@ -308,12 +363,14 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.ui_fragment_editor.object = None
|
self.ui_fragment_editor.object = None
|
||||||
self.object_editor.object = None
|
self.object_editor.object = None
|
||||||
self.object_layout_editor.object = None
|
self.object_layout_editor.object = None
|
||||||
|
self.accessible_editor.object = None
|
||||||
self.signal_editor.object = None
|
self.signal_editor.object = None
|
||||||
self.fragment_editor.object = None
|
self.fragment_editor.object = None
|
||||||
|
|
||||||
if project:
|
if project:
|
||||||
self.__project.connect("notify::filename", self.__on_project_filename_notify)
|
self.__project.connect("notify::filename", self.__on_project_filename_notify)
|
||||||
self.__project.connect("selection-changed", self.__on_project_selection_changed)
|
self.__project.connect("selection-changed", self.__on_project_selection_changed)
|
||||||
|
self.__project.connect("gresource-changed", self.__on_project_gresource_changed)
|
||||||
self.__project.connect("changed", self.__on_project_changed)
|
self.__project.connect("changed", self.__on_project_changed)
|
||||||
self.__on_project_selection_changed(project)
|
self.__on_project_selection_changed(project)
|
||||||
|
|
||||||
@ -396,16 +453,6 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
popover.popup()
|
popover.popup()
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_ui_editor_remove_ui")
|
|
||||||
def __on_ui_editor_remove_ui(self, editor):
|
|
||||||
self.__remove_object_with_confirmation(editor.object)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_css_editor_remove_ui")
|
|
||||||
def __on_css_editor_remove_ui(self, editor):
|
|
||||||
self.__remove_object_with_confirmation(editor.object)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __on_focus_widget_notify(self, obj, pspec):
|
def __on_focus_widget_notify(self, obj, pspec):
|
||||||
widget = self.props.focus_widget
|
widget = self.props.focus_widget
|
||||||
|
|
||||||
@ -425,6 +472,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
sensitive = len(editable.get_chars(0, -1)) != 0
|
sensitive = len(editable.get_chars(0, -1)) != 0
|
||||||
self.np_location_chooser.set_sensitive(sensitive)
|
self.np_location_chooser.set_sensitive(sensitive)
|
||||||
self.np_ui_entry.set_sensitive(sensitive)
|
self.np_ui_entry.set_sensitive(sensitive)
|
||||||
|
self.__update_action_new()
|
||||||
|
|
||||||
def __update_dark_mode(self, style_manager):
|
def __update_dark_mode(self, style_manager):
|
||||||
if style_manager.props.dark:
|
if style_manager.props.dark:
|
||||||
@ -495,31 +543,60 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.__update_action_undo_redo()
|
self.__update_action_undo_redo()
|
||||||
self.__update_action_save()
|
self.__update_action_save()
|
||||||
|
|
||||||
|
def __update_gresource_view(self):
|
||||||
|
if self.workspace_stack.get_visible_child_name() != "gresource":
|
||||||
|
return
|
||||||
|
|
||||||
|
sel = self.project.get_selection()
|
||||||
|
if len(sel) < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = sel[0]
|
||||||
|
|
||||||
|
if isinstance(obj, CmbGResource):
|
||||||
|
gresource_id = obj.gresources_bundle.gresource_id
|
||||||
|
resource_xml = self.project.db.gresource_tostring(gresource_id)
|
||||||
|
else:
|
||||||
|
resource_xml = ""
|
||||||
|
|
||||||
|
self.source_view.buffer.set_text(resource_xml)
|
||||||
|
|
||||||
|
def __on_project_gresource_changed(self, project, gresource, field):
|
||||||
|
self.__update_gresource_view()
|
||||||
|
|
||||||
def __on_project_selection_changed(self, project):
|
def __on_project_selection_changed(self, project):
|
||||||
sel = project.get_selection()
|
sel = project.get_selection()
|
||||||
self.__update_action_clipboard()
|
self.__update_action_clipboard()
|
||||||
|
|
||||||
obj = sel[0] if len(sel) > 0 else None
|
obj = sel[0] if len(sel) > 0 else None
|
||||||
|
|
||||||
if type(obj) is CmbUI:
|
if isinstance(obj, CmbUI):
|
||||||
self.ui_editor.object = obj
|
self.ui_editor.object = obj
|
||||||
self.ui_requires_editor.object = obj
|
self.ui_requires_editor.object = obj
|
||||||
self.ui_fragment_editor.object = obj
|
self.ui_fragment_editor.object = obj
|
||||||
|
self.workspace_stack.set_visible_child_name("ui")
|
||||||
self.editor_stack.set_visible_child_name("ui")
|
self.editor_stack.set_visible_child_name("ui")
|
||||||
obj = None
|
obj = None
|
||||||
elif type(obj) is CmbObject:
|
elif isinstance(obj, CmbObject):
|
||||||
|
self.workspace_stack.set_visible_child_name("ui")
|
||||||
self.editor_stack.set_visible_child_name("object")
|
self.editor_stack.set_visible_child_name("object")
|
||||||
if obj:
|
if obj:
|
||||||
self.__user_message_by_type(obj.info)
|
self.__user_message_by_type(obj.info)
|
||||||
elif type(obj) is CmbCSS:
|
elif isinstance(obj, CmbCSS):
|
||||||
self.css_editor.object = obj
|
self.css_editor.object = obj
|
||||||
self.editor_stack.set_visible_child_name("css")
|
self.editor_stack.set_visible_child_name("css")
|
||||||
obj = None
|
obj = None
|
||||||
|
elif isinstance(obj, CmbGResource):
|
||||||
|
self.gresource_editor.object = obj
|
||||||
|
self.workspace_stack.set_visible_child_name("gresource")
|
||||||
|
self.editor_stack.set_visible_child_name("gresource")
|
||||||
|
self.__update_gresource_view()
|
||||||
|
obj = None
|
||||||
|
|
||||||
self.object_editor.object = obj
|
self.object_editor.object = obj
|
||||||
|
|
||||||
is_not_builtin = not obj.info.is_builtin if obj else True
|
is_not_builtin = not obj.info.is_builtin if obj else True
|
||||||
for editor in [self.object_layout_editor, self.signal_editor, self.fragment_editor]:
|
for editor in [self.object_layout_editor, self.signal_editor, self.fragment_editor, self.accessible_editor]:
|
||||||
editor.object = obj
|
editor.object = obj
|
||||||
editor.props.visible = is_not_builtin
|
editor.props.visible = is_not_builtin
|
||||||
|
|
||||||
@ -535,6 +612,9 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
self.intro_button.set_visible(enabled)
|
self.intro_button.set_visible(enabled)
|
||||||
|
|
||||||
|
def __update_action_notification(self):
|
||||||
|
self.actions["notification"].set_enabled(len(notification_center.store) > 0)
|
||||||
|
|
||||||
def __update_action_add_object(self):
|
def __update_action_add_object(self):
|
||||||
has_project = self.__is_project_visible()
|
has_project = self.__is_project_visible()
|
||||||
has_selection = True if self.project and len(self.project.get_selection()) > 0 else False
|
has_selection = True if self.project and len(self.project.get_selection()) > 0 else False
|
||||||
@ -575,28 +655,43 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
else:
|
else:
|
||||||
self.title.remove_css_class("changed")
|
self.title.remove_css_class("changed")
|
||||||
|
|
||||||
|
def __update_action_new(self):
|
||||||
|
self.actions["new"].set_enabled(len(self.np_name_entry.props.text) > 0)
|
||||||
|
|
||||||
def __update_actions(self):
|
def __update_actions(self):
|
||||||
has_project = self.__is_project_visible()
|
has_project = self.__is_project_visible()
|
||||||
|
|
||||||
for action in ["save_as", "add_ui", "add_css", "delete", "import", "export", "close", "debug"]:
|
for action in [
|
||||||
|
"save_as",
|
||||||
|
"add_ui",
|
||||||
|
"add_css",
|
||||||
|
"add_gresource",
|
||||||
|
"delete",
|
||||||
|
"import",
|
||||||
|
"close",
|
||||||
|
"debug"
|
||||||
|
]:
|
||||||
self.actions[action].set_enabled(has_project)
|
self.actions[action].set_enabled(has_project)
|
||||||
|
|
||||||
|
self.__update_action_new()
|
||||||
self.__update_action_save()
|
self.__update_action_save()
|
||||||
self.__update_action_intro()
|
self.__update_action_intro()
|
||||||
|
self.__update_action_notification()
|
||||||
self.__update_action_clipboard()
|
self.__update_action_clipboard()
|
||||||
self.__update_action_undo_redo()
|
self.__update_action_undo_redo()
|
||||||
self.__update_action_add_object()
|
self.__update_action_add_object()
|
||||||
self.__update_action_remove_parent()
|
self.__update_action_remove_parent()
|
||||||
|
|
||||||
def __file_open_dialog_new(self, title, filter_obj=None, accept_label=None):
|
def __file_open_dialog_new(self, title, filter_obj=None, accept_label=None, use_project_dir=False, filters=None):
|
||||||
dialog = Gtk.FileDialog(
|
dialog = Gtk.FileDialog(
|
||||||
modal=True,
|
modal=True,
|
||||||
title=title,
|
title=title,
|
||||||
default_filter=filter_obj,
|
default_filter=filter_obj,
|
||||||
|
filters=filters,
|
||||||
accept_label=accept_label,
|
accept_label=accept_label,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.project and self.project.filename:
|
if use_project_dir and self.project and self.project.filename:
|
||||||
dialog.set_initial_folder(Gio.File.new_for_path(os.path.dirname(self.project.filename)))
|
dialog.set_initial_folder(Gio.File.new_for_path(os.path.dirname(self.project.filename)))
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
@ -631,15 +726,25 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
dialog.connect("response", lambda d, r: dialog.destroy())
|
dialog.connect("response", lambda d, r: dialog.destroy())
|
||||||
dialog.present()
|
dialog.present()
|
||||||
|
|
||||||
|
def __check_if_filename_is_in_portal(self, filename):
|
||||||
|
if os.environ.get("FLATPAK_ID", None) != "ar.xjuan.Cambalache":
|
||||||
|
return False
|
||||||
|
|
||||||
|
return filename.startswith(f"/run/user/{os.getuid()}/doc/")
|
||||||
|
|
||||||
def import_file(self, filename, target_tk=None):
|
def import_file(self, filename, target_tk=None):
|
||||||
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)
|
||||||
name, ext = os.path.splitext(basename)
|
name, ext = os.path.splitext(basename)
|
||||||
|
|
||||||
if target_tk is None:
|
if not target_tk:
|
||||||
target_tk = CmbProject.get_target_from_ui_file(filename)
|
target_tk = CmbProject.get_target_from_ui_file(filename)
|
||||||
|
|
||||||
|
if not target_tk:
|
||||||
|
self.ask_gtk_version(filename)
|
||||||
|
return
|
||||||
|
|
||||||
self.project = CmbProject(filename=os.path.join(dirname, f"{name}.cmb"), target_tk=target_tk)
|
self.project = CmbProject(filename=os.path.join(dirname, f"{name}.cmb"), target_tk=target_tk)
|
||||||
self.__set_page("workspace")
|
self.__set_page("workspace")
|
||||||
self.__update_actions()
|
self.__update_actions()
|
||||||
@ -652,6 +757,9 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if self.__check_if_filename_is_in_portal(filename):
|
||||||
|
raise Exception(self.__portal_access_msg)
|
||||||
|
|
||||||
ui, msg, detail = self.project.import_file(filename)
|
ui, msg, detail = self.project.import_file(filename)
|
||||||
|
|
||||||
self.project.set_selection([ui])
|
self.project.set_selection([ui])
|
||||||
@ -673,14 +781,14 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
first_msg = _("Cambalache encounter the following issues:")
|
first_msg = _("Cambalache encounter the following issues:")
|
||||||
|
|
||||||
# Translators: this is the last message after the list of unsupported features
|
# Translators: this is the last message after the list of unsupported features
|
||||||
last_msg = _("Your file will be exported as '{name}.cmb.ui' to avoid data loss.").format(name=name)
|
last_msg = _("Your file will be saved as '{name}.cmb.ui' to avoid data loss.").format(name=name)
|
||||||
|
|
||||||
unsupported_features_list = [first_msg] + list + [last_msg]
|
unsupported_features_list = [first_msg] + list + [last_msg]
|
||||||
else:
|
else:
|
||||||
unsupported_feature = msg[0]
|
unsupported_feature = msg[0]
|
||||||
text = _(
|
text = _(
|
||||||
"Cambalache encounter {unsupported_feature}\n"
|
"Cambalache encounter {unsupported_feature}\n"
|
||||||
"Your file will be exported as '{name}.cmb.ui' to avoid data loss."
|
"Your file will be saved as '{name}.cmb.ui' to avoid data loss."
|
||||||
).format(unsupported_feature=unsupported_feature, name=name)
|
).format(unsupported_feature=unsupported_feature, name=name)
|
||||||
|
|
||||||
self.present_message_to_user(
|
self.present_message_to_user(
|
||||||
@ -690,9 +798,9 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
filename = os.path.basename(filename)
|
filename = os.path.basename(filename)
|
||||||
logger.warning(f"Error loading {filename} {traceback.format_exc()}")
|
logger.warning(f"Error loading {filename}", exc_info=True)
|
||||||
self.present_message_to_user(
|
self.present_message_to_user(
|
||||||
_("Error importing {filename}").format(filename=os.path.basename(filename)), secondary_text=str(e)
|
_("Error importing {filename}").format(filename=filename), secondary_text=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
def ask_gtk_version(self, filename):
|
def ask_gtk_version(self, filename):
|
||||||
@ -717,6 +825,20 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
dialog.connect("response", on_ask_gtk_version_response)
|
dialog.connect("response", on_ask_gtk_version_response)
|
||||||
dialog.present()
|
dialog.present()
|
||||||
|
|
||||||
|
def create_project(self, target_tk, filename, uipath):
|
||||||
|
if self.project:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.project = CmbProject(filename=filename, target_tk=target_tk)
|
||||||
|
self.__last_saved_index = self.project.history_index
|
||||||
|
|
||||||
|
# Create UI and select it
|
||||||
|
ui = self.project.add_ui(uipath)
|
||||||
|
self.project.set_selection([ui])
|
||||||
|
|
||||||
|
self.__set_page("workspace")
|
||||||
|
self.__update_actions()
|
||||||
|
|
||||||
def open_project(self, filename, target_tk):
|
def open_project(self, filename, target_tk):
|
||||||
try:
|
try:
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
@ -741,16 +863,27 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.__set_page("workspace")
|
self.__set_page("workspace")
|
||||||
self.__update_actions()
|
self.__update_actions()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error loading {filename} {traceback.format_exc()}")
|
filename = os.path.basename(filename)
|
||||||
|
logger.warning(f"Error loading {filename}", exc_info=True)
|
||||||
self.present_message_to_user(
|
self.present_message_to_user(
|
||||||
_("Error loading {filename}").format(filename=os.path.basename(filename)), secondary_text=str(e)
|
_("Error loading {filename}").format(filename=filename), secondary_text=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __app_activate_open(self, filename, target_tk=None):
|
||||||
|
if self.__check_if_filename_is_in_portal(filename):
|
||||||
|
self.present_message_to_user(
|
||||||
|
_("Error opening {filename}").format(filename=os.path.basename(filename)),
|
||||||
|
secondary_text=self.__portal_access_msg
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.props.application.activate_action("open", GLib.Variant("(ss)", (filename, target_tk or "")))
|
||||||
|
|
||||||
def _on_open_activate(self, action, data):
|
def _on_open_activate(self, action, data):
|
||||||
def dialog_callback(dialog, res):
|
def dialog_callback(dialog, res):
|
||||||
try:
|
try:
|
||||||
file = dialog.open_finish(res)
|
file = dialog.open_finish(res)
|
||||||
self.emit("open-project", file.get_path(), None)
|
self.__app_activate_open(file.get_path())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error {e}")
|
logger.warning(f"Error {e}")
|
||||||
|
|
||||||
@ -783,13 +916,15 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
def _on_new_activate(self, action, data):
|
def _on_new_activate(self, action, data):
|
||||||
name = self.np_name_entry.props.text
|
name = self.np_name_entry.props.text
|
||||||
uiname = self.np_ui_entry.props.text
|
uiname = self.np_ui_entry.props.text
|
||||||
filename = None
|
filename = ""
|
||||||
uipath = None
|
uipath = ""
|
||||||
|
|
||||||
if self.np_gtk3_radiobutton.get_active():
|
if self.np_gtk3_radiobutton.get_active():
|
||||||
target_tk = "gtk+-3.0"
|
target_tk = "gtk+-3.0"
|
||||||
elif self.np_gtk4_radiobutton.get_active():
|
elif self.np_gtk4_radiobutton.get_active():
|
||||||
target_tk = "gtk-4.0"
|
target_tk = "gtk-4.0"
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
if len(name):
|
if len(name):
|
||||||
name, ext = os.path.splitext(name)
|
name, ext = os.path.splitext(name)
|
||||||
@ -805,11 +940,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
uipath = os.path.join(self.__np_location, uiname)
|
uipath = os.path.join(self.__np_location, uiname)
|
||||||
|
|
||||||
self.emit("open-project", filename, target_tk)
|
self.props.application.activate_action("new", GLib.Variant("(sss)", (target_tk, filename, uipath)))
|
||||||
|
|
||||||
# Create Ui and select it
|
|
||||||
ui = self.project.add_ui(uipath)
|
|
||||||
self.project.set_selection([ui])
|
|
||||||
self.__set_page("workspace" if self.project else "cambalache")
|
self.__set_page("workspace" if self.project else "cambalache")
|
||||||
|
|
||||||
def __on_undo_redo_activate(self, undo):
|
def __on_undo_redo_activate(self, undo):
|
||||||
@ -821,7 +952,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
else:
|
else:
|
||||||
self.project.redo()
|
self.project.redo()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Undo/Redo error {traceback.format_exc()}")
|
logger.warning("Undo/Redo error", exc_info=True)
|
||||||
self.present_message_to_user(
|
self.present_message_to_user(
|
||||||
_("Undo/Redo stack got corrupted"),
|
_("Undo/Redo stack got corrupted"),
|
||||||
secondary_text=_("Please try to reproduce and file an issue\n Error: {msg}").format(msg=str(e))
|
secondary_text=_("Please try to reproduce and file an issue\n Error: {msg}").format(msg=str(e))
|
||||||
@ -838,12 +969,29 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.save_project()
|
self.save_project()
|
||||||
|
|
||||||
def __save_dialog_callback(self, dialog, res):
|
def __save_dialog_callback(self, dialog, res):
|
||||||
|
filename = None
|
||||||
try:
|
try:
|
||||||
file = dialog.save_finish(res)
|
file = dialog.save_finish(res)
|
||||||
self.project.filename = file.get_path()
|
filename = file.get_path()
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.__check_if_filename_is_in_portal(filename):
|
||||||
|
raise Exception(self.__portal_access_msg)
|
||||||
|
|
||||||
|
self.project.filename = filename
|
||||||
self.__save()
|
self.__save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error {e}")
|
filename = os.path.basename(filename) if filename else filename
|
||||||
|
logger.warning(f"Error saving {filename}", exc_info=True)
|
||||||
|
self.present_message_to_user(
|
||||||
|
_("Error importing {filename}").format(filename=filename),
|
||||||
|
secondary_text=str(e)
|
||||||
|
)
|
||||||
|
|
||||||
def _on_save_as_activate(self, action, data):
|
def _on_save_as_activate(self, action, data):
|
||||||
if self.project is None:
|
if self.project is None:
|
||||||
@ -877,10 +1025,12 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def on_dialog_response(dialog, response):
|
def on_dialog_response(dialog, response):
|
||||||
if response == Gtk.ResponseType.YES:
|
if response == Gtk.ResponseType.YES:
|
||||||
if type(obj) is CmbUI:
|
if isinstance(obj, CmbUI):
|
||||||
self.project.remove_ui(obj)
|
self.project.remove_ui(obj)
|
||||||
elif type(obj) is CmbCSS:
|
elif isinstance(obj, CmbCSS):
|
||||||
self.project.remove_css(obj)
|
self.project.remove_css(obj)
|
||||||
|
elif isinstance(obj, CmbGResource):
|
||||||
|
self.project.remove_gresource(obj)
|
||||||
|
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
@ -908,10 +1058,18 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
selection = self.project.get_selection()
|
selection = self.project.get_selection()
|
||||||
for obj in selection:
|
for obj in selection:
|
||||||
if type(obj) is CmbObject:
|
try:
|
||||||
self.project.remove_object(obj)
|
if isinstance(obj, CmbObject):
|
||||||
else:
|
self.project.remove_object(obj)
|
||||||
self.__remove_object_with_confirmation(obj)
|
elif isinstance(obj, CmbGResource):
|
||||||
|
if obj.resource_type == "gresources":
|
||||||
|
self.__remove_object_with_confirmation(obj)
|
||||||
|
else:
|
||||||
|
self.project.remove_gresource(obj)
|
||||||
|
else:
|
||||||
|
self.__remove_object_with_confirmation(obj)
|
||||||
|
except Exception as e:
|
||||||
|
self.present_message_to_user(_("Error deleting {name}").format(name=obj.display_name_type), secondary_text=str(e))
|
||||||
|
|
||||||
def _on_add_object_activate(self, action, data):
|
def _on_add_object_activate(self, action, data):
|
||||||
info = self.type_chooser.props.selected_type
|
info = self.type_chooser.props.selected_type
|
||||||
@ -960,13 +1118,13 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
first_msg = _("Cambalache encounter the following issues:")
|
first_msg = _("Cambalache encounter the following issues:")
|
||||||
|
|
||||||
# Translators: this is the last message after the list of unsupported features
|
# Translators: this is the last message after the list of unsupported features
|
||||||
last_msg = _("Your file will be exported as '{name}.cmb.ui' to avoid data loss.").format(name=name)
|
last_msg = _("Your file will be saved as '{name}.cmb.ui' to avoid data loss.").format(name=name)
|
||||||
|
|
||||||
unsupported_features_list = [first_msg] + list + [last_msg]
|
unsupported_features_list = [first_msg] + list + [last_msg]
|
||||||
else:
|
else:
|
||||||
unsupported_feature = msg[0]
|
unsupported_feature = msg[0]
|
||||||
text = _(
|
text = _(
|
||||||
"Cambalache encounter {unsupported_feature}\nYour file will be exported as '{name}.cmb.ui' to avoid data loss."
|
"Cambalache encounter {unsupported_feature}\nYour file will be saved as '{name}.cmb.ui' to avoid data loss."
|
||||||
).format(unsupported_feature=unsupported_feature, name=name)
|
).format(unsupported_feature=unsupported_feature, name=name)
|
||||||
|
|
||||||
self.present_message_to_user(
|
self.present_message_to_user(
|
||||||
@ -982,20 +1140,59 @@ 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):
|
||||||
self.import_file(file.get_path())
|
path = 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}")
|
||||||
|
|
||||||
|
if self.project.target_tk == "gtk-4.0":
|
||||||
|
import_filters = self.gtk4_import_filters
|
||||||
|
else:
|
||||||
|
import_filters = self.gtk3_import_filters
|
||||||
|
|
||||||
dialog = self.__file_open_dialog_new(
|
dialog = self.__file_open_dialog_new(
|
||||||
_("Choose file to import"), filter_obj=self.import_filter, accept_label=_("Import")
|
_("Choose file to import"),
|
||||||
|
filters=import_filters,
|
||||||
|
accept_label=_("Import")
|
||||||
)
|
)
|
||||||
dialog.open_multiple(self, None, dialog_callback)
|
dialog.open_multiple(self, None, dialog_callback)
|
||||||
|
|
||||||
|
def _on_add_gresource_activate(self, action, data):
|
||||||
|
if self.project is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
gresource = self.project.add_gresource("gresources")
|
||||||
|
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:
|
||||||
@ -1011,16 +1208,6 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _on_export_activate(self, action, data):
|
|
||||||
if self.project is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.save_project()
|
|
||||||
|
|
||||||
n = self.project.export()
|
|
||||||
|
|
||||||
self._show_message(_("{n} files exported").format(n=n) if n > 1 else _("File exported"))
|
|
||||||
|
|
||||||
def _close_project_dialog_new(self):
|
def _close_project_dialog_new(self):
|
||||||
text = _("Save changes before closing?")
|
text = _("Save changes before closing?")
|
||||||
dialog = Gtk.MessageDialog(
|
dialog = Gtk.MessageDialog(
|
||||||
@ -1048,6 +1235,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
def close_project():
|
def close_project():
|
||||||
self.project = None
|
self.project = None
|
||||||
self.__set_page("cambalache")
|
self.__set_page("cambalache")
|
||||||
|
self.emit("project-closed")
|
||||||
|
|
||||||
if self.actions["save"].get_enabled():
|
if self.actions["save"].get_enabled():
|
||||||
dialog = self._close_project_dialog_new()
|
dialog = self._close_project_dialog_new()
|
||||||
@ -1119,20 +1307,19 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
about.props.translator_credits = "\n".join(translator_list)
|
about.props.translator_credits = "\n".join(translator_list)
|
||||||
|
|
||||||
def _on_about_activate(self, action, data):
|
def _on_about_activate(self, action, data):
|
||||||
about = Adw.AboutWindow.new_from_appdata("/ar/xjuan/Cambalache/app/metainfo.xml", config.VERSION)
|
about = Adw.AboutDialog.new_from_appdata("/ar/xjuan/Cambalache/app/metainfo.xml", config.VERSION)
|
||||||
|
|
||||||
about.props.transient_for = self
|
|
||||||
about.props.artists = [
|
about.props.artists = [
|
||||||
"Franco Dodorico",
|
"Franco Dodorico",
|
||||||
"Juan Pablo Ugarte",
|
"Juan Pablo Ugarte",
|
||||||
]
|
]
|
||||||
about.props.copyright = "© 2020-2024 Juan Pablo Ugarte"
|
about.props.copyright = "© 2020-2025 Juan Pablo Ugarte"
|
||||||
about.props.license_type = Gtk.License.LGPL_2_1_ONLY
|
about.props.license_type = Gtk.License.LGPL_2_1_ONLY
|
||||||
|
|
||||||
self.__update_translators(about)
|
self.__update_translators(about)
|
||||||
self.__populate_supporters(about)
|
self.__populate_supporters(about)
|
||||||
|
|
||||||
about.present()
|
about.present(self)
|
||||||
|
|
||||||
def _on_add_parent_activate(self, action, data):
|
def _on_add_parent_activate(self, action, data):
|
||||||
obj = self.project.get_selection()[0]
|
obj = self.project.get_selection()[0]
|
||||||
@ -1227,7 +1414,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
elif node in ["add-ui", "add-window", "add-grid", "add-button"]:
|
elif node in ["add-ui", "add-window", "add-grid", "add-button"]:
|
||||||
self.tutor_waiting_for_user_action = True
|
self.tutor_waiting_for_user_action = True
|
||||||
self.tutor.pause()
|
self.tutor.pause()
|
||||||
elif node in ["donate", "export_all"]:
|
elif node in ["donate"]:
|
||||||
self.menu_button.popdown()
|
self.menu_button.popdown()
|
||||||
elif node == "show-type-popover":
|
elif node == "show-type-popover":
|
||||||
widget.props.popover.popdown()
|
widget.props.popover.popdown()
|
||||||
@ -1269,7 +1456,7 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
self.view.inspect()
|
self.view.inspect()
|
||||||
|
|
||||||
def _on_open_recent_activate(self, action, data):
|
def _on_open_recent_activate(self, action, data):
|
||||||
self.emit("open-project", data.get_string(), None)
|
self.__app_activate_open(data.get_string(), "")
|
||||||
|
|
||||||
def __update_recent_menu(self):
|
def __update_recent_menu(self):
|
||||||
mime_types = ["application/x-cambalache-project"]
|
mime_types = ["application/x-cambalache-project"]
|
||||||
@ -1356,3 +1543,13 @@ class CmbWindow(Adw.ApplicationWindow):
|
|||||||
else:
|
else:
|
||||||
self.message_revealer.props.reveal_child = False
|
self.message_revealer.props.reveal_child = False
|
||||||
|
|
||||||
|
def __notification_present(self):
|
||||||
|
if self.stack.get_visible_child_name() != "cambalache":
|
||||||
|
self.notification_dialog.present(self)
|
||||||
|
|
||||||
|
def _on_notification_activate(self, action, data):
|
||||||
|
self.__notification_present()
|
||||||
|
|
||||||
|
def __on_new_notification(self, center, notification):
|
||||||
|
self.__notification_present()
|
||||||
|
self.__update_action_notification()
|
||||||
|
@ -1,48 +1,45 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- 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 -->
|
||||||
<requires lib="gio" version="2.0"/>
|
<requires lib="gio" version="2.0"/>
|
||||||
<requires lib="gtk" version="4.14"/>
|
<requires lib="gtk" version="4.14"/>
|
||||||
<requires lib="libadwaita" version="1.5"/>
|
<requires lib="libadwaita" version="1.5"/>
|
||||||
<object class="GtkFileFilter" id="import_filter">
|
|
||||||
<mime-types>
|
|
||||||
<mime-type>application/x-glade</mime-type>
|
|
||||||
<mime-type>application/x-gtk-builder</mime-type>
|
|
||||||
</mime-types>
|
|
||||||
<patterns>
|
|
||||||
<pattern>*.ui</pattern>
|
|
||||||
<pattern>*.glade</pattern>
|
|
||||||
</patterns>
|
|
||||||
</object>
|
|
||||||
<menu id="main_menu">
|
<menu id="main_menu">
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.create_new</attribute>
|
<attribute name="action">win.create_new</attribute>
|
||||||
<attribute name="label">New Project</attribute>
|
<attribute name="label" translatable="yes">New Project</attribute>
|
||||||
</item>
|
</item>
|
||||||
<section>
|
<section>
|
||||||
|
<submenu>
|
||||||
|
<attribute name="label" translatable="yes">Add file</attribute>
|
||||||
|
<item>
|
||||||
|
<attribute name="action">win.add_ui</attribute>
|
||||||
|
<attribute name="label" translatable="yes">Add UI</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="action">win.add_css</attribute>
|
||||||
|
<attribute name="label" translatable="yes">Add CSS</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="action">win.add_gresource</attribute>
|
||||||
|
<attribute name="label" translatable="yes">Add GResource</attribute>
|
||||||
|
</item>
|
||||||
|
</submenu>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.import</attribute>
|
<attribute name="action">win.import</attribute>
|
||||||
<attribute name="label" translatable="yes">Import</attribute>
|
<attribute name="label" translatable="yes">Import file</attribute>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="action">win.export</attribute>
|
|
||||||
<attribute name="label" translatable="yes">Export all</attribute>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<attribute name="action">win.add_css</attribute>
|
|
||||||
<attribute name="label" translatable="yes">Add CSS file</attribute>
|
|
||||||
</item>
|
</item>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.save</attribute>
|
<attribute name="action">win.save</attribute>
|
||||||
<attribute name="label">Save</attribute>
|
<attribute name="label" translatable="yes">Save</attribute>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.save_as</attribute>
|
<attribute name="action">win.save_as</attribute>
|
||||||
<attribute name="label">Save As</attribute>
|
<attribute name="label" translatable="yes">Save As</attribute>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.close</attribute>
|
<attribute name="action">win.close</attribute>
|
||||||
@ -54,6 +51,10 @@
|
|||||||
<attribute name="action">win.intro</attribute>
|
<attribute name="action">win.intro</attribute>
|
||||||
<attribute name="label" translatable="yes">Interactive intro</attribute>
|
<attribute name="label" translatable="yes">Interactive intro</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="action">win.notification</attribute>
|
||||||
|
<attribute name="label" translatable="yes">Notifications</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="action">win.contact</attribute>
|
<attribute name="action">win.contact</attribute>
|
||||||
<attribute name="label" translatable="yes">Contact</attribute>
|
<attribute name="label" translatable="yes">Contact</attribute>
|
||||||
@ -95,7 +96,7 @@
|
|||||||
<object class="AdwSplitButton" id="open_button">
|
<object class="AdwSplitButton" id="open_button">
|
||||||
<property name="action-name">win.open</property>
|
<property name="action-name">win.open</property>
|
||||||
<property name="dropdown-tooltip">Recent Projects</property>
|
<property name="dropdown-tooltip">Recent Projects</property>
|
||||||
<property name="label">_Open</property>
|
<property name="label" translatable="yes">_Open</property>
|
||||||
<property name="menu-model">recent_menu</property>
|
<property name="menu-model">recent_menu</property>
|
||||||
<property name="use-underline">True</property>
|
<property name="use-underline">True</property>
|
||||||
</object>
|
</object>
|
||||||
@ -176,13 +177,19 @@
|
|||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="CmbNotificationListView" id="front_notification_list_view">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="version_label">
|
<object class="GtkLabel" id="version_label">
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="margin-bottom">4</property>
|
<property name="margin-bottom">4</property>
|
||||||
<property name="name">version</property>
|
<property name="name">version</property>
|
||||||
<property name="valign">end</property>
|
<property name="valign">end</property>
|
||||||
<property name="vexpand">1</property>
|
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="size" value="9000"/>
|
<attribute name="size" value="9000"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
@ -252,7 +259,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="np_name_entry">
|
<object class="GtkEntry" id="np_name_entry">
|
||||||
<property name="focusable">1</property>
|
<property name="focusable">1</property>
|
||||||
<property name="input-hints">GTK_INPUT_HINT_LOWERCASE | GTK_INPUT_HINT_NONE</property>
|
<property name="input-hints">lowercase|none</property>
|
||||||
<property name="input-purpose">alpha</property>
|
<property name="input-purpose">alpha</property>
|
||||||
<property name="placeholder-text" translatable="yes"><project basename></property>
|
<property name="placeholder-text" translatable="yes"><project basename></property>
|
||||||
<property name="width-chars">32</property>
|
<property name="width-chars">32</property>
|
||||||
@ -402,7 +409,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkEntry" id="np_ui_entry">
|
<object class="GtkEntry" id="np_ui_entry">
|
||||||
<property name="focusable">1</property>
|
<property name="focusable">1</property>
|
||||||
<property name="input-hints">GTK_INPUT_HINT_LOWERCASE | GTK_INPUT_HINT_NONE</property>
|
<property name="input-hints">lowercase|none</property>
|
||||||
<property name="input-purpose">alpha</property>
|
<property name="input-purpose">alpha</property>
|
||||||
<property name="sensitive">0</property>
|
<property name="sensitive">0</property>
|
||||||
<property name="width-chars">32</property>
|
<property name="width-chars">32</property>
|
||||||
@ -486,6 +493,19 @@
|
|||||||
<property name="title" translatable="yes">Signals</property>
|
<property name="title" translatable="yes">Signals</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="child">
|
||||||
|
<object class="CmbAccessibleEditor" id="accessible_editor"/>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">accessible</property>
|
||||||
|
<property name="title">a11y</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkStackPage">
|
<object class="GtkStackPage">
|
||||||
<property name="child">
|
<property name="child">
|
||||||
@ -528,7 +548,6 @@
|
|||||||
<object class="CmbUIEditor" id="ui_editor">
|
<object class="CmbUIEditor" id="ui_editor">
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<signal name="remove-ui" handler="on_ui_editor_remove_ui"/>
|
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<property name="name">properties</property>
|
<property name="name">properties</property>
|
||||||
@ -571,13 +590,21 @@
|
|||||||
<object class="CmbCSSEditor" id="css_editor">
|
<object class="CmbCSSEditor" id="css_editor">
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<signal name="remove-css" handler="on_css_editor_remove_ui"/>
|
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<property name="name">css</property>
|
<property name="name">css</property>
|
||||||
<property name="title" translatable="yes">CSS Editor</property>
|
<property name="title" translatable="yes">CSS Editor</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="CmbGResourceEditor" id="gresource_editor"/>
|
||||||
|
</property>
|
||||||
|
<property name="name">gresource</property>
|
||||||
|
<property name="title">GResource Editor</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<property name="focusable">1</property>
|
<property name="focusable">1</property>
|
||||||
@ -588,21 +615,46 @@
|
|||||||
<property name="start-child">
|
<property name="start-child">
|
||||||
<object class="GtkPaned">
|
<object class="GtkPaned">
|
||||||
<property name="end-child">
|
<property name="end-child">
|
||||||
<object class="GtkBox">
|
<object class="GtkStack" id="workspace_stack">
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="CmbTypeChooser" id="type_chooser">
|
<object class="GtkStackPage">
|
||||||
<property name="spacing">2</property>
|
<property name="child">
|
||||||
<signal name="chooser-popdown" handler="on_type_chooser_chooser_popdown"/>
|
<object class="GtkBox">
|
||||||
<signal name="chooser-popup" handler="on_type_chooser_chooser_popup"/>
|
<property name="orientation">vertical</property>
|
||||||
<signal name="type-selected" handler="on_type_chooser_type_selected"/>
|
<child>
|
||||||
|
<object class="CmbTypeChooser" id="type_chooser">
|
||||||
|
<property name="spacing">2</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<signal name="chooser-popdown" handler="on_type_chooser_chooser_popdown"/>
|
||||||
|
<signal name="chooser-popup" handler="on_type_chooser_chooser_popup"/>
|
||||||
|
<signal name="type-selected" handler="on_type_chooser_type_selected"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbView" id="view">
|
||||||
|
<property name="vexpand">1</property>
|
||||||
|
<signal name="placeholder-activated" handler="on_view_placeholder_activated"/>
|
||||||
|
<signal name="placeholder-selected" handler="on_view_placeholder_selected"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">ui</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="CmbView" id="view">
|
<object class="GtkStackPage">
|
||||||
<property name="vexpand">1</property>
|
<property name="child">
|
||||||
<signal name="placeholder-activated" handler="on_view_placeholder_activated"/>
|
<object class="GtkScrolledWindow">
|
||||||
<signal name="placeholder-selected" handler="on_view_placeholder_selected"/>
|
<property name="child">
|
||||||
|
<object class="CmbSourceView" id="source_view">
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="lang">xml</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">gresource</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -620,7 +672,7 @@
|
|||||||
<property name="propagate-natural-height">True</property>
|
<property name="propagate-natural-height">True</property>
|
||||||
<property name="propagate-natural-width">True</property>
|
<property name="propagate-natural-width">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="CmbColumnView" id="column_view"/>
|
<object class="CmbListView" id="list_view"/>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -767,9 +819,7 @@
|
|||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="transition-type">slide-up</property>
|
<property name="transition-type">slide-up</property>
|
||||||
<property name="valign">end</property>
|
<property name="valign">end</property>
|
||||||
<style>
|
<style/>
|
||||||
<class name="message"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -780,4 +830,48 @@
|
|||||||
<class name="cmb-window"/>
|
<class name="cmb-window"/>
|
||||||
</style>
|
</style>
|
||||||
</template>
|
</template>
|
||||||
|
<object class="GtkFileFilter" id="gtk_builder_filter">
|
||||||
|
<property name="mime-types">application/x-gtk-builder</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="glade_filter">
|
||||||
|
<property name="mime-types">application/x-glade</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="blueprint_filter">
|
||||||
|
<property name="mime-types">text/x-blueprint</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="css_filter">
|
||||||
|
<property name="mime-types">text/css</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="gresource_filter">
|
||||||
|
<property name="suffixes">gresource.xml</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="gtk4_filter">
|
||||||
|
<property name="mime-types">application/x-gtk-builder
|
||||||
|
text/x-blueprint
|
||||||
|
text/css</property>
|
||||||
|
<property name="name">All supported files</property>
|
||||||
|
<property name="suffixes">gresource.xml</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkFileFilter" id="gtk3_filter">
|
||||||
|
<property name="mime-types">application/x-gtk-builder
|
||||||
|
application/x-glade
|
||||||
|
text/css</property>
|
||||||
|
<property name="name">All supported files</property>
|
||||||
|
<property name="suffixes">gresource.xml</property>
|
||||||
|
</object>
|
||||||
|
<object class="AdwDialog" id="notification_dialog">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwHeaderBar"/>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbNotificationListView" id="notification_list_view"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="follows-content-size">True</property>
|
||||||
|
<property name="title" translatable="yes">Notifications</property>
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,16 @@ CmbPropertyLabel.hidden:hover > box > image {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
popover.cmb-binding-popover button.close,
|
||||||
|
list.notifications button.close {
|
||||||
|
padding: unset;
|
||||||
|
margin: unset;
|
||||||
|
border: unset;
|
||||||
|
border-radius: 50%;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
CmbPropertyLabel {
|
CmbPropertyLabel {
|
||||||
min-width:unset;
|
min-width:unset;
|
||||||
min-height: unset;
|
min-height: unset;
|
||||||
@ -51,6 +61,22 @@ CmbPropertyLabel {
|
|||||||
outline: unset;
|
outline: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CmbPropertyLabel > box > label {
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
CmbPropertyLabel:focus > box > label {
|
||||||
|
/*
|
||||||
|
FIXME: use focus_border_color
|
||||||
|
$focus_border_color: if($variant == 'light', transparentize($selected_bg_color, 0.5), transparentize($selected_bg_color, 0.3));
|
||||||
|
*/
|
||||||
|
outline-color: color-mix(in srgb, var(--accent-bg-color) 60%, transparent);
|
||||||
|
outline-offset: -2px;
|
||||||
|
outline-width: 2px;
|
||||||
|
outline-style: solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
CmbPropertyLabel.modified > box > label {
|
CmbPropertyLabel.modified > box > label {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
@ -59,10 +85,47 @@ CmbPropertyLabel.warning > box > label {
|
|||||||
text-decoration: underline wavy @warning_color;
|
text-decoration: underline wavy @warning_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
row.drop-before:drop(active) {
|
listview.cmb-list-view {
|
||||||
box-shadow: 0px 1px 0px @theme_selected_bg_color inset;
|
background-color: @theme_bg_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
row.drop-after:drop(active) {
|
listview.cmb-list-view > row {
|
||||||
box-shadow: 0px -1px 0px @theme_selected_bg_color inset;
|
padding: 2px 8px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row:drop(active):not(.drop-after):not(.drop-before) {
|
||||||
|
outline: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row.drop-before:drop(active) {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-top: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row.drop-after:drop(active) {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-bottom: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row > treeexpander.cmb-path > expander {
|
||||||
|
-gtk-icon-source: -gtk-icontheme("folder-symbolic");
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row > treeexpander.cmb-path > expander:checked {
|
||||||
|
-gtk-icon-source: -gtk-icontheme("folder-open-symbolic");
|
||||||
|
}
|
||||||
|
|
||||||
|
listview.cmb-list-view > row > treeexpander.cmb-unsaved-path > expander {
|
||||||
|
-gtk-icon-source: -gtk-icontheme("view-list-symbolic");
|
||||||
|
}
|
||||||
|
|
||||||
|
button.compact {
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,31 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/ar/xjuan/Cambalache">
|
<gresource prefix="/ar/xjuan/Cambalache">
|
||||||
<file>control/cmb_translatable_widget.ui</file>
|
<file>cambalache.css</file>
|
||||||
<file>db/cmb_base.sql</file>
|
|
||||||
<file>db/cmb_project.sql</file>
|
|
||||||
<file>db/cmb_history.sql</file>
|
|
||||||
<file>cmb_view.ui</file>
|
|
||||||
<file>cmb_context_menu.ui</file>
|
<file>cmb_context_menu.ui</file>
|
||||||
|
<file>cmb_css_editor.ui</file>
|
||||||
|
<file>cmb_db_inspector.ui</file>
|
||||||
|
<file>cmb_fragment_editor.ui</file>
|
||||||
|
<file>cmb_gresource_editor.ui</file>
|
||||||
|
<file>cmb_notification_list_view.ui</file>
|
||||||
|
<file>cmb_version_notification_view.ui</file>
|
||||||
|
<file>cmb_message_notification_view.ui</file>
|
||||||
|
<file>cmb_poll_option_check.ui</file>
|
||||||
|
<file>cmb_poll_notification_view.ui</file>
|
||||||
|
<file>cmb_object_data_editor.ui</file>
|
||||||
|
<file>cmb_signal_editor.ui</file>
|
||||||
<file>cmb_type_chooser.ui</file>
|
<file>cmb_type_chooser.ui</file>
|
||||||
<file>cmb_type_chooser_widget.ui</file>
|
<file>cmb_type_chooser_widget.ui</file>
|
||||||
<file>cmb_ui_editor.ui</file>
|
<file>cmb_ui_editor.ui</file>
|
||||||
<file>cmb_css_editor.ui</file>
|
<file>cmb_view.ui</file>
|
||||||
<file>cmb_fragment_editor.ui</file>
|
<file>control/cmb_file_button.ui</file>
|
||||||
<file>cmb_object_data_editor.ui</file>
|
<file>control/cmb_translatable_widget.ui</file>
|
||||||
<file>cmb_signal_editor.ui</file>
|
<file>db/cmb_base.sql</file>
|
||||||
<file>cmb_db_inspector.ui</file>
|
<file>db/cmb_history.sql</file>
|
||||||
<file>cambalache.css</file>
|
<file>db/cmb_project.sql</file>
|
||||||
<file>icons/scalable/actions/bind-symbolic.svg</file>
|
|
||||||
<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>cmb_notification_list_row.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
|
236
cambalache/cmb_accessible_editor.py
Normal file
236
cambalache/cmb_accessible_editor.py
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
#
|
||||||
|
# CmbAccessibleEditor - Cambalache Accessible Editor
|
||||||
|
#
|
||||||
|
# Copyright (C) 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 .control import cmb_create_editor, CmbEnumComboBox
|
||||||
|
from .cmb_property_label import CmbPropertyLabel
|
||||||
|
from . import utils, _
|
||||||
|
|
||||||
|
|
||||||
|
class CmbAccessibleEditor(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbAccessibleEditor"
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__object = None
|
||||||
|
self.__bindings = []
|
||||||
|
self.__accessibility_metadata = None
|
||||||
|
self.__role_filter_model = None
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.props.orientation = Gtk.Orientation.VERTICAL
|
||||||
|
|
||||||
|
self.__role_box = Gtk.Box(spacing=6)
|
||||||
|
self.__role_box.append(Gtk.Label(label="accessible-role"))
|
||||||
|
self.__role_combobox = CmbEnumComboBox()
|
||||||
|
self.__role_box.append(self.__role_combobox)
|
||||||
|
|
||||||
|
self.append(self.__role_box)
|
||||||
|
|
||||||
|
self.__box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
self.append(self.__box)
|
||||||
|
|
||||||
|
def bind_property(self, *args):
|
||||||
|
binding = GObject.Object.bind_property(*args)
|
||||||
|
self.__bindings.append(binding)
|
||||||
|
return binding
|
||||||
|
|
||||||
|
def __on_expander_expanded(self, expander, pspec, revealer):
|
||||||
|
expanded = expander.props.expanded
|
||||||
|
|
||||||
|
if expanded:
|
||||||
|
revealer.props.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN
|
||||||
|
else:
|
||||||
|
revealer.props.transition_type = Gtk.RevealerTransitionType.SLIDE_UP
|
||||||
|
|
||||||
|
revealer.props.reveal_child = expanded
|
||||||
|
|
||||||
|
def __update_view(self):
|
||||||
|
for child in utils.widget_get_children(self.__box):
|
||||||
|
self.__box.remove(child)
|
||||||
|
|
||||||
|
if self.__object is None or not self.__object.info.is_a("GtkWidget"):
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = self.__object
|
||||||
|
|
||||||
|
if obj.project.target_tk == "gtk-4.0":
|
||||||
|
if not self.__object.info.is_a("GtkWidget"):
|
||||||
|
self.__role_box.hide()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__role_box.show()
|
||||||
|
|
||||||
|
prop = self.__object.properties_dict["accessible-role"]
|
||||||
|
if prop.value in ["none", "presentation"]:
|
||||||
|
# No need to set properties if role none or presentation
|
||||||
|
return
|
||||||
|
|
||||||
|
role_data = self.__object.project.db.accessibility_metadata.get(prop.value, None)
|
||||||
|
if role_data:
|
||||||
|
role_properties = role_data["properties"]
|
||||||
|
role_states = role_data["states"]
|
||||||
|
else:
|
||||||
|
role_properties = None
|
||||||
|
role_states = None
|
||||||
|
|
||||||
|
a11y_data = [
|
||||||
|
("CmbAccessibleProperty", _("Properties"), len("cmb-a11y-properties"), role_properties),
|
||||||
|
("CmbAccessibleState", _("States"), len("cmb-a11y-states"), role_states),
|
||||||
|
("CmbAccessibleRelation", _("Relations"), None, None),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
type_actions = self.__object.project.db.accessibility_metadata.get(obj.type_id, [])
|
||||||
|
|
||||||
|
a11y_data = [
|
||||||
|
("CmbAccessibleAction", _("Actions"), len("cmb-a11y-actions"), type_actions),
|
||||||
|
("CmbAccessibleProperty", _("Properties"), None, None),
|
||||||
|
("CmbAccessibleRelation", _("Relations"), None, None),
|
||||||
|
]
|
||||||
|
self.__role_box.hide()
|
||||||
|
|
||||||
|
properties = obj.properties_dict
|
||||||
|
|
||||||
|
for owner_id, title, prefix_len, allowed_ids in a11y_data:
|
||||||
|
info = obj.project.type_info.get(owner_id, None)
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Editor count
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# Grid for all editors
|
||||||
|
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
|
||||||
|
|
||||||
|
# Accessible iface properties
|
||||||
|
for property_id in info.properties:
|
||||||
|
# Ignore properties or status not for this role
|
||||||
|
if prefix_len and allowed_ids is not None and property_id[prefix_len:] not in allowed_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
prop = properties.get(property_id, None)
|
||||||
|
|
||||||
|
if prop is None or prop.info is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
editor = cmb_create_editor(prop.project, prop.info.type_id, prop=prop)
|
||||||
|
|
||||||
|
if editor is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
self.bind_property(
|
||||||
|
prop,
|
||||||
|
"value",
|
||||||
|
editor,
|
||||||
|
"cmb-value",
|
||||||
|
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
label = CmbPropertyLabel(prop=prop, bindable=False)
|
||||||
|
grid.attach(label, 0, i, 1, 1)
|
||||||
|
grid.attach(editor, 1, i, 1, 1)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create expander/revealer to pack editor grid
|
||||||
|
expander = Gtk.Expander(label=f"<b>{title}</b>", use_markup=True, expanded=True)
|
||||||
|
revealer = Gtk.Revealer(reveal_child=True)
|
||||||
|
expander.connect("notify::expanded", self.__on_expander_expanded, revealer)
|
||||||
|
revealer.set_child(grid)
|
||||||
|
self.__box.append(expander)
|
||||||
|
self.__box.append(revealer)
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def __on_object_property_changed_notify(self, obj, prop):
|
||||||
|
if prop.property_id == "accessible-role":
|
||||||
|
self.__update_view()
|
||||||
|
|
||||||
|
def __visible_func(self, model, iter, data):
|
||||||
|
if self.__accessibility_metadata is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
name, nick, value = model[iter]
|
||||||
|
|
||||||
|
role_data = self.__accessibility_metadata.get(nick, None)
|
||||||
|
if role_data:
|
||||||
|
# Ignore abstract roles
|
||||||
|
if nick != "none" and role_data.get("is_abstract", False):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@GObject.Property(type=CmbObject)
|
||||||
|
def object(self):
|
||||||
|
return self.__object
|
||||||
|
|
||||||
|
@object.setter
|
||||||
|
def _set_object(self, obj):
|
||||||
|
if obj == self.__object:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.__object and self.__object.info.is_a("GtkWidget"):
|
||||||
|
self.__object.disconnect_by_func(self.__on_object_property_changed_notify)
|
||||||
|
self.__role_combobox.props.model = None
|
||||||
|
self.__accessibility_metadata = None
|
||||||
|
|
||||||
|
for binding in self.__bindings:
|
||||||
|
binding.unbind()
|
||||||
|
|
||||||
|
self.__bindings = []
|
||||||
|
self.__object = obj
|
||||||
|
|
||||||
|
if self.__object and self.__object.info.is_a("GtkWidget"):
|
||||||
|
self.__object.connect("property-changed", self.__on_object_property_changed_notify)
|
||||||
|
|
||||||
|
self.__accessibility_metadata = self.__object.project.db.accessibility_metadata
|
||||||
|
a11y_info = self.__object.project.type_info.get("GtkAccessibleRole", None)
|
||||||
|
|
||||||
|
if a11y_info:
|
||||||
|
a11y_info = self.__object.project.type_info.get("GtkAccessibleRole", None)
|
||||||
|
|
||||||
|
self.__role_filter_model = Gtk.TreeModelFilter(child_model=a11y_info.enum)
|
||||||
|
self.__role_filter_model.set_visible_func(self.__visible_func)
|
||||||
|
self.__role_combobox.info = a11y_info
|
||||||
|
self.__role_combobox.props.model = self.__role_filter_model
|
||||||
|
|
||||||
|
prop = self.__object.properties_dict.get("accessible-role", None)
|
||||||
|
self.bind_property(
|
||||||
|
prop,
|
||||||
|
"value",
|
||||||
|
self.__role_combobox,
|
||||||
|
"cmb-value",
|
||||||
|
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__update_view()
|
||||||
|
|
||||||
|
|
||||||
|
Gtk.WidgetClass.set_css_name(CmbAccessibleEditor, "CmbAccessibleEditor")
|
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()
|
||||||
|
|
@ -1,524 +0,0 @@
|
|||||||
#
|
|
||||||
# CmbColumnView
|
|
||||||
#
|
|
||||||
# Copyright (C) 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, Gdk, Gtk
|
|
||||||
from .cmb_ui import CmbUI
|
|
||||||
from .cmb_object import CmbObject
|
|
||||||
from .cmb_context_menu import CmbContextMenu
|
|
||||||
from .cmb_project import CmbProject
|
|
||||||
from cambalache import _
|
|
||||||
|
|
||||||
|
|
||||||
class CmbColumnView(Gtk.ColumnView):
|
|
||||||
__gtype_name__ = "CmbColumnView"
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__project = None
|
|
||||||
self.__tree_model = None
|
|
||||||
self.__in_selection_change = False
|
|
||||||
self.single_selection = Gtk.SingleSelection()
|
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
self.props.has_tooltip = True
|
|
||||||
self.props.hexpand = True
|
|
||||||
self.props.show_row_separators = False
|
|
||||||
self.props.show_column_separators = False
|
|
||||||
self.props.reorderable = False
|
|
||||||
|
|
||||||
self.__add_column("display-name")
|
|
||||||
self.single_selection.connect("notify::selected-item", self.__on_selected_item_notify)
|
|
||||||
self.set_model(self.single_selection)
|
|
||||||
|
|
||||||
gesture = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY)
|
|
||||||
gesture.connect("pressed", self.__on_button_press)
|
|
||||||
self.add_controller(gesture)
|
|
||||||
|
|
||||||
self.connect("activate", self.__on_activate)
|
|
||||||
|
|
||||||
def __add_column(self, property_id):
|
|
||||||
factory = Gtk.SignalListItemFactory()
|
|
||||||
factory.connect("setup", self._on_factory_setup)
|
|
||||||
factory.connect("bind", self._on_factory_bind, property_id)
|
|
||||||
factory.connect("unbind", self._on_factory_unbind)
|
|
||||||
|
|
||||||
column = Gtk.ColumnViewColumn(factory=factory, expand=True)
|
|
||||||
self.append_column(column)
|
|
||||||
|
|
||||||
# FIXME: Add api to Gtk to hide column title widget
|
|
||||||
column_view = column.get_column_view()
|
|
||||||
child = column_view.get_first_child()
|
|
||||||
if GObject.type_name(child.__gtype__) == "GtkColumnViewRowWidget":
|
|
||||||
child.set_visible(False)
|
|
||||||
|
|
||||||
def __on_button_press(self, gesture, npress, x, y):
|
|
||||||
expander = self.__get_tree_expander(x, y)
|
|
||||||
|
|
||||||
if expander is None or npress != 1:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Select row at x,y
|
|
||||||
list_row = expander.get_list_row()
|
|
||||||
self.single_selection.set_selected(list_row.get_position())
|
|
||||||
|
|
||||||
menu = CmbContextMenu()
|
|
||||||
|
|
||||||
if self.__project:
|
|
||||||
menu.target_tk = self.__project.target_tk
|
|
||||||
|
|
||||||
menu.set_parent(self)
|
|
||||||
menu.popup_at(x, y)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __get_object_ancestors(self, obj):
|
|
||||||
if isinstance(obj, CmbObject):
|
|
||||||
ancestors = {obj.ui}
|
|
||||||
parent = obj.parent
|
|
||||||
while parent:
|
|
||||||
ancestors.add(parent)
|
|
||||||
parent = parent.parent
|
|
||||||
|
|
||||||
return ancestors
|
|
||||||
|
|
||||||
# CmbUI and CmbCSS do not have ancestors
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def __object_ancestor_expand(self, obj):
|
|
||||||
ancestors = self.__get_object_ancestors(obj)
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
# Iterate over tree model
|
|
||||||
# NOTE: only visible/expanded rows are returned
|
|
||||||
list_row = self.__tree_model.get_row(i)
|
|
||||||
while list_row:
|
|
||||||
item = list_row.get_item()
|
|
||||||
|
|
||||||
# Return position if we reached the object row
|
|
||||||
if item == obj:
|
|
||||||
return i
|
|
||||||
elif item in ancestors:
|
|
||||||
# Expand row if its part of the hierarchy
|
|
||||||
list_row.set_expanded(True)
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
list_row = self.__tree_model.get_row(i)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __on_project_selection_changed(self, p):
|
|
||||||
list_row = self.single_selection.get_selected_item()
|
|
||||||
current_selection = [list_row.get_item()] if list_row else []
|
|
||||||
selection = self.__project.get_selection()
|
|
||||||
|
|
||||||
if selection == current_selection:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.__in_selection_change = True
|
|
||||||
|
|
||||||
if len(selection) > 0:
|
|
||||||
position = self.__object_ancestor_expand(selection[0])
|
|
||||||
if position is not None:
|
|
||||||
self.single_selection.select_item(position, True)
|
|
||||||
else:
|
|
||||||
self.single_selection.unselect_all()
|
|
||||||
else:
|
|
||||||
self.single_selection.unselect_all()
|
|
||||||
|
|
||||||
self.__in_selection_change = False
|
|
||||||
|
|
||||||
@GObject.Property(type=CmbProject)
|
|
||||||
def project(self):
|
|
||||||
return self.__project
|
|
||||||
|
|
||||||
@project.setter
|
|
||||||
def _set_project(self, project):
|
|
||||||
if self.__project:
|
|
||||||
self.__project.disconnect_by_func(self.__on_project_selection_changed)
|
|
||||||
|
|
||||||
self.__project = project
|
|
||||||
|
|
||||||
if project:
|
|
||||||
self.__tree_model = Gtk.TreeListModel.new(
|
|
||||||
project,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
self.__tree_model_create_func,
|
|
||||||
None
|
|
||||||
)
|
|
||||||
self.single_selection.props.model = self.__tree_model
|
|
||||||
self.__project.connect("selection-changed", self.__on_project_selection_changed)
|
|
||||||
else:
|
|
||||||
self.single_selection.props.model = None
|
|
||||||
|
|
||||||
def __tree_model_create_func(self, item, data):
|
|
||||||
if isinstance(item, CmbObject):
|
|
||||||
return item
|
|
||||||
elif isinstance(item, CmbUI):
|
|
||||||
return item
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __on_selected_item_notify(self, single_selection, pspec):
|
|
||||||
if self.__in_selection_change or self.__project is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
list_item = single_selection.get_selected_item()
|
|
||||||
|
|
||||||
if list_item:
|
|
||||||
item = list_item.get_item()
|
|
||||||
self.__project.set_selection([item])
|
|
||||||
else:
|
|
||||||
self.__project.set_selection([])
|
|
||||||
|
|
||||||
def __on_item_notify(self, item, pspec, label):
|
|
||||||
self.__update_label(item, label, pspec.name)
|
|
||||||
|
|
||||||
def __update_label(self, item, label, property_id):
|
|
||||||
val = str(item.get_property(property_id))
|
|
||||||
label.set_markup(val if val else "")
|
|
||||||
|
|
||||||
def _on_factory_setup(self, factory, list_item):
|
|
||||||
expander = Gtk.TreeExpander()
|
|
||||||
label = Gtk.Inscription(hexpand=True)
|
|
||||||
expander.set_child(label)
|
|
||||||
list_item.set_child(expander)
|
|
||||||
|
|
||||||
def __on_list_store_n_items_notify(self, list_store, pspec, expander):
|
|
||||||
expander.props.hide_expander = list_store.props.n_items == 0
|
|
||||||
|
|
||||||
def __drop_target_new(self):
|
|
||||||
drop_target = Gtk.DropTarget.new(
|
|
||||||
type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY
|
|
||||||
)
|
|
||||||
drop_target.set_gtypes([CmbObject, CmbUI])
|
|
||||||
|
|
||||||
return drop_target
|
|
||||||
|
|
||||||
def _on_factory_bind(self, factory, list_item, property_id):
|
|
||||||
row = list_item.get_item()
|
|
||||||
expander = list_item.get_child()
|
|
||||||
row_widget = expander.props.parent.props.parent
|
|
||||||
expander.set_list_row(row)
|
|
||||||
item = row.get_item()
|
|
||||||
label = expander.get_child()
|
|
||||||
|
|
||||||
# ensure drag&drop variables
|
|
||||||
row_widget._drop_target = None
|
|
||||||
row_widget._drag_source = None
|
|
||||||
expander._drop_target = None
|
|
||||||
|
|
||||||
# Handle label
|
|
||||||
self.__update_label(item, label, property_id)
|
|
||||||
item.connect(f"notify::{property_id}", self.__on_item_notify, label)
|
|
||||||
|
|
||||||
# Add controllers and drag sources
|
|
||||||
if isinstance(item, CmbObject):
|
|
||||||
# Drag source, only objects can be dragged
|
|
||||||
drag_source = Gtk.DragSource()
|
|
||||||
drag_source.connect("prepare", self.__on_drag_prepare)
|
|
||||||
drag_source.connect("drag-begin", self.__on_drag_begin)
|
|
||||||
row_widget.add_controller(drag_source)
|
|
||||||
row_widget._drag_source = drag_source
|
|
||||||
|
|
||||||
# Expander Drop target
|
|
||||||
drop_target = self.__drop_target_new()
|
|
||||||
drop_target.connect("accept", self.__on_expander_drop_accept)
|
|
||||||
drop_target.connect("drop", self.__on_expander_drop_drop)
|
|
||||||
expander.add_controller(drop_target)
|
|
||||||
expander._drop_target = drop_target
|
|
||||||
|
|
||||||
# Row Drop target
|
|
||||||
drop_target = self.__drop_target_new()
|
|
||||||
drop_target.connect("accept", self.__on_row_drop_accept)
|
|
||||||
drop_target.connect("motion", self.__on_row_drop_motion)
|
|
||||||
drop_target.connect("drop", self.__on_row_drop_drop)
|
|
||||||
row_widget.add_controller(drop_target)
|
|
||||||
row_widget._drop_target = drop_target
|
|
||||||
elif isinstance(item, CmbUI):
|
|
||||||
# Expander Drop target
|
|
||||||
drop_target = self.__drop_target_new()
|
|
||||||
drop_target.connect("accept", self.__on_ui_expander_drop_accept)
|
|
||||||
drop_target.connect("drop", self.__on_ui_expander_drop_drop)
|
|
||||||
expander.add_controller(drop_target)
|
|
||||||
expander._drop_target = drop_target
|
|
||||||
|
|
||||||
# Row Drop target
|
|
||||||
drop_target = self.__drop_target_new()
|
|
||||||
drop_target.connect("accept", self.__on_ui_row_drop_accept)
|
|
||||||
drop_target.connect("drop", self.__on_ui_row_drop_drop)
|
|
||||||
row_widget.add_controller(drop_target)
|
|
||||||
row_widget.__drop_target = drop_target
|
|
||||||
else:
|
|
||||||
expander.props.hide_expander = True
|
|
||||||
return
|
|
||||||
|
|
||||||
expander.props.hide_expander = item.props.n_items == 0
|
|
||||||
item.connect("notify::n-items", self.__on_list_store_n_items_notify, expander)
|
|
||||||
|
|
||||||
def _on_factory_unbind(self, factory, list_item):
|
|
||||||
row = list_item.get_item()
|
|
||||||
item = row.get_item()
|
|
||||||
expander = list_item.get_child()
|
|
||||||
|
|
||||||
item.disconnect_by_func(self.__on_item_notify)
|
|
||||||
|
|
||||||
if isinstance(item, CmbObject) or isinstance(item, CmbUI):
|
|
||||||
item.disconnect_by_func(self.__on_list_store_n_items_notify)
|
|
||||||
|
|
||||||
if expander is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Clear controllers
|
|
||||||
if expander._drop_target:
|
|
||||||
expander.remove_controller(expander._drop_target)
|
|
||||||
expander._drop_target = None
|
|
||||||
|
|
||||||
row_widget = expander.props.parent.props.parent
|
|
||||||
if row_widget:
|
|
||||||
if row_widget._drop_target:
|
|
||||||
row_widget.remove_controller(row_widget._drop_target)
|
|
||||||
row_widget._drop_target = None
|
|
||||||
if row_widget._drag_source:
|
|
||||||
row_widget.remove_controller(row_widget._drag_source)
|
|
||||||
row_widget._drag_source = None
|
|
||||||
|
|
||||||
def __get_item_from_target(self, target):
|
|
||||||
target_widget = target.get_widget()
|
|
||||||
|
|
||||||
if isinstance(target_widget, Gtk.TreeExpander):
|
|
||||||
expander = target_widget
|
|
||||||
else:
|
|
||||||
cell = target_widget.get_first_child()
|
|
||||||
expander = cell.get_first_child()
|
|
||||||
|
|
||||||
list_row = expander.get_list_row()
|
|
||||||
item = list_row.get_item()
|
|
||||||
|
|
||||||
return item
|
|
||||||
|
|
||||||
def __on_drag_prepare(self, drag_source, x, y):
|
|
||||||
item = self.__get_item_from_target(drag_source)
|
|
||||||
return Gdk.ContentProvider.new_for_value(item)
|
|
||||||
|
|
||||||
def __on_drag_begin(self, drag_source, drag):
|
|
||||||
expander = drag_source.get_widget().get_first_child().get_first_child()
|
|
||||||
drag._item = self.__get_item_from_target(drag_source)
|
|
||||||
drag_source.set_icon(Gtk.WidgetPaintable.new(expander.get_first_child()), 0, 0)
|
|
||||||
|
|
||||||
def __get_drop_before(self, widget, x, y):
|
|
||||||
return True if y < widget.get_height()/2 else False
|
|
||||||
|
|
||||||
def __ui_drop_accept(self, drop, item):
|
|
||||||
origin_item = drop.get_drag()._item
|
|
||||||
|
|
||||||
if origin_item == item:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Ignore if its the same UI and item is already a toplevel
|
|
||||||
if origin_item.ui_id == item.ui_id and origin_item.parent_id is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __on_ui_expander_drop_accept(self, target, drop):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
return self.__ui_drop_accept(drop, item)
|
|
||||||
|
|
||||||
def __on_ui_row_drop_accept(self, target, drop):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
return self.__ui_drop_accept(drop, item)
|
|
||||||
|
|
||||||
def __on_object_drop_accept(self, drop, item):
|
|
||||||
origin_item = drop.get_drag()._item
|
|
||||||
|
|
||||||
if origin_item == item:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not isinstance(item, CmbObject):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Ignore if its the same parent
|
|
||||||
if origin_item.parent_id == item.object_id:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return origin_item
|
|
||||||
|
|
||||||
def __on_expander_drop_accept(self, target, drop):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
origin_item = self.__on_object_drop_accept(drop, item)
|
|
||||||
|
|
||||||
if origin_item is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self.__project._check_can_add(origin_item.type_id, item.type_id)
|
|
||||||
|
|
||||||
def __on_row_drop_accept(self, target, drop):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
origin_item = self.__on_object_drop_accept(drop, item)
|
|
||||||
|
|
||||||
if origin_item is None or item.parent is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self.__project._check_can_add(origin_item.type_id, item.parent.type_id)
|
|
||||||
|
|
||||||
def __on_row_drop_motion(self, target, x, y):
|
|
||||||
row_widget = target.get_widget()
|
|
||||||
|
|
||||||
drop_before = self.__get_drop_before(row_widget, x, y)
|
|
||||||
|
|
||||||
row_widget.remove_css_class("drop-before")
|
|
||||||
row_widget.remove_css_class("drop-after")
|
|
||||||
|
|
||||||
if drop_before:
|
|
||||||
row_widget.add_css_class("drop-before")
|
|
||||||
else:
|
|
||||||
row_widget.add_css_class("drop-after")
|
|
||||||
|
|
||||||
return Gdk.DragAction.COPY
|
|
||||||
|
|
||||||
def __on_drop_drop(self, origin_item, item):
|
|
||||||
if not isinstance(item, CmbUI):
|
|
||||||
return
|
|
||||||
|
|
||||||
if origin_item.ui_id == item.ui_id:
|
|
||||||
self.__project.history_push(_("Move {name} as toplevel").format(name=origin_item.display_name))
|
|
||||||
origin_item.parent_id = 0
|
|
||||||
self.__project.history_pop()
|
|
||||||
else:
|
|
||||||
# TODO: Use copy/paste to move across UI files
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __on_ui_row_drop_drop(self, target, origin_item, x, y):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
self.__on_drop_drop(origin_item, item)
|
|
||||||
|
|
||||||
def __on_ui_expander_drop_drop(self, target, origin_item, x, y):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
self.__on_drop_drop(origin_item, item)
|
|
||||||
|
|
||||||
def __on_expander_drop_drop(self, target, origin_item, x, y):
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
|
|
||||||
if not isinstance(item, CmbObject):
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO: handle dragging from one UI to another
|
|
||||||
if origin_item.ui_id != item.ui_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if origin_item.parent_id != item.object_id:
|
|
||||||
self.__project.history_push(
|
|
||||||
_("Move {name} to {target}").format(name=origin_item.display_name, target=item.display_name)
|
|
||||||
)
|
|
||||||
origin_item.parent_id = item.object_id
|
|
||||||
self.__project.history_pop()
|
|
||||||
|
|
||||||
def __on_row_drop_drop(self, target, origin_item, x, y):
|
|
||||||
row_widget = target.get_widget()
|
|
||||||
item = self.__get_item_from_target(target)
|
|
||||||
|
|
||||||
drop_before = self.__get_drop_before(row_widget, x, y)
|
|
||||||
|
|
||||||
if not isinstance(item, CmbObject):
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO: handle dragging from one UI to another
|
|
||||||
if origin_item.ui_id != item.ui_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if origin_item.parent_id != item.parent_id:
|
|
||||||
if drop_before:
|
|
||||||
msg = _("Move {name} before {target}").format(name=origin_item.display_name, target=item.display_name)
|
|
||||||
else:
|
|
||||||
msg = _("Move {name} after {target}").format(name=origin_item.display_name, target=item.display_name)
|
|
||||||
|
|
||||||
self.__project.history_push(msg)
|
|
||||||
|
|
||||||
origin_item.parent_id = item.parent_id
|
|
||||||
else:
|
|
||||||
msg = None
|
|
||||||
|
|
||||||
parent = item.parent
|
|
||||||
|
|
||||||
origin_position = origin_item.position
|
|
||||||
target_position = item.position
|
|
||||||
|
|
||||||
if origin_position > target_position:
|
|
||||||
if drop_before:
|
|
||||||
position = item.position
|
|
||||||
else:
|
|
||||||
position = item.position + 1
|
|
||||||
else:
|
|
||||||
if drop_before:
|
|
||||||
position = item.position - 1
|
|
||||||
else:
|
|
||||||
position = item.position
|
|
||||||
|
|
||||||
parent.reorder_child(origin_item, position)
|
|
||||||
|
|
||||||
if msg:
|
|
||||||
self.__project.history_pop()
|
|
||||||
|
|
||||||
self.__project.set_selection([origin_item])
|
|
||||||
|
|
||||||
def __get_tree_expander(self, x, y):
|
|
||||||
pick = self.pick(x, y, Gtk.PickFlags.DEFAULT)
|
|
||||||
|
|
||||||
if pick is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(pick, Gtk.TreeExpander):
|
|
||||||
return pick
|
|
||||||
|
|
||||||
child = pick.get_first_child()
|
|
||||||
|
|
||||||
if child and isinstance(child, Gtk.TreeExpander):
|
|
||||||
return child
|
|
||||||
|
|
||||||
parent = pick.props.parent
|
|
||||||
if parent and isinstance(parent, Gtk.TreeExpander):
|
|
||||||
return parent
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __on_activate(self, column_view, position):
|
|
||||||
item = self.__tree_model.get_item(position)
|
|
||||||
item.set_expanded(not item.get_expanded())
|
|
||||||
|
|
||||||
def do_query_tooltip(self, x, y, keyboard_mode, tooltip):
|
|
||||||
expander = self.__get_tree_expander(x, y)
|
|
||||||
|
|
||||||
if expander is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
obj = expander.get_item()
|
|
||||||
|
|
||||||
if isinstance(obj, CmbObject):
|
|
||||||
msg = obj.version_warning
|
|
||||||
if msg:
|
|
||||||
tooltip.set_text(msg)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_context_menu.ui -->
|
<!-- interface-name cmb_context_menu.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
|
@ -24,7 +24,10 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from gi.repository import GObject, Gio
|
from gi.repository import GObject, Gio
|
||||||
|
|
||||||
|
from .cmb_path import CmbPath
|
||||||
from .cmb_objects_base import CmbBaseCSS
|
from .cmb_objects_base import CmbBaseCSS
|
||||||
from cambalache import _
|
from cambalache import _
|
||||||
|
|
||||||
@ -34,6 +37,7 @@ class CmbCSS(CmbBaseCSS):
|
|||||||
"file-changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
|
"file-changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||||
css = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
css = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@ -53,9 +57,13 @@ class CmbCSS(CmbBaseCSS):
|
|||||||
if pspec.name == "filename":
|
if pspec.name == "filename":
|
||||||
self.load_css()
|
self.load_css()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_display_name(cls, css_id, filename):
|
||||||
|
return os.path.basename(filename) if filename else _("Unnamed CSS {css_id}").format(css_id=css_id)
|
||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
return self.filename if self.filename else _("Unnamed CSS {css_id}").format(css_id=self.css_id)
|
return CmbCSS.get_display_name(self.css_id, self.filename)
|
||||||
|
|
||||||
@GObject.Property(type=int)
|
@GObject.Property(type=int)
|
||||||
def priority(self):
|
def priority(self):
|
||||||
|
@ -81,6 +81,7 @@ class CmbCSSEditor(Gtk.Grid):
|
|||||||
self.set_sensitive(False)
|
self.set_sensitive(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.filename.dirname = obj.project.dirname
|
||||||
self.set_sensitive(True)
|
self.set_sensitive(True)
|
||||||
|
|
||||||
for field, target in self.fields:
|
for field, target in self.fields:
|
||||||
@ -107,10 +108,6 @@ class CmbCSSEditor(Gtk.Grid):
|
|||||||
self.__update_provider_for()
|
self.__update_provider_for()
|
||||||
self.__update_ui_button_label()
|
self.__update_ui_button_label()
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_remove_button_clicked")
|
|
||||||
def __on_remove_button_clicked(self, button):
|
|
||||||
self.emit("remove-css")
|
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_save_button_clicked")
|
@Gtk.Template.Callback("on_save_button_clicked")
|
||||||
def __on_save_button_clicked(self, button):
|
def __on_save_button_clicked(self, button):
|
||||||
self._object.save_css()
|
self._object.save_css()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_css_editor.ui -->
|
<!-- interface-name cmb_css_editor.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -39,11 +39,8 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="CmbEntry" id="filename">
|
<object class="CmbFileButton" id="filename">
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="placeholder-text" translatable="yes"><file name relative to project></property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">0</property>
|
<property name="row">0</property>
|
||||||
@ -111,20 +108,6 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="spacing">4</property>
|
<property name="spacing">4</property>
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="remove_button">
|
|
||||||
<property name="focusable">1</property>
|
|
||||||
<!-- <property name="tooltip-text" translatable="1">Remove CSS file from project</property> -->
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="valign">end</property>
|
|
||||||
<signal name="clicked" handler="on_remove_button_clicked"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="icon-name">app-remove-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
|
1134
cambalache/cmb_db.py
1134
cambalache/cmb_db.py
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
|
<!-- interface-name cmb_db_inspector.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
<requires lib="gtk" version="4.0"/>
|
<requires lib="gtk" version="4.0"/>
|
||||||
<template class="CmbDBInspector" parent="GtkBox">
|
<template class="CmbDBInspector" parent="GtkBox">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_fragment_editor.ui -->
|
<!-- interface-name cmb_fragment_editor.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
|
151
cambalache/cmb_gresource.py
Normal file
151
cambalache/cmb_gresource.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#
|
||||||
|
# Cambalache GResource wrapper
|
||||||
|
#
|
||||||
|
# Copyright (C) 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, Gio
|
||||||
|
|
||||||
|
from .cmb_path import CmbPath
|
||||||
|
from .cmb_objects_base import CmbBaseGResource
|
||||||
|
from .cmb_list_error import CmbListError
|
||||||
|
from cambalache import _
|
||||||
|
|
||||||
|
|
||||||
|
class CmbGResource(CmbBaseGResource, Gio.ListModel):
|
||||||
|
|
||||||
|
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self._last_known = None
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.connect("notify", self.__on_notify)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"CmbGResource<{self.resource_type}> id={self.gresource_id}"
|
||||||
|
|
||||||
|
def __on_notify(self, obj, pspec):
|
||||||
|
resource_type = self.resource_type
|
||||||
|
|
||||||
|
if (resource_type == "gresources" and pspec.name == "gresources-filename") or \
|
||||||
|
(resource_type == "gresource" and pspec.name == "gresource-prefix") or \
|
||||||
|
(resource_type == "file" and pspec.name == "file-filename"):
|
||||||
|
obj.notify("display-name")
|
||||||
|
|
||||||
|
self.project._gresource_changed(self, pspec.name)
|
||||||
|
|
||||||
|
@GObject.Property(type=CmbBaseGResource)
|
||||||
|
def parent(self):
|
||||||
|
if self.resource_type in ["gresource", "file"]:
|
||||||
|
return self.project.get_gresource_by_id(self.parent_id)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@GObject.Property(type=CmbBaseGResource)
|
||||||
|
def gresources_bundle(self):
|
||||||
|
resource_type = self.resource_type
|
||||||
|
if resource_type == "gresource":
|
||||||
|
return self.parent
|
||||||
|
elif resource_type == "file":
|
||||||
|
return self.parent.parent
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def display_name(self):
|
||||||
|
resource_type = self.resource_type
|
||||||
|
|
||||||
|
if resource_type == "gresources":
|
||||||
|
gresources_filename = self.gresources_filename
|
||||||
|
if gresources_filename:
|
||||||
|
basename, relpath = self.project._get_basename_relpath(self.gresources_filename)
|
||||||
|
return basename
|
||||||
|
|
||||||
|
return _("Unnamed GResource {id}").format(id=self.gresource_id)
|
||||||
|
elif resource_type == "gresource":
|
||||||
|
gresource_prefix = self.gresource_prefix
|
||||||
|
return gresource_prefix if gresource_prefix else _("Unprefixed resource {id}").format(id=self.gresource_id)
|
||||||
|
elif resource_type == "file":
|
||||||
|
file_filename = self.file_filename
|
||||||
|
return file_filename if file_filename else _("Unnamed file {id}").format(id=self.gresource_id)
|
||||||
|
|
||||||
|
# GListModel helpers
|
||||||
|
def _save_last_known_parent_and_position(self):
|
||||||
|
self._last_known = (self.parent, self.position)
|
||||||
|
|
||||||
|
def _update_new_parent(self):
|
||||||
|
parent = self.parent
|
||||||
|
position = self.position
|
||||||
|
|
||||||
|
# Emit GListModel signal to update model
|
||||||
|
if parent:
|
||||||
|
parent.items_changed(position, 0, 1)
|
||||||
|
parent.notify("n-items")
|
||||||
|
|
||||||
|
self._last_known = None
|
||||||
|
|
||||||
|
def _remove_from_old_parent(self):
|
||||||
|
if self._last_known is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
parent, position = self._last_known
|
||||||
|
|
||||||
|
# Emit GListModel signal to update model
|
||||||
|
if parent:
|
||||||
|
parent.items_changed(position, 1, 0)
|
||||||
|
parent.notify("n-items")
|
||||||
|
|
||||||
|
self._last_known = None
|
||||||
|
|
||||||
|
# GListModel iface
|
||||||
|
def do_get_item(self, position):
|
||||||
|
gresource_id = self.gresource_id
|
||||||
|
key = self.db_get(
|
||||||
|
"SELECT gresource_id FROM gresource WHERE parent_id=? AND position=?;",
|
||||||
|
(gresource_id, position)
|
||||||
|
)
|
||||||
|
|
||||||
|
if key is not None:
|
||||||
|
return self.project.get_gresource_by_id(key)
|
||||||
|
|
||||||
|
# This should not happen
|
||||||
|
return CmbListError()
|
||||||
|
|
||||||
|
def do_get_item_type(self):
|
||||||
|
return CmbBaseGResource
|
||||||
|
|
||||||
|
@GObject.Property(type=int)
|
||||||
|
def n_items(self):
|
||||||
|
if self.resource_type in ["gresources", "gresource"]:
|
||||||
|
retval = self.db_get("SELECT COUNT(gresource_id) FROM gresource WHERE parent_id=?;", (self.gresource_id, ))
|
||||||
|
return retval if retval is not None else 0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def do_get_n_items(self):
|
||||||
|
return self.n_items
|
||||||
|
|
117
cambalache/cmb_gresource_editor.py
Normal file
117
cambalache/cmb_gresource_editor.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#
|
||||||
|
# CmbGResourceEditor - Cambalache GResource Editor
|
||||||
|
#
|
||||||
|
# Copyright (C) 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_gresource import CmbGResource
|
||||||
|
from cambalache import getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_gresource_editor.ui")
|
||||||
|
class CmbGResourceEditor(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbGResourceEditor"
|
||||||
|
|
||||||
|
stack = Gtk.Template.Child()
|
||||||
|
|
||||||
|
gresources_filename = Gtk.Template.Child()
|
||||||
|
gresource_prefix = Gtk.Template.Child()
|
||||||
|
file_filename = Gtk.Template.Child()
|
||||||
|
file_compressed = Gtk.Template.Child()
|
||||||
|
file_preprocess = Gtk.Template.Child()
|
||||||
|
file_alias = Gtk.Template.Child()
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
("gresources", "gresources_filename", "cmb-value"),
|
||||||
|
("gresource", "gresource_prefix", "cmb-value"),
|
||||||
|
("file", "file_filename", "cmb-value"),
|
||||||
|
("file", "file_compressed", "active"),
|
||||||
|
("file", "file_preprocess", "cmb-value"),
|
||||||
|
("file", "file_alias", "cmb-value"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self._object = None
|
||||||
|
self._bindings = []
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@GObject.Property(type=CmbGResource)
|
||||||
|
def object(self):
|
||||||
|
return self._object
|
||||||
|
|
||||||
|
@object.setter
|
||||||
|
def _set_object(self, obj):
|
||||||
|
if obj == self._object:
|
||||||
|
return
|
||||||
|
|
||||||
|
for binding in self._bindings:
|
||||||
|
binding.unbind()
|
||||||
|
|
||||||
|
self._bindings = []
|
||||||
|
self._object = obj
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
self.set_sensitive(False)
|
||||||
|
for for_type, field, target in self.fields:
|
||||||
|
widget = getattr(self, field)
|
||||||
|
target_prop = getattr(widget, target)
|
||||||
|
|
||||||
|
if isinstance(target_prop, int):
|
||||||
|
setattr(widget, target, 0)
|
||||||
|
else:
|
||||||
|
setattr(widget, target, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
resource_type = obj.resource_type
|
||||||
|
self.set_sensitive(True)
|
||||||
|
self.stack.set_visible_child_name(resource_type)
|
||||||
|
self.gresources_filename.dirname = obj.project.dirname
|
||||||
|
self.file_filename.dirname = obj.project.dirname
|
||||||
|
|
||||||
|
for for_type, field, target in self.fields:
|
||||||
|
if resource_type != for_type:
|
||||||
|
continue
|
||||||
|
|
||||||
|
binding = GObject.Object.bind_property(
|
||||||
|
obj,
|
||||||
|
field,
|
||||||
|
getattr(self, field),
|
||||||
|
target,
|
||||||
|
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||||
|
)
|
||||||
|
self._bindings.append(binding)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_add_gresource_button_clicked")
|
||||||
|
def __on_add_gresource_button_clicked(self, button):
|
||||||
|
self._object.project.add_gresource("gresource", parent_id=self._object.gresource_id)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_add_file_button_clicked")
|
||||||
|
def __on_add_file_button_clicked(self, button):
|
||||||
|
self._object.project.add_gresource("file", parent_id=self._object.gresource_id)
|
||||||
|
|
||||||
|
|
||||||
|
Gtk.WidgetClass.set_css_name(CmbGResourceEditor, "CmbGResourceEditor")
|
241
cambalache/cmb_gresource_editor.ui
Normal file
241
cambalache/cmb_gresource_editor.ui
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_gresource_editor.ui -->
|
||||||
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
|
<requires lib="gtk" version="4.12"/>
|
||||||
|
<template class="CmbGResourceEditor" parent="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkStack" id="stack">
|
||||||
|
<property name="transition-type">crossfade</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">Filename</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbFileButton" id="gresources_filename">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="label" translatable="yes">Add</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="column-span">1</property>
|
||||||
|
<property name="row">1</property>
|
||||||
|
<property name="row-span">1</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label" translatable="yes">* This resource file need to be compiled and loaded at runtime</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="xalign">0.0</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="column-span">2</property>
|
||||||
|
<property name="row">2</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_gresource_button">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label">GResource</property>
|
||||||
|
<signal name="clicked" handler="on_add_gresource_button_clicked"/>
|
||||||
|
<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>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">gresources</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Prefix</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbEntry" id="gresource_prefix">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label" translatable="yes">* Files defined inside this will be available at gresource://prefix</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="xalign">0.0</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="column-span">2</property>
|
||||||
|
<property name="row">2</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label" translatable="yes">Add</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="column-span">1</property>
|
||||||
|
<property name="row">1</property>
|
||||||
|
<property name="row-span">1</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="add_file_button">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">file</property>
|
||||||
|
<signal name="clicked" handler="on_add_file_button_clicked"/>
|
||||||
|
<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>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">gresource</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkStackPage">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Filename</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Compressed</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">Preprocess</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">Alias</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">0</property>
|
||||||
|
<property name="row">3</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="file_compressed">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="row">1</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbEntry" id="file_preprocess">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="row">2</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbEntry" id="file_alias">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="row">3</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="CmbFileButton" id="file_filename">
|
||||||
|
<property name="use-open">True</property>
|
||||||
|
<layout>
|
||||||
|
<property name="column">1</property>
|
||||||
|
<property name="column-span">1</property>
|
||||||
|
<property name="row">0</property>
|
||||||
|
<property name="row-span">1</property>
|
||||||
|
</layout>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="name">file</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -25,7 +25,8 @@
|
|||||||
|
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
|
||||||
from .cmb_objects_base import CmbBaseLayoutProperty, CmbPropertyInfo
|
from .cmb_objects_base import CmbBaseLayoutProperty
|
||||||
|
from .cmb_property_info import CmbPropertyInfo
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
@ -34,9 +35,7 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
|||||||
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__on_init = True
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.__on_init = False
|
|
||||||
self.version_warning = None
|
self.version_warning = None
|
||||||
|
|
||||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||||
@ -67,11 +66,6 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
|||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def _set_value(self, value):
|
def _set_value(self, value):
|
||||||
# Update object position if this is a position property
|
|
||||||
if self.info.is_position and not self.__on_init:
|
|
||||||
self.object.parent.reorder_child(self.object, int(value) if value else 0)
|
|
||||||
return
|
|
||||||
|
|
||||||
c = self.project.db.cursor()
|
c = self.project.db.cursor()
|
||||||
|
|
||||||
if value is None or value == self.info.default_value:
|
if value is None or value == self.info.default_value:
|
||||||
@ -112,9 +106,6 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
|||||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value),
|
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.__on_init:
|
|
||||||
self.object._layout_property_changed(self)
|
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
def _update_version_warning(self):
|
def _update_version_warning(self):
|
||||||
|
@ -36,3 +36,7 @@ class CmbListError(CmbBase):
|
|||||||
def display_name(self):
|
def display_name(self):
|
||||||
return "list error"
|
return "list error"
|
||||||
|
|
||||||
|
@GObject.Property(type=int)
|
||||||
|
def n_items(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
260
cambalache/cmb_list_view.py
Normal file
260
cambalache/cmb_list_view.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
#
|
||||||
|
# CmbColumnView
|
||||||
|
#
|
||||||
|
# Copyright (C) 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 GLib, GObject, Gdk, Gtk
|
||||||
|
|
||||||
|
from .cmb_ui import CmbUI
|
||||||
|
from .cmb_object import CmbObject
|
||||||
|
from .cmb_gresource import CmbGResource
|
||||||
|
from .cmb_context_menu import CmbContextMenu
|
||||||
|
from .cmb_path import CmbPath
|
||||||
|
from .cmb_project import CmbProject
|
||||||
|
from .cmb_tree_expander import CmbTreeExpander
|
||||||
|
|
||||||
|
|
||||||
|
class CmbListView(Gtk.ListView):
|
||||||
|
__gtype_name__ = "CmbListView"
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__project = None
|
||||||
|
self.__tree_model = None
|
||||||
|
self.__in_selection_change = False
|
||||||
|
self.single_selection = Gtk.SingleSelection()
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.props.has_tooltip = True
|
||||||
|
self.props.hexpand = True
|
||||||
|
|
||||||
|
factory = Gtk.SignalListItemFactory()
|
||||||
|
factory.connect("setup", self._on_factory_setup)
|
||||||
|
factory.connect("bind", self._on_factory_bind)
|
||||||
|
factory.connect("unbind", self._on_factory_unbind)
|
||||||
|
self.props.factory = factory
|
||||||
|
|
||||||
|
self.single_selection.connect("notify::selected-item", self.__on_selected_item_notify)
|
||||||
|
self.set_model(self.single_selection)
|
||||||
|
|
||||||
|
gesture = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY)
|
||||||
|
gesture.connect("pressed", self.__on_button_press)
|
||||||
|
self.add_controller(gesture)
|
||||||
|
|
||||||
|
self.connect("activate", self.__on_activate)
|
||||||
|
|
||||||
|
self.add_css_class("navigation-sidebar")
|
||||||
|
self.add_css_class("cmb-list-view")
|
||||||
|
|
||||||
|
def __on_button_press(self, gesture, npress, x, y):
|
||||||
|
expander = self.__get_tree_expander(x, y)
|
||||||
|
|
||||||
|
if expander is None or npress != 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Select row at x,y
|
||||||
|
list_row = expander.get_list_row()
|
||||||
|
self.single_selection.set_selected(list_row.get_position())
|
||||||
|
|
||||||
|
menu = CmbContextMenu()
|
||||||
|
|
||||||
|
if self.__project:
|
||||||
|
menu.target_tk = self.__project.target_tk
|
||||||
|
|
||||||
|
menu.set_parent(self)
|
||||||
|
menu.popup_at(x, y)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __get_path_parent(self, obj):
|
||||||
|
if isinstance(obj, CmbObject):
|
||||||
|
parent = obj.parent
|
||||||
|
return parent if parent else obj.ui
|
||||||
|
elif isinstance(obj, CmbGResource):
|
||||||
|
return obj.path_parent if obj.resource_type == "gresources" else obj.parent
|
||||||
|
elif isinstance(obj, CmbProject):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.path_parent
|
||||||
|
|
||||||
|
def __get_object_ancestors(self, obj):
|
||||||
|
ancestors = set()
|
||||||
|
|
||||||
|
parent = self.__get_path_parent(obj)
|
||||||
|
while parent:
|
||||||
|
ancestors.add(parent)
|
||||||
|
parent = self.__get_path_parent(parent)
|
||||||
|
|
||||||
|
return ancestors
|
||||||
|
|
||||||
|
def __object_ancestor_expand(self, obj):
|
||||||
|
ancestors = self.__get_object_ancestors(obj)
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# Iterate over tree model
|
||||||
|
# NOTE: only visible/expanded rows are returned
|
||||||
|
list_row = self.__tree_model.get_row(i)
|
||||||
|
while list_row:
|
||||||
|
item = list_row.get_item()
|
||||||
|
|
||||||
|
# Return position if we reached the object row
|
||||||
|
if item == obj:
|
||||||
|
return i
|
||||||
|
elif item in ancestors:
|
||||||
|
# Expand row if its part of the hierarchy
|
||||||
|
list_row.set_expanded(True)
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
list_row = self.__tree_model.get_row(i)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __on_project_selection_changed(self, p):
|
||||||
|
list_row = self.single_selection.get_selected_item()
|
||||||
|
current_selection = [list_row.get_item()] if list_row else []
|
||||||
|
selection = self.__project.get_selection()
|
||||||
|
|
||||||
|
if selection == current_selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__in_selection_change = True
|
||||||
|
|
||||||
|
if len(selection) > 0:
|
||||||
|
position = self.__object_ancestor_expand(selection[0])
|
||||||
|
if position is not None:
|
||||||
|
self.single_selection.select_item(position, True)
|
||||||
|
else:
|
||||||
|
self.single_selection.unselect_all()
|
||||||
|
else:
|
||||||
|
self.single_selection.unselect_all()
|
||||||
|
|
||||||
|
self.__in_selection_change = False
|
||||||
|
|
||||||
|
@GObject.Property(type=CmbProject)
|
||||||
|
def project(self):
|
||||||
|
return self.__project
|
||||||
|
|
||||||
|
@project.setter
|
||||||
|
def _set_project(self, project):
|
||||||
|
if self.__project:
|
||||||
|
self.__project.disconnect_by_func(self.__on_project_selection_changed)
|
||||||
|
|
||||||
|
self.__project = project
|
||||||
|
|
||||||
|
if project:
|
||||||
|
self.__tree_model = Gtk.TreeListModel.new(
|
||||||
|
project,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
self.__tree_model_create_func,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
self.single_selection.props.model = self.__tree_model
|
||||||
|
self.__project.connect("selection-changed", self.__on_project_selection_changed)
|
||||||
|
else:
|
||||||
|
self.single_selection.props.model = None
|
||||||
|
|
||||||
|
def __tree_model_create_func(self, item, data):
|
||||||
|
if isinstance(item, CmbObject):
|
||||||
|
return item
|
||||||
|
elif isinstance(item, CmbUI):
|
||||||
|
return item
|
||||||
|
elif isinstance(item, CmbGResource):
|
||||||
|
return item
|
||||||
|
elif isinstance(item, CmbPath):
|
||||||
|
return item
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __on_selected_item_notify(self, single_selection, pspec):
|
||||||
|
if self.__in_selection_change or self.__project is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
list_item = single_selection.get_selected_item()
|
||||||
|
position = single_selection.get_selected()
|
||||||
|
|
||||||
|
if list_item is None:
|
||||||
|
self.__project.set_selection([])
|
||||||
|
return
|
||||||
|
|
||||||
|
item = list_item.get_item()
|
||||||
|
self.activate_action("list.activate-item", GLib.Variant("u", position))
|
||||||
|
|
||||||
|
if item and not isinstance(item, CmbPath):
|
||||||
|
item = list_item.get_item()
|
||||||
|
self.__project.set_selection([item])
|
||||||
|
else:
|
||||||
|
self.__project.set_selection([])
|
||||||
|
|
||||||
|
def _on_factory_setup(self, factory, list_item):
|
||||||
|
expander = CmbTreeExpander()
|
||||||
|
list_item.set_child(expander)
|
||||||
|
|
||||||
|
def _on_factory_bind(self, factory, list_item):
|
||||||
|
row = list_item.get_item()
|
||||||
|
expander = list_item.get_child()
|
||||||
|
expander.set_list_row(row)
|
||||||
|
expander.update_bind()
|
||||||
|
|
||||||
|
def _on_factory_unbind(self, factory, list_item):
|
||||||
|
expander = list_item.get_child()
|
||||||
|
expander.clear_bind()
|
||||||
|
|
||||||
|
def __get_tree_expander(self, x, y):
|
||||||
|
pick = self.pick(x, y, Gtk.PickFlags.DEFAULT)
|
||||||
|
|
||||||
|
if pick is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(pick, Gtk.TreeExpander):
|
||||||
|
return pick
|
||||||
|
|
||||||
|
child = pick.get_first_child()
|
||||||
|
|
||||||
|
if child and isinstance(child, Gtk.TreeExpander):
|
||||||
|
return child
|
||||||
|
|
||||||
|
parent = pick.props.parent
|
||||||
|
if parent and isinstance(parent, Gtk.TreeExpander):
|
||||||
|
return parent
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __on_activate(self, column_view, position):
|
||||||
|
item = self.__tree_model.get_item(position)
|
||||||
|
item.set_expanded(not item.get_expanded())
|
||||||
|
|
||||||
|
def do_query_tooltip(self, x, y, keyboard_mode, tooltip):
|
||||||
|
expander = self.__get_tree_expander(x, y)
|
||||||
|
|
||||||
|
if expander is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
obj = expander.get_item()
|
||||||
|
|
||||||
|
if isinstance(obj, CmbObject):
|
||||||
|
msg = obj.version_warning
|
||||||
|
if msg:
|
||||||
|
tooltip.set_text(msg)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
@ -1,7 +1,7 @@
|
|||||||
#
|
#
|
||||||
# CmbListStore - Cambalache List Store
|
# CmbMessageNotificationView
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
# Copyright (C) 2025 Juan Pablo Ugarte
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
@ -23,26 +23,28 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from cambalache import getLogger
|
||||||
from gi.repository import GObject, Gtk
|
from gi.repository import GObject, Gtk
|
||||||
|
from .cmb_notification import CmbMessageNotification
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CmbListStore(Gtk.ListStore):
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_message_notification_view.ui")
|
||||||
__gtype_name__ = "CmbListStore"
|
class CmbMessageNotificationView(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbMessageNotificationView"
|
||||||
|
|
||||||
table = GObject.Property(type=str)
|
notification = GObject.Property(
|
||||||
query = GObject.Property(type=str)
|
type=CmbMessageNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||||
project = GObject.Property(type=GObject.GObject)
|
)
|
||||||
|
|
||||||
|
# Message
|
||||||
|
title_label = Gtk.Template.Child()
|
||||||
|
message_label = Gtk.Template.Child()
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
data = self.project._get_table_data(self.table)
|
notification = self.notification
|
||||||
self.set_column_types(data["types"])
|
self.title_label.props.label = f"<b>{notification.title}</b>"
|
||||||
self.__populate()
|
self.message_label.props.label = notification.message
|
||||||
|
|
||||||
def __populate(self):
|
|
||||||
c = self.project.db.cursor()
|
|
||||||
for row in c.execute(self.query):
|
|
||||||
self.append(row)
|
|
||||||
|
|
||||||
c.close()
|
|
22
cambalache/cmb_message_notification_view.ui
Normal file
22
cambalache/cmb_message_notification_view.ui
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_message_notification_view.ui -->
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="CmbMessageNotificationView" parent="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="title_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="message_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
378
cambalache/cmb_notification.py
Normal file
378
cambalache/cmb_notification.py
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
#
|
||||||
|
# Cambalache notification system
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import http.client
|
||||||
|
import time
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from .config import VERSION
|
||||||
|
from gi.repository import GObject, GLib, Gio, Gdk, Gtk, Adw, HarfBuzz
|
||||||
|
from cambalache import getLogger
|
||||||
|
from . import utils
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBaseData(GObject.GObject):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for prop in self.list_properties():
|
||||||
|
name = prop.name.replace("-", "_")
|
||||||
|
|
||||||
|
if name not in kwargs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
value = kwargs[name]
|
||||||
|
|
||||||
|
if isinstance(value, dict) and prop.value_type in GTYPE_PTYHON:
|
||||||
|
Klass = GTYPE_PTYHON[prop.value_type]
|
||||||
|
kwargs[name] = Klass(**value)
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
retval = {}
|
||||||
|
|
||||||
|
for prop in self.list_properties():
|
||||||
|
name = prop.name.replace("-", "_")
|
||||||
|
|
||||||
|
value = self.get_property(prop.name)
|
||||||
|
|
||||||
|
if prop.value_type in GTYPE_PTYHON:
|
||||||
|
retval[name] = value.dict() if value else None
|
||||||
|
elif not isinstance(value, GObject.Object):
|
||||||
|
retval[name] = value
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
class CmbPollData(CmbBaseData):
|
||||||
|
id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
title = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
description = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
options = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
allowed_votes = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
start_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
end_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbPollResult(CmbBaseData):
|
||||||
|
votes = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
total = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
GTYPE_PTYHON = {CmbPollData.__gtype__: CmbPollData, CmbPollResult.__gtype__: CmbPollResult}
|
||||||
|
|
||||||
|
|
||||||
|
class CmbNotification(CmbBaseData):
|
||||||
|
center = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
|
type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
start_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
end_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbVersionNotification(CmbNotification):
|
||||||
|
version = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
release_notes = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
read_more_url = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbMessageNotification(CmbNotification):
|
||||||
|
title = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
message = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbPollNotification(CmbNotification):
|
||||||
|
poll = GObject.Property(type=CmbPollData, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
results = GObject.Property(type=CmbPollResult, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
my_votes = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbNotificationCenter(GObject.GObject):
|
||||||
|
__gsignals__ = {
|
||||||
|
"new-notification": (GObject.SignalFlags.RUN_FIRST, None, (CmbNotification,)),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
enabled = GObject.Property(type=bool, default=True, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
uuid = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
next_request = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
notifications = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
store = GObject.Property(type=Gio.ListStore, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.retry_interval = 2
|
||||||
|
self.user_agent = self.__get_user_agent()
|
||||||
|
self.store = Gio.ListStore(item_type=CmbNotification)
|
||||||
|
self.settings = Gio.Settings(schema_id="ar.xjuan.Cambalache.notification")
|
||||||
|
|
||||||
|
for prop in ["enabled", "uuid", "next-request", "notifications"]:
|
||||||
|
self.settings.bind(prop, self, prop.replace("-", "_"), Gio.SettingsBindFlags.DEFAULT)
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
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":
|
||||||
|
logger.info(f"Backend: {backend.scheme}://{backend.hostname}:{backend.port}")
|
||||||
|
self.connection = http.client.HTTPSConnection(backend.hostname, backend.port, timeout=8)
|
||||||
|
else:
|
||||||
|
self.connection = None
|
||||||
|
logger.warning(f"{backend.scheme} is not supported, only HTTPS")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Ensure we have a UUID
|
||||||
|
if not self.uuid:
|
||||||
|
self.uuid = str(uuid4())
|
||||||
|
|
||||||
|
logger.info(f"User Agent: {self.user_agent}")
|
||||||
|
logger.info(f"UUID: {self.uuid}")
|
||||||
|
|
||||||
|
self._get_notification()
|
||||||
|
|
||||||
|
def __get_container(self):
|
||||||
|
if "FLATPAK_ID" in os.environ:
|
||||||
|
return "flatpak"
|
||||||
|
elif "APPIMAGE" in os.environ:
|
||||||
|
return "appimage"
|
||||||
|
elif "SNAP" in os.environ:
|
||||||
|
return "snap"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __get_user_agent(self):
|
||||||
|
u = platform.uname()
|
||||||
|
platform_strings = []
|
||||||
|
table = str.maketrans({",": "\\,"})
|
||||||
|
|
||||||
|
if u.system == "Linux":
|
||||||
|
release = platform.freedesktop_os_release()
|
||||||
|
system = f"Linux {release['ID']}"
|
||||||
|
|
||||||
|
if "VERSION_ID" in release:
|
||||||
|
system += f" {release['VERSION_ID']}"
|
||||||
|
if "VERSION_CODENAME" in release:
|
||||||
|
system += f" {release['VERSION_CODENAME']}"
|
||||||
|
else:
|
||||||
|
system = u.system
|
||||||
|
|
||||||
|
display_type = GObject.type_name(Gdk.Display.get_default())
|
||||||
|
backend = display_type.removeprefix("Gdk").removesuffix("Display")
|
||||||
|
lang = HarfBuzz.language_to_string(HarfBuzz.language_get_default())
|
||||||
|
extra = []
|
||||||
|
|
||||||
|
# Container type
|
||||||
|
container = self.__get_container()
|
||||||
|
if container:
|
||||||
|
extra.append(f"container {container}")
|
||||||
|
|
||||||
|
# GSettings backend
|
||||||
|
settings_backend = Gio.SettingsBackend.get_default()
|
||||||
|
gsettings_backend = GObject.type_name(settings_backend).removesuffix("SettingsBackend")
|
||||||
|
|
||||||
|
for name, lib in [("GLib", GLib), ("Gtk", Gtk), ("Adw", Adw)]:
|
||||||
|
extra.append(f"{name} {lib.MAJOR_VERSION}.{lib.MINOR_VERSION}.{lib.MICRO_VERSION}")
|
||||||
|
|
||||||
|
# Ignore node name as that is private and irrelevant information
|
||||||
|
for string in [system, u.release, u.version, u.machine, backend, gsettings_backend]:
|
||||||
|
if not string:
|
||||||
|
continue
|
||||||
|
platform_strings.append(string.translate(table))
|
||||||
|
|
||||||
|
return f"Cambalache/{VERSION} ({', '.join(platform_strings)}; {'; '.join(extra)}; {lang})"
|
||||||
|
|
||||||
|
def __load_notifications(self):
|
||||||
|
self.store.remove_all()
|
||||||
|
|
||||||
|
if not self.notifications:
|
||||||
|
return
|
||||||
|
|
||||||
|
notifications = json.loads(self.notifications)
|
||||||
|
now = utils.utcnow()
|
||||||
|
|
||||||
|
for data in notifications:
|
||||||
|
if "end_date" in data and now > data["end_date"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.store.append(self.__notification_from_dict(data))
|
||||||
|
|
||||||
|
def __save_notifications(self):
|
||||||
|
notifications = []
|
||||||
|
|
||||||
|
for notification in self.store:
|
||||||
|
notifications.append(notification.dict())
|
||||||
|
|
||||||
|
# Store in GSettings
|
||||||
|
self.notifications = json.dumps(notifications, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
def __notification_from_dict(self, data):
|
||||||
|
ntype = data.get("type", None)
|
||||||
|
|
||||||
|
if ntype == "version":
|
||||||
|
return CmbVersionNotification(center=self, **data)
|
||||||
|
elif ntype == "message":
|
||||||
|
return CmbMessageNotification(center=self, **data)
|
||||||
|
elif ntype == "poll":
|
||||||
|
return CmbPollNotification(center=self, **data)
|
||||||
|
|
||||||
|
def __get_notification_idle(self, data):
|
||||||
|
logger.debug(f"Got notification response {json.dumps(data, indent=2, sort_keys=True)}")
|
||||||
|
|
||||||
|
if "notification" in data:
|
||||||
|
notification = self.__notification_from_dict(data["notification"])
|
||||||
|
self.store.insert(0, notification)
|
||||||
|
self.__save_notifications()
|
||||||
|
self.emit("new-notification", notification)
|
||||||
|
|
||||||
|
now = int(time.time())
|
||||||
|
self.next_request = now + self.REQUEST_INTERVAL
|
||||||
|
self._get_notification()
|
||||||
|
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
def __get_notification_thread(self):
|
||||||
|
headers = {
|
||||||
|
"User-Agent": self.user_agent,
|
||||||
|
"x-cambalache-uuid": self.uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"GET /notification {headers=}")
|
||||||
|
|
||||||
|
self.connection.request("GET", "/notification", headers=headers)
|
||||||
|
response = self.connection.getresponse()
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
# Reset retry interval
|
||||||
|
self.retry_interval = 8
|
||||||
|
|
||||||
|
data = response.read().decode()
|
||||||
|
|
||||||
|
logger.info(f"response={data}")
|
||||||
|
|
||||||
|
if data:
|
||||||
|
GLib.idle_add(self.__get_notification_idle, json.loads(data))
|
||||||
|
except Exception as e:
|
||||||
|
# If it fails we just wait a bit before retrying
|
||||||
|
self.retry_interval *= 2
|
||||||
|
self.retry_interval = min(self.retry_interval, 256)
|
||||||
|
|
||||||
|
logger.info(f"Request error {e}, retrying in {self.retry_interval}s")
|
||||||
|
GLib.timeout_add_seconds(self.retry_interval, self._get_notification)
|
||||||
|
|
||||||
|
self.connection.close()
|
||||||
|
|
||||||
|
def __run_in_thread(self, function, *args, **kwargs):
|
||||||
|
if not self.connection:
|
||||||
|
logger.warning("No connection defined")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.enabled:
|
||||||
|
logger.info("Notifications disabled")
|
||||||
|
return
|
||||||
|
|
||||||
|
thread = threading.Thread(target=function, args=args, kwargs=kwargs, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _get_notification(self):
|
||||||
|
now = int(time.time())
|
||||||
|
|
||||||
|
if now >= self.next_request:
|
||||||
|
self.__run_in_thread(self.__get_notification_thread)
|
||||||
|
else:
|
||||||
|
GLib.timeout_add_seconds(self.next_request - now, self._get_notification)
|
||||||
|
|
||||||
|
def __poll_vote_idle(self, data):
|
||||||
|
logger.debug(f"Got vote response {data}")
|
||||||
|
|
||||||
|
poll_uuid = data["uuid"]
|
||||||
|
results = data["results"]
|
||||||
|
|
||||||
|
for notification in self.store:
|
||||||
|
if isinstance(notification, CmbPollNotification) and notification.poll.id == poll_uuid:
|
||||||
|
notification.results = CmbPollResult(**results)
|
||||||
|
self.__save_notifications()
|
||||||
|
break
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
def __poll_vote_exception_idle(self, poll_uuid):
|
||||||
|
for notification in self.store:
|
||||||
|
if isinstance(notification, CmbPollNotification) and notification.poll.id == poll_uuid:
|
||||||
|
notification.my_votes = []
|
||||||
|
break
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
def __poll_vote_thread(self, method, poll_uuid, votes=None):
|
||||||
|
headers = {"User-Agent": self.user_agent, "x-cambalache-uuid": self.uuid, "Content-type": "application/json"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.dumps({"votes": votes}) if method == "POST" else None
|
||||||
|
self.connection.request(method, f"/poll/{poll_uuid}", payload, headers)
|
||||||
|
response = self.connection.getresponse()
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
data = response.read().decode()
|
||||||
|
GLib.idle_add(self.__poll_vote_idle, json.loads(data))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error voting {e}")
|
||||||
|
GLib.idle_add(self.__poll_vote_exception_idle, poll_uuid)
|
||||||
|
|
||||||
|
self.connection.close()
|
||||||
|
|
||||||
|
def poll_vote(self, notification: CmbPollNotification, votes: list[int]):
|
||||||
|
if self.uuid is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
notification.my_votes = votes
|
||||||
|
|
||||||
|
self.__run_in_thread(self.__poll_vote_thread, "POST", notification.poll.id, votes)
|
||||||
|
|
||||||
|
def poll_refresh_results(self, notification: CmbPollNotification):
|
||||||
|
if self.uuid is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__run_in_thread(self.__poll_vote_thread, "GET", notification.poll.id)
|
||||||
|
|
||||||
|
def remove(self, notification: CmbNotification):
|
||||||
|
valid, position = self.store.find(notification)
|
||||||
|
if valid:
|
||||||
|
self.store.remove(position)
|
||||||
|
self.__save_notifications()
|
||||||
|
|
||||||
|
|
||||||
|
notification_center = CmbNotificationCenter()
|
60
cambalache/cmb_notification_list_row.py
Normal file
60
cambalache/cmb_notification_list_row.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#
|
||||||
|
# CmbNotificationListRow
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
|
||||||
|
from cambalache import getLogger
|
||||||
|
from gi.repository import GObject, Gtk
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_notification_list_row.ui")
|
||||||
|
class CmbNotificationListRow(Gtk.ListBoxRow):
|
||||||
|
__gtype_name__ = "CmbNotificationListRow"
|
||||||
|
|
||||||
|
view = GObject.Property(type=Gtk.Widget, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
|
box = Gtk.Template.Child()
|
||||||
|
date_label = Gtk.Template.Child()
|
||||||
|
close_button = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.props.activatable = False
|
||||||
|
|
||||||
|
notification = self.view.notification
|
||||||
|
start_date = datetime.datetime.utcfromtimestamp(notification.start_date).strftime("%x")
|
||||||
|
self.date_label.set_label(f"<small>{start_date}</small>")
|
||||||
|
self.box.append(self.view)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_map")
|
||||||
|
def __on_map(self, w):
|
||||||
|
self.props.child.props.reveal_child = True
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_close_button_clicked")
|
||||||
|
def __on_close_button_clicked(self, button):
|
||||||
|
notification = self.view.notification
|
||||||
|
notification.center.remove(notification)
|
42
cambalache/cmb_notification_list_row.ui
Normal file
42
cambalache/cmb_notification_list_row.ui
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_notification_list_row.ui -->
|
||||||
|
<requires lib="gtk" version="4.12"/>
|
||||||
|
<template class="CmbNotificationListRow" parent="GtkListBoxRow">
|
||||||
|
<signal name="map" handler="on_map"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkRevealer">
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="box">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
<property name="vexpand-set">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="date_label">
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="close_button">
|
||||||
|
<property name="icon-name">window-close-symbolic</property>
|
||||||
|
<signal name="clicked" handler="on_close_button_clicked"/>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
<class name="compact"/>
|
||||||
|
<class name="close"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
67
cambalache/cmb_notification_list_view.py
Normal file
67
cambalache/cmb_notification_list_view.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#
|
||||||
|
# CmbNotificationListView
|
||||||
|
#
|
||||||
|
# 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 cambalache import getLogger
|
||||||
|
from gi.repository import GObject, Gtk
|
||||||
|
from .cmb_version_notification_view import CmbVersionNotificationView
|
||||||
|
from .cmb_message_notification_view import CmbMessageNotificationView
|
||||||
|
from .cmb_poll_notification_view import CmbPollNotificationView
|
||||||
|
from .cmb_notification_list_row import CmbNotificationListRow
|
||||||
|
from .cmb_notification import CmbNotificationCenter, CmbVersionNotification, CmbMessageNotification, CmbPollNotification
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_notification_list_view.ui")
|
||||||
|
class CmbNotificationListView(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbNotificationListView"
|
||||||
|
|
||||||
|
notification_center = GObject.Property(type=CmbNotificationCenter, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
list_box = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.connect("notify::notification-center", self.__on_notification_center_notify)
|
||||||
|
|
||||||
|
def __on_notification_center_notify(self, obj, pspec):
|
||||||
|
self.list_box.bind_model(self.notification_center.store, self.__create_widget_func)
|
||||||
|
GObject.Object.bind_property(
|
||||||
|
self.notification_center.store,
|
||||||
|
"n-items",
|
||||||
|
self.list_box,
|
||||||
|
"visible",
|
||||||
|
GObject.BindingFlags.SYNC_CREATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __create_widget_func(self, item):
|
||||||
|
if isinstance(item, CmbVersionNotification):
|
||||||
|
view = CmbVersionNotificationView(notification=item)
|
||||||
|
elif isinstance(item, CmbMessageNotification):
|
||||||
|
view = CmbMessageNotificationView(notification=item)
|
||||||
|
elif isinstance(item, CmbPollNotification):
|
||||||
|
view = CmbPollNotificationView(notification=item)
|
||||||
|
|
||||||
|
return CmbNotificationListRow(view=view)
|
26
cambalache/cmb_notification_list_view.ui
Normal file
26
cambalache/cmb_notification_list_view.ui
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_notification_list_view.ui -->
|
||||||
|
<requires lib="gtk" version="4.6"/>
|
||||||
|
<template class="CmbNotificationListView" parent="GtkBox">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="propagate-natural-height">True</property>
|
||||||
|
<property name="propagate-natural-width">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="list_box">
|
||||||
|
<property name="activate-on-single-click">False</property>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<property name="show-separators">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="notifications"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -32,6 +32,7 @@ from .cmb_layout_property import CmbLayoutProperty
|
|||||||
from .cmb_object_data import CmbObjectData
|
from .cmb_object_data import CmbObjectData
|
||||||
from .cmb_type_info import CmbTypeInfo
|
from .cmb_type_info import CmbTypeInfo
|
||||||
from .cmb_ui import CmbUI
|
from .cmb_ui import CmbUI
|
||||||
|
from .constants import GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
|
||||||
from . import utils
|
from . import utils
|
||||||
from cambalache import getLogger, _
|
from cambalache import getLogger, _
|
||||||
|
|
||||||
@ -61,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.position_layout_property = None
|
|
||||||
self.inline_property_id = None
|
self.inline_property_id = None
|
||||||
self.version_warning = None
|
self.version_warning = None
|
||||||
self.__is_template = False
|
self.__is_template = False
|
||||||
@ -144,8 +144,10 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
if property_info is None:
|
if property_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
for property_name in property_info:
|
for property_name, info in property_info.items():
|
||||||
info = property_info[property_name]
|
# Check if this property was already installed by a derived class
|
||||||
|
if property_name in self.__properties_dict:
|
||||||
|
continue
|
||||||
|
|
||||||
prop = CmbProperty(
|
prop = CmbProperty(
|
||||||
object=self,
|
object=self,
|
||||||
@ -173,6 +175,16 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
for parent_id in self.info.hierarchy:
|
for parent_id in self.info.hierarchy:
|
||||||
self.__populate_type_properties(parent_id)
|
self.__populate_type_properties(parent_id)
|
||||||
|
|
||||||
|
# Add accessible properties for GtkWidgets
|
||||||
|
if parent_id == "GtkWidget":
|
||||||
|
for accessible_id in [
|
||||||
|
"CmbAccessibleProperty",
|
||||||
|
"CmbAccessibleRelation",
|
||||||
|
"CmbAccessibleState",
|
||||||
|
"CmbAccessibleAction"
|
||||||
|
]:
|
||||||
|
self.__populate_type_properties(accessible_id)
|
||||||
|
|
||||||
def __populate_layout_properties_from_type(self, name):
|
def __populate_layout_properties_from_type(self, name):
|
||||||
property_info = self.project.get_type_properties(name)
|
property_info = self.project.get_type_properties(name)
|
||||||
if property_info is None:
|
if property_info is None:
|
||||||
@ -194,10 +206,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
info=info,
|
info=info,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Keep a reference to the position layout property
|
|
||||||
if info.is_position:
|
|
||||||
self.position_layout_property = prop
|
|
||||||
|
|
||||||
self.__layout.append(prop)
|
self.__layout.append(prop)
|
||||||
|
|
||||||
# Dictionary of properties
|
# Dictionary of properties
|
||||||
@ -226,9 +234,7 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
self.project._object_signal_changed(self, signal)
|
self.project._object_signal_changed(self, signal)
|
||||||
|
|
||||||
def __add_data_object(self, data):
|
def __add_data_object(self, data):
|
||||||
self.__populate_data()
|
if data.get_id_string() in self.data_dict:
|
||||||
|
|
||||||
if data in self.__data:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__data.append(data)
|
self.__data.append(data)
|
||||||
@ -324,7 +330,7 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
|
|
||||||
project.db.execute(
|
project.db.execute(
|
||||||
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
||||||
(new_parent_id, new_position, ui_id, object_id)
|
(new_parent_id, new_position or 0, ui_id, object_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update children positions in old parent
|
# Update children positions in old parent
|
||||||
@ -440,7 +446,7 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
return self._add_data(owner_id, data_id, id, info=taginfo)
|
return self._add_data(owner_id, data_id, id, info=taginfo)
|
||||||
|
|
||||||
def _remove_data(self, data):
|
def _remove_data(self, data):
|
||||||
if data not in self.__data:
|
if data.get_id_string() not in self.data_dict:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__data.remove(data)
|
self.__data.remove(data)
|
||||||
@ -451,12 +457,18 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
|
|
||||||
def remove_data(self, data):
|
def remove_data(self, data):
|
||||||
try:
|
try:
|
||||||
assert data in self.__data
|
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
|
||||||
@ -524,23 +536,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
# Set new position
|
# Set new position
|
||||||
db.execute("UPDATE object SET position=? WHERE ui_id=? AND object_id=?;", (position, self.ui_id, child.object_id))
|
db.execute("UPDATE object SET position=? WHERE ui_id=? AND object_id=?;", (position, self.ui_id, child.object_id))
|
||||||
|
|
||||||
# Update position layout property (Example GtkBox position in Gtk3)
|
|
||||||
if child.position_layout_property:
|
|
||||||
db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE object_layout_property AS olp SET value=o.position
|
|
||||||
FROM object AS o
|
|
||||||
WHERE
|
|
||||||
o.ui_id=? AND
|
|
||||||
o.parent_id=? AND
|
|
||||||
olp.ui_id=o.ui_id AND
|
|
||||||
olp.object_id=o.parent_id AND
|
|
||||||
olp.child_id=o.object_id AND
|
|
||||||
olp.property_id=?;
|
|
||||||
""",
|
|
||||||
(self.ui_id, self.object_id, child.position_layout_property.property_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
db.ignore_check_constraints = False
|
db.ignore_check_constraints = False
|
||||||
|
|
||||||
list_position = child.list_position
|
list_position = child.list_position
|
||||||
@ -551,8 +546,10 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
self.items_changed(list_position, 0, 1)
|
self.items_changed(list_position, 0, 1)
|
||||||
self.items_changed(old_list_position+1, 1, 0)
|
self.items_changed(old_list_position+1, 1, 0)
|
||||||
else:
|
else:
|
||||||
self.items_changed(old_list_position, 1, 0)
|
if old_list_position != list_position:
|
||||||
self.items_changed(list_position, 0, 1)
|
self.items_changed(old_list_position, 1, 0)
|
||||||
|
self.items_changed(list_position, 0, 1)
|
||||||
|
|
||||||
self.project._ignore_selection = False
|
self.project._ignore_selection = False
|
||||||
|
|
||||||
self.project.history_pop()
|
self.project.history_pop()
|
||||||
@ -585,15 +582,30 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def display_name_type(self):
|
def display_name_type(self):
|
||||||
return f"{self.name} {self.type_id}" if self.name else self.type_id
|
return f"{self.type_id} {self.name}" if self.name else self.type_id
|
||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
inline_prop = self.inline_property_id
|
name = self.name or ""
|
||||||
inline_prop = f"<b>{inline_prop}</b> " if inline_prop else ""
|
type_id = self.type_id
|
||||||
name = f"{self.name} " if self.name else ""
|
parent_id = self.parent_id
|
||||||
extra = _("(template)") if not self.parent_id and self.ui.template_id == self.object_id else self.type_id
|
|
||||||
display_name = f"{inline_prop}{name}<i>{extra}</i>"
|
if type_id in [GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]:
|
||||||
|
prop = self.properties_dict["label"]
|
||||||
|
label = prop.value or ""
|
||||||
|
display_name = f"{type_id} <i>{label}</i>"
|
||||||
|
elif not parent_id and self.ui.template_id == self.object_id:
|
||||||
|
# Translators: This is used for Template classes in the object tree
|
||||||
|
display_name = _("{name} (template)").format(name=name)
|
||||||
|
else:
|
||||||
|
inline_prop = self.inline_property_id
|
||||||
|
internal = self.internal
|
||||||
|
if inline_prop:
|
||||||
|
display_name = f"{type_id} <b>{inline_prop}</b> <i>{name}</i>"
|
||||||
|
elif internal:
|
||||||
|
display_name = f"{type_id} <b>{internal}</b> <i>{name}</i>"
|
||||||
|
else:
|
||||||
|
display_name = f"{type_id} <i>{name}</i>"
|
||||||
|
|
||||||
if self.version_warning:
|
if self.version_warning:
|
||||||
return f'<span underline="error">{display_name}</span>'
|
return f'<span underline="error">{display_name}</span>'
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
@ -61,9 +61,6 @@ class CmbObjectData(CmbBaseObjectData):
|
|||||||
if self.object is None:
|
if self.object is None:
|
||||||
self.object = self.project.get_object_by_id(self.ui_id, self.object_id)
|
self.object = self.project.get_object_by_id(self.ui_id, self.object_id)
|
||||||
|
|
||||||
if self.parent_id is not None and self.parent is None:
|
|
||||||
self.parent = self.object.data_dict.get(f"{self.owner_id}.{self.parent_id}", None)
|
|
||||||
|
|
||||||
self.__populate_children()
|
self.__populate_children()
|
||||||
self.connect("notify", self._on_notify)
|
self.connect("notify", self._on_notify)
|
||||||
|
|
||||||
@ -178,6 +175,7 @@ class CmbObjectData(CmbBaseObjectData):
|
|||||||
(self.ui_id, self.object_id, self.owner_id, self.id),
|
(self.ui_id, self.object_id, self.owner_id, self.id),
|
||||||
):
|
):
|
||||||
obj = CmbObjectData.from_row(self.project, *row)
|
obj = CmbObjectData.from_row(self.project, *row)
|
||||||
|
obj.parent = self
|
||||||
self.__add_child(obj)
|
self.__add_child(obj)
|
||||||
|
|
||||||
def add_data(self, data_key, value=None, comment=None):
|
def add_data(self, data_key, value=None, comment=None):
|
||||||
@ -196,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,7 +75,7 @@ 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:
|
||||||
self.__data.parent.remove_data(self.__data)
|
self.__data.parent.remove_data(self.__data)
|
||||||
|
|
||||||
@GObject.Property(type=GObject.Object)
|
@GObject.Property(type=GObject.Object)
|
||||||
@ -138,8 +144,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
|||||||
self.__update_view()
|
self.__update_view()
|
||||||
|
|
||||||
def __on_data_removed(self, obj, data):
|
def __on_data_removed(self, obj, data):
|
||||||
if self.object and self.info:
|
self.__remove_data_editor(data)
|
||||||
self.__remove_data_editor(data)
|
|
||||||
|
|
||||||
def __ensure_object_data(self, history_message):
|
def __ensure_object_data(self, history_message):
|
||||||
if self.data:
|
if self.data:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_object_data_editor.ui -->
|
<!-- interface-name cmb_object_data_editor.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -70,7 +70,6 @@
|
|||||||
<object class="GtkGrid" id="grid">
|
<object class="GtkGrid" id="grid">
|
||||||
<property name="column-spacing">4</property>
|
<property name="column-spacing">4</property>
|
||||||
<property name="row-spacing">4</property>
|
<property name="row-spacing">4</property>
|
||||||
<property name="vexpand">1</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</template>
|
</template>
|
||||||
|
@ -215,6 +215,10 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
|||||||
if prop is None or prop.info is None:
|
if prop is None or prop.info is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Only show properties in the class they where originally defined
|
||||||
|
if prop.info.original_owner_id is not None and owner_id != prop.info.original_owner_id:
|
||||||
|
continue
|
||||||
|
|
||||||
editor = cmb_create_editor(prop.project, prop.info.type_id, prop=prop)
|
editor = cmb_create_editor(prop.project, prop.info.type_id, prop=prop)
|
||||||
|
|
||||||
if editor is None:
|
if editor is None:
|
||||||
@ -233,7 +237,10 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
|||||||
else:
|
else:
|
||||||
label = CmbPropertyLabel(prop=prop, bindable=not is_builtin)
|
label = CmbPropertyLabel(prop=prop, bindable=not is_builtin)
|
||||||
|
|
||||||
# Keep a dict of labels
|
if prop.info.disabled:
|
||||||
|
for w in [editor, label]:
|
||||||
|
w.set_tooltip_text(_("This property is disabled for {type_id}").format(type_id=obj.type_id))
|
||||||
|
w.props.sensitive = False
|
||||||
|
|
||||||
grid.attach(label, 0, i, 1, 1)
|
grid.attach(label, 0, i, 1, 1)
|
||||||
grid.attach(editor, 1, i, 1, 1)
|
grid.attach(editor, 1, i, 1, 1)
|
||||||
@ -253,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)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Cambalache Base Object wrappers
|
# Cambalache Base Object wrappers
|
||||||
#
|
#
|
||||||
# Copyright (C) 2021-2022 Juan Pablo Ugarte
|
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||||
#
|
#
|
||||||
# This library is free software; you can redistribute it and/or
|
# This library is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
# modify it under the terms of the GNU Lesser General Public
|
||||||
@ -57,8 +57,8 @@ class CmbBaseLibraryInfo(CmbBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CmbPropertyInfo(CmbBase):
|
class CmbBasePropertyInfo(CmbBase):
|
||||||
__gtype_name__ = "CmbPropertyInfo"
|
__gtype_name__ = "CmbBasePropertyInfo"
|
||||||
|
|
||||||
owner_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
owner_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
property_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
property_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
@ -83,13 +83,15 @@ class CmbPropertyInfo(CmbBase):
|
|||||||
disable_inline_object = GObject.Property(
|
disable_inline_object = GObject.Property(
|
||||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||||
)
|
)
|
||||||
is_position = GObject.Property(
|
deprecated = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
|
||||||
)
|
|
||||||
required = GObject.Property(
|
required = GObject.Property(
|
||||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||||
)
|
)
|
||||||
workspace_default = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
workspace_default = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
original_owner_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
disabled = GObject.Property(
|
||||||
|
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@ -111,9 +113,11 @@ class CmbPropertyInfo(CmbBase):
|
|||||||
deprecated_version,
|
deprecated_version,
|
||||||
translatable,
|
translatable,
|
||||||
disable_inline_object,
|
disable_inline_object,
|
||||||
is_position,
|
deprecated,
|
||||||
required,
|
required,
|
||||||
workspace_default,
|
workspace_default,
|
||||||
|
original_owner_id,
|
||||||
|
disabled,
|
||||||
):
|
):
|
||||||
return cls(
|
return cls(
|
||||||
project=project,
|
project=project,
|
||||||
@ -130,9 +134,11 @@ class CmbPropertyInfo(CmbBase):
|
|||||||
deprecated_version=deprecated_version,
|
deprecated_version=deprecated_version,
|
||||||
translatable=translatable,
|
translatable=translatable,
|
||||||
disable_inline_object=disable_inline_object,
|
disable_inline_object=disable_inline_object,
|
||||||
is_position=is_position,
|
deprecated=deprecated,
|
||||||
required=required,
|
required=required,
|
||||||
workspace_default=workspace_default,
|
workspace_default=workspace_default,
|
||||||
|
original_owner_id=original_owner_id,
|
||||||
|
disabled=disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -279,6 +285,30 @@ class CmbTypeChildInfo(CmbBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBaseTypeInternalChildInfo(CmbBase):
|
||||||
|
__gtype_name__ = "CmbBaseTypeInternalChildInfo"
|
||||||
|
|
||||||
|
type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
internal_child_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
internal_parent_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
internal_type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
creation_property_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, project, type_id, internal_child_id, internal_parent_id, internal_type, creation_property_id):
|
||||||
|
return cls(
|
||||||
|
project=project,
|
||||||
|
type_id=type_id,
|
||||||
|
internal_child_id=internal_child_id,
|
||||||
|
internal_parent_id=internal_parent_id,
|
||||||
|
internal_type=internal_type,
|
||||||
|
creation_property_id=creation_property_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CmbBaseUI(CmbBase):
|
class CmbBaseUI(CmbBase):
|
||||||
__gtype_name__ = "CmbBaseUI"
|
__gtype_name__ = "CmbBaseUI"
|
||||||
|
|
||||||
@ -423,6 +453,97 @@ class CmbBaseCSS(CmbBase):
|
|||||||
self.db_set("UPDATE css SET is_global=? WHERE (css_id) IS (?);", (self.css_id,), value)
|
self.db_set("UPDATE css SET is_global=? WHERE (css_id) IS (?);", (self.css_id,), value)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBaseGResource(CmbBase):
|
||||||
|
__gtype_name__ = "CmbBaseGResource"
|
||||||
|
|
||||||
|
gresource_id = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
resource_type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(
|
||||||
|
cls,
|
||||||
|
project,
|
||||||
|
gresource_id,
|
||||||
|
resource_type,
|
||||||
|
parent_id,
|
||||||
|
position,
|
||||||
|
gresources_filename,
|
||||||
|
gresource_prefix,
|
||||||
|
file_filename,
|
||||||
|
file_compressed,
|
||||||
|
file_preprocess,
|
||||||
|
file_alias,
|
||||||
|
):
|
||||||
|
return cls(project=project, gresource_id=gresource_id)
|
||||||
|
|
||||||
|
@GObject.Property(type=int)
|
||||||
|
def parent_id(self):
|
||||||
|
return self.db_get("SELECT parent_id FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@parent_id.setter
|
||||||
|
def _set_parent_id(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET parent_id=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=int)
|
||||||
|
def position(self):
|
||||||
|
return self.db_get("SELECT position FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@position.setter
|
||||||
|
def _set_position(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET position=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def gresources_filename(self):
|
||||||
|
return self.db_get("SELECT gresources_filename FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@gresources_filename.setter
|
||||||
|
def _set_gresources_filename(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET gresources_filename=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def gresource_prefix(self):
|
||||||
|
return self.db_get("SELECT gresource_prefix FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@gresource_prefix.setter
|
||||||
|
def _set_gresource_prefix(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET gresource_prefix=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def file_filename(self):
|
||||||
|
return self.db_get("SELECT file_filename FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@file_filename.setter
|
||||||
|
def _set_file_filename(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET file_filename=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=bool, default=False)
|
||||||
|
def file_compressed(self):
|
||||||
|
return self.db_get("SELECT file_compressed FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@file_compressed.setter
|
||||||
|
def _set_file_compressed(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET file_compressed=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def file_preprocess(self):
|
||||||
|
return self.db_get("SELECT file_preprocess FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@file_preprocess.setter
|
||||||
|
def _set_file_preprocess(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET file_preprocess=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def file_alias(self):
|
||||||
|
return self.db_get("SELECT file_alias FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||||
|
|
||||||
|
@file_alias.setter
|
||||||
|
def _set_file_alias(self, value):
|
||||||
|
self.db_set("UPDATE gresource SET file_alias=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||||
|
|
||||||
|
|
||||||
class CmbBaseProperty(CmbBase):
|
class CmbBaseProperty(CmbBase):
|
||||||
__gtype_name__ = "CmbBaseProperty"
|
__gtype_name__ = "CmbBaseProperty"
|
||||||
|
|
||||||
|
118
cambalache/cmb_path.py
Normal file
118
cambalache/cmb_path.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#
|
||||||
|
# CmbPath
|
||||||
|
#
|
||||||
|
# Copyright (C) 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, Gio
|
||||||
|
|
||||||
|
from .cmb_base import CmbBase
|
||||||
|
from cambalache import _, getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbPath(CmbBase, Gio.ListModel):
|
||||||
|
__gtype_name__ = "CmbPath"
|
||||||
|
|
||||||
|
path_parent = GObject.Property(type=CmbBase, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
path = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# GListModel
|
||||||
|
self.__items = []
|
||||||
|
self.__path_items = {}
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"CmbPath<{self.path}>"
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def display_name(self):
|
||||||
|
return self.path or _("{n} unsaved files").format(n=self.n_items)
|
||||||
|
|
||||||
|
def get_path_item(self, directory):
|
||||||
|
return self.__path_items.get(directory, None)
|
||||||
|
|
||||||
|
def add_item(self, item):
|
||||||
|
if item in self.__items:
|
||||||
|
return
|
||||||
|
|
||||||
|
display_name = item.display_name
|
||||||
|
is_path = isinstance(item, CmbPath)
|
||||||
|
|
||||||
|
if is_path:
|
||||||
|
self.__path_items[item.path] = item
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for list_item in self.__items:
|
||||||
|
if is_path:
|
||||||
|
if not isinstance(list_item, CmbPath):
|
||||||
|
break
|
||||||
|
|
||||||
|
if display_name < list_item.display_name:
|
||||||
|
break
|
||||||
|
elif not isinstance(list_item, CmbPath) and display_name < list_item.display_name:
|
||||||
|
break
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
item.path_parent = self
|
||||||
|
self.__items.insert(i, item)
|
||||||
|
self.items_changed(i, 0, 1)
|
||||||
|
|
||||||
|
if not self.path:
|
||||||
|
self.notify("display-name")
|
||||||
|
|
||||||
|
def remove_item(self, item):
|
||||||
|
if item not in self.__items:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(item, CmbPath) and item.path in self.__path_items:
|
||||||
|
del self.__path_items[item.path]
|
||||||
|
|
||||||
|
item.path_parent = None
|
||||||
|
i = self.__items.index(item)
|
||||||
|
self.__items.pop(i)
|
||||||
|
self.items_changed(i, 1, 0)
|
||||||
|
|
||||||
|
if not self.path:
|
||||||
|
self.notify("display-name")
|
||||||
|
|
||||||
|
# GListModel iface
|
||||||
|
@GObject.Property(type=int)
|
||||||
|
def n_items(self):
|
||||||
|
return len(self.__items)
|
||||||
|
|
||||||
|
def do_get_item(self, position):
|
||||||
|
return self.__items[position] if position < len(self.__items) else None
|
||||||
|
|
||||||
|
def do_get_item_type(self):
|
||||||
|
return CmbBase
|
||||||
|
|
||||||
|
def do_get_n_items(self):
|
||||||
|
return self.n_items
|
||||||
|
|
142
cambalache/cmb_poll_notification_view.py
Normal file
142
cambalache/cmb_poll_notification_view.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#
|
||||||
|
# CmbPollNotificationView
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
|
||||||
|
from cambalache import _, getLogger
|
||||||
|
from gi.repository import GObject, Gtk
|
||||||
|
from . import utils
|
||||||
|
from .cmb_notification import CmbPollNotification
|
||||||
|
from .cmb_poll_option_check import CmbPollOptionCheck
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_notification_view.ui")
|
||||||
|
class CmbPollNotificationView(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbPollNotificationView"
|
||||||
|
|
||||||
|
notification = GObject.Property(
|
||||||
|
type=CmbPollNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
# Poll
|
||||||
|
title_label = Gtk.Template.Child()
|
||||||
|
description_label = Gtk.Template.Child()
|
||||||
|
option_box = Gtk.Template.Child()
|
||||||
|
total_label = Gtk.Template.Child()
|
||||||
|
end_date_label = Gtk.Template.Child()
|
||||||
|
refresh_button = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__option_checks = []
|
||||||
|
self.__updating = False
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
notification = self.notification
|
||||||
|
poll = notification.poll
|
||||||
|
active = utils.utcnow() < poll.end_date
|
||||||
|
|
||||||
|
self.title_label.props.label = f"<b>{poll.title}</b>"
|
||||||
|
self.description_label.props.label = poll.description
|
||||||
|
|
||||||
|
close_msg = _("<small>• Closes on {date}</small>") if active else _("<small>• Closed on {date}</small>")
|
||||||
|
end_date = datetime.datetime.fromtimestamp(poll.end_date)
|
||||||
|
self.end_date_label.props.label = close_msg.format(date=end_date.strftime("%x"))
|
||||||
|
self.end_date_label.props.tooltip_text = end_date.strftime("%c")
|
||||||
|
|
||||||
|
first_check = None
|
||||||
|
n_option = 0
|
||||||
|
for option in poll.options:
|
||||||
|
button = CmbPollOptionCheck(option=option, sensitive=active)
|
||||||
|
|
||||||
|
if poll.allowed_votes == 1:
|
||||||
|
if first_check is None:
|
||||||
|
first_check = button
|
||||||
|
else:
|
||||||
|
button.set_group(first_check)
|
||||||
|
|
||||||
|
button.connect("toggled", self.__on_check_button_toggled, n_option)
|
||||||
|
|
||||||
|
self.__option_checks.append(button)
|
||||||
|
self.option_box.append(button)
|
||||||
|
n_option += 1
|
||||||
|
|
||||||
|
self.__update_results()
|
||||||
|
notification.connect("notify", self.__on_poll_notify)
|
||||||
|
|
||||||
|
def __on_check_button_toggled(self, button, n_option):
|
||||||
|
allowed_votes = self.notification.poll.allowed_votes
|
||||||
|
|
||||||
|
if self.__updating or (allowed_votes == 1 and not button.props.active):
|
||||||
|
return
|
||||||
|
|
||||||
|
votes = []
|
||||||
|
for i, check in enumerate(self.__option_checks):
|
||||||
|
if check.props.active:
|
||||||
|
votes.append(i)
|
||||||
|
|
||||||
|
if allowed_votes > 1:
|
||||||
|
not_done = len(votes) < allowed_votes
|
||||||
|
for i, check in enumerate(self.__option_checks):
|
||||||
|
if not_done:
|
||||||
|
check.set_sensitive(True)
|
||||||
|
elif not check.props.active:
|
||||||
|
check.set_sensitive(False)
|
||||||
|
|
||||||
|
self.notification.center.poll_vote(self.notification, votes)
|
||||||
|
|
||||||
|
def __on_poll_notify(self, notification, pspec):
|
||||||
|
if pspec.name in ["my-votes", "results"]:
|
||||||
|
self.__update_results()
|
||||||
|
|
||||||
|
def __update_results(self):
|
||||||
|
notification = self.notification
|
||||||
|
results = notification.results
|
||||||
|
my_votes = notification.my_votes
|
||||||
|
|
||||||
|
if not results or not my_votes:
|
||||||
|
self.total_label.props.label = ""
|
||||||
|
for check in self.__option_checks:
|
||||||
|
check.fraction = -1
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__updating = True
|
||||||
|
|
||||||
|
votes = results.votes
|
||||||
|
total = results.total
|
||||||
|
|
||||||
|
for i, check in enumerate(self.__option_checks):
|
||||||
|
check.set_active(i in my_votes)
|
||||||
|
check.fraction = votes[i] / total if total else 0
|
||||||
|
|
||||||
|
self.total_label.props.label = _("<small>• {total} vote</small>").format(total=results.total)
|
||||||
|
|
||||||
|
self.__updating = False
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_refresh_button_clicked")
|
||||||
|
def __on_refresh_button_clicked(self, button):
|
||||||
|
self.notification.center.poll_refresh_results(self.notification)
|
||||||
|
|
64
cambalache/cmb_poll_notification_view.ui
Normal file
64
cambalache/cmb_poll_notification_view.ui
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_poll_notification_view.ui -->
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="CmbPollNotificationView" parent="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="title_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="description_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="option_box">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
<property name="vexpand-set">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="refresh_button">
|
||||||
|
<property name="child">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label"><small>Refresh</small></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<signal name="clicked" handler="on_refresh_button_clicked"/>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
<class name="compact"/>
|
||||||
|
<class name="link"/>
|
||||||
|
<class name="text-button"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="total_label">
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="link"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="end_date_label">
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
76
cambalache/cmb_poll_option_check.py
Normal file
76
cambalache/cmb_poll_option_check.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#
|
||||||
|
# CmbPollOptionCheck
|
||||||
|
#
|
||||||
|
# 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 cambalache import getLogger
|
||||||
|
from gi.repository import GLib, GObject, Gtk
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_option_check.ui")
|
||||||
|
class CmbPollOptionCheck(Gtk.CheckButton):
|
||||||
|
__gtype_name__ = "CmbPollOptionCheck"
|
||||||
|
|
||||||
|
label = Gtk.Template.Child()
|
||||||
|
bar = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__fraction = None
|
||||||
|
self.__tick_id = None
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def option(self):
|
||||||
|
return self.label.props.label
|
||||||
|
|
||||||
|
@option.setter
|
||||||
|
def _set_option(self, option):
|
||||||
|
self.label.props.label = option
|
||||||
|
|
||||||
|
@GObject.Property(type=float)
|
||||||
|
def fraction(self):
|
||||||
|
return self.__fraction
|
||||||
|
|
||||||
|
@fraction.setter
|
||||||
|
def _set_fraction(self, fraction):
|
||||||
|
self.__fraction = fraction
|
||||||
|
|
||||||
|
if fraction < 0:
|
||||||
|
self.bar.props.visible = False
|
||||||
|
else:
|
||||||
|
self.bar.props.visible = True
|
||||||
|
if self.__tick_id is None:
|
||||||
|
self.__tick_id = self.add_tick_callback(self.__update_fraction)
|
||||||
|
|
||||||
|
def __update_fraction(self, widget, frame_clock):
|
||||||
|
if self.bar.props.fraction < self.__fraction:
|
||||||
|
self.bar.props.fraction = min(self.__fraction, self.bar.props.fraction + 0.08)
|
||||||
|
elif self.bar.props.fraction > self.__fraction:
|
||||||
|
self.bar.props.fraction = max(self.__fraction, self.bar.props.fraction - 0.08)
|
||||||
|
else:
|
||||||
|
self.__tick_id = None
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
return GLib.SOURCE_CONTINUE
|
23
cambalache/cmb_poll_option_check.ui
Normal file
23
cambalache/cmb_poll_option_check.ui
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name r.ui -->
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="CmbPollOptionCheck" parent="GtkCheckButton">
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkProgressBar" id="bar"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
File diff suppressed because it is too large
Load Diff
@ -25,9 +25,12 @@
|
|||||||
|
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
|
||||||
from .cmb_objects_base import CmbBaseProperty, CmbPropertyInfo
|
from .cmb_objects_base import CmbBaseProperty
|
||||||
|
from .cmb_property_info import CmbPropertyInfo
|
||||||
from . import utils
|
from . import utils
|
||||||
from cambalache import _
|
from cambalache import _, getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CmbProperty(CmbBaseProperty):
|
class CmbProperty(CmbBaseProperty):
|
||||||
@ -63,9 +66,96 @@ class CmbProperty(CmbBaseProperty):
|
|||||||
|
|
||||||
@value.setter
|
@value.setter
|
||||||
def _set_value(self, value):
|
def _set_value(self, value):
|
||||||
self.__update_values(value, self.bind_property)
|
self.__update_values(value, self.translatable, self.translation_context, self.translation_comments, self.bind_property)
|
||||||
|
|
||||||
def __update_values(self, value, bind_property):
|
@GObject.Property(type=bool, default=False)
|
||||||
|
def translatable(self):
|
||||||
|
return super().translatable
|
||||||
|
|
||||||
|
@translatable.setter
|
||||||
|
def _set_translatable(self, value):
|
||||||
|
self.__update_values(self.value, value, self.translation_context, self.translation_comments, self.bind_property)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def translation_context(self):
|
||||||
|
return self.db_get(
|
||||||
|
"SELECT translation_context FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
|
||||||
|
(
|
||||||
|
self.ui_id,
|
||||||
|
self.object_id,
|
||||||
|
self.owner_id,
|
||||||
|
self.property_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@translation_context.setter
|
||||||
|
def _set_translation_context(self, value):
|
||||||
|
self.__update_values(self.value, self.translatable, value, self.translation_comments, self.bind_property)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def translation_comments(self):
|
||||||
|
return self.db_get(
|
||||||
|
"SELECT translation_comments FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
|
||||||
|
(
|
||||||
|
self.ui_id,
|
||||||
|
self.object_id,
|
||||||
|
self.owner_id,
|
||||||
|
self.property_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@translation_comments.setter
|
||||||
|
def _set_translation_comments(self, value):
|
||||||
|
self.__update_values(self.value, self.translatable, self.translation_context, value, self.bind_property)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
if self.info.internal_child:
|
||||||
|
self.project.history_push(_("Unset {obj} {prop} {prop_type}").format(**self.__get_msgs()))
|
||||||
|
|
||||||
|
self.project.db.execute(
|
||||||
|
"DELETE 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.__update_internal_child()
|
||||||
|
|
||||||
|
if self.info.internal_child:
|
||||||
|
self.project.history_pop()
|
||||||
|
|
||||||
|
def __update_internal_child(self):
|
||||||
|
internal_info = self.info.internal_child
|
||||||
|
if internal_info and internal_info.internal_parent_id:
|
||||||
|
logger.warning("Adding an internal child within an internal child automatically is not implemented")
|
||||||
|
return
|
||||||
|
elif internal_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
value = self.value
|
||||||
|
child_id = self.db_get(
|
||||||
|
"SELECT object_id FROM object WHERE ui_id=? AND parent_id=? AND internal=?",
|
||||||
|
(self.ui_id, self.object_id, internal_info.internal_child_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if value and not child_id:
|
||||||
|
self.project.add_object(
|
||||||
|
self.ui_id,
|
||||||
|
internal_info.internal_type,
|
||||||
|
parent_id=self.object_id,
|
||||||
|
internal=internal_info.internal_child_id
|
||||||
|
)
|
||||||
|
elif child_id:
|
||||||
|
internal_child = self.project.get_object_by_id(self.ui_id, child_id)
|
||||||
|
if internal_child:
|
||||||
|
self.project.remove_object(internal_child, allow_internal_removal=True)
|
||||||
|
|
||||||
|
def __get_msgs(self, value=None):
|
||||||
|
return {
|
||||||
|
"obj": self.object.display_name_type,
|
||||||
|
"prop": self.property_id,
|
||||||
|
"prop_type": _("property"),
|
||||||
|
"value": str(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __update_values(self, value, translatable, translation_context, translation_comments, bind_property):
|
||||||
c = self.project.db.cursor()
|
c = self.project.db.cursor()
|
||||||
|
|
||||||
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
|
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
|
||||||
@ -75,18 +165,22 @@ class CmbProperty(CmbBaseProperty):
|
|||||||
bind_property_id = bind_property.property_id
|
bind_property_id = bind_property.property_id
|
||||||
|
|
||||||
if (
|
if (
|
||||||
value is None or value == self.info.default_value or (self.info.is_object and value == 0)
|
(value is None or value == self.info.default_value or (self.info.is_object and value == 0)) and
|
||||||
) and bind_property is None:
|
bind_property is None and
|
||||||
c.execute(
|
not translatable and
|
||||||
"DELETE FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
translation_context is None and
|
||||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
translation_comments is None
|
||||||
)
|
):
|
||||||
|
self.reset()
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
value is None
|
value == self.value and
|
||||||
and bind_source_id == self.bind_source_id
|
translatable == self.translatable and
|
||||||
and bind_owner_id == self.bind_owner_id
|
translation_context == self.translation_context and
|
||||||
and bind_property_id == self.bind_property_id
|
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
|
return
|
||||||
|
|
||||||
@ -97,14 +191,23 @@ class CmbProperty(CmbBaseProperty):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if count:
|
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(
|
c.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE object_property
|
UPDATE object_property
|
||||||
SET value=?, bind_source_id=?, bind_owner_id=?, bind_property_id=?
|
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=?;
|
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
value,
|
value,
|
||||||
|
translatable,
|
||||||
|
translation_context,
|
||||||
|
translation_comments,
|
||||||
bind_source_id,
|
bind_source_id,
|
||||||
bind_owner_id,
|
bind_owner_id,
|
||||||
bind_property_id,
|
bind_property_id,
|
||||||
@ -115,11 +218,19 @@ class CmbProperty(CmbBaseProperty):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
if self.info.internal_child:
|
||||||
|
self.project.history_push(_("Set {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||||
|
|
||||||
c.execute(
|
c.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO object_property
|
INSERT INTO object_property
|
||||||
(ui_id, object_id, owner_id, property_id, value, bind_source_id, bind_owner_id, bind_property_id)
|
(
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
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.ui_id,
|
||||||
@ -127,12 +238,20 @@ class CmbProperty(CmbBaseProperty):
|
|||||||
self.owner_id,
|
self.owner_id,
|
||||||
self.property_id,
|
self.property_id,
|
||||||
value,
|
value,
|
||||||
|
translatable,
|
||||||
|
translation_context,
|
||||||
|
translation_comments,
|
||||||
bind_source_id,
|
bind_source_id,
|
||||||
bind_owner_id,
|
bind_owner_id,
|
||||||
bind_property_id,
|
bind_property_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.__update_internal_child()
|
||||||
|
|
||||||
|
if self.info.internal_child:
|
||||||
|
self.project.history_pop()
|
||||||
|
|
||||||
if self._init is False:
|
if self._init is False:
|
||||||
self.object._property_changed(self)
|
self.object._property_changed(self)
|
||||||
|
|
||||||
@ -159,7 +278,7 @@ 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, bind_property)
|
self.__update_values(self.value, self.translatable, self.translation_context, self.translation_comments, bind_property)
|
||||||
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):
|
||||||
|
66
cambalache/cmb_property_info.py
Normal file
66
cambalache/cmb_property_info.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#
|
||||||
|
# Cambalache Property Type Info wrapper
|
||||||
|
#
|
||||||
|
# Copyright (C) 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
|
||||||
|
from .cmb_objects_base import CmbBasePropertyInfo
|
||||||
|
|
||||||
|
from cambalache import getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmbPropertyInfo(CmbBasePropertyInfo):
|
||||||
|
internal_child = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.is_a11y = CmbPropertyInfo.type_is_accessible(self.owner_id)
|
||||||
|
self.a11y_property_id = CmbPropertyInfo.accessible_property_remove_prefix(self.owner_id, self.property_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def type_is_accessible(cls, owner_id):
|
||||||
|
return owner_id in [
|
||||||
|
"CmbAccessibleProperty",
|
||||||
|
"CmbAccessibleRelation",
|
||||||
|
"CmbAccessibleState",
|
||||||
|
"CmbAccessibleAction"
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def accessible_property_remove_prefix(cls, owner_id, property_id):
|
||||||
|
prefix = {
|
||||||
|
"CmbAccessibleProperty": "cmb-a11y-property-",
|
||||||
|
"CmbAccessibleRelation": "cmb-a11y-relation-",
|
||||||
|
"CmbAccessibleState": "cmb-a11y-state-",
|
||||||
|
"CmbAccessibleAction": "cmb-a11y-action-"
|
||||||
|
}.get(owner_id, None)
|
||||||
|
|
||||||
|
if prefix is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# A11y property name without prefix
|
||||||
|
return property_id.removeprefix(prefix)
|
||||||
|
|
@ -28,8 +28,9 @@ from gi.repository import GObject, Gtk
|
|||||||
from .cmb_object import CmbObject
|
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_objects_base import CmbPropertyInfo
|
from .cmb_property_info import CmbPropertyInfo
|
||||||
from .control import CmbObjectChooser, CmbFlagsEntry
|
from .control import CmbObjectChooser, CmbFlagsEntry
|
||||||
|
from cambalache import _
|
||||||
|
|
||||||
|
|
||||||
class CmbPropertyLabel(Gtk.Button):
|
class CmbPropertyLabel(Gtk.Button):
|
||||||
@ -48,15 +49,17 @@ class CmbPropertyLabel(Gtk.Button):
|
|||||||
raise Exception("CmbPropertyLabel requires prop or layout_prop to be set")
|
raise Exception("CmbPropertyLabel requires prop or layout_prop to be set")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.label = Gtk.Label(xalign=0, visible=True)
|
self.props.focus_on_click = False
|
||||||
box = Gtk.Box(visible=True)
|
self.label = Gtk.Label(halign=Gtk.Align.START, valign=Gtk.Align.CENTER)
|
||||||
|
box = Gtk.Box()
|
||||||
|
|
||||||
# Update label status
|
# Update label status
|
||||||
if self.prop:
|
if self.prop:
|
||||||
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL, visible=True)
|
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL)
|
||||||
box.append(self.bind_icon)
|
box.append(self.bind_icon)
|
||||||
|
|
||||||
self.label.props.label = self.prop.property_id
|
# A11y properties are prefixed to avoid clashes, do not show prefix here
|
||||||
|
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::value", lambda o, p: self.__update_property_label())
|
||||||
@ -125,13 +128,23 @@ class CmbPropertyLabel(Gtk.Button):
|
|||||||
self.__update_property_label()
|
self.__update_property_label()
|
||||||
popover.popdown()
|
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)
|
popover = Gtk.Popover(position=Gtk.PositionType.LEFT, css_classes=["cmb-binding-popover"])
|
||||||
popover.set_parent(self)
|
popover.set_parent(self)
|
||||||
|
|
||||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4, visible=True)
|
label = Gtk.Label(label="<b>Property Binding</b>", use_markup=True, halign=Gtk.Align.START, hexpand=True)
|
||||||
|
close = Gtk.Button(icon_name="window-close-symbolic", halign=Gtk.Align.END, css_classes=["close"])
|
||||||
|
close.connect("clicked", self.__on_close_clicked, popover)
|
||||||
|
|
||||||
grid.attach(Gtk.Label(label="<b>Property Binding</b>", use_markup=True, visible=True, xalign=0), 0, 0, 2, 1)
|
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
|
# 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)
|
bind_source, bind_property = self.__find_bind_source_property(self.prop.bind_source_id, self.prop.bind_property_id)
|
||||||
@ -161,18 +174,16 @@ class CmbPropertyLabel(Gtk.Button):
|
|||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
|
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
|
||||||
editor.props.visible = True
|
label = Gtk.Label(label=prop_label, xalign=0)
|
||||||
|
|
||||||
label = Gtk.Label(label=prop_label, xalign=0, visible=True)
|
|
||||||
|
|
||||||
grid.attach(label, 0, i, 1, 1)
|
grid.attach(label, 0, i, 1, 1)
|
||||||
grid.attach(editor, 1, i, 1, 1)
|
grid.attach(editor, 1, i, 1, 1)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
clear = Gtk.Button(label="Clear", visible=True, halign=Gtk.Align.END)
|
clear = Gtk.Button(label=_("Clear"), halign=Gtk.Align.END)
|
||||||
clear.connect("clicked", self.__on_clear_clicked, popover)
|
clear.connect("clicked", self.__on_clear_clicked, popover)
|
||||||
|
|
||||||
grid.attach(clear, 0, i, 2, 1)
|
grid.attach(clear, 0, i, 2, 1)
|
||||||
|
|
||||||
object_editor.grab_focus()
|
object_editor.grab_focus()
|
||||||
|
|
||||||
popover.set_child(grid)
|
popover.set_child(grid)
|
||||||
@ -212,6 +223,9 @@ class CmbPropertyChooser(Gtk.ComboBoxText):
|
|||||||
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
|
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
|
||||||
info = prop.info
|
info = prop.info
|
||||||
|
|
||||||
|
if info.is_a11y:
|
||||||
|
continue
|
||||||
|
|
||||||
# Ignore construct only properties
|
# Ignore construct only properties
|
||||||
if info.construct_only:
|
if info.construct_only:
|
||||||
continue
|
continue
|
||||||
|
@ -138,13 +138,17 @@ class CmbSignalEditor(Gtk.Box):
|
|||||||
if data_obj:
|
if data_obj:
|
||||||
signal.user_data = data_obj.object_id
|
signal.user_data = data_obj.object_id
|
||||||
name = data_obj.name
|
name = data_obj.name
|
||||||
|
signal.swap = True
|
||||||
else:
|
else:
|
||||||
signal.user_data = 0
|
signal.user_data = 0
|
||||||
|
signal.swap = False
|
||||||
|
self.treestore[iter_][Col.SWAP.value] = signal.swap
|
||||||
name = ""
|
name = ""
|
||||||
|
|
||||||
self.treestore[iter_][Col.USER_DATA.value] = name
|
self.treestore[iter_][Col.USER_DATA.value] = name
|
||||||
else:
|
else:
|
||||||
signal.user_data = 0
|
signal.user_data = 0
|
||||||
|
signal.swap = False
|
||||||
self.treestore[iter_][Col.USER_DATA.value] = ""
|
self.treestore[iter_][Col.USER_DATA.value] = ""
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_swap_toggled")
|
@Gtk.Template.Callback("on_swap_toggled")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_signal_editor.ui -->
|
<!-- interface-name cmb_signal_editor.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -37,9 +37,6 @@
|
|||||||
<property name="focusable">1</property>
|
<property name="focusable">1</property>
|
||||||
<property name="model">treestore</property>
|
<property name="model">treestore</property>
|
||||||
<property name="tooltip-column">9</property>
|
<property name="tooltip-column">9</property>
|
||||||
<child internal-child="selection">
|
|
||||||
<object class="GtkTreeSelection"/>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkTreeViewColumn" id="signal_id_column">
|
<object class="GtkTreeViewColumn" id="signal_id_column">
|
||||||
<property name="min-width">64</property>
|
<property name="min-width">64</property>
|
||||||
|
266
cambalache/cmb_tree_expander.py
Normal file
266
cambalache/cmb_tree_expander.py
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
#
|
||||||
|
# CmbTreeExpander
|
||||||
|
#
|
||||||
|
# Copyright (C) 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, Gdk, Gtk, Graphene
|
||||||
|
|
||||||
|
from .cmb_ui import CmbUI
|
||||||
|
from .cmb_object import CmbObject
|
||||||
|
from .cmb_css import CmbCSS
|
||||||
|
from .cmb_path import CmbPath
|
||||||
|
from cambalache import _
|
||||||
|
|
||||||
|
|
||||||
|
class CmbTreeExpander(Gtk.TreeExpander):
|
||||||
|
__gtype_name__ = "CmbTreeExpander"
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.__project = None
|
||||||
|
self.__drop_before = None
|
||||||
|
|
||||||
|
self.label = Gtk.Inscription(hexpand=True)
|
||||||
|
self.set_child(self.label)
|
||||||
|
|
||||||
|
self.__parent = None
|
||||||
|
self.__drag_source = None
|
||||||
|
self.__drop_target = None
|
||||||
|
|
||||||
|
def update_bind(self):
|
||||||
|
item = self.props.item
|
||||||
|
|
||||||
|
self.__parent = self.props.parent
|
||||||
|
# Drag source
|
||||||
|
self.__drag_source = Gtk.DragSource()
|
||||||
|
self.__drag_source.connect("prepare", self.__on_drag_prepare)
|
||||||
|
self.__drag_source.connect("drag-begin", self.__on_drag_begin)
|
||||||
|
self.__parent.add_controller(self.__drag_source)
|
||||||
|
|
||||||
|
# Drop target
|
||||||
|
self.__drop_target = Gtk.DropTarget.new(type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY)
|
||||||
|
self.__drop_target.set_gtypes([CmbObject, CmbUI])
|
||||||
|
self.__drop_target.connect("accept", self.__on_drop_accept)
|
||||||
|
self.__drop_target.connect("motion", self.__on_drop_motion)
|
||||||
|
self.__drop_target.connect("drop", self.__on_drop_drop)
|
||||||
|
self.__parent.add_controller(self.__drop_target)
|
||||||
|
|
||||||
|
# Handle label
|
||||||
|
self.__update_label(item)
|
||||||
|
item.connect("notify::display-name", self.__on_item_display_name_notify)
|
||||||
|
|
||||||
|
self.__project = item.project
|
||||||
|
|
||||||
|
if isinstance(item, CmbCSS):
|
||||||
|
self.props.hide_expander = True
|
||||||
|
return
|
||||||
|
elif isinstance(item, CmbPath):
|
||||||
|
self.props.hide_expander = False
|
||||||
|
self.add_css_class("cmb-path" if item.path else "cmb-unsaved-path")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.props.hide_expander = item.props.n_items == 0
|
||||||
|
item.connect("notify::n-items", self.__on_item_n_items_notify)
|
||||||
|
|
||||||
|
def clear_bind(self):
|
||||||
|
item = self.props.item
|
||||||
|
|
||||||
|
if self.__parent:
|
||||||
|
self.__parent.remove_controller(self.__drag_source)
|
||||||
|
self.__parent.remove_controller(self.__drop_target)
|
||||||
|
self.__parent = None
|
||||||
|
self.__drag_source = None
|
||||||
|
self.__drop_target = None
|
||||||
|
|
||||||
|
self.remove_css_class("cmb-path")
|
||||||
|
self.remove_css_class("cmb-unsaved-path")
|
||||||
|
|
||||||
|
item.disconnect_by_func(self.__on_item_display_name_notify)
|
||||||
|
|
||||||
|
if isinstance(item, CmbCSS) or isinstance(item, CmbPath):
|
||||||
|
return
|
||||||
|
|
||||||
|
item.disconnect_by_func(self.__on_item_n_items_notify)
|
||||||
|
|
||||||
|
def __on_item_n_items_notify(self, item, pspec):
|
||||||
|
self.props.hide_expander = item.props.n_items == 0
|
||||||
|
|
||||||
|
def __on_item_display_name_notify(self, item, pspec):
|
||||||
|
self.__update_label(item)
|
||||||
|
|
||||||
|
def __update_label(self, item):
|
||||||
|
self.label.set_markup(item.props.display_name or "")
|
||||||
|
|
||||||
|
# Drop target callbacks
|
||||||
|
def __get_drop_before(self, widget, x, y):
|
||||||
|
h = widget.get_height()
|
||||||
|
|
||||||
|
if y < h/4:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if y > h * 0.8:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __on_object_drop_accept(self, drop, item):
|
||||||
|
origin_item = drop.get_drag()._item
|
||||||
|
|
||||||
|
if origin_item == item:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not isinstance(item, CmbObject):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ignore if its the same parent
|
||||||
|
if origin_item.parent_id == item.object_id:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return origin_item
|
||||||
|
|
||||||
|
def __on_drop_accept(self, target, drop):
|
||||||
|
item = self.props.item
|
||||||
|
origin_item = drop.get_drag()._item
|
||||||
|
|
||||||
|
if isinstance(item, CmbObject):
|
||||||
|
origin_item = self.__on_object_drop_accept(drop, item)
|
||||||
|
|
||||||
|
self.__drop_before = None
|
||||||
|
|
||||||
|
if origin_item is None or item.parent is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.__project._check_can_add(origin_item.type_id, item.parent.type_id)
|
||||||
|
elif isinstance(item, CmbUI):
|
||||||
|
if origin_item == item:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Ignore if its the same UI and item is already a toplevel
|
||||||
|
if origin_item.ui_id == item.ui_id and origin_item.parent_id is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __on_drop_motion(self, target, x, y):
|
||||||
|
item = self.props.item
|
||||||
|
|
||||||
|
if isinstance(item, CmbObject):
|
||||||
|
row_widget = target.get_widget()
|
||||||
|
|
||||||
|
drop_before = self.__get_drop_before(row_widget, x, y)
|
||||||
|
|
||||||
|
if self.__drop_before == drop_before:
|
||||||
|
return Gdk.DragAction.COPY
|
||||||
|
|
||||||
|
row_widget.remove_css_class("drop-before")
|
||||||
|
row_widget.remove_css_class("drop-after")
|
||||||
|
|
||||||
|
self.__drop_before = drop_before
|
||||||
|
|
||||||
|
if drop_before is not None:
|
||||||
|
row_widget.add_css_class("drop-before" if drop_before else "drop-after")
|
||||||
|
|
||||||
|
return Gdk.DragAction.COPY
|
||||||
|
|
||||||
|
def __on_drop_drop(self, target, origin_item, x, y):
|
||||||
|
row_widget = target.get_widget()
|
||||||
|
item = self.props.item
|
||||||
|
|
||||||
|
if isinstance(item, CmbObject):
|
||||||
|
drop_before = self.__get_drop_before(row_widget, x, y)
|
||||||
|
|
||||||
|
if drop_before is None:
|
||||||
|
# TODO: handle dragging from one UI to another
|
||||||
|
if origin_item.ui_id != item.ui_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
if origin_item.parent_id != item.object_id:
|
||||||
|
self.__project.history_push(
|
||||||
|
_("Move {name} to {target}").format(name=origin_item.display_name, target=item.display_name)
|
||||||
|
)
|
||||||
|
origin_item.parent_id = item.object_id
|
||||||
|
self.__project.history_pop()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if origin_item.parent_id != item.parent_id:
|
||||||
|
if drop_before:
|
||||||
|
msg = _("Move {name} before {target}").format(name=origin_item.display_name, target=item.display_name)
|
||||||
|
else:
|
||||||
|
msg = _("Move {name} after {target}").format(name=origin_item.display_name, target=item.display_name)
|
||||||
|
|
||||||
|
self.__project.history_push(msg)
|
||||||
|
|
||||||
|
origin_item.parent_id = item.parent_id
|
||||||
|
else:
|
||||||
|
msg = None
|
||||||
|
|
||||||
|
parent = item.parent
|
||||||
|
|
||||||
|
origin_position = origin_item.position
|
||||||
|
target_position = item.position
|
||||||
|
|
||||||
|
if origin_position > target_position:
|
||||||
|
if drop_before:
|
||||||
|
position = item.position
|
||||||
|
else:
|
||||||
|
position = item.position + 1
|
||||||
|
else:
|
||||||
|
if drop_before:
|
||||||
|
position = item.position - 1
|
||||||
|
else:
|
||||||
|
position = item.position
|
||||||
|
|
||||||
|
parent.reorder_child(origin_item, position)
|
||||||
|
|
||||||
|
if msg:
|
||||||
|
self.__project.history_pop()
|
||||||
|
|
||||||
|
self.__project.set_selection([origin_item])
|
||||||
|
elif isinstance(item, CmbUI):
|
||||||
|
if origin_item.ui_id == item.ui_id:
|
||||||
|
self.__project.history_push(_("Move {name} as toplevel").format(name=origin_item.display_name))
|
||||||
|
origin_item.parent_id = 0
|
||||||
|
self.__project.history_pop()
|
||||||
|
else:
|
||||||
|
# TODO: Use copy/paste to move across UI files
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Drag Source callbacks
|
||||||
|
def __on_drag_prepare(self, drag_source, x, y):
|
||||||
|
item = self.props.item
|
||||||
|
|
||||||
|
# Only CmbObject start a drag
|
||||||
|
if not isinstance(item, CmbObject):
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.__drag_point = Graphene.Point()
|
||||||
|
self.__drag_point.x = x
|
||||||
|
self.__drag_point.y = y
|
||||||
|
return Gdk.ContentProvider.new_for_value(self.props.item)
|
||||||
|
|
||||||
|
def __on_drag_begin(self, drag_source, drag):
|
||||||
|
drag._item = self.props.item
|
||||||
|
valid, p = self.__parent.compute_point(self.label, self.__drag_point)
|
||||||
|
if valid:
|
||||||
|
drag_source.set_icon(Gtk.WidgetPaintable.new(self.label), p.x, p.y)
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_type_chooser.ui -->
|
<!-- interface-name cmb_type_chooser.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
from gi.repository import GObject, Gtk
|
from gi.repository import GLib, GObject, Gio, Gtk
|
||||||
|
|
||||||
from .cmb_project import CmbProject
|
from .cmb_project import CmbProject
|
||||||
from .cmb_type_info import CmbTypeInfo
|
from .cmb_type_info import CmbTypeInfo
|
||||||
@ -48,13 +48,12 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
scrolledwindow = Gtk.Template.Child()
|
scrolledwindow = Gtk.Template.Child()
|
||||||
treeview = Gtk.Template.Child()
|
listview = Gtk.Template.Child()
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__project = None
|
self.__project = None
|
||||||
self.__model = None
|
|
||||||
self._search_text = ""
|
self._search_text = ""
|
||||||
self._filter = None
|
self.__model = None
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@ -94,10 +93,6 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def __store_append_info(self, store, info):
|
|
||||||
if store:
|
|
||||||
store.append([info.type_id, info.type_id.lower(), info, True])
|
|
||||||
|
|
||||||
def __model_from_project(self, project):
|
def __model_from_project(self, project):
|
||||||
if project is None:
|
if project is None:
|
||||||
return None
|
return None
|
||||||
@ -113,7 +108,12 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
order = {"toplevel": 0, "layout": 1, "control": 2, "display": 3, "model": 4}
|
order = {"toplevel": 0, "layout": 1, "control": 2, "display": 3, "model": 4}
|
||||||
|
|
||||||
# type_id, type_id.lower(), CmbTypeInfo, sensitive
|
# type_id, type_id.lower(), CmbTypeInfo, sensitive
|
||||||
store = Gtk.ListStore(str, str, CmbTypeInfo, bool)
|
store = Gio.ListStore()
|
||||||
|
|
||||||
|
custom_filter = Gtk.CustomFilter()
|
||||||
|
custom_filter.set_filter_func(self.__custom_filter_func, None)
|
||||||
|
filter_model = Gtk.FilterListModel(model=store, filter=custom_filter)
|
||||||
|
|
||||||
infos = []
|
infos = []
|
||||||
|
|
||||||
for key in project.type_info:
|
for key in project.type_info:
|
||||||
@ -135,15 +135,15 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
if show_categories and last_category != i.category:
|
if show_categories and last_category != i.category:
|
||||||
last_category = i.category
|
last_category = i.category
|
||||||
category = categories.get(i.category, _("Others"))
|
category = categories.get(i.category, _("Others"))
|
||||||
store.append([f"<i>▾ {category}</i>", "", None, False])
|
store.append(CmbTypeInfo(type_id=f"<i><b>▾ {category}</b></i>"))
|
||||||
|
|
||||||
self.__store_append_info(store, i)
|
store.append(i)
|
||||||
|
|
||||||
# Special case External object type
|
# Special case External object type
|
||||||
if show_categories or self.uncategorized_only:
|
if show_categories or self.uncategorized_only:
|
||||||
self.__store_append_info(store, project.type_info[constants.EXTERNAL_TYPE])
|
store.append(project.type_info[constants.EXTERNAL_TYPE])
|
||||||
|
|
||||||
return store
|
return filter_model
|
||||||
|
|
||||||
@GObject.Property(type=CmbProject)
|
@GObject.Property(type=CmbProject)
|
||||||
def project(self):
|
def project(self):
|
||||||
@ -154,21 +154,15 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
if self.__project:
|
if self.__project:
|
||||||
self.__project.disconnect_by_func(self.__on_type_info_added)
|
self.__project.disconnect_by_func(self.__on_type_info_added)
|
||||||
self.__project.disconnect_by_func(self.__on_type_info_removed)
|
self.__project.disconnect_by_func(self.__on_type_info_removed)
|
||||||
self.__project.disconnect_by_func(self.__on_type_info_changed)
|
|
||||||
|
|
||||||
self.__project = project
|
self.__project = project
|
||||||
|
|
||||||
self.__model = self.__model_from_project(project)
|
self.__model = self.__model_from_project(project)
|
||||||
self._filter = Gtk.TreeModelFilter(child_model=self.__model) if self.__model else None
|
self.listview.set_model(Gtk.NoSelection(model=self.__model))
|
||||||
if self._filter:
|
|
||||||
self._filter.set_visible_func(self.__visible_func)
|
|
||||||
|
|
||||||
self.treeview.props.model = self._filter
|
|
||||||
|
|
||||||
if project:
|
if project:
|
||||||
project.connect("type-info-added", self.__on_type_info_added)
|
project.connect("type-info-added", self.__on_type_info_added)
|
||||||
project.connect("type-info-removed", self.__on_type_info_removed)
|
project.connect("type-info-removed", self.__on_type_info_removed)
|
||||||
project.connect("type-info-changed", self.__on_type_info_changed)
|
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_searchentry_activate")
|
@Gtk.Template.Callback("on_searchentry_activate")
|
||||||
def __on_searchentry_activate(self, entry):
|
def __on_searchentry_activate(self, entry):
|
||||||
@ -181,61 +175,34 @@ class CmbTypeChooserWidget(Gtk.Box):
|
|||||||
@Gtk.Template.Callback("on_searchentry_search_changed")
|
@Gtk.Template.Callback("on_searchentry_search_changed")
|
||||||
def __on_searchentry_search_changed(self, entry):
|
def __on_searchentry_search_changed(self, entry):
|
||||||
self._search_text = entry.props.text.lower()
|
self._search_text = entry.props.text.lower()
|
||||||
self._filter.refilter()
|
self.__model.props.filter.changed(Gtk.FilterChange.DIFFERENT)
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_treeview_row_activated")
|
@Gtk.Template.Callback("on_listview_activate")
|
||||||
def __on_treeview_row_activated(self, treeview, path, column):
|
def __on_listview_activate(self, listview, position):
|
||||||
model = treeview.props.model
|
info = self.__model.get_item(position)
|
||||||
info = model[model.get_iter(path)][2]
|
|
||||||
|
|
||||||
if info is not None:
|
if info is not None and info.project:
|
||||||
self.emit("type-selected", info)
|
self.emit("type-selected", info)
|
||||||
|
|
||||||
def __visible_func(self, model, iter, data):
|
def __custom_filter_func(self, info, data):
|
||||||
type_id, type_id_lower, info, sensitive = model[iter]
|
return info.type_id.lower().find(self._search_text) >= 0
|
||||||
|
|
||||||
# Always show categories if we are not searching
|
|
||||||
if self._search_text == "" and info is None:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return type_id_lower.find(self._search_text) >= 0
|
|
||||||
|
|
||||||
def __on_type_info_added(self, project, info):
|
def __on_type_info_added(self, project, info):
|
||||||
if self.__model is None:
|
if self.__model is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Append new type info
|
# Append new type info
|
||||||
# TODO: insert in order
|
|
||||||
if self.__type_info_should_append(info):
|
if self.__type_info_should_append(info):
|
||||||
self.__store_append_info(self.__model, info)
|
self.__model.props.model.insert_sorted(info, lambda a, b, d: GLib.strcmp0(a.type_id, b.type_id), None)
|
||||||
|
|
||||||
def __on_type_info_removed(self, project, info):
|
def __on_type_info_removed(self, project, info):
|
||||||
if self.__model is None:
|
if self.__model is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Find info and remove it from model
|
# Find info and remove it from model
|
||||||
for row in self.__model:
|
found, position = self.__model.props.model.find(info)
|
||||||
if info == row[2]:
|
if found:
|
||||||
self.__model.remove(row.iter)
|
self.__model.props.model.remove(position)
|
||||||
|
|
||||||
def __on_type_info_changed(self, project, info):
|
|
||||||
if self.__model is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
info_row = None
|
|
||||||
|
|
||||||
# Find info and update it from model
|
|
||||||
for row in self.__model:
|
|
||||||
if info == row[2]:
|
|
||||||
info_row = row
|
|
||||||
break
|
|
||||||
|
|
||||||
if info_row is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Update Type Name
|
|
||||||
info_row[0] = info.type_id
|
|
||||||
info_row[1] = info.type_id.lower()
|
|
||||||
|
|
||||||
|
|
||||||
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, "CmbTypeChooserWidget")
|
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, "CmbTypeChooserWidget")
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_type_chooser_widget.ui -->
|
<!-- interface-name cmb_type_chooser_widget.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
@ -15,34 +15,37 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="scrolledwindow">
|
<object class="GtkScrolledWindow" id="scrolledwindow">
|
||||||
<property name="child">
|
<property name="hexpand">True</property>
|
||||||
<object class="GtkTreeView" id="treeview">
|
<property name="propagate-natural-height">True</property>
|
||||||
<property name="activate-on-single-click">1</property>
|
<property name="propagate-natural-width">True</property>
|
||||||
<property name="enable-search">0</property>
|
<property name="vexpand">True</property>
|
||||||
<property name="headers-visible">0</property>
|
<child>
|
||||||
<signal name="row-activated" handler="on_treeview_row_activated"/>
|
<object class="GtkListView" id="listview">
|
||||||
<child internal-child="selection">
|
<property name="factory">
|
||||||
<object class="GtkTreeSelection" id="treeview-selection"/>
|
<object class="GtkBuilderListItemFactory">
|
||||||
</child>
|
<property name="bytes"><![CDATA[<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<child>
|
<interface>
|
||||||
<object class="GtkTreeViewColumn" id="column_adaptor">
|
<template class="GtkListItem" parent="GObject">
|
||||||
<child>
|
<property name="child">
|
||||||
<object class="GtkCellRendererText" id="adaptor_cell"/>
|
<object class="GtkInscription">
|
||||||
<!-- Custom child fragments -->
|
<binding name="markup">
|
||||||
<attributes>
|
<lookup name="type_id" type="CmbTypeInfo">
|
||||||
<attribute name="markup">0</attribute>
|
<lookup name="item">GtkListItem</lookup>
|
||||||
<attribute name="sensitive">3</attribute>
|
</lookup>
|
||||||
</attributes>
|
</binding>
|
||||||
</child>
|
</object>
|
||||||
|
</property>
|
||||||
|
</template>
|
||||||
|
</interface>]]></property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="single-click-activate">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<signal name="activate" handler="on_listview_activate"/>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</child>
|
||||||
<property name="hscrollbar-policy">never</property>
|
|
||||||
<property name="min-content-height">256</property>
|
|
||||||
<property name="propagate-natural-height">1</property>
|
|
||||||
<property name="propagate-natural-width">1</property>
|
|
||||||
<property name="window-placement">bottom-left</property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</template>
|
</template>
|
||||||
|
@ -29,10 +29,11 @@ from .cmb_objects_base import (
|
|||||||
CmbBaseTypeInfo,
|
CmbBaseTypeInfo,
|
||||||
CmbBaseTypeDataInfo,
|
CmbBaseTypeDataInfo,
|
||||||
CmbBaseTypeDataArgInfo,
|
CmbBaseTypeDataArgInfo,
|
||||||
|
CmbBaseTypeInternalChildInfo,
|
||||||
CmbTypeChildInfo,
|
CmbTypeChildInfo,
|
||||||
CmbPropertyInfo,
|
|
||||||
CmbSignalInfo,
|
CmbSignalInfo,
|
||||||
)
|
)
|
||||||
|
from .cmb_property_info import CmbPropertyInfo
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -59,19 +60,33 @@ class CmbTypeDataInfo(CmbBaseTypeDataInfo):
|
|||||||
return f"CmbTypeDataArgInfo<{self.owner_id}>::{self.key}"
|
return f"CmbTypeDataArgInfo<{self.owner_id}>::{self.key}"
|
||||||
|
|
||||||
|
|
||||||
|
class CmbTypeInternalChildInfo(CmbBaseTypeInternalChildInfo):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.children = {}
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"CmbTypeInternalChildInfo<{self.type_id}>::{self.internal_child_id}"
|
||||||
|
|
||||||
|
|
||||||
class CmbTypeInfo(CmbBaseTypeInfo):
|
class CmbTypeInfo(CmbBaseTypeInfo):
|
||||||
|
__gtype_name__ = "CmbTypeInfo"
|
||||||
|
|
||||||
type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
||||||
parent_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
parent_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
||||||
parent = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
parent = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
if self.project is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.hierarchy = self.__init_hierarchy()
|
self.hierarchy = self.__init_hierarchy()
|
||||||
self.interfaces = self.__init_interfaces()
|
self.interfaces = self.__init_interfaces()
|
||||||
self.properties = self.__init_properties_signals(CmbPropertyInfo, "property")
|
self.properties = self.__init_properties_signals(CmbPropertyInfo, "property")
|
||||||
self.signals = self.__init_properties_signals(CmbSignalInfo, "signal")
|
self.signals = self.__init_properties_signals(CmbSignalInfo, "signal")
|
||||||
self.data = self.__init_data()
|
self.data = self.__init_data()
|
||||||
|
self.internal_children = self.__init_internal_children()
|
||||||
self.child_constraint, self.child_type_shortcuts = self.__init_child_constraint()
|
self.child_constraint, self.child_type_shortcuts = self.__init_child_constraint()
|
||||||
|
|
||||||
if self.parent_id == "enum":
|
if self.parent_id == "enum":
|
||||||
@ -140,7 +155,7 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
|||||||
|
|
||||||
c = self.project.db.cursor()
|
c = self.project.db.cursor()
|
||||||
for row in c.execute(f"SELECT * FROM {table} WHERE owner_id=? ORDER BY {table}_id;", (self.type_id,)):
|
for row in c.execute(f"SELECT * FROM {table} WHERE owner_id=? ORDER BY {table}_id;", (self.type_id,)):
|
||||||
retval[row[1]] = Klass.from_row(self, *row)
|
retval[row[1]] = Klass.from_row(self.project, *row)
|
||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
return retval
|
return retval
|
||||||
@ -149,14 +164,14 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
|||||||
args = {}
|
args = {}
|
||||||
children = {}
|
children = {}
|
||||||
parent_id = parent_id if parent_id is not None else 0
|
parent_id = parent_id if parent_id is not None else 0
|
||||||
retval = CmbTypeDataInfo.from_row(self, owner_id, data_id, parent_id, key, type_id, translatable)
|
retval = CmbTypeDataInfo.from_row(self.project, owner_id, data_id, parent_id, key, type_id, translatable)
|
||||||
|
|
||||||
c = self.project.db.cursor()
|
c = self.project.db.cursor()
|
||||||
|
|
||||||
# Collect Arguments
|
# Collect Arguments
|
||||||
for row in c.execute("SELECT * FROM type_data_arg WHERE owner_id=? AND data_id=?;", (owner_id, data_id)):
|
for row in c.execute("SELECT * FROM type_data_arg WHERE owner_id=? AND data_id=?;", (owner_id, data_id)):
|
||||||
_key = row[2]
|
_key = row[2]
|
||||||
args[_key] = CmbTypeDataArgInfo.from_row(self, *row)
|
args[_key] = CmbTypeDataArgInfo.from_row(self.project, *row)
|
||||||
|
|
||||||
# Recurse children
|
# Recurse children
|
||||||
for row in c.execute("SELECT * FROM type_data WHERE owner_id=? AND parent_id=?;", (owner_id, data_id)):
|
for row in c.execute("SELECT * FROM type_data WHERE owner_id=? AND parent_id=?;", (owner_id, data_id)):
|
||||||
@ -183,6 +198,52 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
|||||||
c.close()
|
c.close()
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
def __type_get_internal_child(self, type_id, internal_child_id, internal_parent_id, internal_type, creation_property_id):
|
||||||
|
retval = CmbTypeInternalChildInfo.from_row(
|
||||||
|
self.project,
|
||||||
|
type_id,
|
||||||
|
internal_child_id,
|
||||||
|
internal_parent_id,
|
||||||
|
internal_type,
|
||||||
|
creation_property_id
|
||||||
|
)
|
||||||
|
children = {}
|
||||||
|
|
||||||
|
c = self.project.db.cursor()
|
||||||
|
|
||||||
|
# Recurse children
|
||||||
|
for row in c.execute(
|
||||||
|
"SELECT * FROM type_internal_child WHERE type_id=? AND internal_parent_id=?;",
|
||||||
|
(type_id, internal_child_id)
|
||||||
|
):
|
||||||
|
key = row[1]
|
||||||
|
children[key] = self.__type_get_internal_child(*row)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
retval.children = children
|
||||||
|
|
||||||
|
# Internal child back reference in property
|
||||||
|
if creation_property_id:
|
||||||
|
if creation_property_id in self.properties:
|
||||||
|
self.properties[creation_property_id].internal_child = retval
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def __init_internal_children(self):
|
||||||
|
retval = {}
|
||||||
|
|
||||||
|
c = self.project.db.cursor()
|
||||||
|
for row in c.execute(
|
||||||
|
"SELECT * FROM type_internal_child WHERE type_id=? AND internal_parent_id IS NULL ORDER BY internal_child_id;",
|
||||||
|
(self.type_id,)
|
||||||
|
):
|
||||||
|
key = row[1]
|
||||||
|
retval[key] = self.__type_get_internal_child(*row)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
return retval
|
||||||
|
|
||||||
def __init_child_constraint(self):
|
def __init_child_constraint(self):
|
||||||
retval = {}
|
retval = {}
|
||||||
shortcuts = []
|
shortcuts = []
|
||||||
@ -270,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
|
||||||
|
|
||||||
@ -279,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
|
||||||
|
|
||||||
|
@ -23,8 +23,10 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
from gi.repository import GObject, Gio
|
from gi.repository import GObject, Gio
|
||||||
|
|
||||||
|
from .cmb_path import CmbPath
|
||||||
from .cmb_list_error import CmbListError
|
from .cmb_list_error import CmbListError
|
||||||
from .cmb_objects_base import CmbBaseUI, CmbBaseObject
|
from .cmb_objects_base import CmbBaseUI, CmbBaseObject
|
||||||
from cambalache import getLogger, _
|
from cambalache import getLogger, _
|
||||||
@ -37,6 +39,8 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
|||||||
"library-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
"library-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@ -61,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 = {}
|
||||||
|
|
||||||
@ -124,19 +132,21 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
|||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_display_name(cls, ui_id, filename):
|
||||||
|
return os.path.basename(filename) if filename else _("Unnamed {ui_id}").format(ui_id=ui_id)
|
||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
if self.filename:
|
filename = self.filename
|
||||||
return self.filename
|
|
||||||
|
|
||||||
template_id = self.template_id
|
template_id = self.template_id
|
||||||
|
|
||||||
if template_id:
|
if filename is None and template_id:
|
||||||
template = self.project.get_object_by_id(self.ui_id, template_id)
|
template = self.project.get_object_by_id(self.ui_id, template_id)
|
||||||
if template:
|
if template:
|
||||||
return template.name
|
return template.name
|
||||||
|
|
||||||
return _("Unnamed {ui_id}").format(ui_id=self.ui_id)
|
return CmbUI.get_display_name(self.ui_id, filename)
|
||||||
|
|
||||||
def __get_infered_target(self, library_id):
|
def __get_infered_target(self, library_id):
|
||||||
ui_id = self.ui_id
|
ui_id = self.ui_id
|
||||||
@ -183,10 +193,17 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
|||||||
|
|
||||||
# This query should use auto index from UNIQUE constraint
|
# This query should use auto index from UNIQUE constraint
|
||||||
retval = self.db_get(
|
retval = self.db_get(
|
||||||
"SELECT object_id FROM object WHERE ui_id=? AND parent_id IS NULL AND position=?;",
|
"""
|
||||||
(ui_id, position)
|
SELECT object_id
|
||||||
|
FROM (
|
||||||
|
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||||
|
FROM object
|
||||||
|
WHERE ui_id=? AND parent_id IS NULL
|
||||||
|
)
|
||||||
|
WHERE rownum=?;
|
||||||
|
""",
|
||||||
|
(ui_id, position+1)
|
||||||
)
|
)
|
||||||
|
|
||||||
if retval is not None:
|
if retval is not None:
|
||||||
return self.project.get_object_by_id(ui_id, retval)
|
return self.project.get_object_by_id(ui_id, 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()
|
||||||
|
|
||||||
@ -77,10 +78,17 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
|
|
||||||
self.set_sensitive(True)
|
self.set_sensitive(True)
|
||||||
self.template_id.object = obj
|
self.template_id.object = obj
|
||||||
|
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",
|
||||||
@ -88,37 +96,42 @@ class CmbUIEditor(Gtk.Grid):
|
|||||||
)
|
)
|
||||||
self._bindings.append(binding)
|
self._bindings.append(binding)
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_remove_button_clicked")
|
if obj.project.target_tk == "gtk-4.0":
|
||||||
def __on_remove_button_clicked(self, button):
|
self.filename.mime_types = "application/x-gtk-builder;text/x-blueprint"
|
||||||
self.emit("remove-ui")
|
|
||||||
|
|
||||||
@Gtk.Template.Callback("on_export_button_clicked")
|
# filename -> format
|
||||||
def __on_export_button_clicked(self, button):
|
binding = obj.bind_property(
|
||||||
self.emit("export-ui")
|
"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)
|
||||||
|
|
||||||
@GObject.Signal(
|
self.format.show()
|
||||||
flags=GObject.SignalFlags.RUN_LAST,
|
self.format.set_sensitive(bool(obj.filename))
|
||||||
return_type=bool,
|
else:
|
||||||
arg_types=(),
|
self.filename.mime_types = "application/x-gtk-builder;application/x-glade"
|
||||||
accumulator=GObject.signal_accumulator_true_handled,
|
self.format.hide()
|
||||||
)
|
|
||||||
def export_ui(self):
|
|
||||||
if self.object:
|
|
||||||
self.object.project.export_ui(self.object)
|
|
||||||
|
|
||||||
return True
|
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
|
||||||
|
|
||||||
@GObject.Signal(
|
return 1 if source_value.endswith(".blp") else 0
|
||||||
flags=GObject.SignalFlags.RUN_LAST,
|
|
||||||
return_type=bool,
|
|
||||||
arg_types=(),
|
|
||||||
accumulator=GObject.signal_accumulator_true_handled,
|
|
||||||
)
|
|
||||||
def remove_ui(self):
|
|
||||||
if self.object:
|
|
||||||
self.object.project.remove_ui(self.object)
|
|
||||||
|
|
||||||
return True
|
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.91.3 -->
|
<!-- 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,16 +57,13 @@
|
|||||||
<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>
|
||||||
<child>
|
<child>
|
||||||
<object class="CmbEntry" id="filename">
|
<object class="CmbFileButton" id="filename">
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="placeholder-text" translatable="yes"><file name relative to project></property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<layout>
|
<layout>
|
||||||
<property name="column">1</property>
|
<property name="column">1</property>
|
||||||
<property name="row">0</property>
|
<property name="row">0</property>
|
||||||
@ -81,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>
|
||||||
@ -98,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>
|
||||||
@ -115,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>
|
||||||
@ -127,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>
|
||||||
@ -137,45 +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>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="spacing">4</property>
|
|
||||||
<property name="valign">end</property>
|
|
||||||
<property name="vexpand">1</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="remove_button">
|
|
||||||
<property name="focusable">1</property>
|
|
||||||
<property name="receives-default">1</property>
|
|
||||||
<signal name="clicked" handler="on_remove_button_clicked"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="icon-name">user-trash-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="export_button">
|
|
||||||
<property name="focusable">1</property>
|
|
||||||
<property name="halign">end</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="label" translatable="yes">Export</property>
|
|
||||||
<property name="receives-default">1</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Export</property>
|
|
||||||
<signal name="clicked" handler="on_export_button_clicked"/>
|
|
||||||
<style>
|
|
||||||
<class name="suggested-action"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<layout>
|
|
||||||
<property name="column">0</property>
|
|
||||||
<property name="column-span">2</property>
|
|
||||||
<property name="row">7</property>
|
|
||||||
</layout>
|
</layout>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@ -192,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>
|
||||||
@ -202,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>
|
||||||
@ -219,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>
|
||||||
|
57
cambalache/cmb_version_notification_view.py
Normal file
57
cambalache/cmb_version_notification_view.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#
|
||||||
|
# CmbVersionNotificationView
|
||||||
|
#
|
||||||
|
# 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 cambalache import _, getLogger
|
||||||
|
from gi.repository import GObject, Gtk
|
||||||
|
from .cmb_notification import CmbVersionNotification
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_version_notification_view.ui")
|
||||||
|
class CmbVersionNotificationView(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbVersionNotificationView"
|
||||||
|
|
||||||
|
notification = GObject.Property(
|
||||||
|
type=CmbVersionNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
# Version
|
||||||
|
version_label = Gtk.Template.Child()
|
||||||
|
release_notes_label = Gtk.Template.Child()
|
||||||
|
read_more_button = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
notification = self.notification
|
||||||
|
self.version_label.props.label = _("<b>Version {version} is available</b>").format(version=notification.version)
|
||||||
|
self.release_notes_label.props.label = notification.release_notes
|
||||||
|
|
||||||
|
if notification.read_more_url:
|
||||||
|
self.read_more_button.props.uri = notification.read_more_url
|
||||||
|
self.read_more_button.show()
|
||||||
|
else:
|
||||||
|
self.read_more_button.hide()
|
50
cambalache/cmb_version_notification_view.ui
Normal file
50
cambalache/cmb_version_notification_view.ui
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_version_notification_view.ui -->
|
||||||
|
<requires lib="gtk" version="4.12"/>
|
||||||
|
<template class="CmbVersionNotificationView" parent="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="version_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="release_notes_label">
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="lines">16</property>
|
||||||
|
<property name="max-width-chars">32</property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
<property name="valign">start</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
<property name="valign">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLinkButton" id="read_more_button">
|
||||||
|
<property name="label" translatable="yes">Read more...</property>
|
||||||
|
<property name="uri">https://blogs.gnome.org/xjuan/</property>
|
||||||
|
<style>
|
||||||
|
<class name="compact"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLinkButton" id="download_button">
|
||||||
|
<property name="label" translatable="yes">Get it on Flathub</property>
|
||||||
|
<property name="uri">https://flathub.org/apps/ar.xjuan.Cambalache</property>
|
||||||
|
<style>
|
||||||
|
<class name="compact"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -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__)
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ class CmbMerengueProcess(GObject.Object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
gtk_version = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
gtk_version = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
merengue_started = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.__command_queue = []
|
self.__command_queue = []
|
||||||
@ -166,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +187,8 @@ class CmbMerengueProcess(GObject.Object):
|
|||||||
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)
|
||||||
|
|
||||||
def __cleanup(self):
|
def __cleanup(self):
|
||||||
|
self.merengue_started = False
|
||||||
|
|
||||||
if self.__on_command_in_source:
|
if self.__on_command_in_source:
|
||||||
GLib.source_remove(self.__on_command_in_source)
|
GLib.source_remove(self.__on_command_in_source)
|
||||||
self.__on_command_in_source = None
|
self.__on_command_in_source = None
|
||||||
@ -210,7 +213,13 @@ class CmbMerengueProcess(GObject.Object):
|
|||||||
self.__pid = 0
|
self.__pid = 0
|
||||||
|
|
||||||
def write_command(self, command, payload=None, args=None):
|
def write_command(self, command, payload=None, args=None):
|
||||||
cmd = {"command": command, "payload": payload is not None}
|
cmd = {"command": command}
|
||||||
|
|
||||||
|
if payload is not None:
|
||||||
|
# Encode to binary first, before calculating lenght
|
||||||
|
payload = payload.encode()
|
||||||
|
cmd["payload_length"] = len(payload)
|
||||||
|
logger.debug(f"write_command {command} {len(payload)}")
|
||||||
|
|
||||||
if args is not None:
|
if args is not None:
|
||||||
cmd["args"] = args
|
cmd["args"] = args
|
||||||
@ -225,12 +234,18 @@ class CmbMerengueProcess(GObject.Object):
|
|||||||
def __socket_write_command(self, cmd, payload=None):
|
def __socket_write_command(self, cmd, payload=None):
|
||||||
# Send command in one line as json
|
# Send command in one line as json
|
||||||
output_stream = self.__connection.props.output_stream
|
output_stream = self.__connection.props.output_stream
|
||||||
output_stream.write(json.dumps(cmd).encode())
|
|
||||||
output_stream.write(b"\n")
|
def write_data(data):
|
||||||
|
total_bytes = len(data)
|
||||||
|
total_sent = 0
|
||||||
|
while total_sent < total_bytes:
|
||||||
|
total_sent += output_stream.write(data[total_sent:])
|
||||||
|
|
||||||
|
write_data(json.dumps(cmd).encode())
|
||||||
|
write_data(b"\n")
|
||||||
|
|
||||||
if payload is not None:
|
if payload is not None:
|
||||||
output_stream.write(GLib.strescape(payload).encode())
|
write_data(payload)
|
||||||
output_stream.write(b"\n")
|
|
||||||
|
|
||||||
# Flush
|
# Flush
|
||||||
output_stream.flush()
|
output_stream.flush()
|
||||||
@ -250,6 +265,7 @@ class CmbView(Gtk.Box):
|
|||||||
"placeholder-activated": (GObject.SignalFlags.RUN_LAST, None, (int, int, object, int, str)),
|
"placeholder-activated": (GObject.SignalFlags.RUN_LAST, None, (int, int, object, int, str)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_merengue = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||||
preview = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
preview = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
stack = Gtk.Template.Child()
|
stack = Gtk.Template.Child()
|
||||||
@ -263,9 +279,8 @@ 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.__dark = False
|
|
||||||
|
|
||||||
self.menu = self.__create_context_menu()
|
self.menu = self.__create_context_menu()
|
||||||
|
|
||||||
@ -300,27 +315,56 @@ class CmbView(Gtk.Box):
|
|||||||
if os.path.exists(dirname):
|
if os.path.exists(dirname):
|
||||||
shutil.rmtree(dirname)
|
shutil.rmtree(dirname)
|
||||||
|
|
||||||
|
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):
|
def _set_dark_mode(self, dark):
|
||||||
self.__dark = dark
|
# This needs to be called in an idle because theme_bg_color has not changed at this point
|
||||||
bg_color = Gdk.RGBA()
|
GLib.idle_add(self.__set_dark_mode, dark)
|
||||||
bg_color.parse("gray18" if dark else "white")
|
|
||||||
self.compositor.props.bg_color = bg_color
|
|
||||||
|
|
||||||
def __merengue_command(self, command, payload=None, args=None):
|
def __merengue_command(self, command, payload=None, args=None):
|
||||||
self.__merengue.write_command(command, payload, args)
|
if self.__merengue.merengue_started:
|
||||||
|
self.__merengue.write_command(command, payload, args)
|
||||||
|
|
||||||
def __get_ui_xml(self, ui_id, merengue=False):
|
def __get_ui_xml(self, ui_id, merengue=False):
|
||||||
|
if self.show_merengue:
|
||||||
|
merengue = True
|
||||||
|
|
||||||
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()
|
||||||
@ -373,7 +417,13 @@ class CmbView(Gtk.Box):
|
|||||||
self.__merengue_update_ui(obj.ui_id)
|
self.__merengue_update_ui(obj.ui_id)
|
||||||
|
|
||||||
def __on_object_property_changed(self, project, obj, prop):
|
def __on_object_property_changed(self, project, obj, prop):
|
||||||
if obj.info.workspace_type is None and prop.info.construct_only:
|
info = prop.info
|
||||||
|
|
||||||
|
# FIXME: implement new merengue command for updating a11y props
|
||||||
|
if info.is_a11y:
|
||||||
|
return
|
||||||
|
|
||||||
|
if obj.info.workspace_type is None and info.construct_only:
|
||||||
self.__merengue_update_ui(obj.ui_id)
|
self.__merengue_update_ui(obj.ui_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -418,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()
|
||||||
@ -602,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):
|
||||||
@ -654,12 +708,14 @@ class CmbView(Gtk.Box):
|
|||||||
if command == "selection_changed":
|
if command == "selection_changed":
|
||||||
self.__command_selection_changed(**args)
|
self.__command_selection_changed(**args)
|
||||||
elif command == "started":
|
elif command == "started":
|
||||||
|
self.__merengue.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.__load_namespaces()
|
self.__load_namespaces()
|
||||||
|
|
||||||
self.__load_css_providers()
|
self.__load_css_providers()
|
||||||
|
|
||||||
|
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(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.3 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_view.ui -->
|
<!-- interface-name cmb_view.ui -->
|
||||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||||
|
@ -22,8 +22,10 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from .cmb_boolean_undefined import CmbBooleanUndefined
|
||||||
from .cmb_child_type_combo_box import CmbChildTypeComboBox
|
from .cmb_child_type_combo_box import CmbChildTypeComboBox
|
||||||
from .cmb_entry import CmbEntry
|
from .cmb_entry import CmbEntry
|
||||||
|
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
|
||||||
@ -34,6 +36,7 @@ from .cmb_color_entry import CmbColorEntry
|
|||||||
from .cmb_enum_combo_box import CmbEnumComboBox
|
from .cmb_enum_combo_box import CmbEnumComboBox
|
||||||
from .cmb_flags_entry import CmbFlagsEntry
|
from .cmb_flags_entry import CmbFlagsEntry
|
||||||
from .cmb_object_chooser import CmbObjectChooser
|
from .cmb_object_chooser import CmbObjectChooser
|
||||||
|
from .cmb_object_list_editor import CmbObjectListEditor
|
||||||
from .cmb_source_view import CmbSourceView
|
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
|
||||||
|
78
cambalache/control/cmb_boolean_undefined.py
Normal file
78
cambalache/control/cmb_boolean_undefined.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#
|
||||||
|
# CmbBooleanUndefined
|
||||||
|
#
|
||||||
|
# Copyright (C) 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 cambalache import _
|
||||||
|
|
||||||
|
|
||||||
|
class CmbBooleanUndefined(Gtk.Box):
|
||||||
|
__gtype_name__ = "CmbBooleanUndefined"
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.__undefined = Gtk.CheckButton(label=_("undefined"))
|
||||||
|
self.__true = Gtk.CheckButton(label=_("true"), group=self.__undefined)
|
||||||
|
self.__false = Gtk.CheckButton(label=_("false"), group=self.__undefined)
|
||||||
|
|
||||||
|
for button in [self.__undefined, self.__true, self.__false]:
|
||||||
|
self.append(button)
|
||||||
|
button.connect("notify::active", self.__on_notify)
|
||||||
|
|
||||||
|
def __on_notify(self, obj, pspec):
|
||||||
|
self.notify("cmb-value")
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def cmb_value(self):
|
||||||
|
if self.__undefined.props.active:
|
||||||
|
return "undefined"
|
||||||
|
elif self.__true.props.active:
|
||||||
|
return "True"
|
||||||
|
|
||||||
|
return "False"
|
||||||
|
|
||||||
|
@cmb_value.setter
|
||||||
|
def _set_cmb_value(self, value):
|
||||||
|
if value is not None:
|
||||||
|
if type(value) is str:
|
||||||
|
val = value.lower()
|
||||||
|
|
||||||
|
if val == "undefined":
|
||||||
|
self.__undefined.props.active = True
|
||||||
|
return
|
||||||
|
|
||||||
|
if val in {"1", "t", "y", "true", "yes"}:
|
||||||
|
active = True
|
||||||
|
else:
|
||||||
|
active = False
|
||||||
|
else:
|
||||||
|
active = bool(value)
|
||||||
|
|
||||||
|
if active:
|
||||||
|
self.__true.props.active = True
|
||||||
|
else:
|
||||||
|
self.__false.props.active = True
|
||||||
|
else:
|
||||||
|
self.__undefined.active = True
|
@ -26,6 +26,7 @@ import os
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
from gi.repository import GLib, Gtk
|
from gi.repository import GLib, Gtk
|
||||||
|
from .cmb_boolean_undefined import CmbBooleanUndefined
|
||||||
from .cmb_entry import CmbEntry
|
from .cmb_entry import CmbEntry
|
||||||
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
|
||||||
@ -35,6 +36,7 @@ from .cmb_color_entry import CmbColorEntry
|
|||||||
from .cmb_enum_combo_box import CmbEnumComboBox
|
from .cmb_enum_combo_box import CmbEnumComboBox
|
||||||
from .cmb_flags_entry import CmbFlagsEntry
|
from .cmb_flags_entry import CmbFlagsEntry
|
||||||
from .cmb_object_chooser import CmbObjectChooser
|
from .cmb_object_chooser import CmbObjectChooser
|
||||||
|
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
|
||||||
|
|
||||||
@ -132,6 +134,13 @@ def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
|
|||||||
editor = CmbIconNameEntry(hexpand=True, placeholder_text="<Icon Name>")
|
editor = CmbIconNameEntry(hexpand=True, placeholder_text="<Icon Name>")
|
||||||
elif type_id in ["GtkShortcutTrigger", "GtkShortcutAction"]:
|
elif type_id in ["GtkShortcutTrigger", "GtkShortcutAction"]:
|
||||||
editor = CmbEntry(hexpand=True, placeholder_text=f"<{type_id}>")
|
editor = CmbEntry(hexpand=True, placeholder_text=f"<{type_id}>")
|
||||||
|
elif type_id == "CmbBooleanUndefined":
|
||||||
|
editor = CmbBooleanUndefined()
|
||||||
|
elif type_id == "CmbAccessibleList":
|
||||||
|
editor = CmbObjectListEditor(
|
||||||
|
parent=prop.object if prop else parent,
|
||||||
|
type_id="GtkAccessible",
|
||||||
|
)
|
||||||
elif info:
|
elif info:
|
||||||
if info.is_object or info.parent_id == "interface":
|
if info.is_object or info.parent_id == "interface":
|
||||||
if prop is None:
|
if prop is None:
|
||||||
|
@ -30,7 +30,7 @@ from ..cmb_type_info import CmbTypeInfo
|
|||||||
class CmbEnumComboBox(Gtk.ComboBox):
|
class CmbEnumComboBox(Gtk.ComboBox):
|
||||||
__gtype_name__ = "CmbEnumComboBox"
|
__gtype_name__ = "CmbEnumComboBox"
|
||||||
|
|
||||||
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE)
|
||||||
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_ONLY)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@ -42,7 +42,9 @@ class CmbEnumComboBox(Gtk.ComboBox):
|
|||||||
self.add_attribute(renderer_text, "text", self.text_column)
|
self.add_attribute(renderer_text, "text", self.text_column)
|
||||||
|
|
||||||
self.props.id_column = self.text_column
|
self.props.id_column = self.text_column
|
||||||
self.props.model = self.info.enum
|
|
||||||
|
if self.info:
|
||||||
|
self.props.model = self.info.enum
|
||||||
|
|
||||||
def __on_changed(self, obj):
|
def __on_changed(self, obj):
|
||||||
self.notify("cmb-value")
|
self.notify("cmb-value")
|
||||||
@ -53,6 +55,9 @@ class CmbEnumComboBox(Gtk.ComboBox):
|
|||||||
|
|
||||||
@cmb_value.setter
|
@cmb_value.setter
|
||||||
def _set_cmb_value(self, value):
|
def _set_cmb_value(self, value):
|
||||||
|
if self.info is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.props.active_id = None
|
self.props.active_id = None
|
||||||
active_id = self.info.enum_get_value_as_string(value)
|
active_id = self.info.enum_get_value_as_string(value)
|
||||||
|
|
||||||
|
114
cambalache/control/cmb_file_button.py
Normal file
114
cambalache/control/cmb_file_button.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#
|
||||||
|
# CmbFileButton
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from cambalache import _
|
||||||
|
from gi.repository import GObject, Gio, Gtk
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/control/cmb_file_button.ui")
|
||||||
|
class CmbFileButton(Gtk.Button):
|
||||||
|
__gtype_name__ = "CmbFileButton"
|
||||||
|
|
||||||
|
dirname = GObject.Property(type=str, 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)
|
||||||
|
|
||||||
|
label = Gtk.Template.Child()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.__filename = None
|
||||||
|
self.__filters = None
|
||||||
|
|
||||||
|
@Gtk.Template.Callback("on_button_clicked")
|
||||||
|
def __on_button_clicked(self, button):
|
||||||
|
dialog = Gtk.FileDialog(
|
||||||
|
modal=True,
|
||||||
|
filters=self.__filters,
|
||||||
|
title=self.dialog_title,
|
||||||
|
accept_label=self.accept_label
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.dirname is not None:
|
||||||
|
if self.__filename:
|
||||||
|
fullpath = os.path.join(self.dirname, self.__filename)
|
||||||
|
|
||||||
|
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:
|
||||||
|
dialog.set_initial_folder(Gio.File.new_for_path(self.dirname))
|
||||||
|
if self.unnamed_filename:
|
||||||
|
dialog.set_initial_name(self.unnamed_filename)
|
||||||
|
|
||||||
|
def dialog_callback(dialog, res):
|
||||||
|
try:
|
||||||
|
file = dialog.open_finish(res) if self.use_open else dialog.save_finish(res)
|
||||||
|
self.cmb_value = os.path.relpath(file.get_path(), start=self.dirname)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.use_open:
|
||||||
|
dialog.open(self.get_root(), None, dialog_callback)
|
||||||
|
else:
|
||||||
|
dialog.save(self.get_root(), None, dialog_callback)
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def cmb_value(self):
|
||||||
|
return self.__filename
|
||||||
|
|
||||||
|
@cmb_value.setter
|
||||||
|
def _set_cmb_value(self, value):
|
||||||
|
if value == self.__filename:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__filename = value if value is not None else ""
|
||||||
|
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
|
34
cambalache/control/cmb_file_button.ui
Normal file
34
cambalache/control/cmb_file_button.ui
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
|
<interface>
|
||||||
|
<!-- interface-name cmb_file_button.ui -->
|
||||||
|
<requires lib="gtk" version="4.12"/>
|
||||||
|
<template class="CmbFileButton" parent="GtkButton">
|
||||||
|
<signal name="clicked" handler="on_button_clicked"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="file_icon">
|
||||||
|
<property name="visible">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label">
|
||||||
|
<property name="ellipsize">start</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="label">(None)</property>
|
||||||
|
<property name="width-chars">8</property>
|
||||||
|
<property name="xalign">0.0</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="icon-name">folder-open-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -34,6 +34,7 @@ class CmbFlagsEntry(Gtk.Entry):
|
|||||||
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_ONLY)
|
||||||
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_ONLY)
|
||||||
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_ONLY)
|
||||||
|
separator = GObject.Property(type=str, default="|", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.flags = {}
|
self.flags = {}
|
||||||
@ -84,7 +85,7 @@ class CmbFlagsEntry(Gtk.Entry):
|
|||||||
for row in self.info.flags:
|
for row in self.info.flags:
|
||||||
flag_id = row[self.id_column]
|
flag_id = row[self.id_column]
|
||||||
if self.flags.get(flag_id, False):
|
if self.flags.get(flag_id, False):
|
||||||
retval = flag_id if retval is None else f"{retval} | {flag_id}"
|
retval = flag_id if retval is None else f"{retval} {self.separator} {flag_id}"
|
||||||
|
|
||||||
return retval if retval is not None else ""
|
return retval if retval is not None else ""
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ class CmbFlagsEntry(Gtk.Entry):
|
|||||||
self._checks[check].props.active = False
|
self._checks[check].props.active = False
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
tokens = [t.strip() for t in value.split("|")]
|
tokens = [t.strip() for t in value.split(self.separator)]
|
||||||
|
|
||||||
for row in self.info.flags:
|
for row in self.info.flags:
|
||||||
flag_id = row[self.id_column]
|
flag_id = row[self.id_column]
|
||||||
|
93
cambalache/control/cmb_object_list_editor.py
Normal file
93
cambalache/control/cmb_object_list_editor.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#
|
||||||
|
# CmbObjectListEditor
|
||||||
|
#
|
||||||
|
# Copyright (C) 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
|
||||||
|
|
||||||
|
|
||||||
|
class CmbObjectListEditor(Gtk.ScrolledWindow):
|
||||||
|
__gtype_name__ = "CmbObjectListEditor"
|
||||||
|
|
||||||
|
parent = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
type_id = GObject.Property(type=str, default=None, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.__updating = False
|
||||||
|
self.__object_ids = None
|
||||||
|
|
||||||
|
self.props.height_request = 96
|
||||||
|
self.buffer = Gtk.TextBuffer()
|
||||||
|
self.view = Gtk.TextView(visible=True, buffer=self.buffer)
|
||||||
|
self.set_child(self.view)
|
||||||
|
|
||||||
|
self.buffer .connect("notify::text", self.__on_text_notify)
|
||||||
|
|
||||||
|
def __on_text_notify(self, obj, pspec):
|
||||||
|
if self.__updating:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = self.buffer.props.text
|
||||||
|
objects = []
|
||||||
|
|
||||||
|
if text and len(text):
|
||||||
|
names = [name.strip() for name in text.split("\n")]
|
||||||
|
ui_id = self.parent.ui_id
|
||||||
|
project = self.parent.project
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
obj = project.get_object_by_name(ui_id, name)
|
||||||
|
if obj is not None:
|
||||||
|
objects.append(str(obj.object_id))
|
||||||
|
|
||||||
|
objects = ",".join(objects)
|
||||||
|
|
||||||
|
if self.__object_ids == objects:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__object_ids = objects
|
||||||
|
self.notify("cmb-value")
|
||||||
|
|
||||||
|
@GObject.Property(type=str)
|
||||||
|
def cmb_value(self):
|
||||||
|
return self.__object_ids
|
||||||
|
|
||||||
|
@cmb_value.setter
|
||||||
|
def _set_cmb_value(self, value):
|
||||||
|
value = value if value else None
|
||||||
|
|
||||||
|
if self.__object_ids == value:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__object_ids = value
|
||||||
|
|
||||||
|
self.__updating = True
|
||||||
|
if self.__object_ids:
|
||||||
|
names = self.parent.project._get_object_list_names(self.parent.ui_id, self.__object_ids)
|
||||||
|
self.buffer.props.text = "\n".join(names)
|
||||||
|
else:
|
||||||
|
self.buffer.props.text = ""
|
||||||
|
self.__updating = False
|
@ -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):
|
||||||
@ -49,7 +50,7 @@ class CmbSourceView(GtkSource.View):
|
|||||||
|
|
||||||
@GObject.Property(type=str)
|
@GObject.Property(type=str)
|
||||||
def text(self):
|
def text(self):
|
||||||
return self.buffer.props.text
|
return self.buffer.props.text if len(self.buffer.props.text) else None
|
||||||
|
|
||||||
@text.setter
|
@text.setter
|
||||||
def _set_text(self, value):
|
def _set_text(self, value):
|
||||||
|
@ -44,10 +44,9 @@ class CmbSwitch(Gtk.Switch):
|
|||||||
@cmb_value.setter
|
@cmb_value.setter
|
||||||
def _set_cmb_value(self, value):
|
def _set_cmb_value(self, value):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
val = value.lower()
|
if type(value) is str:
|
||||||
|
val = value.lower()
|
||||||
if type(val) is str:
|
if val in {"1", "t", "y", "true", "yes"}:
|
||||||
if val.lower() in {"1", "t", "y", "true", "yes"}:
|
|
||||||
active = True
|
active = True
|
||||||
else:
|
else:
|
||||||
active = False
|
active = False
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!-- Created with Cambalache 0.91.1 -->
|
<!-- Created with Cambalache 0.95.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<!-- interface-name cmb_translatable_widget.ui -->
|
<!-- interface-name cmb_translatable_widget.ui -->
|
||||||
<!-- interface-copyright Philipp Unger -->
|
<!-- interface-copyright Philipp Unger -->
|
||||||
|
@ -2,22 +2,25 @@ moduledir = join_paths(modulesdir, 'cambalache', 'control')
|
|||||||
|
|
||||||
|
|
||||||
install_data([
|
install_data([
|
||||||
|
'cmb_boolean_undefined.py',
|
||||||
'cmb_child_type_combo_box.py',
|
'cmb_child_type_combo_box.py',
|
||||||
'cmb_entry.py',
|
|
||||||
'cmb_file_entry.py',
|
|
||||||
'cmb_icon_name_entry.py',
|
|
||||||
'cmb_pixbuf_entry.py',
|
|
||||||
'cmb_spin_button.py',
|
|
||||||
'cmb_text_buffer.py',
|
|
||||||
'cmb_toplevel_chooser.py',
|
|
||||||
'cmb_color_entry.py',
|
'cmb_color_entry.py',
|
||||||
'cmb_create_editor.py',
|
'cmb_create_editor.py',
|
||||||
|
'cmb_entry.py',
|
||||||
'cmb_enum_combo_box.py',
|
'cmb_enum_combo_box.py',
|
||||||
|
'cmb_file_button.py',
|
||||||
|
'cmb_file_entry.py',
|
||||||
'cmb_flags_entry.py',
|
'cmb_flags_entry.py',
|
||||||
|
'cmb_icon_name_entry.py',
|
||||||
'cmb_object_chooser.py',
|
'cmb_object_chooser.py',
|
||||||
|
'cmb_object_list_editor.py',
|
||||||
|
'cmb_pixbuf_entry.py',
|
||||||
'cmb_source_view.py',
|
'cmb_source_view.py',
|
||||||
|
'cmb_spin_button.py',
|
||||||
'cmb_switch.py',
|
'cmb_switch.py',
|
||||||
|
'cmb_text_buffer.py',
|
||||||
'cmb_text_view.py',
|
'cmb_text_view.py',
|
||||||
|
'cmb_toplevel_chooser.py',
|
||||||
'cmb_translatable_popover.py',
|
'cmb_translatable_popover.py',
|
||||||
'cmb_translatable_widget.py',
|
'cmb_translatable_widget.py',
|
||||||
'icon_naming_spec.py',
|
'icon_naming_spec.py',
|
||||||
|
@ -95,8 +95,8 @@ INSERT INTO type (type_id) VALUES
|
|||||||
* Keep a list of interfaces implemented by type
|
* Keep a list of interfaces implemented by type
|
||||||
*/
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS type_iface (
|
CREATE TABLE IF NOT EXISTS type_iface (
|
||||||
type_id TEXT REFERENCES type ON UPDATE CASCADE,
|
type_id TEXT REFERENCES type ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
iface_id TEXT REFERENCES type ON UPDATE CASCADE,
|
iface_id TEXT REFERENCES type ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
PRIMARY KEY(type_id, iface_id)
|
PRIMARY KEY(type_id, iface_id)
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
@ -170,29 +170,19 @@ CREATE TABLE IF NOT EXISTS type_child_constraint (
|
|||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
|
||||||
|
/* Type Internal Child
|
||||||
/* Type Tree
|
|
||||||
*
|
*
|
||||||
* VIEW of ancestors and ifaces by type
|
|
||||||
*/
|
*/
|
||||||
CREATE VIEW IF NOT EXISTS type_tree AS
|
CREATE TABLE IF NOT EXISTS type_internal_child (
|
||||||
WITH RECURSIVE ancestor(type_id, generation, parent_id) AS (
|
type_id TEXT REFERENCES type ON UPDATE CASCADE,
|
||||||
SELECT type_id, 1, parent_id FROM type
|
internal_child_id TEXT,
|
||||||
WHERE parent_id IS NOT NULL AND
|
internal_parent_id TEXT,
|
||||||
parent_id != 'interface' AND
|
internal_type TEXT REFERENCES type ON UPDATE CASCADE,
|
||||||
parent_id != 'enum' AND
|
creation_property_id TEXT,
|
||||||
parent_id != 'flags'
|
PRIMARY KEY(type_id, internal_child_id),
|
||||||
UNION ALL
|
FOREIGN KEY(type_id, internal_parent_id) REFERENCES type_internal_child(type_id, internal_child_id)
|
||||||
SELECT ancestor.type_id, generation + 1, type.parent_id
|
FOREIGN KEY(type_id, creation_property_id) REFERENCES property(owner_id, property_id)
|
||||||
FROM type JOIN ancestor ON type.type_id = ancestor.parent_id
|
) WITHOUT ROWID;
|
||||||
WHERE type.parent_id IS NOT NULL
|
|
||||||
)
|
|
||||||
SELECT * FROM ancestor
|
|
||||||
UNION
|
|
||||||
SELECT ancestor.type_id, 0, type_iface.iface_id
|
|
||||||
FROM ancestor JOIN type_iface
|
|
||||||
WHERE ancestor.type_id = type_iface.type_id
|
|
||||||
ORDER BY type_id,generation;
|
|
||||||
|
|
||||||
|
|
||||||
/* Property
|
/* Property
|
||||||
@ -213,10 +203,13 @@ CREATE TABLE IF NOT EXISTS property (
|
|||||||
deprecated_version TEXT,
|
deprecated_version TEXT,
|
||||||
translatable BOOLEAN CHECK (type_id IN (NULL, 'gchararray')),
|
translatable BOOLEAN CHECK (type_id IN (NULL, 'gchararray')),
|
||||||
disable_inline_object BOOLEAN DEFAULT 0,
|
disable_inline_object BOOLEAN DEFAULT 0,
|
||||||
is_position BOOLEAN DEFAULT 0,
|
deprecated TEXT,
|
||||||
required BOOLEAN DEFAULT 0,
|
required BOOLEAN DEFAULT 0,
|
||||||
workspace_default TEXT,
|
workspace_default TEXT,
|
||||||
|
original_owner_id TEXT REFERENCES type ON UPDATE CASCADE,
|
||||||
|
disabled BOOLEAN DEFAULT 0,
|
||||||
PRIMARY KEY(owner_id, property_id)
|
PRIMARY KEY(owner_id, property_id)
|
||||||
|
FOREIGN KEY(original_owner_id, property_id) REFERENCES property(owner_id, property_id)
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS property_type_id_fk ON property (type_id);
|
CREATE INDEX IF NOT EXISTS property_type_id_fk ON property (type_id);
|
||||||
|
@ -41,7 +41,7 @@ CREATE TABLE history (
|
|||||||
command TEXT NOT NULL,
|
command TEXT NOT NULL,
|
||||||
range_id INTEGER REFERENCES history,
|
range_id INTEGER REFERENCES history,
|
||||||
table_name TEXT,
|
table_name TEXT,
|
||||||
column_name TEXT,
|
columns JSON,
|
||||||
message TEXT,
|
message TEXT,
|
||||||
table_pk JSON,
|
table_pk JSON,
|
||||||
new_values JSON,
|
new_values JSON,
|
||||||
|
@ -69,8 +69,13 @@ WHEN
|
|||||||
(SELECT name FROM object WHERE ui_id=NEW.ui_id AND object_id=NEW.template_id) IS NOT NULL
|
(SELECT name FROM object WHERE ui_id=NEW.ui_id AND object_id=NEW.template_id) IS NOT NULL
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO type (type_id, parent_id)
|
INSERT INTO type (type_id, parent_id)
|
||||||
SELECT name, type_id FROM object
|
SELECT name, type_id FROM object WHERE ui_id=NEW.ui_id AND object_id=NEW.template_id
|
||||||
WHERE ui_id=NEW.ui_id AND object_id=NEW.template_id;
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO type_iface (type_id, iface_id)
|
||||||
|
WITH v AS (SELECT name, type_id FROM object WHERE ui_id=NEW.ui_id AND object_id=NEW.template_id)
|
||||||
|
SELECT v.name, t.iface_id FROM type_iface AS t, v WHERE t.type_id=v.type_id
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
|
||||||
@ -108,6 +113,26 @@ CREATE TABLE css_ui (
|
|||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
|
||||||
|
/* GResource
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
CREATE TABLE gresource (
|
||||||
|
gresource_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
resource_type TEXT CHECK (resource_type IN ('gresources', 'gresource', 'file')),
|
||||||
|
parent_id INTEGER REFERENCES gresource ON DELETE CASCADE,
|
||||||
|
position INTEGER NOT NULL CHECK (position >= 0),
|
||||||
|
|
||||||
|
gresources_filename TEXT UNIQUE,
|
||||||
|
|
||||||
|
gresource_prefix TEXT UNIQUE,
|
||||||
|
|
||||||
|
file_filename TEXT,
|
||||||
|
file_compressed BOOLEAN,
|
||||||
|
file_preprocess TEXT,
|
||||||
|
file_alias TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/* Object
|
/* Object
|
||||||
*
|
*
|
||||||
* TODO: check type_id is an object
|
* TODO: check type_id is an object
|
||||||
@ -171,7 +196,13 @@ WHEN
|
|||||||
NEW.name IS NOT NULL AND
|
NEW.name IS NOT NULL AND
|
||||||
NEW.object_id IS (SELECT template_id FROM ui WHERE ui_id=NEW.ui_id)
|
NEW.object_id IS (SELECT template_id FROM ui WHERE ui_id=NEW.ui_id)
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO type(type_id, parent_id, derivable) VALUES (NEW.name, NEW.type_id, 1);
|
INSERT INTO type(type_id, parent_id, derivable)
|
||||||
|
VALUES (NEW.name, NEW.type_id, 1)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO type_iface (type_id, iface_id)
|
||||||
|
SELECT NEW.name, iface_id FROM type_iface WHERE type_id=NEW.type_id
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER on_object_delete_remove_type AFTER DELETE ON object
|
CREATE TRIGGER on_object_delete_remove_type AFTER DELETE ON object
|
||||||
@ -193,10 +224,21 @@ BEGIN
|
|||||||
|
|
||||||
/* Clear references to object */
|
/* Clear references to object */
|
||||||
UPDATE object_property SET value=NULL
|
UPDATE object_property SET value=NULL
|
||||||
FROM property AS p, object AS o
|
FROM property AS p
|
||||||
WHERE object_property.ui_id=OLD.ui_id AND p.is_object AND object_property.owner_id = p.owner_id AND
|
WHERE object_property.ui_id=OLD.ui_id AND
|
||||||
object_property.property_id = p.property_id AND o.ui_id = object_property.ui_id AND
|
object_property.owner_id = p.owner_id AND
|
||||||
|
object_property.property_id = p.property_id AND
|
||||||
|
p.is_object AND
|
||||||
object_property.value = OLD.object_id;
|
object_property.value = OLD.object_id;
|
||||||
|
|
||||||
|
/* Clear CmbAccessibleList references to object */
|
||||||
|
UPDATE object_property SET value=cmb_object_list_remove(value, OLD.object_id)
|
||||||
|
FROM property AS p
|
||||||
|
WHERE object_property.ui_id=OLD.ui_id AND
|
||||||
|
object_property.owner_id = p.owner_id AND
|
||||||
|
object_property.property_id = p.property_id AND
|
||||||
|
p.type_id = 'CmbAccessibleList' AND
|
||||||
|
value IS NOT NULL;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,14 +23,22 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import gi
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
gi.require_version('GIRepository', '3.0')
|
||||||
from . import config
|
from . import config
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio, Gtk
|
||||||
|
|
||||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "merengue.gresource"))
|
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "merengue.gresource"))
|
||||||
resource._register()
|
resource._register()
|
||||||
|
|
||||||
|
repository = gi.Repository.get_default()
|
||||||
|
repository.prepend_search_path(config.privatecambalachedir)
|
||||||
|
repository.prepend_library_path(config.privatecambalachedir)
|
||||||
|
|
||||||
|
gi.require_version("CambalachePrivate", "4.0" if Gtk.MAJOR_VERSION == 4 else "3.0")
|
||||||
|
|
||||||
|
|
||||||
def getLogger(name):
|
def getLogger(name):
|
||||||
formatter = logging.Formatter("%(levelname)s:%(name)s %(message)s")
|
formatter = logging.Formatter("%(levelname)s:%(name)s %(message)s")
|
||||||
@ -49,3 +57,4 @@ from .mrg_application import MrgApplication
|
|||||||
from .mrg_controller import MrgController
|
from .mrg_controller import MrgController
|
||||||
from .mrg_placeholder import MrgPlaceholder
|
from .mrg_placeholder import MrgPlaceholder
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
VERSION = '@VERSION@'
|
VERSION = '@VERSION@'
|
||||||
pkgdatadir = '@pkgdatadir@'
|
pkgdatadir = '@pkgdatadir@'
|
||||||
merenguedir = '@merenguedir@'
|
merenguedir = '@merenguedir@'
|
||||||
|
privatecambalachedir = '@privatecambalachedir@'
|
||||||
|
@ -29,7 +29,7 @@ import gi
|
|||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
merenguedir = '@merenguedir@'
|
merenguedir = "@merenguedir@"
|
||||||
|
|
||||||
sys.path.insert(1, merenguedir)
|
sys.path.insert(1, merenguedir)
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
@ -43,9 +43,9 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
version = sys.argv[1]
|
version = sys.argv[1]
|
||||||
command_socket = sys.argv[2]
|
command_socket = sys.argv[2]
|
||||||
gi.require_version('Gdk', version)
|
|
||||||
gi.require_version('Gtk', version)
|
gi.require_version("Gdk", version)
|
||||||
gi.require_version('CambalachePrivate', version)
|
gi.require_version("Gtk", version)
|
||||||
|
|
||||||
from merengue import MrgApplication
|
from merengue import MrgApplication
|
||||||
app = MrgApplication(command_socket=command_socket)
|
app = MrgApplication(command_socket=command_socket)
|
||||||
|
@ -12,6 +12,7 @@ conf.set('VERSION', meson.project_version())
|
|||||||
conf.set('PYTHON', python_bin.full_path())
|
conf.set('PYTHON', python_bin.full_path())
|
||||||
conf.set('pkgdatadir', pkgdatadir)
|
conf.set('pkgdatadir', pkgdatadir)
|
||||||
conf.set('merenguedir', merenguedir)
|
conf.set('merenguedir', merenguedir)
|
||||||
|
conf.set('privatecambalachedir', privatecambalachedir)
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
input: 'config.py.in',
|
input: 'config.py.in',
|
||||||
@ -49,6 +50,7 @@ install_data([
|
|||||||
'mrg_gtk/mrg_gtk_bin.py',
|
'mrg_gtk/mrg_gtk_bin.py',
|
||||||
'mrg_gtk/mrg_gtk_box.py',
|
'mrg_gtk/mrg_gtk_box.py',
|
||||||
'mrg_gtk/mrg_gtk_center_box.py',
|
'mrg_gtk/mrg_gtk_center_box.py',
|
||||||
|
'mrg_gtk/mrg_gtk_dialog.py',
|
||||||
'mrg_gtk/mrg_gtk_expander.py',
|
'mrg_gtk/mrg_gtk_expander.py',
|
||||||
'mrg_gtk/mrg_gtk_frame.py',
|
'mrg_gtk/mrg_gtk_frame.py',
|
||||||
'mrg_gtk/mrg_gtk_grid.py',
|
'mrg_gtk/mrg_gtk_grid.py',
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
from gi.repository import GObject, Adw, Gtk, CambalachePrivate
|
from gi.repository import GObject, Adw, Gtk, CambalachePrivate
|
||||||
from merengue.mrg_gtk import MrgGtkWidget, MrgSelection
|
from merengue.mrg_gtk import MrgGtkWidget, MrgSelection
|
||||||
|
from merengue import MrgPlaceholder
|
||||||
|
|
||||||
|
|
||||||
class MrgAdwDialog(MrgGtkWidget):
|
class MrgAdwDialog(MrgGtkWidget):
|
||||||
@ -53,6 +54,7 @@ class MrgAdwDialog(MrgGtkWidget):
|
|||||||
|
|
||||||
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()
|
||||||
|
|
||||||
def __window_new(self):
|
def __window_new(self):
|
||||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
@ -67,6 +69,13 @@ class MrgAdwDialog(MrgGtkWidget):
|
|||||||
if self.object:
|
if self.object:
|
||||||
self.object.present(self.window)
|
self.object.present(self.window)
|
||||||
|
|
||||||
|
def __update_placeholder(self):
|
||||||
|
if self.object is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(self.get_children()) == 0:
|
||||||
|
self.add(MrgPlaceholder(visible=True, controller=self))
|
||||||
|
|
||||||
def get_children(self):
|
def get_children(self):
|
||||||
child = self.object.get_child() if self.object else None
|
child = self.object.get_child() if self.object else None
|
||||||
return [child] if child else []
|
return [child] if child else []
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import gi
|
import gi
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
@ -80,10 +79,6 @@ class MrgApplication(Gtk.Application):
|
|||||||
# Current UI ID
|
# Current UI ID
|
||||||
self.ui_id = None
|
self.ui_id = None
|
||||||
|
|
||||||
# The widget that got the last button press this is used to select
|
|
||||||
# a widget on button release
|
|
||||||
self.preselected_widget = None
|
|
||||||
|
|
||||||
self.settings = Gtk.Settings.get_default()
|
self.settings = Gtk.Settings.get_default()
|
||||||
|
|
||||||
# Keep a reference to the default seat to easily ungrab the pointer
|
# Keep a reference to the default seat to easily ungrab the pointer
|
||||||
@ -125,7 +120,6 @@ class MrgApplication(Gtk.Application):
|
|||||||
|
|
||||||
def clear_all(self):
|
def clear_all(self):
|
||||||
self.ui_id = None
|
self.ui_id = None
|
||||||
self.preselected_widget = None
|
|
||||||
|
|
||||||
# Unset controllers objects
|
# Unset controllers objects
|
||||||
for key in self.controllers:
|
for key in self.controllers:
|
||||||
@ -304,7 +298,13 @@ class MrgApplication(Gtk.Application):
|
|||||||
self.set_property(property, value)
|
self.set_property(property, value)
|
||||||
|
|
||||||
def add_css_provider(self, css_id, filename, priority, is_global, provider_for):
|
def add_css_provider(self, css_id, filename, priority, is_global, provider_for):
|
||||||
css = MrgCssProvider(filename=filename, priority=priority, is_global=is_global, ui_id=self.ui_id if self.ui_id else 0)
|
css = MrgCssProvider(
|
||||||
|
filename=filename,
|
||||||
|
priority=priority,
|
||||||
|
is_global=is_global,
|
||||||
|
provider_for=provider_for,
|
||||||
|
ui_id=self.ui_id if self.ui_id else 0
|
||||||
|
)
|
||||||
|
|
||||||
self.css_providers[css_id] = css
|
self.css_providers[css_id] = css
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ class MrgApplication(Gtk.Application):
|
|||||||
css.set_property(field, value)
|
css.set_property(field, value)
|
||||||
|
|
||||||
def run_command(self, command, args, payload):
|
def run_command(self, command, args, payload):
|
||||||
logger.debug(f"{command} {args}")
|
logger.debug(f"{command} {args} payload={len(payload) if payload else -1}")
|
||||||
|
|
||||||
if command == "clear_all":
|
if command == "clear_all":
|
||||||
self.clear_all()
|
self.clear_all()
|
||||||
@ -370,8 +370,8 @@ class MrgApplication(Gtk.Application):
|
|||||||
# We receive a command in each line
|
# We receive a command in each line
|
||||||
retval = self.command_in.readline()
|
retval = self.command_in.readline()
|
||||||
|
|
||||||
# if len(retval) == 0:
|
if len(retval) == 0:
|
||||||
# return GLib.SOURCE_CONTINUE
|
return GLib.SOURCE_CONTINUE
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Command is a Json string with a command, args and payload fields
|
# Command is a Json string with a command, args and payload fields
|
||||||
@ -383,10 +383,14 @@ class MrgApplication(Gtk.Application):
|
|||||||
else:
|
else:
|
||||||
command = cmd.get("command", None)
|
command = cmd.get("command", None)
|
||||||
args = cmd.get("args", {})
|
args = cmd.get("args", {})
|
||||||
has_payload = cmd.get("payload", False)
|
payload = None
|
||||||
|
payload_length = cmd.get("payload_length", False)
|
||||||
|
|
||||||
# Read payload
|
# Read payload
|
||||||
payload = GLib.strcompress(self.command_in.readline()) if has_payload else None
|
if payload_length:
|
||||||
|
payload = self.command_in.read(payload_length)
|
||||||
|
logger.debug(f"Payload read {payload_length=}, {len(payload)}")
|
||||||
|
payload = payload.decode()
|
||||||
|
|
||||||
# Run command
|
# Run command
|
||||||
self.run_command(command, args, payload)
|
self.run_command(command, args, payload)
|
||||||
|
@ -36,6 +36,7 @@ elif Gtk.MAJOR_VERSION == 4:
|
|||||||
from .mrg_gtk_stack_page import MrgGtkStackPage
|
from .mrg_gtk_stack_page import MrgGtkStackPage
|
||||||
|
|
||||||
from .mrg_gtk_box import MrgGtkBox
|
from .mrg_gtk_box import MrgGtkBox
|
||||||
|
from .mrg_gtk_dialog import MrgGtkDialog
|
||||||
from .mrg_gtk_expander import MrgGtkExpander
|
from .mrg_gtk_expander import MrgGtkExpander
|
||||||
from .mrg_gtk_frame import MrgGtkFrame
|
from .mrg_gtk_frame import MrgGtkFrame
|
||||||
from .mrg_gtk_grid import MrgGtkGrid
|
from .mrg_gtk_grid import MrgGtkGrid
|
||||||
@ -59,3 +60,4 @@ from .mrg_selection import MrgSelection
|
|||||||
from .mrg_g_binding import MrgGBindingProxy
|
from .mrg_g_binding import MrgGBindingProxy
|
||||||
|
|
||||||
GObject.type_ensure(MrgGBindingProxy.__gtype__)
|
GObject.type_ensure(MrgGBindingProxy.__gtype__)
|
||||||
|
|
||||||
|
52
cambalache/merengue/mrg_gtk/mrg_gtk_dialog.py
Normal file
52
cambalache/merengue/mrg_gtk/mrg_gtk_dialog.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# GtkDialog Controller
|
||||||
|
#
|
||||||
|
# Copyright (C) 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 GLib, GObject, Gtk
|
||||||
|
|
||||||
|
from .mrg_gtk_window import MrgGtkWindow
|
||||||
|
from merengue import getLogger
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MrgGtkDialog(MrgGtkWindow):
|
||||||
|
object = GObject.Property(type=Gtk.Dialog, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def __queue_resize(self, data):
|
||||||
|
if self.object:
|
||||||
|
self.object.props.border_width = data
|
||||||
|
return GLib.SOURCE_REMOVE
|
||||||
|
|
||||||
|
def object_changed(self, old, new):
|
||||||
|
super().object_changed(old, new)
|
||||||
|
|
||||||
|
if Gtk.MAJOR_VERSION == 3 and new:
|
||||||
|
# FIXME: Hack, force dialog to resize properly
|
||||||
|
# GtkDialog gets allocated too much space or not enough for messages, setting message or container border fix it.
|
||||||
|
# Probably a bug in Casilda or a workaround in gnome-shell
|
||||||
|
GLib.timeout_add(100, self.__queue_resize, new.props.border_width)
|
||||||
|
new.props.border_width = 1
|
@ -90,10 +90,6 @@ class MrgGtkGrid(MrgGtkWidget):
|
|||||||
|
|
||||||
self.__ensure_placeholders(max(width, self.width), max(height, self.height))
|
self.__ensure_placeholders(max(width, self.width), max(height, self.height))
|
||||||
|
|
||||||
def get_child_position(self, child):
|
|
||||||
x, y, w, h = self.child_get(child, self._packing)
|
|
||||||
return (x+1) * (y+1)
|
|
||||||
|
|
||||||
def get_child_layout(self, child, layout):
|
def get_child_layout(self, child, layout):
|
||||||
for prop in self._packing:
|
for prop in self._packing:
|
||||||
layout[prop] = self.child_get(child, [prop])[0]
|
layout[prop] = self.child_get(child, [prop])[0]
|
||||||
|
@ -42,14 +42,15 @@ class MrgGtkLabel(MrgGtkWidget):
|
|||||||
|
|
||||||
# Ensure a label so that it can be selected in the workspace
|
# Ensure a label so that it can be selected in the workspace
|
||||||
if self.object.props.label == "":
|
if self.object.props.label == "":
|
||||||
self.object.set_label("<label>")
|
label = f"<label {self.object_id}>" if self.object.props.use_markup else f"<label {self.object_id}>"
|
||||||
|
self.object.set_label(label)
|
||||||
|
|
||||||
def object_changed(self, old, new):
|
def object_changed(self, old, new):
|
||||||
super().object_changed(old, new)
|
super().object_changed(old, new)
|
||||||
self.__init_label()
|
self.__init_label()
|
||||||
|
|
||||||
def set_object_property(self, name, value):
|
def set_object_property(self, name, value):
|
||||||
if name == "label" and value == "":
|
if (name == "label" and value == "") or name == "use-markup":
|
||||||
self.__init_label()
|
self.__init_label()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
from gi.repository import GObject, Gdk, Gtk
|
from gi.repository import GObject, Gdk, Gtk, CambalachePrivate
|
||||||
|
|
||||||
from .mrg_selection import MrgSelection
|
from .mrg_selection import MrgSelection
|
||||||
from .mrg_gtk_widget import MrgGtkWidget
|
from .mrg_gtk_widget import MrgGtkWidget
|
||||||
@ -43,7 +43,7 @@ class MrgGtkMenu(MrgGtkWidget):
|
|||||||
if self.object is None:
|
if self.object is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.object.popup_at_widget(self.__button, Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, None)
|
self.object.popup_at_widget(self.object.get_attach_widget(), Gdk.Gravity.SOUTH_WEST, Gdk.Gravity.NORTH_WEST, None)
|
||||||
|
|
||||||
def object_changed(self, old, new):
|
def object_changed(self, old, new):
|
||||||
self.selection = None
|
self.selection = None
|
||||||
@ -54,6 +54,10 @@ class MrgGtkMenu(MrgGtkWidget):
|
|||||||
self.window = None
|
self.window = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.selection = MrgSelection(app=self.app, container=self.object)
|
||||||
|
if self.object.get_attach_widget() is not None:
|
||||||
|
return
|
||||||
|
|
||||||
if self.window is None:
|
if self.window is None:
|
||||||
self.__button = Gtk.MenuButton(
|
self.__button = Gtk.MenuButton(
|
||||||
visible=True, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, receives_default=False
|
visible=True, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, receives_default=False
|
||||||
@ -63,21 +67,19 @@ class MrgGtkMenu(MrgGtkWidget):
|
|||||||
self.__button.add(image)
|
self.__button.add(image)
|
||||||
|
|
||||||
self.window = Gtk.Window(title="Menu Preview Window", deletable=False, width_request=320, height_request=240)
|
self.window = Gtk.Window(title="Menu Preview Window", deletable=False, width_request=320, height_request=240)
|
||||||
|
|
||||||
self.window.set_default_size(640, 480)
|
|
||||||
self.window.add(self.__button)
|
self.window.add(self.__button)
|
||||||
|
|
||||||
self.selection = MrgSelection(app=self.app, container=self.object)
|
|
||||||
self.__button.set_popup(self.object)
|
self.__button.set_popup(self.object)
|
||||||
self.object.show_all()
|
|
||||||
self.window.show_all()
|
self.window.show_all()
|
||||||
|
CambalachePrivate.widget_set_application_id(self.window, f"Casilda:{self.ui_id}.{self.object_id}")
|
||||||
|
|
||||||
def on_selected_changed(self):
|
def on_selected_changed(self):
|
||||||
super().on_selected_changed()
|
super().on_selected_changed()
|
||||||
|
|
||||||
if self.__button:
|
if self.selected:
|
||||||
self.__button.set_active(True)
|
self.popup()
|
||||||
|
else:
|
||||||
|
self.object.popdown()
|
||||||
|
|
||||||
def show_child(self, child):
|
def show_child(self, child):
|
||||||
if self.__button:
|
self.popup()
|
||||||
self.__button.set_active(True)
|
|
||||||
|
@ -74,7 +74,7 @@ class MrgGtkWidget(MrgController):
|
|||||||
self.window.hide()
|
self.window.hide()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.toplevel or issubclass(type(self.object), Gtk.Window):
|
if not self.toplevel or issubclass(type(self.object), Gtk.Window) or self.object.props.parent is not None:
|
||||||
# Make sure widget is visible in workspace
|
# Make sure widget is visible in workspace
|
||||||
self.object.set_visible(True)
|
self.object.set_visible(True)
|
||||||
return
|
return
|
||||||
|
@ -26,8 +26,6 @@ from gi.repository import GObject, Gtk
|
|||||||
|
|
||||||
from merengue import utils, MrgPlaceholder
|
from merengue import utils, MrgPlaceholder
|
||||||
|
|
||||||
preselected_widget = None
|
|
||||||
|
|
||||||
|
|
||||||
class FindInContainerData:
|
class FindInContainerData:
|
||||||
def __init__(self, toplevel, x, y):
|
def __init__(self, toplevel, x, y):
|
||||||
@ -42,6 +40,7 @@ class MrgSelection(GObject.GObject):
|
|||||||
app = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE)
|
app = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
self.__selecting = False
|
||||||
self._container = None
|
self._container = None
|
||||||
self.gesture = None
|
self.gesture = None
|
||||||
|
|
||||||
@ -59,29 +58,14 @@ class MrgSelection(GObject.GObject):
|
|||||||
self.gesture = utils.gesture_click_new(self._container, propagation_phase=Gtk.PropagationPhase.CAPTURE)
|
self.gesture = utils.gesture_click_new(self._container, propagation_phase=Gtk.PropagationPhase.CAPTURE)
|
||||||
self.gesture.connect("pressed", self.__on_gesture_button_pressed)
|
self.gesture.connect("pressed", self.__on_gesture_button_pressed)
|
||||||
self.gesture.connect("released", self.__on_gesture_button_released)
|
self.gesture.connect("released", self.__on_gesture_button_released)
|
||||||
else:
|
elif self.gesture:
|
||||||
|
self.gesture.disconnect_by_func(self.__on_gesture_button_pressed)
|
||||||
|
self.gesture.disconnect_by_func(self.__on_gesture_button_released)
|
||||||
self.gesture = None
|
self.gesture = None
|
||||||
|
|
||||||
def __on_gesture_button_pressed(self, gesture, n_press, x, y):
|
def __on_gesture_button_pressed(self, gesture, n_press, x, y):
|
||||||
global preselected_widget
|
|
||||||
|
|
||||||
child = self.get_child_at_position(self._container, x, y)
|
child = self.get_child_at_position(self._container, x, y)
|
||||||
|
|
||||||
if not self.is_widget_from_ui(child):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Pre select a widget on button press
|
|
||||||
if preselected_widget != child:
|
|
||||||
preselected_widget = child
|
|
||||||
gesture.set_state(Gtk.EventSequenceState.CLAIMED)
|
|
||||||
|
|
||||||
def __on_gesture_button_released(self, gesture, n_press, x, y):
|
|
||||||
global preselected_widget
|
|
||||||
|
|
||||||
child = self.get_child_at_position(self._container, x, y)
|
|
||||||
if child != preselected_widget:
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(child, MrgPlaceholder):
|
if isinstance(child, MrgPlaceholder):
|
||||||
controller = child.controller
|
controller = child.controller
|
||||||
|
|
||||||
@ -103,7 +87,15 @@ class MrgSelection(GObject.GObject):
|
|||||||
# Select widget on button release only if its preselected
|
# Select widget on button release only if its preselected
|
||||||
self.app.write_command("selection_changed", args={"selection": [object_id]})
|
self.app.write_command("selection_changed", args={"selection": [object_id]})
|
||||||
controller.selected = True
|
controller.selected = True
|
||||||
gesture.set_state(Gtk.EventSequenceState.CLAIMED)
|
|
||||||
|
if not isinstance(child, Gtk.Window) and not isinstance(child, Gtk.HeaderBar):
|
||||||
|
gesture.set_state(Gtk.EventSequenceState.CLAIMED)
|
||||||
|
self.__selecting = True
|
||||||
|
|
||||||
|
def __on_gesture_button_released(self, gesture, n_press, x, y):
|
||||||
|
if self.__selecting:
|
||||||
|
gesture.set_state(Gtk.EventSequenceState.CLAIMED)
|
||||||
|
self.__selecting = False
|
||||||
|
|
||||||
def is_widget_from_ui(self, obj):
|
def is_widget_from_ui(self, obj):
|
||||||
if isinstance(obj, MrgPlaceholder):
|
if isinstance(obj, MrgPlaceholder):
|
||||||
@ -157,3 +149,4 @@ class MrgSelection(GObject.GObject):
|
|||||||
return widget
|
return widget
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -24,8 +24,9 @@ configure_file(
|
|||||||
)
|
)
|
||||||
|
|
||||||
install_data([
|
install_data([
|
||||||
|
'cmb_accessible_editor.py',
|
||||||
'cmb_base.py',
|
'cmb_base.py',
|
||||||
'cmb_column_view.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',
|
||||||
@ -34,19 +35,30 @@ install_data([
|
|||||||
'cmb_db_migration.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_editor.py',
|
||||||
'cmb_layout_property.py',
|
'cmb_layout_property.py',
|
||||||
'cmb_library_info.py',
|
'cmb_library_info.py',
|
||||||
'cmb_list_error.py',
|
'cmb_list_error.py',
|
||||||
'cmb_list_store.py',
|
'cmb_list_view.py',
|
||||||
|
'cmb_message_notification_view.py',
|
||||||
|
'cmb_notification.py',
|
||||||
|
'cmb_notification_list_row.py',
|
||||||
|
'cmb_notification_list_view.py',
|
||||||
'cmb_object.py',
|
'cmb_object.py',
|
||||||
'cmb_object_data.py',
|
'cmb_object_data.py',
|
||||||
'cmb_object_data_editor.py',
|
'cmb_object_data_editor.py',
|
||||||
'cmb_object_editor.py',
|
'cmb_object_editor.py',
|
||||||
'cmb_objects_base.py',
|
'cmb_objects_base.py',
|
||||||
|
'cmb_path.py',
|
||||||
|
'cmb_poll_notification_view.py',
|
||||||
|
'cmb_poll_option_check.py',
|
||||||
'cmb_project.py',
|
'cmb_project.py',
|
||||||
'cmb_property.py',
|
'cmb_property.py',
|
||||||
|
'cmb_property_info.py',
|
||||||
'cmb_property_label.py',
|
'cmb_property_label.py',
|
||||||
'cmb_signal_editor.py',
|
'cmb_signal_editor.py',
|
||||||
|
'cmb_tree_expander.py',
|
||||||
'cmb_type_chooser.py',
|
'cmb_type_chooser.py',
|
||||||
'cmb_type_chooser_popover.py',
|
'cmb_type_chooser_popover.py',
|
||||||
'cmb_type_chooser_widget.py',
|
'cmb_type_chooser_widget.py',
|
||||||
@ -54,6 +66,7 @@ install_data([
|
|||||||
'cmb_ui.py',
|
'cmb_ui.py',
|
||||||
'cmb_ui_editor.py',
|
'cmb_ui_editor.py',
|
||||||
'cmb_ui_requires_editor.py',
|
'cmb_ui_requires_editor.py',
|
||||||
|
'cmb_version_notification_view.py',
|
||||||
'cmb_view.py',
|
'cmb_view.py',
|
||||||
'constants.py',
|
'constants.py',
|
||||||
'utils.py',
|
'utils.py',
|
||||||
|
@ -15,6 +15,7 @@ foreach d : [['3', gtk3_dep], ['4', gtk4_dep]]
|
|||||||
sources,
|
sources,
|
||||||
dependencies: dep,
|
dependencies: dep,
|
||||||
install: true,
|
install: true,
|
||||||
|
install_dir: privatecambalachedir,
|
||||||
)
|
)
|
||||||
|
|
||||||
gnome.generate_gir(
|
gnome.generate_gir(
|
||||||
@ -27,5 +28,6 @@ foreach d : [['3', gtk3_dep], ['4', gtk4_dep]]
|
|||||||
symbol_prefix: 'cmb_private',
|
symbol_prefix: 'cmb_private',
|
||||||
header: 'cmb_private.h',
|
header: 'cmb_private.h',
|
||||||
install: true,
|
install: true,
|
||||||
|
install_dir_typelib: privatecambalachedir,
|
||||||
)
|
)
|
||||||
endforeach
|
endforeach
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
# SPDX-License-Identifier: LGPL-2.1-only
|
# SPDX-License-Identifier: LGPL-2.1-only
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
from gi.repository import Gdk, Gio
|
from gi.repository import Gdk, Gio
|
||||||
|
|
||||||
|
|
||||||
@ -99,3 +103,101 @@ def content_type_guess(path):
|
|||||||
content_type, uncertain = Gio.content_type_guess(path, data)
|
content_type, uncertain = Gio.content_type_guess(path, data)
|
||||||
|
|
||||||
return content_type
|
return content_type
|
||||||
|
|
||||||
|
|
||||||
|
def utcnow():
|
||||||
|
return int(datetime.datetime.now(datetime.UTC).timestamp())
|
||||||
|
|
||||||
|
|
||||||
|
# XML utilities
|
||||||
|
def bool_from_string(value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value.lower() in {"1", "t", "y", "true", "yes"} if value else False
|
||||||
|
return bool(value)
|
||||||
|
|
||||||
|
|
||||||
|
def xml_node_get(node, *args, errors=None):
|
||||||
|
keys = node.keys()
|
||||||
|
knowns = []
|
||||||
|
retval = []
|
||||||
|
|
||||||
|
def get_key_val(node, attr):
|
||||||
|
tokens = attr.split(":")
|
||||||
|
key = tokens[0]
|
||||||
|
val = node.get(key, None)
|
||||||
|
|
||||||
|
if len(tokens) > 1:
|
||||||
|
t = tokens[1]
|
||||||
|
if t == "bool":
|
||||||
|
return (key, bool_from_string(val))
|
||||||
|
elif t == "int":
|
||||||
|
return (key, int(val))
|
||||||
|
|
||||||
|
return (key, val)
|
||||||
|
|
||||||
|
for attr in args:
|
||||||
|
if isinstance(attr, list):
|
||||||
|
for opt in attr:
|
||||||
|
key, val = get_key_val(node, opt)
|
||||||
|
retval.append(val)
|
||||||
|
knowns.append(key)
|
||||||
|
elif attr in keys:
|
||||||
|
key, val = get_key_val(node, attr)
|
||||||
|
retval.append(val)
|
||||||
|
knowns.append(key)
|
||||||
|
elif errors is not None:
|
||||||
|
errors.append(("missing-attr", node, attr))
|
||||||
|
|
||||||
|
if errors is not None:
|
||||||
|
unknown = list(set(keys) - set(knowns))
|
||||||
|
for attr in unknown:
|
||||||
|
errors.append(("unknown-attr", node, attr))
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
def xml_node_get_comment(node):
|
||||||
|
prev = node.getprevious()
|
||||||
|
if prev is not None and prev.tag is etree.Comment:
|
||||||
|
return prev.text if not prev.text.strip().startswith("interface-") else None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def xml_node_set(node, attr, val):
|
||||||
|
if val is not None:
|
||||||
|
node.set(attr, str(val))
|
||||||
|
|
||||||
|
|
||||||
|
# Duck typing Classes
|
||||||
|
|
||||||
|
|
||||||
|
class FileHash():
|
||||||
|
def __init__(self, fd=None):
|
||||||
|
self.__fd = fd
|
||||||
|
self.__hash = hashlib.sha256()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.__fd:
|
||||||
|
self.__fd.close()
|
||||||
|
|
||||||
|
def peek(self, size):
|
||||||
|
return self.__fd.peek(size) if self.__fd else None
|
||||||
|
|
||||||
|
def read(self, size):
|
||||||
|
if self.__fd:
|
||||||
|
data = self.__fd.read(size)
|
||||||
|
self.__hash.update(data)
|
||||||
|
return data
|
||||||
|
return None
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
if self.__fd:
|
||||||
|
self.__fd.flush()
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self.__hash.update(data)
|
||||||
|
if self.__fd:
|
||||||
|
self.__fd.write(data)
|
||||||
|
|
||||||
|
def hexdigest(self):
|
||||||
|
return self.__hash.hexdigest()
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
CMB_CATALOG_GEN=flatpak run --devel \
|
CMB_CATALOG_GEN=flatpak run --devel \
|
||||||
--command=cmb-catalog-gen \
|
--command=cmb-catalog-gen \
|
||||||
ar.xjuan.Cambalache \
|
ar.xjuan.Cambalache
|
||||||
2>/dev/null
|
|
||||||
|
# CMB_CATALOG_GEN=../run-cmb-catalog-gen.sh
|
||||||
|
|
||||||
BASE_CATALOG_FILES = \
|
BASE_CATALOG_FILES = \
|
||||||
glib/gobject-2.0.xml \
|
glib/gobject-2.0.xml \
|
||||||
@ -11,6 +12,7 @@ BASE_CATALOG_FILES = \
|
|||||||
|
|
||||||
GTK3_DEPS = \
|
GTK3_DEPS = \
|
||||||
${BASE_CATALOG_FILES} \
|
${BASE_CATALOG_FILES} \
|
||||||
|
atk/atk-1.0.xml \
|
||||||
gtk/gdk-3.0.xml
|
gtk/gdk-3.0.xml
|
||||||
|
|
||||||
GTK4_DEPS = \
|
GTK4_DEPS = \
|
||||||
@ -66,6 +68,15 @@ pango/pango-1.0.xml:
|
|||||||
--exclude-objects \
|
--exclude-objects \
|
||||||
--output $@
|
--output $@
|
||||||
|
|
||||||
|
atk/atk-1.0.xml:
|
||||||
|
${CMB_CATALOG_GEN} \
|
||||||
|
--dependencies gobject-2.0 \
|
||||||
|
--gir /usr/share/gir-1.0/Atk-1.0.gir \
|
||||||
|
--types AtkObject \
|
||||||
|
--exclude-objects \
|
||||||
|
--extra-data atk/Atk.xml \
|
||||||
|
--output $@
|
||||||
|
|
||||||
gtk/gdk-3.0.xml:
|
gtk/gdk-3.0.xml:
|
||||||
${CMB_CATALOG_GEN} \
|
${CMB_CATALOG_GEN} \
|
||||||
--dependencies pango-1.0 gdkpixbuf-2.0 \
|
--dependencies pango-1.0 gdkpixbuf-2.0 \
|
||||||
@ -94,7 +105,7 @@ gtk/gsk-4.0.xml:
|
|||||||
|
|
||||||
gtk/gtk+-3.0.xml: gtk/Gtk.xml ${GTK3_DEPS}
|
gtk/gtk+-3.0.xml: gtk/Gtk.xml ${GTK3_DEPS}
|
||||||
${CMB_CATALOG_GEN} \
|
${CMB_CATALOG_GEN} \
|
||||||
--dependencies gdk-3.0 \
|
--dependencies atk-1.0 gdk-3.0 \
|
||||||
--gir /usr/share/gir-1.0/Gtk-3.0.gir \
|
--gir /usr/share/gir-1.0/Gtk-3.0.gir \
|
||||||
--external-catalogs ${GTK3_DEPS} \
|
--external-catalogs ${GTK3_DEPS} \
|
||||||
--extra-data gtk/Gtk.xml \
|
--extra-data gtk/Gtk.xml \
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user