mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-06-25 00:02:51 -04:00
Compare commits
No commits in common. "main" and "0.10.2" have entirely different histories.
18
.flake8
18
.flake8
@ -1,18 +0,0 @@
|
||||
[flake8]
|
||||
max-line-length = 128
|
||||
extend-ignore = E203
|
||||
per-file-ignores =
|
||||
cambalache/__init__.py:F401,E402
|
||||
cambalache/app/__init__.py:F401,E402
|
||||
cambalache/control/__init__.py:F401,E402
|
||||
cambalache/merengue/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_adw/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_handy/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_webkit/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_webkit2/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_gtk/__init__.py:F401,E402
|
||||
glade/gladecambalache/__init__.py:F401,E402
|
||||
exclude =
|
||||
.flatpak-builder
|
||||
build
|
||||
_build
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,13 +1,9 @@
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
__pycache__
|
||||
.flatpak-builder
|
||||
.catalogs
|
||||
.local
|
||||
.lib
|
||||
.lc_messages
|
||||
.vscode
|
||||
.env.local
|
||||
.coverage*
|
||||
build
|
||||
repo
|
||||
cambalache.flatpak
|
||||
@ -17,7 +13,6 @@ cambalache/app.gresource
|
||||
cambalache/config.py
|
||||
cambalache/merengue/config.py
|
||||
cambalache/merengue/merengue
|
||||
subprojects/casilda
|
||||
tools/CmbUtils-3.0.gir
|
||||
tools/CmbUtils-3.0.typelib
|
||||
tools/CmbUtils-4.0.gir
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "tests/images"]
|
||||
path = tests/images
|
||||
url = https://gitlab.gnome.org/jpu/cambalache-test-images.git
|
323
CHANGELOG.md
323
CHANGELOG.md
@ -1,323 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This documents user relevant changes which are also included in
|
||||
data/ar.xjuan.Cambalache.metainfo.xml.in, closed issues from
|
||||
(Gitlab)[https://gitlab.gnome.org/jpu/cambalache/-/issues/] and
|
||||
packaging changes like new dependencies or build system changes.
|
||||
|
||||
Cambalache used even/odd minor numbers to differentiate between stable and
|
||||
development releases.
|
||||
|
||||
|
||||
## 0.96.0
|
||||
|
||||
- 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
|
||||
|
||||
### Packaging changes
|
||||
|
||||
- pygobject-3.0 dependency bumped to 3.52 which depends on the new gi repository from GLib
|
||||
- libcambalacheprivate-[3|4] and its typelib are now installed under libdir/cambalache
|
||||
- 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
|
||||
|
||||
- #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"
|
||||
|
||||
## 0.92.0
|
||||
|
||||
2024-09-27 - Adwaita + Casilda release
|
||||
|
||||
- Support 3rd party libraries
|
||||
- Improved Drag&Drop support
|
||||
- Streamline headerbar
|
||||
- Replaced widget hierarchy treeview with column view
|
||||
- New custom wayland compositor for workspace
|
||||
- Improve workspace performance
|
||||
- Fix window ordering
|
||||
- Enable workspace animations
|
||||
- Basic port to Adwaita
|
||||
- Support new desktop dark style
|
||||
- Many thanks to emersion, kennylevinsen, vyivel and the wlroots community for their support and awesome project
|
||||
|
||||
### Packaging changes
|
||||
|
||||
- New dependency on [casilda 0.2.0](https://gitlab.gnome.org/jpu/casilda)
|
||||
Used for workspace compositor, depends on wlroots 0.18
|
||||
- New python tool cmb-catalog-gen
|
||||
- New shared library cmbcatalogutils-[3|4] used by cmb-catalog-gen
|
||||
This library is built twice once linked with Gtk 3 and one with Gtk 4
|
||||
- Depends on Gtk 4.16 and Adwaita 1.6
|
||||
|
||||
### Issues
|
||||
|
||||
- #231 "Workspace will crash with inserting Some Adw objects"
|
||||
- #230 "Exporting byte data messes encoding (libxml)"
|
||||
- #227 "Add casilda as meson subproject" (sid)
|
||||
- #220 "BUG: Typing cursor for style classes always in the front of style entries."
|
||||
- #222 "cannot create instance of abstract (non-instantiatable) type 'GtkWidget'"
|
||||
- #223 "Cannot add widgets to GtkSizeGroup"
|
||||
- #225 "Cambalache crashes"
|
||||
- #219 "Move existing widgets / hierarchy sections into property fields"
|
||||
- #224 "GtkPicture:file property does not work out of the box"
|
||||
- #11 "Support 3rd party libraries"
|
||||
- #216 "Cambalache 0.90.2 Segment faults"
|
||||
- #213 "Cannot open .ui file created using Gnome Builder"
|
||||
- #215 "Port UI to LibAdwaita"
|
||||
|
||||
|
||||
## 0.90.4
|
||||
|
||||
2024-03-29 - Gtk 4 port
|
||||
|
||||
- Migrate main application to Gtk 4
|
||||
- Update widget catalogs to SDK 46
|
||||
- Add support for child custom fragments
|
||||
- Add add parent context menu action
|
||||
- Mark AdwSplitButton.dropdown-tooltip translatable. (Danial Behzadi)
|
||||
- Bumped version to 0.90 to better indicate we are close to version 1.0
|
||||
- Add WebKitWebContext class
|
||||
- Add brand colors
|
||||
|
||||
### Issues
|
||||
|
||||
- #184 "Headerbar save button not enabled when "translatable" checkbox's state is changed"
|
||||
- #207 "Adding or changing data to signal doesn't activate 'Save' button"
|
||||
- #212 "[Feature] add parent"
|
||||
- #199 "Copy and pasting messes references between widgets"
|
||||
- #196 "postinstall.py is trying to modify files in prefix."
|
||||
- #201 "AdwToolbarView needs special child types"
|
||||
- #220 "BUG: Typing cursor for style classes always in the front of style entries."
|
||||
|
||||
|
||||
## 0.16.0
|
||||
|
||||
2023-09-24: GNOME 45 Release!
|
||||
|
||||
- Bump SDK dependency to SDK 45
|
||||
- Add support for types and properties added in SDK 45
|
||||
- Marked various missing translatable properties
|
||||
|
||||
### Issues
|
||||
|
||||
- #190 "Missing translatable property for Gtk.ColumnViewColumn.title"
|
||||
- #190 "Unassigned local variable"
|
||||
|
||||
|
||||
## 0.14.0
|
||||
|
||||
2023-09-07: GMenu release!
|
||||
|
||||
- Add GMenu support
|
||||
- Add UI requirements edit support
|
||||
- Add Swedish translation. Anders Jonsson
|
||||
- Updated Italian translation. Lorenzo Capalbo
|
||||
- Show deprecated and not available warnings for Objects, properties and signals
|
||||
- Output minimum required library version instead of latest one
|
||||
- Fix output for templates with inline object properties
|
||||
- Various optimizations and bug fixes
|
||||
- Bump test coverage to 66%
|
||||
|
||||
### Issues
|
||||
|
||||
- #185 "Unable to import certain files converted from GTK3 to GTK4""
|
||||
- #177 "Panel is not derivable"
|
||||
- #173 "Cambalache 0.12.0 can't open 0.10.3 project"
|
||||
|
||||
|
||||
## 0.12.0
|
||||
|
||||
2023-06-16: New Features release!
|
||||
|
||||
- User Templates: use your templates anywhere in your project
|
||||
- Workspace CSS support: see your CSS changes live
|
||||
- GtkBuildable Custom Tags: support for styles, items, etc
|
||||
- Property Bindings: bind your property to any source property
|
||||
- XML Fragments: add any xml to any object or UI as a fallback
|
||||
- Preview mode: hide placeholders in workspace
|
||||
- WebKit support: new widget catalog available
|
||||
- External objects references support
|
||||
- Add support for GdkPixbuf, GListModel and GListStore types
|
||||
- Add missing child type attributes to Gtk4 GtkActionBar (B. Teeuwen)
|
||||
- Added French Translation (rene-coty)
|
||||
|
||||
### Issues
|
||||
|
||||
- #121 "Adding handy fails silently without libhandy installed"
|
||||
- #113 "Add button/toggle to disable the placeholders and make the window look like it would look as an app"
|
||||
- #123 "Export should be more user-friendly"
|
||||
- #130 "GtkAboutDialog missing properties"
|
||||
- #135 "List of string properties that should be translatable in Adw"
|
||||
- #136 "Can't build via Flatpak"
|
||||
- #138 "libadwaita widgets aren't categorized"
|
||||
- #122 "Handy widgets not correctly categorized."
|
||||
- #96 "Window resize itself when cut content of notebook tab and go to first tab"
|
||||
- #101 "Right clicking after deselcting button, brokes mouse input"
|
||||
- #120 "Box doesn't remove empty space"
|
||||
- #147 "The "Close" button doesn't close the "About" dialog."
|
||||
- #146 "Scrolling a properties pane conflicts with mousewheel handling of property widgets"
|
||||
- #143 "Support for nested files"
|
||||
- #148 "bug: preview display"
|
||||
- #156 "GDK_BACKEND leaks to workspace process"
|
||||
- #154 "GtkPaned: for properties to be set consistently, need to use start-child and end-child instead of child
|
||||
- #160 "Faster prototyping"
|
||||
- #166 "Allow external Widget or/and from another ui template"
|
||||
- #163 "Add named object to Gtk.Stack"
|
||||
- #170 "Support for actions (GtkActionable, menu models)"
|
||||
- #169 "[main] GtkOrientable is missing in GtkBox properties (maybe in others too)"
|
||||
- #167 "Gtk*Selection models are missing the model property"
|
||||
- #168 "Is there a way to add string items to a GtkStringList?"
|
||||
- #171 "Extended support for inline objects"
|
||||
- #172 "Certain Adw widgets are not availabe (AdwEntryRow)"
|
||||
|
||||
|
||||
## 0.10.0
|
||||
|
||||
2022-06-15: 3rd party libs release!
|
||||
|
||||
- Add Adwaita and Handy library support
|
||||
- Add inline object properties support (only Gtk 4)
|
||||
- Add special child type support (GtkWindow title widget)
|
||||
- Improve clipboard functionality
|
||||
- Add support for reordering children position
|
||||
- Add/Improve worspace support for GtkMenu, GtkNotebook, GtkPopover, GtkStack, GtkAssistant, GtkListBox, GtkMenuItem and GtkCenterBox
|
||||
- New property editors for icon name and color properties
|
||||
- Add support for GdkPixbuf, Pango, Gio, Gdk and Gsk flags/enums types
|
||||
- Add Ukrainian translation (Volodymyr M. Lisivka)
|
||||
- Add Italian translation (capaz)
|
||||
- Add Dutch translation (Gert)
|
||||
|
||||
### Issues
|
||||
|
||||
- #47 "Proper ui file(which compile properly), fails to open in cambalache and show error"
|
||||
- #79 "Change column/row count of GtkBox and GtkGrid"
|
||||
- #81 "No way to add rows to GtkListBox"
|
||||
- #68 "Trouble with GtkHeaderBar"
|
||||
- #82 "Can't change x and y values of widgets in Gtk4 when using GtkFixed"
|
||||
- #62 "Many widget-specific properties appear to be missing"
|
||||
- #83 "Gettext domain is not initialized properly"
|
||||
- #66 "Allow adding new items directly in tree view instead of (only) through preview view"
|
||||
- #86 "Automatically restart merengue when merengue crashes"
|
||||
- #89 "Error `AttributeError: 'NoneType' object has no attribute 'info'` when deleting UI file"
|
||||
- #90 "Cambalache fails to import valid glade/ui files"
|
||||
- #75 "How to use GtkStack"
|
||||
- #78 "How to use GTKAssistant"
|
||||
- #63 "Allow automatically exporting on save (or make it easier to do so)"
|
||||
- #91 "Unable to export"
|
||||
- #85 "Provide icon selection for Button / Image"
|
||||
- #92 "'Debug Project Data' does nothing"
|
||||
- #9 "Support for libadwaita and libhandy"
|
||||
- #59 "Reordering children in a parent"
|
||||
- #100 "Signals get broken"
|
||||
- #105 "Child layout properties not available when parent is a subclass (AdwHeaderBar)"
|
||||
- #102 "Popovers are not visible"
|
||||
- #104 "Error when trying to add children to buttonbox"
|
||||
- #98 "No way to add tab in Notebook"
|
||||
- #108 "Popovers stay on scene after deleting file which contains them"
|
||||
- #109 "Cambalache adds to container GtkRecentChooserMenu even if prints that this won't happen"
|
||||
- #110 "Screen flashing when creating GBinding"
|
||||
- #116 "Error when trying to click at Notebook content"
|
||||
- #117 "Error `'NoneType' object has no attribute 'props'` when changing notebook tab"
|
||||
- #115 "Cannot copy/paste widget"
|
||||
- #69 "Undo and redo operations don't always match up"
|
||||
|
||||
|
||||
## 0.8.0
|
||||
|
||||
2021-12-09: UX improvements Release!
|
||||
|
||||
- New Type chooser bar
|
||||
- Workspace placeholder support
|
||||
- Translatable properties support (Philipp Unger)
|
||||
- Clipboard actions support (Copy, Paste, Cut)
|
||||
- Better unsupported features report
|
||||
- New Matrix channel #cambalache:gnome.org
|
||||
- You can now also support Cambalache on Liberapay
|
||||
|
||||
### Issues
|
||||
|
||||
- #22: Gtk.AboutDialog: license bug
|
||||
- #10: Export widgets layout data packed in GtkGrid
|
||||
- #23: Better appdata summary
|
||||
- #25: Error about target version mismatch
|
||||
- #29: Error opening project
|
||||
- #27: Needs a better icon
|
||||
- #31: Newest ver (git) doesn't display loaded UI
|
||||
- #34: Translations aren't working in the interactive tour
|
||||
- #35: Interactive tour isn't working anymore
|
||||
- #30: Gtk types listed in Cambalache
|
||||
- #36: Can't build Flatpak after the update of the german translation
|
||||
- #38: Add translatable metadata to CmbPropertyInfo
|
||||
- #37: Add support for translatable properties
|
||||
- #39: Save window state (Philipp Unger)
|
||||
- #41: Add clipboard support
|
||||
- #33: No context menu on left pane, the "project view"
|
||||
|
||||
|
||||
## 0.7.0
|
||||
|
||||
2021-08-08: New translations release!
|
||||
|
||||
- Add Czech translation. Vojtěch Perník
|
||||
- Add German translation. PhilProg
|
||||
- Add x-cambalache mimetype with icon
|
||||
|
||||
|
||||
## 0.6.0
|
||||
|
||||
2021-07-21: First public release!
|
||||
|
||||
- Suport for both Gtk 3 and 4 versions
|
||||
- Import and export multiple UI at once
|
||||
- Support plain (no custom tags) GtkBuilder features
|
||||
- Undo / Redo stack
|
||||
- LGPL version 2.1
|
60
Dockerfile
60
Dockerfile
@ -1,60 +0,0 @@
|
||||
FROM debian:sid-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
desktop-file-utils \
|
||||
gettext \
|
||||
gir1.2-adw-1 \
|
||||
gir1.2-gtk-3.0 \
|
||||
gir1.2-gtk-4.0 \
|
||||
gir1.2-gtksource-5 \
|
||||
gir1.2-handy-1 \
|
||||
gir1.2-webkit2-4.1 \
|
||||
gir1.2-webkit-6.0 \
|
||||
git \
|
||||
libadwaita-1-dev \
|
||||
libgirepository-1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libgtk-4-dev \
|
||||
libhandy-1-dev \
|
||||
libwlroots-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
python3-gi \
|
||||
python3-lxml \
|
||||
python-gi-dev
|
||||
|
||||
|
||||
RUN useradd -ms /bin/bash discepolo
|
||||
ENV DISPLAY :0
|
||||
|
||||
RUN mkdir -p /src/build
|
||||
|
||||
COPY . /src/
|
||||
WORKDIR /src
|
||||
|
||||
RUN git clone -b 0.18 https://gitlab.freedesktop.org/wlroots/wlroots.git && \
|
||||
cd wlroots && \
|
||||
meson setup build/ && \
|
||||
ninja -C build/ && \
|
||||
ninja -C build/ install
|
||||
|
||||
WORKDIR /src/build
|
||||
|
||||
RUN meson --prefix=/usr && ninja && ninja install
|
||||
|
||||
RUN rm -rf /src
|
||||
|
||||
RUN apt-get remove -y \
|
||||
git \
|
||||
libadwaita-1-dev \
|
||||
libgirepository-1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libgtk-4-dev \
|
||||
libhandy-1-dev \
|
||||
libwlroots-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
python-gi-dev
|
||||
|
||||
USER discepolo
|
||||
ENTRYPOINT ["/bin/sh", "-c", "$0 \"$@\"", "cambalache"]
|
19
Makefile
19
Makefile
@ -1,19 +0,0 @@
|
||||
repo: ar.xjuan.Cambalache.json .git/objects
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak install --noninteractive --user flathub org.gnome.Sdk//48
|
||||
flatpak install --noninteractive --user flathub org.gnome.Platform//48
|
||||
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
|
||||
|
||||
cambalache.flatpak: repo
|
||||
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache
|
||||
|
||||
.PHONY: install clean veryclean
|
||||
|
||||
install: cambalache.flatpak
|
||||
flatpak install --user cambalache.flatpak
|
||||
|
||||
clean:
|
||||
rm -rf repo cambalache.flatpak
|
||||
|
||||
veryclean: clean
|
||||
rm -rf .flatpak-builder
|
@ -1,16 +0,0 @@
|
||||
## README Mac OS
|
||||
|
||||
There is an on going effort to run Cambalache using dependencies from mac port.
|
||||
See issue [#161](https://gitlab.gnome.org/jpu/cambalache/-/issues/161)
|
||||
|
||||
In the mean time you can run Cambalache building a docker image and installing
|
||||
a X server.
|
||||
|
||||
Steps:
|
||||
- Install [Docker](https://www.docker.com/) and [Xquarts](https://www.xquartz.org/)
|
||||
- Build cambalache docker image
|
||||
- `docker build -t cambalache .`
|
||||
- Make sure docker can connect to the server
|
||||
- `xhost +localhost`
|
||||
- Run docker image
|
||||
- `docker run -e DISPLAY=host.docker.internal:0 cambalache`
|
157
README.md
157
README.md
@ -1,12 +1,12 @@
|
||||

|
||||
|
||||
Cambalache is a RAD tool for Gtk 4 and 3 with a clear MVC design and data model first philosophy.
|
||||
Cambalache is a new RAD tool for Gtk 4 and 3 with a clear MVC design and data model first philosophy.
|
||||
This translates to a wide feature coverage with minimal/none developer intervention for basic support.
|
||||
|
||||

|
||||
|
||||
To support multiple Gtk versions it renders the workspace out of process using
|
||||
a custom wayland compositor widget based on wlroots.
|
||||
the Gdk broadway backend.
|
||||
|
||||

|
||||
|
||||
@ -27,21 +27,36 @@ Source code lives on GNOME gitlab [here](https://gitlab.gnome.org/jpu/cambalache
|
||||
## Dependencies
|
||||
|
||||
* Python 3 - Cambalache is written in Python
|
||||
* [Meson](http://mesonbuild.com) build system
|
||||
* [GTK](http://www.gtk.org) 3 and 4
|
||||
* [Meson](http://mesonbuild.org) build system
|
||||
* [GTK](http://www.gtk.org) 3 and 4 with broadway backend enabled
|
||||
* python-gi - Python GTK bindings
|
||||
* python3-lxml - Python libxml2 bindings
|
||||
* [casilda](https://gitlab.gnome.org/jpu/casilda) - Workspace custom compositor
|
||||
* WebkitGTK - Webview for workspace
|
||||
|
||||
## Running from sources
|
||||
|
||||
To run it without installing use run-dev.py script, it will automatically compile
|
||||
resources and create extra files needed to run.
|
||||
|
||||
`./run-dev.py`
|
||||
|
||||
The minimum requirements are Gtk 3 and lxml, Gtk 4 is only needed to have a functional Gtk 4 workspace.
|
||||
|
||||
## Flatpak
|
||||
|
||||
The preferred way to run Cambalache is using flatpak.
|
||||
Instructions on how to install flatpak can be found [here](https://flatpak.org/setup/).
|
||||
|
||||
Build your bundle with the following commands
|
||||
```
|
||||
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
|
||||
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache
|
||||
flatpak install --user cambalache.flatpak
|
||||
```
|
||||
|
||||
## Flathub
|
||||
|
||||
Flathub is the place to get and distribute apps for all of desktop Linux.
|
||||
It is powered by Flatpak, allowing Flathub apps to run on almost any Linux
|
||||
distribution.
|
||||
|
||||
Instructions on how to install flatpak can be found [here](https://flatpak.org/setup/).
|
||||
|
||||
You can get the official build [here](https://flathub.org/apps/details/ar.xjuan.Cambalache)
|
||||
You can get Cambalache prebuilt bundles [here](https://flathub.org/apps/details/ar.xjuan.Cambalache)
|
||||
|
||||
Use the following to install:
|
||||
```
|
||||
@ -49,116 +64,29 @@ flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flath
|
||||
flatpak install --user flathub ar.xjuan.Cambalache
|
||||
```
|
||||
|
||||
## Flatpak
|
||||
|
||||
Use the following commands to install build dependencies:
|
||||
|
||||
```
|
||||
flatpak remote-add --user --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
|
||||
flatpak install --user org.gnome.Sdk//master
|
||||
flatpak install --user org.gnome.Platform//master
|
||||
```
|
||||
|
||||
Build your bundle with the following commands
|
||||
|
||||
```
|
||||
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
|
||||
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache
|
||||
flatpak install --user cambalache.flatpak
|
||||
```
|
||||
Or if you have `make` installed in your host
|
||||
|
||||
```
|
||||
make install
|
||||
```
|
||||
|
||||
Will create the flatpak repository, then the bundle and install it
|
||||
|
||||
Run as:
|
||||
```
|
||||
flatpak run --user ar.xjuan.Cambalache//master
|
||||
```
|
||||
|
||||
## Manual installation
|
||||
|
||||
This is a regular meson package and can be installed the usual way.
|
||||
|
||||
```
|
||||
# Configure project in _build directory
|
||||
meson setup --wipe --prefix=~/.local _build .
|
||||
# Create build directory and configure project
|
||||
mkdir _build && cd _build
|
||||
meson --prefix=~/.local
|
||||
|
||||
# Build and install in ~/.local
|
||||
ninja -C _build install
|
||||
# Build and install
|
||||
ninja
|
||||
ninja install
|
||||
```
|
||||
|
||||
To run it from .local/ you might need to setup a few env variable depending on your distribution
|
||||
To run it from .local/ you might need to setup PYTHONPATH env variable depending
|
||||
on your distribution defaults
|
||||
|
||||
```
|
||||
export PYTHONPATH=~/.local/lib/python3/dist-packages/
|
||||
export LD_LIBRARY_PATH=~/.local/lib/x86_64-linux-gnu/
|
||||
export GI_TYPELIB_PATH=~/.local/lib/x86_64-linux-gnu/girepository-1.0/
|
||||
cambalache
|
||||
export PYTHONPATH=.local/lib/python3/dist-packages/
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
While docker is not meant for UI applications it is possible to build an image
|
||||
with Cambalache and run it.
|
||||
|
||||
Build the image with:
|
||||
```
|
||||
docker build -t cambalache .
|
||||
```
|
||||
|
||||
On linux you can run it on wayland with:
|
||||
|
||||
```
|
||||
docker run \
|
||||
-e XDG_RUNTIME_DIR=/tmp \
|
||||
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
|
||||
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
|
||||
--user=$(id -u):$(id -g) \
|
||||
cambalache
|
||||
```
|
||||
|
||||
or on X server with:
|
||||
```
|
||||
xhost +local:
|
||||
docker run -v /tmp/.X11-unix:/tmp/.X11-unix cambalache
|
||||
```
|
||||
|
||||
NOTE: There is no official support for Docker, please use Flatpak if possible.
|
||||
|
||||
## MS Windows
|
||||
|
||||
Instructions to run in MS Windows are [here](README.win.md)
|
||||
|
||||
NOTE: There is no official support for Windows yet, these instruction should be
|
||||
taken with a grain of salt as they might not work on all Windows versions or
|
||||
be obsolete.
|
||||
|
||||
## MacOS
|
||||
|
||||
Instructions to run in MacOS are [here](README.mac.md)
|
||||
|
||||
NOTE: There is no official support for MacOS yet, these instruction should be
|
||||
taken with a grain of salt as they might not work on all MacOS versions or
|
||||
be obsolete.
|
||||
|
||||
## Running from sources
|
||||
|
||||
To run it without installing use run-dev.sh script, it will automatically compile
|
||||
cambalache under .local directoy and set up all environment variables needed to
|
||||
run the app from the source directory. (Follow manual installation to ensure
|
||||
you have everything needed)
|
||||
|
||||
`./run-dev.py`
|
||||
|
||||
This is meant for Cambalache development only.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing you can open an issue [here](https://gitlab.gnome.org/jpu/cambalache/-/issues)
|
||||
If you are interested in contributing you can open and issue [here](https://gitlab.gnome.org/jpu/cambalache/-/issues)
|
||||
and/or a merge request [here](https://gitlab.gnome.org/jpu/cambalache/-/merge_requests)
|
||||
|
||||
## Contact
|
||||
@ -186,11 +114,10 @@ like all these [people](./SUPPORTERS.md) did.
|
||||
- ~8% commission fee
|
||||
- ~8% payment processing fee
|
||||
|
||||
## cmb-catalog-gen
|
||||
## Tools
|
||||
|
||||
This tool is used to generate Cambalache catalogs from Gir files.
|
||||
- cambalache-db:
|
||||
Generate Data Model from Gir files
|
||||
|
||||
A catalog is a XML file with all the necessary data for Cambalache to produce
|
||||
UI files with widgets from a particular library, this includes the different
|
||||
GTypes, with their properties, signals and everything else except
|
||||
the actual object implementations.
|
||||
- db-codegen:
|
||||
Generate GObject classes from DB tables
|
||||
|
127
README.win.md
127
README.win.md
@ -1,127 +0,0 @@
|
||||
## README MS Windows
|
||||
|
||||
These instructions have been tested on Windows 10.
|
||||
They should also work on Windows 11.
|
||||
|
||||
## 1. Install WSL2
|
||||
1. Start -> type `cmd` -> Right-click `Command Prompt` -> select `Run as administrator`.
|
||||
2. Type this command:
|
||||
```
|
||||
wsl --install
|
||||
```
|
||||
By default, this will install Ubuntu 20.
|
||||
|
||||
## 2. Add Ubuntu 22 to WSL
|
||||
1. Start -> type `store` -> Select `Microsoft Store`.
|
||||
2. Click the search bar at the top. Type: `ubuntu 22`
|
||||
3. Click the result: `Ubuntu 22.04.x LTS`. Click `Install`.
|
||||
- Troubleshooting: If it says "There has been an error.", try rebooting Windows.
|
||||
|
||||
## 3. Configure a Linux user
|
||||
1. New window appears with message:
|
||||
> Installing, this may take a few minutes...
|
||||
2. Then:
|
||||
> Please create a default UNIX user account. The username does not need to match your Windows username.
|
||||
>
|
||||
> For more information visit: https://aka.ms/wslusers
|
||||
>
|
||||
> Enter new UNIX username:
|
||||
3. Type a username you want to use, e.g., `jmoore`.
|
||||
4. Type a password for the user, e.g. " ". (1 space character)
|
||||
5. Repeat the password.
|
||||
- You should now be at a Linux command prompt.
|
||||
## 4. Install software in Ubuntu 22
|
||||
- Start -> type `ubu` -> Select `Ubuntu 22.04.x`.
|
||||
- Paste these commands, one at a time. There will be interactive user prompts for password and Yes/No questions.
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install flatpak
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
```
|
||||
## 5. Install the `GWSL` X Windows Server for Microsoft Windows.
|
||||
1. Start -> type `store` -> Select `Microsoft Store`.
|
||||
2. Click the search bar at the top. Type: `gwsl`
|
||||
3. Click the result: `GWSL`. Click `Install`.
|
||||
4. Start -> type `gwsl` -> Select `GWSL`.
|
||||
5. Set `Public` to Checked. This allows X Windows clients from Ubuntu 22 WSL2 installation to connect to the GWSL X-Windows server.
|
||||
- The System Tray notification area should have a new orange icon, the GWSL X server running.
|
||||
## 6. Install and Run Cambalache as a flatpak
|
||||
1. Create a startup script for needed daemon, environment variables:
|
||||
```
|
||||
vi ~/start_cam.sh
|
||||
```
|
||||
You can put these commands on the command-line at first as a test, put them in another script, `~/.bashrc`, etc. Making a script helps when you want to start it from a MS Windows icon.
|
||||
|
||||
2. Paste these lines as the script:
|
||||
```
|
||||
#!/bin/sh
|
||||
export WEBKIT_DISABLE_COMPOSITING_MODE=1
|
||||
flatpak run --user ar.xjuan.Cambalache
|
||||
```
|
||||
- The middle line forces the Workspace area of Cambalache, displayed with `webkit`, to not try to use hardware acceleration. If it does try such acceleration in `WSL2`, the Workspace area cannot be drawn and remains blank, even though it is being rendered OK in `broadwayd`.
|
||||
|
||||
- The last line runs the flatpak for Cambalache.
|
||||
|
||||
Save and quit (`Esc :wq`).
|
||||
|
||||
3. Make the script executable:
|
||||
```
|
||||
chmod +x start_cam.sh
|
||||
```
|
||||
|
||||
4. Install Cambalache from flatpak.
|
||||
```
|
||||
sudo service dbus start
|
||||
flatpak install --user flathub ar.xjuan.Cambalache
|
||||
```
|
||||
- The first line starts the dbus service, required for flatpak to work. Modern Linux installations start dbus at boot, but WSL2 does not.
|
||||
|
||||
5. Run Cambalache from the command-line.
|
||||
```
|
||||
export DISPLAY="`grep nameserver /etc/resolv.conf | sed 's/nameserver //'`:0"
|
||||
./start_cam.sh
|
||||
```
|
||||
- The first line defines how to find the X windows server. In WSL2, it is at the IP address of the host Windows computer. The $DISPLAY environment variable lets X clients in WSL get to the X Windows server running in MS Windows. It ends up being a value like:
|
||||
```
|
||||
$ echo $DISPLAY
|
||||
192.168.144.1:0
|
||||
```
|
||||
`GWSL` was installed, started, and confirmed running in the previous section. So, it is at that IP address, running in MS Windows, waiting for X windows clients to connect to it.
|
||||
|
||||
## 7. Starting Cambalache from Windows
|
||||
1. Click the Windows taskbar Notification area (near the Clock) expand arrow -> (orange GWSL icon) -> `Dashboard`.
|
||||
2. Click `Shortcut Creator`.
|
||||
Enter the following settings:
|
||||
|
||||
- Shortcut Label: Cambalache
|
||||
|
||||
- Shortcut Command:
|
||||
```
|
||||
/home/(your WSL2 username)/start_cam.sh
|
||||
```
|
||||
e.g.
|
||||
```
|
||||
/home/jmoore/start_cam.sh
|
||||
```
|
||||
|
||||
- Run in: `Ubuntu-22.04` <-- IMPORTANT
|
||||
|
||||
- Click `More Options`.
|
||||
|
||||
- Select `Color Mode`: `Light Mode`. <-- especially if you run Windows in Dark Mode
|
||||
|
||||
- Select `Use DBus (Sudo Required)`: `True`.
|
||||
|
||||
When the icon is run, this will start dbus, required by flatpak - but will prompt for the user's password each time.
|
||||
|
||||
WSL2 also has the dubiously helpful feature of closing all WSL2 processes when there are no WSL2 terminal sessions open for 15 seconds. So it would kill Cambalache.
|
||||
|
||||
To defeat this, select this option. It keeps the WSL2 session running when there are no WSL2 console windows open.
|
||||
|
||||
- Click `Add to Start Menu`.
|
||||
|
||||
3. Start -> type `cam` -> `Right-click` `Cambalache on Ubuntu 22.04`.
|
||||
4. Select `Pin to Start`.
|
||||
5. Click `Start` again.
|
||||
- Note how Cambalache is now featured prominently and can be started easily from Windows.
|
||||
6. Click `Cambalache on Ubuntu 22.04` to start it.
|
@ -1,26 +1,20 @@
|
||||
# Cambalache supporters
|
||||
|
||||
Many thanks to all the people that support the project
|
||||
Many thanks to all the people that suppport the project
|
||||
|
||||
- Stephan McCormick
|
||||
- Willo Vincent
|
||||
- Javier Jardón
|
||||
- Franz Gratzer
|
||||
- David
|
||||
- Sonny Piers
|
||||
- Stephan McCormick
|
||||
- Patrick Griffis
|
||||
- Aemilia Scott
|
||||
- Jonathan K.
|
||||
- Luis Barron
|
||||
- Mitch 4J
|
||||
- JustRyan
|
||||
- Javier Jardón
|
||||
- Sonny Piers
|
||||
- Platon workaccount
|
||||
- ~1826340
|
||||
- Mula Gabriel
|
||||
- Felipe Borges
|
||||
- Johannes Deutsch
|
||||
- David
|
||||
- Michel Fodje
|
||||
- Vincent Bermel
|
||||
- Aemilia Scott
|
||||
- Patrick
|
||||
- 2 kojix
|
||||
- Coleman
|
||||
- Muasim
|
||||
- Coleman
|
||||
- Shogo Takata
|
||||
- Jonathan K.
|
||||
|
63
TODO.md
Normal file
63
TODO.md
Normal file
@ -0,0 +1,63 @@
|
||||
## Project:
|
||||
|
||||
- Templates
|
||||
|
||||
- CSS
|
||||
|
||||
- GResource
|
||||
|
||||
|
||||
## GtkBuilder missing features:
|
||||
|
||||
- Special type children
|
||||
- <child type="">
|
||||
|
||||
- Internal children
|
||||
- <child internal-child="name">
|
||||
|
||||
- Properties
|
||||
- Translatable parameter
|
||||
- Bindings
|
||||
|
||||
- GMenuModel
|
||||
<menu id="">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="" translatable="yes">Value</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<submenu>
|
||||
<attribute name="" translatable="yes">Value</attribute>
|
||||
<section/>
|
||||
</submenu>
|
||||
</section>
|
||||
</menu>
|
||||
|
||||
- GtkWidget
|
||||
- <style> <class name="css-class"/> </style>
|
||||
- <action-widgets>
|
||||
<action-widget response="">value</action-widget>
|
||||
</action-widgets>
|
||||
|
||||
- GtkListStore
|
||||
- <columns> <column type="gtype"/> </columns>
|
||||
- <data> <row> <col id="int">value</col> </row> </data>
|
||||
|
||||
- GtkTreeStore
|
||||
- <columns> <column type="gtype"/> </columns>
|
||||
|
||||
- GtkLabel
|
||||
- <attributes> <attribute name="gchararray" value="red" start="5" end="10">int</attribute> </attributes>
|
||||
|
||||
- GtkLevelBar
|
||||
- <offsets> <offset name="gchararray" value="1"/> </offsets>
|
||||
|
||||
- GtkScale
|
||||
- <marks> <mark value="0" position="bottom"> </mark>
|
||||
|
||||
- GtkComboBoxText
|
||||
- <items> <item>1</item> </items>
|
||||
|
||||
- GtkSizeGroup
|
||||
- <widgets> <widget name="gchararray"/> </widgets>
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"app-id" : "ar.xjuan.Cambalache",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "48",
|
||||
"runtime-version" : "42",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"separate-locales" : false,
|
||||
"separate-locales": false,
|
||||
"command" : "cambalache",
|
||||
"finish-args" : [
|
||||
"--share=ipc",
|
||||
@ -26,65 +26,16 @@
|
||||
],
|
||||
"modules" : [
|
||||
{
|
||||
"name" : "python3-lxml",
|
||||
"buildsystem" : "simple",
|
||||
"build-commands" : [
|
||||
"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" : [
|
||||
"sources": [
|
||||
{
|
||||
"type" : "file",
|
||||
"url" : "https://files.pythonhosted.org/packages/80/61/d3dc048cd6c7be6fe45b80cedcbdd4326ba4d550375f266d9f4246d0f4bc/lxml-5.3.2.tar.gz",
|
||||
"sha256" : "773947d0ed809ddad824b7b14467e1a481b8976e87278ac4a730c2f7c7fcddc1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "libseat",
|
||||
"buildsystem" : "meson",
|
||||
"config-opts" : [
|
||||
"-Dserver=disabled",
|
||||
"-Dman-pages=disabled"
|
||||
],
|
||||
"sources" : [
|
||||
{
|
||||
"type" : "archive",
|
||||
"url" : "https://git.sr.ht/~kennylevinsen/seatd/archive/0.8.0.tar.gz",
|
||||
"sha256" : "a562a44ee33ccb20954a1c1ec9a90ecb2db7a07ad6b18d0ac904328efbcf65a0",
|
||||
"x-checker-data" : {
|
||||
"type" : "anitya",
|
||||
"project-id" : 234932,
|
||||
"stable-only" : true,
|
||||
"url-template" : "https://git.sr.ht/~kennylevinsen/seatd/archive/$version.tar.gz"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "wlroots",
|
||||
"builddir" : true,
|
||||
"buildsystem" : "meson",
|
||||
"config-opts" : [],
|
||||
"sources" : [
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "https://gitlab.freedesktop.org/wlroots/wlroots.git",
|
||||
"tag" : "0.18.1",
|
||||
"commit" : "5bc39071d173301eb8b2cd652c711075526dfbd9"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : "casilda",
|
||||
"builddir" : true,
|
||||
"buildsystem" : "meson",
|
||||
"config-opts" : [],
|
||||
"sources" : [
|
||||
{
|
||||
"type" : "git",
|
||||
"url" : "https://gitlab.gnome.org/jpu/casilda.git",
|
||||
"tag" : "0.9.0",
|
||||
"commit" : "4f7b1be321cf76832b12bda11fd91897257377e2"
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/e5/21/a2e4517e3d216f0051687eea3d3317557bde68736f038a3b105ac3809247/lxml-4.6.3.tar.gz",
|
||||
"sha256": "39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -96,15 +47,9 @@
|
||||
{
|
||||
"type" : "git",
|
||||
"path" : ".",
|
||||
"branch" : "HEAD"
|
||||
"branch": "HEAD"
|
||||
}
|
||||
],
|
||||
"config-opts" : [
|
||||
"--libdir=lib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"build-options" : {
|
||||
"env" : { }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Cambalache
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -19,8 +19,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
@ -28,80 +26,54 @@ import logging
|
||||
import locale
|
||||
import builtins
|
||||
|
||||
from . import config
|
||||
|
||||
gi.require_version("GIRepository", "3.0")
|
||||
gi.require_version("Gdk", "4.0")
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("GtkSource", "5")
|
||||
gi.require_version("WebKit", "6.0")
|
||||
gi.require_version('Adw', '1')
|
||||
|
||||
# Ensure _() builtin
|
||||
if "_" not in builtins.__dict__:
|
||||
_ = locale.gettext
|
||||
if '_' not in builtins.__dict__:
|
||||
builtins.__dict__['_'] = locale.gettext
|
||||
|
||||
if "N_" not in builtins.__dict__:
|
||||
if 'N_' not in builtins.__dict__:
|
||||
builtins.__dict__['N_'] = lambda s, p, n: _(p) if n > 1 else _(s)
|
||||
|
||||
def N_(s, p, n):
|
||||
return _(p) if n > 1 else _(s)
|
||||
from .config import *
|
||||
|
||||
|
||||
# noqa: E402,E401
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gdk, Gtk
|
||||
|
||||
|
||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "cambalache.gresource"))
|
||||
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'cambalache.gresource'))
|
||||
resource._register()
|
||||
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_resource("/ar/xjuan/Cambalache/cambalache.css")
|
||||
display = Gdk.Display.get_default()
|
||||
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - 1)
|
||||
|
||||
# FIXME: this is needed in flatpak for icons to work
|
||||
Gtk.IconTheme.get_for_display(display).add_search_path("/app/share/icons")
|
||||
|
||||
provider.load_from_resource('/ar/xjuan/Cambalache/cambalache.css')
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
def getLogger(name):
|
||||
formatter = logging.Formatter("%(levelname)s:%(name)s %(message)s")
|
||||
formatter = logging.Formatter('%(levelname)s:%(name)s %(message)s')
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(os.environ.get("CAMBALACHE_LOGLEVEL", "WARNING").upper())
|
||||
logger.setLevel(os.environ.get('MERENGUE_LOGLEVEL', 'WARNING').upper())
|
||||
logger.addHandler(ch)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
from .cmb_objects_base import CmbBaseObject
|
||||
from .cmb_css import CmbCSS
|
||||
from .cmb_ui import CmbUI
|
||||
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_label import CmbPropertyLabel
|
||||
from .cmb_layout_property import CmbLayoutProperty
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from .cmb_project import CmbProject
|
||||
|
||||
from .cmb_db_inspector import CmbDBInspector
|
||||
from .cmb_view import CmbView
|
||||
from .cmb_list_view import CmbListView
|
||||
from .cmb_notification import notification_center, CmbNotification, CmbNotificationCenter
|
||||
from .cmb_notification_list_view import CmbNotificationListView
|
||||
from .cmb_tree_view import CmbTreeView
|
||||
from .cmb_object_editor import CmbObjectEditor
|
||||
from .cmb_signal_editor import CmbSignalEditor
|
||||
from .cmb_ui_editor import CmbUIEditor
|
||||
from .cmb_ui_requires_editor import CmbUIRequiresEditor
|
||||
from .cmb_css_editor import CmbCSSEditor
|
||||
from .cmb_gresource_editor import CmbGResourceEditor
|
||||
from .cmb_fragment_editor import CmbFragmentEditor
|
||||
from .cmb_accessible_editor import CmbAccessibleEditor
|
||||
from .cmb_type_chooser import CmbTypeChooser
|
||||
from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
||||
from .cmb_type_chooser_popover import CmbTypeChooserPopover
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Cambalache Application
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -19,16 +19,14 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from cambalache import config
|
||||
from gi.repository import Gio
|
||||
|
||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "app.gresource"))
|
||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, 'app.gresource'))
|
||||
resource._register()
|
||||
|
||||
from .cmb_application import CmbApplication
|
||||
from .cmb_scrolled_window import CmbScrolledWindow
|
||||
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/ar/xjuan/Cambalache/app">
|
||||
<file>metainfo.xml</file>
|
||||
<file>SUPPORTERS.md</file>
|
||||
<file>cmb_window.ui</file>
|
||||
<file>cmb_shortcuts.ui</file>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* cambalache.css
|
||||
*
|
||||
* Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
* Copyright (C) 2021 Juan Pablo Ugarte
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -21,55 +21,42 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
window.cmb-window .logo {
|
||||
background: url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg') no-repeat 50% 35% / 40%;
|
||||
@binding-set WindowBindings {
|
||||
bind "<Control>s" { "cmb-action" ("save") };
|
||||
bind "<Control>w" { "cmb-action" ("close") };
|
||||
bind "<Control>z" { "cmb-action" ("undo") };
|
||||
bind "<Control><shift>z" { "cmb-action" ("redo") };
|
||||
bind "Delete" { "cmb-action" ("delete") };
|
||||
bind "<Control>n" { "cmb-action" ("create_new") };
|
||||
bind "<Control>o" { "cmb-action" ("open") };
|
||||
bind "<Control>Insert" { "cmb-action-bool" ("add_placeholder", 0) };
|
||||
bind "<Control>Delete" { "cmb-action-bool" ("remove_placeholder", 0) };
|
||||
bind "<Control><shift>Insert" { "cmb-action-bool" ("add_placeholder", 1) };
|
||||
bind "<Control><shift>Delete" { "cmb-action-bool" ("remove_placeholder", 1) };
|
||||
}
|
||||
|
||||
window.cmb-window.dark .logo {
|
||||
CmbWindow .logo {
|
||||
background: url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg') no-repeat 50% 35% / 40%
|
||||
}
|
||||
|
||||
CmbWindow.dark .logo {
|
||||
color: white;
|
||||
background: -gtk-recolor(url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg'), success #ffcb85, error #1a1a1a) no-repeat 50% 35% / 40%;
|
||||
background: -gtk-recolor(url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg'), success #ffcb85, error #1a1a1a) no-repeat 50% 35% / 40%
|
||||
}
|
||||
|
||||
window.cmb-window label.message {
|
||||
CmbWindow label.message {
|
||||
padding: 1ex 1em;
|
||||
border-radius: 1ex 1ex 0 0;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
}
|
||||
|
||||
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 {
|
||||
CmbWindow.dark label.message {
|
||||
color: black;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
}
|
||||
|
||||
window.cmb-window stackswitcher.compact > button {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
window.cmb-window box.donate {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
popover.cmb-tutor > * {
|
||||
popover.cmb-tutor {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@ -78,63 +65,13 @@ popover.cmb-tutor label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
popover.cmb-tutor image {
|
||||
padding-right: 1em;
|
||||
-gtk-icon-size: 48px;
|
||||
}
|
||||
|
||||
button.cmb-tutor-highlight,
|
||||
modelbutton.cmb-tutor-highlight,
|
||||
buttonbox.cmb-tutor-highlight > button,
|
||||
menubutton.cmb-tutor-highlight > button,
|
||||
stackswitcher.cmb-tutor-highlight > button,
|
||||
stack.cmb-tutor-highlight,
|
||||
entry.cmb-tutor-highlight,
|
||||
treeview.cmb-tutor-highlight,
|
||||
box.cmb-tutor-highlight,
|
||||
CmbView.cmb-tutor-highlight {
|
||||
box.cmb-tutor-highlight {
|
||||
box-shadow: inset 0px 0px 6px @theme_selected_bg_color;
|
||||
transition: box-shadow .75s ease;
|
||||
}
|
||||
|
||||
CmbView.cmb-tutor-highlight {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
CmbTypeChooser {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
CmbUIEditor,
|
||||
CmbFragmentEditor,
|
||||
CmbObjectEditor,
|
||||
CmbAccessibleEditor {
|
||||
padding: 0 4px 4px 4px;
|
||||
}
|
||||
|
||||
CmbCSSEditor,
|
||||
CmbGResourceEditor,
|
||||
CmbGResourceFileEditor,
|
||||
stackswitcher.property-pane {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
textview {
|
||||
border: solid @borders 1px;
|
||||
}
|
||||
|
||||
button.borderless {
|
||||
border: unset;
|
||||
}
|
||||
|
||||
image.icon-size-32 {
|
||||
-gtk-icon-size: 32px;
|
||||
}
|
||||
|
||||
image.icon-size-64 {
|
||||
-gtk-icon-size: 64px;
|
||||
}
|
||||
|
||||
windowtitle.changed {
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -21,8 +21,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache Application
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,245 +20,122 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import gi
|
||||
|
||||
gi.require_version('Adw', '1')
|
||||
from gi.repository import GLib, Gdk, Gtk, Gio, Adw
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GLib, Gdk, Gtk, Gio
|
||||
|
||||
from cambalache import *
|
||||
|
||||
from .cmb_window import CmbWindow
|
||||
from cambalache import CmbProject, utils, config, _
|
||||
|
||||
basedir = os.path.dirname(__file__) or "."
|
||||
basedir = os.path.dirname(__file__) or '.'
|
||||
|
||||
|
||||
class CmbApplication(Adw.Application):
|
||||
class CmbApplication(Gtk.Application):
|
||||
def __init__(self):
|
||||
super().__init__(application_id="ar.xjuan.Cambalache", flags=Gio.ApplicationFlags.HANDLES_OPEN)
|
||||
super().__init__(application_id='ar.xjuan.Cambalache',
|
||||
flags=Gio.ApplicationFlags.HANDLES_OPEN)
|
||||
|
||||
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, _("Deprecated: Export project"), None
|
||||
)
|
||||
self.add_main_option('export-all', b'E',
|
||||
GLib.OptionFlags.NONE,
|
||||
GLib.OptionArg.FILENAME,
|
||||
_("Export project"),
|
||||
None)
|
||||
|
||||
def add_new_window(self):
|
||||
def __add_window(self):
|
||||
window = CmbWindow(application=self)
|
||||
window.connect("close-request", self.__on_window_close_request)
|
||||
window.connect("project-closed", self.__on_window_project_closed)
|
||||
window.connect('open-project', self.__on_open_project)
|
||||
self.add_window(window)
|
||||
return window
|
||||
|
||||
def open_project(self, path, target_tk=None):
|
||||
def open(self, path, target_tk=None, uiname=None):
|
||||
window = None
|
||||
|
||||
for win in self.get_windows():
|
||||
if win.project and win.project.filename == path:
|
||||
if win.project is not None and win.project.filename == path:
|
||||
window = win
|
||||
|
||||
if window is None:
|
||||
window = self.add_new_window()
|
||||
window = self.__add_window()
|
||||
if path is not None:
|
||||
window.open_project(path, target_tk=target_tk)
|
||||
window.open_project(path, target_tk=target_tk, uiname=uiname)
|
||||
|
||||
window.present()
|
||||
|
||||
def import_file(self, path):
|
||||
window = self.add_new_window() if self.props.active_window is None else self.props.active_window
|
||||
window = self.__add_window() if self.props.active_window is None else self.props.active_window
|
||||
window.import_file(path)
|
||||
window.present()
|
||||
|
||||
def check_can_quit(self, window=None):
|
||||
windows = self.__get_windows() if window is None else [window]
|
||||
unsaved_windows = []
|
||||
windows2save = []
|
||||
|
||||
def do_quit():
|
||||
if window is None:
|
||||
self.quit()
|
||||
else:
|
||||
self.remove_window(window)
|
||||
window.destroy()
|
||||
|
||||
# Gather projects that needs saving
|
||||
for win in windows:
|
||||
if win.project is None:
|
||||
continue
|
||||
|
||||
if win.actions["save"].get_enabled():
|
||||
unsaved_windows.append(win)
|
||||
|
||||
unsaved_windows_len = len(unsaved_windows)
|
||||
if unsaved_windows_len == 0:
|
||||
do_quit()
|
||||
return
|
||||
|
||||
# Create Dialog
|
||||
window = windows[0]
|
||||
dialog = window._close_project_dialog_new()
|
||||
|
||||
if unsaved_windows_len > 1 or unsaved_windows[0].project.filename is None:
|
||||
# Add checkbox for each unsaved project
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||
box.append(Gtk.Label(label=_("Select which files:"), halign=Gtk.Align.START))
|
||||
|
||||
home = GLib.get_home_dir()
|
||||
untitled = 0
|
||||
|
||||
for win in unsaved_windows:
|
||||
if win.project.filename is None:
|
||||
untitled += 1
|
||||
|
||||
# Find Unique name
|
||||
while os.path.exists(f"Untitled {untitled}.cmb"):
|
||||
untitled += 1
|
||||
|
||||
check = Gtk.CheckButton(active=True, margin_start=8, can_focus=False)
|
||||
entry = Gtk.Entry(text=f"Untitled {untitled}")
|
||||
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
hbox.append(check)
|
||||
hbox.append(entry)
|
||||
|
||||
box.append(hbox)
|
||||
else:
|
||||
path = win.project.filename.replace(home, "~")
|
||||
check = Gtk.CheckButton(label=path, active=True, margin_start=8, can_focus=False)
|
||||
box.append(check)
|
||||
|
||||
windows2save.append((win, check, entry))
|
||||
|
||||
box.show()
|
||||
dialog.props.message_area.append(box)
|
||||
else:
|
||||
windows2save.append((unsaved_windows[0], None, None))
|
||||
|
||||
def callback(dialog, response, window):
|
||||
dialog.destroy()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
for win, check, entry in windows2save:
|
||||
if entry is not None:
|
||||
win.project.filename = entry.props.text
|
||||
if check is None or check.props.active:
|
||||
win.save_project()
|
||||
elif response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
do_quit()
|
||||
|
||||
dialog.connect("response", callback, window)
|
||||
dialog.present()
|
||||
|
||||
def __get_windows(self):
|
||||
retval = []
|
||||
|
||||
for win in self.get_windows():
|
||||
if win.props.application is not None:
|
||||
retval.append(win)
|
||||
|
||||
return retval
|
||||
|
||||
def __on_window_close_request(self, window):
|
||||
self.check_can_quit(window)
|
||||
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"]:
|
||||
content_type, uncertain = Gio.content_type_guess(path, None)
|
||||
if uncertain:
|
||||
with open(path, 'rb') as fd:
|
||||
data = fd.read(1024)
|
||||
content_type, uncertain = Gio.content_type_guess(path, data)
|
||||
|
||||
if content_type == 'application/x-cambalache-project':
|
||||
self.open(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)
|
||||
Gtk.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"))
|
||||
for action in ['quit']:
|
||||
gaction= Gio.SimpleAction.new(action, 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)
|
||||
provider.load_from_resource('/ar/xjuan/Cambalache/app/cambalache.css')
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
def do_activate(self):
|
||||
if self.props.active_window is None:
|
||||
self.open_project(None)
|
||||
self.open(None)
|
||||
|
||||
def do_window_removed(self, window):
|
||||
windows = self.__get_windows()
|
||||
def __on_open_project(self, window, filename, target_tk, uiname):
|
||||
if window.project is None:
|
||||
window.open_project(filename, target_tk, uiname)
|
||||
else:
|
||||
self.open(filename, target_tk, uiname)
|
||||
|
||||
if len(windows) == 0:
|
||||
self.activate_action("quit")
|
||||
def _on_quit_activate(self, action, data):
|
||||
self.quit()
|
||||
|
||||
def do_handle_local_options(self, options):
|
||||
if options.contains("version"):
|
||||
print(config.VERSION)
|
||||
if options.contains('version'):
|
||||
print(VERSION)
|
||||
return 0
|
||||
|
||||
if options.contains("export-all"):
|
||||
print("Export has been deprecated and does nothing. Every UI file is updated on project save.")
|
||||
if options.contains('export-all'):
|
||||
filename = options.lookup_value('export-all')
|
||||
filename = ''.join([ chr(c) for c in filename.unpack()])
|
||||
project = CmbProject(filename=filename)
|
||||
project.export()
|
||||
return 0
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
app = CmbApplication()
|
||||
app.run(sys.argv)
|
||||
|
@ -1,44 +0,0 @@
|
||||
#
|
||||
# CmbScrolledWindow
|
||||
#
|
||||
# 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 Gtk
|
||||
|
||||
|
||||
class CmbScrolledWindow(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = "CmbScrolledWindow"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Do not let children get scroll events!
|
||||
sroll = Gtk.EventControllerScroll(
|
||||
flags=Gtk.EventControllerScrollFlags.VERTICAL, propagation_phase=Gtk.PropagationPhase.CAPTURE
|
||||
)
|
||||
sroll.connect("scroll", self.handle_scroll_capture)
|
||||
self.add_controller(sroll)
|
||||
|
||||
def handle_scroll_capture(self, ec, dx, dy):
|
||||
self.props.vadjustment.props.value += self.props.vadjustment.props.step_increment * dy
|
||||
return True
|
@ -1,70 +1,51 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.9.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_shortcuts.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gtk+" version="3.0"/>
|
||||
<object class="GtkShortcutsWindow" id="shortcuts">
|
||||
<property name="section-name">shortcuts</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsSection">
|
||||
<property name="section-name">shortcuts</property>
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Project</property>
|
||||
<property name="view">shortcuts</property>
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>n</property>
|
||||
<property name="accelerator"><Control>n</property>
|
||||
<property name="title" translatable="yes">Create new project</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>o</property>
|
||||
<property name="accelerator"><Control>o</property>
|
||||
<property name="title" translatable="yes">Open a project</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<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="accelerator"><Control>w</property>
|
||||
<property name="title" translatable="yes">Close the project</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">General</property>
|
||||
<property name="view">general</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>question</property>
|
||||
<property name="title" translatable="yes">Keyboard Shortcuts</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>q</property>
|
||||
<property name="title" translatable="yes">Quit application</property>
|
||||
<property name="accelerator"><Control>s</property>
|
||||
<property name="title" translatable="yes">Save the project</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><Control>e</property>
|
||||
<property name="title">Save and Export</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@ -73,34 +54,33 @@
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Workspace</property>
|
||||
<property name="view">shortcuts</property>
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<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="accelerator"><Control>Insert</property>
|
||||
<property name="title" translatable="yes">Add slot/column</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>Delete</property>
|
||||
<property name="accelerator"><Control>Delete</property>
|
||||
<property name="title" translatable="yes">Remove slot/column</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary><shift>Insert</property>
|
||||
<property name="accelerator"><Control><shift>Insert</property>
|
||||
<property name="title" translatable="yes">Add row</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary><shift>Delete</property>
|
||||
<property name="accelerator"><Control><shift>Delete</property>
|
||||
<property name="title" translatable="yes">Remove row</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache Tutor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,15 +20,15 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
# Based on glade-intro.c (C) 2017-2018 Juan Pablo Ugarte
|
||||
#
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, GLib, Gdk, Gtk
|
||||
from enum import Enum
|
||||
from collections import namedtuple
|
||||
from cambalache import utils
|
||||
|
||||
|
||||
class CmbTutorState(Enum):
|
||||
@ -44,16 +44,17 @@ class CmbTutorPosition(Enum):
|
||||
CENTER = 4
|
||||
|
||||
|
||||
ScriptNode = namedtuple("ScriptNode", "widget text delay name position")
|
||||
ScriptNode = namedtuple('ScriptNode', 'widget text delay name position')
|
||||
|
||||
|
||||
class CmbTutor(GObject.GObject):
|
||||
__gsignals__ = {
|
||||
"show-node": (GObject.SignalFlags.RUN_LAST, None, (str, Gtk.Widget)),
|
||||
"hide-node": (GObject.SignalFlags.RUN_LAST, None, (str, Gtk.Widget)),
|
||||
'show-node': (GObject.SIGNAL_RUN_LAST, None, (str, Gtk.Widget)),
|
||||
'hide-node': (GObject.SIGNAL_RUN_LAST, None, (str, Gtk.Widget)),
|
||||
}
|
||||
|
||||
window = GObject.Property(type=Gtk.Window, flags=GObject.ParamFlags.READWRITE)
|
||||
window = GObject.Property(type=Gtk.Window, flags = GObject.ParamFlags.READWRITE)
|
||||
state = GObject.Property(type=int, flags = GObject.ParamFlags.READABLE)
|
||||
|
||||
def __init__(self, script, **kwargs):
|
||||
# List of ScriptNode
|
||||
@ -75,7 +76,7 @@ class CmbTutor(GObject.GObject):
|
||||
for node in script:
|
||||
self.__add(*node)
|
||||
|
||||
@GObject.Property(type=int, flags=GObject.ParamFlags.READABLE)
|
||||
@GObject.Property()
|
||||
def state(self):
|
||||
if self.timeout_id:
|
||||
return CmbTutorState.PLAYING
|
||||
@ -85,30 +86,34 @@ class CmbTutor(GObject.GObject):
|
||||
return CmbTutorState.NULL
|
||||
|
||||
def __add(self, text, widget_name, delay, name=None, position=CmbTutorPosition.BOTTOM):
|
||||
def find_by_css_name_or_buildable_id(widget, name):
|
||||
retval = None
|
||||
css_name = widget.get_name()
|
||||
retval = {}
|
||||
|
||||
# Get css name first
|
||||
if css_name and css_name != GObject.type_name(widget) and css_name == name:
|
||||
return widget
|
||||
def find_widget(w, data):
|
||||
if data.get('widget', None):
|
||||
return
|
||||
|
||||
# then GtkBuildable name
|
||||
if isinstance(widget, Gtk.Buildable) and Gtk.Buildable.get_buildable_id(widget) == name:
|
||||
return widget
|
||||
name = None
|
||||
n = w.get_name()
|
||||
|
||||
# or ModelButton name
|
||||
if GObject.type_name(widget) == "GtkModelButton" and widget.props.text == name:
|
||||
return widget
|
||||
# Get css name first then GtkBuildable name
|
||||
if n and n != GObject.type_name(w):
|
||||
name = n
|
||||
elif isinstance(w, Gtk.Buildable):
|
||||
n = Gtk.Buildable.get_name(w)
|
||||
if n and not n.startswith('___object'):
|
||||
name = n
|
||||
|
||||
for child in utils.widget_get_children(widget):
|
||||
retval = find_by_css_name_or_buildable_id(child, name)
|
||||
if retval:
|
||||
return retval
|
||||
# Return widget
|
||||
if name == widget_name:
|
||||
data['widget'] = w
|
||||
return
|
||||
|
||||
return retval
|
||||
if isinstance(w, Gtk.Container):
|
||||
w.forall(find_widget, data)
|
||||
|
||||
widget = find_by_css_name_or_buildable_id(self.window, widget_name)
|
||||
self.window.forall(find_widget, retval)
|
||||
|
||||
widget = retval.get('widget', None)
|
||||
|
||||
if widget:
|
||||
self.script.append(ScriptNode(widget, text, delay, name, position))
|
||||
@ -118,11 +123,11 @@ class CmbTutor(GObject.GObject):
|
||||
return
|
||||
|
||||
if self.current is None:
|
||||
self.current = 0
|
||||
self.current = 0
|
||||
|
||||
self.__script_play()
|
||||
|
||||
self.notify("state")
|
||||
self.notify('state')
|
||||
|
||||
def pause(self):
|
||||
if self.timeout_id:
|
||||
@ -130,32 +135,37 @@ class CmbTutor(GObject.GObject):
|
||||
|
||||
self.timeout_id = None
|
||||
self.__hide_node(self.current)
|
||||
self.notify("state")
|
||||
self.notify('state')
|
||||
|
||||
def stop(self):
|
||||
self.pause()
|
||||
self.current = None
|
||||
self.notify("state")
|
||||
self.notify('state')
|
||||
|
||||
def __popover_new(self, text):
|
||||
popover = Gtk.Popover(autohide=False)
|
||||
popover.add_css_class("cmb-tutor")
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, hexpand=True)
|
||||
popover = Gtk.Popover(modal=False)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=6)
|
||||
|
||||
box.append(Gtk.Image(icon_name="dialog-information-symbolic"))
|
||||
box.append(Gtk.Label(label=text, vexpand=False, hexpand=True, wrap=True, max_width_chars=24))
|
||||
popover.set_child(box)
|
||||
box.add(Gtk.Image(icon_name="dialog-information-symbolic",
|
||||
icon_size=Gtk.IconSize.DIALOG))
|
||||
box.add(Gtk.Label(label=text,
|
||||
wrap=True,
|
||||
max_width_chars=28))
|
||||
popover.add(box)
|
||||
popover.get_style_context().add_class("cmb-tutor")
|
||||
box.show_all()
|
||||
|
||||
return popover
|
||||
|
||||
def __script_transition(self):
|
||||
self.timeout_id = GLib.timeout_add(250, self.__script_play)
|
||||
self.timeout_id = GLib.timeout_add (250, self.__script_play)
|
||||
|
||||
self.__hide_current_node()
|
||||
|
||||
# Set next node
|
||||
if self.current is not None:
|
||||
self.current = self.current + 1 if self.current < (len(self.script) - 1) else None
|
||||
self.current = self.current + 1 if self.current < (len(self.script) - 1) else None;
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
@ -180,12 +190,12 @@ class CmbTutor(GObject.GObject):
|
||||
|
||||
if self.current is not None:
|
||||
node = self.script[self.current]
|
||||
self.emit("hide-node", node.name, node.widget)
|
||||
self.emit('hide-node', node.name, node.widget)
|
||||
|
||||
self.hiding_node = False
|
||||
|
||||
def __script_play(self):
|
||||
self.timeout_id = None
|
||||
self.timeout_id = None;
|
||||
|
||||
if self.current is None:
|
||||
return GLib.SOURCE_REMOVE
|
||||
@ -200,11 +210,11 @@ class CmbTutor(GObject.GObject):
|
||||
if parent:
|
||||
parent.popup()
|
||||
|
||||
node.widget.add_css_class("cmb-tutor-highlight")
|
||||
node.widget.get_style_context().add_class("cmb-tutor-highlight")
|
||||
|
||||
# Create popover
|
||||
self.popover = self.__popover_new(node.text)
|
||||
self.popover.set_parent(node.widget)
|
||||
self.popover.set_relative_to(node.widget)
|
||||
|
||||
if node.position == CmbTutorPosition.BOTTOM:
|
||||
self.popover.set_position(Gtk.PositionType.BOTTOM)
|
||||
@ -213,18 +223,19 @@ class CmbTutor(GObject.GObject):
|
||||
elif node.position == CmbTutorPosition.RIGHT:
|
||||
self.popover.set_position(Gtk.PositionType.RIGHT)
|
||||
elif node.position == CmbTutorPosition.CENTER:
|
||||
rect = Gdk.Rectangle()
|
||||
rect = Gdk.Rectangle();
|
||||
rect.x = node.widget.get_allocated_width() / 2
|
||||
rect.y = node.widget.get_allocated_height() / 2
|
||||
self.popover.set_pointing_to(rect)
|
||||
self.popover.set_position(Gtk.PositionType.TOP)
|
||||
self.popover.set_pointing_to(rect);
|
||||
self.popover.set_position(Gtk.PositionType.TOP);
|
||||
|
||||
self.emit("show-node", node.name, node.widget)
|
||||
self.emit('show-node', node.name, node.widget);
|
||||
|
||||
if self.popover:
|
||||
self.popover.set_sensitive(True)
|
||||
self.popover.popup()
|
||||
|
||||
self.timeout_id = GLib.timeout_add(node.delay * 1000, self.__script_transition)
|
||||
self.timeout_id = GLib.timeout_add(node.delay * 1000, self.__script_transition);
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbTutorial
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,71 +20,56 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from .cmb_tutor import CmbTutorPosition
|
||||
from cambalache import _
|
||||
|
||||
intro = [
|
||||
(_("Hi, I will show you around Cambalache"), "intro_button", 5),
|
||||
(_("You can open a project and find recently used"), "open_button", 5),
|
||||
(_("Common actions like Undo"), "undo_button", 4),
|
||||
(_("Redo"), "redo_button", 2),
|
||||
(_("and Add new UI are directly accessible in the headerbar"), "add_button", 3),
|
||||
(_("together with the main menu"), "menu_button", 3),
|
||||
(
|
||||
_("Where you can create a new project"),
|
||||
_("New Project"),
|
||||
5,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(
|
||||
_("Import UI files"),
|
||||
_("Import"),
|
||||
3,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(_("Create a project to continue"), "intro_button", 2, "add-project"),
|
||||
(_("Great!"), "intro_button", 2),
|
||||
(
|
||||
_("This is the project workspace, where you can see and select the widgets to edit"),
|
||||
"view",
|
||||
6,
|
||||
None,
|
||||
CmbTutorPosition.CENTER,
|
||||
),
|
||||
(_("Project tree, with multiple UI support"), "tree_view", 4, None, CmbTutorPosition.CENTER),
|
||||
(
|
||||
_("Class selector bar"),
|
||||
"type_chooser_box",
|
||||
3,
|
||||
),
|
||||
(_("And the object editor"), "editor_stack", 3, None, CmbTutorPosition.CENTER),
|
||||
(_("You can search all supported classes here"), "type_chooser_all", 4, "show-type-popover", CmbTutorPosition.LEFT),
|
||||
(_("or investigate what is in each group"), "type_chooser_gtk", 4, "show-type-popover-gtk", CmbTutorPosition.LEFT),
|
||||
(_("Now let's add a new UI file"), "add_button", 5, "add-ui"),
|
||||
(_("Good, now try to create a window"), "intro_button", 4, "add-window"),
|
||||
(_("Excellent!"), "intro_button", 2),
|
||||
(_("BTW, did you know you can double click on any placeholder to create widgets?"), "intro_button", 5),
|
||||
(_("Try adding a grid"), "intro_button", 3, "add-grid"),
|
||||
(_("and a button"), "intro_button", 3, "add-button"),
|
||||
(_("Quite easy! Isn't it?"), "intro_button", 3),
|
||||
(
|
||||
_("If you have any question, contact us on Matrix!"),
|
||||
_("Contact"),
|
||||
7,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(
|
||||
_("That is all for now.\nIf you find Cambalache useful please consider donating"),
|
||||
_("Donate"),
|
||||
7,
|
||||
"donate",
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(_("Have a nice day!"), "intro_button", 3, "intro-end"),
|
||||
(_('Hi, I will show you around Cambalache'),
|
||||
'intro_button', 5),
|
||||
|
||||
(_('You can open a project'), 'open_button', 3),
|
||||
(_('find recently used'), 'recent_button', 2,),
|
||||
(_('or create a new one'), 'new_button', 4),
|
||||
|
||||
(_('Common actions like Undo'), 'undo_button', 4),
|
||||
(_('Redo'), 'redo_button', 2),
|
||||
(_('Add new UI file'), 'add_button', 3),
|
||||
(_('and Save are directly accessible in the headerbar'), 'save_button', 6),
|
||||
(_('just like Save As'), 'save_as_button', 2),
|
||||
(_('and the main menu'), 'menu_button', 3),
|
||||
|
||||
(_('Create a project to continue'), 'intro_button', 2, 'add-project'),
|
||||
(_('Great!'), 'intro_button', 2),
|
||||
|
||||
(_('This is the project workspace, where you can see and select the widgets to edit'),
|
||||
'view', 6, None, CmbTutorPosition.CENTER),
|
||||
(_('Project tree, with multiple UI support'),
|
||||
'tree_view', 4, None, CmbTutorPosition.CENTER),
|
||||
(_('Class selector bar'), 'type_chooser_box', 3, ),
|
||||
(_('And the object editor'),
|
||||
'editor_stack', 3, None, CmbTutorPosition.CENTER),
|
||||
|
||||
(_('You can search all supported classes here'),
|
||||
'type_chooser_all', 4, 'show-type-popover', CmbTutorPosition.LEFT),
|
||||
(_('or investigate what is in each group'),
|
||||
'type_chooser_gtk', 4, 'show-type-popover-gtk', CmbTutorPosition.LEFT),
|
||||
|
||||
(_('Now let\'s add a new UI file'), 'add_button', 5, 'add-ui'),
|
||||
|
||||
(_('Good, now try to create a window'), 'intro_button', 4, 'add-window'),
|
||||
(_('Excelent!'), 'intro_button', 2),
|
||||
|
||||
(_('BTW, did you know you can double click on any placeholder to create widgets?'),
|
||||
'intro_button', 5),
|
||||
(_('Try adding a grid'), 'intro_button', 3, 'add-grid'),
|
||||
(_('and a button'), 'intro_button', 3, 'add-button'),
|
||||
|
||||
(_('Quite easy! Isn\'t it?'), 'intro_button', 3),
|
||||
|
||||
(_('Once you finish, you can export all UI files to xml here'),
|
||||
'export_all', 5, 'main-menu', CmbTutorPosition.LEFT),
|
||||
|
||||
(_('That is all for now.\nIf you find Cambalache usefull please consider donating'),
|
||||
'donate', 7, 'donate', CmbTutorPosition.LEFT),
|
||||
(_('Have a nice day!'), 'intro_button', 3, 'intro-end'),
|
||||
]
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
moduledir = join_paths(modulesdir, 'cambalache', 'app')
|
||||
moduledir = join_paths(get_option('prefix'), python_bin.get_install_dir(), 'cambalache', 'app')
|
||||
|
||||
gnome.compile_resources('app',
|
||||
'app.gresource.xml',
|
||||
@ -9,7 +9,7 @@ gnome.compile_resources('app',
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('VERSION', meson.project_version())
|
||||
conf.set('PYTHON', python_bin.full_path())
|
||||
conf.set('PYTHON', python_bin.path())
|
||||
conf.set('localedir', localedir)
|
||||
conf.set('pkgdatadir', pkgdatadir)
|
||||
|
||||
@ -24,7 +24,6 @@ configure_file(
|
||||
install_data([
|
||||
'__init__.py',
|
||||
'cmb_application.py',
|
||||
'cmb_scrolled_window.py',
|
||||
'cmb_window.py',
|
||||
'cmb_tutor.py',
|
||||
'cmb_tutorial.py',
|
||||
|
@ -1 +0,0 @@
|
||||
../../data/ar.xjuan.Cambalache.metainfo.xml.in
|
@ -1,290 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
|
||||
<gresources filename="cambalache.gresource.xml" sha256="fdcf4cd517493f548aa4b4fe206ff7762cee9cdda7ec5a85a718b46eb1c4731b"/>
|
||||
<gresources filename="app/app.gresource.xml" sha256="3684aa78fce08d8e81d0907317214aeb179c5aea091dd0df405476b43e286941"/>
|
||||
<css filename="cambalache.css" priority="400" is_global="1"/>
|
||||
<css filename="app/cambalache.css" is_global="0"/>
|
||||
<ui template-class="CmbChildTypeComboBox">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbChildTypeComboBox -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbChildTypeComboBox" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbColorEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbColorEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbColorEntry" parent="GtkBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbEnumComboBox">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbEnumComboBox -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbEnumComboBox" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFileEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbFileEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFileEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFlagsEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbFlagsEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFlagsEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbIconNameEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbIconNameEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbIconNameEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbObjectChooser">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbObjectChooser -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectChooser" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSourceView">
|
||||
<property id="lang" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSourceView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSourceView" parent="GtkTextView"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSpinButton">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSpinButton -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSpinButton" parent="GtkSpinButton"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSwitch">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSwitch -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSwitch" parent="GtkSwitch"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTextBuffer">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTextBuffer -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTextBuffer" parent="GtkTextBuffer"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTextView">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTextView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTextView" parent="GtkScrolledWindow"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbToplevelChooser">
|
||||
<property id="derivable-only" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbToplevelChooser -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbToplevelChooser" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTranslatablePopover">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTranslatablePopover -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTranslatablePopover" parent="GtkPopover"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTranslatableWidget" filename="control/cmb_translatable_widget.ui" sha256="b3178157210f93308b92d99f933cb8d04f2de0278ad75680c7460e2c520b1684"/>
|
||||
<ui template-class="CasildaCompositor">
|
||||
<signal id="context-menu"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbCompositor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CasildaCompositor" parent="GtkDrawingArea"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooserPopover">
|
||||
<property id="category" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="derived-type-id" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="parent-type-id" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="show-categories" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="uncategorized-only" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTypeChooserPopover -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTypeChooserPopover" parent="GtkPopover"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbAccessibleEditor">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbAccessibleEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbAccessibleEditor" parent="GtkGrid"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbUIRequiresEditor">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbUIRequiresEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbUIRequiresEditor" parent="GtkGrid"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbScrolledWindow">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbScrolledWindow -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbScrolledWindow" parent="GtkScrolledWindow"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbObjectEditor">
|
||||
<property id="lang" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="layout" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbObjectEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectEditor" parent="GtkBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbListView">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbListView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbListView" parent="GtkListView"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooserWidget" filename="cmb_type_chooser_widget.ui" sha256="15dedacc452656abdb9d245ec876370705c610422201213d944c1f6329c5c8f6"/>
|
||||
<ui template-class="CmbSignalEditor" filename="cmb_signal_editor.ui" sha256="df7ca002e39a3a8e16a7334e48d8b1a7847972b0a3f5213f71ba154a70194a1d"/>
|
||||
<ui template-class="CmbObjectDataEditor" filename="cmb_object_data_editor.ui" sha256="4a590fc58d66e781f731134214a9fdeefabd8b8c11edcc50f6530cac81f796d1"/>
|
||||
<ui template-class="CmbContextMenu" filename="cmb_context_menu.ui" sha256="81eba3adf715348a5c03ef4cbc151eebd5d9aa8b5a14c5968232f68a61ae573c"/>
|
||||
<ui template-class="CmbDBInspector" filename="cmb_db_inspector.ui" sha256="4451cdb08d24bd4a802ea692c0ebb4ef46af13152984c0b435d29bf4eb7dab55"/>
|
||||
<ui filename="app/cmb_shortcuts.ui" sha256="d7ac37fd2430788a9e210ed4bc84dcfeba5609bdcc801afb192bfd900c7a8883"/>
|
||||
<ui template-class="CmbFileButton" filename="control/cmb_file_button.ui" sha256="f859b4f85d7c80c1fef69b68ebb9129423d9c72fdb38d304132784f7361cbbfd">
|
||||
<property id="dialog-title" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="use-open" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
</ui>
|
||||
<ui template-class="CmbNotificationListView" filename="cmb_notification_list_view.ui" sha256="13622645038ef2aaa154f74cd300f9c0fa0dccf69d45d6c9376f9034e6ee57fb"/>
|
||||
<ui template-class="CmbVersionNotificationView" filename="cmb_version_notification_view.ui" sha256="9a3ced46b90eb7e425d1c345853c4e8e908870c61c75475f7e20ce3c9ee8cec6"/>
|
||||
<ui template-class="CmbMessageNotificationView" filename="cmb_message_notification_view.ui" sha256="debeffd184e225d82ed29ac590654b8160363e8d5606366dc8acb3ff9840fee3"/>
|
||||
<ui template-class="CmbPollNotificationView" filename="cmb_poll_notification_view.ui" sha256="8f47a1e503b85eb5ac3ac54962a40fc2237588e1216afba859a3016a1dcfc121"/>
|
||||
<ui template-class="CmbPollOptionCheck" filename="cmb_poll_option_check.ui" sha256="aa433f201dc1863f3727e1baa2c4cc239192a1ae4c53553de69d529ba2cc6fed"/>
|
||||
<ui template-class="CmbNotificationListRow" filename="cmb_notification_list_row.ui" sha256="5ef66fcc24e10d40a91ff0eada84f6aa8a595e368961364d98fde1800755edfc"/>
|
||||
<ui template-class="CmbPixbufEntry">
|
||||
<requires>CmbFileEntry</requires>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbPixbufEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<template class="CmbPixbufEntry" parent="CmbFileEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFragmentEditor" filename="cmb_fragment_editor.ui" sha256="0c2de873c689cd60558a835a39edaf4e1d5e68ef3afeb157628e13fd57282057">
|
||||
<requires>CmbSourceView</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooser" filename="cmb_type_chooser.ui" sha256="1b4be880ede6d6d6e88e452418c435640d91f5f61cad97b3f82f44429247e5e6">
|
||||
<requires>CmbTypeChooserPopover</requires>
|
||||
<signal id="chooser-popdown"/>
|
||||
<signal id="chooser-popup"/>
|
||||
<signal id="type-selected"/>
|
||||
</ui>
|
||||
<ui template-class="CmbView" filename="cmb_view.ui" sha256="6b9251d30d6e4751b8b04b2af923eec9a8f00d590a322d96bf080fb200b58424">
|
||||
<requires>CasildaCompositor</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
<requires>CmbDBInspector</requires>
|
||||
<signal id="placeholder-activated"/>
|
||||
<signal id="placeholder-selected"/>
|
||||
</ui>
|
||||
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="2050887ef1c45facb6ebff14500214bb035e6808ca61d2a7d661e696d79026ca">
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbEntry</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbCSSEditor" filename="cmb_css_editor.ui" sha256="6f39c9cb2f112dac9da415322792a717b8c0a80845dcbb2edacd7695388cba63">
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbUIEditor" filename="cmb_ui_editor.ui" sha256="70e272e2c6c499a5424c6019154cd8338d9edde1fa111ad592a1c104019bb7ee">
|
||||
<requires>CmbTextBuffer</requires>
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbEntry</requires>
|
||||
<requires>CmbToplevelChooser</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="df07e3e03b88f9b097ad7d65efa15923d339db83b9d4a7c015a312a71f8c9685">
|
||||
<requires>CmbNotificationListView</requires>
|
||||
<requires>CmbScrolledWindow</requires>
|
||||
<requires>CmbObjectEditor</requires>
|
||||
<requires>CmbSignalEditor</requires>
|
||||
<requires>CmbAccessibleEditor</requires>
|
||||
<requires>CmbFragmentEditor</requires>
|
||||
<requires>CmbUIEditor</requires>
|
||||
<requires>CmbUIRequiresEditor</requires>
|
||||
<requires>CmbCSSEditor</requires>
|
||||
<requires>CmbGResourceEditor</requires>
|
||||
<requires>CmbTypeChooser</requires>
|
||||
<requires>CmbView</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
<requires>CmbListView</requires>
|
||||
<css-provider>app/cambalache.css</css-provider>
|
||||
</ui>
|
||||
</cambalache-project>
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* cambalache.css
|
||||
*
|
||||
* Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
* Copyright (C) 2021 Juan Pablo Ugarte
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -25,107 +25,11 @@ CmbView {
|
||||
background-color: @theme_base_color;
|
||||
}
|
||||
|
||||
popover.cmb-icon-chooser iconview:not(:selected) {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
button.hidden,
|
||||
CmbPropertyLabel.hidden > box > image {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in-out 0;
|
||||
}
|
||||
|
||||
button.hidden:hover,
|
||||
CmbPropertyLabel.hidden:hover > box > image {
|
||||
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 {
|
||||
min-width:unset;
|
||||
min-height: unset;
|
||||
padding: unset;
|
||||
margin: unset;
|
||||
border: unset;
|
||||
background: unset;
|
||||
box-shadow: 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 {
|
||||
CmbObjectEditor label.modified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
CmbPropertyLabel.warning > box > label {
|
||||
text-decoration: underline wavy @warning_color;
|
||||
}
|
||||
|
||||
listview.cmb-list-view {
|
||||
background-color: @theme_bg_color;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row {
|
||||
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;
|
||||
popover.cmb-icon-chooser iconview:not(:selected) {
|
||||
background-color: unset;
|
||||
}
|
||||
|
@ -1,31 +1,16 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/ar/xjuan/Cambalache">
|
||||
<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_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_widget.ui</file>
|
||||
<file>cmb_ui_editor.ui</file>
|
||||
<file>cmb_view.ui</file>
|
||||
<file>control/cmb_file_button.ui</file>
|
||||
<file>control/cmb_translatable_widget.ui</file>
|
||||
<file>db/cmb_base.sql</file>
|
||||
<file>db/cmb_history.sql</file>
|
||||
<file>db/cmb_project.sql</file>
|
||||
<file>icons/scalable/actions/binded-symbolic.svg</file>
|
||||
<file>icons/scalable/actions/bind-symbolic.svg</file>
|
||||
<file>cmb_notification_list_row.ui</file>
|
||||
<file>cmb_signal_editor.ui</file>
|
||||
<file>cmb_translatable_widget.ui</file>
|
||||
<file>cambalache.css</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
@ -1,236 +0,0 @@
|
||||
#
|
||||
# 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")
|
@ -20,15 +20,13 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class CmbBase(GObject.GObject):
|
||||
project = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
display_name = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
project = GObject.Property(type=GObject.GObject, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@ -40,4 +38,4 @@ class CmbBase(GObject.GObject):
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def db_set(self, query, pk, value):
|
||||
self.project.db.execute(query, (value,) + pk)
|
||||
self.project.db.execute(query, (value, ) + pk)
|
||||
|
@ -1,86 +0,0 @@
|
||||
#
|
||||
# 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,7 +1,7 @@
|
||||
#
|
||||
# CmbContextMenu - Cambalache UI Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,89 +20,46 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject, GLib, Gio, Gdk, Gtk
|
||||
from cambalache import _
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, GLib, Gdk, Gtk
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_context_menu.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_context_menu.ui')
|
||||
class CmbContextMenu(Gtk.PopoverMenu):
|
||||
__gtype_name__ = "CmbContextMenu"
|
||||
__gtype_name__ = 'CmbContextMenu'
|
||||
|
||||
enable_theme = GObject.Property(
|
||||
type=bool,
|
||||
default=False,
|
||||
flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
target_tk = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
gtk_theme = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
target_tk = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
main_section = Gtk.Template.Child()
|
||||
add_submenu = Gtk.Template.Child()
|
||||
main_box = Gtk.Template.Child()
|
||||
separator = Gtk.Template.Child()
|
||||
css_theme = Gtk.Template.Child()
|
||||
css_theme_box = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.theme_submenu = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify::target-tk", self.__on_target_tk_notify)
|
||||
self.connect('notify::target-tk', lambda o, p: self.__populate_css_theme_box())
|
||||
|
||||
def __on_target_tk_notify(self, obj, pspec):
|
||||
self.__populate_css_theme_box()
|
||||
self.__update_add_submenu()
|
||||
|
||||
def __update_add_submenu(self):
|
||||
if self.target_tk not in ["gtk-4.0", "gtk+-3.0"]:
|
||||
return
|
||||
|
||||
types = [
|
||||
"GtkBox",
|
||||
"GtkGrid",
|
||||
"GtkExpander",
|
||||
"GtkRevealer",
|
||||
"GtkOverlay",
|
||||
]
|
||||
|
||||
if self.target_tk == "gtk+-3.0":
|
||||
types += [
|
||||
"GtkAligment",
|
||||
"GtkEventBox"
|
||||
]
|
||||
else:
|
||||
types += [
|
||||
"GtkGraphicsOffload",
|
||||
]
|
||||
|
||||
self.add_submenu.remove_all()
|
||||
|
||||
for gtype in sorted(types):
|
||||
item = Gio.MenuItem()
|
||||
item.set_label(gtype)
|
||||
item.set_action_and_target_value("win.add_parent", GLib.Variant("s", gtype))
|
||||
self.add_submenu.append_item(item)
|
||||
def __on_css_theme_button_toggled(self, button, data):
|
||||
if button.props.active:
|
||||
self.gtk_theme = data
|
||||
|
||||
def __populate_css_theme_box(self):
|
||||
gtk_path = "gtk-3.0"
|
||||
gtk_path = 'gtk-3.0'
|
||||
|
||||
if not self.enable_theme or self.target_tk not in ["gtk-4.0", "gtk+-3.0"]:
|
||||
if self.target_tk in [None, '']:
|
||||
return
|
||||
|
||||
if self.target_tk == "gtk-4.0":
|
||||
gtk_path = "gtk-4.0"
|
||||
# FIXME: whats the real default theme for gtk4?
|
||||
themes = ["Default"]
|
||||
else:
|
||||
themes = ["Adwaita", "HighContrast", "HighContrastInverse"]
|
||||
if self.target_tk == 'gtk-4.0':
|
||||
gtk_path = 'gtk-4.0'
|
||||
|
||||
if self.theme_submenu is None:
|
||||
self.theme_submenu = Gio.Menu()
|
||||
self.main_section.prepend_submenu(_("CSS theme"), self.theme_submenu)
|
||||
|
||||
# Remove all items from theme submenu
|
||||
self.theme_submenu.remove_all()
|
||||
for child in self.css_theme_box.get_children():
|
||||
self.css_theme_box.remove(child)
|
||||
|
||||
dirs = []
|
||||
|
||||
@ -110,32 +67,50 @@ class CmbContextMenu(Gtk.PopoverMenu):
|
||||
dirs.append(GLib.get_user_data_dir())
|
||||
|
||||
# Add /themes to every dir
|
||||
dirs = list(map(lambda d: os.path.join(d, "themes"), dirs))
|
||||
dirs = list(map(lambda d: os.path.join(d, 'themes'), dirs))
|
||||
|
||||
# Append ~/.themes
|
||||
dirs.append(os.path.join(GLib.get_home_dir(), ".themes"))
|
||||
dirs.append(os.path.join(GLib.get_home_dir(), '.themes'))
|
||||
|
||||
# Default themes
|
||||
themes = ['Adwaita', 'HighContrast', 'HighContrastInverse']
|
||||
|
||||
for path in dirs:
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
for theme in os.listdir(path):
|
||||
tpath = os.path.join(path, theme, gtk_path, "gtk.css")
|
||||
tpath = os.path.join(path, theme, gtk_path, 'gtk.css')
|
||||
if os.path.exists(tpath):
|
||||
themes.append(theme)
|
||||
|
||||
# Dedup and sort
|
||||
themes = list(dict.fromkeys(themes))
|
||||
|
||||
# Add back item
|
||||
button = Gtk.ModelButton(text=_('CSS themes'),
|
||||
menu_name='main',
|
||||
inverted=True,
|
||||
centered=True,
|
||||
visible=True)
|
||||
self.css_theme_box.add(button)
|
||||
|
||||
group = None
|
||||
for theme in sorted(themes):
|
||||
item = Gio.MenuItem()
|
||||
item.set_label(theme)
|
||||
item.set_action_and_target_value("win.workspace_theme", GLib.Variant("s", theme))
|
||||
self.theme_submenu.append_item(item)
|
||||
button = Gtk.RadioButton(label=theme,
|
||||
group=group,
|
||||
active=self.gtk_theme == theme,
|
||||
visible=True)
|
||||
if group is None:
|
||||
group = button
|
||||
|
||||
button.connect('toggled', self.__on_css_theme_button_toggled, theme)
|
||||
self.css_theme_box.add(button)
|
||||
|
||||
self.separator.props.visible = self.css_theme.props.visible = len(themes) > 0
|
||||
|
||||
def popup_at(self, x, y):
|
||||
r = Gdk.Rectangle()
|
||||
r.x, r.y = (x, y)
|
||||
r.width = r.height = 0
|
||||
r.x, r.y, r.width, r.height = (x, y, 10, 10)
|
||||
self.set_pointing_to(r)
|
||||
self.popup()
|
||||
|
@ -1,57 +1,182 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_context_menu.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<menu id="menu_model">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.cut</attribute>
|
||||
<attribute name="label" translatable="yes">Cut</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.copy</attribute>
|
||||
<attribute name="label" translatable="yes">Copy</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.paste</attribute>
|
||||
<attribute name="label" translatable="yes">Paste</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.delete</attribute>
|
||||
<attribute name="label" translatable="yes">Delete</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.add_object</attribute>
|
||||
<attribute name="label" translatable="yes">Add object here</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.add_object_toplevel</attribute>
|
||||
<attribute name="label" translatable="yes">Add object as toplevel</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.remove_parent</attribute>
|
||||
<attribute name="label">Remove parent</attribute>
|
||||
</item>
|
||||
<submenu id="add_submenu">
|
||||
<attribute name="label">Add parent</attribute>
|
||||
</submenu>
|
||||
<item>
|
||||
<attribute name="action">win.clear</attribute>
|
||||
<attribute name="label" translatable="yes">Clear Properties</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.documentation</attribute>
|
||||
<attribute name="label" translatable="yes">Read Documentation</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="main_section"/>
|
||||
</menu>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<template class="CmbContextMenu" parent="GtkPopoverMenu">
|
||||
<property name="menu-model">menu_model</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.cut</property>
|
||||
<property name="text" translatable="yes">Cut</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.copy</property>
|
||||
<property name="text" translatable="yes">Copy</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.paste</property>
|
||||
<property name="text" translatable="yes">Paste</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.delete</property>
|
||||
<property name="text" translatable="yes">Delete</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.add_object</property>
|
||||
<property name="text" translatable="yes">Add object here</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.add_object_toplevel</property>
|
||||
<property name="text" translatable="yes">Add object as toplevel</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.clear</property>
|
||||
<property name="text" translatable="yes">Clear Properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="action-name">win.documentation</property>
|
||||
<property name="text" translatable="yes">Read Documentation</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="separator">
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="css_theme">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="text" translatable="yes">CSS theme</property>
|
||||
<property name="menu-name">css-theme</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="submenu">main</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="css_theme_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="submenu">css-theme</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -1,161 +0,0 @@
|
||||
#
|
||||
# Cambalache CSS wrapper
|
||||
#
|
||||
# Copyright (C) 2022 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 gi.repository import GObject, Gio
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_objects_base import CmbBaseCSS
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbCSS(CmbBaseCSS):
|
||||
__gsignals__ = {
|
||||
"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)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._path = None
|
||||
self._monitor = None
|
||||
self.__saving = False
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
self.load_css()
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
if pspec.name not in ["css"]:
|
||||
self.project._css_changed(self, pspec.name)
|
||||
|
||||
if pspec.name == "filename":
|
||||
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)
|
||||
def display_name(self):
|
||||
return CmbCSS.get_display_name(self.css_id, self.filename)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def priority(self):
|
||||
retval = self.db_get("SELECT priority FROM css WHERE css_id=?;", (self.css_id,))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@priority.setter
|
||||
def _set_priority(self, value):
|
||||
self.db_set("UPDATE css SET priority=? WHERE css_id=?;", (self.css_id,), value if value != 0 else None)
|
||||
|
||||
@GObject.Property(type=object)
|
||||
def provider_for(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
retval = []
|
||||
for row in c.execute("SELECT ui_id FROM css_ui WHERE css_id=? ORDER BY ui_id;", (self.css_id,)):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __on_css_file_changed(self, file_monitor, file, other_file, event_type):
|
||||
if event_type != Gio.FileMonitorEvent.CHANGES_DONE_HINT:
|
||||
return
|
||||
|
||||
if self.__saving:
|
||||
self.__saving = False
|
||||
return
|
||||
else:
|
||||
self.emit("file-changed")
|
||||
|
||||
def load_css(self):
|
||||
if not self.project or not self.filename:
|
||||
return False
|
||||
|
||||
dirname = os.path.dirname(self.project.filename)
|
||||
path = os.path.join(dirname, self.filename)
|
||||
|
||||
if os.path.exists(path):
|
||||
self._path = path
|
||||
with open(path) as fd:
|
||||
self.css = fd.read()
|
||||
fd.close()
|
||||
|
||||
if self._monitor:
|
||||
self._monitor.cancel()
|
||||
|
||||
gfile = Gio.File.new_for_path(path)
|
||||
self._monitor = gfile.monitor(Gio.FileMonitorFlags.NONE, None)
|
||||
self._monitor.connect("changed", self.__on_css_file_changed)
|
||||
|
||||
return True
|
||||
else:
|
||||
self._path = None
|
||||
|
||||
return False
|
||||
|
||||
def save_css(self):
|
||||
if not self.project or not self.filename:
|
||||
return
|
||||
|
||||
needs_load = False
|
||||
|
||||
if self._path is None:
|
||||
dirname = os.path.dirname(self.project.filename)
|
||||
self._path = os.path.join(dirname, self.filename)
|
||||
needs_load = True
|
||||
|
||||
self.__saving = True
|
||||
with open(self._path, "w") as fd:
|
||||
fd.write(self.css)
|
||||
|
||||
if needs_load:
|
||||
self.notify("filename")
|
||||
|
||||
def add_ui(self, ui):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get("SELECT count(css_id) FROM css_ui WHERE css_id=? AND ui_id=?;", (self.css_id, ui.ui_id))
|
||||
|
||||
if count == 0:
|
||||
c.execute("INSERT INTO css_ui (css_id, ui_id) VALUES (?, ?);", (self.css_id, ui.ui_id))
|
||||
|
||||
c.close()
|
||||
|
||||
self.notify("provider_for")
|
||||
|
||||
def remove_ui(self, ui):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
c.execute("DELETE FROM css_ui WHERE css_id=? AND ui_id=?;", (self.css_id, ui.ui_id))
|
||||
c.close()
|
||||
|
||||
self.notify("provider_for")
|
@ -1,200 +0,0 @@
|
||||
#
|
||||
# CmbCSSEditor - Cambalache CSS Editor
|
||||
#
|
||||
# Copyright (C) 2022-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 utils, _
|
||||
from .cmb_css import CmbCSS
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_css_editor.ui")
|
||||
class CmbCSSEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbCSSEditor"
|
||||
|
||||
filename = Gtk.Template.Child()
|
||||
priority = Gtk.Template.Child()
|
||||
is_global = Gtk.Template.Child()
|
||||
|
||||
ui_menu_button = Gtk.Template.Child()
|
||||
ui_box = Gtk.Template.Child()
|
||||
infobar = Gtk.Template.Child()
|
||||
save_button = Gtk.Template.Child()
|
||||
view = Gtk.Template.Child()
|
||||
|
||||
fields = [("filename", "cmb-value"), ("priority", "value"), ("is_global", "active")]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self._bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
self.priority.set_range(0, 10000)
|
||||
self.priority.set_increments(10, 100)
|
||||
|
||||
@GObject.Property(type=CmbCSS)
|
||||
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()
|
||||
|
||||
if self._object:
|
||||
self._object.project.disconnect_by_func(self.__on_ui_added_removed)
|
||||
self._object.disconnect_by_func(self.__on_provider_for_notify)
|
||||
self._object.disconnect_by_func(self.__on_css_notify)
|
||||
self._object.disconnect_by_func(self.__on_file_changed)
|
||||
|
||||
self._bindings = []
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj is None:
|
||||
self.set_sensitive(False)
|
||||
return
|
||||
|
||||
self.filename.dirname = obj.project.dirname
|
||||
self.set_sensitive(True)
|
||||
|
||||
for field, target in self.fields:
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
field,
|
||||
getattr(self, field),
|
||||
target,
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
binding = GObject.Object.bind_property(
|
||||
obj, "css", self.view, "text", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
obj.project.connect("ui-added", self.__on_ui_added_removed)
|
||||
obj.project.connect("ui-removed", self.__on_ui_added_removed)
|
||||
obj.connect("notify::provider-for", self.__on_provider_for_notify)
|
||||
obj.connect("notify::css", self.__on_css_notify)
|
||||
obj.connect("file-changed", self.__on_file_changed)
|
||||
|
||||
self.__update_provider_for()
|
||||
self.__update_ui_button_label()
|
||||
|
||||
@Gtk.Template.Callback("on_save_button_clicked")
|
||||
def __on_save_button_clicked(self, button):
|
||||
self._object.save_css()
|
||||
self.infobar.set_revealed(False)
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
@Gtk.Template.Callback("on_infobar_response")
|
||||
def __on_infobar_response(self, infobar, response_id):
|
||||
if response_id == Gtk.ResponseType.OK:
|
||||
self.__load_filename()
|
||||
|
||||
self.infobar.set_revealed(False)
|
||||
|
||||
def __update_provider_for(self):
|
||||
# Remove all css_ui check buttons
|
||||
for child in utils.widget_get_children(self.ui_box):
|
||||
self.ui_box.remove(child)
|
||||
|
||||
if self._object is None:
|
||||
return
|
||||
|
||||
ui_list = self._object.project.get_ui_list()
|
||||
provider_for = self._object.provider_for
|
||||
|
||||
# Generate a check button for each UI
|
||||
for ui in ui_list:
|
||||
check = Gtk.CheckButton(
|
||||
label=ui.display_name, active=ui.ui_id in provider_for, halign=Gtk.Align.START, visible=True
|
||||
)
|
||||
check.connect("toggled", self.__on_check_button_toggled, ui)
|
||||
self.ui_box.append(check)
|
||||
|
||||
def __on_file_changed(self, obj):
|
||||
self.infobar.set_revealed(True)
|
||||
self.save_button.set_sensitive(True)
|
||||
|
||||
def __load_filename(self):
|
||||
if not self.object or not self.object.load_css():
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
def __on_check_button_toggled(self, button, ui):
|
||||
if button.props.active:
|
||||
self.object.add_ui(ui)
|
||||
else:
|
||||
self.object.remove_ui(ui)
|
||||
|
||||
self.__update_ui_button_label()
|
||||
|
||||
def __update_ui_button_label(self):
|
||||
n = 0
|
||||
first_one = None
|
||||
child = self.ui_box.get_first_child()
|
||||
|
||||
while child is not None:
|
||||
if child.props.active:
|
||||
n += 1
|
||||
|
||||
if first_one is None:
|
||||
first_one = child
|
||||
|
||||
child = child.get_next_sibling()
|
||||
|
||||
if first_one is None:
|
||||
self.ui_menu_button.props.label = _("None")
|
||||
else:
|
||||
self.ui_menu_button.props.label = f"{first_one.props.label} + {n - 1}" if n > 1 else first_one.props.label
|
||||
|
||||
def __on_ui_added_removed(self, project, ui):
|
||||
self.__update_provider_for()
|
||||
|
||||
def __on_provider_for_notify(self, obj, pspec):
|
||||
self.__update_provider_for()
|
||||
|
||||
def __on_css_notify(self, obj, pspec):
|
||||
self.save_button.set_sensitive(True)
|
||||
|
||||
@GObject.Signal(
|
||||
flags=GObject.SignalFlags.RUN_LAST,
|
||||
return_type=bool,
|
||||
arg_types=(),
|
||||
accumulator=GObject.signal_accumulator_true_handled,
|
||||
)
|
||||
def remove_css(self):
|
||||
if self.object:
|
||||
self.object.project.remove_css(self.object)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbCSSEditor, "CmbCSSEditor")
|
@ -1,203 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_css_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbCSSEditor" parent="GtkGrid">
|
||||
<property name="column-spacing">3</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">Priority:</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">Global:</property>
|
||||
<property name="tooltip-text" translatable="yes">This provider will be used in all UI.</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="ui_menu_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="popover">
|
||||
<object class="GtkPopover">
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="propagate-natural-width">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ui_box">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">3</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="priority">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="is_global">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Provider for:</property>
|
||||
<property name="tooltip-text" translatable="yes">List of UI where this provider will be used</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes"><small>Note: CSS files need to be loaded at runtime</small></property>
|
||||
<property name="use-markup">1</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="tooltip-text" translatable="yes">Save CSS file</property>
|
||||
<property name="valign">end</property>
|
||||
<signal name="clicked" handler="on_save_button_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">document-save-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="infobar">
|
||||
<property name="message-type">warning</property>
|
||||
<property name="revealed">0</property>
|
||||
<property name="show-close-button">1</property>
|
||||
<signal name="response" handler="on_infobar_response"/>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="reload_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Reload</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="valign">end</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="hexpand">1</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="label" translatable="yes">The file changed on disk.</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="CmbSourceView" id="view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">css</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="vexpand-set">True</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
2994
cambalache/cmb_db.py
2994
cambalache/cmb_db.py
File diff suppressed because it is too large
Load Diff
@ -1,294 +0,0 @@
|
||||
#
|
||||
# CmbDBInspector
|
||||
#
|
||||
# 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, Gio, Gtk
|
||||
from cambalache import CmbProject
|
||||
|
||||
|
||||
class CmbDBTable(GObject.Object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__properties = {}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def do_get_property(self, prop):
|
||||
# TODO: read from DB directly
|
||||
if not prop.name.startswith("cmb-int-") and prop.name not in self.__properties_set__:
|
||||
raise AttributeError('unknown property %s' % prop.name)
|
||||
return self.__properties[prop.name]
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
# TODO: only store PK values when using DB
|
||||
if not prop.name.startswith("cmb-int-") and prop.name not in self.__properties_set__:
|
||||
raise AttributeError('unknown property %s' % prop.name)
|
||||
self.__properties[prop.name] = value
|
||||
self.notify(prop.name)
|
||||
|
||||
|
||||
class CmbDBStore(GObject.GObject, Gio.ListModel):
|
||||
__gtype_name__ = 'CmbDBStore'
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, ItemClass, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__item_class = ItemClass
|
||||
self.__history_index = None
|
||||
self._objects = []
|
||||
|
||||
def do_get_item(self, position):
|
||||
self.__check_refresh()
|
||||
return self._objects[position] if position < len(self._objects) else None
|
||||
|
||||
def do_get_item_type(self):
|
||||
return self.__item_class
|
||||
|
||||
def do_get_n_items(self):
|
||||
self.__check_refresh()
|
||||
return len(self._objects)
|
||||
|
||||
def __check_refresh(self):
|
||||
history_index = self.project.history_index
|
||||
|
||||
# Nothing to update if history did not changed
|
||||
if history_index == self.__history_index:
|
||||
return
|
||||
|
||||
ItemClass = self.__item_class
|
||||
properties = ItemClass.__properties__
|
||||
int_properties = ItemClass.__int_properties__
|
||||
table = ItemClass.__table__
|
||||
needs_update = False
|
||||
|
||||
# Basic optimization, only update if something changed in this table
|
||||
# TODO: this could be optimized more by check command to know exactly which row changed
|
||||
if self.__history_index is None or table.startswith("history") or table in ["global", "__profile__"]:
|
||||
needs_update = True
|
||||
else:
|
||||
change_table = table[7:] if table.startswith("history_") else table
|
||||
|
||||
# TODO: detect command compression
|
||||
for row in self.project.db.execute(
|
||||
"SELECT table_name FROM history WHERE history_id >= ? ORDER BY history_id;", (self.__history_index, )
|
||||
):
|
||||
table_name, = row
|
||||
if table_name == change_table:
|
||||
needs_update = True
|
||||
break
|
||||
|
||||
self.__history_index = history_index
|
||||
|
||||
if not needs_update:
|
||||
return
|
||||
|
||||
# Emit signal to clear model
|
||||
n_items = len(self._objects)
|
||||
if n_items:
|
||||
self._objects = []
|
||||
self.items_changed(0, n_items, 0)
|
||||
|
||||
if len(ItemClass.__pk__):
|
||||
pk_columns = ",".join(ItemClass.__pk__)
|
||||
else:
|
||||
pk_columns = "rowid"
|
||||
|
||||
for row in self.project.db.execute(f"SELECT * FROM {table} ORDER BY {pk_columns};"):
|
||||
item = ItemClass()
|
||||
for i, val in enumerate(row):
|
||||
property_id = properties[i]
|
||||
if property_id in int_properties:
|
||||
item.set_property(f"cmb-int-{property_id}", val if val is not None else 0)
|
||||
item.set_property(property_id, val)
|
||||
|
||||
self._objects.append(item)
|
||||
|
||||
# Emit signal to populate model
|
||||
self.items_changed(0, 0, len(self._objects))
|
||||
|
||||
|
||||
class TableView(Gtk.ColumnView):
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, ItemClass, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.show_row_separators = True
|
||||
self.props.show_column_separators = True
|
||||
self.props.reorderable = False
|
||||
self.__model = None
|
||||
self.__filter_model = None
|
||||
self.__item_class = ItemClass
|
||||
|
||||
for property_id in ItemClass.__properties__:
|
||||
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)
|
||||
|
||||
col = Gtk.ColumnViewColumn(title=property_id, factory=factory)
|
||||
|
||||
if property_id in ItemClass.__int_properties__:
|
||||
property_expression = Gtk.PropertyExpression.new(ItemClass, None, f"cmb-int-{property_id}")
|
||||
sorter = Gtk.NumericSorter()
|
||||
else:
|
||||
property_expression = Gtk.PropertyExpression.new(ItemClass, None, property_id)
|
||||
sorter = Gtk.StringSorter()
|
||||
col.props.resizable = True
|
||||
col.props.expand = True
|
||||
|
||||
sorter.set_expression(property_expression)
|
||||
col.set_sorter(sorter)
|
||||
self.append_column(col)
|
||||
|
||||
# TODO: keep track of project changes only while we are showing this model
|
||||
self.connect("map", self.__on_map)
|
||||
self.project.connect("changed", self.__on_project_changed)
|
||||
|
||||
def __update_label(self, item, label, property_id):
|
||||
val = str(item.get_property(property_id))
|
||||
label.set_text(val if val else "")
|
||||
|
||||
def __on_item_notify(self, item, pspec, label):
|
||||
self.__update_label(item, label, pspec.name)
|
||||
|
||||
def _on_factory_setup(self, factory, list_item):
|
||||
label = Gtk.Inscription()
|
||||
list_item.set_child(label)
|
||||
|
||||
def _on_factory_bind(self, factory, list_item, property_id):
|
||||
label = list_item.get_child()
|
||||
item = list_item.get_item()
|
||||
|
||||
self.__update_label(item, label, property_id)
|
||||
item.connect(f"notify::{property_id}", self.__on_item_notify, label)
|
||||
|
||||
def _on_factory_unbind(self, factory, list_item):
|
||||
item = list_item.get_item()
|
||||
item.disconnect_by_func(self.__on_item_notify)
|
||||
|
||||
def __on_map(self, w):
|
||||
# Trigger check refresh
|
||||
if self.__model is not None:
|
||||
self.__model.get_n_items()
|
||||
return
|
||||
|
||||
# Load model when widget is shown
|
||||
self.__model = CmbDBStore(self.__item_class, project=self.project)
|
||||
self.__filter_model = Gtk.SortListModel(model=self.__model, sorter=self.get_sorter())
|
||||
self.set_model(Gtk.NoSelection(model=self.__filter_model))
|
||||
|
||||
def __on_project_changed(self, project):
|
||||
# Trigger check refresh
|
||||
if self.__model is not None and self.is_visible():
|
||||
self.__model.get_n_items()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_db_inspector.ui")
|
||||
class CmbDBInspector(Gtk.Box):
|
||||
__gtype_name__ = "CmbDBInspector"
|
||||
|
||||
stack = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__project = None
|
||||
self.__table_classes = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("map", self.__on_map)
|
||||
|
||||
@GObject.Property(type=CmbProject)
|
||||
def project(self):
|
||||
return self.__project
|
||||
|
||||
@project.setter
|
||||
def _set_project(self, project):
|
||||
self.__project = project
|
||||
|
||||
def __init_tables(self):
|
||||
db = self.project.db
|
||||
self.__table_classes = {}
|
||||
|
||||
for row in db.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"):
|
||||
table, = row
|
||||
|
||||
if table.startswith("sqlite_"):
|
||||
continue
|
||||
|
||||
klass = self.__class_from_table(table)
|
||||
self.__table_classes[table] = klass
|
||||
|
||||
def _metadata_from_table(self, table):
|
||||
db = self.project.db
|
||||
properties = []
|
||||
gproperties = {}
|
||||
int_properties = set()
|
||||
pk_list = []
|
||||
|
||||
for row in db.execute(f"PRAGMA table_info({table});"):
|
||||
col = row[1]
|
||||
col_type = row[2]
|
||||
pk = row[5]
|
||||
|
||||
name = col.replace("_", "-")
|
||||
properties.append(name)
|
||||
if col_type == "INTEGER":
|
||||
int_properties.add(name)
|
||||
gproperties[f"cmb-int-{name}"] = (int, "", "", GLib.MININT, GLib.MAXINT, 0, GObject.ParamFlags.READWRITE)
|
||||
|
||||
gproperties[name] = (str, "", "", None, GObject.ParamFlags.READWRITE)
|
||||
|
||||
if pk:
|
||||
pk_list.append(col)
|
||||
|
||||
return properties, gproperties, int_properties, pk_list
|
||||
|
||||
def __class_from_table(self, table):
|
||||
class_name = f"CmbDBTable_{table}"
|
||||
properties, gproperties, int_properties, pk = self._metadata_from_table(table)
|
||||
klass = type(class_name, (CmbDBTable,), dict(
|
||||
__table__=table,
|
||||
__gproperties__=gproperties,
|
||||
__properties__=properties,
|
||||
__properties_set__=set(properties),
|
||||
__int_properties__=int_properties,
|
||||
__pk__=pk)
|
||||
)
|
||||
return klass
|
||||
|
||||
def __populate_stack(self):
|
||||
for table, klass in self.__table_classes.items():
|
||||
sw = Gtk.ScrolledWindow(
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
propagate_natural_width=True,
|
||||
propagate_natural_height=True)
|
||||
view = TableView(klass, project=self.__project)
|
||||
sw.set_child(view)
|
||||
self.stack.add_titled(sw, table, table)
|
||||
|
||||
def __on_map(self, w):
|
||||
if self.__table_classes is None and self.__project is not None:
|
||||
self.__init_tables()
|
||||
self.__populate_stack()
|
@ -1,21 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_db_inspector.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbDBInspector" parent="GtkBox">
|
||||
<child>
|
||||
<object class="GtkStackSidebar">
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="halign">start</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbDBmigration - Cambalache DataBase Migration functions
|
||||
#
|
||||
# Copyright (C) 2021-2023 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,15 +20,12 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
|
||||
def ensure_columns_for_0_7_5(table, data):
|
||||
if table == "object":
|
||||
if table == 'object':
|
||||
# Append position column
|
||||
return [row + (None,) for row in data]
|
||||
elif table in ["object_property", "object_layout_property"]:
|
||||
return [row + (None, ) for row in data]
|
||||
elif table in ['object_property', 'object_layout_property']:
|
||||
# Append translation_context, translation_comments columns
|
||||
return [row + (None, None) for row in data]
|
||||
|
||||
@ -36,128 +33,48 @@ def ensure_columns_for_0_7_5(table, data):
|
||||
|
||||
|
||||
def migrate_table_data_to_0_7_5(c, table, data):
|
||||
if table == "object":
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
if table == 'object':
|
||||
c.execute('''
|
||||
UPDATE object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY parent_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
SELECT row_number() OVER (
|
||||
PARTITION BY parent_id ORDER BY object_id
|
||||
) position, ui_id, object_id
|
||||
FROM object
|
||||
WHERE parent_id IS NOT NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
WHERE object.ui_id=new.ui_id AND object.object_id=new.object_id;
|
||||
''')
|
||||
c.execute('''
|
||||
UPDATE object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
SELECT row_number() OVER (
|
||||
PARTITION BY ui_id ORDER BY object_id
|
||||
) position, ui_id, object_id
|
||||
FROM object
|
||||
WHERE parent_id IS NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
WHERE object.ui_id=new.ui_id AND object.object_id=new.object_id;
|
||||
''')
|
||||
|
||||
|
||||
def ensure_columns_for_0_9_0(table, data):
|
||||
if table == "object_property":
|
||||
if table == 'object_property':
|
||||
# Append inline_object_id column
|
||||
return [row + (None,) for row in data]
|
||||
return [row + (None, ) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def migrate_table_data_to_0_9_0(c, table, data):
|
||||
if table == "object_property":
|
||||
if table == 'object_property':
|
||||
# Remove all object properties with a 0 as value
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM temp.object_property AS op
|
||||
WHERE value = 0 AND
|
||||
(SELECT property_id FROM temp.property WHERE owner_id=op.owner_id AND property_id=op.property_id AND is_object)
|
||||
IS NOT NULL;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def ensure_columns_for_0_11_2(table, data):
|
||||
if table in ["object", "ui"]:
|
||||
# Append custom_text column
|
||||
return [row + (None,) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_11_4(table, data):
|
||||
if table == "object_property":
|
||||
# Append bind_[source_id owner_id property_id flags] column
|
||||
return [row + (None, None, None, None) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_13_1(table, data):
|
||||
if table == "object_data":
|
||||
# Append translatable, translation_context, translation_comments columns
|
||||
return [row + (None, None, None) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_17_3(table, data):
|
||||
if table == "object":
|
||||
# Append custom_child_fragment column
|
||||
return [row + (None,) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def migrate_table_data_to_0_17_3(c, table, data):
|
||||
if table in ["object_property", "object_layout_property", "object_data"]:
|
||||
c.executescript(
|
||||
f"""
|
||||
UPDATE temp.{table} SET translatable=1
|
||||
WHERE translatable IS NOT NULL AND lower(translatable) IN (1, 'y', 'yes', 't', 'true');
|
||||
UPDATE temp.{table} SET translatable=NULL
|
||||
WHERE translatable IS NOT NULL AND translatable != 1;
|
||||
"""
|
||||
)
|
||||
|
||||
if table == "object_signal":
|
||||
for prop in ["swap", "after"]:
|
||||
c.executescript(
|
||||
f"""
|
||||
UPDATE temp.object_signal SET {prop}=1
|
||||
WHERE {prop} IS NOT NULL AND lower({prop}) IN (1, 'y', 'yes', 't', 'true');
|
||||
UPDATE temp.object_signal SET {prop}=NULL WHERE {prop} IS NOT NULL AND after != 1;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def migrate_table_data_to_0_91_3(c, table, data):
|
||||
# Ensure every object has a position
|
||||
if table == "object":
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id, parent_id ORDER BY position, object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NOT NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute('''
|
||||
DELETE FROM object_property AS op
|
||||
WHERE value = 0 AND
|
||||
(SELECT property_id
|
||||
FROM property
|
||||
WHERE owner_id=op.owner_id
|
||||
AND property_id=op.property_id
|
||||
AND is_object)
|
||||
IS NOT NULL;
|
||||
''')
|
||||
|
@ -1,112 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
import sqlite3
|
||||
|
||||
|
||||
class CmbProfileConnection(sqlite3.Connection):
|
||||
def __init__(self, path, **kwargs):
|
||||
super().__init__(path, **kwargs)
|
||||
|
||||
self.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS __profile__ (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
query TEXT NOT NULL,
|
||||
plan TEXT,
|
||||
executions INTEGER NOT NULL DEFAULT 1,
|
||||
total_time INTEGER NOT NULL DEFAULT 0,
|
||||
average_time INTEGER NOT NULL DEFAULT 0,
|
||||
min_time INTEGER NOT NULL DEFAULT 0,
|
||||
max_time INTEGER NOT NULL DEFAULT 0,
|
||||
callers JSONB
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
# Striped querys PK dictionary
|
||||
self._querys = {}
|
||||
|
||||
# Populate querys
|
||||
for row in super().execute("SELECT id, query FROM __profile__;"):
|
||||
id, query = row
|
||||
self._querys[query] = id
|
||||
|
||||
def cursor(self):
|
||||
return super(CmbProfileConnection, self).cursor(CmbProfileCursor)
|
||||
|
||||
def execute(self, *args):
|
||||
start = time.monotonic_ns()
|
||||
retval = super().execute(*args)
|
||||
self.log_query(time.monotonic_ns() - start, *args)
|
||||
return retval
|
||||
|
||||
def log_query(self, exec_time, *args):
|
||||
query = args[0].strip()
|
||||
|
||||
if query.startswith("CREATE") or query.startswith("PRAGMA"):
|
||||
return
|
||||
|
||||
caller = inspect.getframeinfo(inspect.stack()[2][0])
|
||||
file = os.path.basename(caller.filename).removesuffix('.py')
|
||||
function = caller.function
|
||||
# Use a different dot to avoid json syntax error
|
||||
caller_id = f"{file}․{function}:{caller.lineno}"
|
||||
|
||||
if file == "cmb_db" and function == "execute":
|
||||
caller = inspect.getframeinfo(inspect.stack()[3][0])
|
||||
file = os.path.basename(caller.filename).removesuffix('.py')
|
||||
caller_id = f"{file}․{caller.function}:{caller.lineno} {caller_id}"
|
||||
|
||||
# Convert from nano seconds to micro seconds
|
||||
exec_time = int(exec_time / 1000)
|
||||
pk_id = self._querys.get(query, None)
|
||||
|
||||
if pk_id is None:
|
||||
# Get query plan
|
||||
if len(args) > 1:
|
||||
c = super().execute(f"EXPLAIN QUERY PLAN {query}", args[1])
|
||||
else:
|
||||
c = super().execute(f"EXPLAIN QUERY PLAN {query}")
|
||||
|
||||
# Convert plan to a string
|
||||
plan = []
|
||||
for row in c:
|
||||
plan.append(" ".join(str(row)))
|
||||
plan = "\n".join(plan)
|
||||
|
||||
# Create new query entry in profile table
|
||||
c = super().execute(
|
||||
"""
|
||||
INSERT INTO __profile__(query, plan, total_time, average_time, min_time, max_time, callers)
|
||||
VALUES(?, ?, ?, ?, ?, ?, json(?))
|
||||
RETURNING id;
|
||||
""",
|
||||
(query, plan, exec_time, exec_time, exec_time, exec_time, f"""{{"{caller_id}": 1}}""")
|
||||
)
|
||||
pk_id = c.fetchone()[0]
|
||||
self._querys[query] = pk_id
|
||||
else:
|
||||
# Increment number of executions of this query
|
||||
super().execute(
|
||||
f"""
|
||||
UPDATE __profile__
|
||||
SET
|
||||
executions=executions+1,
|
||||
total_time=total_time+?,
|
||||
average_time=total_time/executions,
|
||||
min_time=min(min_time, ?),
|
||||
max_time=max(max_time, ?),
|
||||
callers=json_set(callers, '$.{caller_id}', callers->'$.{caller_id}' + 1)
|
||||
WHERE id=?;
|
||||
""",
|
||||
(exec_time, exec_time, exec_time, pk_id)
|
||||
)
|
||||
|
||||
|
||||
class CmbProfileCursor(sqlite3.Cursor):
|
||||
def execute(self, *args):
|
||||
start = time.monotonic_ns()
|
||||
retval = super().execute(*args)
|
||||
self.connection.log_query(time.monotonic_ns() - start, *args)
|
||||
return retval
|
@ -1,86 +0,0 @@
|
||||
#
|
||||
# CmbFragmentEditor - Cambalache CSS Editor
|
||||
#
|
||||
# Copyright (C) 2022-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
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_fragment_editor.ui")
|
||||
class CmbFragmentEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbFragmentEditor"
|
||||
|
||||
view = Gtk.Template.Child()
|
||||
child_view = Gtk.Template.Child()
|
||||
switcher = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self.__bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
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:
|
||||
return
|
||||
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
"custom-fragment",
|
||||
self.view,
|
||||
"text",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self.__bindings.append(binding)
|
||||
|
||||
# Only objects have child fragments
|
||||
if type(obj) is CmbObject and obj.parent:
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
"custom-child-fragment",
|
||||
self.child_view,
|
||||
"text",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self.__bindings.append(binding)
|
||||
|
||||
self.switcher.set_visible(True)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbFragmentEditor, "CmbFragmentEditor")
|
@ -1,61 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_fragment_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFragmentEditor" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Extra fragments:</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="fragment_stack">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<child>
|
||||
<object class="CmbSourceView" id="view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">fragment</property>
|
||||
<property name="title" translatable="yes">Fragment</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<child>
|
||||
<object class="CmbSourceView" id="child_view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">child_fragment</property>
|
||||
<property name="title" translatable="yes">Child Fragment</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher" id="switcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="stack">fragment_stack</property>
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,151 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,117 +0,0 @@
|
||||
#
|
||||
# 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")
|
@ -1,241 +0,0 @@
|
||||
<?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>
|
@ -20,47 +20,30 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseLayoutProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from . import utils
|
||||
from .cmb_objects_base import CmbBaseLayoutProperty, CmbPropertyInfo
|
||||
|
||||
|
||||
class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
object = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
object = GObject.Property(type=GObject.GObject, 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):
|
||||
self.__on_init = True
|
||||
super().__init__(**kwargs)
|
||||
self.version_warning = None
|
||||
|
||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||
self.library_id = owner_info.library_id
|
||||
self._update_version_warning()
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbLayoutProperty<{self.object.type_id} {self.info.owner_id}:{self.property_id}>"
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
obj = self.object
|
||||
self.project._object_layout_property_changed(obj.parent, obj, self)
|
||||
self.__on_init = False
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def value(self):
|
||||
c = self.project.db.execute(
|
||||
"""
|
||||
SELECT value
|
||||
FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
c = self.project.db.execute("SELECT value FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.child_id,
|
||||
self.owner_id,
|
||||
self.property_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@ -69,45 +52,27 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
if value is None or value == self.info.default_value:
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
c.execute("DELETE FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
value = None
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"""
|
||||
SELECT count(value)
|
||||
FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
count = self.db_get("SELECT count(value) FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_layout_property
|
||||
SET value=?
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(value, self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
c.execute("UPDATE object_layout_property SET value=? WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(value, self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
else:
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_layout_property (ui_id, object_id, child_id, owner_id, property_id, value)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value),
|
||||
)
|
||||
c.execute("INSERT INTO object_layout_property (ui_id, object_id, child_id, owner_id, property_id, value) VALUES (?, ?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value))
|
||||
|
||||
# Update object position if this is a position property
|
||||
if self.info.is_position:
|
||||
self.object.position = int(value) if value else 0
|
||||
|
||||
if not self.__on_init:
|
||||
self.object._layout_property_changed(self)
|
||||
|
||||
c.close()
|
||||
|
||||
def _update_version_warning(self):
|
||||
target = self.object.ui.get_target(self.library_id)
|
||||
return utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.property_id)
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache Library Info wrapper
|
||||
#
|
||||
# Copyright (C) 2022-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2022 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
|
||||
@ -20,38 +20,29 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseLibraryInfo
|
||||
|
||||
|
||||
class CmbLibraryInfo(CmbBaseLibraryInfo):
|
||||
third_party = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.object_types = self.__init_object_types()
|
||||
self.min_version = self.__init_min_version()
|
||||
|
||||
def __init_object_types(self):
|
||||
prefix = self.prefix
|
||||
prefix_len = len(prefix)
|
||||
retval = []
|
||||
|
||||
for row in self.project.db.execute("SELECT type_id FROM type WHERE library_id=?", (self.library_id,)):
|
||||
(type_id,) = row
|
||||
for row in self.project.db.execute('SELECT type_id FROM type WHERE library_id=?',
|
||||
(self.library_id, )):
|
||||
type_id, = row
|
||||
if type_id.startswith(prefix):
|
||||
# Remove Prefix from type name
|
||||
retval.append(type_id[prefix_len:])
|
||||
|
||||
return retval
|
||||
|
||||
def __init_min_version(self):
|
||||
row = self.project.db.execute(
|
||||
"SELECT MIN_VERSION(version) FROM library_version WHERE library_id=?;", (self.library_id,)
|
||||
).fetchone()
|
||||
|
||||
return row[0] if row is not None else None
|
||||
|
@ -1,42 +0,0 @@
|
||||
#
|
||||
# Cambalache GListModel error item
|
||||
#
|
||||
# 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_base import CmbBase
|
||||
|
||||
|
||||
# This class is used by GListModel implementations when they do not know which item to return
|
||||
class CmbListError(CmbBase):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
return "list error"
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
return 0
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbTextBuffer
|
||||
# CmbListStore - Cambalache List Store
|
||||
#
|
||||
# Copyright (C) 2021-2023 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,29 +20,31 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import io
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
||||
class CmbTextBuffer(Gtk.TextBuffer):
|
||||
__gtype_name__ = "CmbTextBuffer"
|
||||
class CmbListStore(Gtk.ListStore):
|
||||
__gtype_name__ = 'CmbListStore'
|
||||
|
||||
table = GObject.Property(type=str)
|
||||
query = GObject.Property(type=str)
|
||||
project = GObject.Property(type=GObject.GObject)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect("notify::text", self.__on_text_notify)
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify("cmb-value")
|
||||
data = self.project._get_table_data(self.table)
|
||||
self.set_column_types(data['types'])
|
||||
self.__populate()
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != "" else None
|
||||
def __populate(self):
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(self.query):
|
||||
self.append(row)
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if value == self.props.text:
|
||||
return
|
||||
|
||||
self.props.text = value if value is not None else ""
|
||||
c.close()
|
@ -1,260 +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 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,50 +0,0 @@
|
||||
#
|
||||
# CmbMessageNotificationView
|
||||
#
|
||||
# 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 CmbMessageNotification
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_message_notification_view.ui")
|
||||
class CmbMessageNotificationView(Gtk.Box):
|
||||
__gtype_name__ = "CmbMessageNotificationView"
|
||||
|
||||
notification = GObject.Property(
|
||||
type=CmbMessageNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
|
||||
# Message
|
||||
title_label = Gtk.Template.Child()
|
||||
message_label = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
notification = self.notification
|
||||
self.title_label.props.label = f"<b>{notification.title}</b>"
|
||||
self.message_label.props.label = notification.message
|
@ -1,22 +0,0 @@
|
||||
<?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>
|
@ -1,378 +0,0 @@
|
||||
#
|
||||
# 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()
|
@ -1,60 +0,0 @@
|
||||
#
|
||||
# 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)
|
@ -1,42 +0,0 @@
|
||||
<?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>
|
@ -1,67 +0,0 @@
|
||||
#
|
||||
# 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)
|
@ -1,26 +0,0 @@
|
||||
<?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>
|
@ -20,171 +20,85 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_list_error import CmbListError
|
||||
from .cmb_objects_base import CmbBaseObject, CmbSignal
|
||||
from .cmb_property import CmbProperty
|
||||
from .cmb_layout_property import CmbLayoutProperty
|
||||
from .cmb_object_data import CmbObjectData
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from .cmb_ui import CmbUI
|
||||
from .constants import GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
|
||||
from . import utils
|
||||
from cambalache import getLogger, _
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
class CmbObject(CmbBaseObject):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
__gsignals__ = {
|
||||
"property-changed": (GObject.SignalFlags.RUN_FIRST, None, (CmbProperty,)),
|
||||
"layout-property-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.GObject, CmbLayoutProperty)),
|
||||
"signal-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"signal-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"signal-changed": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"data-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbObjectData,)),
|
||||
"data-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbObjectData,)),
|
||||
"child-reordered": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObject, int, int)),
|
||||
'property-changed': (GObject.SignalFlags.RUN_FIRST, None, (CmbProperty, )),
|
||||
|
||||
'layout-property-changed': (GObject.SignalFlags.RUN_FIRST, None, (GObject.GObject, CmbLayoutProperty)),
|
||||
|
||||
'signal-added': (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal, )),
|
||||
|
||||
'signal-removed': (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal, ))
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__properties = None
|
||||
self.__properties_dict = None
|
||||
self.__layout = None
|
||||
self.__layout_dict = None
|
||||
self.__signals = None
|
||||
self.__signals_dict = None
|
||||
self.__data = None
|
||||
self.__data_dict = None
|
||||
self.inline_property_id = None
|
||||
self.version_warning = None
|
||||
self.__is_template = False
|
||||
|
||||
self._last_known = None
|
||||
self.properties = []
|
||||
self.properties_dict = {}
|
||||
self.layout = []
|
||||
self.layout_dict = {}
|
||||
self.signals = []
|
||||
self.data = []
|
||||
self.position_layout_property = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
self.connect('notify', self.__on_notify)
|
||||
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
self.__update_inline_property_id()
|
||||
self.__update_version_warning()
|
||||
self.ui.connect("notify", self._on_ui_notify)
|
||||
self.ui.connect("library-changed", self._on_ui_library_changed)
|
||||
|
||||
def __bool__(self):
|
||||
# Override Truth Value Testing to ensure that CmbObject objects evaluates to True even if it does not have children
|
||||
return True
|
||||
self.__populate_properties()
|
||||
self.__populate_layout_properties()
|
||||
self.__populate_signals()
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbObject<{self.display_name_type}> {self.ui_id}:{self.object_id}"
|
||||
|
||||
@property
|
||||
def properties(self):
|
||||
self.__populate_properties()
|
||||
return self.__properties
|
||||
|
||||
@property
|
||||
def properties_dict(self):
|
||||
self.__populate_properties()
|
||||
return self.__properties_dict
|
||||
|
||||
@property
|
||||
def layout(self):
|
||||
self.__populate_layout()
|
||||
return self.__layout
|
||||
|
||||
@property
|
||||
def layout_dict(self):
|
||||
self.__populate_layout()
|
||||
return self.__layout_dict
|
||||
|
||||
@property
|
||||
def signals(self):
|
||||
self.__populate_signals()
|
||||
return self.__signals
|
||||
|
||||
@property
|
||||
def signals_dict(self):
|
||||
self.__populate_signals()
|
||||
return self.__signals_dict
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
self.__populate_data()
|
||||
return self.__data
|
||||
|
||||
@property
|
||||
def data_dict(self):
|
||||
self.__populate_data()
|
||||
return self.__data_dict
|
||||
|
||||
def __update_inline_property_id(self):
|
||||
ui_id = self.ui_id
|
||||
object_id = self.object_id
|
||||
parent_id = self.parent_id
|
||||
|
||||
if parent_id:
|
||||
# Set which parent property makes a reference to this inline object
|
||||
row = self.project.db.execute(
|
||||
"SELECT property_id FROM object_property WHERE ui_id=? AND inline_object_id=?;", (ui_id, object_id)
|
||||
).fetchone()
|
||||
self.inline_property_id = row[0] if row else None
|
||||
return f'CmbObject<{self.type_id}> {self.ui_id}:{self.object_id}'
|
||||
|
||||
def __populate_type_properties(self, name):
|
||||
property_info = self.project.get_type_properties(name)
|
||||
if property_info is None:
|
||||
return
|
||||
|
||||
for property_name, info in property_info.items():
|
||||
# Check if this property was already installed by a derived class
|
||||
if property_name in self.__properties_dict:
|
||||
continue
|
||||
for property_name in property_info:
|
||||
info = property_info[property_name]
|
||||
|
||||
prop = CmbProperty(
|
||||
object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info,
|
||||
)
|
||||
prop = CmbProperty(object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info)
|
||||
|
||||
# List of property
|
||||
self.__properties.append(prop)
|
||||
self.properties.append(prop)
|
||||
|
||||
# Dictionary of properties
|
||||
self.__properties_dict[property_name] = prop
|
||||
self.properties_dict[property_name] = prop
|
||||
|
||||
def __populate_properties(self):
|
||||
if self.__properties is not None:
|
||||
return
|
||||
self.__properties = []
|
||||
self.__properties_dict = {}
|
||||
|
||||
self.__populate_type_properties(self.type_id)
|
||||
for parent_id in self.info.hierarchy:
|
||||
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):
|
||||
property_info = self.project.get_type_properties(name)
|
||||
if property_info is None:
|
||||
@ -195,150 +109,69 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
for property_name in property_info:
|
||||
info = property_info[property_name]
|
||||
|
||||
prop = CmbLayoutProperty(
|
||||
object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=parent_id,
|
||||
child_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info,
|
||||
)
|
||||
prop = CmbLayoutProperty(object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=parent_id,
|
||||
child_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info)
|
||||
|
||||
self.__layout.append(prop)
|
||||
# Keep a reference to the position layout property
|
||||
if info.is_position:
|
||||
self.position_layout_property = prop
|
||||
|
||||
self.layout.append(prop)
|
||||
|
||||
# Dictionary of properties
|
||||
self.__layout_dict[property_name] = prop
|
||||
self.layout_dict[property_name] = prop
|
||||
|
||||
def _property_changed(self, prop):
|
||||
self.emit("property-changed", prop)
|
||||
self.emit('property-changed', prop)
|
||||
self.project._object_property_changed(self, prop)
|
||||
|
||||
def _layout_property_changed(self, prop):
|
||||
parent = self.project.get_object_by_id(self.ui_id, self.parent_id)
|
||||
self.emit("layout-property-changed", parent, prop)
|
||||
self.emit('layout-property-changed', parent, prop)
|
||||
self.project._object_layout_property_changed(parent, self, prop)
|
||||
|
||||
def __add_signal_object(self, signal):
|
||||
self.__populate_signals()
|
||||
self.__signals.append(signal)
|
||||
self.__signals_dict[signal.signal_pk] = signal
|
||||
self.emit("signal-added", signal)
|
||||
self.signals.append(signal)
|
||||
self.emit('signal-added', signal)
|
||||
self.project._object_signal_added(self, signal)
|
||||
|
||||
signal.connect("notify", self.__on_signal_notify)
|
||||
|
||||
def __on_signal_notify(self, signal, pspec):
|
||||
self.emit("signal-changed", signal)
|
||||
self.project._object_signal_changed(self, signal)
|
||||
|
||||
def __add_data_object(self, data):
|
||||
if data.get_id_string() in self.data_dict:
|
||||
return
|
||||
|
||||
self.__data.append(data)
|
||||
self.__data_dict[data.get_id_string()] = data
|
||||
self.emit("data-added", data)
|
||||
self.project._object_data_added(self, data)
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
if pspec.name == "parent-id":
|
||||
self.__populate_layout_properties()
|
||||
|
||||
self.project._object_changed(self, pspec.name)
|
||||
|
||||
def __populate_signals(self):
|
||||
if self.__signals is not None:
|
||||
return
|
||||
self.__signals = []
|
||||
self.__signals_dict = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate signals
|
||||
for row in c.execute("SELECT * FROM object_signal WHERE ui_id=? AND object_id=?;", (self.ui_id, self.object_id)):
|
||||
for row in c.execute('SELECT * FROM object_signal WHERE ui_id=? AND object_id=?;',
|
||||
(self.ui_id, self.object_id)):
|
||||
self.__add_signal_object(CmbSignal.from_row(self.project, *row))
|
||||
|
||||
def __populate_data(self):
|
||||
if self.__data is not None:
|
||||
return
|
||||
self.__data = []
|
||||
self.__data_dict = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate data
|
||||
for row in c.execute(
|
||||
"SELECT * FROM object_data WHERE ui_id=? AND object_id=? AND parent_id IS NULL;",
|
||||
(self.ui_id, self.object_id),
|
||||
):
|
||||
self.__add_data_object(CmbObjectData.from_row(self.project, *row))
|
||||
|
||||
def __populate_layout_properties(self):
|
||||
parent_id = self.parent_id
|
||||
|
||||
# FIXME: delete is anything is set?
|
||||
self.__layout = []
|
||||
self.__layout_dict = {}
|
||||
|
||||
if parent_id > 0:
|
||||
parent = self.project.get_object_by_id(self.ui_id, parent_id)
|
||||
for owner_id in [parent.type_id] + parent.info.hierarchy:
|
||||
self.__populate_layout_properties_from_type(f"{owner_id}LayoutChild")
|
||||
|
||||
def __populate_layout(self):
|
||||
if self.__layout is None:
|
||||
self.__populate_layout_properties()
|
||||
self.__populate_layout_properties_from_type(f"{parent.type_id}LayoutChild")
|
||||
else:
|
||||
self.layout = []
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def parent_id(self):
|
||||
retval = self.db_get(
|
||||
"SELECT parent_id FROM object WHERE (ui_id, object_id) IS (?, ?);",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
),
|
||||
)
|
||||
retval = self.db_get('SELECT parent_id FROM object WHERE (ui_id, object_id) IS (?, ?);',
|
||||
(self.ui_id, self.object_id, ))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@parent_id.setter
|
||||
def _set_parent_id(self, value):
|
||||
new_parent_id = value if value != 0 else None
|
||||
old_parent_id = self.parent_id if self.parent_id != 0 else None
|
||||
|
||||
if old_parent_id == new_parent_id:
|
||||
return
|
||||
|
||||
# Save old parent and position
|
||||
self._save_last_known_parent_and_position()
|
||||
|
||||
project = self.project
|
||||
ui_id = self.ui_id
|
||||
object_id = self.object_id
|
||||
|
||||
if new_parent_id is None:
|
||||
new_position = self.db_get(
|
||||
"SELECT MAX(position)+1 FROM object WHERE ui_id=? AND parent_id IS NULL",
|
||||
(ui_id, )
|
||||
)
|
||||
else:
|
||||
new_position = self.db_get(
|
||||
"SELECT MAX(position)+1 FROM object WHERE ui_id=? AND parent_id=?",
|
||||
(ui_id, new_parent_id)
|
||||
)
|
||||
|
||||
project.db.execute(
|
||||
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
||||
(new_parent_id, new_position or 0, ui_id, object_id)
|
||||
)
|
||||
|
||||
# Update children positions in old parent
|
||||
project.db.update_children_position(ui_id, old_parent_id)
|
||||
|
||||
# Update GListModel
|
||||
self._remove_from_old_parent()
|
||||
self._update_new_parent()
|
||||
self.db_set('UPDATE object SET parent_id=? WHERE (ui_id, object_id) IS (?, ?);',
|
||||
(self.ui_id, self.object_id, ),
|
||||
value if value != 0 else None)
|
||||
|
||||
self.__populate_layout_properties()
|
||||
|
||||
@ -351,19 +184,17 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
return self.project.get_object_by_id(self.ui_id, self.parent_id)
|
||||
|
||||
def _add_signal(self, signal_pk, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
signal = CmbSignal(
|
||||
project=self.project,
|
||||
signal_pk=signal_pk,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
signal_id=signal_id,
|
||||
handler=handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after,
|
||||
)
|
||||
signal = CmbSignal(project=self.project,
|
||||
signal_pk=signal_pk,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
signal_id=signal_id,
|
||||
handler=handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after)
|
||||
|
||||
self.__add_signal_object(signal)
|
||||
|
||||
@ -372,66 +203,41 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
def add_signal(self, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
try:
|
||||
c = self.project.db.cursor()
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_signal (ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, owner_id, signal_id, handler, detail, user_data, swap, after),
|
||||
)
|
||||
c.execute("INSERT INTO object_signal (ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, owner_id, signal_id, handler, detail, user_data, swap, after))
|
||||
signal_pk = c.lastrowid
|
||||
c.close()
|
||||
self.project.db.commit()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error adding signal handler {owner_id}:{signal_id} {handler} to object {self.ui_id}.{{self.object_id}} {e}"
|
||||
)
|
||||
logger.warning(f'Error adding signal handler {owner_id}:{signal_id} {handler} to object {self.ui_id}.{{self.object_id}} {e}')
|
||||
return None
|
||||
else:
|
||||
return self._add_signal(
|
||||
signal_pk,
|
||||
owner_id,
|
||||
signal_id,
|
||||
handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after,
|
||||
)
|
||||
return self._add_signal(signal_pk,
|
||||
owner_id,
|
||||
signal_id,
|
||||
handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after)
|
||||
|
||||
def _remove_signal(self, signal):
|
||||
self.__signals.remove(signal)
|
||||
del self.__signals_dict[signal.signal_pk]
|
||||
|
||||
self.emit("signal-removed", signal)
|
||||
self.signals.remove(signal)
|
||||
self.emit('signal-removed', signal)
|
||||
self.project._object_signal_removed(self, signal)
|
||||
|
||||
def remove_signal(self, signal):
|
||||
try:
|
||||
self.project.db.execute("DELETE FROM object_signal WHERE signal_pk=?;", (signal.signal_pk,))
|
||||
self.project.db.execute("DELETE FROM object_signal WHERE signal_pk=?;",
|
||||
(signal.signal_pk, ))
|
||||
self.project.db.commit()
|
||||
except Exception as e:
|
||||
handler = f"{signal.owner_id}:{signal.signal_id} {signal.handler}"
|
||||
logger.warning(f"Error removing signal handler {handler} from object {self.ui_id}.{{self.object_id}} {e}")
|
||||
logger.warning(f'Error removing signal handler {signal.owner_id}:{signal.signal_id} {signal.handler} from object {self.ui_id}.{{self.object_id}} {e}')
|
||||
return False
|
||||
else:
|
||||
self._remove_signal(signal)
|
||||
return True
|
||||
|
||||
def _add_data(self, owner_id, data_id, id, info=None):
|
||||
data = CmbObjectData(
|
||||
project=self.project,
|
||||
object=self,
|
||||
info=info,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
)
|
||||
self.__add_data_object(data)
|
||||
return data
|
||||
|
||||
def add_data(self, data_key, value=None, comment=None):
|
||||
try:
|
||||
value = str(value) if value is not None else None
|
||||
@ -440,133 +246,88 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
data_id = taginfo.data_id
|
||||
id = self.project.db.object_add_data(self.ui_id, self.object_id, owner_id, data_id, value, None, comment)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding data {data_key} {e}")
|
||||
logger.warning(f'Error adding data {data_key} {e}')
|
||||
return None
|
||||
else:
|
||||
return self._add_data(owner_id, data_id, id, info=taginfo)
|
||||
|
||||
def _remove_data(self, data):
|
||||
if data.get_id_string() not in self.data_dict:
|
||||
return
|
||||
|
||||
self.__data.remove(data)
|
||||
del self.__data_dict[data.get_id_string()]
|
||||
|
||||
self.emit("data-removed", data)
|
||||
self.project._object_data_removed(self, data)
|
||||
new_data = CmbObjectData(project=self.project,
|
||||
object=self,
|
||||
info=taginfo,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
value=value,
|
||||
comment=comment)
|
||||
self.data.append(new_data)
|
||||
return new_data
|
||||
|
||||
def remove_data(self, data):
|
||||
try:
|
||||
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(
|
||||
"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),
|
||||
)
|
||||
assert data in self.data
|
||||
self.project.db.execute("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.project.db.commit()
|
||||
self.project.history_pop()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||
logger.warning(f'{self} Error removing data {data}: {e}')
|
||||
return False
|
||||
else:
|
||||
self._remove_data(data)
|
||||
self.data.remove(data)
|
||||
return True
|
||||
|
||||
def reorder_child(self, child, position):
|
||||
if child is None:
|
||||
logger.warning("child has to be a CmbObject")
|
||||
logger.warning(f'child has to be a CmbObject')
|
||||
return
|
||||
|
||||
if self.ui_id != child.ui_id or self.object_id != child.parent_id:
|
||||
logger.warning(f"{child} is not children of {self}")
|
||||
return
|
||||
|
||||
old_position = child.position
|
||||
old_list_position = child.list_position
|
||||
if old_position == position:
|
||||
logger.warning(f'{child} is not children of {self}')
|
||||
return
|
||||
|
||||
name = child.name if child.name is not None else child.type_id
|
||||
self.project.history_push(
|
||||
_("Reorder object {name} from position {old} to {new}").format(name=name, old=old_position, new=position)
|
||||
)
|
||||
self.project.history_push(_('Reorder object {name} from position {old} to {new}').format(name=name, old=child.position, new=position))
|
||||
|
||||
db = self.project.db
|
||||
children = []
|
||||
|
||||
# Consider this children
|
||||
#
|
||||
# label 0
|
||||
# button 1
|
||||
# entry 2
|
||||
# switch 3
|
||||
# toggle 4
|
||||
|
||||
# Disable check so we can set position temporally to -1
|
||||
db.ignore_check_constraints = True
|
||||
db.execute("UPDATE object SET position=-1 WHERE ui_id=? AND object_id=?;", (self.ui_id, child.object_id))
|
||||
|
||||
# Make room for new position
|
||||
for select_stmt, update_stmt in [
|
||||
(
|
||||
"""
|
||||
SELECT ui_id, object_id
|
||||
# Get children in order
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute('''
|
||||
SELECT object_id, position
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=? AND position <= ? AND position > ?
|
||||
ORDER BY position ASC
|
||||
""",
|
||||
"UPDATE object SET position=position - 1 WHERE ui_id=? AND object_id=?;"
|
||||
),
|
||||
(
|
||||
"""
|
||||
SELECT ui_id, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=? AND position >= ? AND position < ?
|
||||
ORDER BY position DESC
|
||||
""",
|
||||
"UPDATE object SET position=position + 1 WHERE ui_id=? AND object_id=?;"
|
||||
),
|
||||
]:
|
||||
for row in db.execute(select_stmt, (self.ui_id, self.object_id, position, old_position)):
|
||||
db.execute(update_stmt, tuple(row))
|
||||
WHERE ui_id=? AND parent_id=? AND internal IS NULL AND object_id!=?
|
||||
AND object_id NOT IN (SELECT inline_object_id FROM object_property WHERE inline_object_id IS NOT NULL AND ui_id=? AND object_id=?)
|
||||
ORDER BY position;''', (self.ui_id, self.object_id, child.object_id, self.ui_id, self.object_id)):
|
||||
child_id, child_position = row
|
||||
|
||||
# Set new position
|
||||
db.execute("UPDATE object SET position=? WHERE ui_id=? AND object_id=?;", (position, self.ui_id, child.object_id))
|
||||
obj = self.project.get_object_by_id(self.ui_id, child_id)
|
||||
if obj:
|
||||
children.append(obj)
|
||||
|
||||
db.ignore_check_constraints = False
|
||||
# Insert child in new position
|
||||
children.insert(position, child)
|
||||
|
||||
list_position = child.list_position
|
||||
|
||||
self.project._ignore_selection = True
|
||||
# Emit GListModel signals
|
||||
if position < old_position:
|
||||
self.items_changed(list_position, 0, 1)
|
||||
self.items_changed(old_list_position+1, 1, 0)
|
||||
else:
|
||||
if old_list_position != list_position:
|
||||
self.items_changed(old_list_position, 1, 0)
|
||||
self.items_changed(list_position, 0, 1)
|
||||
|
||||
self.project._ignore_selection = False
|
||||
# Update all positions
|
||||
for pos, obj in enumerate(children):
|
||||
# Sync layout property
|
||||
if obj.position_layout_property:
|
||||
obj.position_layout_property.value = pos
|
||||
else:
|
||||
# Or object position
|
||||
obj.position = pos
|
||||
|
||||
c.close()
|
||||
self.project.history_pop()
|
||||
self.emit("child-reordered", child, old_position, position)
|
||||
self.project._object_child_reordered(self, child, old_position, position)
|
||||
|
||||
def clear_properties(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
name = self.name
|
||||
name = name if name is not None else self.type_id
|
||||
self.project.history_push(_("Clear object {name} properties").format(name=name))
|
||||
self.project.history_push(_('Clear object {name} properties').format(name=name))
|
||||
|
||||
properties = []
|
||||
for row in c.execute(
|
||||
"SELECT property_id FROM object_property WHERE ui_id=? AND object_id=?;", (self.ui_id, self.object_id)
|
||||
):
|
||||
for row in c.execute("SELECT property_id FROM object_property WHERE ui_id=? AND object_id=?;",
|
||||
(self.ui_id, self.object_id)):
|
||||
properties.append(row[0])
|
||||
|
||||
# Remove all properties from this object
|
||||
@ -576,171 +337,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
c.close()
|
||||
|
||||
for prop_id in properties:
|
||||
prop = self.__properties_dict[prop_id]
|
||||
prop.notify("value")
|
||||
prop = self.properties_dict[prop_id]
|
||||
prop.notify('value')
|
||||
self._property_changed(prop)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name_type(self):
|
||||
return f"{self.type_id} {self.name}" if self.name else self.type_id
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
name = self.name or ""
|
||||
type_id = self.type_id
|
||||
parent_id = self.parent_id
|
||||
|
||||
if type_id in [GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]:
|
||||
prop = self.properties_dict["label"]
|
||||
label = prop.value or ""
|
||||
display_name = f"{type_id} <i>{label}</i>"
|
||||
elif 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:
|
||||
return f'<span underline="error">{display_name}</span>'
|
||||
else:
|
||||
return display_name
|
||||
|
||||
def __update_version_warning(self):
|
||||
target = self.ui.get_target(self.info.library_id)
|
||||
self.version_warning = utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.type_id)
|
||||
|
||||
def _on_ui_notify(self, obj, pspec):
|
||||
property_id = pspec.name
|
||||
|
||||
if property_id == "template-id":
|
||||
was_template = self.__is_template
|
||||
self.__is_template = obj.template_id == self.object_id
|
||||
|
||||
if was_template or self.__is_template:
|
||||
self.notify("display-name")
|
||||
self.notify("display-name-type")
|
||||
|
||||
def _on_ui_library_changed(self, ui, library_id):
|
||||
self.__update_version_warning()
|
||||
|
||||
self.__populate_properties()
|
||||
self.__populate_layout()
|
||||
|
||||
# Update properties directly, to avoid having to connect too many times to this signal
|
||||
for props in [self.__properties, self.__layout]:
|
||||
for prop in props:
|
||||
if prop.library_id == library_id:
|
||||
prop._update_version_warning()
|
||||
|
||||
# GListModel helpers
|
||||
def _save_last_known_parent_and_position(self):
|
||||
self._last_known = (self.parent, self.list_position)
|
||||
|
||||
def _update_new_parent(self):
|
||||
parent = self.parent
|
||||
position = self.list_position
|
||||
|
||||
# Emit GListModel signal to update model
|
||||
if parent:
|
||||
parent.items_changed(position, 0, 1)
|
||||
parent.notify("n-items")
|
||||
else:
|
||||
ui = self.ui
|
||||
ui.items_changed(position, 0, 1)
|
||||
ui.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")
|
||||
else:
|
||||
ui = self.ui
|
||||
ui.items_changed(position, 1, 0)
|
||||
ui.notify("n-items")
|
||||
|
||||
self._last_known = None
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def list_position(self):
|
||||
ui_id = self.ui_id
|
||||
|
||||
if self.parent_id:
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT rownum-1
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=?
|
||||
)
|
||||
WHERE object_id=?;
|
||||
""",
|
||||
(ui_id, self.parent_id, self.object_id)
|
||||
)
|
||||
else:
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT rownum-1
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id IS NULL
|
||||
)
|
||||
WHERE object_id=?;
|
||||
""",
|
||||
(ui_id, self.object_id)
|
||||
)
|
||||
|
||||
return retval
|
||||
|
||||
# GListModel iface
|
||||
def do_get_item(self, position):
|
||||
ui_id = self.ui_id
|
||||
|
||||
# This query should use index object_ui_id_parent_id_position_idx
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT object_id
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=?
|
||||
)
|
||||
WHERE rownum=?;
|
||||
""",
|
||||
(ui_id, self.object_id, position+1)
|
||||
)
|
||||
if retval is not None:
|
||||
return self.project.get_object_by_id(ui_id, retval)
|
||||
|
||||
# This should not happen
|
||||
return CmbListError()
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBaseObject
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
if self.project is None:
|
||||
return 0
|
||||
|
||||
retval = self.db_get("SELECT COUNT(object_id) FROM object WHERE ui_id=? AND parent_id=?;", (self.ui_id, self.object_id))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache Object Data wrapper
|
||||
#
|
||||
# Copyright (C) 2022-2023 Juan Pablo Ugarte
|
||||
# Copyright (C) 2022 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
|
||||
@ -20,29 +20,21 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_objects_base import CmbBaseObjectData
|
||||
from .cmb_type_info import CmbTypeDataInfo
|
||||
from cambalache import getLogger, _
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbObjectData(CmbBaseObjectData):
|
||||
__gsignals__ = {
|
||||
"data-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObjectData,)),
|
||||
"data-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObjectData,)),
|
||||
"arg-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
parent = GObject.Property(type=CmbBaseObjectData, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
object = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
||||
info = GObject.Property(type=CmbTypeDataInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
object = GObject.Property(type=GObject.Object, flags = GObject.ParamFlags.READWRITE)
|
||||
info = GObject.Property(type=CmbTypeDataInfo, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.args = []
|
||||
@ -53,129 +45,64 @@ class CmbObjectData(CmbBaseObjectData):
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
if self.info is None:
|
||||
type_info = self.project.type_info.get(self.owner_id, None)
|
||||
if type_info:
|
||||
self.info = type_info.find_data_info(self.data_id)
|
||||
|
||||
if self.object is None:
|
||||
self.object = self.project.get_object_by_id(self.ui_id, self.object_id)
|
||||
|
||||
self.__populate_children()
|
||||
self.connect("notify", self._on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbObjectData<{self.owner_id}:{self.info.key}> obj={self.ui_id}:{self.object_id} data={self.data_id}:{self.id}"
|
||||
|
||||
def get_id_string(self):
|
||||
return f"{self.owner_id}.{self.id}"
|
||||
return f'CmbObjectData<{self.owner_id}> {self.ui_id}:{self.object_id} {self.data_id} {self.id}'
|
||||
|
||||
def get_arg(self, key):
|
||||
c = self.project.db.execute(
|
||||
"SELECT value FROM object_data_arg WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
c = self.project.db.execute("SELECT value FROM object_data_arg WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND key=?;",
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.data_id,
|
||||
key))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def _on_notify(self, obj, pspec):
|
||||
if pspec.name in ["value"]:
|
||||
self.project._object_data_changed(self)
|
||||
|
||||
def _arg_changed(self, key):
|
||||
self.emit("arg-changed", key)
|
||||
self.project._object_data_arg_changed(self, key)
|
||||
|
||||
def set_arg(self, key, value):
|
||||
# Prevent potential infinite recursion
|
||||
val = self.get_arg(key)
|
||||
if val == value:
|
||||
return
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
try:
|
||||
if value is None:
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM object_data_arg
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
c.execute("DELETE FROM object_data_arg WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key))
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"""
|
||||
SELECT count(value) FROM object_data_arg
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
count = self.db_get("SELECT count(value) FROM object_data_arg WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key))
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_data_arg SET value=?
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(str(value), self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
c.execute("UPDATE object_data_arg SET value=? WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;",
|
||||
(str(value), self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key))
|
||||
else:
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_data_arg (ui_id, object_id, owner_id, data_id, id, key, value)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key, str(value)),
|
||||
)
|
||||
c.execute("INSERT INTO object_data_arg (ui_id, object_id, owner_id, data_id, id, key, value) VALUES (?, ?, ?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key, str(value)))
|
||||
|
||||
self._arg_changed(key)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error setting arg {key}={value}: {e}")
|
||||
logger.warning(f'{self} Error setting arg {key}={value}: {e}')
|
||||
|
||||
c.close()
|
||||
|
||||
def __add_child(self, child):
|
||||
if child in self.children:
|
||||
return
|
||||
|
||||
self.children.append(child)
|
||||
self.object.data_dict[child.get_id_string()] = child
|
||||
self.emit("data-added", child)
|
||||
self.project._object_data_data_added(self, child)
|
||||
|
||||
def _remove_child(self, child):
|
||||
self.children.remove(child)
|
||||
del self.object.data_dict[child.get_id_string()]
|
||||
self.emit("data-removed", child)
|
||||
self.project._object_data_data_removed(self, child)
|
||||
|
||||
def _add_child(self, owner_id, data_id, id, info=None):
|
||||
new_data = CmbObjectData(
|
||||
project=self.project,
|
||||
object=self.object,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
parent=self,
|
||||
info=info,
|
||||
)
|
||||
self.__add_child(new_data)
|
||||
return new_data
|
||||
# TODO: add necessary signals
|
||||
#self.emit('child-added', child)
|
||||
#self.project._object_data_child_added(self, )
|
||||
|
||||
def __populate_children(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate children
|
||||
for row in c.execute(
|
||||
"SELECT * FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND parent_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.id),
|
||||
):
|
||||
for row in c.execute('SELECT * FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND parent_id=?;',
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.id)):
|
||||
obj = CmbObjectData.from_row(self.project, *row)
|
||||
obj.parent = self
|
||||
obj.object = self.object
|
||||
obj.info = self.info.children.get(row[3])
|
||||
self.__add_child(obj)
|
||||
|
||||
def add_data(self, data_key, value=None, comment=None):
|
||||
@ -186,28 +113,36 @@ class CmbObjectData(CmbBaseObjectData):
|
||||
data_id = taginfo.data_id
|
||||
id = self.project.db.object_add_data(self.ui_id, self.object_id, owner_id, data_id, value, self.id, comment)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error adding child data {data_key}: {e}")
|
||||
logger.warning(f'{self} Error adding child data {data_key}: {e}')
|
||||
return None
|
||||
else:
|
||||
return self._add_child(owner_id, data_id, id, taginfo)
|
||||
new_data = CmbObjectData(project=self.project,
|
||||
object=self.object,
|
||||
info=taginfo,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
value=value,
|
||||
parent_id=self.id,
|
||||
comment=comment)
|
||||
self.__add_child(new_data)
|
||||
return new_data
|
||||
|
||||
def remove_data(self, data):
|
||||
try:
|
||||
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(
|
||||
"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.project.db.execute("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.project.db.commit()
|
||||
self.project.history_pop()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||
logger.warning(f'{self} Error removing data {data}: {e}')
|
||||
return False
|
||||
else:
|
||||
self._remove_child(data)
|
||||
self.children.remove(data)
|
||||
|
||||
# TODO: add necessary signals
|
||||
#self.emit('child-removed', child)
|
||||
#self.project._object_data_child_removed(self, )
|
||||
return True
|
||||
|
@ -1,293 +0,0 @@
|
||||
#
|
||||
# CmbObjectDataEditor - Cambalache Object Data Editor
|
||||
#
|
||||
# Copyright (C) 2022-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_type_info import CmbTypeDataInfo
|
||||
from .cmb_object_data import CmbObjectData
|
||||
from .control import cmb_create_editor
|
||||
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")
|
||||
class CmbObjectDataEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbObjectDataEditor"
|
||||
|
||||
info = GObject.Property(type=CmbTypeDataInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
label = Gtk.Template.Child()
|
||||
top_box = Gtk.Template.Child()
|
||||
add_child = Gtk.Template.Child()
|
||||
add_only_child = Gtk.Template.Child()
|
||||
remove_button = Gtk.Template.Child()
|
||||
grid = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__data = None
|
||||
|
||||
self.__size_group = None
|
||||
self.__value_editor = None
|
||||
self.__arg_editors = {}
|
||||
self.__editors = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__update_view()
|
||||
|
||||
@Gtk.Template.Callback("on_add_only_child_clicked")
|
||||
def __on_add_only_child_clicked(self, button):
|
||||
info = self.data.info if self.data else self.info
|
||||
|
||||
# Do not add a menu if there is only one child type
|
||||
child_info = list(info.children.values())[0]
|
||||
self.__on_child_button_clicked(button, child_info)
|
||||
|
||||
@Gtk.Template.Callback("on_remove_clicked")
|
||||
def __on_remove_clicked(self, button):
|
||||
if self.info:
|
||||
self.object.remove_data(self.__data)
|
||||
elif self.__data:
|
||||
self.__data.parent.remove_data(self.__data)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
def object(self):
|
||||
return self.__object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, value):
|
||||
if self.__object:
|
||||
self.__object.disconnect_by_func(self.__on_data_added)
|
||||
self.__object.disconnect_by_func(self.__on_data_removed)
|
||||
|
||||
self.__object = value
|
||||
|
||||
if self.__object:
|
||||
self.__object.connect("data-added", self.__on_data_added)
|
||||
self.__object.connect("data-removed", self.__on_data_removed)
|
||||
|
||||
@GObject.Property(type=CmbObjectData)
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
@data.setter
|
||||
def _set_data(self, value):
|
||||
if self.__data:
|
||||
self.__data.disconnect_by_func(self.__on_data_data_added)
|
||||
self.__data.disconnect_by_func(self.__on_data_data_removed)
|
||||
self.__data.disconnect_by_func(self.__on_data_arg_changed)
|
||||
|
||||
# Clear old editors
|
||||
for editor in self.__editors:
|
||||
self.grid.remove(editor)
|
||||
self.__editors = []
|
||||
|
||||
self.__data = value
|
||||
|
||||
if self.__data:
|
||||
self.__data.connect("data-added", self.__on_data_data_added)
|
||||
self.__data.connect("data-removed", self.__on_data_data_removed)
|
||||
self.__data.connect("arg-changed", self.__on_data_arg_changed)
|
||||
|
||||
def __update_arg(self, key):
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
editor = self.__arg_editors.get(key, None)
|
||||
if editor:
|
||||
val = self.data.get_arg(key)
|
||||
|
||||
# Only update if there is a change
|
||||
if val != editor.cmb_value:
|
||||
editor.cmb_value = val
|
||||
|
||||
def __on_data_data_added(self, parent, data):
|
||||
self.__add_data_editor(data)
|
||||
|
||||
def __on_data_data_removed(self, parent, data):
|
||||
self.__remove_data_editor(data)
|
||||
|
||||
def __on_data_arg_changed(self, data, key):
|
||||
self.__update_arg(key)
|
||||
|
||||
def __on_data_added(self, obj, data):
|
||||
if self.info and self.data is None and self.info == data.info:
|
||||
self.data = data
|
||||
self.__update_view()
|
||||
|
||||
def __on_data_removed(self, obj, data):
|
||||
self.__remove_data_editor(data)
|
||||
|
||||
def __ensure_object_data(self, history_message):
|
||||
if self.data:
|
||||
return False
|
||||
|
||||
self.object.project.history_push(history_message)
|
||||
self.data = self.object.add_data(self.info.key)
|
||||
|
||||
if self.__value_editor:
|
||||
GObject.Object.bind_property(
|
||||
self.data,
|
||||
"value",
|
||||
self.__value_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
return True
|
||||
|
||||
def __on_child_button_clicked(self, button, info):
|
||||
msg = _("Add {key}").format(key=info.key)
|
||||
history_pushed = self.__ensure_object_data(msg)
|
||||
self.data.add_data(info.key)
|
||||
if history_pushed:
|
||||
self.object.project.history_pop()
|
||||
|
||||
def __context_menu_new(self, info):
|
||||
popover = Gtk.Popover(position=Gtk.PositionType.BOTTOM)
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, visible=True, spacing=4, border_width=4)
|
||||
|
||||
# Add children types
|
||||
for child in info.children:
|
||||
child_info = info.children[child]
|
||||
button = Gtk.ModelButton(label=_("Add {key}").format(key=child_info.key), visible=True)
|
||||
button.connect("clicked", self.__on_child_button_clicked, child_info)
|
||||
box.append(button)
|
||||
|
||||
popover.set_child(box)
|
||||
|
||||
return popover
|
||||
|
||||
def __on_arg_notify(self, obj, pspec, info):
|
||||
msg = _("Set {key} to {value}").format(key=info.key, value=obj.cmb_value)
|
||||
history_pushed = self.__ensure_object_data(msg)
|
||||
self.data.set_arg(info.key, obj.cmb_value)
|
||||
if history_pushed:
|
||||
self.object.project.history_pop()
|
||||
|
||||
def __add(self, editor, label=None):
|
||||
neditors = len(self.__editors)
|
||||
|
||||
if label:
|
||||
self.grid.attach(label, 0, neditors, 1, 1)
|
||||
self.__size_group.add_widget(label)
|
||||
|
||||
self.grid.attach(editor, 1, neditors, 1, 1)
|
||||
|
||||
self.__editors.append(editor)
|
||||
|
||||
def __add_data_editor(self, data):
|
||||
editor = CmbObjectDataEditor(visible=True, hexpand=True, margin_start=16, object=self.object, data=data)
|
||||
self.__add(editor)
|
||||
|
||||
def __remove_data_editor(self, data):
|
||||
if self.__data == data:
|
||||
self.data = None
|
||||
return
|
||||
|
||||
for editor in self.__editors:
|
||||
if data == editor.data:
|
||||
self.grid.remove(editor)
|
||||
self.__editors.remove(editor)
|
||||
break
|
||||
|
||||
def __update_view(self):
|
||||
if self.data is None and self.info is None:
|
||||
return
|
||||
|
||||
info = self.data.info if self.data else self.info
|
||||
project = self.__object.project
|
||||
|
||||
if info is None:
|
||||
return
|
||||
|
||||
nchildren = len(info.children)
|
||||
|
||||
self.remove_button.props.tooltip_text = _("Remove {key}").format(key=info.key)
|
||||
|
||||
# Add a menu if there is more than one child type
|
||||
if nchildren > 1:
|
||||
self.add_child.props.popover = self.__context_menu_new(info)
|
||||
self.add_child.set_visible(True)
|
||||
elif nchildren:
|
||||
key = list(info.children.keys())[0]
|
||||
self.add_only_child.props.tooltip_text = _("Add {key}").format(key=key)
|
||||
self.add_only_child.set_visible(True)
|
||||
|
||||
# Item name
|
||||
self.label.props.label = info.key
|
||||
|
||||
self.__size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
|
||||
self.__size_group.add_widget(self.label)
|
||||
|
||||
# Value
|
||||
if info.type_id:
|
||||
editor = cmb_create_editor(project, info.type_id, data=self.data, parent=self.object)
|
||||
self.__value_editor = editor
|
||||
|
||||
if self.data:
|
||||
GObject.Object.bind_property(
|
||||
self.data,
|
||||
"value",
|
||||
self.__value_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
self.top_box.append(editor)
|
||||
|
||||
nargs = len(info.args)
|
||||
|
||||
# Arguments
|
||||
for arg in info.args:
|
||||
arg_info = info.args[arg]
|
||||
|
||||
editor = cmb_create_editor(project, arg_info.type_id, parent=self.object)
|
||||
self.__arg_editors[arg_info.key] = editor
|
||||
|
||||
# Initialize value
|
||||
self.__update_arg(arg_info.key)
|
||||
|
||||
# Listen for editor value changes and update argument
|
||||
editor.connect("notify::cmb-value", self.__on_arg_notify, arg_info)
|
||||
|
||||
# Special case items with one argument and no value (like styles)
|
||||
if nargs == 1 and not info.type_id:
|
||||
self.label.props.label = f"{info.key} {arg_info.key}"
|
||||
self.top_box.append(editor)
|
||||
else:
|
||||
label = Gtk.Label(visible=True, label=arg_info.key, xalign=1)
|
||||
self.__add(editor, label)
|
||||
|
||||
# Current children
|
||||
if self.data:
|
||||
for child in self.data.children:
|
||||
self.__add_data_editor(child)
|
@ -1,76 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_object_data_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectDataEditor" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="top_box">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<signal name="clicked" handler="on_remove_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="borderless"/>
|
||||
</style>
|
||||
<style>
|
||||
<class name="hidden"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="add_child">
|
||||
<property name="halign">end</property>
|
||||
<property name="visible">0</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_only_child">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="visible">0</property>
|
||||
<signal name="clicked" handler="on_add_only_child_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="borderless"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="grid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbObjectEditor - Cambalache Object Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,104 +20,69 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
import gi
|
||||
import math
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GLib, GObject, Gtk
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_object_data_editor import CmbObjectDataEditor
|
||||
from .control import CmbEntry, CmbChildTypeComboBox, cmb_create_editor
|
||||
from .cmb_property_label import CmbPropertyLabel
|
||||
from cambalache import _
|
||||
from .constants import EXTERNAL_TYPE
|
||||
from . import utils
|
||||
from .cmb_property_controls import *
|
||||
|
||||
|
||||
class CmbObjectEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbObjectEditor"
|
||||
__gtype_name__ = 'CmbObjectEditor'
|
||||
|
||||
layout = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False)
|
||||
layout = GObject.Property(type=bool,
|
||||
flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
||||
default=False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__id_label = None
|
||||
self.__template_switch = None
|
||||
self.__bindings = []
|
||||
self.__labels = {}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.orientation = Gtk.Orientation.VERTICAL
|
||||
|
||||
def bind_property(self, *args):
|
||||
binding = GObject.Object.bind_property(*args)
|
||||
self.__bindings.append(binding)
|
||||
return binding
|
||||
|
||||
def __create_id_editor(self):
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=6)
|
||||
|
||||
# Label
|
||||
self.__id_label = Gtk.Label(label=_("Object Id"), halign=Gtk.Align.START)
|
||||
self.__id_label = Gtk.Label(label=_('Object Id'), width_chars=8)
|
||||
|
||||
# Template check
|
||||
if self.__object and not self.__object.parent_id:
|
||||
is_template = self.__object.object_id == self.__object.ui.template_id
|
||||
|
||||
check = Gtk.CheckButton(active=is_template,
|
||||
tooltip_text=_('Switch between object and template'))
|
||||
check.connect('toggled', self.__on_template_check_toggled)
|
||||
self.__update_template_label()
|
||||
|
||||
check.add(self.__id_label)
|
||||
box.add(check)
|
||||
else:
|
||||
box.add(self.__id_label)
|
||||
|
||||
# Id/Class entry
|
||||
entry = CmbEntry()
|
||||
self.bind_property(
|
||||
self.__object,
|
||||
"name",
|
||||
entry,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
grid.attach(self.__id_label, 0, 0, 1, 1)
|
||||
grid.attach(entry, 1, 0, 1, 1)
|
||||
|
||||
# Template check
|
||||
if self.__object and self.__object.parent_id == 0:
|
||||
is_template = self.__object.object_id == self.__object.ui.template_id
|
||||
tooltip_text = _("Switch between object and template")
|
||||
derivable = self.__object.info.derivable
|
||||
|
||||
if not derivable:
|
||||
tooltip_text = _("{type} is not derivable.").format(type=self.__object.info.type_id)
|
||||
|
||||
label = Gtk.Label(label=_("Template"), halign=Gtk.Align.START, tooltip_text=tooltip_text, sensitive=derivable)
|
||||
self.__template_switch = Gtk.Switch(
|
||||
active=is_template, halign=Gtk.Align.START, tooltip_text=tooltip_text, sensitive=derivable
|
||||
)
|
||||
|
||||
self.__template_switch.connect("notify::active", self.__on_template_switch_notify)
|
||||
self.__update_template_label()
|
||||
|
||||
grid.attach(label, 0, 1, 1, 1)
|
||||
grid.attach(self.__template_switch, 1, 1, 1, 1)
|
||||
|
||||
return grid
|
||||
|
||||
def __on_shortcut_button_clicked(self, button, type_id):
|
||||
obj = self.__object
|
||||
obj.project.add_object(obj.ui_id, type_id, parent_id=obj.object_id)
|
||||
|
||||
def __create_child_shortcuts(self, info):
|
||||
box = Gtk.FlowBox(visible=True, hexpand=True, selection_mode=Gtk.SelectionMode.NONE)
|
||||
|
||||
label = Gtk.Label(label=_("Add"), xalign=0, visible=True)
|
||||
box.append(label)
|
||||
|
||||
for type_id in info.child_type_shortcuts:
|
||||
button = Gtk.Button(label=type_id, visible=True)
|
||||
button.connect("clicked", self.__on_shortcut_button_clicked, type_id)
|
||||
box.append(button)
|
||||
|
||||
GObject.Object.bind_property(self.__object, 'name',
|
||||
entry, 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
box.pack_start(entry, True, True, 0)
|
||||
return box
|
||||
|
||||
def __update_template_label(self):
|
||||
istmpl = self.__object.ui.template_id == self.__object.object_id
|
||||
self.__id_label.props.label = _("Type Name") if istmpl else _("Object Id")
|
||||
self.__id_label.props.label = _('Template') if istmpl else _('Object Id')
|
||||
|
||||
def __on_template_switch_notify(self, switch, pspec):
|
||||
self.__object.ui.template_id = self.__object.object_id if switch.props.active else 0
|
||||
def __on_template_check_toggled(self, button):
|
||||
self.__object.ui.template_id = self.__object.object_id if button.props.active else 0
|
||||
self.__update_template_label()
|
||||
|
||||
def __on_expander_expanded(self, expander, pspec, revealer):
|
||||
@ -131,65 +96,48 @@ class CmbObjectEditor(Gtk.Box):
|
||||
revealer.props.reveal_child = expanded
|
||||
|
||||
def __create_child_type_editor(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=6)
|
||||
|
||||
box.append(Gtk.Label(label=_("Child Type"), width_chars=8))
|
||||
box.add(Gtk.Label(label=_('Child Type'), width_chars=8))
|
||||
|
||||
combo = CmbChildTypeComboBox(object=self.__object)
|
||||
|
||||
self.bind_property(
|
||||
self.__object,
|
||||
"type",
|
||||
combo,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
box.append(combo)
|
||||
GObject.Object.bind_property(self.__object, 'type',
|
||||
combo, 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
box.pack_start(combo, True, True, 0)
|
||||
return box
|
||||
|
||||
def __update_view(self):
|
||||
for child in utils.widget_get_children(self):
|
||||
self.__labels = {}
|
||||
|
||||
for child in self.get_children():
|
||||
self.remove(child)
|
||||
|
||||
if self.__object is None:
|
||||
return
|
||||
|
||||
obj = self.__object
|
||||
parent = obj.parent
|
||||
is_builtin = obj.info.is_builtin
|
||||
parent = self.__object.parent
|
||||
|
||||
if self.layout:
|
||||
if parent is None or is_builtin:
|
||||
if parent is None:
|
||||
return
|
||||
|
||||
# Child Type input
|
||||
if parent.info.has_child_types():
|
||||
self.append(self.__create_child_type_editor())
|
||||
self.add(self.__create_child_type_editor())
|
||||
else:
|
||||
# ID
|
||||
self.append(self.__create_id_editor())
|
||||
self.add(self.__create_id_editor())
|
||||
|
||||
if obj.type_id == EXTERNAL_TYPE:
|
||||
label = Gtk.Label(
|
||||
label=_(
|
||||
"This object will not be exported, it is only used to make a reference to it. \
|
||||
It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
),
|
||||
halign=Gtk.Align.START,
|
||||
margin_top=8,
|
||||
xalign=0,
|
||||
wrap=True,
|
||||
)
|
||||
self.append(label)
|
||||
self.show()
|
||||
return
|
||||
|
||||
info = parent.info if self.layout and parent else obj.info
|
||||
info = parent.info if self.layout and parent else self.__object.info
|
||||
for owner_id in [info.type_id] + info.hierarchy:
|
||||
if self.layout:
|
||||
owner_id = f"{owner_id}LayoutChild"
|
||||
owner_id = f'{owner_id}LayoutChild'
|
||||
|
||||
info = obj.project.type_info.get(owner_id, None)
|
||||
info = self.__object.project.type_info.get(owner_id, None)
|
||||
|
||||
if info is None:
|
||||
continue
|
||||
@ -198,95 +146,69 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
i = 0
|
||||
|
||||
# Grid for all properties and custom data editors
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
|
||||
|
||||
# Add shortcuts
|
||||
if not self.layout and len(info.child_type_shortcuts):
|
||||
shortcuts = self.__create_child_shortcuts(info)
|
||||
shortcuts.props.margin_start = 14
|
||||
grid.attach(shortcuts, 0, i, 2, 1)
|
||||
i += 1
|
||||
grid = Gtk.Grid(hexpand=True,
|
||||
margin_start=16,
|
||||
row_spacing=4,
|
||||
column_spacing=4)
|
||||
|
||||
# Properties
|
||||
properties = obj.layout_dict if self.layout else obj.properties_dict
|
||||
properties = self.__object.layout_dict if self.layout else self.__object.properties_dict
|
||||
for property_id in info.properties:
|
||||
prop = properties.get(property_id, None)
|
||||
|
||||
if prop is None or prop.info is None:
|
||||
if prop is None:
|
||||
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 = self.__create_editor_for_property(prop)
|
||||
|
||||
if editor is None:
|
||||
continue
|
||||
|
||||
self.bind_property(
|
||||
prop,
|
||||
"value",
|
||||
editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
label = Gtk.Label(label=prop.property_id,
|
||||
xalign=0)
|
||||
|
||||
if self.layout:
|
||||
label = CmbPropertyLabel(layout_prop=prop)
|
||||
else:
|
||||
label = CmbPropertyLabel(prop=prop, bindable=not is_builtin)
|
||||
# Keep a dict of labels
|
||||
self.__labels[prop.property_id] = label
|
||||
|
||||
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
|
||||
# Update labe status
|
||||
self.__update_property_label(prop)
|
||||
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
for data_key in info.data:
|
||||
data = None
|
||||
|
||||
# Find data
|
||||
for d in obj.data:
|
||||
if d.info.key == data_key:
|
||||
data = d
|
||||
break
|
||||
|
||||
editor = CmbObjectDataEditor(
|
||||
visible=True,
|
||||
hexpand=True,
|
||||
object=obj,
|
||||
data=data,
|
||||
info=info.data[data_key],
|
||||
)
|
||||
|
||||
grid.attach(editor, 0, i, 2, 1)
|
||||
i += 1
|
||||
|
||||
# Continue if class had no editors to add
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
# Create expander/revealer to pack editor grid
|
||||
expander = Gtk.Expander(label=f"<b>{owner_id}</b>", use_markup=True, expanded=True)
|
||||
expander = Gtk.Expander(label=f'<b>{owner_id}</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.append(expander)
|
||||
self.append(revealer)
|
||||
expander.connect('notify::expanded', self.__on_expander_expanded, revealer)
|
||||
revealer.add(grid)
|
||||
self.add(expander)
|
||||
self.add(revealer)
|
||||
|
||||
self.show()
|
||||
self.show_all()
|
||||
|
||||
def __on_object_ui_notify(self, obj, pspec):
|
||||
if pspec.name == "template-id" and self.__template_switch:
|
||||
self.__template_switch.set_active(obj.props.template_id != 0)
|
||||
def __on_property_changed(self, obj, prop):
|
||||
self.__update_property_label(prop)
|
||||
|
||||
def __on_object_notify(self, obj, pspec):
|
||||
if pspec.name == "parent-id":
|
||||
self.__update_view()
|
||||
def __on_layout_property_changed(self, obj, child, prop):
|
||||
self.__update_property_label(prop)
|
||||
|
||||
def __update_property_label(self, prop):
|
||||
label = self.__labels.get(prop.property_id, None)
|
||||
|
||||
if label is None:
|
||||
return
|
||||
|
||||
if prop.value != prop.info.default_value:
|
||||
label.get_style_context().add_class('modified')
|
||||
else:
|
||||
label.get_style_context().remove_class('modified')
|
||||
|
||||
@GObject.Property(type=CmbObject)
|
||||
def object(self):
|
||||
@ -298,21 +220,112 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
return
|
||||
|
||||
if self.__object:
|
||||
self.__object.disconnect_by_func(self.__on_object_notify)
|
||||
self.__object.ui.disconnect_by_func(self.__on_object_ui_notify)
|
||||
|
||||
for binding in self.__bindings:
|
||||
binding.unbind()
|
||||
|
||||
self.__bindings = []
|
||||
self.__object.disconnect_by_func(self.__on_property_changed)
|
||||
self.__object.disconnect_by_func(self.__on_layout_property_changed)
|
||||
|
||||
self.__object = obj
|
||||
|
||||
if obj:
|
||||
obj.connect("notify", self.__on_object_notify)
|
||||
obj.ui.connect("notify", self.__on_object_ui_notify)
|
||||
self.__object.connect('property-changed',
|
||||
self.__on_property_changed)
|
||||
self.__object.connect('layout-property-changed',
|
||||
self.__on_layout_property_changed)
|
||||
|
||||
self.__update_view()
|
||||
|
||||
def __get_min_max_for_type(self, type_id):
|
||||
if type_id == 'gchar':
|
||||
return (GLib.MININT8, GLib.MAXINT8)
|
||||
elif type_id == 'guchar':
|
||||
return (0, GLib.MAXUINT8)
|
||||
elif type_id == 'gint':
|
||||
return (GLib.MININT, GLib.MAXINT)
|
||||
elif type_id == 'guint':
|
||||
return (0, GLib.MAXUINT)
|
||||
elif type_id == 'glong':
|
||||
return (GLib.MINLONG, GLib.MAXLONG)
|
||||
elif type_id == 'gulong':
|
||||
return (0, GLib.MAXULONG)
|
||||
elif type_id == 'gint64':
|
||||
return (GLib.MININT64, GLib.MAXINT64)
|
||||
elif type_id == 'guint64':
|
||||
return (0, GLib.MAXUINT64)
|
||||
elif type_id == 'gfloat':
|
||||
return (-GLib.MAXFLOAT, GLib.MAXFLOAT)
|
||||
elif type_id == 'gdouble':
|
||||
return (-GLib.MAXDOUBLE, GLib.MAXDOUBLE)
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbObjectEditor, "CmbObjectEditor")
|
||||
def __create_editor_for_property(self, prop):
|
||||
editor = None
|
||||
|
||||
if prop.info is None:
|
||||
return None
|
||||
|
||||
info = prop.info
|
||||
type_id = info.type_id
|
||||
tinfo = self.__object.project.type_info.get(type_id, None)
|
||||
|
||||
if type_id == 'gboolean':
|
||||
editor = CmbSwitch()
|
||||
if type_id == 'gunichar':
|
||||
editor = CmbEntry(hexpand=True,
|
||||
max_length=1,
|
||||
placeholder_text=f'<{type_id}>')
|
||||
elif type_id == 'gchar' or type_id == 'guchar' or \
|
||||
type_id == 'gint' or type_id == 'guint' or \
|
||||
type_id == 'glong' or type_id == 'gulong' or \
|
||||
type_id == 'gint64' or type_id == 'guint64'or \
|
||||
type_id == 'gfloat' or type_id == 'gdouble':
|
||||
|
||||
digits = 0
|
||||
step_increment = 1
|
||||
minimum, maximum = self.__get_min_max_for_type(type_id)
|
||||
|
||||
# FIXME: is there a better way to handle inf -inf values other
|
||||
# than casting to str?
|
||||
if info.minimum is not None:
|
||||
value = float(info.minimum)
|
||||
minimum = value if value != -math.inf else -GLib.MAXDOUBLE
|
||||
if info.maximum is not None:
|
||||
value = float(info.maximum)
|
||||
maximum = value if value != math.inf else GLib.MAXDOUBLE
|
||||
|
||||
if type_id == 'gfloat' or type_id == 'gdouble':
|
||||
digits = 4
|
||||
step_increment = 0.1
|
||||
|
||||
adjustment = Gtk.Adjustment(lower=minimum,
|
||||
upper=maximum,
|
||||
step_increment=step_increment,
|
||||
page_increment=10)
|
||||
|
||||
editor = CmbSpinButton(digits=digits,
|
||||
adjustment=adjustment)
|
||||
elif type_id == 'GdkRGBA':
|
||||
editor = CmbColorEntry()
|
||||
elif type_id == 'GdkColor':
|
||||
editor = CmbColorEntry(use_color=True)
|
||||
elif type_id == 'CmbIconName':
|
||||
editor = CmbIconNameEntry(hexpand=True,
|
||||
placeholder_text=f'<Icon Name>')
|
||||
elif info.is_object:
|
||||
editor = CmbObjectChooser(prop=prop)
|
||||
elif tinfo:
|
||||
if tinfo.parent_id == 'enum':
|
||||
editor = CmbEnumComboBox(info=tinfo)
|
||||
elif tinfo.parent_id == 'flags':
|
||||
editor = CmbFlagsEntry(info=tinfo)
|
||||
|
||||
if editor is None:
|
||||
editor = CmbEntry(hexpand=True, placeholder_text=f'<{type_id}>')
|
||||
if info.translatable == True:
|
||||
editor.make_translatable(target = prop)
|
||||
|
||||
GObject.Object.bind_property(prop, 'value',
|
||||
editor, 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
return editor
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbObjectEditor, 'CmbObjectEditor')
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,118 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
|
@ -1,142 +0,0 @@
|
||||
#
|
||||
# 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)
|
||||
|
@ -1,64 +0,0 @@
|
||||
<?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>
|
@ -1,76 +0,0 @@
|
||||
#
|
||||
# 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
|
@ -1,23 +0,0 @@
|
||||
<?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
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache Property wrapper
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,280 +20,53 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from . import utils
|
||||
from cambalache import _, getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
from .cmb_objects_base import CmbBaseProperty, CmbPropertyInfo
|
||||
|
||||
|
||||
class CmbProperty(CmbBaseProperty):
|
||||
object = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
object = GObject.Property(type=GObject.GObject, 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):
|
||||
self._init = True
|
||||
super().__init__(**kwargs)
|
||||
self._init = False
|
||||
self.version_warning = None
|
||||
|
||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||
self.library_id = owner_info.library_id
|
||||
self._update_version_warning()
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbProperty<{self.object.type_id} {self.info.owner_id}:{self.property_id}>"
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
self.project._object_property_changed(self.object, self)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def value(self):
|
||||
c = self.project.db.execute(
|
||||
"SELECT value FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
)
|
||||
c = self.project.db.execute("SELECT value FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
self.__update_values(value, self.translatable, self.translation_context, self.translation_comments, self.bind_property)
|
||||
|
||||
@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()
|
||||
|
||||
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
|
||||
if bind_property:
|
||||
bind_source_id = bind_property.object.object_id
|
||||
bind_owner_id = bind_property.owner_id
|
||||
bind_property_id = bind_property.property_id
|
||||
|
||||
if (
|
||||
(value is None or value == self.info.default_value or (self.info.is_object and value == 0)) and
|
||||
bind_property is None and
|
||||
not translatable and
|
||||
translation_context is None and
|
||||
translation_comments is None
|
||||
):
|
||||
self.reset()
|
||||
if value is None or value == self.info.default_value or (self.info.is_object and value == 0):
|
||||
c.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))
|
||||
else:
|
||||
if (
|
||||
value == self.value and
|
||||
translatable == self.translatable and
|
||||
translation_context == self.translation_context and
|
||||
translation_comments == self.translation_comments and
|
||||
bind_source_id == self.bind_source_id and
|
||||
bind_owner_id == self.bind_owner_id and
|
||||
bind_property_id == self.bind_property_id
|
||||
):
|
||||
return
|
||||
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"SELECT count(ui_id) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
)
|
||||
count = self.db_get("SELECT count(value) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id))
|
||||
|
||||
if count:
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Update {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_property
|
||||
SET
|
||||
value=?,
|
||||
translatable=?, translation_context=?, translation_comments=?,
|
||||
bind_source_id=?, bind_owner_id=?, bind_property_id=?
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(
|
||||
value,
|
||||
translatable,
|
||||
translation_context,
|
||||
translation_comments,
|
||||
bind_source_id,
|
||||
bind_owner_id,
|
||||
bind_property_id,
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
),
|
||||
)
|
||||
c.execute("UPDATE object_property SET value=? WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(value, self.ui_id, self.object_id, self.owner_id, self.property_id))
|
||||
else:
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Set {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||
c.execute("INSERT INTO object_property (ui_id, object_id, owner_id, property_id, value) VALUES (?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id, value))
|
||||
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_property
|
||||
(
|
||||
ui_id, object_id, owner_id, property_id,
|
||||
value,
|
||||
translatable, translation_context, translation_comments,
|
||||
bind_source_id, bind_owner_id, bind_property_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
value,
|
||||
translatable,
|
||||
translation_context,
|
||||
translation_comments,
|
||||
bind_source_id,
|
||||
bind_owner_id,
|
||||
bind_property_id,
|
||||
),
|
||||
)
|
||||
|
||||
self.__update_internal_child()
|
||||
|
||||
if self.info.internal_child:
|
||||
self.project.history_pop()
|
||||
|
||||
if self._init is False:
|
||||
if self._init == False:
|
||||
self.object._property_changed(self)
|
||||
|
||||
c.close()
|
||||
|
||||
@GObject.Property(type=CmbBaseProperty)
|
||||
def bind_property(self):
|
||||
c = self.project.db.cursor()
|
||||
row = c.execute(
|
||||
"""
|
||||
SELECT bind_source_id, bind_property_id
|
||||
FROM object_property
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
).fetchone()
|
||||
|
||||
if row:
|
||||
bind_source_id, bind_property_id = row
|
||||
source = self.project.get_object_by_id(self.ui_id, bind_source_id) if bind_property_id else None
|
||||
return source.properties_dict.get(bind_property_id, None) if source else None
|
||||
|
||||
return None
|
||||
|
||||
@bind_property.setter
|
||||
def _set_bind_property(self, bind_property):
|
||||
self.__update_values(self.value, self.translatable, self.translation_context, self.translation_comments, bind_property)
|
||||
self.project._object_property_binding_changed(self.object, self)
|
||||
|
||||
def _update_version_warning(self):
|
||||
target = self.object.ui.get_target(self.library_id)
|
||||
warning = utils.get_version_warning(
|
||||
target, self.info.version, self.info.deprecated_version, self.property_id
|
||||
) or ""
|
||||
|
||||
if self.project.target_tk == "gtk-4.0" and self.info.type_id == "GFile":
|
||||
target = self.object.ui.get_target("gtk")
|
||||
if target is not None:
|
||||
version = utils.parse_version(target)
|
||||
if version is None or utils.version_cmp(version, (4, 16, 0)) < 0:
|
||||
if len(warning):
|
||||
warning += "\n"
|
||||
warning += _("Warning: GFile uri needs to be absolute for Gtk < 4.16")
|
||||
|
||||
self.version_warning = warning if len(warning) else None
|
||||
|
714
cambalache/cmb_property_controls.py
Normal file
714
cambalache/cmb_property_controls.py
Normal file
@ -0,0 +1,714 @@
|
||||
#
|
||||
# CmbPropertyControls - Cambalache Property Controls
|
||||
#
|
||||
# Copyright (C) 2021 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>
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
import math
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GLib, GObject, Gdk, Gtk, Pango, GdkPixbuf
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_ui import CmbUI
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from .cmb_translatable_popover import CmbTranslatablePopover
|
||||
from .cmb_type_chooser_popover import CmbTypeChooserPopover
|
||||
from .cmb_property import CmbProperty
|
||||
from .icon_naming_spec import standard_icon_names, standard_icon_context
|
||||
|
||||
|
||||
class CmbEntry(Gtk.Entry):
|
||||
__gtype_name__ = 'CmbEntry'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::text', self.__on_text_notify)
|
||||
|
||||
def make_translatable(self, target):
|
||||
self._target = target
|
||||
self.props.secondary_icon_name = 'document-edit-symbolic'
|
||||
self.connect("icon-press", self.__on_icon_pressed)
|
||||
|
||||
def __on_icon_pressed(self, widget, icon_pos, event):
|
||||
popover = CmbTranslatablePopover(self._target, relative_to=self)
|
||||
popover.popup()
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.props.text = value if value is not None else ''
|
||||
|
||||
|
||||
class CmbTextBuffer(Gtk.TextBuffer):
|
||||
__gtype_name__ = 'CmbTextBuffer'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::text', self.__on_text_notify)
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.props.text = value if value is not None else ''
|
||||
|
||||
|
||||
class CmbSpinButton(Gtk.SpinButton):
|
||||
__gtype_name__ = 'CmbSpinButton'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::value', self.__on_text_notify)
|
||||
self.props.halign=Gtk.Align.START
|
||||
self.props.numeric=True
|
||||
self.props.width_chars=8
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
# FIXME: value should always use C locale
|
||||
if self.props.digits == 0:
|
||||
return str(int(self.props.value))
|
||||
else:
|
||||
# NOTE: round() to avoid setting numbers like 0.7000000000000001
|
||||
return str(round(self.props.value, 15))
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
value = float(value)
|
||||
|
||||
if value == math.inf:
|
||||
self.props.value = GLib.MAXDOUBLE
|
||||
elif value == -math.inf:
|
||||
self.props.value = -GLib.MAXDOUBLE
|
||||
else:
|
||||
self.props.value = value
|
||||
|
||||
|
||||
class CmbSwitch(Gtk.Switch):
|
||||
__gtype_name__ = 'CmbSwitch'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::active', self.__on_notify)
|
||||
self.props.halign=Gtk.Align.START
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return 'True' if self.props.active else 'False'
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if value is not None:
|
||||
val = value.lower()
|
||||
|
||||
if type(val) == str:
|
||||
if val.lower() in {'1', 't', 'y', 'true', 'yes'}:
|
||||
self.props.active = True
|
||||
else:
|
||||
self.props.active = False
|
||||
else:
|
||||
self.props.active = bool(value)
|
||||
else:
|
||||
self.props.active = False
|
||||
|
||||
|
||||
class CmbEnumComboBox(Gtk.ComboBox):
|
||||
__gtype_name__ = 'CmbEnumComboBox'
|
||||
|
||||
info = GObject.Property(type=CmbTypeInfo, 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):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('changed', self.__on_changed)
|
||||
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
self.pack_start(renderer_text, True)
|
||||
self.add_attribute(renderer_text, "text", self.text_column)
|
||||
|
||||
self.props.id_column = self.text_column
|
||||
self.props.model = self.info.enum
|
||||
|
||||
def __on_changed(self, obj):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.active_id
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.props.active_id = None
|
||||
|
||||
for row in self.info.enum:
|
||||
enum_name = row[0]
|
||||
enum_nick = row[1]
|
||||
|
||||
# Always use nick as value
|
||||
if value == enum_name or value == enum_nick:
|
||||
self.props.active_id = enum_nick
|
||||
|
||||
|
||||
class CmbFlagsEntry(Gtk.Entry):
|
||||
__gtype_name__ = 'CmbFlagsEntry'
|
||||
|
||||
info = GObject.Property(type=CmbTypeInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
id_column = GObject.Property(type=int, default = 1, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
text_column = GObject.Property(type=int, default = 1, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
value_column = GObject.Property(type=int, default = 2, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.editable = False
|
||||
self.props.secondary_icon_name = 'document-edit-symbolic'
|
||||
|
||||
self.connect('icon-release', self.__on_icon_release)
|
||||
|
||||
self.__init_popover()
|
||||
|
||||
def __init_popover(self):
|
||||
self.flags = {}
|
||||
self._checks = {}
|
||||
self._popover = Gtk.Popover(relative_to=self)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
box.pack_start(Gtk.Label(label=f'<b>{self.info.type_id}</b>',
|
||||
use_markup=True),
|
||||
False, True, 4)
|
||||
box.pack_start(Gtk.Separator(), False, False, 0)
|
||||
sw = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER,
|
||||
propagate_natural_height=True,
|
||||
max_content_height=360)
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
sw.add(vbox)
|
||||
box.pack_start(sw, True, True, 0)
|
||||
|
||||
for row in self.info.flags:
|
||||
flag = row[self.text_column]
|
||||
flag_id = row[self.id_column]
|
||||
|
||||
check = Gtk.CheckButton(label=flag)
|
||||
check.connect('toggled', self.__on_check_toggled, flag_id)
|
||||
vbox.pack_start(check, False, True, 4)
|
||||
self._checks[flag_id] = check
|
||||
|
||||
box.show_all()
|
||||
self._popover.add(box)
|
||||
|
||||
def __on_check_toggled(self, check, flag_id):
|
||||
self.flags[flag_id] = check.props.active
|
||||
self.props.text = self.__to_string()
|
||||
self.notify('cmb-value')
|
||||
|
||||
def __on_icon_release(self, obj, pos, event):
|
||||
self._popover.popup()
|
||||
|
||||
def __to_string(self):
|
||||
retval = None
|
||||
for row in self.info.flags:
|
||||
flag_id = row[self.id_column]
|
||||
if self.flags.get(flag_id, False):
|
||||
retval = flag_id if retval is None else f'{retval} | {flag_id}'
|
||||
|
||||
return retval if retval is not None else ''
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.props.text = value if value is not None else ''
|
||||
|
||||
self.flags = {}
|
||||
for check in self._checks:
|
||||
self._checks[check].props.active = False
|
||||
|
||||
if value:
|
||||
tokens = [t.strip() for t in value.split('|')]
|
||||
|
||||
for row in self.info.flags:
|
||||
flag = row[self.text_column]
|
||||
flag_id = row[self.id_column]
|
||||
flag_name = row[0]
|
||||
flag_nick = row[1]
|
||||
|
||||
check = self._checks.get(flag_id, None)
|
||||
if check:
|
||||
val = flag_name in tokens or flag_nick in tokens
|
||||
check.props.active = val
|
||||
self.flags[flag_id] = val
|
||||
|
||||
|
||||
class CmbObjectChooser(Gtk.Entry):
|
||||
__gtype_name__ = 'CmbObjectChooser'
|
||||
|
||||
prop = GObject.Property(type=CmbProperty, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._value = None
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::text', self.__on_text_notify)
|
||||
|
||||
if self.prop.info.is_inline_object:
|
||||
self.connect("icon-press", self.__on_icon_pressed)
|
||||
self.prop.object.connect("property-changed",
|
||||
lambda o, p: self.__update_icons())
|
||||
self.__update_icons()
|
||||
else:
|
||||
self.props.placeholder_text = f'<{self.prop.info.type_id}>'
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
if self.prop.inline_object_id:
|
||||
return
|
||||
|
||||
obj = self.prop.project.get_object_by_name(self.prop.ui_id,
|
||||
self.props.text)
|
||||
self._value = obj.object_id if obj else None
|
||||
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self._value
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
prop = self.prop
|
||||
|
||||
self._value = int(value) if value else None
|
||||
|
||||
if self._value:
|
||||
obj = prop.project.get_object_by_id(prop.ui_id, self._value)
|
||||
self.props.text = obj.name if obj else ''
|
||||
else:
|
||||
self.props.text = ''
|
||||
|
||||
def __update_icons(self):
|
||||
if not self.prop.info.is_inline_object:
|
||||
return
|
||||
|
||||
if self.prop.inline_object_id:
|
||||
obj = self.prop.project.get_object_by_id(self.prop.ui_id,
|
||||
self.prop.inline_object_id)
|
||||
type = obj.type_id
|
||||
self.props.secondary_icon_name = 'edit-clear-symbolic'
|
||||
self.props.secondary_icon_tooltip_text = _('Clear property')
|
||||
self.props.placeholder_text = f'<inline {type}>'
|
||||
self.props.editable = False
|
||||
self.props.can_focus = False
|
||||
else:
|
||||
self.props.secondary_icon_name = 'list-add-symbolic'
|
||||
self.props.secondary_icon_tooltip_text = _('Add inline object')
|
||||
self.props.placeholder_text = f'<{self.prop.info.type_id}>'
|
||||
self.props.editable = True
|
||||
self.props.can_focus = True
|
||||
|
||||
def __get_name_for_object(self, obj):
|
||||
name = obj.name
|
||||
return obj.type_id.lower() if name is None else name
|
||||
|
||||
def __on_type_selected(self, popover, info):
|
||||
prop = self.prop
|
||||
prop.project.add_object(prop.ui_id,
|
||||
info.type_id,
|
||||
parent_id=prop.object_id,
|
||||
inline_property=prop.property_id)
|
||||
self.__update_icons()
|
||||
|
||||
def __on_icon_pressed(self, widget, icon_pos, event):
|
||||
prop = self.prop
|
||||
|
||||
if self.prop.inline_object_id:
|
||||
obj = prop.project.get_object_by_id(prop.ui_id, prop.inline_object_id)
|
||||
prop.project.remove_object(obj)
|
||||
self.__update_icons()
|
||||
else:
|
||||
chooser = CmbTypeChooserPopover(relative_to=self,
|
||||
parent_type_id=prop.object.type_id,
|
||||
derived_type_id=prop.info.type_id)
|
||||
chooser.project = prop.project
|
||||
chooser.connect('type-selected', self.__on_type_selected)
|
||||
chooser.popup()
|
||||
|
||||
|
||||
class CmbToplevelChooser(Gtk.ComboBoxText):
|
||||
__gtype_name__ = 'CmbToplevelChooser'
|
||||
|
||||
object = GObject.Property(type=CmbUI, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::object', self.__on_object_notify)
|
||||
self.connect('changed', self.__on_changed)
|
||||
|
||||
def _filter_func(self, model, iter, data):
|
||||
obj = model[iter][0]
|
||||
|
||||
if type(obj) == CmbObject:
|
||||
return obj.parent_id == 0
|
||||
|
||||
return False
|
||||
|
||||
def __on_object_notify(self, obj, pspec):
|
||||
self.remove_all()
|
||||
|
||||
if self.object is None:
|
||||
return
|
||||
|
||||
self.append('0', '(None)')
|
||||
|
||||
# TODO: add api to get toplevels in CmbUI
|
||||
# TODO: update model on project change
|
||||
for ui in self.object.project:
|
||||
if ui[0] == self.object:
|
||||
for child in ui.iterchildren():
|
||||
obj = child[0]
|
||||
name = obj.name or ''
|
||||
self.append(f'{obj.object_id}', f'{name}({obj.type_id})')
|
||||
|
||||
def __on_changed(self, combo):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def cmb_value(self):
|
||||
active_id = self.get_active_id()
|
||||
return int(active_id) if active_id is not None else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if self.object is None:
|
||||
return
|
||||
|
||||
self.set_active_id(str(value) if value is not None else None)
|
||||
|
||||
|
||||
class CmbChildTypeComboBox(Gtk.ComboBox):
|
||||
__gtype_name__ = 'CmbChildTypeComboBox'
|
||||
|
||||
object = GObject.Property(type=GObject.Object, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('changed', self.__on_changed)
|
||||
|
||||
# Model, store it in a Python variable to make sure we hold a reference
|
||||
# First column is the ID and the second is if you can select the child or not
|
||||
self.__model = Gtk.ListStore(str, bool)
|
||||
self.props.model = self.__model
|
||||
self.props.id_column = 0
|
||||
|
||||
# Simple cell renderer
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
self.pack_start(renderer_text, True)
|
||||
self.add_attribute(renderer_text, "text", 0)
|
||||
self.add_attribute(renderer_text, "sensitive", 1)
|
||||
|
||||
self.__populate_model()
|
||||
|
||||
def __populate_model(self):
|
||||
self.__model.clear()
|
||||
|
||||
parent = self.object.parent
|
||||
if parent is None:
|
||||
return
|
||||
|
||||
self.__model.append([None, True])
|
||||
|
||||
pinfo = parent.info
|
||||
while pinfo:
|
||||
if pinfo.child_types:
|
||||
for t in pinfo.child_types:
|
||||
self.__model.append([t, True])
|
||||
pinfo = pinfo.parent
|
||||
|
||||
def __on_changed(self, obj):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.active_id
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.props.active_id = value
|
||||
|
||||
|
||||
class CmbIconNameEntry(CmbEntry):
|
||||
__gtype_name__ = 'CmbIconNameEntry'
|
||||
|
||||
object = GObject.Property(type=GObject.Object, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
standard_only = GObject.Property(type=bool, default=True, flags = GObject.ParamFlags.READWRITE)
|
||||
symbolic_only = GObject.Property(type=bool, default=False, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
COL_ICON_NAME = 0
|
||||
COL_MARKUP = 1
|
||||
COL_CONTEXT = 2
|
||||
COL_STANDARD = 3
|
||||
COL_SYMBOLIC = 4
|
||||
COL_STANDARD_SYMBOLIC = 5
|
||||
COL_PIXBUF = 6
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._filters = {}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
GObject.Object.bind_property(self, 'cmb-value',
|
||||
self, 'primary-icon-name',
|
||||
GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
self.props.secondary_icon_name = 'document-edit-symbolic'
|
||||
self.connect("icon-press", self.__on_icon_pressed)
|
||||
|
||||
# Model, store it in a Python variable to make sure we hold a reference
|
||||
# icon-name markup context standard symbolic standard_symbolic pixbuf
|
||||
self.__model = Gtk.ListStore(str, str, str, bool, bool, bool, GdkPixbuf.Pixbuf)
|
||||
|
||||
# Completion
|
||||
self.__completion = Gtk.EntryCompletion()
|
||||
self.__completion.props.model = self.__model
|
||||
self.__completion.props.text_column = self.COL_ICON_NAME
|
||||
self.__completion.props.inline_completion = True
|
||||
self.__completion.props.inline_selection = True
|
||||
self.__completion.props.popup_set_width = True
|
||||
self.props.completion = self.__completion
|
||||
|
||||
self.__completion.set_match_func(lambda o, key, iter, d: key in self.__model[iter][0], None)
|
||||
|
||||
# Icon
|
||||
renderer_text = Gtk.CellRendererPixbuf(xpad=4)
|
||||
self.__completion.pack_start(renderer_text, False)
|
||||
self.__completion.add_attribute(renderer_text, 'icon-name', 0)
|
||||
|
||||
# Icon Name
|
||||
renderer_text = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END)
|
||||
self.__completion.pack_start(renderer_text, False)
|
||||
self.__completion.add_attribute(renderer_text, 'markup', 1)
|
||||
|
||||
self.__populate_model()
|
||||
|
||||
def __populate_model(self):
|
||||
iconlist = []
|
||||
|
||||
self.__model.clear()
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
|
||||
for context in theme.list_contexts():
|
||||
for icon in theme.list_icons(context):
|
||||
iconlist.append((icon, context, icon in standard_icon_names))
|
||||
|
||||
for icon, context, standard in sorted(iconlist, key=lambda i: i[0].lower()):
|
||||
if icon.endswith('.symbolic'):
|
||||
continue
|
||||
|
||||
info = theme.lookup_icon(icon, 32, Gtk.IconLookupFlags.FORCE_SIZE)
|
||||
symbolic = info.is_symbolic()
|
||||
|
||||
if not os.path.exists(info.get_filename()):
|
||||
continue
|
||||
|
||||
standard_symbolic = symbolic and icon.removesuffix('-symbolic') in standard_icon_names
|
||||
|
||||
iter = self.__model.append([icon,
|
||||
icon if standard else f'<i>{icon}</i>',
|
||||
context,
|
||||
standard,
|
||||
symbolic,
|
||||
standard_symbolic,
|
||||
None])
|
||||
info.load_icon_async(None, self.__load_icon_finish, iter)
|
||||
|
||||
def __load_icon_finish(self, info, res, data):
|
||||
self.__model[data][6] = info.load_icon_finish(res)
|
||||
|
||||
def __model_filter_func(self, model, iter, data):
|
||||
if self.standard_only and self.symbolic_only:
|
||||
if not model[iter][self.COL_STANDARD_SYMBOLIC]:
|
||||
return False
|
||||
elif self.standard_only and not model[iter][self.COL_STANDARD]:
|
||||
return False
|
||||
elif self.symbolic_only and not model[iter][self.COL_SYMBOLIC]:
|
||||
return False
|
||||
|
||||
if data == 'cmb_all':
|
||||
return True
|
||||
|
||||
return model[iter][self.COL_CONTEXT] == data
|
||||
|
||||
def __on_check_active_notify(self, button, pspec):
|
||||
for filter in self._filters:
|
||||
self._filters[filter].refilter()
|
||||
|
||||
def __on_view_selection_changed(self, view):
|
||||
selection = view.get_selected_items()
|
||||
|
||||
if selection:
|
||||
model = view.props.model
|
||||
iter = model.get_iter(selection[0])
|
||||
self.cmb_value = model[iter][self.COL_ICON_NAME]
|
||||
else:
|
||||
self.cmb_value = None
|
||||
|
||||
def __on_icon_pressed(self, widget, icon_pos, event):
|
||||
# Create popover with icon chooser
|
||||
popover = Gtk.Popover(relative_to=self)
|
||||
hbox = Gtk.Box(visible=True)
|
||||
vbox = Gtk.Box(visible=True,
|
||||
orientation=Gtk.Orientation.VERTICAL,
|
||||
vexpand=True)
|
||||
stack = Gtk.Stack(visible=True,
|
||||
transition_type=Gtk.StackTransitionType.CROSSFADE)
|
||||
sidebar = Gtk.StackSidebar(visible=True,
|
||||
stack=stack,
|
||||
vexpand=True)
|
||||
vbox.pack_start(sidebar, True, True, 4)
|
||||
hbox.pack_start(vbox, False, True, 4)
|
||||
hbox.pack_start(stack, True, True, 4)
|
||||
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
|
||||
sorted_contexts = sorted(theme.list_contexts())
|
||||
sorted_contexts.insert(0, 'cmb_all')
|
||||
|
||||
# Add one icon view per context
|
||||
for context in sorted_contexts:
|
||||
filter = self._filters.get(context, None)
|
||||
|
||||
if filter is None:
|
||||
self._filters[context] = Gtk.TreeModelFilter(child_model=self.__model)
|
||||
filter = self._filters[context]
|
||||
filter.set_visible_func(self.__model_filter_func, data=context)
|
||||
filter.refilter()
|
||||
|
||||
sw = Gtk.ScrolledWindow(visible=True,
|
||||
min_content_width=600,
|
||||
min_content_height=480)
|
||||
view = Gtk.IconView(visible=True,
|
||||
model=filter,
|
||||
pixbuf_column=self.COL_PIXBUF,
|
||||
text_column=self.COL_ICON_NAME)
|
||||
view.connect('selection-changed', self.__on_view_selection_changed)
|
||||
sw.add(view)
|
||||
stack.add_titled(sw, context,
|
||||
standard_icon_context.get(context, context))
|
||||
|
||||
# Add filters
|
||||
for prop, label in [('standard_only', _('Only standard')),
|
||||
('symbolic_only', _('Only symbolic'))]:
|
||||
check = Gtk.CheckButton(visible=True,
|
||||
label=label)
|
||||
GObject.Object.bind_property(self, prop,
|
||||
check, 'active',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
check.connect_after('notify::active', self.__on_check_active_notify)
|
||||
vbox.pack_start(check, False, True, 4)
|
||||
|
||||
popover.get_style_context().add_class("cmb-icon-chooser")
|
||||
popover.add(hbox)
|
||||
popover.popup()
|
||||
|
||||
|
||||
class CmbColorEntry(Gtk.Box):
|
||||
__gtype_name__ = 'CmbColorEntry'
|
||||
|
||||
use_color = GObject.Property(type=bool, default=False, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__ignore_notify = False
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.entry = Gtk.Entry(visible=True,
|
||||
width_chars=14,
|
||||
editable=False)
|
||||
self.button = Gtk.ColorButton(visible=True,
|
||||
use_alpha=True)
|
||||
|
||||
self.__default_color = self.button.props.color
|
||||
self.__default_rgba = self.button.props.rgba
|
||||
|
||||
self.pack_start(self.entry, False, True, 0)
|
||||
self.pack_start(self.button, False, True, 4)
|
||||
|
||||
self.button.connect('color-set', self.__on_color_set)
|
||||
self.entry.connect("icon-press", self.__on_entry_icon_pressed)
|
||||
|
||||
def __on_entry_icon_pressed(self, widget, icon_pos, event):
|
||||
self.cmb_value = None
|
||||
|
||||
def __on_color_set(self, obj):
|
||||
if self.use_color:
|
||||
self.cmb_value = self.button.props.color.to_string() if self.button.props.color else None
|
||||
else:
|
||||
self.cmb_value = self.button.props.rgba.to_string() if self.button.props.rgba else None
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.entry.props.text if self.entry.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if value:
|
||||
self.entry.props.text = value
|
||||
self.entry.props.secondary_icon_name = 'edit-clear-symbolic'
|
||||
else:
|
||||
self.entry.props.text = ''
|
||||
self.entry.props.secondary_icon_name = None
|
||||
|
||||
valid = False
|
||||
|
||||
if self.use_color:
|
||||
color = None
|
||||
if value:
|
||||
valid, color = Gdk.Color.parse(value)
|
||||
|
||||
self.button.set_color(color if valid else self.__default_color)
|
||||
else:
|
||||
rgba = Gdk.RGBA()
|
||||
|
||||
if value:
|
||||
valid = rgba.parse(value)
|
||||
|
||||
self.button.set_rgba(rgba if valid else self.__default_rgba)
|
||||
|
@ -1,66 +0,0 @@
|
||||
#
|
||||
# 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)
|
||||
|
@ -1,270 +0,0 @@
|
||||
#
|
||||
# CmbPropertyLabel
|
||||
#
|
||||
# Copyright (C) 2023-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_property import CmbProperty
|
||||
from .cmb_layout_property import CmbLayoutProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from .control import CmbObjectChooser, CmbFlagsEntry
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbPropertyLabel(Gtk.Button):
|
||||
__gtype_name__ = "CmbPropertyLabel"
|
||||
|
||||
prop = GObject.Property(type=CmbProperty, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
layout_prop = GObject.Property(
|
||||
type=CmbLayoutProperty, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
bindable = GObject.Property(type=bool, default=True, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if not self.prop and not self.layout_prop:
|
||||
raise Exception("CmbPropertyLabel requires prop or layout_prop to be set")
|
||||
return
|
||||
|
||||
self.props.focus_on_click = False
|
||||
self.label = Gtk.Label(halign=Gtk.Align.START, valign=Gtk.Align.CENTER)
|
||||
box = Gtk.Box()
|
||||
|
||||
# Update label status
|
||||
if self.prop:
|
||||
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL)
|
||||
box.append(self.bind_icon)
|
||||
|
||||
# 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.prop.connect("notify::value", lambda o, p: self.__update_property_label())
|
||||
|
||||
if self.bindable:
|
||||
self.connect("clicked", self.__on_bind_button_clicked)
|
||||
|
||||
if self.layout_prop:
|
||||
self.bind_icon = None
|
||||
self.label.props.label = self.layout_prop.property_id
|
||||
self.__update_layout_property_label()
|
||||
self.layout_prop.connect("notify::value", lambda o, p: self.__update_layout_property_label())
|
||||
|
||||
box.append(self.label)
|
||||
self.set_child(box)
|
||||
|
||||
def __update_label(self, prop):
|
||||
if prop.value != prop.info.default_value:
|
||||
self.add_css_class("modified")
|
||||
else:
|
||||
self.remove_css_class("modified")
|
||||
|
||||
msg = prop.version_warning
|
||||
self.set_tooltip_text(msg)
|
||||
|
||||
if msg:
|
||||
self.add_css_class("warning")
|
||||
else:
|
||||
self.remove_css_class("warning")
|
||||
|
||||
def __update_layout_property_label(self):
|
||||
self.__update_label(self.layout_prop)
|
||||
|
||||
def __update_property_label(self):
|
||||
self.__update_label(self.prop)
|
||||
|
||||
if not self.bindable:
|
||||
return
|
||||
|
||||
if self.prop.bind_property_id:
|
||||
self.bind_icon.props.icon_name = "binded-symbolic"
|
||||
self.remove_css_class("hidden")
|
||||
else:
|
||||
self.bind_icon.props.icon_name = "bind-symbolic"
|
||||
self.add_css_class("hidden")
|
||||
|
||||
def __on_object_editor_notify(self, object_editor, pspec, property_editor):
|
||||
object_id = object_editor.cmb_value
|
||||
if object_id:
|
||||
property_editor.object = self.prop.project.get_object_by_id(self.prop.ui_id, int(object_id))
|
||||
|
||||
def __on_property_editor_changed(self, combo):
|
||||
bind_source, bind_property = self.__find_bind_source_property(combo.object.object_id, combo.props.active_id)
|
||||
self.prop.bind_property = bind_property
|
||||
self.__update_property_label()
|
||||
|
||||
def __find_bind_source_property(self, bind_source_id, bind_property_id):
|
||||
bind_source = self.prop.project.get_object_by_id(self.prop.ui_id, bind_source_id) if bind_source_id else None
|
||||
bind_property = bind_source.properties_dict.get(bind_property_id, None) if bind_source else None
|
||||
|
||||
return bind_source, bind_property
|
||||
|
||||
def __on_clear_clicked(self, button, popover):
|
||||
self.prop.bind_property = None
|
||||
self.prop.bind_flags = None
|
||||
self.__update_property_label()
|
||||
popover.popdown()
|
||||
|
||||
def __on_close_clicked(self, button, popover):
|
||||
popover.popdown()
|
||||
|
||||
def __on_bind_button_clicked(self, button):
|
||||
popover = Gtk.Popover(position=Gtk.PositionType.LEFT, css_classes=["cmb-binding-popover"])
|
||||
popover.set_parent(self)
|
||||
|
||||
label = Gtk.Label(label="<b>Property Binding</b>", use_markup=True, halign=Gtk.Align.START, hexpand=True)
|
||||
close = Gtk.Button(icon_name="window-close-symbolic", halign=Gtk.Align.END, css_classes=["close"])
|
||||
close.connect("clicked", self.__on_close_clicked, popover)
|
||||
|
||||
box = Gtk.Box(hexpand=True)
|
||||
box.append(label)
|
||||
box.append(close)
|
||||
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=6, column_spacing=6)
|
||||
grid.attach(box, 0, 0, 2, 1)
|
||||
|
||||
# Get bind property to initialize inputs
|
||||
bind_source, bind_property = self.__find_bind_source_property(self.prop.bind_source_id, self.prop.bind_property_id)
|
||||
|
||||
# Create Property editor
|
||||
property_editor = CmbPropertyChooser(object=bind_source, target_info=self.prop.info)
|
||||
property_editor.connect("changed", self.__on_property_editor_changed)
|
||||
|
||||
# Update active_id after letting the object populate the properties
|
||||
if bind_property:
|
||||
property_editor.props.active_id = bind_property.property_id
|
||||
|
||||
# Object editor (it does not set the object directly to CmbProperty, just choose the object in the prop chooser)
|
||||
object_editor = CmbObjectChooser(parent=self.prop.object, cmb_value=bind_source.object_id if bind_source else 0)
|
||||
object_editor.connect("notify::cmb-value", self.__on_object_editor_notify, property_editor)
|
||||
|
||||
# Flags editor
|
||||
binding_flags_info = self.prop.project.type_info.get("GBindingFlags", None)
|
||||
flags_editor = CmbFlagsEntry(info=binding_flags_info)
|
||||
GObject.Object.bind_property(
|
||||
self.prop,
|
||||
"bind_flags",
|
||||
flags_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
i = 1
|
||||
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
|
||||
label = Gtk.Label(label=prop_label, xalign=0)
|
||||
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
clear = Gtk.Button(label=_("Clear"), halign=Gtk.Align.END)
|
||||
clear.connect("clicked", self.__on_clear_clicked, popover)
|
||||
grid.attach(clear, 0, i, 2, 1)
|
||||
|
||||
object_editor.grab_focus()
|
||||
|
||||
popover.set_child(grid)
|
||||
popover.popup()
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbPropertyLabel, "CmbPropertyLabel")
|
||||
|
||||
|
||||
class CmbPropertyChooser(Gtk.ComboBoxText):
|
||||
__gtype_name__ = "CmbPropertyChooser"
|
||||
|
||||
object = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE)
|
||||
target_info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__populate()
|
||||
self.connect("notify::object", self.__on_object_notify)
|
||||
|
||||
def __on_object_notify(self, obj, pspec):
|
||||
self.__populate()
|
||||
|
||||
def __populate(self):
|
||||
self.remove_all()
|
||||
|
||||
if self.object is None:
|
||||
return
|
||||
|
||||
target_info = self.target_info
|
||||
target_type = target_info.type_id
|
||||
target_type_info = self.object.project.type_info.get(target_type, None)
|
||||
target_is_object = target_info.is_object
|
||||
target_is_iface = target_type_info.parent_id == "interface" if target_type_info else False
|
||||
|
||||
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
|
||||
info = prop.info
|
||||
|
||||
if info.is_a11y:
|
||||
continue
|
||||
|
||||
# Ignore construct only properties
|
||||
if info.construct_only:
|
||||
continue
|
||||
|
||||
source_type_info = self.object.project.type_info.get(info.type_id, None)
|
||||
source_is_object = info.is_object
|
||||
source_is_iface = source_type_info.parent_id == "interface" if source_type_info else False
|
||||
|
||||
if target_is_object or target_is_iface:
|
||||
# Ignore non object properties
|
||||
if not source_is_object and not source_is_iface:
|
||||
continue
|
||||
|
||||
# Ignore object properties of a different type
|
||||
if source_type_info and not source_type_info.is_a(target_info.type_id):
|
||||
continue
|
||||
elif source_is_object or source_is_iface:
|
||||
continue
|
||||
|
||||
# Enums and Flags has to be the same type
|
||||
if target_type_info and target_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
|
||||
continue
|
||||
|
||||
if source_type_info and source_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
|
||||
continue
|
||||
|
||||
compatible = info.type_id == target_type
|
||||
|
||||
if not compatible:
|
||||
try:
|
||||
gtype_id = GObject.type_from_name(info.type_id)
|
||||
gtarget_id = GObject.type_from_name(target_type)
|
||||
if gtype_id and gtarget_id:
|
||||
compatible = GObject.Value.type_compatible(gtype_id, gtarget_id)
|
||||
if not compatible:
|
||||
compatible = GObject.Value.type_transformable(gtype_id, gtarget_id)
|
||||
except Exception as e: # noqa F841
|
||||
self.append(prop.property_id, prop.property_id + "*")
|
||||
continue
|
||||
|
||||
if compatible:
|
||||
self.append(prop.property_id, prop.property_id)
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbSignalEditor - Cambalache Signal Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,17 +20,17 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk, Pango
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from . import utils
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Col(Enum):
|
||||
SIGNAL = 0
|
||||
OWNER_ID = 1
|
||||
@ -41,12 +41,11 @@ class Col(Enum):
|
||||
SWAP = 6
|
||||
AFTER = 7
|
||||
INFO = 8
|
||||
WARNING_MESSAGE = 9
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_signal_editor.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_signal_editor.ui')
|
||||
class CmbSignalEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbSignalEditor"
|
||||
__gtype_name__ = 'CmbSignalEditor'
|
||||
|
||||
treeview = Gtk.Template.Child()
|
||||
treestore = Gtk.Template.Child()
|
||||
@ -66,11 +65,21 @@ class CmbSignalEditor(Gtk.Box):
|
||||
self._object = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.signal_id_column.set_cell_data_func(self.signal_id, self.__signal_id_data_func, None)
|
||||
self.handler_column.set_cell_data_func(self.handler, self.__data_func, Col.HANDLER.value)
|
||||
self.user_data_column.set_cell_data_func(self.user_data, self.__data_func, Col.USER_DATA.value)
|
||||
self.swap_column.set_cell_data_func(self.swap, self.__data_func, Col.SWAP.value)
|
||||
self.after_column.set_cell_data_func(self.after, self.__data_func, Col.AFTER.value)
|
||||
self.signal_id_column.set_cell_data_func(self.signal_id,
|
||||
self.__signal_id_data_func,
|
||||
None)
|
||||
self.handler_column.set_cell_data_func(self.handler,
|
||||
self.__data_func,
|
||||
Col.HANDLER.value)
|
||||
self.user_data_column.set_cell_data_func(self.user_data,
|
||||
self.__data_func,
|
||||
Col.USER_DATA.value)
|
||||
self.swap_column.set_cell_data_func(self.swap,
|
||||
self.__data_func,
|
||||
Col.SWAP.value)
|
||||
self.after_column.set_cell_data_func(self.after,
|
||||
self.__data_func,
|
||||
Col.AFTER.value)
|
||||
|
||||
@GObject.Property(type=CmbObject)
|
||||
def object(self):
|
||||
@ -78,21 +87,19 @@ class CmbSignalEditor(Gtk.Box):
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if self._object:
|
||||
if self._object is not None:
|
||||
self.treestore.clear()
|
||||
self._object.disconnect_by_func(self.__on_signal_added)
|
||||
self._object.disconnect_by_func(self.__on_signal_removed)
|
||||
self._object.disconnect_by_func(self.__on_signal_changed)
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj:
|
||||
if obj is not None:
|
||||
self.__populate_treestore()
|
||||
self._object.connect("signal-added", self.__on_signal_added)
|
||||
self._object.connect("signal-removed", self.__on_signal_removed)
|
||||
self._object.connect("signal-changed", self.__on_signal_changed)
|
||||
self._object.connect('signal-added', self.__on_signal_added)
|
||||
self._object.connect('signal-removed', self.__on_signal_removed)
|
||||
|
||||
@Gtk.Template.Callback("on_handler_edited")
|
||||
@Gtk.Template.Callback('on_handler_edited')
|
||||
def __on_handler_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
@ -109,14 +116,14 @@ class CmbSignalEditor(Gtk.Box):
|
||||
else:
|
||||
self._object.remove_signal(signal)
|
||||
|
||||
@Gtk.Template.Callback("on_detail_edited")
|
||||
@Gtk.Template.Callback('on_detail_edited')
|
||||
def __on_detail_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
if signal is not None:
|
||||
if len(new_text) > 0:
|
||||
tokens = new_text.split("::")
|
||||
tokens = new_text.split('::')
|
||||
if len(tokens) == 2 and len(tokens[1]) > 0:
|
||||
signal.detail = tokens[1]
|
||||
else:
|
||||
@ -126,7 +133,7 @@ class CmbSignalEditor(Gtk.Box):
|
||||
|
||||
self.treestore[iter_][Col.DETAIL.value] = signal.detail
|
||||
|
||||
@Gtk.Template.Callback("on_user_data_edited")
|
||||
@Gtk.Template.Callback('on_user_data_edited')
|
||||
def __on_user_data_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
@ -138,20 +145,16 @@ class CmbSignalEditor(Gtk.Box):
|
||||
if data_obj:
|
||||
signal.user_data = data_obj.object_id
|
||||
name = data_obj.name
|
||||
signal.swap = True
|
||||
else:
|
||||
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
|
||||
else:
|
||||
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')
|
||||
def __on_swap_toggled(self, renderer, path):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
@ -160,7 +163,7 @@ class CmbSignalEditor(Gtk.Box):
|
||||
signal.swap = not self.treestore[iter_][Col.SWAP.value]
|
||||
self.treestore[iter_][Col.SWAP.value] = signal.swap
|
||||
|
||||
@Gtk.Template.Callback("on_after_toggled")
|
||||
@Gtk.Template.Callback('on_after_toggled')
|
||||
def __on_after_toggled(self, renderer, path):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
@ -173,23 +176,19 @@ class CmbSignalEditor(Gtk.Box):
|
||||
for row in self.treestore:
|
||||
if row[Col.OWNER_ID.value] == signal.owner_id:
|
||||
for child in row.iterchildren():
|
||||
if child[Col.SIGNAL.value] is None and child[Col.SIGNAL_ID.value] == signal.signal_id:
|
||||
self.treestore.insert_before(
|
||||
row.iter,
|
||||
child.iter,
|
||||
(
|
||||
signal,
|
||||
signal.owner_id,
|
||||
signal.signal_id,
|
||||
signal.detail,
|
||||
signal.handler,
|
||||
str(signal.user_data),
|
||||
signal.swap,
|
||||
signal.after,
|
||||
child[Col.INFO.value],
|
||||
None,
|
||||
),
|
||||
)
|
||||
if child[Col.SIGNAL.value] is None and \
|
||||
child[Col.SIGNAL_ID.value] == signal.signal_id:
|
||||
self.treestore.insert_before(row.iter,
|
||||
child.iter,
|
||||
(signal,
|
||||
signal.owner_id,
|
||||
signal.signal_id,
|
||||
signal.detail,
|
||||
signal.handler,
|
||||
str(signal.user_data),
|
||||
signal.swap,
|
||||
signal.after,
|
||||
child[Col.INFO.value]))
|
||||
break
|
||||
|
||||
def __on_signal_removed(self, obj, signal):
|
||||
@ -199,33 +198,37 @@ class CmbSignalEditor(Gtk.Box):
|
||||
self.treestore.remove(child.iter)
|
||||
return
|
||||
|
||||
def __on_signal_changed(self, obj, signal):
|
||||
for row in self.treestore:
|
||||
for child in row.iterchildren():
|
||||
if child[Col.SIGNAL.value] == signal:
|
||||
child[Col.DETAIL.value] = signal.detail
|
||||
child[Col.HANDLER.value] = signal.handler
|
||||
child[Col.USER_DATA.value] = str(signal.user_data)
|
||||
child[Col.SWAP.value] = signal.swap
|
||||
child[Col.AFTER.value] = signal.after
|
||||
return
|
||||
|
||||
def __populate_from_type(self, info, target):
|
||||
def __populate_from_type(self, info):
|
||||
if len(info.signals) == 0:
|
||||
return None
|
||||
|
||||
parent = self.treestore.append(None, (None, info.type_id, info.type_id, None, None, None, False, False, None, None))
|
||||
parent = self.treestore.append(None,
|
||||
(None,
|
||||
info.type_id,
|
||||
info.type_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
None))
|
||||
for signal_id in info.signals:
|
||||
signal = info.signals[signal_id]
|
||||
msg = utils.get_version_warning(target, signal.version, signal.deprecated_version, signal.signal_id)
|
||||
self.treestore.append(parent, (None, info.type_id, signal.signal_id, None, None, None, False, False, signal, msg))
|
||||
self.treestore.append(parent,
|
||||
(None,
|
||||
info.type_id,
|
||||
signal.signal_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
signal))
|
||||
return parent
|
||||
|
||||
def __populate_treestore(self):
|
||||
target = self._object.ui.get_target(self._object.info.library_id)
|
||||
|
||||
# Populate object type signals
|
||||
parent = self.__populate_from_type(self._object.info, target)
|
||||
parent = self.__populate_from_type(self._object.info)
|
||||
|
||||
# Expand object type signals
|
||||
if parent:
|
||||
@ -235,7 +238,7 @@ class CmbSignalEditor(Gtk.Box):
|
||||
for type_id in self._object.info.hierarchy:
|
||||
info = self._object.project.type_info.get(type_id, None)
|
||||
if info:
|
||||
self.__populate_from_type(info, target)
|
||||
self.__populate_from_type(info)
|
||||
|
||||
# Populate object signals
|
||||
for signal in self._object.signals:
|
||||
@ -244,20 +247,17 @@ class CmbSignalEditor(Gtk.Box):
|
||||
def __signal_id_data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
info = tree_model[iter_][Col.INFO.value]
|
||||
signal_id = tree_model[iter_][Col.SIGNAL_ID.value]
|
||||
warning = tree_model[iter_][Col.WARNING_MESSAGE.value]
|
||||
|
||||
if info and info.detailed:
|
||||
if info is not None and info.detailed:
|
||||
detail = tree_model[iter_][Col.DETAIL.value]
|
||||
signal = tree_model[iter_][Col.SIGNAL.value]
|
||||
|
||||
cell.props.editable = False if signal is None else True
|
||||
cell.props.text = f"{signal_id}::{detail}" if detail is not None else signal_id
|
||||
cell.props.text = f'{signal_id}::{detail}' if detail is not None else signal_id
|
||||
else:
|
||||
cell.props.editable = False
|
||||
cell.props.text = signal_id
|
||||
|
||||
cell.props.underline = Pango.Underline.ERROR if warning else Pango.Underline.NONE
|
||||
|
||||
def __data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
info = tree_model[iter_][Col.INFO.value]
|
||||
signal = tree_model[iter_][Col.SIGNAL.value]
|
||||
@ -277,6 +277,6 @@ class CmbSignalEditor(Gtk.Box):
|
||||
user_data = signal.user_data
|
||||
if user_data:
|
||||
data_obj = self._object.project.get_object_by_id(signal.ui_id, user_data)
|
||||
cell.props.text = data_obj.name if data_obj else ""
|
||||
cell.props.text = data_obj.name if data_obj else ''
|
||||
else:
|
||||
cell.props.text = ""
|
||||
cell.props.text = ''
|
||||
|
@ -1,13 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_signal_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkEntryCompletion" id="handler_entrycompletion">
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
@ -15,38 +12,53 @@
|
||||
</object>
|
||||
<object class="GtkTreeStore" id="treestore">
|
||||
<columns>
|
||||
<!-- column-name signal -->
|
||||
<column type="GObject"/>
|
||||
<!-- column-name owner_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name signal_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name detail -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name handler -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name user_data -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name swap -->
|
||||
<column type="gboolean"/>
|
||||
<!-- column-name after -->
|
||||
<column type="gboolean"/>
|
||||
<!-- column-name info -->
|
||||
<column type="GObject"/>
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<template class="CmbSignalEditor" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="treeview">
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">treestore</property>
|
||||
<property name="tooltip-column">9</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="signal_id_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min-width">64</property>
|
||||
<property name="resizable">1</property>
|
||||
<property name="title" translatable="yes">Signal</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="signal_id">
|
||||
<signal name="edited" handler="on_detail_edited"/>
|
||||
<signal name="edited" handler="on_detail_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
@ -55,17 +67,16 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="handler_column">
|
||||
<property name="expand">1</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="min-width">64</property>
|
||||
<property name="resizable">1</property>
|
||||
<property name="title" translatable="yes">Handler</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="handler">
|
||||
<property name="editable">1</property>
|
||||
<property name="editable">True</property>
|
||||
<property name="placeholder-text"><Enter callback></property>
|
||||
<signal name="edited" handler="on_handler_edited"/>
|
||||
<signal name="edited" handler="on_handler_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
@ -77,11 +88,10 @@
|
||||
<property name="title" translatable="yes">Data</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="user_data">
|
||||
<property name="editable">1</property>
|
||||
<property name="editable">True</property>
|
||||
<property name="placeholder-text"><object></property>
|
||||
<signal name="edited" handler="on_user_data_edited"/>
|
||||
<signal name="edited" handler="on_user_data_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
@ -93,9 +103,8 @@
|
||||
<property name="title" translatable="yes">Swap</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="swap">
|
||||
<signal name="toggled" handler="on_swap_toggled"/>
|
||||
<signal name="toggled" handler="on_swap_toggled" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="active">6</attribute>
|
||||
</attributes>
|
||||
@ -107,9 +116,8 @@
|
||||
<property name="title" translatable="yes">After</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="after">
|
||||
<signal name="toggled" handler="on_after_toggled"/>
|
||||
<signal name="toggled" handler="on_after_toggled" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="active">7</attribute>
|
||||
</attributes>
|
||||
@ -117,10 +125,13 @@
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -20,30 +20,31 @@
|
||||
# Authors:
|
||||
# Philipp Unger <philipp.unger.1988@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import Gtk
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_translatable_widget import CmbTranslatableWidget
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbTranslatablePopover(Gtk.Popover):
|
||||
__gtype_name__ = "CmbTranslatablePopover"
|
||||
__gtype_name__ = 'CmbTranslatablePopover'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, target, **kwargs):
|
||||
self._object = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
box.append(Gtk.Label(label=_("<b>Translation</b>"), use_markup=True))
|
||||
box.append(Gtk.Separator())
|
||||
box.pack_start(Gtk.Label(label=_(f'<b>Translation</b>'),
|
||||
use_markup=True),
|
||||
False, True, 4)
|
||||
box.pack_start(Gtk.Separator(), False, False, 0)
|
||||
|
||||
self._translation = CmbTranslatableWidget()
|
||||
box.append(self._translation)
|
||||
|
||||
self.set_child(box)
|
||||
|
||||
def bind_properties(self, target):
|
||||
self._translation.bind_properties(target)
|
||||
box.pack_start(self._translation, False, False, 0)
|
||||
|
||||
box.show_all()
|
||||
self.add(box)
|
@ -20,15 +20,16 @@
|
||||
# Authors:
|
||||
# Philipp Unger <philipp.unger.1988@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/control/cmb_translatable_widget.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_translatable_widget.ui')
|
||||
class CmbTranslatableWidget(Gtk.Box):
|
||||
__gtype_name__ = "CmbTranslatableWidget"
|
||||
__gtype_name__ = 'CmbTranslatableWidget'
|
||||
|
||||
buffer_text = Gtk.Template.Child()
|
||||
check_button_translatable = Gtk.Template.Child()
|
||||
@ -39,47 +40,42 @@ class CmbTranslatableWidget(Gtk.Box):
|
||||
self._object = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.buffer_text.connect("notify::text", self.__on_text_notify)
|
||||
self.check_button_translatable.connect("toggled", self.__on_translatable_notify)
|
||||
self.buffer_context.connect("notify::text", self.__on_context_notify)
|
||||
self.buffer_comments.connect("notify::text", self.__on_comments_notify)
|
||||
self.buffer_text.connect('notify::text', self.__on_text_notify)
|
||||
self.check_button_translatable.connect('toggled', self.__on_translatable_notify)
|
||||
self.buffer_context.connect('notify::text', self.__on_context_notify)
|
||||
self.buffer_comments.connect('notify::text', self.__on_comments_notify)
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify("cmb-value")
|
||||
self.notify('cmb-value')
|
||||
|
||||
def __on_translatable_notify(self, data):
|
||||
self.notify("cmb-translatable")
|
||||
self.notify('cmb-translatable')
|
||||
|
||||
def __on_context_notify(self, obj, pspec):
|
||||
self.notify("cmb-context")
|
||||
self.notify('cmb-context')
|
||||
|
||||
def __on_comments_notify(self, obj, pspec):
|
||||
self.notify("cmb-comment")
|
||||
self.notify('cmb-comment')
|
||||
|
||||
def bind_properties(self, target):
|
||||
for source_prop, target_prop in [
|
||||
("value", "cmb-value"),
|
||||
("translatable", "cmb-translatable"),
|
||||
("translation_context", "cmb-context"),
|
||||
("translation_comments", "cmb-comment"),
|
||||
]:
|
||||
GObject.Object.bind_property(
|
||||
target,
|
||||
source_prop,
|
||||
self,
|
||||
target_prop,
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
for source_prop, target_prop in [('value', 'cmb-value'),
|
||||
('translatable', 'cmb-translatable'),
|
||||
('translation_context', 'cmb-context'),
|
||||
('translation_comments', 'cmb-comment')]:
|
||||
GObject.Object.bind_property(target, source_prop,
|
||||
self, target_prop,
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.buffer_text.props.text if self.buffer_text.props.text != "" else None
|
||||
return self.buffer_text.props.text if self.buffer_text.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
self.buffer_text.props.text = value if value is not None else ""
|
||||
self.buffer_text.props.text = value if value is not None else ''
|
||||
|
||||
@GObject.Property(type=bool, default=False)
|
||||
@GObject.Property(type=bool, default = False)
|
||||
def cmb_translatable(self):
|
||||
return self.check_button_translatable.props.active
|
||||
|
||||
@ -89,16 +85,16 @@ class CmbTranslatableWidget(Gtk.Box):
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_context(self):
|
||||
return self.buffer_context.props.text if self.buffer_context.props.text != "" else None
|
||||
return self.buffer_context.props.text if self.buffer_context.props.text != '' else None
|
||||
|
||||
@cmb_context.setter
|
||||
def _set_cmb_context(self, value):
|
||||
self.buffer_context.props.text = value if value is not None else ""
|
||||
self.buffer_context.props.text = value if value is not None else ''
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_comment(self):
|
||||
return self.buffer_comments.props.text if self.buffer_comments.props.text != "" else None
|
||||
return self.buffer_comments.props.text if self.buffer_comments.props.text != '' else None
|
||||
|
||||
@cmb_comment.setter
|
||||
def _set_cmb_comment(self, value):
|
||||
self.buffer_comments.props.text = value if value is not None else ""
|
||||
self.buffer_comments.props.text = value if value is not None else ''
|
141
cambalache/cmb_translatable_widget.ui
Normal file
141
cambalache/cmb_translatable_widget.ui
Normal file
@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkTextBuffer" id="buffer_comments"/>
|
||||
<object class="GtkTextBuffer" id="buffer_context"/>
|
||||
<object class="GtkTextBuffer" id="buffer_text"/>
|
||||
<template class="CmbTranslatableWidget" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">4</property>
|
||||
<property name="margin-end">4</property>
|
||||
<property name="margin-top">4</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Text:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="width-request">300</property>
|
||||
<property name="height-request">60</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view_value">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="border-width">2</property>
|
||||
<property name="buffer">buffer_text</property>
|
||||
<property name="accepts-tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="check_button_translatable">
|
||||
<property name="label" translatable="yes">translatable</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">8</property>
|
||||
<property name="label" translatable="yes">Translation context:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="height-request">60</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view_context">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="border-width">2</property>
|
||||
<property name="buffer">buffer_context</property>
|
||||
<property name="accepts-tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">8</property>
|
||||
<property name="label" translatable="yes">Comments for translators:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="height-request">60</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view_comments">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="border-width">2</property>
|
||||
<property name="buffer">buffer_comments</property>
|
||||
<property name="accepts-tab">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,266 +0,0 @@
|
||||
#
|
||||
# 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)
|
172
cambalache/cmb_tree_view.py
Normal file
172
cambalache/cmb_tree_view.py
Normal file
@ -0,0 +1,172 @@
|
||||
#
|
||||
# CmbTreeView - Cambalache Tree View
|
||||
#
|
||||
# Copyright (C) 2021 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>
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gdk, Gtk
|
||||
|
||||
from .cmb_ui import CmbUI
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_context_menu import CmbContextMenu
|
||||
|
||||
|
||||
class CmbTreeView(Gtk.TreeView):
|
||||
__gtype_name__ = 'CmbTreeView'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._project = None
|
||||
self._selection = self.get_selection()
|
||||
self.__in_selection_change = False
|
||||
self._selection.connect('changed', self.__on_selection_changed)
|
||||
self.set_headers_visible (False)
|
||||
self.__right_click = False
|
||||
self.__inline_objects = {}
|
||||
|
||||
renderer = Gtk.CellRendererText()
|
||||
column = Gtk.TreeViewColumn('Object(Type)', renderer)
|
||||
column.set_cell_data_func(renderer, self.__name_cell_data_func, None)
|
||||
self.append_column(column)
|
||||
|
||||
self.connect('notify::model', self.__on_model_notify)
|
||||
self.connect('row-activated', self.__on_row_activated)
|
||||
|
||||
self.menu = CmbContextMenu(relative_to=self)
|
||||
|
||||
self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
|
||||
Gdk.EventMask.BUTTON_RELEASE_MASK)
|
||||
self.connect('button-press-event', self.__on_button_press_event)
|
||||
self.connect('button-release-event', self.__on_button_release_event)
|
||||
|
||||
self.set_reorderable(True)
|
||||
|
||||
def __on_button_press_event(self, widget, event):
|
||||
if event.window != self.get_bin_window() or event.button != 3:
|
||||
return False
|
||||
|
||||
self.__right_click = True
|
||||
return True
|
||||
|
||||
def __on_button_release_event(self, widget, event):
|
||||
if event.window != self.get_bin_window() or event.button != 3:
|
||||
return False
|
||||
|
||||
if not self.__right_click:
|
||||
return False
|
||||
|
||||
self.__right_click = False
|
||||
|
||||
retval = self.get_path_at_pos(event.x, event.y)
|
||||
|
||||
if retval is None:
|
||||
return False
|
||||
|
||||
path, col, xx, yy = retval
|
||||
self.get_selection().select_path(path)
|
||||
|
||||
self.menu.popup_at(event.x, event.y)
|
||||
|
||||
return True
|
||||
|
||||
def __object_get_inline_prop(self, obj):
|
||||
prop = self.__inline_objects.get(f'{obj.ui_id}.{obj.object_id}', None)
|
||||
return f'<b>{prop}</b> ' if prop else ''
|
||||
|
||||
def __name_cell_data_func(self, column, cell, model, iter_, data):
|
||||
obj = model.get_value(iter_, 0)
|
||||
|
||||
if type(obj) == CmbObject:
|
||||
inline_prop = self.__object_get_inline_prop(obj)
|
||||
name = f'{obj.name} ' if obj.name else ''
|
||||
extra = _('(template)') if not obj.parent_id and obj.ui.template_id == obj.object_id else obj.type_id
|
||||
text = f'{inline_prop}{name}<i>{extra}</i>'
|
||||
elif type(obj) == CmbUI:
|
||||
text = f'<b>{obj.filename}</b>' if obj.filename else _('<b>Unnamed {ui_id}</b>').format(ui_id=obj.ui_id)
|
||||
|
||||
cell.set_property('markup', text)
|
||||
|
||||
def __update_inline_objects(self):
|
||||
self.__inline_objects = {}
|
||||
|
||||
if self._project is None:
|
||||
return
|
||||
|
||||
for row in self._project.db.execute('SELECT ui_id, property_id, inline_object_id FROM object_property WHERE inline_object_id IS NOT NULL;'):
|
||||
ui_id, property_id, inline_object_id = row
|
||||
self.__inline_objects[f'{ui_id}.{inline_object_id}'] = property_id
|
||||
|
||||
def __on_project_changed(self, project):
|
||||
self.__update_inline_objects()
|
||||
|
||||
def __on_model_notify(self, treeview, pspec):
|
||||
if self._project is not None:
|
||||
self._project.disconnect_by_func(self.__on_project_selection_changed)
|
||||
self._project.disconnect_by_func(self.__on_project_changed)
|
||||
|
||||
self._project = self.props.model
|
||||
|
||||
if self._project:
|
||||
self._project.connect('selection-changed', self.__on_project_selection_changed)
|
||||
self._project.connect('changed', self.__on_project_changed)
|
||||
|
||||
self.__update_inline_objects()
|
||||
|
||||
def __on_row_activated(self, view, path, column):
|
||||
if self.row_expanded(path):
|
||||
self.collapse_row(path)
|
||||
else:
|
||||
self.expand_row(path, True)
|
||||
|
||||
def __on_project_selection_changed(self, p):
|
||||
project, _iter = self._selection.get_selected()
|
||||
current = [project.get_value(_iter, 0)] if _iter is not None else []
|
||||
selection = project.get_selection()
|
||||
|
||||
if selection == current:
|
||||
return
|
||||
|
||||
self.__in_selection_change = True
|
||||
|
||||
if len(selection) > 0:
|
||||
_iter = project.get_iter_from_object(selection[0])
|
||||
path = project.get_path(_iter)
|
||||
self.expand_to_path(path)
|
||||
self._selection.select_iter(_iter)
|
||||
else:
|
||||
self._selection.unselect_all()
|
||||
|
||||
self.__in_selection_change = False
|
||||
|
||||
def __on_selection_changed(self, selection):
|
||||
if self.__in_selection_change:
|
||||
return
|
||||
|
||||
project, _iter = selection.get_selected()
|
||||
|
||||
if _iter is not None:
|
||||
obj = project.get_value(_iter, 0)
|
||||
project.set_selection([obj])
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbTypeChooserBar - Cambalache Type Chooser Bar
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,30 +20,30 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_type_chooser.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_type_chooser.ui')
|
||||
class CmbTypeChooser(Gtk.Box):
|
||||
__gtype_name__ = "CmbTypeChooser"
|
||||
__gtype_name__ = 'CmbTypeChooser'
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
"chooser-popup": (GObject.SignalFlags.RUN_LAST, None, (GObject.Object,)),
|
||||
"chooser-popdown": (GObject.SignalFlags.RUN_LAST, None, (GObject.Object,)),
|
||||
'type-selected': (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo, )),
|
||||
'chooser-popup': (GObject.SignalFlags.RUN_LAST, None, (GObject.Object, )),
|
||||
'chooser-popdown': (GObject.SignalFlags.RUN_LAST, None, (GObject.Object, ))
|
||||
}
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE)
|
||||
selected_type = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
project = GObject.Property(type=CmbProject, flags = GObject.ParamFlags.READWRITE)
|
||||
selected_type = GObject.Property(type=CmbTypeInfo, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
type_label = Gtk.Template.Child()
|
||||
content = Gtk.Template.Child()
|
||||
all = Gtk.Template.Child()
|
||||
toplevel = Gtk.Template.Child()
|
||||
layout = Gtk.Template.Child()
|
||||
@ -55,14 +55,22 @@ class CmbTypeChooser(Gtk.Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._choosers = [self.all, self.toplevel, self.layout, self.control, self.display, self.model, self.extra]
|
||||
self._choosers = [
|
||||
self.all,
|
||||
self.toplevel,
|
||||
self.layout,
|
||||
self.control,
|
||||
self.display,
|
||||
self.model,
|
||||
self.extra
|
||||
]
|
||||
|
||||
self.connect("notify::project", self.__on_project_notify)
|
||||
self.connect("notify::selected-type", self.__on_selected_type_notify)
|
||||
self.connect('notify::project', self.__on_project_notify)
|
||||
self.connect('notify::selected-type', self.__on_selected_type_notify)
|
||||
|
||||
for chooser in self._choosers:
|
||||
chooser.connect("type-selected", lambda o, t: self.emit("type-selected", t))
|
||||
chooser.connect("notify::visible", self.__on_chooser_visible_notify)
|
||||
chooser.connect('type-selected', lambda o, t: self.emit('type-selected', t))
|
||||
chooser.connect('notify::visible', self.__on_chooser_visible_notify)
|
||||
|
||||
def __on_project_notify(self, object, pspec):
|
||||
project = self.project
|
||||
@ -72,20 +80,11 @@ class CmbTypeChooser(Gtk.Box):
|
||||
chooser.project = project
|
||||
|
||||
def __on_selected_type_notify(self, object, pspec):
|
||||
project_target = self.project.target_tk if self.project else ""
|
||||
project_target = self.project.target_tk if self.project else ''
|
||||
self.type_label.props.label = self.selected_type.type_id if self.selected_type else project_target
|
||||
|
||||
def __on_chooser_visible_notify(self, obj, pspec):
|
||||
if obj.props.visible:
|
||||
self.emit("chooser-popup", obj)
|
||||
self.emit('chooser-popup', obj)
|
||||
else:
|
||||
self.emit("chooser-popdown", obj)
|
||||
|
||||
def select_type_id(self, type_id):
|
||||
info = self.project.type_info.get(type_id, None)
|
||||
if info:
|
||||
self.selected_type = info
|
||||
self.emit("type-selected", info)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooser, "CmbTypeChooser")
|
||||
self.emit('chooser-popdown', obj)
|
||||
|
@ -1,147 +1,215 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_type_chooser.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="gladecambalache" version="0.0"/>
|
||||
<object class="CmbTypeChooserPopover" id="all">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-categories">True</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="control">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="category">control</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="display">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="category">display</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="extra">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="uncategorized-only">True</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="layout">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="category">layout</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="model">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="category">model</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="toplevel">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="category">toplevel</property>
|
||||
</object>
|
||||
<template class="CmbTypeChooser" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">4</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="content">
|
||||
<object class="GtkMenuButton" id="type_chooser_all">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">all</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="type_chooser_all">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">all</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="type_label">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<attributes>
|
||||
<attribute name="style" value="italic"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="type_chooser_gtk">
|
||||
<property name="homogeneous">False</property>
|
||||
<object class="GtkButtonBox" id="type_chooser_gtk">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">toplevel</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Widget group for toplevels/windows">Toplevel</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">layout</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for container widgets like GtkBox grid">Layout</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Widget group for container widgets liek GtkBox grid">Layout</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">control</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for control widget like buttons, entries">Control</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Widget group for control wildget like buttons, entries">Control</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">display</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Widget group for display widgets (label, image)">Display</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">model</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" comments="Widget group for model objects (ListStore, TextBuffer)">Model</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="popover">extra</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">pan-down-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbTypeChooserPopover - Cambalache Type Chooser Popover
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,9 +20,10 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
@ -31,38 +32,33 @@ from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
||||
|
||||
|
||||
class CmbTypeChooserPopover(Gtk.Popover):
|
||||
__gtype_name__ = "CmbTypeChooserPopover"
|
||||
__gtype_name__ = 'CmbTypeChooserPopover'
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
'type-selected': (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo, )),
|
||||
}
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE)
|
||||
category = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
project = GObject.Property(type=CmbProject, flags = GObject.ParamFlags.READWRITE)
|
||||
category = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags = GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags = GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._chooser = CmbTypeChooserWidget()
|
||||
self._chooser.connect("type-selected", self.__on_type_selected)
|
||||
self._chooser.connect('type-selected', self.__on_type_selected)
|
||||
self._chooser.show_all()
|
||||
self.add(self._chooser)
|
||||
|
||||
self.set_child(self._chooser)
|
||||
self.set_default_widget(self._chooser)
|
||||
|
||||
for prop in [
|
||||
"project",
|
||||
"category",
|
||||
"uncategorized_only",
|
||||
"show_categories",
|
||||
"parent_type_id",
|
||||
"derived_type_id",
|
||||
]:
|
||||
GObject.Object.bind_property(self, prop, self._chooser, prop, GObject.BindingFlags.SYNC_CREATE)
|
||||
for prop in ['project', 'category', 'uncategorized_only', 'show_categories', 'parent_type_id', 'derived_type_id']:
|
||||
GObject.Object.bind_property(self, prop,
|
||||
self._chooser, prop,
|
||||
GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
def __on_type_selected(self, chooser, info):
|
||||
self.emit("type-selected", info)
|
||||
self.emit('type-selected', info)
|
||||
self.popdown()
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbTypeChooserWidget - Cambalache Type Chooser Widget
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,189 +20,149 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GLib, GObject, Gio, Gtk
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from . import constants
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_type_chooser_widget.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_type_chooser_widget.ui')
|
||||
class CmbTypeChooserWidget(Gtk.Box):
|
||||
__gtype_name__ = "CmbTypeChooserWidget"
|
||||
__gtype_name__ = 'CmbTypeChooserWidget'
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
'type-selected': (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo, )),
|
||||
}
|
||||
|
||||
category = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
project = GObject.Property(type=CmbProject, flags = GObject.ParamFlags.READWRITE)
|
||||
category = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags = GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags = GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
entrycompletion = Gtk.Template.Child()
|
||||
scrolledwindow = Gtk.Template.Child()
|
||||
listview = Gtk.Template.Child()
|
||||
treeview = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__project = None
|
||||
self._search_text = ""
|
||||
self.__model = None
|
||||
self._search_text = ''
|
||||
self._filter = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("map", self.__on_map)
|
||||
|
||||
def __on_map(self, widget):
|
||||
root = widget.get_root()
|
||||
|
||||
if root is not None:
|
||||
height = root.get_allocated_height() - 100
|
||||
if height > 460:
|
||||
height = height * 0.7
|
||||
|
||||
self.scrolledwindow.set_max_content_height(height)
|
||||
return False
|
||||
|
||||
def __type_info_should_append(self, info):
|
||||
retval = False
|
||||
|
||||
if not info.instantiable or info.layout not in [None, "container"]:
|
||||
return False
|
||||
|
||||
if info.category == "hidden":
|
||||
return False
|
||||
|
||||
if self.parent_type_id != "":
|
||||
retval = self.project._check_can_add(info.type_id, self.parent_type_id)
|
||||
else:
|
||||
retval = (
|
||||
info.category is None
|
||||
if self.uncategorized_only
|
||||
else (self.category != "" and info.category == self.category) or self.category == ""
|
||||
)
|
||||
|
||||
if retval and self.derived_type_id != "":
|
||||
retval = info.is_a(self.derived_type_id)
|
||||
|
||||
return retval
|
||||
self.connect('notify::project', self.__on_project_notify)
|
||||
self.connect('map', self.__on_map)
|
||||
|
||||
def __model_from_project(self, project):
|
||||
if project is None:
|
||||
return None
|
||||
|
||||
categories = {
|
||||
"toplevel": _("Toplevel"),
|
||||
"layout": _("Layout"),
|
||||
"control": _("Control"),
|
||||
"display": _("Display"),
|
||||
"model": _("Model"),
|
||||
'toplevel': _('Toplevel'),
|
||||
'layout': _('Layout'),
|
||||
'control': _('Control'),
|
||||
'display': _('Display'),
|
||||
'model': _('Model')
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
store = Gtk.ListStore(str, str, CmbTypeInfo, bool)
|
||||
infos = []
|
||||
|
||||
for key in project.type_info:
|
||||
# Ignore types with no name, just in case
|
||||
if key:
|
||||
infos.append(project.type_info[key])
|
||||
else:
|
||||
logger.warning("Tried to create a TypeInfo without a name")
|
||||
i = project.type_info[key]
|
||||
|
||||
if i.instantiable and i.layout in [None, 'container']:
|
||||
infos.append(i)
|
||||
|
||||
infos = sorted(infos, key=lambda i: (order.get(i.category, 99), i.type_id))
|
||||
show_categories = self.show_categories
|
||||
last_category = None
|
||||
|
||||
for i in infos:
|
||||
if not self.__type_info_should_append(i):
|
||||
continue
|
||||
|
||||
# Append category
|
||||
if show_categories and last_category != i.category:
|
||||
last_category = i.category
|
||||
category = categories.get(i.category, _("Others"))
|
||||
store.append(CmbTypeInfo(type_id=f"<i><b>▾ {category}</b></i>"))
|
||||
category = categories.get(i.category, _('Others'))
|
||||
store.append([f'<i>▾ {category}</i>', '', None, False])
|
||||
|
||||
store.append(i)
|
||||
if self.parent_type_id != '':
|
||||
append = self.project._check_can_add(i.type_id, self.parent_type_id)
|
||||
else:
|
||||
append = i.category is None if self.uncategorized_only else \
|
||||
(self.category != '' and i.category == self.category) or self.category == ''
|
||||
|
||||
# Special case External object type
|
||||
if show_categories or self.uncategorized_only:
|
||||
store.append(project.type_info[constants.EXTERNAL_TYPE])
|
||||
if append and self.derived_type_id != '':
|
||||
append = i.is_a(self.derived_type_id)
|
||||
|
||||
return filter_model
|
||||
if append:
|
||||
store.append([i.type_id, i.type_id.lower(), i, True])
|
||||
|
||||
@GObject.Property(type=CmbProject)
|
||||
def project(self):
|
||||
return self.__project
|
||||
return store
|
||||
|
||||
@project.setter
|
||||
def _set_project(self, project):
|
||||
if self.__project:
|
||||
self.__project.disconnect_by_func(self.__on_type_info_added)
|
||||
self.__project.disconnect_by_func(self.__on_type_info_removed)
|
||||
def __on_project_notify(self, object, pspec):
|
||||
model = self.__model_from_project(self.project)
|
||||
self._filter = Gtk.TreeModelFilter(child_model=model) if model else None
|
||||
if self._filter:
|
||||
self._filter.set_visible_func(self.__visible_func)
|
||||
|
||||
self.__project = project
|
||||
self.entrycompletion.props.model = model
|
||||
self.treeview.props.model = self._filter
|
||||
|
||||
self.__model = self.__model_from_project(project)
|
||||
self.listview.set_model(Gtk.NoSelection(model=self.__model))
|
||||
|
||||
if project:
|
||||
project.connect("type-info-added", self.__on_type_info_added)
|
||||
project.connect("type-info-removed", self.__on_type_info_removed)
|
||||
|
||||
@Gtk.Template.Callback("on_searchentry_activate")
|
||||
@Gtk.Template.Callback('on_searchentry_activate')
|
||||
def __on_searchentry_activate(self, entry):
|
||||
search_text = entry.props.text
|
||||
|
||||
info = self.project.type_info.get(search_text, None)
|
||||
if info:
|
||||
self.emit("type-selected", info)
|
||||
self.emit('type-selected', info)
|
||||
|
||||
@Gtk.Template.Callback("on_searchentry_search_changed")
|
||||
@Gtk.Template.Callback('on_searchentry_search_changed')
|
||||
def __on_searchentry_search_changed(self, entry):
|
||||
self._search_text = entry.props.text.lower()
|
||||
self.__model.props.filter.changed(Gtk.FilterChange.DIFFERENT)
|
||||
self._filter.refilter()
|
||||
|
||||
@Gtk.Template.Callback("on_listview_activate")
|
||||
def __on_listview_activate(self, listview, position):
|
||||
info = self.__model.get_item(position)
|
||||
@Gtk.Template.Callback('on_treeview_row_activated')
|
||||
def __on_treeview_row_activated(self, treeview, path, column):
|
||||
model = treeview.props.model
|
||||
info = model[model.get_iter(path)][2]
|
||||
|
||||
if info is not None and info.project:
|
||||
self.emit("type-selected", info)
|
||||
if info is not None:
|
||||
self.emit('type-selected', info)
|
||||
|
||||
def __custom_filter_func(self, info, data):
|
||||
return info.type_id.lower().find(self._search_text) >= 0
|
||||
def __visible_func(self, model, iter, data):
|
||||
type_id, type_id_lower, info, sensitive = model[iter]
|
||||
|
||||
def __on_type_info_added(self, project, info):
|
||||
if self.__model is None:
|
||||
return
|
||||
# Always show categories if we are not searching
|
||||
if self._search_text == '' and info is None:
|
||||
return True
|
||||
|
||||
# Append new type info
|
||||
if self.__type_info_should_append(info):
|
||||
self.__model.props.model.insert_sorted(info, lambda a, b, d: GLib.strcmp0(a.type_id, b.type_id), None)
|
||||
return type_id_lower.find(self._search_text) >= 0
|
||||
|
||||
def __on_type_info_removed(self, project, info):
|
||||
if self.__model is None:
|
||||
return
|
||||
def __on_map(self, widget):
|
||||
toplevel = widget.get_toplevel()
|
||||
|
||||
# Find info and remove it from model
|
||||
found, position = self.__model.props.model.find(info)
|
||||
if found:
|
||||
self.__model.props.model.remove(position)
|
||||
if toplevel:
|
||||
height = toplevel.get_allocated_height() - 100;
|
||||
if height > 460:
|
||||
height = height * 0.7;
|
||||
|
||||
self.scrolledwindow.set_max_content_height(height)
|
||||
return False
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, "CmbTypeChooserWidget")
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, 'CmbTypeChooserWidget')
|
||||
|
||||
|
@ -1,52 +1,77 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_type_chooser_widget.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkEntryCompletion" id="entrycompletion">
|
||||
<property name="text-column">0</property>
|
||||
<property name="inline-completion">True</property>
|
||||
<property name="popup-completion">False</property>
|
||||
<property name="popup-single-match">False</property>
|
||||
</object>
|
||||
<template class="CmbTypeChooserWidget" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">6</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="searchentry">
|
||||
<signal name="activate" handler="on_searchentry_activate"/>
|
||||
<signal name="search-changed" handler="on_searchentry_search_changed"/>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
<property name="completion">entrycompletion</property>
|
||||
<property name="input-hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
|
||||
<signal name="activate" handler="on_searchentry_activate" swapped="no"/>
|
||||
<signal name="search-changed" handler="on_searchentry_search_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="window-placement">bottom-left</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="max-content-height">512</property>
|
||||
<property name="propagate-natural-width">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<child>
|
||||
<object class="GtkListView" id="listview">
|
||||
<property name="factory">
|
||||
<object class="GtkBuilderListItemFactory">
|
||||
<property name="bytes"><![CDATA[<?xml version='1.0' encoding='UTF-8'?>
|
||||
<interface>
|
||||
<template class="GtkListItem" parent="GObject">
|
||||
<property name="child">
|
||||
<object class="GtkInscription">
|
||||
<binding name="markup">
|
||||
<lookup name="type_id" type="CmbTypeInfo">
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>]]></property>
|
||||
<object class="GtkTreeView" id="treeview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="headers-visible">False</property>
|
||||
<property name="enable-search">False</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="row-activated" handler="on_treeview_row_activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview-selection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="column_adaptor">
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="adaptor_cell"/>
|
||||
<attributes>
|
||||
<attribute name="markup">0</attribute>
|
||||
<attribute name="sensitive">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</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"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -20,35 +20,18 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_objects_base import (
|
||||
CmbBaseTypeInfo,
|
||||
CmbBaseTypeDataInfo,
|
||||
CmbBaseTypeDataArgInfo,
|
||||
CmbBaseTypeInternalChildInfo,
|
||||
CmbTypeChildInfo,
|
||||
CmbSignalInfo,
|
||||
)
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
|
||||
from .constants import EXTERNAL_TYPE, GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
|
||||
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
from .cmb_objects_base import CmbBaseTypeInfo, CmbBaseTypeDataInfo, CmbBaseTypeDataArgInfo, CmbTypeChildInfo, CmbPropertyInfo, CmbSignalInfo
|
||||
|
||||
|
||||
class CmbTypeDataArgInfo(CmbBaseTypeDataArgInfo):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeDataArgInfo<{self.owner_id}>::{self.key}"
|
||||
|
||||
|
||||
class CmbTypeDataInfo(CmbBaseTypeDataInfo):
|
||||
def __init__(self, **kwargs):
|
||||
@ -56,84 +39,55 @@ class CmbTypeDataInfo(CmbBaseTypeDataInfo):
|
||||
self.children = {}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
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):
|
||||
__gtype_name__ = "CmbTypeInfo"
|
||||
|
||||
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 = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
||||
parent = GObject.Property(type=GObject.Object, flags = GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
self.hierarchy = self.__init_hierarchy()
|
||||
self.interfaces = self.__init_interfaces()
|
||||
self.properties = self.__init_properties_signals(CmbPropertyInfo, "property")
|
||||
self.signals = self.__init_properties_signals(CmbSignalInfo, "signal")
|
||||
self.properties = self.__init_properties_signals(CmbPropertyInfo, 'property')
|
||||
self.signals = self.__init_properties_signals(CmbSignalInfo, 'signal')
|
||||
self.data = self.__init_data()
|
||||
self.internal_children = self.__init_internal_children()
|
||||
self.child_constraint, self.child_type_shortcuts = self.__init_child_constraint()
|
||||
|
||||
if self.parent_id == "enum":
|
||||
self.enum = self.__init_enum_flags("enum")
|
||||
elif self.parent_id == "flags":
|
||||
self.flags = self.__init_enum_flags("flags")
|
||||
if self.parent_id == 'enum':
|
||||
self.enum = self.__init_enum_flags('enum')
|
||||
elif self.parent_id == 'flags':
|
||||
self.flags = self.__init_enum_flags('flags')
|
||||
|
||||
self.child_types = self.__init_child_type()
|
||||
|
||||
self.is_object = self.is_a("GObject")
|
||||
|
||||
self.instantiable = self.is_object and not self.abstract
|
||||
|
||||
self.is_menu_builtin = self.type_id in [GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]
|
||||
self.is_builtin = self.is_menu_builtin or self.type_id in [EXTERNAL_TYPE]
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeInfo<{self.type_id}>"
|
||||
self.instantiable = self.is_a('GObject') and not self.abstract
|
||||
|
||||
def __init_hierarchy(self):
|
||||
retval = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"""
|
||||
WITH RECURSIVE ancestor(type_id, generation, parent_id) AS (
|
||||
SELECT type_id, 1, parent_id
|
||||
FROM type
|
||||
WHERE parent_id IS NOT NULL AND
|
||||
parent_id != 'interface' AND
|
||||
parent_id != 'enum' AND
|
||||
parent_id != 'flags' AND
|
||||
type_id=?
|
||||
UNION ALL
|
||||
SELECT ancestor.type_id, generation + 1, type.parent_id
|
||||
FROM type JOIN ancestor ON type.type_id = ancestor.parent_id
|
||||
WHERE type.parent_id IS NOT NULL AND type.parent_id != 'object' AND ancestor.type_id=?
|
||||
)
|
||||
SELECT parent_id, generation FROM ancestor
|
||||
UNION
|
||||
SELECT type_iface.iface_id, 0
|
||||
FROM ancestor JOIN type_iface
|
||||
WHERE ancestor.type_id = type_iface.type_id
|
||||
ORDER BY generation;
|
||||
""",
|
||||
(self.type_id, self.type_id),
|
||||
):
|
||||
for row in c.execute('''
|
||||
WITH RECURSIVE ancestor(type_id, generation, parent_id) AS (
|
||||
SELECT type_id, 1, parent_id FROM type
|
||||
WHERE parent_id IS NOT NULL AND
|
||||
parent_id != 'interface' AND
|
||||
parent_id != 'enum' AND
|
||||
parent_id != 'flags' AND
|
||||
type_id=?
|
||||
UNION ALL
|
||||
SELECT ancestor.type_id, generation + 1, type.parent_id
|
||||
FROM type
|
||||
JOIN ancestor ON type.type_id = ancestor.parent_id
|
||||
WHERE type.parent_id IS NOT NULL AND
|
||||
type.parent_id != 'object' AND
|
||||
ancestor.type_id=?
|
||||
)
|
||||
SELECT parent_id, generation FROM ancestor
|
||||
UNION
|
||||
SELECT type_iface.iface_id, 0
|
||||
FROM ancestor JOIN type_iface
|
||||
WHERE ancestor.type_id = type_iface.type_id
|
||||
ORDER BY generation;''',
|
||||
(self.type_id, self.type_id)):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
@ -144,7 +98,8 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
retval = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute("SELECT iface_id FROM type_iface WHERE type_id=? ORDER BY iface_id;", (self.type_id,)):
|
||||
for row in c.execute(f'SELECT iface_id FROM type_iface WHERE type_id=? ORDER BY iface_id;',
|
||||
(self.type_id, )):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
@ -154,27 +109,30 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
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.project, *row)
|
||||
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)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __type_get_data(self, owner_id, data_id, parent_id, key, type_id, translatable):
|
||||
def __type_get_data(self, owner_id, data_id, parent_id, key, type_id):
|
||||
args = {}
|
||||
children = {}
|
||||
parent_id = parent_id if parent_id is not None else 0
|
||||
retval = CmbTypeDataInfo.from_row(self.project, owner_id, data_id, parent_id, key, type_id, translatable)
|
||||
retval = CmbTypeDataInfo.from_row(self, owner_id, data_id, parent_id, key, type_id)
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# 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]
|
||||
args[_key] = CmbTypeDataArgInfo.from_row(self.project, *row)
|
||||
args[_key] = CmbTypeDataArgInfo.from_row(self, *row)
|
||||
|
||||
# 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)):
|
||||
_key = row[3]
|
||||
children[_key] = self.__type_get_data(*row)
|
||||
|
||||
@ -189,90 +147,26 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"SELECT * FROM type_data WHERE parent_id IS NULL AND owner_id=? ORDER BY data_id;", (self.type_id,)
|
||||
):
|
||||
for row in c.execute('SELECT * FROM type_data WHERE parent_id IS NULL AND owner_id=? ORDER BY data_id;',
|
||||
(self.type_id, )):
|
||||
key = row[3]
|
||||
retval[key] = self.__type_get_data(*row)
|
||||
|
||||
c.close()
|
||||
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):
|
||||
retval = {}
|
||||
shortcuts = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"SELECT child_type_id, allowed, shortcut FROM type_child_constraint WHERE type_id=?;", (self.type_id,)
|
||||
):
|
||||
child_type_id, allowed, shortcut = row
|
||||
retval[child_type_id] = allowed
|
||||
if shortcut:
|
||||
shortcuts.append(child_type_id)
|
||||
|
||||
c.close()
|
||||
return retval, shortcuts
|
||||
|
||||
def __init_child_type(self):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute("SELECT * FROM type_child_type WHERE type_id=?;", (self.type_id,)):
|
||||
for row in c.execute('SELECT * FROM type_child_type WHERE type_id=?;',
|
||||
(self.type_id, )):
|
||||
type_id, child_type, max_children, linked_property_id = row
|
||||
retval[child_type] = CmbTypeChildInfo(
|
||||
project=self.project,
|
||||
type_id=type_id,
|
||||
child_type=child_type,
|
||||
max_children=max_children if max_children else 0,
|
||||
linked_property_id=linked_property_id,
|
||||
)
|
||||
retval[child_type] = CmbTypeChildInfo(project=self.project,
|
||||
type_id=type_id,
|
||||
child_type=child_type,
|
||||
max_children=max_children if max_children else 0,
|
||||
linked_property_id=linked_property_id)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
@ -281,7 +175,7 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
retval = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT)
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(f"SELECT name, nick, value FROM type_{name} WHERE type_id=? ORDER BY nick;", (self.type_id,)):
|
||||
for row in c.execute(f'SELECT name, nick, value FROM type_{name} WHERE type_id=?', (self.type_id,)):
|
||||
retval.append(row)
|
||||
|
||||
c.close()
|
||||
@ -300,28 +194,6 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
|
||||
return None
|
||||
|
||||
def find_data_info(self, data_id):
|
||||
def find_child_info(info, data_id):
|
||||
for name in info.children:
|
||||
child_info = info.children[name]
|
||||
if child_info.data_id == data_id:
|
||||
return child_info
|
||||
|
||||
retval = find_child_info(child_info, data_id)
|
||||
if retval:
|
||||
return retval
|
||||
|
||||
for name in self.data:
|
||||
info = self.data[name]
|
||||
if info.data_id == data_id:
|
||||
return info
|
||||
|
||||
retval = find_child_info(info, data_id)
|
||||
if retval:
|
||||
return retval
|
||||
|
||||
return None
|
||||
|
||||
def has_child_types(self):
|
||||
parent = self
|
||||
while parent:
|
||||
@ -330,48 +202,3 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
parent = parent.parent
|
||||
|
||||
return False
|
||||
|
||||
def enum_get_value_as_string(self, value, use_nick=True):
|
||||
if self.parent_id != "enum":
|
||||
return None
|
||||
|
||||
for row in self.enum:
|
||||
enum_name, enum_nick, enum_value = row
|
||||
|
||||
# Always use nick as value
|
||||
if value == enum_name or value == enum_nick or value == str(enum_value):
|
||||
return enum_nick if use_nick else enum_value
|
||||
|
||||
return None
|
||||
|
||||
def flags_get_value_as_string(self, value):
|
||||
if self.parent_id != "flags":
|
||||
return None
|
||||
|
||||
value_type = type(value)
|
||||
tokens = None
|
||||
|
||||
if value_type == str:
|
||||
if value.isnumeric():
|
||||
value = int(value)
|
||||
value_type = int
|
||||
else:
|
||||
tokens = [t.strip() for t in value.split("|")]
|
||||
elif value_type != int:
|
||||
logger.warning(f"Unhandled value type {value_type} {value}")
|
||||
return None
|
||||
|
||||
flags = []
|
||||
|
||||
for row in self.flags:
|
||||
flag_name, flag_nick, flag_value = row
|
||||
|
||||
if value_type == str:
|
||||
# Always use nick as value
|
||||
if flag_name in tokens or flag_nick in tokens:
|
||||
flags.append(flag_nick)
|
||||
else:
|
||||
if flag_value & value:
|
||||
flags.append(flag_nick)
|
||||
|
||||
return "|".join(flags)
|
||||
|
@ -20,203 +20,56 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
from gi.repository import GObject, Gio
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_list_error import CmbListError
|
||||
from .cmb_objects_base import CmbBaseUI, CmbBaseObject
|
||||
from cambalache import getLogger, _
|
||||
from .cmb_objects_base import CmbBaseUI
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
__gsignals__ = {
|
||||
"library-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
class CmbUI(CmbBaseUI):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __bool__(self):
|
||||
# Override Truth Value Testing to ensure that CmbUI objects evaluates to True even if it does not have children objects
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbUI<{self.display_name}>"
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def template_id(self):
|
||||
retval = self.db_get("SELECT template_id FROM ui WHERE (ui_id) IS (?);", (self.ui_id,))
|
||||
retval = self.db_get('SELECT template_id FROM ui WHERE (ui_id) IS (?);',
|
||||
(self.ui_id, ))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@template_id.setter
|
||||
def _set_template_id(self, value):
|
||||
self.db_set("UPDATE ui SET template_id=? WHERE (ui_id) IS (?);", (self.ui_id,), value if value != 0 else None)
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
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):
|
||||
retval = {}
|
||||
|
||||
for row in self.project.db.execute(
|
||||
"""
|
||||
SELECT DISTINCT t.library_id, NULL
|
||||
FROM object AS o, type AS t
|
||||
WHERE t.library_id IS NOT NULL AND o.ui_id=? AND o.type_id = t.type_id
|
||||
UNION
|
||||
SELECT library_id, version FROM ui_library WHERE ui_id=?
|
||||
""",
|
||||
(self.ui_id, self.ui_id),
|
||||
).fetchall():
|
||||
library_id, version = row
|
||||
|
||||
versions = []
|
||||
for row in self.project.db.execute(
|
||||
"SELECT version FROM library_version WHERE library_id=? ORDER BY version COLLATE version DESC;", (library_id,)
|
||||
).fetchall():
|
||||
versions.append(row[0])
|
||||
|
||||
retval[library_id] = {"target": version, "versions": versions}
|
||||
|
||||
return retval
|
||||
self.db_set('UPDATE ui SET template_id=? WHERE (ui_id) IS (?);',
|
||||
(self.ui_id, ), value if value != 0 else None)
|
||||
|
||||
def get_library(self, library_id):
|
||||
c = self.project.db.execute("SELECT version FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id))
|
||||
c = self.project.db.execute("SELECT version FROM ui_library WHERE ui_id=? AND library_id=?;",
|
||||
(self.ui_id, library_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def _library_changed(self, lib):
|
||||
self.emit("library-changed", lib)
|
||||
self.project._ui_library_changed(self, lib)
|
||||
|
||||
def set_library(self, library_id, version, comment=None):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
try:
|
||||
if version is None:
|
||||
c.execute("DELETE FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id))
|
||||
c.execute("DELETE FROM ui_library WHERE ui_id=? AND library_id=?;",
|
||||
(self.ui_id, library_id))
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"SELECT count(version) FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id)
|
||||
)
|
||||
count = self.db_get("SELECT count(version) FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id))
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"UPDATE ui_library SET version=?, comment=? WHERE ui_id=? AND library_id=?;",
|
||||
(str(version), comment, self.ui_id, library_id),
|
||||
)
|
||||
c.execute("UPDATE ui_library SET version=?, comment=? WHERE ui_id=?, library_id=?;",
|
||||
(str(version), comment, self.ui_id, library_id))
|
||||
else:
|
||||
c.execute(
|
||||
"INSERT INTO ui_library (ui_id, library_id, version, comment) VALUES (?, ?, ?, ?);",
|
||||
(self.ui_id, library_id, str(version), comment),
|
||||
)
|
||||
c.execute("INSERT INTO ui_library (ui_id, library_id, version, comment) VALUES (?, ?, ?, ?);",
|
||||
(self.ui_id, library_id, str(version), comment))
|
||||
|
||||
self._library_changed(library_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error setting library {library_id}={version}: {e}")
|
||||
logger.warning(f'{self} Error setting library {library_id}={version}: {e}')
|
||||
|
||||
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)
|
||||
def display_name(self):
|
||||
filename = self.filename
|
||||
template_id = self.template_id
|
||||
|
||||
if filename is None and template_id:
|
||||
template = self.project.get_object_by_id(self.ui_id, template_id)
|
||||
if template:
|
||||
return template.name
|
||||
|
||||
return CmbUI.get_display_name(self.ui_id, filename)
|
||||
|
||||
def __get_infered_target(self, library_id):
|
||||
ui_id = self.ui_id
|
||||
|
||||
row = self.project.db.execute(
|
||||
"""
|
||||
WITH lib_version(version) AS (
|
||||
SELECT t.version
|
||||
FROM object AS o, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.type_id = t.type_id AND t.version IS NOT NULL
|
||||
UNION
|
||||
SELECT p.version
|
||||
FROM object_property AS o, property AS p, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.owner_id = t.type_id AND o.owner_id = p.owner_id
|
||||
AND p.version IS NOT NULL
|
||||
UNION
|
||||
SELECT s.version
|
||||
FROM object_signal AS o, signal AS s, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.owner_id = t.type_id AND o.owner_id = s.owner_id
|
||||
AND s.version IS NOT NULL
|
||||
)
|
||||
SELECT MAX_VERSION(version) FROM lib_version;
|
||||
""",
|
||||
(library_id, ui_id, library_id, ui_id, library_id, ui_id),
|
||||
).fetchone()
|
||||
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def get_target(self, library_id):
|
||||
target = self.get_library(library_id)
|
||||
if target is None:
|
||||
target = self.__get_infered_target(library_id)
|
||||
|
||||
if target is None:
|
||||
info = self.project.library_info.get(library_id, None)
|
||||
if info:
|
||||
return info.min_version
|
||||
|
||||
return target
|
||||
|
||||
# GListModel iface
|
||||
def do_get_item(self, position):
|
||||
ui_id = self.ui_id
|
||||
|
||||
# This query should use auto index from UNIQUE constraint
|
||||
retval = self.db_get(
|
||||
"""
|
||||
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:
|
||||
return self.project.get_object_by_id(ui_id, retval)
|
||||
|
||||
# This should not happen
|
||||
return CmbListError()
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBaseObject
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
retval = self.db_get("SELECT COUNT(object_id) FROM object WHERE ui_id=? AND parent_id IS NULL;", (self.ui_id,))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# CmbUIEditor - Cambalache UI Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 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
|
||||
@ -20,31 +20,36 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from cambalache import _
|
||||
from .cmb_ui import CmbUI
|
||||
from .cmb_property_controls import *
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_ui_editor.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_ui_editor.ui')
|
||||
class CmbUIEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbUIEditor"
|
||||
__gtype_name__ = 'CmbUIEditor'
|
||||
|
||||
filename = Gtk.Template.Child()
|
||||
format = Gtk.Template.Child()
|
||||
template_id = Gtk.Template.Child()
|
||||
description = Gtk.Template.Child()
|
||||
copyright = Gtk.Template.Child()
|
||||
authors = Gtk.Template.Child()
|
||||
translation_domain = Gtk.Template.Child()
|
||||
comment = Gtk.Template.Child()
|
||||
|
||||
fields = ["filename", "template_id", "description", "copyright", "authors", "translation_domain", "comment"]
|
||||
fields = [
|
||||
'filename',
|
||||
'template_id',
|
||||
'description',
|
||||
'copyright',
|
||||
'authors',
|
||||
'translation_domain'
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
@ -58,6 +63,9 @@ class CmbUIEditor(Gtk.Grid):
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self._object:
|
||||
return
|
||||
|
||||
for binding in self._bindings:
|
||||
binding.unbind()
|
||||
|
||||
@ -70,7 +78,7 @@ class CmbUIEditor(Gtk.Grid):
|
||||
for field in self.fields:
|
||||
widget = getattr(self, field)
|
||||
|
||||
if type(widget.cmb_value) is int:
|
||||
if type(widget.cmb_value) == int:
|
||||
widget.cmb_value = 0
|
||||
else:
|
||||
widget.cmb_value = None
|
||||
@ -78,60 +86,36 @@ class CmbUIEditor(Gtk.Grid):
|
||||
|
||||
self.set_sensitive(True)
|
||||
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:
|
||||
binding = obj.bind_property(
|
||||
field,
|
||||
getattr(self, field),
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
binding = GObject.Object.bind_property(obj, field,
|
||||
getattr(self, field), 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
self._bindings.append(binding)
|
||||
|
||||
if obj.project.target_tk == "gtk-4.0":
|
||||
self.filename.mime_types = "application/x-gtk-builder;text/x-blueprint"
|
||||
@Gtk.Template.Callback('on_remove_button_clicked')
|
||||
def __on_remove_button_clicked(self, button):
|
||||
self.emit('remove-ui')
|
||||
|
||||
# filename -> format
|
||||
binding = obj.bind_property(
|
||||
"filename",
|
||||
self.format,
|
||||
"selected",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
transform_to=self.__filename_to_format,
|
||||
transform_from=self.__format_to_filename,
|
||||
user_data=obj
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
@Gtk.Template.Callback('on_export_button_clicked')
|
||||
def __on_export_button_clicked(self, button):
|
||||
self.emit('export-ui')
|
||||
|
||||
self.format.show()
|
||||
self.format.set_sensitive(bool(obj.filename))
|
||||
else:
|
||||
self.filename.mime_types = "application/x-gtk-builder;application/x-glade"
|
||||
self.format.hide()
|
||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool,
|
||||
arg_types=(),
|
||||
accumulator=GObject.signal_accumulator_true_handled)
|
||||
def export_ui(self):
|
||||
if self.object:
|
||||
self.object.project.export_ui(self.object)
|
||||
|
||||
def __filename_to_format(self, binding, source_value, ui):
|
||||
if not source_value:
|
||||
self.format.props.sensitive = False
|
||||
return 0
|
||||
self.format.props.sensitive = True
|
||||
return True
|
||||
|
||||
return 1 if source_value.endswith(".blp") else 0
|
||||
@GObject.Signal(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)
|
||||
|
||||
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")
|
||||
return True
|
||||
|
@ -1,213 +1,237 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_ui_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="CmbTextBuffer" id="authors"/>
|
||||
<object class="CmbTextBuffer" id="comment"/>
|
||||
<object class="CmbTextBuffer" id="copyright"/>
|
||||
<object class="CmbTextBuffer" id="description"/>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<!-- n-columns=2 n-rows=7 -->
|
||||
<template class="CmbUIEditor" parent="GtkGrid">
|
||||
<property name="column-spacing">3</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<property name="column-spacing">3</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<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>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Description:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Copyright:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Authors:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Domain:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<object class="CmbEntry" id="filename">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="placeholder-text" translatable="yes"><file name relative to project></property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbEntry" id="translation_domain">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="width-chars">16</property>
|
||||
<property name="placeholder-text" translatable="yes"><translation domain></property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="buffer">description</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">authors</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="buffer">authors</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbToplevelChooser" id="template_id">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="derivable-only">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Template:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="clicked" handler="on_remove_button_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">app-remove-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="export_button">
|
||||
<property name="label" translatable="yes">Export</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Export</property>
|
||||
<property name="halign">start</property>
|
||||
<signal name="clicked" handler="on_export_button_clicked" swapped="no"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">6</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<child>
|
||||
<object class="GtkTextView">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="buffer">copyright</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Comment:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">7</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">comment</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</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>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
<object class="CmbTextBuffer" id="authors"/>
|
||||
<object class="CmbTextBuffer" id="copyright"/>
|
||||
<object class="CmbTextBuffer" id="description"/>
|
||||
</interface>
|
||||
|
@ -1,113 +0,0 @@
|
||||
#
|
||||
# CmbUIRequiresEditor - Cambalache UI Requires Editor
|
||||
#
|
||||
# Copyright (C) 2023-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_ui import CmbUI
|
||||
from . import utils
|
||||
|
||||
|
||||
class CmbUIRequiresEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbUIRequiresEditor"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__combos = {}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.column_spacing = 4
|
||||
self.props.row_spacing = 4
|
||||
|
||||
@GObject.Property(type=CmbUI)
|
||||
def object(self):
|
||||
return self.__object
|
||||
|
||||
@object.setter
|
||||
def _set__object(self, obj):
|
||||
if obj == self.__object:
|
||||
return
|
||||
|
||||
if self.__object:
|
||||
self.__object.project.disconnect_by_func(self.__on_project_added_removed)
|
||||
self.__object.disconnect_by_func(self.__on_library_changed)
|
||||
|
||||
self.__object = obj
|
||||
self.set_sensitive(obj is not None)
|
||||
|
||||
if obj:
|
||||
self.__object.project.connect("object-added", self.__on_project_added_removed)
|
||||
self.__object.project.connect("object-removed", self.__on_project_added_removed)
|
||||
self.__object.connect("library-changed", self.__on_library_changed)
|
||||
self.__update()
|
||||
|
||||
def __on_project_added_removed(self, project, obj):
|
||||
self.__update()
|
||||
|
||||
def __on_library_changed(self, ui, lib):
|
||||
combo = self.__combos.get(lib, None)
|
||||
if combo:
|
||||
combo.set_active_id(ui.get_library(lib))
|
||||
|
||||
def __update(self):
|
||||
self.__combos = {}
|
||||
|
||||
for child in utils.widget_get_children(self):
|
||||
self.remove(child)
|
||||
|
||||
if self.__object is None:
|
||||
return
|
||||
|
||||
i = 0
|
||||
for library_id, data in self.__object.list_libraries().items():
|
||||
label = Gtk.Label(label=library_id, visible=True, halign=Gtk.Align.START)
|
||||
combo = self.__combobox_new(library_id, **data)
|
||||
|
||||
# Keep a reference by library_id
|
||||
self.__combos[library_id] = combo
|
||||
|
||||
# Append to grid
|
||||
self.attach(label, 1, i, 1, 1)
|
||||
self.attach(combo, 2, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
def __on_combobox_changed(self, combo, library_id):
|
||||
if self.__object:
|
||||
self.__object.set_library(library_id, combo.get_active_id())
|
||||
|
||||
def __combobox_new(self, library_id, versions=[], target=None):
|
||||
combo = Gtk.ComboBoxText(visible=True)
|
||||
|
||||
combo.append(None, "")
|
||||
|
||||
for version in versions:
|
||||
combo.append(version, version)
|
||||
|
||||
if target:
|
||||
combo.set_active_id(target)
|
||||
|
||||
combo.connect("changed", self.__on_combobox_changed, library_id)
|
||||
|
||||
return combo
|
@ -1,57 +0,0 @@
|
||||
#
|
||||
# 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()
|
@ -1,50 +0,0 @@
|
||||
<?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>
|
File diff suppressed because it is too large
Load Diff
@ -1,121 +1,88 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_view.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.14"/>
|
||||
<template class="CmbView" parent="GtkBox">
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="webkit2gtk" version="2.28"/>
|
||||
<object class="GtkTextBuffer" id="buffer"/>
|
||||
<object class="WebKitSettings" type-func="webkit_settings_get_type" id="settings">
|
||||
<property name="enable-html5-local-storage">False</property>
|
||||
<property name="enable-html5-database">False</property>
|
||||
<property name="enable-java">False</property>
|
||||
<property name="enable-fullscreen">False</property>
|
||||
<property name="enable-webaudio">False</property>
|
||||
<property name="media-playback-allows-inline">False</property>
|
||||
<property name="user-agent">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15</property>
|
||||
<property name="enable-accelerated-2d-canvas">True</property>
|
||||
<property name="enable-media">False</property>
|
||||
</object>
|
||||
<template class="CmbView" parent="GtkStack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="events">GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
|
||||
<property name="transition-duration">300</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="transition-duration">300</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<object class="WebKitWebView" type-func="webkit_web_view_get_type" id="webview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="settings">settings</property>
|
||||
<signal name="context-menu" handler="on_context_menu" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox" id="compositor_box">
|
||||
<child>
|
||||
<object class="GtkGraphicsOffload" id="compositor_offload">
|
||||
<property name="child">
|
||||
<object class="CasildaCompositor" id="compositor">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="error_box">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="visible">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="error_message">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="yalign">0.7</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restart_button">
|
||||
<property name="halign">center</property>
|
||||
<property name="label">Restart worspace</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="vexpand">True</property>
|
||||
<signal name="clicked" handler="on_restart_button_clicked"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui_view</property>
|
||||
<property name="title" translatable="yes">Project View</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="CmbSourceView" id="text_view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="cursor-visible">False</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="margin-top">4</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui_xml</property>
|
||||
<property name="title" translatable="yes">UI Definition</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="CmbDBInspector" id="db_inspector">
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="margin-top">4</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">data_model</property>
|
||||
<property name="title" translatable="yes">Data Model</property>
|
||||
</object>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">ui_view</property>
|
||||
<property name="title" translatable="yes">Project View</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="cursor-visible">False</property>
|
||||
<property name="buffer">buffer</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-top">4</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="stack">CmbView</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">ui_xml</property>
|
||||
<property name="title" translatable="yes">UI Definition</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -1,5 +1,4 @@
|
||||
VERSION = '@VERSION@'
|
||||
FILE_FORMAT_VERSION = '@fileformatversion@'
|
||||
pkgdatadir = '@pkgdatadir@'
|
||||
merenguedir = '@merenguedir@'
|
||||
catalogsdir = '@catalogsdir@'
|
||||
merenguedir = '@merenguedir@'
|
||||
|
@ -1,32 +0,0 @@
|
||||
# Constants
|
||||
#
|
||||
# Copyright (C) 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
|
||||
#
|
||||
|
||||
# This is the name used for external objects references. See gtk_builder_expose_object()
|
||||
# It is not a valid GType name on purpose since it will never be exported.
|
||||
EXTERNAL_TYPE = "(external)"
|
||||
|
||||
GMENU_TYPE = "(menu)"
|
||||
GMENU_SECTION_TYPE = "(section)"
|
||||
GMENU_SUBMENU_TYPE = "(submenu)"
|
||||
GMENU_ITEM_TYPE = "(item)"
|
@ -1,44 +0,0 @@
|
||||
# Cambalache Property Controls
|
||||
#
|
||||
# Copyright (C) 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
|
||||
#
|
||||
|
||||
from .cmb_boolean_undefined import CmbBooleanUndefined
|
||||
from .cmb_child_type_combo_box import CmbChildTypeComboBox
|
||||
from .cmb_entry import CmbEntry
|
||||
from .cmb_file_button import CmbFileButton
|
||||
from .cmb_file_entry import CmbFileEntry
|
||||
from .cmb_icon_name_entry import CmbIconNameEntry
|
||||
from .cmb_pixbuf_entry import CmbPixbufEntry
|
||||
from .cmb_spin_button import CmbSpinButton
|
||||
from .cmb_text_buffer import CmbTextBuffer
|
||||
from .cmb_toplevel_chooser import CmbToplevelChooser
|
||||
from .cmb_color_entry import CmbColorEntry
|
||||
from .cmb_enum_combo_box import CmbEnumComboBox
|
||||
from .cmb_flags_entry import CmbFlagsEntry
|
||||
from .cmb_object_chooser import CmbObjectChooser
|
||||
from .cmb_object_list_editor import CmbObjectListEditor
|
||||
from .cmb_source_view import CmbSourceView
|
||||
from .cmb_switch import CmbSwitch
|
||||
from .cmb_text_view import CmbTextView
|
||||
from .cmb_translatable_popover import CmbTranslatablePopover
|
||||
from .cmb_create_editor import cmb_create_editor
|
@ -1,78 +0,0 @@
|
||||
#
|
||||
# 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
|
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