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.90.2" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,7 +3,7 @@
|
||||
__pycache__
|
||||
.flatpak-builder
|
||||
.catalogs
|
||||
.local
|
||||
.lib
|
||||
.lc_messages
|
||||
.vscode
|
||||
.env.local
|
||||
@ -17,7 +17,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
|
||||
|
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
|
47
Dockerfile
47
Dockerfile
@ -1,28 +1,23 @@
|
||||
FROM debian:sid-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
desktop-file-utils \
|
||||
gettext \
|
||||
gir1.2-adw-1 \
|
||||
python3-gi \
|
||||
gir1.2-gtk-3.0 \
|
||||
gir1.2-gtk-4.0 \
|
||||
gir1.2-gtksource-5 \
|
||||
gir1.2-gtksource-4 \
|
||||
gir1.2-handy-1 \
|
||||
gir1.2-adw-1 \
|
||||
gir1.2-webkit2-4.1 \
|
||||
gir1.2-webkit-6.0 \
|
||||
git \
|
||||
libadwaita-1-dev \
|
||||
libgirepository-1.0-dev \
|
||||
python3-lxml \
|
||||
meson \
|
||||
ninja-build \
|
||||
libgtk-3-dev \
|
||||
libgtk-4-dev \
|
||||
libhandy-1-dev \
|
||||
libwlroots-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
python3-gi \
|
||||
python3-lxml \
|
||||
python-gi-dev
|
||||
|
||||
libadwaita-1-dev \
|
||||
gettext \
|
||||
desktop-file-utils
|
||||
|
||||
RUN useradd -ms /bin/bash discepolo
|
||||
ENV DISPLAY :0
|
||||
@ -30,31 +25,13 @@ 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 meson --prefix=/usr
|
||||
RUN ninja
|
||||
RUN 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"]
|
3
Makefile
3
Makefile
@ -1,7 +1,4 @@
|
||||
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
|
||||
|
105
README.md
105
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.
|
||||
|
||||

|
||||
|
||||
@ -28,39 +28,26 @@ Source code lives on GNOME gitlab [here](https://gitlab.gnome.org/jpu/cambalache
|
||||
|
||||
* Python 3 - Cambalache is written in Python
|
||||
* [Meson](http://mesonbuild.com) build system
|
||||
* [GTK](http://www.gtk.org) 3 and 4
|
||||
* [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
|
||||
|
||||
## Flathub
|
||||
## Running from sources
|
||||
|
||||
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.
|
||||
To run it without installing use run-dev.py script, it will automatically compile
|
||||
resources and create extra files needed to run.
|
||||
|
||||
Instructions on how to install flatpak can be found [here](https://flatpak.org/setup/).
|
||||
`./run-dev.py`
|
||||
|
||||
You can get the official build [here](https://flathub.org/apps/details/ar.xjuan.Cambalache)
|
||||
|
||||
Use the following to install:
|
||||
```
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak install --user flathub ar.xjuan.Cambalache
|
||||
```
|
||||
The minimum requirements are Gtk 3 and lxml, Gtk 4 is only needed to have a functional Gtk 4 workspace.
|
||||
|
||||
## 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
|
||||
```
|
||||
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
|
||||
@ -74,9 +61,14 @@ make install
|
||||
|
||||
Will create the flatpak repository, then the bundle and install it
|
||||
|
||||
Run as:
|
||||
## Flathub
|
||||
|
||||
You can get Cambalache prebuilt bundles [here](https://flathub.org/apps/details/ar.xjuan.Cambalache)
|
||||
|
||||
Use the following to install:
|
||||
```
|
||||
flatpak run --user ar.xjuan.Cambalache//master
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak install --user flathub ar.xjuan.Cambalache
|
||||
```
|
||||
|
||||
## Manual installation
|
||||
@ -84,20 +76,20 @@ flatpak run --user ar.xjuan.Cambalache//master
|
||||
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 and GI_TYPELIB_PATH 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
|
||||
```
|
||||
|
||||
## Docker
|
||||
@ -110,52 +102,20 @@ 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:
|
||||
On linux, enable localhost connections to your X server and run 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)
|
||||
@ -186,11 +146,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
|
||||
|
@ -5,18 +5,16 @@ Many thanks to all the people that support the project
|
||||
- Stephan McCormick
|
||||
- Willo Vincent
|
||||
- Javier Jardón
|
||||
- Franz Gratzer
|
||||
- David
|
||||
- Sonny Piers
|
||||
- David
|
||||
- Patrick Griffis
|
||||
- Aemilia Scott
|
||||
- Jonathan K.
|
||||
- Luis Barron
|
||||
- Mitch 4J
|
||||
- Aemilia Scott
|
||||
- Michel Fodje
|
||||
- JustRyan
|
||||
- Platon workaccount
|
||||
- Jonathan K.
|
||||
- ~1826340
|
||||
- Mula Gabriel
|
||||
- Felipe Borges
|
||||
- Johannes Deutsch
|
||||
- Patrick
|
||||
|
15
TODO.md
Normal file
15
TODO.md
Normal file
@ -0,0 +1,15 @@
|
||||
## Project:
|
||||
|
||||
- GResource
|
||||
|
||||
|
||||
## GtkBuilder missing features:
|
||||
|
||||
- Internal children
|
||||
- <child internal-child="name">
|
||||
|
||||
- GtkWidget
|
||||
- <action-widgets>
|
||||
<action-widget response="">value</action-widget>
|
||||
</action-widgets>
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"app-id" : "ar.xjuan.Cambalache",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "48",
|
||||
"runtime-version" : "46",
|
||||
"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/30/39/7305428d1c4f28282a4f5bdbef24e0f905d351f34cf351ceb131f5cddf78/lxml-4.9.3.tar.gz",
|
||||
"sha256": "48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -96,15 +47,9 @@
|
||||
{
|
||||
"type" : "git",
|
||||
"path" : ".",
|
||||
"branch" : "HEAD"
|
||||
"branch": "HEAD"
|
||||
}
|
||||
],
|
||||
"config-opts" : [
|
||||
"--libdir=lib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"build-options" : {
|
||||
"env" : { }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -19,8 +19,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
@ -30,12 +28,10 @@ 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__:
|
||||
@ -57,7 +53,7 @@ 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)
|
||||
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
# FIXME: this is needed in flatpak for icons to work
|
||||
Gtk.IconTheme.get_for_display(display).add_search_path("/app/share/icons")
|
||||
@ -70,17 +66,15 @@ def getLogger(name):
|
||||
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_property import CmbProperty
|
||||
@ -89,19 +83,14 @@ 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
|
||||
|
@ -19,8 +19,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
|
@ -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>
|
||||
|
@ -38,25 +38,7 @@ window.cmb-window label.message {
|
||||
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 {
|
||||
window.cmb-window.dark label.message {
|
||||
color: black;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
}
|
||||
@ -86,9 +68,7 @@ popover.cmb-tutor image {
|
||||
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,
|
||||
@ -107,14 +87,11 @@ CmbTypeChooser {
|
||||
|
||||
CmbUIEditor,
|
||||
CmbFragmentEditor,
|
||||
CmbObjectEditor,
|
||||
CmbAccessibleEditor {
|
||||
CmbObjectEditor {
|
||||
padding: 0 4px 4px 4px;
|
||||
}
|
||||
|
||||
CmbCSSEditor,
|
||||
CmbGResourceEditor,
|
||||
CmbGResourceFileEditor,
|
||||
stackswitcher.property-pane {
|
||||
padding: 4px;
|
||||
}
|
||||
@ -134,7 +111,3 @@ image.icon-size-32 {
|
||||
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
|
||||
|
@ -20,59 +20,91 @@
|
||||
# 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
|
||||
from gi.repository import GLib, Gdk, Gtk, Gio
|
||||
|
||||
from .cmb_window import CmbWindow
|
||||
from cambalache import CmbProject, utils, config, _
|
||||
from cambalache import CmbProject, config, _
|
||||
|
||||
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)
|
||||
|
||||
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("open-project", self.__on_open_project)
|
||||
|
||||
window.connect("close-request", self.__on_window_close_request)
|
||||
window.connect("project-closed", self.__on_window_project_closed)
|
||||
self.add_window(window)
|
||||
return window
|
||||
|
||||
def open_project(self, path, target_tk=None):
|
||||
def open_project(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):
|
||||
def do_open(self, files, nfiles, hint):
|
||||
for file in files:
|
||||
path = file.get_path()
|
||||
|
||||
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_project(path)
|
||||
elif content_type in ["application/x-gtk-builder", "application/x-glade"]:
|
||||
self.import_file(path)
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
for action in ["quit"]:
|
||||
gaction = Gio.SimpleAction.new(action, None)
|
||||
gaction.connect("activate", getattr(self, f"_on_{action}_activate"))
|
||||
self.add_action(gaction)
|
||||
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_resource("/ar/xjuan/Cambalache/app/cambalache.css")
|
||||
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
def do_activate(self):
|
||||
if self.props.active_window is None:
|
||||
self.open_project(None)
|
||||
|
||||
def __on_open_project(self, window, filename, target_tk, uiname):
|
||||
if window.project is None:
|
||||
window.open_project(filename, target_tk, uiname)
|
||||
else:
|
||||
self.open_project(filename, target_tk, uiname)
|
||||
|
||||
def __check_can_quit(self, window=None):
|
||||
windows = self.__get_windows() if window is None else [window]
|
||||
unsaved_windows = []
|
||||
windows2save = []
|
||||
@ -98,8 +130,26 @@ class CmbApplication(Adw.Application):
|
||||
return
|
||||
|
||||
# Create Dialog
|
||||
window = windows[0]
|
||||
dialog = window._close_project_dialog_new()
|
||||
text = _("Save changes before closing?")
|
||||
dialog = Gtk.MessageDialog(
|
||||
transient_for=windows[0],
|
||||
message_type=Gtk.MessageType.QUESTION,
|
||||
text=f"<b><big>{text}</big></b>",
|
||||
use_markup=True,
|
||||
modal=True,
|
||||
)
|
||||
|
||||
# Add buttons
|
||||
dialog.add_buttons(
|
||||
_("Close without Saving"),
|
||||
Gtk.ResponseType.CLOSE,
|
||||
_("Cancel"),
|
||||
Gtk.ResponseType.CANCEL,
|
||||
_("Save"),
|
||||
Gtk.ResponseType.ACCEPT,
|
||||
)
|
||||
|
||||
dialog.set_default_response(Gtk.ResponseType.ACCEPT)
|
||||
|
||||
if unsaved_windows_len > 1 or unsaved_windows[0].project.filename is None:
|
||||
# Add checkbox for each unsaved project
|
||||
@ -164,96 +214,28 @@ class CmbApplication(Adw.Application):
|
||||
return retval
|
||||
|
||||
def __on_window_close_request(self, window):
|
||||
self.check_can_quit(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"]:
|
||||
self.import_file(path)
|
||||
|
||||
def do_startup(self):
|
||||
Adw.Application.do_startup(self)
|
||||
|
||||
for action, accelerators, parameter_type in [
|
||||
("quit", ["<Primary>q"], None),
|
||||
("open", None, "(ss)"),
|
||||
("new", None, "(sss)"),
|
||||
]:
|
||||
gaction = Gio.SimpleAction.new(action, GLib.VariantType.new(parameter_type) if parameter_type else None)
|
||||
gaction.connect("activate", getattr(self, f"_on_{action}_activate"))
|
||||
self.add_action(gaction)
|
||||
if accelerators:
|
||||
self.set_accels_for_action(f"app.{action}", accelerators)
|
||||
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_resource("/ar/xjuan/Cambalache/app/cambalache.css")
|
||||
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||
|
||||
def do_activate(self):
|
||||
if self.props.active_window is None:
|
||||
self.open_project(None)
|
||||
|
||||
def do_window_removed(self, window):
|
||||
windows = self.__get_windows()
|
||||
|
||||
if len(windows) == 0:
|
||||
self.activate_action("quit")
|
||||
|
||||
def _on_quit_activate(self, action, data):
|
||||
self.__check_can_quit()
|
||||
|
||||
def do_handle_local_options(self, options):
|
||||
if options.contains("version"):
|
||||
print(config.VERSION)
|
||||
return 0
|
||||
|
||||
if options.contains("export-all"):
|
||||
print("Export has been deprecated and does nothing. Every UI file is updated on project save.")
|
||||
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
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_shortcuts.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="GtkShortcutsWindow" id="shortcuts">
|
||||
<property name="section-name">shortcuts</property>
|
||||
@ -15,56 +14,32 @@
|
||||
<property name="view">shortcuts</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>
|
||||
</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>
|
||||
</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="accelerator"><Control>s</property>
|
||||
<property name="title" translatable="yes">Save the project</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>e</property>
|
||||
<property name="title" translatable="yes">Save and Export</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@ -75,31 +50,25 @@
|
||||
<property name="view">shortcuts</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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -20,8 +20,6 @@
|
||||
# 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
|
||||
#
|
||||
|
||||
|
@ -20,33 +20,25 @@
|
||||
# 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),
|
||||
(_("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),
|
||||
(_("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,
|
||||
),
|
||||
(_("Add new UI file"), "add_button", 3),
|
||||
(_("and Save are directly accessible in the headerbar"), "cmb_save_button", 6),
|
||||
(_("just like Save As"), "save_as_button", 2),
|
||||
(_("and the main menu"), "menu_button", 3, "menu_button"),
|
||||
(_("Create a project to continue"), "intro_button", 2, "add-project"),
|
||||
(_("Great!"), "intro_button", 2),
|
||||
(
|
||||
@ -72,6 +64,13 @@ intro = [
|
||||
(_("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,
|
||||
),
|
||||
(
|
||||
_("If you have any question, contact us on Matrix!"),
|
||||
_("Contact"),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +1,47 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_window.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<requires lib="gtk" version="4.14"/>
|
||||
<requires lib="libadwaita" version="1.5"/>
|
||||
<object class="GtkFileFilter" id="import_filter">
|
||||
<mime-types>
|
||||
<mime-type>application/x-glade</mime-type>
|
||||
<mime-type>application/x-gtk-builder</mime-type>
|
||||
</mime-types>
|
||||
<patterns>
|
||||
<pattern>*.ui</pattern>
|
||||
<pattern>*.glade</pattern>
|
||||
</patterns>
|
||||
</object>
|
||||
<menu id="main_menu">
|
||||
<item>
|
||||
<attribute name="action">win.create_new</attribute>
|
||||
<attribute name="label" translatable="yes">New Project</attribute>
|
||||
</item>
|
||||
<section>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Add file</attribute>
|
||||
<item>
|
||||
<attribute name="action">win.add_ui</attribute>
|
||||
<attribute name="label" translatable="yes">Add UI</attribute>
|
||||
<attribute name="action">win.import</attribute>
|
||||
<attribute name="label" translatable="yes">Import</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.export</attribute>
|
||||
<attribute name="label" translatable="yes">Export all</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.add_css</attribute>
|
||||
<attribute name="label" translatable="yes">Add CSS</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.add_gresource</attribute>
|
||||
<attribute name="label" translatable="yes">Add GResource</attribute>
|
||||
</item>
|
||||
</submenu>
|
||||
<item>
|
||||
<attribute name="action">win.import</attribute>
|
||||
<attribute name="label" translatable="yes">Import file</attribute>
|
||||
<attribute name="label" translatable="yes">Add CSS file</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.save</attribute>
|
||||
<attribute name="label" translatable="yes">Save</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.save_as</attribute>
|
||||
<attribute name="label" translatable="yes">Save As</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.close</attribute>
|
||||
<attribute name="label" translatable="yes">Close Project</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.debug</attribute>
|
||||
<attribute name="label" translatable="yes">Debug Project Data</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.intro</attribute>
|
||||
<attribute name="label" translatable="yes">Interactive intro</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.notification</attribute>
|
||||
<attribute name="label" translatable="yes">Notifications</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.contact</attribute>
|
||||
<attribute name="label" translatable="yes">Contact</attribute>
|
||||
@ -81,92 +68,7 @@
|
||||
<mime-type>application/x-cambalache-project</mime-type>
|
||||
</mime-types>
|
||||
</object>
|
||||
<template class="CmbWindow" parent="AdwApplicationWindow">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwHeaderBar" id="headerbar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle" id="title">
|
||||
<property name="title">Cambalache</property>
|
||||
</object>
|
||||
</property>
|
||||
<child>
|
||||
<object class="AdwSplitButton" id="open_button">
|
||||
<property name="action-name">win.open</property>
|
||||
<property name="dropdown-tooltip">Recent Projects</property>
|
||||
<property name="label" translatable="yes">_Open</property>
|
||||
<property name="menu-model">recent_menu</property>
|
||||
<property name="use-underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="undo_button">
|
||||
<property name="action-name">win.undo</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-undo-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="redo_button">
|
||||
<property name="action-name">win.redo</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="use-underline">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-redo-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="menu_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="menu-model">main_menu</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">open-menu-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="add_button">
|
||||
<property name="action-name">win.add_ui</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Add new UI to project</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="intro_button">
|
||||
<property name="action-name">win.intro</property>
|
||||
<property name="has-frame">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Start interactive introduction</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">start-here-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<template class="CmbWindow" parent="GtkApplicationWindow">
|
||||
<child>
|
||||
<object class="GtkOverlay">
|
||||
<property name="child">
|
||||
@ -177,19 +79,13 @@
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="CmbNotificationListView" id="front_notification_list_view">
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="version_label">
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-bottom">4</property>
|
||||
<property name="name">version</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">1</property>
|
||||
<attributes>
|
||||
<attribute name="size" value="9000"/>
|
||||
</attributes>
|
||||
@ -259,7 +155,7 @@
|
||||
<child>
|
||||
<object class="GtkEntry" id="np_name_entry">
|
||||
<property name="focusable">1</property>
|
||||
<property name="input-hints">lowercase|none</property>
|
||||
<property name="input-hints">GTK_INPUT_HINT_LOWERCASE | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="input-purpose">alpha</property>
|
||||
<property name="placeholder-text" translatable="yes"><project basename></property>
|
||||
<property name="width-chars">32</property>
|
||||
@ -409,7 +305,7 @@
|
||||
<child>
|
||||
<object class="GtkEntry" id="np_ui_entry">
|
||||
<property name="focusable">1</property>
|
||||
<property name="input-hints">lowercase|none</property>
|
||||
<property name="input-hints">GTK_INPUT_HINT_LOWERCASE | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="input-purpose">alpha</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="width-chars">32</property>
|
||||
@ -493,19 +389,6 @@
|
||||
<property name="title" translatable="yes">Signals</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="CmbAccessibleEditor" id="accessible_editor"/>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">accessible</property>
|
||||
<property name="title">a11y</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
@ -548,6 +431,7 @@
|
||||
<object class="CmbUIEditor" id="ui_editor">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<signal name="remove-ui" handler="on_ui_editor_remove_ui"/>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">properties</property>
|
||||
@ -590,21 +474,13 @@
|
||||
<object class="CmbCSSEditor" id="css_editor">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<signal name="remove-css" handler="on_css_editor_remove_ui"/>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">css</property>
|
||||
<property name="title" translatable="yes">CSS Editor</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbGResourceEditor" id="gresource_editor"/>
|
||||
</property>
|
||||
<property name="name">gresource</property>
|
||||
<property name="title">GResource Editor</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
@ -615,16 +491,11 @@
|
||||
<property name="start-child">
|
||||
<object class="GtkPaned">
|
||||
<property name="end-child">
|
||||
<object class="GtkStack" id="workspace_stack">
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="CmbTypeChooser" id="type_chooser">
|
||||
<property name="spacing">2</property>
|
||||
<property name="valign">start</property>
|
||||
<signal name="chooser-popdown" handler="on_type_chooser_chooser_popdown"/>
|
||||
<signal name="chooser-popup" handler="on_type_chooser_chooser_popup"/>
|
||||
<signal name="type-selected" handler="on_type_chooser_type_selected"/>
|
||||
@ -639,26 +510,6 @@
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="CmbSourceView" id="source_view">
|
||||
<property name="editable">False</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">gresource</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="resize-start-child">0</property>
|
||||
<property name="shrink-end-child">0</property>
|
||||
@ -666,13 +517,20 @@
|
||||
<property name="start-child">
|
||||
<object class="GtkBox" id="inspector">
|
||||
<property name="focusable">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="CmbScrolledWindow">
|
||||
<property name="min-content-width">200</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="propagate-natural-width">True</property>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="min-content-width">256</property>
|
||||
<property name="propagate-natural-height">1</property>
|
||||
<property name="propagate-natural-width">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="CmbListView" id="list_view"/>
|
||||
<object class="CmbTreeView" id="tree_view">
|
||||
<property name="focusable">1</property>
|
||||
<property name="headers-clickable">False</property>
|
||||
<property name="headers-visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
@ -819,59 +677,189 @@
|
||||
<property name="halign">center</property>
|
||||
<property name="transition-type">slide-up</property>
|
||||
<property name="valign">end</property>
|
||||
<style/>
|
||||
<style>
|
||||
<class name="message"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="headerbar">
|
||||
<child type="title">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Cambalache</property>
|
||||
<property name="vexpand">true</property>
|
||||
<style>
|
||||
<class name="title"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="subtitle">
|
||||
<property name="visible">false</property>
|
||||
<style>
|
||||
<class name="subtitle"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="menu_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="menu-model">main_menu</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">open-menu-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="open_button_box">
|
||||
<child>
|
||||
<object class="GtkButton" id="open_button">
|
||||
<property name="action-name">win.open</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="label" translatable="yes">_Open</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="tooltip-text" translatable="yes">Open a project</property>
|
||||
<property name="use-underline">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="recent_button">
|
||||
<property name="menu-model">recent_menu</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">pan-down-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="new_button">
|
||||
<property name="action-name">win.create_new</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="tooltip-text" translatable="yes">Create a new project</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">document-new-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkBox" id="save_button_box">
|
||||
<child>
|
||||
<object class="GtkButton" id="cmb_save_button">
|
||||
<property name="action-name">win.save</property>
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<property name="tooltip-text" translatable="yes">Save project</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_as_button">
|
||||
<property name="action-name">win.save_as</property>
|
||||
<property name="tooltip-text" translatable="yes">Save project with a different file name</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">document-save-as-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator"/>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="add_button">
|
||||
<property name="action-name">win.add_ui</property>
|
||||
<property name="tooltip-text" translatable="yes">Add new UI to project</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="intro_button">
|
||||
<property name="action-name">win.intro</property>
|
||||
<property name="tooltip-text" translatable="yes">Start interactive introduction</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">start-here-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkButton" id="undo_button">
|
||||
<property name="action-name">win.undo</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-undo-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="redo_button">
|
||||
<property name="action-name">win.redo</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="use-underline">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-redo-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="cmb-window"/>
|
||||
</style>
|
||||
</template>
|
||||
<object class="GtkFileFilter" id="gtk_builder_filter">
|
||||
<property name="mime-types">application/x-gtk-builder</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="glade_filter">
|
||||
<property name="mime-types">application/x-glade</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="blueprint_filter">
|
||||
<property name="mime-types">text/x-blueprint</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="css_filter">
|
||||
<property name="mime-types">text/css</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="gresource_filter">
|
||||
<property name="suffixes">gresource.xml</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="gtk4_filter">
|
||||
<property name="mime-types">application/x-gtk-builder
|
||||
text/x-blueprint
|
||||
text/css</property>
|
||||
<property name="name">All supported files</property>
|
||||
<property name="suffixes">gresource.xml</property>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="gtk3_filter">
|
||||
<property name="mime-types">application/x-gtk-builder
|
||||
application/x-glade
|
||||
text/css</property>
|
||||
<property name="name">All supported files</property>
|
||||
<property name="suffixes">gresource.xml</property>
|
||||
</object>
|
||||
<object class="AdwDialog" id="notification_dialog">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwHeaderBar"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbNotificationListView" id="notification_list_view"/>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="follows-content-size">True</property>
|
||||
<property name="title" translatable="yes">Notifications</property>
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="artists">Franco Dodorico
|
||||
Juan Pablo Ugarte</property>
|
||||
<property name="authors">Juan Pablo Ugarte</property>
|
||||
<property name="comments" translatable="yes">User Interface Maker</property>
|
||||
<property name="copyright">© 2020-2024 Juan Pablo Ugarte</property>
|
||||
<property name="license-type">lgpl-2-1-only</property>
|
||||
<property name="logo-icon-name">ar.xjuan.Cambalache</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="program-name">Cambalache</property>
|
||||
<property name="transient-for">CmbWindow</property>
|
||||
<property name="website">https://gitlab.gnome.org/jpu/cambalache</property>
|
||||
<property name="website-label" translatable="yes">Source repository and issue tracker</property>
|
||||
</object>
|
||||
</interface>
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
@ -40,16 +40,6 @@ 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;
|
||||
@ -61,22 +51,6 @@ CmbPropertyLabel {
|
||||
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 {
|
||||
font-style: italic;
|
||||
}
|
||||
@ -84,48 +58,3 @@ CmbPropertyLabel.modified > box > label {
|
||||
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;
|
||||
}
|
||||
|
@ -1,31 +1,21 @@
|
||||
<?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>control/cmb_translatable_widget.ui</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>cmb_css_editor.ui</file>
|
||||
<file>cmb_fragment_editor.ui</file>
|
||||
<file>cmb_object_data_editor.ui</file>
|
||||
<file>cmb_signal_editor.ui</file>
|
||||
<file>cambalache.css</file>
|
||||
<file>icons/scalable/actions/bind-symbolic.svg</file>
|
||||
<file>cmb_notification_list_row.ui</file>
|
||||
<file>icons/scalable/actions/binded-symbolic.svg</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,12 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
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)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
@ -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()
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
@ -71,10 +69,6 @@ class CmbContextMenu(Gtk.PopoverMenu):
|
||||
"GtkAligment",
|
||||
"GtkEventBox"
|
||||
]
|
||||
else:
|
||||
types += [
|
||||
"GtkGraphicsOffload",
|
||||
]
|
||||
|
||||
self.add_submenu.remove_all()
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_context_menu.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<menu id="menu_model">
|
||||
<section>
|
||||
<item>
|
||||
|
@ -20,14 +20,9 @@
|
||||
# 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 _
|
||||
|
||||
@ -37,7 +32,6 @@ class CmbCSS(CmbBaseCSS):
|
||||
"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):
|
||||
@ -57,13 +51,8 @@ class CmbCSS(CmbBaseCSS):
|
||||
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)
|
||||
def get_display_name(self):
|
||||
return self.filename if self.filename else _("Unnamed CSS {css_id}").format(css_id=self.css_id)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def priority(self):
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
from cambalache import utils, _
|
||||
@ -81,7 +79,6 @@ class CmbCSSEditor(Gtk.Grid):
|
||||
self.set_sensitive(False)
|
||||
return
|
||||
|
||||
self.filename.dirname = obj.project.dirname
|
||||
self.set_sensitive(True)
|
||||
|
||||
for field, target in self.fields:
|
||||
@ -108,6 +105,10 @@ class CmbCSSEditor(Gtk.Grid):
|
||||
self.__update_provider_for()
|
||||
self.__update_ui_button_label()
|
||||
|
||||
@Gtk.Template.Callback("on_remove_button_clicked")
|
||||
def __on_remove_button_clicked(self, button):
|
||||
self.emit("remove-css")
|
||||
|
||||
@Gtk.Template.Callback("on_save_button_clicked")
|
||||
def __on_save_button_clicked(self, button):
|
||||
self._object.save_css()
|
||||
@ -135,7 +136,7 @@ class CmbCSSEditor(Gtk.Grid):
|
||||
# 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
|
||||
label=ui.get_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)
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<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>
|
||||
@ -39,8 +38,11 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<object class="CmbEntry" id="filename">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="placeholder-text" translatable="yes"><file name relative to project></property>
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
@ -108,6 +110,20 @@
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="focusable">1</property>
|
||||
<!-- <property name="tooltip-text" translatable="1">Remove CSS file from project</property> -->
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">end</property>
|
||||
<signal name="clicked" handler="on_remove_button_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">app-remove-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">center</property>
|
||||
@ -173,6 +189,7 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Custom object fragments -->
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
1549
cambalache/cmb_db.py
1549
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>
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
|
||||
def ensure_columns_for_0_7_5(table, data):
|
||||
@ -39,24 +37,24 @@ def migrate_table_data_to_0_7_5(c, table, data):
|
||||
if table == "object":
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
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
|
||||
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;
|
||||
WHERE object.ui_id=new.ui_id AND object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
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
|
||||
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;
|
||||
"""
|
||||
)
|
||||
|
||||
@ -74,9 +72,9 @@ def migrate_table_data_to_0_9_0(c, table, data):
|
||||
# Remove all object properties with a 0 as value
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM temp.object_property AS op
|
||||
DELETE FROM 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)
|
||||
(SELECT property_id FROM property WHERE owner_id=op.owner_id AND property_id=op.property_id AND is_object)
|
||||
IS NOT NULL;
|
||||
"""
|
||||
)
|
||||
@ -118,9 +116,9 @@ 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
|
||||
UPDATE {table} SET translatable=1
|
||||
WHERE translatable IS NOT NULL AND lower(translatable) IN (1, 'y', 'yes', 't', 'true');
|
||||
UPDATE temp.{table} SET translatable=NULL
|
||||
UPDATE {table} SET translatable=NULL
|
||||
WHERE translatable IS NOT NULL AND translatable != 1;
|
||||
"""
|
||||
)
|
||||
@ -129,35 +127,8 @@ def migrate_table_data_to_0_17_3(c, table, data):
|
||||
for prop in ["swap", "after"]:
|
||||
c.executescript(
|
||||
f"""
|
||||
UPDATE temp.object_signal SET {prop}=1
|
||||
UPDATE 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;
|
||||
UPDATE object_signal SET {prop}=NULL WHERE {prop} IS NOT NULL AND after != 1;
|
||||
"""
|
||||
)
|
||||
|
@ -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
|
@ -20,8 +20,6 @@
|
||||
# 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
|
||||
@ -70,7 +68,7 @@ class CmbFragmentEditor(Gtk.Box):
|
||||
self.__bindings.append(binding)
|
||||
|
||||
# Only objects have child fragments
|
||||
if type(obj) is CmbObject and obj.parent:
|
||||
if type(obj) is CmbObject and obj.parent is not None:
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
"custom-child-fragment",
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<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>
|
||||
|
@ -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,13 +20,10 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseLayoutProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from .cmb_objects_base import CmbBaseLayoutProperty, CmbPropertyInfo
|
||||
from . import utils
|
||||
|
||||
|
||||
@ -35,7 +32,9 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
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.__on_init = False
|
||||
self.version_warning = None
|
||||
|
||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||
@ -106,6 +105,13 @@ class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
(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):
|
||||
|
@ -20,16 +20,11 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
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)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Cambalache GListModel error item
|
||||
# CmbListStore - Cambalache List Store
|
||||
#
|
||||
# Copyright (C) 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,23 +20,27 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
from .cmb_base import CmbBase
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
||||
# This class is used by GListModel implementations when they do not know which item to return
|
||||
class CmbListError(CmbBase):
|
||||
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)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
return "list error"
|
||||
data = self.project._get_table_data(self.table)
|
||||
self.set_column_types(data["types"])
|
||||
self.__populate()
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
return 0
|
||||
def __populate(self):
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(self.query):
|
||||
self.append(row)
|
||||
|
||||
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,26 +20,22 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
from gi.repository import GObject
|
||||
|
||||
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, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
class CmbObject(CmbBaseObject):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
__gsignals__ = {
|
||||
@ -50,23 +46,20 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
"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)),
|
||||
}
|
||||
|
||||
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.properties = []
|
||||
self.properties_dict = {}
|
||||
self.layout = []
|
||||
self.layout_dict = {}
|
||||
self.signals = []
|
||||
self.signals_dict = {}
|
||||
self.data = []
|
||||
self.data_dict = {}
|
||||
self.position_layout_property = None
|
||||
self.inline_property_id = None
|
||||
self.version_warning = None
|
||||
self.__is_template = False
|
||||
|
||||
self._last_known = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@ -75,79 +68,26 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
self.__update_inline_property_id()
|
||||
# Append object to project automatically
|
||||
self.project._append_object(self)
|
||||
|
||||
self.__populate_properties()
|
||||
self.__populate_layout_properties()
|
||||
self.__populate_signals()
|
||||
self.__populate_data()
|
||||
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
|
||||
|
||||
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,
|
||||
@ -160,31 +100,16 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
)
|
||||
|
||||
# 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:
|
||||
@ -206,10 +131,14 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
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)
|
||||
@ -221,9 +150,8 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
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.signals.append(signal)
|
||||
self.signals_dict[signal.signal_pk] = signal
|
||||
self.emit("signal-added", signal)
|
||||
self.project._object_signal_added(self, signal)
|
||||
|
||||
@ -234,11 +162,11 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
self.project._object_signal_changed(self, signal)
|
||||
|
||||
def __add_data_object(self, data):
|
||||
if data.get_id_string() in self.data_dict:
|
||||
if data in self.data:
|
||||
return
|
||||
|
||||
self.__data.append(data)
|
||||
self.__data_dict[data.get_id_string()] = data
|
||||
self.data.append(data)
|
||||
self.data_dict[data.get_id_string()] = data
|
||||
self.emit("data-added", data)
|
||||
self.project._object_data_added(self, data)
|
||||
|
||||
@ -249,11 +177,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
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
|
||||
@ -261,11 +184,6 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
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
|
||||
@ -279,17 +197,13 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
parent_id = self.parent_id
|
||||
|
||||
# FIXME: delete is anything is set?
|
||||
self.__layout = []
|
||||
self.__layout_dict = {}
|
||||
self.layout = []
|
||||
self.layout_dict = {}
|
||||
|
||||
if parent_id > 0:
|
||||
# FIXME: what about parent layout properties?
|
||||
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")
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def parent_id(self):
|
||||
@ -304,41 +218,14 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
|
||||
@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, )
|
||||
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,
|
||||
)
|
||||
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.__populate_layout_properties()
|
||||
|
||||
@ -400,8 +287,8 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
)
|
||||
|
||||
def _remove_signal(self, signal):
|
||||
self.__signals.remove(signal)
|
||||
del self.__signals_dict[signal.signal_pk]
|
||||
self.signals.remove(signal)
|
||||
del self.signals_dict[signal.signal_pk]
|
||||
|
||||
self.emit("signal-removed", signal)
|
||||
self.project._object_signal_removed(self, signal)
|
||||
@ -446,29 +333,23 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
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:
|
||||
if data not in self.data:
|
||||
return
|
||||
|
||||
self.__data.remove(data)
|
||||
del self.__data_dict[data.get_id_string()]
|
||||
self.data.remove(data)
|
||||
del self.data_dict[data.get_id_string()]
|
||||
|
||||
self.emit("data-removed", data)
|
||||
self.project._object_data_removed(self, 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)
|
||||
)
|
||||
|
||||
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}")
|
||||
return False
|
||||
@ -485,76 +366,45 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
logger.warning(f"{child} is not children of {self}")
|
||||
return
|
||||
|
||||
old_position = child.position
|
||||
old_list_position = child.list_position
|
||||
if old_position == position:
|
||||
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)
|
||||
_("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 [
|
||||
(
|
||||
# Get children in order
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"""
|
||||
SELECT ui_id, object_id
|
||||
SELECT object_id, position
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=? AND position <= ? AND position > ?
|
||||
ORDER BY position ASC
|
||||
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;
|
||||
""",
|
||||
"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))
|
||||
(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)
|
||||
# Update all positions
|
||||
for pos, obj in enumerate(children):
|
||||
# Sync layout property
|
||||
if obj.position_layout_property:
|
||||
obj.position_layout_property.value = pos
|
||||
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
|
||||
# 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()
|
||||
@ -576,171 +426,22 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
c.close()
|
||||
|
||||
for prop_id in properties:
|
||||
prop = self.__properties_dict[prop_id]
|
||||
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 get_display_name(self):
|
||||
return self.name if self.name is not None else self.type_id
|
||||
|
||||
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 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
|
||||
|
@ -20,14 +20,12 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseObjectData
|
||||
from .cmb_type_info import CmbTypeDataInfo
|
||||
from cambalache import getLogger, _
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
@ -61,6 +59,9 @@ class CmbObjectData(CmbBaseObjectData):
|
||||
if self.object is None:
|
||||
self.object = self.project.get_object_by_id(self.ui_id, self.object_id)
|
||||
|
||||
if self.parent_id is not None and self.parent is None:
|
||||
self.parent = self.object.data_dict.get(f"{self.owner_id}.{self.parent_id}", None)
|
||||
|
||||
self.__populate_children()
|
||||
self.connect("notify", self._on_notify)
|
||||
|
||||
@ -175,7 +176,6 @@ class CmbObjectData(CmbBaseObjectData):
|
||||
(self.ui_id, self.object_id, self.owner_id, self.id),
|
||||
):
|
||||
obj = CmbObjectData.from_row(self.project, *row)
|
||||
obj.parent = self
|
||||
self.__add_child(obj)
|
||||
|
||||
def add_data(self, data_key, value=None, comment=None):
|
||||
@ -194,17 +194,11 @@ class CmbObjectData(CmbBaseObjectData):
|
||||
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.commit()
|
||||
self.project.history_pop()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||
return False
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
@ -31,12 +29,6 @@ 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"
|
||||
@ -75,7 +67,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
||||
def __on_remove_clicked(self, button):
|
||||
if self.info:
|
||||
self.object.remove_data(self.__data)
|
||||
elif self.__data:
|
||||
else:
|
||||
self.__data.parent.remove_data(self.__data)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
@ -123,11 +115,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
||||
|
||||
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
|
||||
editor.cmb_value = self.data.get_arg(key)
|
||||
|
||||
def __on_data_data_added(self, parent, data):
|
||||
self.__add_data_editor(data)
|
||||
@ -144,6 +132,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
||||
self.__update_view()
|
||||
|
||||
def __on_data_removed(self, obj, data):
|
||||
if self.object and self.info:
|
||||
self.__remove_data_editor(data)
|
||||
|
||||
def __ensure_object_data(self, history_message):
|
||||
@ -250,7 +239,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
||||
|
||||
# Value
|
||||
if info.type_id:
|
||||
editor = cmb_create_editor(project, info.type_id, data=self.data, parent=self.object)
|
||||
editor = cmb_create_editor(project, info.type_id, data=self.data)
|
||||
self.__value_editor = editor
|
||||
|
||||
if self.data:
|
||||
@ -270,7 +259,7 @@ class CmbObjectDataEditor(Gtk.Box):
|
||||
for arg in info.args:
|
||||
arg_info = info.args[arg]
|
||||
|
||||
editor = cmb_create_editor(project, arg_info.type_id, parent=self.object)
|
||||
editor = cmb_create_editor(project, arg_info.type_id)
|
||||
self.__arg_editors[arg_info.key] = editor
|
||||
|
||||
# Initialize value
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<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>
|
||||
@ -70,6 +69,7 @@
|
||||
<object class="GtkGrid" id="grid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<property name="vexpand">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
@ -43,17 +41,11 @@ class CmbObjectEditor(Gtk.Box):
|
||||
self.__object = None
|
||||
self.__id_label = None
|
||||
self.__template_switch = None
|
||||
self.__bindings = []
|
||||
|
||||
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)
|
||||
|
||||
@ -62,7 +54,7 @@ class CmbObjectEditor(Gtk.Box):
|
||||
|
||||
# Id/Class entry
|
||||
entry = CmbEntry()
|
||||
self.bind_property(
|
||||
GObject.Object.bind_property(
|
||||
self.__object,
|
||||
"name",
|
||||
entry,
|
||||
@ -74,7 +66,7 @@ class CmbObjectEditor(Gtk.Box):
|
||||
grid.attach(entry, 1, 0, 1, 1)
|
||||
|
||||
# Template check
|
||||
if self.__object and self.__object.parent_id == 0:
|
||||
if self.__object and not self.__object.parent_id:
|
||||
is_template = self.__object.object_id == self.__object.ui.template_id
|
||||
tooltip_text = _("Switch between object and template")
|
||||
derivable = self.__object.info.derivable
|
||||
@ -137,7 +129,7 @@ class CmbObjectEditor(Gtk.Box):
|
||||
|
||||
combo = CmbChildTypeComboBox(object=self.__object)
|
||||
|
||||
self.bind_property(
|
||||
GObject.Object.bind_property(
|
||||
self.__object,
|
||||
"type",
|
||||
combo,
|
||||
@ -215,16 +207,12 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
if prop is None or prop.info 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)
|
||||
|
||||
if editor is None:
|
||||
continue
|
||||
|
||||
self.bind_property(
|
||||
GObject.Object.bind_property(
|
||||
prop,
|
||||
"value",
|
||||
editor,
|
||||
@ -237,10 +225,7 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
else:
|
||||
label = CmbPropertyLabel(prop=prop, bindable=not is_builtin)
|
||||
|
||||
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
|
||||
# Keep a dict of labels
|
||||
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
@ -260,7 +245,7 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
hexpand=True,
|
||||
object=obj,
|
||||
data=data,
|
||||
info=info.data[data_key],
|
||||
info=None if data else info.data[data_key],
|
||||
)
|
||||
|
||||
grid.attach(editor, 0, i, 2, 1)
|
||||
@ -301,16 +286,11 @@ It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
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 = obj
|
||||
|
||||
if obj:
|
||||
obj.connect("notify", self.__on_object_notify)
|
||||
obj.ui.connect("notify", self.__on_object_ui_notify)
|
||||
self.__object.connect("notify", self.__on_object_notify)
|
||||
self.__object.ui.connect("notify", self.__on_object_ui_notify)
|
||||
|
||||
self.__update_view()
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
#
|
||||
# Cambalache Base Object wrappers
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021-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
|
||||
@ -22,8 +22,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
from .cmb_base import CmbBase
|
||||
@ -57,8 +55,8 @@ class CmbBaseLibraryInfo(CmbBase):
|
||||
)
|
||||
|
||||
|
||||
class CmbBasePropertyInfo(CmbBase):
|
||||
__gtype_name__ = "CmbBasePropertyInfo"
|
||||
class CmbPropertyInfo(CmbBase):
|
||||
__gtype_name__ = "CmbPropertyInfo"
|
||||
|
||||
owner_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
property_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
@ -83,15 +81,13 @@ class CmbBasePropertyInfo(CmbBase):
|
||||
disable_inline_object = GObject.Property(
|
||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||
)
|
||||
deprecated = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
is_position = GObject.Property(
|
||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||
)
|
||||
required = GObject.Property(
|
||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||
)
|
||||
workspace_default = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
original_owner_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
disabled = GObject.Property(
|
||||
type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False
|
||||
)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@ -113,11 +109,9 @@ class CmbBasePropertyInfo(CmbBase):
|
||||
deprecated_version,
|
||||
translatable,
|
||||
disable_inline_object,
|
||||
deprecated,
|
||||
is_position,
|
||||
required,
|
||||
workspace_default,
|
||||
original_owner_id,
|
||||
disabled,
|
||||
):
|
||||
return cls(
|
||||
project=project,
|
||||
@ -134,11 +128,9 @@ class CmbBasePropertyInfo(CmbBase):
|
||||
deprecated_version=deprecated_version,
|
||||
translatable=translatable,
|
||||
disable_inline_object=disable_inline_object,
|
||||
deprecated=deprecated,
|
||||
is_position=is_position,
|
||||
required=required,
|
||||
workspace_default=workspace_default,
|
||||
original_owner_id=original_owner_id,
|
||||
disabled=disabled,
|
||||
)
|
||||
|
||||
|
||||
@ -285,30 +277,6 @@ class CmbTypeChildInfo(CmbBase):
|
||||
)
|
||||
|
||||
|
||||
class CmbBaseTypeInternalChildInfo(CmbBase):
|
||||
__gtype_name__ = "CmbBaseTypeInternalChildInfo"
|
||||
|
||||
type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
internal_child_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
internal_parent_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
internal_type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
creation_property_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, project, type_id, internal_child_id, internal_parent_id, internal_type, creation_property_id):
|
||||
return cls(
|
||||
project=project,
|
||||
type_id=type_id,
|
||||
internal_child_id=internal_child_id,
|
||||
internal_parent_id=internal_parent_id,
|
||||
internal_type=internal_type,
|
||||
creation_property_id=creation_property_id,
|
||||
)
|
||||
|
||||
|
||||
class CmbBaseUI(CmbBase):
|
||||
__gtype_name__ = "CmbBaseUI"
|
||||
|
||||
@ -453,97 +421,6 @@ class CmbBaseCSS(CmbBase):
|
||||
self.db_set("UPDATE css SET is_global=? WHERE (css_id) IS (?);", (self.css_id,), value)
|
||||
|
||||
|
||||
class CmbBaseGResource(CmbBase):
|
||||
__gtype_name__ = "CmbBaseGResource"
|
||||
|
||||
gresource_id = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
resource_type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_row(
|
||||
cls,
|
||||
project,
|
||||
gresource_id,
|
||||
resource_type,
|
||||
parent_id,
|
||||
position,
|
||||
gresources_filename,
|
||||
gresource_prefix,
|
||||
file_filename,
|
||||
file_compressed,
|
||||
file_preprocess,
|
||||
file_alias,
|
||||
):
|
||||
return cls(project=project, gresource_id=gresource_id)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def parent_id(self):
|
||||
return self.db_get("SELECT parent_id FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@parent_id.setter
|
||||
def _set_parent_id(self, value):
|
||||
self.db_set("UPDATE gresource SET parent_id=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def position(self):
|
||||
return self.db_get("SELECT position FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@position.setter
|
||||
def _set_position(self, value):
|
||||
self.db_set("UPDATE gresource SET position=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def gresources_filename(self):
|
||||
return self.db_get("SELECT gresources_filename FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@gresources_filename.setter
|
||||
def _set_gresources_filename(self, value):
|
||||
self.db_set("UPDATE gresource SET gresources_filename=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def gresource_prefix(self):
|
||||
return self.db_get("SELECT gresource_prefix FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@gresource_prefix.setter
|
||||
def _set_gresource_prefix(self, value):
|
||||
self.db_set("UPDATE gresource SET gresource_prefix=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def file_filename(self):
|
||||
return self.db_get("SELECT file_filename FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@file_filename.setter
|
||||
def _set_file_filename(self, value):
|
||||
self.db_set("UPDATE gresource SET file_filename=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=bool, default=False)
|
||||
def file_compressed(self):
|
||||
return self.db_get("SELECT file_compressed FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@file_compressed.setter
|
||||
def _set_file_compressed(self, value):
|
||||
self.db_set("UPDATE gresource SET file_compressed=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def file_preprocess(self):
|
||||
return self.db_get("SELECT file_preprocess FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@file_preprocess.setter
|
||||
def _set_file_preprocess(self, value):
|
||||
self.db_set("UPDATE gresource SET file_preprocess=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def file_alias(self):
|
||||
return self.db_get("SELECT file_alias FROM gresource WHERE (gresource_id) IS (?);", (self.gresource_id,))
|
||||
|
||||
@file_alias.setter
|
||||
def _set_file_alias(self, value):
|
||||
self.db_set("UPDATE gresource SET file_alias=? WHERE (gresource_id) IS (?);", (self.gresource_id,), value)
|
||||
|
||||
|
||||
class CmbBaseProperty(CmbBase):
|
||||
__gtype_name__ = "CmbBaseProperty"
|
||||
|
||||
|
@ -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
@ -20,17 +20,11 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from .cmb_objects_base import CmbBaseProperty, CmbPropertyInfo
|
||||
from . import utils
|
||||
from cambalache import _, getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbProperty(CmbBaseProperty):
|
||||
@ -66,96 +60,9 @@ class CmbProperty(CmbBaseProperty):
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
self.__update_values(value, self.translatable, self.translation_context, self.translation_comments, self.bind_property)
|
||||
self.__update_values(value, 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):
|
||||
def __update_values(self, value, bind_property):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
|
||||
@ -165,22 +72,18 @@ class CmbProperty(CmbBaseProperty):
|
||||
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()
|
||||
value is None or value == self.info.default_value or (self.info.is_object and value == 0)
|
||||
) and bind_property is None:
|
||||
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
|
||||
value is None
|
||||
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
|
||||
|
||||
@ -191,23 +94,14 @@ class CmbProperty(CmbBaseProperty):
|
||||
)
|
||||
|
||||
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=?
|
||||
SET value=?, 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,
|
||||
@ -218,19 +112,11 @@ class CmbProperty(CmbBaseProperty):
|
||||
),
|
||||
)
|
||||
else:
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Set {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_property
|
||||
(
|
||||
ui_id, object_id, owner_id, property_id,
|
||||
value,
|
||||
translatable, translation_context, translation_comments,
|
||||
bind_source_id, bind_owner_id, bind_property_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
(ui_id, object_id, owner_id, property_id, value, bind_source_id, bind_owner_id, bind_property_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(
|
||||
self.ui_id,
|
||||
@ -238,20 +124,12 @@ class CmbProperty(CmbBaseProperty):
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
value,
|
||||
translatable,
|
||||
translation_context,
|
||||
translation_comments,
|
||||
bind_source_id,
|
||||
bind_owner_id,
|
||||
bind_property_id,
|
||||
),
|
||||
)
|
||||
|
||||
self.__update_internal_child()
|
||||
|
||||
if self.info.internal_child:
|
||||
self.project.history_pop()
|
||||
|
||||
if self._init is False:
|
||||
self.object._property_changed(self)
|
||||
|
||||
@ -278,22 +156,11 @@ class CmbProperty(CmbBaseProperty):
|
||||
|
||||
@bind_property.setter
|
||||
def _set_bind_property(self, bind_property):
|
||||
self.__update_values(self.value, self.translatable, self.translation_context, self.translation_comments, bind_property)
|
||||
self.__update_values(self.value, 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(
|
||||
self.version_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
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -20,17 +20,14 @@
|
||||
# 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 .cmb_objects_base import CmbPropertyInfo
|
||||
from .control import CmbObjectChooser, CmbFlagsEntry
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbPropertyLabel(Gtk.Button):
|
||||
@ -49,17 +46,15 @@ class CmbPropertyLabel(Gtk.Button):
|
||||
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()
|
||||
self.label = Gtk.Label(xalign=0, visible=True)
|
||||
box = Gtk.Box(visible=True)
|
||||
|
||||
# Update label status
|
||||
if self.prop:
|
||||
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL)
|
||||
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL, visible=True)
|
||||
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.label.props.label = self.prop.property_id
|
||||
|
||||
self.__update_property_label()
|
||||
self.prop.connect("notify::value", lambda o, p: self.__update_property_label())
|
||||
@ -128,23 +123,13 @@ class CmbPropertyLabel(Gtk.Button):
|
||||
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 = Gtk.Popover(position=Gtk.PositionType.LEFT)
|
||||
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)
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4, visible=True)
|
||||
|
||||
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)
|
||||
grid.attach(Gtk.Label(label="<b>Property Binding</b>", use_markup=True, visible=True, xalign=0), 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)
|
||||
@ -174,16 +159,18 @@ class CmbPropertyLabel(Gtk.Button):
|
||||
|
||||
i = 1
|
||||
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
|
||||
label = Gtk.Label(label=prop_label, xalign=0)
|
||||
editor.props.visible = True
|
||||
|
||||
label = Gtk.Label(label=prop_label, xalign=0, visible=True)
|
||||
|
||||
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 = Gtk.Button(label="Clear", visible=True, halign=Gtk.Align.END)
|
||||
clear.connect("clicked", self.__on_clear_clicked, popover)
|
||||
grid.attach(clear, 0, i, 2, 1)
|
||||
|
||||
grid.attach(clear, 0, i, 2, 1)
|
||||
object_editor.grab_focus()
|
||||
|
||||
popover.set_child(grid)
|
||||
@ -223,9 +210,6 @@ class CmbPropertyChooser(Gtk.ComboBoxText):
|
||||
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
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk, Pango
|
||||
|
||||
@ -78,7 +76,7 @@ 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)
|
||||
@ -86,7 +84,7 @@ class CmbSignalEditor(Gtk.Box):
|
||||
|
||||
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)
|
||||
@ -138,17 +136,13 @@ 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 = ""
|
||||
|
||||
self.treestore[iter_][Col.USER_DATA.value] = name
|
||||
else:
|
||||
signal.user_data = 0
|
||||
signal.swap = False
|
||||
self.treestore[iter_][Col.USER_DATA.value] = ""
|
||||
|
||||
@Gtk.Template.Callback("on_swap_toggled")
|
||||
@ -246,7 +240,7 @@ class CmbSignalEditor(Gtk.Box):
|
||||
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]
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_signal_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="GtkEntryCompletion" id="handler_entrycompletion">
|
||||
<child>
|
||||
@ -37,6 +36,9 @@
|
||||
<property name="focusable">1</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="min-width">64</property>
|
||||
|
@ -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)
|
163
cambalache/cmb_tree_view.py
Normal file
163
cambalache/cmb_tree_view.py
Normal file
@ -0,0 +1,163 @@
|
||||
#
|
||||
# CmbTreeView - Cambalache Tree View
|
||||
#
|
||||
# Copyright (C) 2021-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>
|
||||
#
|
||||
|
||||
from gi.repository import Gtk, Pango
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_context_menu import CmbContextMenu
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbTreeView(Gtk.TreeView):
|
||||
__gtype_name__ = "CmbTreeView"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.props.has_tooltip = True
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
gesture = Gtk.GestureClick(button=3)
|
||||
gesture.connect("pressed", self.__on_button_press)
|
||||
self.add_controller(gesture)
|
||||
|
||||
self.set_reorderable(True)
|
||||
|
||||
def __on_button_press(self, widget, npress, x, y):
|
||||
retval = self.get_path_at_pos(x, y)
|
||||
|
||||
if retval is None:
|
||||
return False
|
||||
|
||||
path, col, xx, yy = retval
|
||||
self.get_selection().select_path(path)
|
||||
|
||||
menu = CmbContextMenu()
|
||||
|
||||
if self._project is not None:
|
||||
menu.target_tk = self._project.target_tk
|
||||
|
||||
# Use parent instead of self to avoid warning and focus not working properly
|
||||
# (run-dev.py:188589): Gtk-CRITICAL **: 16:45:12.790: gtk_css_node_insert_after: assertion 'previous_sibling == NULL ||
|
||||
# previous_sibling->parent == parent' failed
|
||||
menu.set_parent(self.props.parent)
|
||||
menu.popup_at(x, y)
|
||||
|
||||
return True
|
||||
|
||||
def __name_cell_data_func(self, column, cell, model, iter_, data):
|
||||
obj = model.get_value(iter_, 0)
|
||||
msg = None
|
||||
|
||||
if type(obj) is CmbObject:
|
||||
inline_prop = obj.inline_property_id
|
||||
inline_prop = f"<b>{inline_prop}</b> " if inline_prop else ""
|
||||
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
|
||||
msg = obj.version_warning
|
||||
|
||||
text = f"{inline_prop}{name}<i>{extra}</i>"
|
||||
else:
|
||||
text = f"<b>{obj.get_display_name()}</b>"
|
||||
|
||||
cell.set_property("markup", text)
|
||||
cell.set_property("underline", Pango.Underline.ERROR if msg else Pango.Underline.NONE)
|
||||
|
||||
def __on_project_ui_library_changed(self, project, ui, library_id):
|
||||
self.queue_draw()
|
||||
|
||||
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_ui_library_changed)
|
||||
|
||||
self._project = self.props.model
|
||||
|
||||
if self._project:
|
||||
self._project.connect("selection-changed", self.__on_project_selection_changed)
|
||||
self._project.connect("ui-library-changed", self.__on_project_ui_library_changed)
|
||||
|
||||
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:
|
||||
obj = selection[0]
|
||||
_iter = project.get_iter_from_object(obj)
|
||||
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])
|
||||
|
||||
def do_query_tooltip(self, x, y, keyboard_mode, tooltip):
|
||||
retval, model, path, iter_ = self.get_tooltip_context(x, y, keyboard_mode)
|
||||
|
||||
if not retval:
|
||||
return False
|
||||
|
||||
obj = model.get_value(iter_, 0)
|
||||
|
||||
if type(obj) is CmbObject:
|
||||
msg = obj.version_warning
|
||||
if msg:
|
||||
tooltip.set_text(msg)
|
||||
return True
|
||||
|
||||
return False
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_type_chooser.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="CmbTypeChooserPopover" id="all">
|
||||
<property name="show-categories">True</property>
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
@ -20,10 +20,8 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GLib, GObject, Gio, Gtk
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
@ -48,12 +46,13 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
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)
|
||||
|
||||
@ -93,6 +92,10 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
|
||||
return retval
|
||||
|
||||
def __store_append_info(self, store, info):
|
||||
if store:
|
||||
store.append([info.type_id, info.type_id.lower(), info, True])
|
||||
|
||||
def __model_from_project(self, project):
|
||||
if project is None:
|
||||
return None
|
||||
@ -108,12 +111,7 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
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:
|
||||
@ -135,15 +133,15 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
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>"))
|
||||
store.append([f"<i>▾ {category}</i>", "", None, False])
|
||||
|
||||
store.append(i)
|
||||
self.__store_append_info(store, i)
|
||||
|
||||
# Special case External object type
|
||||
if show_categories or self.uncategorized_only:
|
||||
store.append(project.type_info[constants.EXTERNAL_TYPE])
|
||||
self.__store_append_info(store, project.type_info[constants.EXTERNAL_TYPE])
|
||||
|
||||
return filter_model
|
||||
return store
|
||||
|
||||
@GObject.Property(type=CmbProject)
|
||||
def project(self):
|
||||
@ -151,18 +149,24 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
|
||||
@project.setter
|
||||
def _set_project(self, project):
|
||||
if self.__project:
|
||||
if self.__project is not None:
|
||||
self.__project.disconnect_by_func(self.__on_type_info_added)
|
||||
self.__project.disconnect_by_func(self.__on_type_info_removed)
|
||||
self.__project.disconnect_by_func(self.__on_type_info_changed)
|
||||
|
||||
self.__project = project
|
||||
|
||||
self.__model = self.__model_from_project(project)
|
||||
self.listview.set_model(Gtk.NoSelection(model=self.__model))
|
||||
self._filter = Gtk.TreeModelFilter(child_model=self.__model) if self.__model else None
|
||||
if self._filter:
|
||||
self._filter.set_visible_func(self.__visible_func)
|
||||
|
||||
if project:
|
||||
self.treeview.props.model = self._filter
|
||||
|
||||
if project is not None:
|
||||
project.connect("type-info-added", self.__on_type_info_added)
|
||||
project.connect("type-info-removed", self.__on_type_info_removed)
|
||||
project.connect("type-info-changed", self.__on_type_info_changed)
|
||||
|
||||
@Gtk.Template.Callback("on_searchentry_activate")
|
||||
def __on_searchentry_activate(self, entry):
|
||||
@ -175,34 +179,61 @@ class CmbTypeChooserWidget(Gtk.Box):
|
||||
@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:
|
||||
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]
|
||||
|
||||
# Always show categories if we are not searching
|
||||
if self._search_text == "" and info is None:
|
||||
return True
|
||||
|
||||
return type_id_lower.find(self._search_text) >= 0
|
||||
|
||||
def __on_type_info_added(self, project, info):
|
||||
if self.__model is None:
|
||||
return
|
||||
|
||||
# Append new type info
|
||||
# TODO: insert in order
|
||||
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)
|
||||
self.__store_append_info(self.__model, info)
|
||||
|
||||
def __on_type_info_removed(self, project, info):
|
||||
if self.__model is None:
|
||||
return
|
||||
|
||||
# Find info and remove it from model
|
||||
found, position = self.__model.props.model.find(info)
|
||||
if found:
|
||||
self.__model.props.model.remove(position)
|
||||
for row in self.__model:
|
||||
if info == row[2]:
|
||||
self.__model.remove(row.iter)
|
||||
|
||||
def __on_type_info_changed(self, project, info):
|
||||
if self.__model is None:
|
||||
return
|
||||
|
||||
info_row = None
|
||||
|
||||
# Find info and update it from model
|
||||
for row in self.__model:
|
||||
if info == row[2]:
|
||||
info_row = row
|
||||
break
|
||||
|
||||
if info_row is None:
|
||||
return
|
||||
|
||||
# Update Type Name
|
||||
info_row[0] = info.type_id
|
||||
info_row[1] = info.type_id.lower()
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, "CmbTypeChooserWidget")
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_type_chooser_widget.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTypeChooserWidget" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
@ -15,38 +14,35 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="propagate-natural-width">True</property>
|
||||
<property name="vexpand">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>
|
||||
</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="single-click-activate">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<signal name="activate" handler="on_listview_activate"/>
|
||||
<object class="GtkTreeView" id="treeview">
|
||||
<property name="activate-on-single-click">1</property>
|
||||
<property name="enable-search">0</property>
|
||||
<property name="headers-visible">0</property>
|
||||
<signal name="row-activated" handler="on_treeview_row_activated"/>
|
||||
<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"/>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="markup">0</attribute>
|
||||
<attribute name="sensitive">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="min-content-height">256</property>
|
||||
<property name="propagate-natural-height">1</property>
|
||||
<property name="propagate-natural-width">1</property>
|
||||
<property name="window-placement">bottom-left</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
@ -29,18 +27,13 @@ from .cmb_objects_base import (
|
||||
CmbBaseTypeInfo,
|
||||
CmbBaseTypeDataInfo,
|
||||
CmbBaseTypeDataArgInfo,
|
||||
CmbBaseTypeInternalChildInfo,
|
||||
CmbTypeChildInfo,
|
||||
CmbPropertyInfo,
|
||||
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__)
|
||||
|
||||
|
||||
class CmbTypeDataArgInfo(CmbBaseTypeDataArgInfo):
|
||||
def __init__(self, **kwargs):
|
||||
@ -60,33 +53,19 @@ class CmbTypeDataInfo(CmbBaseTypeDataInfo):
|
||||
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)
|
||||
|
||||
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.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":
|
||||
@ -155,7 +134,7 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
|
||||
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)
|
||||
retval[row[1]] = Klass.from_row(self, *row)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
@ -164,14 +143,14 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
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, translatable)
|
||||
|
||||
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)):
|
||||
_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)):
|
||||
@ -198,52 +177,6 @@ class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
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 = []
|
||||
@ -281,7 +214,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()
|
||||
@ -330,48 +263,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,39 +20,25 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
from gi.repository import GObject, Gio
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_list_error import CmbListError
|
||||
from .cmb_objects_base import CmbBaseUI, CmbBaseObject
|
||||
from .cmb_objects_base import CmbBaseUI
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
class CmbUI(CmbBaseUI):
|
||||
__gsignals__ = {
|
||||
"library-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
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,))
|
||||
@ -65,10 +51,6 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
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 = {}
|
||||
|
||||
@ -132,21 +114,18 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
|
||||
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)
|
||||
def get_display_name(self):
|
||||
if self.filename:
|
||||
return self.filename
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
filename = self.filename
|
||||
template_id = self.template_id
|
||||
|
||||
if filename is None and template_id:
|
||||
if template_id:
|
||||
template = self.project.get_object_by_id(self.ui_id, template_id)
|
||||
if template:
|
||||
if template is not None:
|
||||
return template.name
|
||||
|
||||
return CmbUI.get_display_name(self.ui_id, filename)
|
||||
return _("Unnamed {ui_id}").format(ui_id=self.ui_id)
|
||||
|
||||
def __get_infered_target(self, library_id):
|
||||
ui_id = self.ui_id
|
||||
@ -186,37 +165,3 @@ class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
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
|
||||
|
@ -20,14 +20,9 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from cambalache import _
|
||||
from .cmb_ui import CmbUI
|
||||
|
||||
|
||||
@ -36,7 +31,6 @@ class CmbUIEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbUIEditor"
|
||||
|
||||
filename = Gtk.Template.Child()
|
||||
format = Gtk.Template.Child()
|
||||
template_id = Gtk.Template.Child()
|
||||
description = Gtk.Template.Child()
|
||||
copyright = Gtk.Template.Child()
|
||||
@ -58,6 +52,9 @@ class CmbUIEditor(Gtk.Grid):
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self._object:
|
||||
return
|
||||
|
||||
for binding in self._bindings:
|
||||
binding.unbind()
|
||||
|
||||
@ -78,17 +75,10 @@ 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(
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
field,
|
||||
getattr(self, field),
|
||||
"cmb-value",
|
||||
@ -96,42 +86,37 @@ class CmbUIEditor(Gtk.Grid):
|
||||
)
|
||||
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
|
||||
@Gtk.Template.Callback("on_export_button_clicked")
|
||||
def __on_export_button_clicked(self, button):
|
||||
self.emit("export-ui")
|
||||
|
||||
@GObject.Signal(
|
||||
flags=GObject.SignalFlags.RUN_LAST,
|
||||
return_type=bool,
|
||||
arg_types=(),
|
||||
accumulator=GObject.signal_accumulator_true_handled,
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
def export_ui(self):
|
||||
if self.object:
|
||||
self.object.project.export_ui(self.object)
|
||||
|
||||
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()
|
||||
return True
|
||||
|
||||
def __filename_to_format(self, binding, source_value, ui):
|
||||
if not source_value:
|
||||
self.format.props.sensitive = False
|
||||
return 0
|
||||
self.format.props.sensitive = True
|
||||
@GObject.Signal(
|
||||
flags=GObject.SignalFlags.RUN_LAST,
|
||||
return_type=bool,
|
||||
arg_types=(),
|
||||
accumulator=GObject.signal_accumulator_true_handled,
|
||||
)
|
||||
def remove_ui(self):
|
||||
if self.object:
|
||||
self.object.project.remove_ui(self.object)
|
||||
|
||||
return 1 if source_value.endswith(".blp") else 0
|
||||
|
||||
def __format_to_filename(self, binding, target_value, ui):
|
||||
if not ui.filename:
|
||||
self.format.props.sensitive = False
|
||||
return None
|
||||
self.format.props.sensitive = True
|
||||
|
||||
return os.path.splitext(ui.filename)[0] + (".blp" if target_value == 1 else ".ui")
|
||||
return True
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbUIEditor, "CmbUIEditor")
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<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"/>
|
||||
@ -27,7 +26,7 @@
|
||||
<property name="label" translatable="yes">Description:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -37,7 +36,7 @@
|
||||
<property name="label" translatable="yes">Copyright:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">4</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -47,7 +46,7 @@
|
||||
<property name="label" translatable="yes">Authors:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">5</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -57,13 +56,16 @@
|
||||
<property name="label" translatable="yes">Domain:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">6</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<object class="CmbEntry" id="filename">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="placeholder-text" translatable="yes"><file name relative to project></property>
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
@ -78,7 +80,7 @@
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">6</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -95,7 +97,7 @@
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">3</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -112,7 +114,7 @@
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">5</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -124,7 +126,7 @@
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -134,7 +136,45 @@
|
||||
<property name="label" translatable="yes">Template:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="receives-default">1</property>
|
||||
<signal name="clicked" handler="on_remove_button_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">app-remove-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="export_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Export</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="tooltip-text" translatable="yes">Export</property>
|
||||
<signal name="clicked" handler="on_export_button_clicked"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">7</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -151,7 +191,7 @@
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">4</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -161,7 +201,7 @@
|
||||
<property name="label" translatable="yes">Comment:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">7</property>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
@ -178,34 +218,7 @@
|
||||
<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>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
@ -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>
|
@ -20,157 +20,66 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import gi
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
import fcntl
|
||||
import stat
|
||||
import atexit
|
||||
import shutil
|
||||
|
||||
gi.require_version('Casilda', '0.1')
|
||||
from gi.repository import GObject, GLib, Gio, Gdk, Gtk, Casilda
|
||||
from gi.repository import GObject, GLib, Gtk, WebKit
|
||||
|
||||
from . import config
|
||||
from .cmb_ui import CmbUI
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_context_menu import CmbContextMenu
|
||||
from cambalache.cmb_blueprint import cmb_blueprint_decompile
|
||||
from . import utils
|
||||
from cambalache import getLogger, _, N_
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
basedir = os.path.dirname(__file__) or "."
|
||||
|
||||
GObject.type_ensure(Casilda.Compositor.__gtype__)
|
||||
GObject.type_ensure(WebKit.Settings.__gtype__)
|
||||
GObject.type_ensure(WebKit.WebView.__gtype__)
|
||||
|
||||
|
||||
class CmbMerengueProcess(GObject.Object):
|
||||
class CmbProcess(GObject.Object):
|
||||
__gsignals__ = {
|
||||
"handle-command": (GObject.SignalFlags.RUN_LAST, None, (str,)),
|
||||
"stdout": (GObject.SignalFlags.RUN_LAST, bool, (GLib.IOCondition,)),
|
||||
"exit": (GObject.SignalFlags.RUN_LAST, None, ()),
|
||||
}
|
||||
|
||||
gtk_version = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
merengue_started = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
file = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__command_queue = []
|
||||
self.__file = os.path.join(config.merenguedir, "merengue", "merengue")
|
||||
self.__command_in = None
|
||||
self.__on_command_in_source = None
|
||||
self.__connection = None
|
||||
self.__pid = 0
|
||||
self.__wayland_display = None
|
||||
self.__command_socket = None
|
||||
self.__service = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def wayland_display(self):
|
||||
return self.__wayland_display
|
||||
self.pid = 0
|
||||
self.stdin = None
|
||||
self.stdout = None
|
||||
|
||||
@wayland_display.setter
|
||||
def _set_wayland_display(self, wayland_display):
|
||||
self.cleanup()
|
||||
def stop(self):
|
||||
if self.stdin:
|
||||
self.stdin.shutdown(False)
|
||||
self.stdin = None
|
||||
|
||||
self.__wayland_display = wayland_display
|
||||
|
||||
if wayland_display is None:
|
||||
return
|
||||
|
||||
# Create socket address object
|
||||
dirname = os.path.dirname(wayland_display)
|
||||
self.__command_socket = os.path.join(dirname, "merengue.sock")
|
||||
socket_addr = Gio.UnixSocketAddress.new(self.__command_socket)
|
||||
|
||||
# Lock Socket
|
||||
GLib.mkdir_with_parents(dirname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
||||
lockfd = os.open(f"{self.__command_socket}.lock",
|
||||
os.O_CREAT | os.O_CLOEXEC | os.O_RDWR,
|
||||
stat.S_IRUSR | stat.S_IWUSR)
|
||||
if lockfd < 0:
|
||||
logger.warning(f"Can not open lockfile for {self.__command_socket}, check permissions")
|
||||
return
|
||||
if self.stdout:
|
||||
self.stdout.shutdown(False)
|
||||
self.stdout = None
|
||||
|
||||
if self.pid:
|
||||
try:
|
||||
fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
GLib.spawn_close_pid(self.pid)
|
||||
os.kill(self.pid, 9)
|
||||
except Exception as e:
|
||||
logger.warning(f"Can not lock lockfile for {self.__command_socket}, is it used by another compositor? {e}")
|
||||
logger.warning(f"Error stopping {self.file} {e}")
|
||||
|
||||
self.pid = 0
|
||||
|
||||
def run(self, args, env={}):
|
||||
if self.file is None or self.pid > 0:
|
||||
return
|
||||
|
||||
# Create socket listener and add address
|
||||
self.__service = Gio.SocketService()
|
||||
self.__service.add_address(socket_addr,
|
||||
Gio.SocketType.STREAM,
|
||||
Gio.SocketProtocol.DEFAULT,
|
||||
None)
|
||||
self.__service.connect("incoming", self.__on_service_incoming)
|
||||
self.__service.start()
|
||||
|
||||
try:
|
||||
os.lstat(self.__command_socket)
|
||||
except Exception as e:
|
||||
logger.warning(f"Can not stat file {self.__command_socket} {e}")
|
||||
|
||||
socket_addr = None
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def pid(self):
|
||||
return self.__pid
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
if self.__command_socket:
|
||||
os.unlink(self.__command_socket)
|
||||
os.unlink(f"{self.__command_socket}.lock")
|
||||
if self.__service:
|
||||
self.__service.start()
|
||||
self.__service = None
|
||||
|
||||
def __on_command_in(self, channel, condition):
|
||||
if condition == GLib.IOCondition.HUP or self.__command_in is None:
|
||||
self.stop()
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
payload = self.__command_in.readline()
|
||||
if payload is not None and payload != "":
|
||||
self.emit("handle-command", payload)
|
||||
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
def __on_service_incoming(self, service, connection, source_object):
|
||||
self.__connection = connection
|
||||
|
||||
self.__command_in = GLib.IOChannel.unix_new(self.__connection.props.input_stream.get_fd())
|
||||
id = GLib.io_add_watch(self.__command_in,
|
||||
GLib.PRIORITY_DEFAULT_IDLE,
|
||||
GLib.IOCondition.IN | GLib.IOCondition.HUP,
|
||||
self.__on_command_in)
|
||||
self.__on_command_in_source = id
|
||||
|
||||
# Consume pending command queue
|
||||
for cmd, payload in self.__command_queue:
|
||||
self.__socket_write_command(cmd, payload)
|
||||
|
||||
self.__command_queue = []
|
||||
|
||||
def start(self):
|
||||
if self.__file is None or self.__pid > 0:
|
||||
return
|
||||
|
||||
env = json.loads(os.environ.get("MERENGUE_DEV_ENV", "{}"))
|
||||
env = env | {
|
||||
"GDK_BACKEND": "wayland",
|
||||
"WAYLAND_DISPLAY": self.wayland_display,
|
||||
}
|
||||
|
||||
envp = [f"{var}={val}" for var, val in os.environ.items() if var not in env]
|
||||
|
||||
# Append extra vars
|
||||
@ -178,83 +87,28 @@ class CmbMerengueProcess(GObject.Object):
|
||||
envp.append(f"{var}={env[var]}")
|
||||
|
||||
pid, stdin, stdout, stderr = GLib.spawn_async(
|
||||
[self.__file, self.gtk_version, self.__command_socket],
|
||||
[self.file] + args,
|
||||
envp=envp,
|
||||
flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD,
|
||||
standard_input=True,
|
||||
standard_output=True,
|
||||
)
|
||||
self.pid = pid
|
||||
|
||||
self.stdin = GLib.IOChannel.unix_new(stdin)
|
||||
self.stdout = GLib.IOChannel.unix_new(stdout)
|
||||
|
||||
GLib.io_add_watch(self.stdout, GLib.PRIORITY_DEFAULT_IDLE, GLib.IOCondition.IN | GLib.IOCondition.HUP, self.__on_stdout)
|
||||
|
||||
self.__pid = pid
|
||||
GLib.child_watch_add(GLib.PRIORITY_DEFAULT_IDLE, pid, self.__on_exit, None)
|
||||
|
||||
def __cleanup(self):
|
||||
self.merengue_started = False
|
||||
|
||||
if self.__on_command_in_source:
|
||||
GLib.source_remove(self.__on_command_in_source)
|
||||
self.__on_command_in_source = None
|
||||
|
||||
if self.__command_in:
|
||||
self.__command_in = None
|
||||
|
||||
if self.__connection:
|
||||
self.__connection.close()
|
||||
self.__connection = None
|
||||
|
||||
def stop(self):
|
||||
self.__cleanup()
|
||||
|
||||
if self.__pid:
|
||||
try:
|
||||
GLib.spawn_close_pid(self.__pid)
|
||||
os.kill(self.__pid, 9)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error stopping {self.__file} {e}")
|
||||
finally:
|
||||
self.__pid = 0
|
||||
|
||||
def write_command(self, command, payload=None, args=None):
|
||||
cmd = {"command": command}
|
||||
|
||||
if payload is not None:
|
||||
# Encode to binary first, before calculating lenght
|
||||
payload = payload.encode()
|
||||
cmd["payload_length"] = len(payload)
|
||||
logger.debug(f"write_command {command} {len(payload)}")
|
||||
|
||||
if args is not None:
|
||||
cmd["args"] = args
|
||||
|
||||
# Queue command while we are not connected
|
||||
if self.__connection is None:
|
||||
self.__command_queue.append((cmd, payload))
|
||||
return
|
||||
|
||||
self.__socket_write_command(cmd, payload)
|
||||
|
||||
def __socket_write_command(self, cmd, payload=None):
|
||||
# Send command in one line as json
|
||||
output_stream = self.__connection.props.output_stream
|
||||
|
||||
def write_data(data):
|
||||
total_bytes = len(data)
|
||||
total_sent = 0
|
||||
while total_sent < total_bytes:
|
||||
total_sent += output_stream.write(data[total_sent:])
|
||||
|
||||
write_data(json.dumps(cmd).encode())
|
||||
write_data(b"\n")
|
||||
|
||||
if payload is not None:
|
||||
write_data(payload)
|
||||
|
||||
# Flush
|
||||
output_stream.flush()
|
||||
|
||||
def __on_exit(self, pid, status, data):
|
||||
self.__cleanup()
|
||||
self.__pid = 0
|
||||
self.stop()
|
||||
self.emit("exit")
|
||||
|
||||
def __on_stdout(self, channel, condition):
|
||||
return self.emit("stdout", condition)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_view.ui")
|
||||
class CmbView(Gtk.Box):
|
||||
@ -265,106 +119,112 @@ class CmbView(Gtk.Box):
|
||||
"placeholder-activated": (GObject.SignalFlags.RUN_LAST, None, (int, int, object, int, str)),
|
||||
}
|
||||
|
||||
show_merengue = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
preview = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
stack = Gtk.Template.Child()
|
||||
compositor = Gtk.Template.Child()
|
||||
compositor_offload = Gtk.Template.Child()
|
||||
compositor_box = Gtk.Template.Child()
|
||||
error_box = Gtk.Template.Child()
|
||||
error_message = Gtk.Template.Child()
|
||||
webview = Gtk.Template.Child()
|
||||
text_view = Gtk.Template.Child()
|
||||
db_inspector = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__project = None
|
||||
self.__ui = None
|
||||
self.__restart_project = None
|
||||
self.__ui_id = 0
|
||||
self.__theme = None
|
||||
self.__dark = False
|
||||
|
||||
self.menu = self.__create_context_menu()
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__click_gesture = Gtk.GestureClick(
|
||||
propagation_phase=Gtk.PropagationPhase.CAPTURE,
|
||||
button=3
|
||||
)
|
||||
self.__click_gesture.connect("pressed", self.__on_click_gesture_pressed)
|
||||
self.compositor_box.add_controller(self.__click_gesture)
|
||||
self.__merengue_bin = os.path.join(config.merenguedir, "merengue", "merengue")
|
||||
self.__broadwayd_bin = GLib.find_program_in_path("broadwayd")
|
||||
self.__gtk4_broadwayd_bin = GLib.find_program_in_path("gtk4-broadwayd")
|
||||
|
||||
self.__merengue = CmbMerengueProcess(wayland_display=self.compositor.props.socket)
|
||||
self.__merengue.connect("exit", self.__on_process_exit)
|
||||
self.webview.connect("load-changed", self.__on_load_changed)
|
||||
|
||||
self.__merengue = None
|
||||
self.__broadwayd = None
|
||||
self.__port = None
|
||||
self.__merengue_last_exit = None
|
||||
|
||||
if self.__broadwayd_bin is None:
|
||||
logger.warning("broadwayd not found, Gtk 3 workspace wont work.")
|
||||
|
||||
if self.__gtk4_broadwayd_bin is None:
|
||||
logger.warning("gtk4-broadwayd not found, Gtk 4 workspace wont work.")
|
||||
|
||||
self.connect("notify::preview", self.__on_preview_notify)
|
||||
|
||||
# Ensure we delete all socket files when exiting
|
||||
atexit.register(self.__atexit)
|
||||
|
||||
@Gtk.Template.Callback("on_restart_button_clicked")
|
||||
def __on_restart_button_clicked(self, button):
|
||||
self.restart_workspace()
|
||||
|
||||
def __atexit(self):
|
||||
dirname = os.path.dirname(self.compositor.props.socket)
|
||||
|
||||
def do_destroy(self):
|
||||
if self.__merengue:
|
||||
self.__merengue_command("quit")
|
||||
self.__merengue.cleanup()
|
||||
|
||||
if os.path.exists(dirname):
|
||||
shutil.rmtree(dirname)
|
||||
if self.__broadwayd:
|
||||
self.__broadwayd.stop()
|
||||
|
||||
def __set_dark_mode(self, dark):
|
||||
valid, bg_color = self.get_style_context().lookup_color('theme_bg_color')
|
||||
if valid:
|
||||
self.compositor.props.bg_color = bg_color
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
def __evaluate_js(self, script):
|
||||
self.webview.evaluate_javascript(script, -1, None, None, None, None, None, None)
|
||||
|
||||
def _set_dark_mode(self, dark):
|
||||
# This needs to be called in an idle because theme_bg_color has not changed at this point
|
||||
GLib.idle_add(self.__set_dark_mode, dark)
|
||||
self.__dark = dark
|
||||
self.__evaluate_js(f"document.body.style.background = '{'#222' if dark else 'inherit'}';")
|
||||
|
||||
def __on_load_changed(self, webview, event):
|
||||
if event != WebKit.LoadEvent.FINISHED:
|
||||
return
|
||||
|
||||
self._set_dark_mode(self.__dark)
|
||||
|
||||
# Disable alert() function used when broadwayd get disconnected
|
||||
# Monkey pat ch setupDocument() to avoid disabling document.oncontextmenu
|
||||
self.__evaluate_js(
|
||||
"""
|
||||
window.alert = function (message) {
|
||||
console.log (message);
|
||||
}
|
||||
|
||||
window.merengueSetupDocument = setupDocument;
|
||||
|
||||
window.setupDocument = function (document) {
|
||||
var cb = oncontextmenu
|
||||
merengueSetupDocument(document);
|
||||
document.oncontextmenu = cb;
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
def __merengue_command(self, command, payload=None, args=None):
|
||||
if self.__merengue.merengue_started:
|
||||
self.__merengue.write_command(command, payload, args)
|
||||
if self.__merengue is None or self.__merengue.stdin is None:
|
||||
return
|
||||
|
||||
cmd = {"command": command, "payload": payload is not None}
|
||||
|
||||
if args is not None:
|
||||
cmd["args"] = args
|
||||
|
||||
# Send command in one line as json
|
||||
self.__merengue.stdin.write(json.dumps(cmd))
|
||||
self.__merengue.stdin.write("\n")
|
||||
|
||||
if payload is not None:
|
||||
self.__merengue.stdin.write(GLib.strescape(payload))
|
||||
self.__merengue.stdin.write("\n")
|
||||
|
||||
# Flush
|
||||
self.__merengue.stdin.flush()
|
||||
|
||||
def __get_ui_xml(self, ui_id, merengue=False):
|
||||
if self.show_merengue:
|
||||
merengue = True
|
||||
|
||||
return self.__project.db.tostring(ui_id, merengue=merengue)
|
||||
|
||||
def __update_view(self):
|
||||
if self.__project and self.__ui:
|
||||
if self.__project is not None and self.__ui_id > 0:
|
||||
if self.stack.props.visible_child_name == "ui_xml":
|
||||
ui_source = self.__get_ui_xml(self.__ui.ui_id)
|
||||
|
||||
if self.__ui.filename and self.__ui.filename.endswith(".blp"):
|
||||
try:
|
||||
ui_source = cmb_blueprint_decompile(ui_source)
|
||||
self.text_view.lang = "blueprint"
|
||||
except Exception as e:
|
||||
ui_source = _("Error exporting project")
|
||||
ui_source += "\n"
|
||||
ui_source += N_(
|
||||
"blueprintcompiler encounter the following error:",
|
||||
"blueprintcompiler encounter the following errors:",
|
||||
len(e.errors)
|
||||
)
|
||||
ui_source += "\n"
|
||||
ui_source += str(e)
|
||||
self.text_view.lang = ""
|
||||
# TODO: forward error to parent to show to user
|
||||
else:
|
||||
self.text_view.lang = "xml"
|
||||
|
||||
self.text_view.buffer.set_text(ui_source)
|
||||
ui = self.__get_ui_xml(self.__ui_id)
|
||||
self.text_view.buffer.set_text(ui)
|
||||
return
|
||||
|
||||
self.text_view.buffer.set_text("")
|
||||
self.__ui = None
|
||||
self.__ui_id = 0
|
||||
|
||||
def __get_ui_dirname(self, ui_id):
|
||||
dirname = GLib.get_home_dir()
|
||||
@ -383,7 +243,7 @@ class CmbView(Gtk.Box):
|
||||
return dirname
|
||||
|
||||
def __merengue_update_ui(self, ui_id):
|
||||
ui = self.__get_ui_xml(ui_id, merengue=True) if ui_id else None
|
||||
ui = self.__get_ui_xml(ui_id, merengue=True)
|
||||
toplevels = self.__project.db.get_toplevels(ui_id)
|
||||
selection = self.__project.get_selection()
|
||||
objects = self.__get_selection_objects(selection, ui_id)
|
||||
@ -417,13 +277,7 @@ class CmbView(Gtk.Box):
|
||||
self.__merengue_update_ui(obj.ui_id)
|
||||
|
||||
def __on_object_property_changed(self, project, obj, prop):
|
||||
info = prop.info
|
||||
|
||||
# FIXME: implement new merengue command for updating a11y props
|
||||
if info.is_a11y:
|
||||
return
|
||||
|
||||
if obj.info.workspace_type is None and info.construct_only:
|
||||
if obj.info.workspace_type is None and prop.info.construct_only:
|
||||
self.__merengue_update_ui(obj.ui_id)
|
||||
return
|
||||
|
||||
@ -468,27 +322,21 @@ class CmbView(Gtk.Box):
|
||||
if len(selection) > 0:
|
||||
obj = selection[0]
|
||||
|
||||
if isinstance(obj, CmbUI):
|
||||
ui = obj
|
||||
elif isinstance(obj, CmbObject):
|
||||
ui = obj.ui
|
||||
else:
|
||||
if type(obj) not in [CmbUI, CmbObject]:
|
||||
return
|
||||
|
||||
ui_id = obj.ui_id
|
||||
|
||||
if self.__ui != ui:
|
||||
self.__ui = ui
|
||||
self.__merengue_update_ui(ui.ui_id)
|
||||
if self.__ui_id != ui_id:
|
||||
self.__ui_id = ui_id
|
||||
self.__merengue_update_ui(ui_id)
|
||||
|
||||
objects = self.__get_selection_objects(selection, ui.ui_id)
|
||||
objects = self.__get_selection_objects(selection, ui_id)
|
||||
self.__merengue_command("selection_changed", args={"ui_id": ui_id, "selection": objects})
|
||||
else:
|
||||
self.__ui = None
|
||||
self.__ui_id = 0
|
||||
self.__merengue_update_ui(0)
|
||||
|
||||
self.__update_view()
|
||||
|
||||
def __on_css_added(self, project, obj):
|
||||
if self.project.filename and obj.filename:
|
||||
dirname = os.path.dirname(self.project.filename)
|
||||
@ -531,26 +379,13 @@ class CmbView(Gtk.Box):
|
||||
def __on_object_data_arg_changed(self, project, data, value):
|
||||
self.__merengue_update_ui(data.ui_id)
|
||||
|
||||
def __on_object_child_reordered(self, project, obj, child, old_position, new_position):
|
||||
self.__merengue_update_ui(obj.ui_id)
|
||||
|
||||
def __set_error_message(self, message):
|
||||
if message:
|
||||
self.error_message.props.label = message
|
||||
self.compositor_offload.set_visible(False)
|
||||
self.error_box.set_visible(True)
|
||||
else:
|
||||
self.error_message.props.label = ""
|
||||
self.compositor_offload.set_visible(True)
|
||||
self.error_box.set_visible(False)
|
||||
|
||||
@GObject.Property(type=GObject.GObject)
|
||||
def project(self):
|
||||
return self.__project
|
||||
|
||||
@project.setter
|
||||
def _set_project(self, project):
|
||||
if self.__project:
|
||||
if self.__project is not None:
|
||||
self.__project.disconnect_by_func(self.__on_changed)
|
||||
self.__project.disconnect_by_func(self.__on_ui_changed)
|
||||
self.__project.disconnect_by_func(self.__on_object_added)
|
||||
@ -563,20 +398,19 @@ class CmbView(Gtk.Box):
|
||||
self.__project.disconnect_by_func(self.__on_object_data_removed)
|
||||
self.__project.disconnect_by_func(self.__on_object_data_data_removed)
|
||||
self.__project.disconnect_by_func(self.__on_object_data_arg_changed)
|
||||
self.__project.disconnect_by_func(self.__on_object_child_reordered)
|
||||
self.__project.disconnect_by_func(self.__on_project_selection_changed)
|
||||
self.__merengue.disconnect_by_func(self.__on_merengue_stdout)
|
||||
self.__project.disconnect_by_func(self.__on_css_added)
|
||||
self.__project.disconnect_by_func(self.__on_css_removed)
|
||||
self.__project.disconnect_by_func(self.__on_css_changed)
|
||||
self.__merengue.disconnect_by_func(self.__on_merengue_handle_command)
|
||||
self.__merengue.stop()
|
||||
self.__broadwayd.stop()
|
||||
|
||||
self.__project = project
|
||||
self.db_inspector.project = project
|
||||
|
||||
self.__update_view()
|
||||
|
||||
if project:
|
||||
if project is not None:
|
||||
project.connect("changed", self.__on_changed)
|
||||
project.connect("ui-changed", self.__on_ui_changed)
|
||||
project.connect("object-added", self.__on_object_added)
|
||||
@ -589,25 +423,28 @@ class CmbView(Gtk.Box):
|
||||
project.connect("object-data-removed", self.__on_object_data_removed)
|
||||
project.connect("object-data-data-removed", self.__on_object_data_data_removed)
|
||||
project.connect("object-data-arg-changed", self.__on_object_data_arg_changed)
|
||||
project.connect("object-child-reordered", self.__on_object_child_reordered)
|
||||
project.connect("selection-changed", self.__on_project_selection_changed)
|
||||
project.connect("css-added", self.__on_css_added)
|
||||
project.connect("css-removed", self.__on_css_removed)
|
||||
project.connect("css-changed", self.__on_css_changed)
|
||||
self.__merengue.connect("handle-command", self.__on_merengue_handle_command)
|
||||
|
||||
# Run view process
|
||||
if project.target_tk == "gtk+-3.0":
|
||||
self.__merengue.gtk_version = "3.0"
|
||||
elif project.target_tk == "gtk-4.0":
|
||||
self.__merengue.gtk_version = "4.0"
|
||||
self.__merengue = CmbProcess(file=self.__merengue_bin)
|
||||
self.__merengue.connect("stdout", self.__on_merengue_stdout)
|
||||
self.__merengue.connect("exit", self.__on_process_exit)
|
||||
|
||||
# Clear any error
|
||||
self.__set_error_message(None)
|
||||
self.__merengue.start()
|
||||
self.__broadwayd_check(self.__project.target_tk)
|
||||
|
||||
broadwayd = self.__gtk4_broadwayd_bin if self.__project.target_tk == "gtk-4.0" else self.__broadwayd_bin
|
||||
self.__broadwayd = CmbProcess(file=broadwayd)
|
||||
self.__broadwayd.connect("stdout", self.__on_broadwayd_stdout)
|
||||
self.__broadwayd.connect("exit", self.__on_process_exit)
|
||||
|
||||
self.__port = self.__find_free_port()
|
||||
display = self.__port - 8080
|
||||
self.__broadwayd.run([f":{display}"])
|
||||
|
||||
# Update css themes
|
||||
self.menu.target_tk = project.target_tk
|
||||
self.menu.target_tk = self.__project.target_tk
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def gtk_theme(self):
|
||||
@ -618,24 +455,40 @@ class CmbView(Gtk.Box):
|
||||
self.__theme = theme
|
||||
self.__merengue_command("gtk_settings_set", args={"property": "gtk-theme-name", "value": theme})
|
||||
|
||||
def __on_click_gesture_pressed(self, gesture, n_press, x, y):
|
||||
if gesture.get_current_button() == 3:
|
||||
self.menu.popup_at(x, y)
|
||||
@Gtk.Template.Callback("on_context_menu")
|
||||
def __on_context_menu(self, webview, menu, hit_test_result):
|
||||
self.menu.popup_at(*utils.get_pointer(self))
|
||||
return True
|
||||
|
||||
def __webview_set_msg(self, msg):
|
||||
self.webview.load_html(
|
||||
f"""
|
||||
<html>
|
||||
<body>
|
||||
<h3 style="white-space: pre; text-align: center; margin-top: 45vh; opacity: 50%">{msg}</h3>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
|
||||
def __broadwayd_check(self, target_tk):
|
||||
bin = None
|
||||
|
||||
if target_tk == "gtk-4.0" and self.__gtk4_broadwayd_bin is None:
|
||||
bin = "gtk4-broadwayd"
|
||||
if target_tk == "gtk+-3.0" and self.__broadwayd_bin is None:
|
||||
bin = "broadwayd"
|
||||
|
||||
if bin is not None:
|
||||
self.__webview_set_msg(_("Workspace not available\n{bin} executable not found").format(bin=bin))
|
||||
|
||||
def inspect(self):
|
||||
self.stack.props.visible_child_name = "ui_xml"
|
||||
self.__update_view()
|
||||
|
||||
def restart_workspace(self):
|
||||
# Clear last exit timestamp
|
||||
self.__merengue_last_exit = None
|
||||
|
||||
if self.__merengue.pid:
|
||||
# Let __on_process_exit() restart Merengue
|
||||
self.__merengue.stop()
|
||||
else:
|
||||
self.__set_error_message(None)
|
||||
self.__merengue.start()
|
||||
self.__restart_project = self.__project
|
||||
self.project = None
|
||||
|
||||
def __create_context_menu(self):
|
||||
retval = CmbContextMenu(enable_theme=True)
|
||||
@ -647,17 +500,22 @@ class CmbView(Gtk.Box):
|
||||
return retval
|
||||
|
||||
def __on_process_exit(self, process):
|
||||
if process == self.__merengue:
|
||||
if self.__merengue_last_exit is None:
|
||||
self.__merengue_last_exit = time.monotonic()
|
||||
else:
|
||||
# Stop auto restart if Merengue exited less than 2 seconds ago
|
||||
if (time.monotonic() - self.__merengue_last_exit) < 2:
|
||||
self.__set_error_message(_("Workspace process error\nStopping auto restart"))
|
||||
if (time.monotonic() - self.__merengue_last_exit) < 1:
|
||||
self.__webview_set_msg(_("Workspace process error\nStopping auto restart"))
|
||||
self.__merengue_last_exit = None
|
||||
return
|
||||
|
||||
self.__ui = None
|
||||
self.__merengue.start()
|
||||
if self.__broadwayd.pid == 0 and self.__merengue.pid == 0:
|
||||
self.project = self.__restart_project
|
||||
self.__restart_project = None
|
||||
self.__ui_id = 0
|
||||
else:
|
||||
self.__restart_project = self.__project
|
||||
self.project = None
|
||||
|
||||
def __command_selection_changed(self, selection):
|
||||
objects = []
|
||||
@ -672,11 +530,8 @@ class CmbView(Gtk.Box):
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
for id, info in self.project.library_info.items():
|
||||
# Only load 3rd party libraries, Gtk ones are already loaded
|
||||
if not info.third_party:
|
||||
continue
|
||||
|
||||
for id in self.project.library_info:
|
||||
info = self.project.library_info[id]
|
||||
self.__merengue_command(
|
||||
"load_namespace",
|
||||
args={
|
||||
@ -695,27 +550,31 @@ class CmbView(Gtk.Box):
|
||||
for css in providers:
|
||||
self.__on_css_added(self.project, css)
|
||||
|
||||
def __on_merengue_handle_command(self, merengue, payload):
|
||||
def __on_merengue_stdout(self, process, condition):
|
||||
if condition == GLib.IOCondition.HUP:
|
||||
self.__merengue.stop()
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
if self.__merengue.stdout is None:
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
retval = self.__merengue.stdout.readline()
|
||||
cmd = None
|
||||
|
||||
try:
|
||||
cmd = json.loads(payload)
|
||||
cmd = json.loads(retval)
|
||||
command = cmd.get("command", None)
|
||||
args = cmd.get("args", {})
|
||||
except Exception as e:
|
||||
logger.warning(f"Merengue command error: {e}")
|
||||
self.__merengue.stop()
|
||||
return
|
||||
|
||||
if command == "selection_changed":
|
||||
self.__command_selection_changed(**args)
|
||||
elif command == "started":
|
||||
self.__merengue.merengue_started = True
|
||||
self.__merengue_command("gtk_settings_get", args={"property": "gtk-theme-name"})
|
||||
|
||||
self.__load_namespaces()
|
||||
|
||||
self.__load_css_providers()
|
||||
|
||||
self.__ui = None
|
||||
self.__on_project_selection_changed(self.__project)
|
||||
elif command == "placeholder_selected":
|
||||
self.emit(
|
||||
@ -740,6 +599,61 @@ class CmbView(Gtk.Box):
|
||||
self.__theme = args["value"]
|
||||
self.notify("gtk_theme")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Merenge output error: {e}")
|
||||
self.__merengue.stop()
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
def __on_broadwayd_stdout(self, process, condition):
|
||||
if condition == GLib.IOCondition.HUP:
|
||||
self.__broadwayd.stop()
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
if self.__broadwayd.stdout is None:
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
status, retval, length, terminator = self.__broadwayd.stdout.read_line()
|
||||
# path = retval.replace("Listening on ", "").strip()
|
||||
|
||||
# Run view process
|
||||
if self.__project.target_tk == "gtk+-3.0":
|
||||
version = "3.0"
|
||||
elif self.__project.target_tk == "gtk-4.0":
|
||||
version = "4.0"
|
||||
|
||||
display = self.__port - 8080
|
||||
|
||||
env = json.loads(os.environ.get("MERENGUE_DEV_ENV", "{}"))
|
||||
self.__merengue.run(
|
||||
[version],
|
||||
env
|
||||
| {
|
||||
"GDK_BACKEND": "broadway",
|
||||
# 'GTK_DEBUG': 'interactive',
|
||||
"BROADWAY_DISPLAY": f":{display}",
|
||||
},
|
||||
)
|
||||
|
||||
# Load broadway desktop
|
||||
self.webview.load_uri(f"http://127.0.0.1:{self.__port}")
|
||||
|
||||
self.__broadwayd.stdout.shutdown(False)
|
||||
self.__broadwayd.stdout = None
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
def __find_free_port(self):
|
||||
for port in range(8080, 8180):
|
||||
s = socket.socket()
|
||||
retval = s.connect_ex(("127.0.0.1", port))
|
||||
s.close()
|
||||
|
||||
if retval != 0:
|
||||
return port
|
||||
|
||||
return 0
|
||||
|
||||
def __add_remove_placeholder(self, command, modifier):
|
||||
if self.project is None:
|
||||
return
|
||||
@ -759,4 +673,3 @@ class CmbView(Gtk.Box):
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbView, "CmbView")
|
||||
|
||||
|
@ -1,9 +1,18 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<!-- Created with Cambalache 0.17.3 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_view.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.14"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="webkitgtk" version="6.0"/>
|
||||
<object class="WebKitSettings" id="settings">
|
||||
<property name="enable-fullscreen">False</property>
|
||||
<property name="enable-html5-database">False</property>
|
||||
<property name="enable-html5-local-storage">False</property>
|
||||
<property name="enable-media">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>
|
||||
</object>
|
||||
<template class="CmbView" parent="GtkBox">
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
@ -13,45 +22,11 @@
|
||||
<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 class="WebKitWebView" id="webview">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="settings">settings</property>
|
||||
<property name="visible">True</property>
|
||||
<signal name="context-menu" handler="on_context_menu"/>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui_view</property>
|
||||
@ -73,6 +48,7 @@
|
||||
<property name="cursor-visible">False</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="lang">xml</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@ -91,30 +67,6 @@
|
||||
<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>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
@ -1,5 +1,5 @@
|
||||
VERSION = '@VERSION@'
|
||||
FILE_FORMAT_VERSION = '@fileformatversion@'
|
||||
pkgdatadir = '@pkgdatadir@'
|
||||
merenguedir = '@merenguedir@'
|
||||
catalogsdir = '@catalogsdir@'
|
||||
merenguedir = '@merenguedir@'
|
||||
|
@ -19,8 +19,6 @@
|
||||
# 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.
|
||||
|
@ -19,13 +19,9 @@
|
||||
# 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
|
||||
@ -36,7 +32,6 @@ 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
|
||||
|
@ -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
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import Gdk, GObject, Gtk, Pango
|
||||
|
||||
|
@ -19,14 +19,11 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
from gi.repository import GLib, Gtk
|
||||
from .cmb_boolean_undefined import CmbBooleanUndefined
|
||||
from .cmb_entry import CmbEntry
|
||||
from .cmb_file_entry import CmbFileEntry
|
||||
from .cmb_icon_name_entry import CmbIconNameEntry
|
||||
@ -36,12 +33,11 @@ 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_switch import CmbSwitch
|
||||
from .cmb_text_view import CmbTextView
|
||||
|
||||
|
||||
def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
|
||||
def cmb_create_editor(project, type_id, prop=None, data=None):
|
||||
def get_min_max_for_type(type_id):
|
||||
if type_id == "gchar":
|
||||
return (GLib.MININT8, GLib.MAXINT8)
|
||||
@ -118,8 +114,6 @@ def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
|
||||
adjustment = Gtk.Adjustment(lower=minimum, upper=maximum, step_increment=step_increment, page_increment=10)
|
||||
|
||||
editor = CmbSpinButton(digits=digits, adjustment=adjustment)
|
||||
elif type_id == "GBytes":
|
||||
editor = CmbTextView(hexpand=True)
|
||||
elif type_id == "GStrv":
|
||||
editor = CmbTextView(hexpand=True)
|
||||
elif type_id == "GdkRGBA":
|
||||
@ -134,31 +128,11 @@ def cmb_create_editor(project, type_id, prop=None, data=None, parent=None):
|
||||
editor = CmbIconNameEntry(hexpand=True, placeholder_text="<Icon Name>")
|
||||
elif type_id in ["GtkShortcutTrigger", "GtkShortcutAction"]:
|
||||
editor = CmbEntry(hexpand=True, placeholder_text=f"<{type_id}>")
|
||||
elif type_id == "CmbBooleanUndefined":
|
||||
editor = CmbBooleanUndefined()
|
||||
elif type_id == "CmbAccessibleList":
|
||||
editor = CmbObjectListEditor(
|
||||
parent=prop.object if prop else parent,
|
||||
type_id="GtkAccessible",
|
||||
)
|
||||
elif info:
|
||||
if info.is_object or info.parent_id == "interface":
|
||||
if prop is None:
|
||||
editor = CmbObjectChooser(
|
||||
project=project,
|
||||
parent=parent,
|
||||
type_id=type_id,
|
||||
)
|
||||
else:
|
||||
editor = CmbObjectChooser(
|
||||
project=project,
|
||||
parent=prop.object,
|
||||
is_inline=project.target_tk == "gtk-4.0" and not prop.info.disable_inline_object,
|
||||
inline_object_id=prop.inline_object_id,
|
||||
inline_property_id=prop.property_id,
|
||||
type_id=type_id,
|
||||
)
|
||||
elif info.parent_id == "enum":
|
||||
# TODO: replace prop with project and is_inline
|
||||
editor = CmbObjectChooser(parent=prop.object, prop=prop)
|
||||
if info.parent_id == "enum":
|
||||
editor = CmbEnumComboBox(info=info)
|
||||
elif info.parent_id == "flags":
|
||||
editor = CmbFlagsEntry(info=info)
|
||||
|
@ -20,8 +20,6 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
from .cmb_translatable_popover import CmbTranslatablePopover
|
||||
@ -54,8 +52,4 @@ class CmbEntry(Gtk.Entry):
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
# We do not want to emit a change if there is none
|
||||
if value == self.props.text:
|
||||
return
|
||||
|
||||
self.props.text = value if value is not None else ""
|
||||
|
@ -20,8 +20,6 @@
|
||||
# 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 CmbTypeInfo
|
||||
@ -30,7 +28,7 @@ from ..cmb_type_info import CmbTypeInfo
|
||||
class CmbEnumComboBox(Gtk.ComboBox):
|
||||
__gtype_name__ = "CmbEnumComboBox"
|
||||
|
||||
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
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):
|
||||
@ -42,8 +40,6 @@ class CmbEnumComboBox(Gtk.ComboBox):
|
||||
self.add_attribute(renderer_text, "text", self.text_column)
|
||||
|
||||
self.props.id_column = self.text_column
|
||||
|
||||
if self.info:
|
||||
self.props.model = self.info.enum
|
||||
|
||||
def __on_changed(self, obj):
|
||||
@ -55,13 +51,12 @@ class CmbEnumComboBox(Gtk.ComboBox):
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if self.info is None:
|
||||
return
|
||||
|
||||
self.props.active_id = None
|
||||
active_id = self.info.enum_get_value_as_string(value)
|
||||
|
||||
if active_id == self.props.active_id:
|
||||
return
|
||||
for row in self.info.enum:
|
||||
enum_name = row[0]
|
||||
enum_nick = row[1]
|
||||
|
||||
self.props.active_id = active_id
|
||||
# Always use nick as value
|
||||
if value == enum_name or value == enum_nick:
|
||||
self.props.active_id = enum_nick
|
||||
|
@ -1,114 +0,0 @@
|
||||
#
|
||||
# CmbFileButton
|
||||
#
|
||||
# Copyright (C) 2021-2023 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from cambalache import _
|
||||
from gi.repository import GObject, Gio, Gtk
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/control/cmb_file_button.ui")
|
||||
class CmbFileButton(Gtk.Button):
|
||||
__gtype_name__ = "CmbFileButton"
|
||||
|
||||
dirname = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
dialog_title = GObject.Property(type=str, default=_("Select filename"), flags=GObject.ParamFlags.READWRITE)
|
||||
accept_label = GObject.Property(type=str, default=_("Select"), flags=GObject.ParamFlags.READWRITE)
|
||||
unnamed_filename = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
use_open = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
label = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.__filename = None
|
||||
self.__filters = None
|
||||
|
||||
@Gtk.Template.Callback("on_button_clicked")
|
||||
def __on_button_clicked(self, button):
|
||||
dialog = Gtk.FileDialog(
|
||||
modal=True,
|
||||
filters=self.__filters,
|
||||
title=self.dialog_title,
|
||||
accept_label=self.accept_label
|
||||
)
|
||||
|
||||
if self.dirname is not None:
|
||||
if self.__filename:
|
||||
fullpath = os.path.join(self.dirname, self.__filename)
|
||||
|
||||
file = Gio.File.new_for_path(fullpath)
|
||||
dialog.set_initial_file(file)
|
||||
|
||||
# See which filter matches the file info and use it as default
|
||||
if file.query_exists(None):
|
||||
info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, Gio.FileQueryInfoFlags.NONE, None)
|
||||
for filter in self.__filters:
|
||||
if filter.match(info):
|
||||
dialog.set_default_filter(filter)
|
||||
break
|
||||
else:
|
||||
dialog.set_initial_folder(Gio.File.new_for_path(self.dirname))
|
||||
if self.unnamed_filename:
|
||||
dialog.set_initial_name(self.unnamed_filename)
|
||||
|
||||
def dialog_callback(dialog, res):
|
||||
try:
|
||||
file = dialog.open_finish(res) if self.use_open else dialog.save_finish(res)
|
||||
self.cmb_value = os.path.relpath(file.get_path(), start=self.dirname)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.use_open:
|
||||
dialog.open(self.get_root(), None, dialog_callback)
|
||||
else:
|
||||
dialog.save(self.get_root(), None, dialog_callback)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.__filename
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_cmb_value(self, value):
|
||||
if value == self.__filename:
|
||||
return
|
||||
|
||||
self.__filename = value if value is not None else ""
|
||||
self.label.set_label(self.__filename)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def mime_types(self):
|
||||
if self.__filters:
|
||||
return ";".join([f.props.mime_types for f in self.__filters])
|
||||
return ""
|
||||
|
||||
@mime_types.setter
|
||||
def _set_mime_types(self, value):
|
||||
if value:
|
||||
self.__filters = Gio.ListStore()
|
||||
for mime in value.split(';'):
|
||||
self.__filters.append(Gtk.FileFilter(mime_types=[mime]))
|
||||
else:
|
||||
self.__filters = None
|
@ -1,34 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_file_button.ui -->
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<template class="CmbFileButton" parent="GtkButton">
|
||||
<signal name="clicked" handler="on_button_clicked"/>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="file_icon">
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="ellipsize">start</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label">(None)</property>
|
||||
<property name="width-chars">8</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">folder-open-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -20,13 +20,11 @@
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from cambalache import _
|
||||
from gi.repository import GObject, Gio, Gtk
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
||||
class CmbFileEntry(Gtk.Entry):
|
||||
@ -37,7 +35,7 @@ class CmbFileEntry(Gtk.Entry):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.title = _("Select File")
|
||||
self.title = (_("Select File"),)
|
||||
self.filter = None
|
||||
self.props.placeholder_text = "<GFile>"
|
||||
self.props.secondary_icon_name = "document-open-symbolic"
|
||||
@ -45,24 +43,19 @@ class CmbFileEntry(Gtk.Entry):
|
||||
self.connect("notify::text", self.__on_text_notify)
|
||||
self.connect("icon-press", self.__on_icon_pressed)
|
||||
|
||||
def __on_icon_pressed(self, widget, icon_pos):
|
||||
dialog = Gtk.FileDialog(
|
||||
modal=True,
|
||||
title=self.title,
|
||||
default_filter=self.filter,
|
||||
def __on_icon_pressed(self, widget, icon_pos, event):
|
||||
# Create Open Dialog
|
||||
dialog = Gtk.FileChooserNative(
|
||||
title=self.title, transient_for=self.get_toplevel(), action=Gtk.FileChooserAction.OPEN, filter=self.filter
|
||||
)
|
||||
|
||||
if self.dirname is not None:
|
||||
dialog.set_initial_folder(Gio.File.new_for_path(self.dirname))
|
||||
dialog.set_current_folder(self.dirname)
|
||||
|
||||
def dialog_callback(dialog, res):
|
||||
try:
|
||||
file = dialog.open_finish(res)
|
||||
self.props.text = os.path.relpath(file.get_path(), start=self.dirname)
|
||||
except Exception:
|
||||
pass
|
||||
if dialog.run() == Gtk.ResponseType.ACCEPT:
|
||||
self.props.text = os.path.relpath(dialog.get_filename(), start=self.dirname)
|
||||
|
||||
dialog.open(self.get_root(), None, dialog_callback)
|
||||
dialog.destroy()
|
||||
|
||||
def __on_text_notify(self, obj, pspec):
|
||||
self.notify("cmb-value")
|
||||
@ -73,6 +66,4 @@ class CmbFileEntry(Gtk.Entry):
|
||||
|
||||
@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 ""
|
||||
|
@ -20,8 +20,6 @@
|
||||
# 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 CmbTypeInfo
|
||||
@ -34,7 +32,6 @@ class CmbFlagsEntry(Gtk.Entry):
|
||||
id_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
text_column = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
value_column = GObject.Property(type=int, default=2, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
separator = GObject.Property(type=str, default="|", flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.flags = {}
|
||||
@ -85,7 +82,7 @@ class CmbFlagsEntry(Gtk.Entry):
|
||||
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} {self.separator} {flag_id}"
|
||||
retval = flag_id if retval is None else f"{retval} | {flag_id}"
|
||||
|
||||
return retval if retval is not None else ""
|
||||
|
||||
@ -95,9 +92,6 @@ class CmbFlagsEntry(Gtk.Entry):
|
||||
|
||||
@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 ""
|
||||
|
||||
self.flags = {}
|
||||
@ -105,7 +99,7 @@ class CmbFlagsEntry(Gtk.Entry):
|
||||
self._checks[check].props.active = False
|
||||
|
||||
if value:
|
||||
tokens = [t.strip() for t in value.split(self.separator)]
|
||||
tokens = [t.strip() for t in value.split("|")]
|
||||
|
||||
for row in self.info.flags:
|
||||
flag_id = row[self.id_column]
|
||||
|
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