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.5.9" have entirely different histories.
18
.flake8
18
.flake8
@ -1,18 +0,0 @@
|
||||
[flake8]
|
||||
max-line-length = 128
|
||||
extend-ignore = E203
|
||||
per-file-ignores =
|
||||
cambalache/__init__.py:F401,E402
|
||||
cambalache/app/__init__.py:F401,E402
|
||||
cambalache/control/__init__.py:F401,E402
|
||||
cambalache/merengue/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_adw/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_handy/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_webkit/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_webkit2/__init__.py:F401,E402
|
||||
cambalache/merengue/mrg_gtk/__init__.py:F401,E402
|
||||
glade/gladecambalache/__init__.py:F401,E402
|
||||
exclude =
|
||||
.flatpak-builder
|
||||
build
|
||||
_build
|
31
.gitignore
vendored
31
.gitignore
vendored
@ -1,28 +1,9 @@
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
__pycache__
|
||||
.flatpak-builder
|
||||
.catalogs
|
||||
.local
|
||||
.lc_messages
|
||||
.vscode
|
||||
.env.local
|
||||
.coverage*
|
||||
build
|
||||
repo
|
||||
cambalache.flatpak
|
||||
cambalache/cambalache.gresource
|
||||
cambalache/merengue.gresource
|
||||
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
|
||||
tools/CmbUtils-4.0.typelib
|
||||
tools/libcmbutils3.so
|
||||
tools/libcmbutils4.so
|
||||
data/gschemas.compiled
|
||||
data/mime
|
||||
src/cambalache_app.gresource
|
||||
src/config.py
|
||||
tools/CmbUtils-0.1.gir
|
||||
tools/CmbUtils-0.1.typelib
|
||||
tools/libcmbutils.so
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "tests/images"]
|
||||
path = tests/images
|
||||
url = https://gitlab.gnome.org/jpu/cambalache-test-images.git
|
323
CHANGELOG.md
323
CHANGELOG.md
@ -1,323 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This documents user relevant changes which are also included in
|
||||
data/ar.xjuan.Cambalache.metainfo.xml.in, closed issues from
|
||||
(Gitlab)[https://gitlab.gnome.org/jpu/cambalache/-/issues/] and
|
||||
packaging changes like new dependencies or build system changes.
|
||||
|
||||
Cambalache used even/odd minor numbers to differentiate between stable and
|
||||
development releases.
|
||||
|
||||
|
||||
## 0.96.0
|
||||
|
||||
- Add GResource support
|
||||
- Add internal children support
|
||||
- New project format
|
||||
- Save directly to .ui files
|
||||
- Show directory structure in navigation
|
||||
- Unified import dialog for all file types
|
||||
- Add Finnish translation. Erwinjitsu
|
||||
- Use AdwAboutDialog lo-vely
|
||||
- Add action child type to GtkDialog
|
||||
|
||||
### Packaging changes
|
||||
|
||||
- pygobject-3.0 dependency bumped to 3.52 which depends on the new gi repository from GLib
|
||||
- libcambalacheprivate-[3|4] and its typelib are now installed under libdir/cambalache
|
||||
- libcmbcatalogutils-[3|4] and its typelib are now installed under libdir/cmb_catalog_gen
|
||||
- Gtk 3, Handy, webkit2gtk and webkitgtk are now optional dependencies
|
||||
|
||||
### Issues
|
||||
|
||||
- #253 "Error updating UI 1: gtk-builder-error-quark: .:8:1 Invalid object type 'AdwApplicationWindow' (6)"
|
||||
- #145 "Consider Cambalache to manage resource description file for building the resource bundle"
|
||||
- #54 "Add support for internal children"
|
||||
- #255 "Unable to open files via the UI in a KDE Plasma session"
|
||||
- #260 "Wrong default for Swap setting in signals"
|
||||
- #259 "Install private shared libraries in sub directories of the main library path"
|
||||
- #263 "Translatable setting resets when label field is empty"
|
||||
- #264 "Error undoing removal of parent GtkGrid"
|
||||
- #266 "Error "Unknown internal child: entry (6)" with particular GTK 3 UI file"
|
||||
- #265 "GtkButtonBox shows too many buttons"
|
||||
- #267 "Make drag'n'drop of top-level more intuitive"
|
||||
- #269 "Failed to display some element of a validated ui file"
|
||||
- #272 "Background of compositor does not change colors, when adwaita colors are changed"
|
||||
- #273 "GtkComboBoxText items gets their translatable property removed"
|
||||
|
||||
## 0.94.0
|
||||
|
||||
2024-11-25 - Accessibility release
|
||||
|
||||
- Gtk 4 and Gtk 3 accessibility support
|
||||
- Support property subclass override defaults
|
||||
- AdwDialog placeholder support
|
||||
- Improved object description in hierarchy
|
||||
- Lots of bug fixes and minor UI improvements
|
||||
|
||||
### Issues
|
||||
|
||||
- #252 "Workspace process error / "Error updating UI 1: gtk-builder-error-quark: .:185:38 Object with ID reset not found (13)" with specific UI file"
|
||||
- #251 "GTK 3 message dialog from specific .ui file rendered incorrectly"
|
||||
- #250 "Error trying to import specific (LibreOffice) GTK 3 .ui file: "'NoneType has no attribute 'type_id'""
|
||||
- #240 "Do not show cryptic paths for imported ui files (flatpak)"
|
||||
- #202 "cambalache crashes when using"
|
||||
- #203 "AdwActionRow : wrong default for activatable property"
|
||||
- #241 "Handle adding widgets in empty workspace"
|
||||
- #234 "Hold <alt> to create object in place is not clear"
|
||||
- #242 "Support quit via Ctrl + Q"
|
||||
- #239 "Preview feature is not clear"
|
||||
- #235 "Remember last saved / open location"
|
||||
- #236 "`Import` menu operation is not clear"
|
||||
- #233 "Widget tree is confusing"
|
||||
- #137 "Add accessibility support"
|
||||
- #232 "Crashes when restarting workspace"
|
||||
|
||||
## 0.92.0
|
||||
|
||||
2024-09-27 - Adwaita + Casilda release
|
||||
|
||||
- Support 3rd party libraries
|
||||
- Improved Drag&Drop support
|
||||
- Streamline headerbar
|
||||
- Replaced widget hierarchy treeview with column view
|
||||
- New custom wayland compositor for workspace
|
||||
- Improve workspace performance
|
||||
- Fix window ordering
|
||||
- Enable workspace animations
|
||||
- Basic port to Adwaita
|
||||
- Support new desktop dark style
|
||||
- Many thanks to emersion, kennylevinsen, vyivel and the wlroots community for their support and awesome project
|
||||
|
||||
### Packaging changes
|
||||
|
||||
- New dependency on [casilda 0.2.0](https://gitlab.gnome.org/jpu/casilda)
|
||||
Used for workspace compositor, depends on wlroots 0.18
|
||||
- New python tool cmb-catalog-gen
|
||||
- New shared library cmbcatalogutils-[3|4] used by cmb-catalog-gen
|
||||
This library is built twice once linked with Gtk 3 and one with Gtk 4
|
||||
- Depends on Gtk 4.16 and Adwaita 1.6
|
||||
|
||||
### Issues
|
||||
|
||||
- #231 "Workspace will crash with inserting Some Adw objects"
|
||||
- #230 "Exporting byte data messes encoding (libxml)"
|
||||
- #227 "Add casilda as meson subproject" (sid)
|
||||
- #220 "BUG: Typing cursor for style classes always in the front of style entries."
|
||||
- #222 "cannot create instance of abstract (non-instantiatable) type 'GtkWidget'"
|
||||
- #223 "Cannot add widgets to GtkSizeGroup"
|
||||
- #225 "Cambalache crashes"
|
||||
- #219 "Move existing widgets / hierarchy sections into property fields"
|
||||
- #224 "GtkPicture:file property does not work out of the box"
|
||||
- #11 "Support 3rd party libraries"
|
||||
- #216 "Cambalache 0.90.2 Segment faults"
|
||||
- #213 "Cannot open .ui file created using Gnome Builder"
|
||||
- #215 "Port UI to LibAdwaita"
|
||||
|
||||
|
||||
## 0.90.4
|
||||
|
||||
2024-03-29 - Gtk 4 port
|
||||
|
||||
- Migrate main application to Gtk 4
|
||||
- Update widget catalogs to SDK 46
|
||||
- Add support for child custom fragments
|
||||
- Add add parent context menu action
|
||||
- Mark AdwSplitButton.dropdown-tooltip translatable. (Danial Behzadi)
|
||||
- Bumped version to 0.90 to better indicate we are close to version 1.0
|
||||
- Add WebKitWebContext class
|
||||
- Add brand colors
|
||||
|
||||
### Issues
|
||||
|
||||
- #184 "Headerbar save button not enabled when "translatable" checkbox's state is changed"
|
||||
- #207 "Adding or changing data to signal doesn't activate 'Save' button"
|
||||
- #212 "[Feature] add parent"
|
||||
- #199 "Copy and pasting messes references between widgets"
|
||||
- #196 "postinstall.py is trying to modify files in prefix."
|
||||
- #201 "AdwToolbarView needs special child types"
|
||||
- #220 "BUG: Typing cursor for style classes always in the front of style entries."
|
||||
|
||||
|
||||
## 0.16.0
|
||||
|
||||
2023-09-24: GNOME 45 Release!
|
||||
|
||||
- Bump SDK dependency to SDK 45
|
||||
- Add support for types and properties added in SDK 45
|
||||
- Marked various missing translatable properties
|
||||
|
||||
### Issues
|
||||
|
||||
- #190 "Missing translatable property for Gtk.ColumnViewColumn.title"
|
||||
- #190 "Unassigned local variable"
|
||||
|
||||
|
||||
## 0.14.0
|
||||
|
||||
2023-09-07: GMenu release!
|
||||
|
||||
- Add GMenu support
|
||||
- Add UI requirements edit support
|
||||
- Add Swedish translation. Anders Jonsson
|
||||
- Updated Italian translation. Lorenzo Capalbo
|
||||
- Show deprecated and not available warnings for Objects, properties and signals
|
||||
- Output minimum required library version instead of latest one
|
||||
- Fix output for templates with inline object properties
|
||||
- Various optimizations and bug fixes
|
||||
- Bump test coverage to 66%
|
||||
|
||||
### Issues
|
||||
|
||||
- #185 "Unable to import certain files converted from GTK3 to GTK4""
|
||||
- #177 "Panel is not derivable"
|
||||
- #173 "Cambalache 0.12.0 can't open 0.10.3 project"
|
||||
|
||||
|
||||
## 0.12.0
|
||||
|
||||
2023-06-16: New Features release!
|
||||
|
||||
- User Templates: use your templates anywhere in your project
|
||||
- Workspace CSS support: see your CSS changes live
|
||||
- GtkBuildable Custom Tags: support for styles, items, etc
|
||||
- Property Bindings: bind your property to any source property
|
||||
- XML Fragments: add any xml to any object or UI as a fallback
|
||||
- Preview mode: hide placeholders in workspace
|
||||
- WebKit support: new widget catalog available
|
||||
- External objects references support
|
||||
- Add support for GdkPixbuf, GListModel and GListStore types
|
||||
- Add missing child type attributes to Gtk4 GtkActionBar (B. Teeuwen)
|
||||
- Added French Translation (rene-coty)
|
||||
|
||||
### Issues
|
||||
|
||||
- #121 "Adding handy fails silently without libhandy installed"
|
||||
- #113 "Add button/toggle to disable the placeholders and make the window look like it would look as an app"
|
||||
- #123 "Export should be more user-friendly"
|
||||
- #130 "GtkAboutDialog missing properties"
|
||||
- #135 "List of string properties that should be translatable in Adw"
|
||||
- #136 "Can't build via Flatpak"
|
||||
- #138 "libadwaita widgets aren't categorized"
|
||||
- #122 "Handy widgets not correctly categorized."
|
||||
- #96 "Window resize itself when cut content of notebook tab and go to first tab"
|
||||
- #101 "Right clicking after deselcting button, brokes mouse input"
|
||||
- #120 "Box doesn't remove empty space"
|
||||
- #147 "The "Close" button doesn't close the "About" dialog."
|
||||
- #146 "Scrolling a properties pane conflicts with mousewheel handling of property widgets"
|
||||
- #143 "Support for nested files"
|
||||
- #148 "bug: preview display"
|
||||
- #156 "GDK_BACKEND leaks to workspace process"
|
||||
- #154 "GtkPaned: for properties to be set consistently, need to use start-child and end-child instead of child
|
||||
- #160 "Faster prototyping"
|
||||
- #166 "Allow external Widget or/and from another ui template"
|
||||
- #163 "Add named object to Gtk.Stack"
|
||||
- #170 "Support for actions (GtkActionable, menu models)"
|
||||
- #169 "[main] GtkOrientable is missing in GtkBox properties (maybe in others too)"
|
||||
- #167 "Gtk*Selection models are missing the model property"
|
||||
- #168 "Is there a way to add string items to a GtkStringList?"
|
||||
- #171 "Extended support for inline objects"
|
||||
- #172 "Certain Adw widgets are not availabe (AdwEntryRow)"
|
||||
|
||||
|
||||
## 0.10.0
|
||||
|
||||
2022-06-15: 3rd party libs release!
|
||||
|
||||
- Add Adwaita and Handy library support
|
||||
- Add inline object properties support (only Gtk 4)
|
||||
- Add special child type support (GtkWindow title widget)
|
||||
- Improve clipboard functionality
|
||||
- Add support for reordering children position
|
||||
- Add/Improve worspace support for GtkMenu, GtkNotebook, GtkPopover, GtkStack, GtkAssistant, GtkListBox, GtkMenuItem and GtkCenterBox
|
||||
- New property editors for icon name and color properties
|
||||
- Add support for GdkPixbuf, Pango, Gio, Gdk and Gsk flags/enums types
|
||||
- Add Ukrainian translation (Volodymyr M. Lisivka)
|
||||
- Add Italian translation (capaz)
|
||||
- Add Dutch translation (Gert)
|
||||
|
||||
### Issues
|
||||
|
||||
- #47 "Proper ui file(which compile properly), fails to open in cambalache and show error"
|
||||
- #79 "Change column/row count of GtkBox and GtkGrid"
|
||||
- #81 "No way to add rows to GtkListBox"
|
||||
- #68 "Trouble with GtkHeaderBar"
|
||||
- #82 "Can't change x and y values of widgets in Gtk4 when using GtkFixed"
|
||||
- #62 "Many widget-specific properties appear to be missing"
|
||||
- #83 "Gettext domain is not initialized properly"
|
||||
- #66 "Allow adding new items directly in tree view instead of (only) through preview view"
|
||||
- #86 "Automatically restart merengue when merengue crashes"
|
||||
- #89 "Error `AttributeError: 'NoneType' object has no attribute 'info'` when deleting UI file"
|
||||
- #90 "Cambalache fails to import valid glade/ui files"
|
||||
- #75 "How to use GtkStack"
|
||||
- #78 "How to use GTKAssistant"
|
||||
- #63 "Allow automatically exporting on save (or make it easier to do so)"
|
||||
- #91 "Unable to export"
|
||||
- #85 "Provide icon selection for Button / Image"
|
||||
- #92 "'Debug Project Data' does nothing"
|
||||
- #9 "Support for libadwaita and libhandy"
|
||||
- #59 "Reordering children in a parent"
|
||||
- #100 "Signals get broken"
|
||||
- #105 "Child layout properties not available when parent is a subclass (AdwHeaderBar)"
|
||||
- #102 "Popovers are not visible"
|
||||
- #104 "Error when trying to add children to buttonbox"
|
||||
- #98 "No way to add tab in Notebook"
|
||||
- #108 "Popovers stay on scene after deleting file which contains them"
|
||||
- #109 "Cambalache adds to container GtkRecentChooserMenu even if prints that this won't happen"
|
||||
- #110 "Screen flashing when creating GBinding"
|
||||
- #116 "Error when trying to click at Notebook content"
|
||||
- #117 "Error `'NoneType' object has no attribute 'props'` when changing notebook tab"
|
||||
- #115 "Cannot copy/paste widget"
|
||||
- #69 "Undo and redo operations don't always match up"
|
||||
|
||||
|
||||
## 0.8.0
|
||||
|
||||
2021-12-09: UX improvements Release!
|
||||
|
||||
- New Type chooser bar
|
||||
- Workspace placeholder support
|
||||
- Translatable properties support (Philipp Unger)
|
||||
- Clipboard actions support (Copy, Paste, Cut)
|
||||
- Better unsupported features report
|
||||
- New Matrix channel #cambalache:gnome.org
|
||||
- You can now also support Cambalache on Liberapay
|
||||
|
||||
### Issues
|
||||
|
||||
- #22: Gtk.AboutDialog: license bug
|
||||
- #10: Export widgets layout data packed in GtkGrid
|
||||
- #23: Better appdata summary
|
||||
- #25: Error about target version mismatch
|
||||
- #29: Error opening project
|
||||
- #27: Needs a better icon
|
||||
- #31: Newest ver (git) doesn't display loaded UI
|
||||
- #34: Translations aren't working in the interactive tour
|
||||
- #35: Interactive tour isn't working anymore
|
||||
- #30: Gtk types listed in Cambalache
|
||||
- #36: Can't build Flatpak after the update of the german translation
|
||||
- #38: Add translatable metadata to CmbPropertyInfo
|
||||
- #37: Add support for translatable properties
|
||||
- #39: Save window state (Philipp Unger)
|
||||
- #41: Add clipboard support
|
||||
- #33: No context menu on left pane, the "project view"
|
||||
|
||||
|
||||
## 0.7.0
|
||||
|
||||
2021-08-08: New translations release!
|
||||
|
||||
- Add Czech translation. Vojtěch Perník
|
||||
- Add German translation. PhilProg
|
||||
- Add x-cambalache mimetype with icon
|
||||
|
||||
|
||||
## 0.6.0
|
||||
|
||||
2021-07-21: First public release!
|
||||
|
||||
- Suport for both Gtk 3 and 4 versions
|
||||
- Import and export multiple UI at once
|
||||
- Support plain (no custom tags) GtkBuilder features
|
||||
- Undo / Redo stack
|
||||
- LGPL version 2.1
|
461
COPYING
461
COPYING
@ -1,459 +1,8 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
Cambalache UI Maker
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
Copyright (C) 2020-2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Unauthorized copying of this project, via any medium is strictly prohibited.
|
||||
|
||||
This application is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
280
COPYING.GPL
280
COPYING.GPL
@ -1,280 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
60
Dockerfile
60
Dockerfile
@ -1,60 +0,0 @@
|
||||
FROM debian:sid-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
desktop-file-utils \
|
||||
gettext \
|
||||
gir1.2-adw-1 \
|
||||
gir1.2-gtk-3.0 \
|
||||
gir1.2-gtk-4.0 \
|
||||
gir1.2-gtksource-5 \
|
||||
gir1.2-handy-1 \
|
||||
gir1.2-webkit2-4.1 \
|
||||
gir1.2-webkit-6.0 \
|
||||
git \
|
||||
libadwaita-1-dev \
|
||||
libgirepository-1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libgtk-4-dev \
|
||||
libhandy-1-dev \
|
||||
libwlroots-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
python3-gi \
|
||||
python3-lxml \
|
||||
python-gi-dev
|
||||
|
||||
|
||||
RUN useradd -ms /bin/bash discepolo
|
||||
ENV DISPLAY :0
|
||||
|
||||
RUN mkdir -p /src/build
|
||||
|
||||
COPY . /src/
|
||||
WORKDIR /src
|
||||
|
||||
RUN git clone -b 0.18 https://gitlab.freedesktop.org/wlroots/wlroots.git && \
|
||||
cd wlroots && \
|
||||
meson setup build/ && \
|
||||
ninja -C build/ && \
|
||||
ninja -C build/ install
|
||||
|
||||
WORKDIR /src/build
|
||||
|
||||
RUN meson --prefix=/usr && ninja && ninja install
|
||||
|
||||
RUN rm -rf /src
|
||||
|
||||
RUN apt-get remove -y \
|
||||
git \
|
||||
libadwaita-1-dev \
|
||||
libgirepository-1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libgtk-4-dev \
|
||||
libhandy-1-dev \
|
||||
libwlroots-dev \
|
||||
meson \
|
||||
ninja-build \
|
||||
python-gi-dev
|
||||
|
||||
USER discepolo
|
||||
ENTRYPOINT ["/bin/sh", "-c", "$0 \"$@\"", "cambalache"]
|
19
Makefile
19
Makefile
@ -1,19 +0,0 @@
|
||||
repo: ar.xjuan.Cambalache.json .git/objects
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak install --noninteractive --user flathub org.gnome.Sdk//48
|
||||
flatpak install --noninteractive --user flathub org.gnome.Platform//48
|
||||
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
|
||||
|
||||
cambalache.flatpak: repo
|
||||
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache
|
||||
|
||||
.PHONY: install clean veryclean
|
||||
|
||||
install: cambalache.flatpak
|
||||
flatpak install --user cambalache.flatpak
|
||||
|
||||
clean:
|
||||
rm -rf repo cambalache.flatpak
|
||||
|
||||
veryclean: clean
|
||||
rm -rf .flatpak-builder
|
@ -1,16 +0,0 @@
|
||||
## README Mac OS
|
||||
|
||||
There is an on going effort to run Cambalache using dependencies from mac port.
|
||||
See issue [#161](https://gitlab.gnome.org/jpu/cambalache/-/issues/161)
|
||||
|
||||
In the mean time you can run Cambalache building a docker image and installing
|
||||
a X server.
|
||||
|
||||
Steps:
|
||||
- Install [Docker](https://www.docker.com/) and [Xquarts](https://www.xquartz.org/)
|
||||
- Build cambalache docker image
|
||||
- `docker build -t cambalache .`
|
||||
- Make sure docker can connect to the server
|
||||
- `xhost +localhost`
|
||||
- Run docker image
|
||||
- `docker run -e DISPLAY=host.docker.internal:0 cambalache`
|
191
README.md
191
README.md
@ -1,196 +1,19 @@
|
||||

|
||||
# Cambalache
|
||||
|
||||
Cambalache is a 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.
|
||||
UI Designer Library
|
||||
|
||||

|
||||
## cambalache-db
|
||||
|
||||
To support multiple Gtk versions it renders the workspace out of process using
|
||||
a custom wayland compositor widget based on wlroots.
|
||||
Cambalache Data Model generation tool
|
||||
|
||||

|
||||
## db-codegen
|
||||
|
||||
## License
|
||||
|
||||
Cambalache is distributed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html),
|
||||
version 2.1 (LGPL) as described in the COPYING file.
|
||||
|
||||
Tools are distributed under the [GNU General Public License](https://www.gnu.org/licenses/gpl-2.0.en.html),
|
||||
version 2 (GPL) as described in the COPYING.GPL file.
|
||||
|
||||
## Source code
|
||||
|
||||
Source code lives on GNOME gitlab [here](https://gitlab.gnome.org/jpu/cambalache)
|
||||
|
||||
`git clone https://gitlab.gnome.org/jpu/cambalache.git`
|
||||
|
||||
## Dependencies
|
||||
|
||||
* Python 3 - Cambalache is written in Python
|
||||
* [Meson](http://mesonbuild.com) build system
|
||||
* [GTK](http://www.gtk.org) 3 and 4
|
||||
* python-gi - Python GTK bindings
|
||||
* python3-lxml - Python libxml2 bindings
|
||||
* [casilda](https://gitlab.gnome.org/jpu/casilda) - Workspace custom compositor
|
||||
|
||||
## Flathub
|
||||
|
||||
Flathub is the place to get and distribute apps for all of desktop Linux.
|
||||
It is powered by Flatpak, allowing Flathub apps to run on almost any Linux
|
||||
distribution.
|
||||
|
||||
Instructions on how to install flatpak can be found [here](https://flatpak.org/setup/).
|
||||
|
||||
You can get the official build [here](https://flathub.org/apps/details/ar.xjuan.Cambalache)
|
||||
|
||||
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
|
||||
```
|
||||
Tool to generate GObject classes from DB tables
|
||||
|
||||
## Flatpak
|
||||
|
||||
Use the following commands to install build dependencies:
|
||||
|
||||
```
|
||||
flatpak remote-add --user --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
|
||||
flatpak install --user org.gnome.Sdk//master
|
||||
flatpak install --user org.gnome.Platform//master
|
||||
```
|
||||
|
||||
Build your bundle with the following commands
|
||||
|
||||
```
|
||||
flatpak-builder --force-clean --repo=repo build ar.xjuan.Cambalache.json
|
||||
flatpak build-bundle repo cambalache.flatpak ar.xjuan.Cambalache
|
||||
flatpak install --user cambalache.flatpak
|
||||
```
|
||||
Or if you have `make` installed in your host
|
||||
|
||||
```
|
||||
make install
|
||||
flatpak install cambalache.flatpak
|
||||
```
|
||||
|
||||
Will create the flatpak repository, then the bundle and install it
|
||||
|
||||
Run as:
|
||||
```
|
||||
flatpak run --user ar.xjuan.Cambalache//master
|
||||
```
|
||||
|
||||
## Manual installation
|
||||
|
||||
This is a regular meson package and can be installed the usual way.
|
||||
|
||||
```
|
||||
# Configure project in _build directory
|
||||
meson setup --wipe --prefix=~/.local _build .
|
||||
|
||||
# Build and install in ~/.local
|
||||
ninja -C _build install
|
||||
```
|
||||
|
||||
To run it from .local/ you might need to setup a few env variable depending on your distribution
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
While docker is not meant for UI applications it is possible to build an image
|
||||
with Cambalache and run it.
|
||||
|
||||
Build the image with:
|
||||
```
|
||||
docker build -t cambalache .
|
||||
```
|
||||
|
||||
On linux you can run it on wayland with:
|
||||
|
||||
```
|
||||
docker run \
|
||||
-e XDG_RUNTIME_DIR=/tmp \
|
||||
-e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \
|
||||
-v $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/tmp/$WAYLAND_DISPLAY \
|
||||
--user=$(id -u):$(id -g) \
|
||||
cambalache
|
||||
```
|
||||
|
||||
or on X server with:
|
||||
```
|
||||
xhost +local:
|
||||
docker run -v /tmp/.X11-unix:/tmp/.X11-unix cambalache
|
||||
```
|
||||
|
||||
NOTE: There is no official support for Docker, please use Flatpak if possible.
|
||||
|
||||
## MS Windows
|
||||
|
||||
Instructions to run in MS Windows are [here](README.win.md)
|
||||
|
||||
NOTE: There is no official support for Windows yet, these instruction should be
|
||||
taken with a grain of salt as they might not work on all Windows versions or
|
||||
be obsolete.
|
||||
|
||||
## MacOS
|
||||
|
||||
Instructions to run in MacOS are [here](README.mac.md)
|
||||
|
||||
NOTE: There is no official support for MacOS yet, these instruction should be
|
||||
taken with a grain of salt as they might not work on all MacOS versions or
|
||||
be obsolete.
|
||||
|
||||
## Running from sources
|
||||
|
||||
To run it without installing use run-dev.sh script, it will automatically compile
|
||||
cambalache under .local directoy and set up all environment variables needed to
|
||||
run the app from the source directory. (Follow manual installation to ensure
|
||||
you have everything needed)
|
||||
|
||||
`./run-dev.py`
|
||||
|
||||
This is meant for Cambalache development only.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing you can open an issue [here](https://gitlab.gnome.org/jpu/cambalache/-/issues)
|
||||
and/or a merge request [here](https://gitlab.gnome.org/jpu/cambalache/-/merge_requests)
|
||||
|
||||
## Contact
|
||||
|
||||
You can hang with us and ask us questions on Matrix at #cambalache:gnome.org
|
||||
|
||||
[Matrix](https://matrix.to/#/#cambalache:gnome.org)
|
||||
|
||||
## Financial support
|
||||
|
||||
You can financially support Cambalache development on Liberapay or Patreon
|
||||
like all these [people](./SUPPORTERS.md) did.
|
||||
|
||||
[Liberapay](https://liberapay.com/xjuan)
|
||||
- Liberapay is a recurrent donations platform
|
||||
- Run by a non-profit organization
|
||||
- Source code is public
|
||||
- No commission fee
|
||||
- ~5% payment processing fee
|
||||
|
||||
[Patreon](https://www.patreon.com/cambalache)
|
||||
- Patreon is a membership platform for creators
|
||||
- Run by private company
|
||||
- No source code available
|
||||
- ~8% commission fee
|
||||
- ~8% payment processing fee
|
||||
|
||||
## cmb-catalog-gen
|
||||
|
||||
This tool is used to generate Cambalache catalogs 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.
|
||||
|
127
README.win.md
127
README.win.md
@ -1,127 +0,0 @@
|
||||
## README MS Windows
|
||||
|
||||
These instructions have been tested on Windows 10.
|
||||
They should also work on Windows 11.
|
||||
|
||||
## 1. Install WSL2
|
||||
1. Start -> type `cmd` -> Right-click `Command Prompt` -> select `Run as administrator`.
|
||||
2. Type this command:
|
||||
```
|
||||
wsl --install
|
||||
```
|
||||
By default, this will install Ubuntu 20.
|
||||
|
||||
## 2. Add Ubuntu 22 to WSL
|
||||
1. Start -> type `store` -> Select `Microsoft Store`.
|
||||
2. Click the search bar at the top. Type: `ubuntu 22`
|
||||
3. Click the result: `Ubuntu 22.04.x LTS`. Click `Install`.
|
||||
- Troubleshooting: If it says "There has been an error.", try rebooting Windows.
|
||||
|
||||
## 3. Configure a Linux user
|
||||
1. New window appears with message:
|
||||
> Installing, this may take a few minutes...
|
||||
2. Then:
|
||||
> Please create a default UNIX user account. The username does not need to match your Windows username.
|
||||
>
|
||||
> For more information visit: https://aka.ms/wslusers
|
||||
>
|
||||
> Enter new UNIX username:
|
||||
3. Type a username you want to use, e.g., `jmoore`.
|
||||
4. Type a password for the user, e.g. " ". (1 space character)
|
||||
5. Repeat the password.
|
||||
- You should now be at a Linux command prompt.
|
||||
## 4. Install software in Ubuntu 22
|
||||
- Start -> type `ubu` -> Select `Ubuntu 22.04.x`.
|
||||
- Paste these commands, one at a time. There will be interactive user prompts for password and Yes/No questions.
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install flatpak
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
```
|
||||
## 5. Install the `GWSL` X Windows Server for Microsoft Windows.
|
||||
1. Start -> type `store` -> Select `Microsoft Store`.
|
||||
2. Click the search bar at the top. Type: `gwsl`
|
||||
3. Click the result: `GWSL`. Click `Install`.
|
||||
4. Start -> type `gwsl` -> Select `GWSL`.
|
||||
5. Set `Public` to Checked. This allows X Windows clients from Ubuntu 22 WSL2 installation to connect to the GWSL X-Windows server.
|
||||
- The System Tray notification area should have a new orange icon, the GWSL X server running.
|
||||
## 6. Install and Run Cambalache as a flatpak
|
||||
1. Create a startup script for needed daemon, environment variables:
|
||||
```
|
||||
vi ~/start_cam.sh
|
||||
```
|
||||
You can put these commands on the command-line at first as a test, put them in another script, `~/.bashrc`, etc. Making a script helps when you want to start it from a MS Windows icon.
|
||||
|
||||
2. Paste these lines as the script:
|
||||
```
|
||||
#!/bin/sh
|
||||
export WEBKIT_DISABLE_COMPOSITING_MODE=1
|
||||
flatpak run --user ar.xjuan.Cambalache
|
||||
```
|
||||
- The middle line forces the Workspace area of Cambalache, displayed with `webkit`, to not try to use hardware acceleration. If it does try such acceleration in `WSL2`, the Workspace area cannot be drawn and remains blank, even though it is being rendered OK in `broadwayd`.
|
||||
|
||||
- The last line runs the flatpak for Cambalache.
|
||||
|
||||
Save and quit (`Esc :wq`).
|
||||
|
||||
3. Make the script executable:
|
||||
```
|
||||
chmod +x start_cam.sh
|
||||
```
|
||||
|
||||
4. Install Cambalache from flatpak.
|
||||
```
|
||||
sudo service dbus start
|
||||
flatpak install --user flathub ar.xjuan.Cambalache
|
||||
```
|
||||
- The first line starts the dbus service, required for flatpak to work. Modern Linux installations start dbus at boot, but WSL2 does not.
|
||||
|
||||
5. Run Cambalache from the command-line.
|
||||
```
|
||||
export DISPLAY="`grep nameserver /etc/resolv.conf | sed 's/nameserver //'`:0"
|
||||
./start_cam.sh
|
||||
```
|
||||
- The first line defines how to find the X windows server. In WSL2, it is at the IP address of the host Windows computer. The $DISPLAY environment variable lets X clients in WSL get to the X Windows server running in MS Windows. It ends up being a value like:
|
||||
```
|
||||
$ echo $DISPLAY
|
||||
192.168.144.1:0
|
||||
```
|
||||
`GWSL` was installed, started, and confirmed running in the previous section. So, it is at that IP address, running in MS Windows, waiting for X windows clients to connect to it.
|
||||
|
||||
## 7. Starting Cambalache from Windows
|
||||
1. Click the Windows taskbar Notification area (near the Clock) expand arrow -> (orange GWSL icon) -> `Dashboard`.
|
||||
2. Click `Shortcut Creator`.
|
||||
Enter the following settings:
|
||||
|
||||
- Shortcut Label: Cambalache
|
||||
|
||||
- Shortcut Command:
|
||||
```
|
||||
/home/(your WSL2 username)/start_cam.sh
|
||||
```
|
||||
e.g.
|
||||
```
|
||||
/home/jmoore/start_cam.sh
|
||||
```
|
||||
|
||||
- Run in: `Ubuntu-22.04` <-- IMPORTANT
|
||||
|
||||
- Click `More Options`.
|
||||
|
||||
- Select `Color Mode`: `Light Mode`. <-- especially if you run Windows in Dark Mode
|
||||
|
||||
- Select `Use DBus (Sudo Required)`: `True`.
|
||||
|
||||
When the icon is run, this will start dbus, required by flatpak - but will prompt for the user's password each time.
|
||||
|
||||
WSL2 also has the dubiously helpful feature of closing all WSL2 processes when there are no WSL2 terminal sessions open for 15 seconds. So it would kill Cambalache.
|
||||
|
||||
To defeat this, select this option. It keeps the WSL2 session running when there are no WSL2 console windows open.
|
||||
|
||||
- Click `Add to Start Menu`.
|
||||
|
||||
3. Start -> type `cam` -> `Right-click` `Cambalache on Ubuntu 22.04`.
|
||||
4. Select `Pin to Start`.
|
||||
5. Click `Start` again.
|
||||
- Note how Cambalache is now featured prominently and can be started easily from Windows.
|
||||
6. Click `Cambalache on Ubuntu 22.04` to start it.
|
@ -1,26 +0,0 @@
|
||||
# Cambalache supporters
|
||||
|
||||
Many thanks to all the people that support the project
|
||||
|
||||
- Stephan McCormick
|
||||
- Willo Vincent
|
||||
- Javier Jardón
|
||||
- Franz Gratzer
|
||||
- David
|
||||
- Sonny Piers
|
||||
- Patrick Griffis
|
||||
- Aemilia Scott
|
||||
- Jonathan K.
|
||||
- Luis Barron
|
||||
- Mitch 4J
|
||||
- JustRyan
|
||||
- Platon workaccount
|
||||
- ~1826340
|
||||
- Mula Gabriel
|
||||
- Felipe Borges
|
||||
- Johannes Deutsch
|
||||
- Patrick
|
||||
- 2 kojix
|
||||
- Coleman
|
||||
- Muasim
|
||||
- Shogo Takata
|
@ -1,17 +1,14 @@
|
||||
{
|
||||
"app-id" : "ar.xjuan.Cambalache",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "48",
|
||||
"runtime-version" : "40",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"separate-locales" : false,
|
||||
"command" : "cambalache",
|
||||
"command" : "ar.xjuan.Cambalache",
|
||||
"finish-args" : [
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--filesystem=home",
|
||||
"--device=dri"
|
||||
"--filesystem=home"
|
||||
],
|
||||
"cleanup" : [
|
||||
"/include",
|
||||
@ -26,65 +23,16 @@
|
||||
],
|
||||
"modules" : [
|
||||
{
|
||||
"name" : "python3-lxml",
|
||||
"buildsystem" : "simple",
|
||||
"build-commands" : [
|
||||
"pip3 install --exists-action=i --ignore-installed --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"lxml\" --no-build-isolation"
|
||||
"name": "lxml",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl"
|
||||
],
|
||||
"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/52/a5/98a73a83bb06d271c3f915a4a13f9613fdd8a685524f46d9733eebeb55ce/lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl",
|
||||
"sha256": "2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -96,15 +44,9 @@
|
||||
{
|
||||
"type" : "git",
|
||||
"path" : ".",
|
||||
"branch" : "HEAD"
|
||||
"branch": "HEAD"
|
||||
}
|
||||
],
|
||||
"config-opts" : [
|
||||
"--libdir=lib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"build-options" : {
|
||||
"env" : { }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,107 +1,19 @@
|
||||
# Cambalache
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# 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
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
import logging
|
||||
import locale
|
||||
import builtins
|
||||
|
||||
from . import config
|
||||
|
||||
gi.require_version("GIRepository", "3.0")
|
||||
gi.require_version("Gdk", "4.0")
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("GtkSource", "5")
|
||||
gi.require_version("WebKit", "6.0")
|
||||
gi.require_version('Adw', '1')
|
||||
|
||||
# Ensure _() builtin
|
||||
if "_" not in builtins.__dict__:
|
||||
_ = locale.gettext
|
||||
|
||||
if "N_" not in builtins.__dict__:
|
||||
|
||||
def N_(s, p, n):
|
||||
return _(p) if n > 1 else _(s)
|
||||
|
||||
|
||||
# noqa: E402,E401
|
||||
from gi.repository import Gio, Gdk, Gtk
|
||||
|
||||
|
||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "cambalache.gresource"))
|
||||
from .config import *
|
||||
from gi.repository import Gio
|
||||
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'cambalache.gresource'))
|
||||
resource._register()
|
||||
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_resource("/ar/xjuan/Cambalache/cambalache.css")
|
||||
display = Gdk.Display.get_default()
|
||||
Gtk.StyleContext.add_provider_for_display(display, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - 1)
|
||||
|
||||
# FIXME: this is needed in flatpak for icons to work
|
||||
Gtk.IconTheme.get_for_display(display).add_search_path("/app/share/icons")
|
||||
|
||||
|
||||
def getLogger(name):
|
||||
formatter = logging.Formatter("%(levelname)s:%(name)s %(message)s")
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(formatter)
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(os.environ.get("CAMBALACHE_LOGLEVEL", "WARNING").upper())
|
||||
logger.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
|
||||
from .cmb_property_label import CmbPropertyLabel
|
||||
from .cmb_layout_property import CmbLayoutProperty
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from .cmb_objects import *
|
||||
from .cmb_project import CmbProject
|
||||
|
||||
from .cmb_db_inspector import CmbDBInspector
|
||||
from .cmb_view import CmbView
|
||||
from .cmb_list_view import CmbListView
|
||||
from .cmb_notification import notification_center, CmbNotification, CmbNotificationCenter
|
||||
from .cmb_notification_list_view import CmbNotificationListView
|
||||
from .cmb_tree_view import CmbTreeView
|
||||
from .cmb_object_editor import CmbObjectEditor
|
||||
from .cmb_signal_editor import CmbSignalEditor
|
||||
from .cmb_ui_editor import CmbUIEditor
|
||||
from .cmb_ui_requires_editor import CmbUIRequiresEditor
|
||||
from .cmb_css_editor import CmbCSSEditor
|
||||
from .cmb_gresource_editor import CmbGResourceEditor
|
||||
from .cmb_fragment_editor import CmbFragmentEditor
|
||||
from .cmb_accessible_editor import CmbAccessibleEditor
|
||||
from .cmb_type_chooser import CmbTypeChooser
|
||||
from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
||||
from .cmb_type_chooser_popover import CmbTypeChooserPopover
|
||||
|
@ -1 +0,0 @@
|
||||
../../SUPPORTERS.md
|
@ -1,34 +0,0 @@
|
||||
# Cambalache Application
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from cambalache import config
|
||||
from gi.repository import Gio
|
||||
|
||||
resource = Gio.Resource.load(os.path.join(config.pkgdatadir, "app.gresource"))
|
||||
resource._register()
|
||||
|
||||
from .cmb_application import CmbApplication
|
||||
from .cmb_scrolled_window import CmbScrolledWindow
|
@ -1,16 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<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>
|
||||
<file>cambalache.css</file>
|
||||
<file>images/logo-symbolic.svg</file>
|
||||
<file>images/gtk3.svg</file>
|
||||
<file>images/gtk4.svg</file>
|
||||
<file>images/lp-logo.svg</file>
|
||||
<file>images/patreon-logo.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
@ -1,140 +0,0 @@
|
||||
/*
|
||||
* cambalache.css
|
||||
*
|
||||
* Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Author: Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
window.cmb-window .logo {
|
||||
background: url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg') no-repeat 50% 35% / 40%;
|
||||
}
|
||||
|
||||
window.cmb-window.dark .logo {
|
||||
color: white;
|
||||
background: -gtk-recolor(url('resource:///ar/xjuan/Cambalache/app/images/logo-symbolic.svg'), success #ffcb85, error #1a1a1a) no-repeat 50% 35% / 40%;
|
||||
}
|
||||
|
||||
window.cmb-window label.message {
|
||||
padding: 1ex 1em;
|
||||
border-radius: 1ex 1ex 0 0;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
}
|
||||
|
||||
window.cmb-window list.notifications {
|
||||
margin: 1em 2em;
|
||||
border-radius: 1em;
|
||||
border: 1px solid var(--secondary-sidebar-border-color);
|
||||
}
|
||||
|
||||
window.cmb-window list.notifications > row {
|
||||
padding: 1ex;
|
||||
}
|
||||
|
||||
window.cmb-window list.notifications > row:last-child {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
window.cmb-window list.notifications row > revealer > box > box:last-child {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
window.cmb-window.dark .message {
|
||||
color: black;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
}
|
||||
|
||||
window.cmb-window stackswitcher.compact > button {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
window.cmb-window box.donate {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
popover.cmb-tutor > * {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
popover.cmb-tutor label {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
popover.cmb-tutor image {
|
||||
padding-right: 1em;
|
||||
-gtk-icon-size: 48px;
|
||||
}
|
||||
|
||||
button.cmb-tutor-highlight,
|
||||
modelbutton.cmb-tutor-highlight,
|
||||
buttonbox.cmb-tutor-highlight > button,
|
||||
menubutton.cmb-tutor-highlight > button,
|
||||
stackswitcher.cmb-tutor-highlight > button,
|
||||
stack.cmb-tutor-highlight,
|
||||
entry.cmb-tutor-highlight,
|
||||
treeview.cmb-tutor-highlight,
|
||||
box.cmb-tutor-highlight,
|
||||
CmbView.cmb-tutor-highlight {
|
||||
box-shadow: inset 0px 0px 6px @theme_selected_bg_color;
|
||||
transition: box-shadow .75s ease;
|
||||
}
|
||||
|
||||
CmbView.cmb-tutor-highlight {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
CmbTypeChooser {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
CmbUIEditor,
|
||||
CmbFragmentEditor,
|
||||
CmbObjectEditor,
|
||||
CmbAccessibleEditor {
|
||||
padding: 0 4px 4px 4px;
|
||||
}
|
||||
|
||||
CmbCSSEditor,
|
||||
CmbGResourceEditor,
|
||||
CmbGResourceFileEditor,
|
||||
stackswitcher.property-pane {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
textview {
|
||||
border: solid @borders 1px;
|
||||
}
|
||||
|
||||
button.borderless {
|
||||
border: unset;
|
||||
}
|
||||
|
||||
image.icon-size-32 {
|
||||
-gtk-icon-size: 32px;
|
||||
}
|
||||
|
||||
image.icon-size-64 {
|
||||
-gtk-icon-size: 64px;
|
||||
}
|
||||
|
||||
windowtitle.changed {
|
||||
font-style: italic;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
#!@PYTHON@
|
||||
#
|
||||
# Cambalache UI Maker
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import locale
|
||||
import builtins
|
||||
|
||||
pkgdatadir = '@pkgdatadir@'
|
||||
localedir = '@localedir@'
|
||||
|
||||
sys.path.insert(1, pkgdatadir)
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
locale.bindtextdomain("cambalache", localedir)
|
||||
locale.textdomain("cambalache")
|
||||
|
||||
from cambalache.app import CmbApplication
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = CmbApplication()
|
||||
app.run(sys.argv)
|
@ -1,264 +0,0 @@
|
||||
#
|
||||
# Cambalache Application
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# 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 .cmb_window import CmbWindow
|
||||
from cambalache import CmbProject, utils, config, _
|
||||
|
||||
basedir = os.path.dirname(__file__) or "."
|
||||
|
||||
|
||||
class CmbApplication(Adw.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
|
||||
)
|
||||
|
||||
def add_new_window(self):
|
||||
window = CmbWindow(application=self)
|
||||
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):
|
||||
window = None
|
||||
|
||||
for win in self.get_windows():
|
||||
if win.project and win.project.filename == path:
|
||||
window = win
|
||||
|
||||
if window is None:
|
||||
window = self.add_new_window()
|
||||
if path is not None:
|
||||
window.open_project(path, target_tk=target_tk)
|
||||
|
||||
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.import_file(path)
|
||||
window.present()
|
||||
|
||||
def check_can_quit(self, window=None):
|
||||
windows = self.__get_windows() if window is None else [window]
|
||||
unsaved_windows = []
|
||||
windows2save = []
|
||||
|
||||
def do_quit():
|
||||
if window is None:
|
||||
self.quit()
|
||||
else:
|
||||
self.remove_window(window)
|
||||
window.destroy()
|
||||
|
||||
# Gather projects that needs saving
|
||||
for win in windows:
|
||||
if win.project is None:
|
||||
continue
|
||||
|
||||
if win.actions["save"].get_enabled():
|
||||
unsaved_windows.append(win)
|
||||
|
||||
unsaved_windows_len = len(unsaved_windows)
|
||||
if unsaved_windows_len == 0:
|
||||
do_quit()
|
||||
return
|
||||
|
||||
# Create Dialog
|
||||
window = windows[0]
|
||||
dialog = window._close_project_dialog_new()
|
||||
|
||||
if unsaved_windows_len > 1 or unsaved_windows[0].project.filename is None:
|
||||
# Add checkbox for each unsaved project
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||
box.append(Gtk.Label(label=_("Select which files:"), halign=Gtk.Align.START))
|
||||
|
||||
home = GLib.get_home_dir()
|
||||
untitled = 0
|
||||
|
||||
for win in unsaved_windows:
|
||||
if win.project.filename is None:
|
||||
untitled += 1
|
||||
|
||||
# Find Unique name
|
||||
while os.path.exists(f"Untitled {untitled}.cmb"):
|
||||
untitled += 1
|
||||
|
||||
check = Gtk.CheckButton(active=True, margin_start=8, can_focus=False)
|
||||
entry = Gtk.Entry(text=f"Untitled {untitled}")
|
||||
|
||||
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
hbox.append(check)
|
||||
hbox.append(entry)
|
||||
|
||||
box.append(hbox)
|
||||
else:
|
||||
path = win.project.filename.replace(home, "~")
|
||||
check = Gtk.CheckButton(label=path, active=True, margin_start=8, can_focus=False)
|
||||
box.append(check)
|
||||
|
||||
windows2save.append((win, check, entry))
|
||||
|
||||
box.show()
|
||||
dialog.props.message_area.append(box)
|
||||
else:
|
||||
windows2save.append((unsaved_windows[0], None, None))
|
||||
|
||||
def callback(dialog, response, window):
|
||||
dialog.destroy()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
for win, check, entry in windows2save:
|
||||
if entry is not None:
|
||||
win.project.filename = entry.props.text
|
||||
if check is None or check.props.active:
|
||||
win.save_project()
|
||||
elif response == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
do_quit()
|
||||
|
||||
dialog.connect("response", callback, window)
|
||||
dialog.present()
|
||||
|
||||
def __get_windows(self):
|
||||
retval = []
|
||||
|
||||
for win in self.get_windows():
|
||||
if win.props.application is not None:
|
||||
retval.append(win)
|
||||
|
||||
return retval
|
||||
|
||||
def __on_window_close_request(self, window):
|
||||
self.check_can_quit(window)
|
||||
return True
|
||||
|
||||
def __on_window_project_closed(self, window):
|
||||
windows = self.__get_windows()
|
||||
|
||||
if len(windows) > 1:
|
||||
self.remove_window(window)
|
||||
window.destroy()
|
||||
|
||||
# Action handlers
|
||||
def _on_quit_activate(self, action, data):
|
||||
self.check_can_quit()
|
||||
|
||||
def _on_open_activate(self, action, data):
|
||||
filename, target_tk = data.unpack()
|
||||
|
||||
# FIXME: use nullable parameter
|
||||
target_tk = target_tk if target_tk else None
|
||||
filename = filename if filename else None
|
||||
|
||||
window = self.props.active_window
|
||||
|
||||
if window and window.project is None:
|
||||
window.open_project(filename, target_tk)
|
||||
else:
|
||||
self.open_project(filename, target_tk)
|
||||
|
||||
def _on_new_activate(self, action, data):
|
||||
target_tk, filename, uipath = data.unpack()
|
||||
|
||||
# FIXME: use nullable parameter
|
||||
target_tk = target_tk if target_tk else None
|
||||
filename = filename if filename else None
|
||||
uipath = uipath if uipath else None
|
||||
|
||||
window = self.props.active_window
|
||||
|
||||
if window is None or window.project is not None:
|
||||
window = self.add_new_window()
|
||||
|
||||
window.create_project(target_tk, filename, uipath)
|
||||
window.present()
|
||||
|
||||
# GApplication interface
|
||||
def do_open(self, files, nfiles, hint):
|
||||
for file in files:
|
||||
path = file.get_path()
|
||||
content_type = utils.content_type_guess(path)
|
||||
|
||||
if content_type == "application/x-cambalache-project":
|
||||
self.open_project(path)
|
||||
elif content_type in ["application/x-gtk-builder", "application/x-glade"]:
|
||||
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 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.")
|
||||
return 0
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = CmbApplication()
|
||||
app.run(sys.argv)
|
@ -1,44 +0,0 @@
|
||||
#
|
||||
# CmbScrolledWindow
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class CmbScrolledWindow(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = "CmbScrolledWindow"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Do not let children get scroll events!
|
||||
sroll = Gtk.EventControllerScroll(
|
||||
flags=Gtk.EventControllerScrollFlags.VERTICAL, propagation_phase=Gtk.PropagationPhase.CAPTURE
|
||||
)
|
||||
sroll.connect("scroll", self.handle_scroll_capture)
|
||||
self.add_controller(sroll)
|
||||
|
||||
def handle_scroll_capture(self, ec, dx, dy):
|
||||
self.props.vadjustment.props.value += self.props.vadjustment.props.step_increment * dy
|
||||
return True
|
@ -1,111 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<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>
|
||||
<child>
|
||||
<object class="GtkShortcutsSection">
|
||||
<property name="section-name">shortcuts</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Project</property>
|
||||
<property name="view">shortcuts</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>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="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="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>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>q</property>
|
||||
<property name="title" translatable="yes">Quit application</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Workspace</property>
|
||||
<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="title" translatable="yes">Add slot/column</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary>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="title" translatable="yes">Add row</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator"><primary><shift>Delete</property>
|
||||
<property name="title" translatable="yes">Remove row</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
@ -1,230 +0,0 @@
|
||||
#
|
||||
# Cambalache Tutor
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
# Based on glade-intro.c (C) 2017-2018 Juan Pablo Ugarte
|
||||
#
|
||||
|
||||
from gi.repository import GObject, GLib, Gdk, Gtk
|
||||
from enum import Enum
|
||||
from collections import namedtuple
|
||||
from cambalache import utils
|
||||
|
||||
|
||||
class CmbTutorState(Enum):
|
||||
NULL = 1
|
||||
PLAYING = 2
|
||||
PAUSED = 3
|
||||
|
||||
|
||||
class CmbTutorPosition(Enum):
|
||||
BOTTOM = 1
|
||||
LEFT = 2
|
||||
RIGHT = 3
|
||||
CENTER = 4
|
||||
|
||||
|
||||
ScriptNode = namedtuple("ScriptNode", "widget text delay name position")
|
||||
|
||||
|
||||
class CmbTutor(GObject.GObject):
|
||||
__gsignals__ = {
|
||||
"show-node": (GObject.SignalFlags.RUN_LAST, None, (str, Gtk.Widget)),
|
||||
"hide-node": (GObject.SignalFlags.RUN_LAST, None, (str, Gtk.Widget)),
|
||||
}
|
||||
|
||||
window = GObject.Property(type=Gtk.Window, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, script, **kwargs):
|
||||
# List of ScriptNode
|
||||
self.script = []
|
||||
|
||||
# Popover to show the script text
|
||||
self.popover = None
|
||||
|
||||
# Timeout id for running the script
|
||||
self.timeout_id = None
|
||||
|
||||
# Current script node index
|
||||
self.current = None
|
||||
|
||||
self.hiding_node = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
for node in script:
|
||||
self.__add(*node)
|
||||
|
||||
@GObject.Property(type=int, flags=GObject.ParamFlags.READABLE)
|
||||
def state(self):
|
||||
if self.timeout_id:
|
||||
return CmbTutorState.PLAYING
|
||||
elif self.current:
|
||||
return CmbTutorState.PAUSED
|
||||
|
||||
return CmbTutorState.NULL
|
||||
|
||||
def __add(self, text, widget_name, delay, name=None, position=CmbTutorPosition.BOTTOM):
|
||||
def find_by_css_name_or_buildable_id(widget, name):
|
||||
retval = None
|
||||
css_name = widget.get_name()
|
||||
|
||||
# Get css name first
|
||||
if css_name and css_name != GObject.type_name(widget) and css_name == name:
|
||||
return widget
|
||||
|
||||
# then GtkBuildable name
|
||||
if isinstance(widget, Gtk.Buildable) and Gtk.Buildable.get_buildable_id(widget) == name:
|
||||
return widget
|
||||
|
||||
# or ModelButton name
|
||||
if GObject.type_name(widget) == "GtkModelButton" and widget.props.text == name:
|
||||
return widget
|
||||
|
||||
for child in utils.widget_get_children(widget):
|
||||
retval = find_by_css_name_or_buildable_id(child, name)
|
||||
if retval:
|
||||
return retval
|
||||
|
||||
return retval
|
||||
|
||||
widget = find_by_css_name_or_buildable_id(self.window, widget_name)
|
||||
|
||||
if widget:
|
||||
self.script.append(ScriptNode(widget, text, delay, name, position))
|
||||
|
||||
def play(self):
|
||||
if len(self.script) == 0:
|
||||
return
|
||||
|
||||
if self.current is None:
|
||||
self.current = 0
|
||||
|
||||
self.__script_play()
|
||||
|
||||
self.notify("state")
|
||||
|
||||
def pause(self):
|
||||
if self.timeout_id:
|
||||
GLib.source_remove(self.timeout_id)
|
||||
|
||||
self.timeout_id = None
|
||||
self.__hide_node(self.current)
|
||||
self.notify("state")
|
||||
|
||||
def stop(self):
|
||||
self.pause()
|
||||
self.current = None
|
||||
self.notify("state")
|
||||
|
||||
def __popover_new(self, text):
|
||||
popover = Gtk.Popover(autohide=False)
|
||||
popover.add_css_class("cmb-tutor")
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, hexpand=True)
|
||||
|
||||
box.append(Gtk.Image(icon_name="dialog-information-symbolic"))
|
||||
box.append(Gtk.Label(label=text, vexpand=False, hexpand=True, wrap=True, max_width_chars=24))
|
||||
popover.set_child(box)
|
||||
|
||||
return popover
|
||||
|
||||
def __script_transition(self):
|
||||
self.timeout_id = GLib.timeout_add(250, self.__script_play)
|
||||
|
||||
self.__hide_current_node()
|
||||
|
||||
# Set next node
|
||||
if self.current is not None:
|
||||
self.current = self.current + 1 if self.current < (len(self.script) - 1) else None
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
def __hide_node(self, index):
|
||||
if self.popover:
|
||||
self.popover.popdown()
|
||||
self.popover = None
|
||||
|
||||
if index is not None:
|
||||
node = self.script[index]
|
||||
|
||||
if node.widget:
|
||||
node.widget.get_style_context().remove_class("cmb-tutor-highlight")
|
||||
|
||||
def __hide_current_node(self):
|
||||
if self.hiding_node:
|
||||
return
|
||||
|
||||
self.hiding_node = True
|
||||
|
||||
self.__hide_node(self.current)
|
||||
|
||||
if self.current is not None:
|
||||
node = self.script[self.current]
|
||||
self.emit("hide-node", node.name, node.widget)
|
||||
|
||||
self.hiding_node = False
|
||||
|
||||
def __script_play(self):
|
||||
self.timeout_id = None
|
||||
|
||||
if self.current is None:
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
node = self.script[self.current]
|
||||
|
||||
if node and node.text:
|
||||
# Ensure the widget is visible
|
||||
if not node.widget.is_visible():
|
||||
# if the widget is inside a popover pop it up
|
||||
parent = node.widget.get_ancestor(Gtk.Popover)
|
||||
if parent:
|
||||
parent.popup()
|
||||
|
||||
node.widget.add_css_class("cmb-tutor-highlight")
|
||||
|
||||
# Create popover
|
||||
self.popover = self.__popover_new(node.text)
|
||||
self.popover.set_parent(node.widget)
|
||||
|
||||
if node.position == CmbTutorPosition.BOTTOM:
|
||||
self.popover.set_position(Gtk.PositionType.BOTTOM)
|
||||
elif node.position == CmbTutorPosition.LEFT:
|
||||
self.popover.set_position(Gtk.PositionType.LEFT)
|
||||
elif node.position == CmbTutorPosition.RIGHT:
|
||||
self.popover.set_position(Gtk.PositionType.RIGHT)
|
||||
elif node.position == CmbTutorPosition.CENTER:
|
||||
rect = Gdk.Rectangle()
|
||||
rect.x = node.widget.get_allocated_width() / 2
|
||||
rect.y = node.widget.get_allocated_height() / 2
|
||||
self.popover.set_pointing_to(rect)
|
||||
self.popover.set_position(Gtk.PositionType.TOP)
|
||||
|
||||
self.emit("show-node", node.name, node.widget)
|
||||
|
||||
if self.popover:
|
||||
self.popover.set_sensitive(True)
|
||||
self.popover.popup()
|
||||
|
||||
self.timeout_id = GLib.timeout_add(node.delay * 1000, self.__script_transition)
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
@ -1,90 +0,0 @@
|
||||
#
|
||||
# CmbTutorial
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from .cmb_tutor import CmbTutorPosition
|
||||
from cambalache import _
|
||||
|
||||
intro = [
|
||||
(_("Hi, I will show you around Cambalache"), "intro_button", 5),
|
||||
(_("You can open a project and find recently used"), "open_button", 5),
|
||||
(_("Common actions like Undo"), "undo_button", 4),
|
||||
(_("Redo"), "redo_button", 2),
|
||||
(_("and Add new UI are directly accessible in the headerbar"), "add_button", 3),
|
||||
(_("together with the main menu"), "menu_button", 3),
|
||||
(
|
||||
_("Where you can create a new project"),
|
||||
_("New Project"),
|
||||
5,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(
|
||||
_("Import UI files"),
|
||||
_("Import"),
|
||||
3,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(_("Create a project to continue"), "intro_button", 2, "add-project"),
|
||||
(_("Great!"), "intro_button", 2),
|
||||
(
|
||||
_("This is the project workspace, where you can see and select the widgets to edit"),
|
||||
"view",
|
||||
6,
|
||||
None,
|
||||
CmbTutorPosition.CENTER,
|
||||
),
|
||||
(_("Project tree, with multiple UI support"), "tree_view", 4, None, CmbTutorPosition.CENTER),
|
||||
(
|
||||
_("Class selector bar"),
|
||||
"type_chooser_box",
|
||||
3,
|
||||
),
|
||||
(_("And the object editor"), "editor_stack", 3, None, CmbTutorPosition.CENTER),
|
||||
(_("You can search all supported classes here"), "type_chooser_all", 4, "show-type-popover", CmbTutorPosition.LEFT),
|
||||
(_("or investigate what is in each group"), "type_chooser_gtk", 4, "show-type-popover-gtk", CmbTutorPosition.LEFT),
|
||||
(_("Now let's add a new UI file"), "add_button", 5, "add-ui"),
|
||||
(_("Good, now try to create a window"), "intro_button", 4, "add-window"),
|
||||
(_("Excellent!"), "intro_button", 2),
|
||||
(_("BTW, did you know you can double click on any placeholder to create widgets?"), "intro_button", 5),
|
||||
(_("Try adding a grid"), "intro_button", 3, "add-grid"),
|
||||
(_("and a button"), "intro_button", 3, "add-button"),
|
||||
(_("Quite easy! Isn't it?"), "intro_button", 3),
|
||||
(
|
||||
_("If you have any question, contact us on Matrix!"),
|
||||
_("Contact"),
|
||||
7,
|
||||
None,
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(
|
||||
_("That is all for now.\nIf you find Cambalache useful please consider donating"),
|
||||
_("Donate"),
|
||||
7,
|
||||
"donate",
|
||||
CmbTutorPosition.LEFT,
|
||||
),
|
||||
(_("Have a nice day!"), "intro_button", 3, "intro-end"),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -1,877 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_window.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<requires lib="gtk" version="4.14"/>
|
||||
<requires lib="libadwaita" version="1.5"/>
|
||||
<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>
|
||||
</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>
|
||||
</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.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>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.donate</attribute>
|
||||
<attribute name="label" translatable="yes">Donate</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.show-help-overlay</attribute>
|
||||
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.about</attribute>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="recent_menu"/>
|
||||
<object class="GtkFileFilter" id="open_filter">
|
||||
<mime-types>
|
||||
<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>
|
||||
<child>
|
||||
<object class="GtkOverlay">
|
||||
<property name="child">
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<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>
|
||||
<attributes>
|
||||
<attribute name="size" value="9000"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="logo"/>
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">cambalache</property>
|
||||
<property name="title" translatable="yes">Cambalache</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="justify">center</property>
|
||||
<property name="label" translatable="yes"><b>
|
||||
|
||||
<span size="18000">New project</span>
|
||||
</b></property>
|
||||
<property name="use-markup">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="column-homogeneous">1</property>
|
||||
<property name="column-spacing">8</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="row-spacing">8</property>
|
||||
<property name="valign">start</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Name</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Toolkit target</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Location</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="np_name_entry">
|
||||
<property name="focusable">1</property>
|
||||
<property name="input-hints">lowercase|none</property>
|
||||
<property name="input-purpose">alpha</property>
|
||||
<property name="placeholder-text" translatable="yes"><project basename></property>
|
||||
<property name="width-chars">32</property>
|
||||
<signal name="changed" handler="on_np_name_entry_changed"/>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">3</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="np_create_button">
|
||||
<property name="action-name">win.new</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="label" translatable="yes">Create</property>
|
||||
<property name="margin-top">16</property>
|
||||
<property name="receives-default">1</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
<layout>
|
||||
<property name="column">3</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="halign">start</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="np_gtk3_radiobutton">
|
||||
<property name="group">np_gtk4_radiobutton</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="tooltip-text" translatable="yes">Old stable version</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="resource">/ar/xjuan/Cambalache/app/images/gtk3.svg</property>
|
||||
<style>
|
||||
<class name="icon-size-32"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="justify">center</property>
|
||||
<property name="label"><b>Gtk 3</b></property>
|
||||
<property name="use-markup">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="np_gtk4_radiobutton">
|
||||
<property name="active">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="tooltip-text" translatable="yes">Recommended for new projects</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">8</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="resource">/ar/xjuan/Cambalache/app/images/gtk4.svg</property>
|
||||
<style>
|
||||
<class name="icon-size-32"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="justify">center</property>
|
||||
<property name="label"><b>Gtk 4</b></property>
|
||||
<property name="use-markup">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">3</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="np_location_chooser">
|
||||
<property name="action-name">win.select_project_location</property>
|
||||
<property name="icon-name">folder-symbolic</property>
|
||||
<property name="sensitive">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">folder-open-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="np_location_chooser_label">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">3</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="np_cancel_button">
|
||||
<property name="action-name">win.show_workspace</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="margin-top">16</property>
|
||||
<property name="receives-default">1</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">UI Filename</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="np_ui_entry">
|
||||
<property name="focusable">1</property>
|
||||
<property name="input-hints">lowercase|none</property>
|
||||
<property name="input-purpose">alpha</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="width-chars">32</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">3</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">new_project</property>
|
||||
<property name="title" translatable="yes">New Project</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkPaned">
|
||||
<property name="end-child">
|
||||
<object class="GtkStack" id="editor_stack">
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="stack">object_stack</property>
|
||||
<style>
|
||||
<class name="compact"/>
|
||||
<class name="property-pane"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="object_stack">
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="CmbObjectEditor" id="object_editor">
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">properties</property>
|
||||
<property name="title" translatable="yes">Properties</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="CmbObjectEditor" id="object_layout_editor">
|
||||
<property name="layout">True</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">layout</property>
|
||||
<property name="title" translatable="yes">Layout</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbSignalEditor" id="signal_editor"/>
|
||||
</property>
|
||||
<property name="name">signals</property>
|
||||
<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">
|
||||
<object class="CmbFragmentEditor" id="fragment_editor"/>
|
||||
</property>
|
||||
<property name="name">fragment</property>
|
||||
<property name="title"></></property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">object</property>
|
||||
<property name="title" translatable="yes">Object Editor</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="stack">ui_stack</property>
|
||||
<style>
|
||||
<class name="compact"/>
|
||||
<class name="property-pane"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="ui_stack">
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbUIEditor" id="ui_editor">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">properties</property>
|
||||
<property name="title" translatable="yes">Properties</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbUIRequiresEditor" id="ui_requires_editor">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">requires</property>
|
||||
<property name="title" translatable="yes">Requires</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbFragmentEditor" id="ui_fragment_editor">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">fragment</property>
|
||||
<property name="title"></></property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui</property>
|
||||
<property name="title" translatable="yes">UI Editor</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="CmbCSSEditor" id="css_editor">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
</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>
|
||||
<property name="height-request">380</property>
|
||||
<property name="resize-end-child">0</property>
|
||||
<property name="shrink-end-child">0</property>
|
||||
<property name="shrink-start-child">0</property>
|
||||
<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"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbView" id="view">
|
||||
<property name="vexpand">1</property>
|
||||
<signal name="placeholder-activated" handler="on_view_placeholder_activated"/>
|
||||
<signal name="placeholder-selected" handler="on_view_placeholder_selected"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">ui</property>
|
||||
</object>
|
||||
</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>
|
||||
<property name="shrink-start-child">0</property>
|
||||
<property name="start-child">
|
||||
<object class="GtkBox" id="inspector">
|
||||
<property name="focusable">1</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>
|
||||
<child>
|
||||
<object class="CmbListView" id="list_view"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">workspace</property>
|
||||
<property name="title" translatable="yes">Workspace</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">win.show_workspace</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="borderless"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="halign">center</property>
|
||||
<property name="homogeneous">1</property>
|
||||
<property name="margin-bottom">64</property>
|
||||
<property name="margin-top">32</property>
|
||||
<property name="spacing">196</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="halign">center</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">32</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="resource">/ar/xjuan/Cambalache/app/images/lp-logo.svg</property>
|
||||
<style>
|
||||
<class name="icon-size-64"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">• Liberapay is a recurrent donations platform
|
||||
|
||||
• Run by a non-profit organization
|
||||
|
||||
• Source code is public
|
||||
|
||||
• No commission fee
|
||||
|
||||
• ~5% payment processing fee</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">win.liberapay</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Donate</property>
|
||||
<property name="receives-default">1</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="halign">center</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">32</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="resource">/ar/xjuan/Cambalache/app/images/patreon-logo.svg</property>
|
||||
<style>
|
||||
<class name="icon-size-64"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">• Patreon is a membership platform for creators
|
||||
|
||||
• Run by private company
|
||||
|
||||
• No source code available
|
||||
|
||||
• ~8% commission fee
|
||||
|
||||
• ~8% payment processing fee</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="action-name">win.patreon</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Donate</property>
|
||||
<property name="receives-default">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="donate"/>
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">donate</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<child type="overlay">
|
||||
<object class="GtkRevealer" id="message_revealer">
|
||||
<property name="child">
|
||||
<object class="GtkLabel" id="message_label">
|
||||
<style>
|
||||
<class name="message"/>
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="transition-type">slide-up</property>
|
||||
<property name="valign">end</property>
|
||||
<style/>
|
||||
</object>
|
||||
</child>
|
||||
</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>
|
||||
</interface>
|
@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 32 33.336" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="c" x1="-143" x2="-129.5" y1="207.36" y2="108.86" gradientTransform="matrix(.12809 0 0 .12809 33.844 -12.218)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#92cbe2" offset="0"/>
|
||||
<stop stop-color="#7f95fe" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="-133.5" x2="-50" y1="221.86" y2="279.36" gradientTransform="matrix(.12809 0 0 .12809 33.844 -12.218)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#bff872" offset="0"/>
|
||||
<stop stop-color="#7de567" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a" x1="-157.5" x2="-247.5" y1="222.86" y2="276.86" gradientTransform="matrix(.12809 0 0 .12809 33.844 -12.218)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fe798e" offset="0"/>
|
||||
<stop stop-color="#9c2219" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="m14.587 15.159-13.341-7.9466 16.043-6.575 13.469 7.4425z" fill="url(#c)"/>
|
||||
<path d="m15.427 15.181 15.832-6.9539-2.2718 15.91-14.915 8.5469z" fill="url(#b)"/>
|
||||
<path d="m14.402 32.294-13.385-8.2263 0.057475-15.837 13.889 7.1944z" fill="url(#a)"/>
|
||||
<path d="m13.5 32.9c-0.4-0.2-2.4-1.6-4.6-3-5-3.3-6.5-4.3-7.6-4.9-1.4-0.7-1.4 0.1-1.4-8.4 0-8-0.1-9.4 0.6-9.2-0.6-0.6 2.2-1.4 6.5-3.4 4.3-1.9 8.9-3.8 10-4.1 0.3 0 0.9 0.2 3.5 1.5 5.5 2.7 10.1 5.3 10.7 5.9 0.1 0.1 0.3 0.3 0.4 0.3 0.3 0 0.3 0.5 0.2 1.5-0.1 0.5-0.6 3.7-1.2 7.1s-1.1 6.5-1.1 6.9c-0.2 1.5-0.1 1.4-2.4 2.9-1.1 0.7-4 2.4-6.4 3.8-2.4 1.4-4.8 2.7-5.3 3-0.5 0.3-1 0.6-1.1 0.5-0.1 0-0.5-0.2-0.9-0.4zm0.6-3.3c0-1.1 0.1-4.4 0.2-7.3 0.1-6.4 0.2-6.2-1.2-7.1-1.7-1.1-3.4-2.1-6.5-3.7-1.7-0.9-3.5-1.9-4-2.2-0.7-0.4-1.1-0.6-1.2-0.5-0.2 0.2-0.2 6.8 0 11.5l0.1 3.3 0.9 0.5c0.8 0.5 5.4 3.5 9.8 6.6 0.7 0.5 1.4 0.9 1.6 0.9 0.2 0 0.2-0.3 0.3-2.1zm-7.9-5.7 0.2-6.4-2.4-1.3 0.3-2.6 7.7 3.7-0.1 2.5-2.8-1.2c-0.4 1.4-0.4 4.3-0.6 6.5zm14.6 4.5c5.6-3.2 7.5-4.4 7.6-4.7 0-0.2 0.2-0.9 0.3-1.7 0.1-0.8 0.6-3.8 1.1-6.8 0.8-5 1-6.2 0.8-6.2-0.2 0-3.2 1.4-7 3.1-2.4 1.1-5 2.3-5.8 2.7-0.8 0.4-1.6 0.8-1.7 0.9-0.3 0.2-0.9 15-0.6 15.5 0.1 0.2 0.2 0.2 0.8-0.2 0.4-0.2 2.5-1.4 4.6-2.6zm-2.8-2.2c0-3.4-0.3-6.8 0.5-10.3l1.6 0 0.1 4.9 5.5-6.8 2.6-0.7-4.9 6.7 4.5 2.2-1.9 1.2-4.6-2-1.7 0.8-0.1 3.3zm-1.7-12c0.4-0.2 3.1-1.5 6.1-2.9 3-1.4 5.8-2.7 6.2-2.9 1.2-0.5 1.3-0.6 0.7-1-0.9-0.6-5.5-3-8.7-4.6l-3.2-1.6-1.1 0.4c-1.5 0.5-5.8 2.3-9.5 3.9-3.9 1.7-4.4 2-4.4 2.2 0 0.1 0.9 0.6 1.9 1.2 3.3 1.9 10.6 5.6 10.9 5.6 0.2 0 0.6-0.1 1-0.3zm-2-2.1c-1.9-0.5-4-1.5-4.5-2.3-0.4-0.6-0.8-1.6-0.8-2.1 0-0.7 0.4-1.6 0.9-2.1 0.7-0.7 1.6-1 4.1-1.3 2.4-0.3 4.6-0.2 5.8 0.2 1.2 0.4 1.7 0.7 1.9 1 0.6 0.9 1.1 1.7 1.1 1.8 0 0.2-0.6 0.5-1 0.4-0.2 0-0.5-0.1-0.8-0.1-0.4-0.1-0.5-0.2-0.8-0.6-0.3-0.6-1.2-1.2-2-1.4-1.3-0.3-4.8-0.1-5.9 0.5-0.6 0.3-0.9 1-1 2-0.1 0.7 0 0.9 0.2 1.2 0.4 0.4 1.4 0.9 2.6 1.3 0.6 0.2 1 0.3 1.5 0.2 1-0.1 1.2-0.4 0.5-1.2-0.5-0.7-0.7-1-0.5-1.4 0.1-0.2 0.2-0.3 0.8-0.2 0.8 0.1 2.6 0.7 3 1.1 0.4 0.3 0.4 1.1 0.1 1.5-0.4 0.4-2 1.2-2.6 1.4-0.7 0.2-1.8 0.2-2.7 0z" stroke-width="4"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.0 KiB |
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="31.997" height="34.706" version="1.0" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(.34561 0 0 .34685 -6.1578 -4.8943)" stroke="#fff">
|
||||
<g fill-rule="evenodd" stroke-linejoin="round" stroke-width="6.1337">
|
||||
<path d="m20.884 30.827 32.933 24.701 53.516-16.467-36.746-21.883z" fill="#729fcf"/>
|
||||
<path d="m22.942 82.287-2.0583-51.46 32.933 24.701v55.577z" fill="#e40000"/>
|
||||
<path d="m53.817 111.1 49.399-20.584 4.1166-51.46-53.516 16.467z" fill="#7fe719"/>
|
||||
</g>
|
||||
<path d="m23.217 81.319 47.269-13.958 32.898 23.083" fill="none" stroke-width="3.6104"/>
|
||||
<path d="m70.435 17.876v49.109" fill="#babdb6" fill-rule="evenodd" stroke-width="3.6104"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 728 B |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 40 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 50 KiB |
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(.83012 0 0 .83012-135.4-247.7)">
|
||||
<path d="m259.55 385.57c0 5.145-4.169 9.318-9.318 9.318h-77.74c-5.144 0-9.318-4.174-9.318-9.318v-77.74c0-5.145 4.174-9.318 9.318-9.318h77.74c5.149 0 9.318 4.173 9.318 9.318v77.74" fill="#f6c915"/>
|
||||
<g fill="#fff">
|
||||
<path d="m202.45 366.03c-3.104 0-5.541-.405-7.311-1.213-1.77-.809-3.039-1.912-3.803-3.313-.766-1.398-1.137-3-1.115-4.818.021-1.814.272-3.748.754-5.803l8.327-34.817 10.164-1.573-9.114 37.768c-.175.786-.273 1.508-.295 2.163-.023.655.098 1.235.36 1.737.262.504.71.908 1.344 1.213.633.307 1.519.504 2.656.591l-1.967 8.06"/>
|
||||
<path d="m239.16 344.33c0 3.19-.525 6.108-1.574 8.753-1.049 2.646-2.503 4.929-4.36 6.852-1.858 1.925-4.087 3.421-6.688 4.491-2.601 1.07-5.432 1.607-8.49 1.607-1.487 0-2.973-.132-4.459-.395l-2.951 11.869h-9.704l10.884-45.37c1.748-.524 3.748-.994 5.999-1.41 2.252-.415 4.689-.622 7.312-.622 2.448 0 4.558.371 6.327 1.114 1.771.743 3.224 1.76 4.361 3.049 1.136 1.29 1.977 2.798 2.523 4.524.546 1.726.82 3.574.82 5.542m-23.802 13.442c.743.175 1.661.262 2.754.262 1.704 0 3.256-.316 4.655-.951 1.398-.633 2.59-1.518 3.574-2.655.982-1.136 1.747-2.501 2.294-4.098.546-1.595.819-3.354.819-5.278 0-1.879-.416-3.475-1.245-4.787-.831-1.311-2.273-1.967-4.327-1.967-1.4 0-2.711.131-3.935.394l-4.589 19.08"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="80" height="76.8" version="1.1" viewBox="0 0 80 76.8" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.13338 0 0 -.13338 0 76.8)"><g transform="scale(.1)" fill="#ff424d" fill-rule="evenodd"><path d="m3844.9 5757.8c-1190.8 0-2159.5-969.65-2159.5-2161.6 0-1188.3 968.78-2155.1 2159.5-2155.1 1187.1 0 2152.8 966.79 2152.8 2155.1 0 1191.9-965.74 2161.6-2152.8 2161.6"/><path d="M 0,0 H 1054.41 V 5757.81 H 0 V 0"/></g></g></svg>
|
Before Width: | Height: | Size: 505 B |
@ -1,33 +0,0 @@
|
||||
moduledir = join_paths(modulesdir, 'cambalache', 'app')
|
||||
|
||||
gnome.compile_resources('app',
|
||||
'app.gresource.xml',
|
||||
gresource_bundle: true,
|
||||
install: true,
|
||||
install_dir: pkgdatadir,
|
||||
)
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('VERSION', meson.project_version())
|
||||
conf.set('PYTHON', python_bin.full_path())
|
||||
conf.set('localedir', localedir)
|
||||
conf.set('pkgdatadir', pkgdatadir)
|
||||
|
||||
configure_file(
|
||||
input: 'cambalache.in',
|
||||
output: 'cambalache',
|
||||
configuration: conf,
|
||||
install: true,
|
||||
install_dir: get_option('bindir')
|
||||
)
|
||||
|
||||
install_data([
|
||||
'__init__.py',
|
||||
'cmb_application.py',
|
||||
'cmb_scrolled_window.py',
|
||||
'cmb_window.py',
|
||||
'cmb_tutor.py',
|
||||
'cmb_tutorial.py',
|
||||
],
|
||||
install_dir: moduledir)
|
||||
|
@ -1 +0,0 @@
|
||||
../../data/ar.xjuan.Cambalache.metainfo.xml.in
|
@ -1,290 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
|
||||
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<cambalache-project version="0.96.0" target_tk="gtk-4.0">
|
||||
<gresources filename="cambalache.gresource.xml" sha256="fdcf4cd517493f548aa4b4fe206ff7762cee9cdda7ec5a85a718b46eb1c4731b"/>
|
||||
<gresources filename="app/app.gresource.xml" sha256="3684aa78fce08d8e81d0907317214aeb179c5aea091dd0df405476b43e286941"/>
|
||||
<css filename="cambalache.css" priority="400" is_global="1"/>
|
||||
<css filename="app/cambalache.css" is_global="0"/>
|
||||
<ui template-class="CmbChildTypeComboBox">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbChildTypeComboBox -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbChildTypeComboBox" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbColorEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbColorEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbColorEntry" parent="GtkBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbEnumComboBox">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbEnumComboBox -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbEnumComboBox" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFileEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbFileEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFileEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFlagsEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbFlagsEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFlagsEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbIconNameEntry">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbIconNameEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbIconNameEntry" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbObjectChooser">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbObjectChooser -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectChooser" parent="GtkEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSourceView">
|
||||
<property id="lang" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSourceView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSourceView" parent="GtkTextView"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSpinButton">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSpinButton -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSpinButton" parent="GtkSpinButton"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbSwitch">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbSwitch -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbSwitch" parent="GtkSwitch"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTextBuffer">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTextBuffer -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTextBuffer" parent="GtkTextBuffer"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTextView">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTextView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTextView" parent="GtkScrolledWindow"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbToplevelChooser">
|
||||
<property id="derivable-only" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbToplevelChooser -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbToplevelChooser" parent="GtkComboBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTranslatablePopover">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTranslatablePopover -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTranslatablePopover" parent="GtkPopover"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTranslatableWidget" filename="control/cmb_translatable_widget.ui" sha256="b3178157210f93308b92d99f933cb8d04f2de0278ad75680c7460e2c520b1684"/>
|
||||
<ui template-class="CasildaCompositor">
|
||||
<signal id="context-menu"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbCompositor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CasildaCompositor" parent="GtkDrawingArea"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooserPopover">
|
||||
<property id="category" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="derived-type-id" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="parent-type-id" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="show-categories" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="uncategorized-only" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbTypeChooserPopover -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbTypeChooserPopover" parent="GtkPopover"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbAccessibleEditor">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbAccessibleEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbAccessibleEditor" parent="GtkGrid"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbUIRequiresEditor">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbUIRequiresEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbUIRequiresEditor" parent="GtkGrid"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbScrolledWindow">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbScrolledWindow -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbScrolledWindow" parent="GtkScrolledWindow"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbObjectEditor">
|
||||
<property id="lang" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="layout" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbObjectEditor -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectEditor" parent="GtkBox"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbListView">
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbListView -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbListView" parent="GtkListView"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooserWidget" filename="cmb_type_chooser_widget.ui" sha256="15dedacc452656abdb9d245ec876370705c610422201213d944c1f6329c5c8f6"/>
|
||||
<ui template-class="CmbSignalEditor" filename="cmb_signal_editor.ui" sha256="df7ca002e39a3a8e16a7334e48d8b1a7847972b0a3f5213f71ba154a70194a1d"/>
|
||||
<ui template-class="CmbObjectDataEditor" filename="cmb_object_data_editor.ui" sha256="4a590fc58d66e781f731134214a9fdeefabd8b8c11edcc50f6530cac81f796d1"/>
|
||||
<ui template-class="CmbContextMenu" filename="cmb_context_menu.ui" sha256="81eba3adf715348a5c03ef4cbc151eebd5d9aa8b5a14c5968232f68a61ae573c"/>
|
||||
<ui template-class="CmbDBInspector" filename="cmb_db_inspector.ui" sha256="4451cdb08d24bd4a802ea692c0ebb4ef46af13152984c0b435d29bf4eb7dab55"/>
|
||||
<ui filename="app/cmb_shortcuts.ui" sha256="d7ac37fd2430788a9e210ed4bc84dcfeba5609bdcc801afb192bfd900c7a8883"/>
|
||||
<ui template-class="CmbFileButton" filename="control/cmb_file_button.ui" sha256="f859b4f85d7c80c1fef69b68ebb9129423d9c72fdb38d304132784f7361cbbfd">
|
||||
<property id="dialog-title" type-id="gchararray" disable-inline-object="0" required="0" disabled="0"/>
|
||||
<property id="use-open" type-id="gboolean" disable-inline-object="0" required="0" disabled="0"/>
|
||||
</ui>
|
||||
<ui template-class="CmbNotificationListView" filename="cmb_notification_list_view.ui" sha256="13622645038ef2aaa154f74cd300f9c0fa0dccf69d45d6c9376f9034e6ee57fb"/>
|
||||
<ui template-class="CmbVersionNotificationView" filename="cmb_version_notification_view.ui" sha256="9a3ced46b90eb7e425d1c345853c4e8e908870c61c75475f7e20ce3c9ee8cec6"/>
|
||||
<ui template-class="CmbMessageNotificationView" filename="cmb_message_notification_view.ui" sha256="debeffd184e225d82ed29ac590654b8160363e8d5606366dc8acb3ff9840fee3"/>
|
||||
<ui template-class="CmbPollNotificationView" filename="cmb_poll_notification_view.ui" sha256="8f47a1e503b85eb5ac3ac54962a40fc2237588e1216afba859a3016a1dcfc121"/>
|
||||
<ui template-class="CmbPollOptionCheck" filename="cmb_poll_option_check.ui" sha256="aa433f201dc1863f3727e1baa2c4cc239192a1ae4c53553de69d529ba2cc6fed"/>
|
||||
<ui template-class="CmbNotificationListRow" filename="cmb_notification_list_row.ui" sha256="5ef66fcc24e10d40a91ff0eada84f6aa8a595e368961364d98fde1800755edfc"/>
|
||||
<ui template-class="CmbPixbufEntry">
|
||||
<requires>CmbFileEntry</requires>
|
||||
<content><![CDATA[<interface>
|
||||
<!-- interface-name CmbPixbufEntry -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<template class="CmbPixbufEntry" parent="CmbFileEntry"/>
|
||||
</interface>
|
||||
]]></content>
|
||||
</ui>
|
||||
<ui template-class="CmbFragmentEditor" filename="cmb_fragment_editor.ui" sha256="0c2de873c689cd60558a835a39edaf4e1d5e68ef3afeb157628e13fd57282057">
|
||||
<requires>CmbSourceView</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbTypeChooser" filename="cmb_type_chooser.ui" sha256="1b4be880ede6d6d6e88e452418c435640d91f5f61cad97b3f82f44429247e5e6">
|
||||
<requires>CmbTypeChooserPopover</requires>
|
||||
<signal id="chooser-popdown"/>
|
||||
<signal id="chooser-popup"/>
|
||||
<signal id="type-selected"/>
|
||||
</ui>
|
||||
<ui template-class="CmbView" filename="cmb_view.ui" sha256="6b9251d30d6e4751b8b04b2af923eec9a8f00d590a322d96bf080fb200b58424">
|
||||
<requires>CasildaCompositor</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
<requires>CmbDBInspector</requires>
|
||||
<signal id="placeholder-activated"/>
|
||||
<signal id="placeholder-selected"/>
|
||||
</ui>
|
||||
<ui template-class="CmbGResourceEditor" filename="cmb_gresource_editor.ui" sha256="2050887ef1c45facb6ebff14500214bb035e6808ca61d2a7d661e696d79026ca">
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbEntry</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbCSSEditor" filename="cmb_css_editor.ui" sha256="6f39c9cb2f112dac9da415322792a717b8c0a80845dcbb2edacd7695388cba63">
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbUIEditor" filename="cmb_ui_editor.ui" sha256="70e272e2c6c499a5424c6019154cd8338d9edde1fa111ad592a1c104019bb7ee">
|
||||
<requires>CmbTextBuffer</requires>
|
||||
<requires>CmbFileButton</requires>
|
||||
<requires>CmbEntry</requires>
|
||||
<requires>CmbToplevelChooser</requires>
|
||||
</ui>
|
||||
<ui template-class="CmbWindow" filename="app/cmb_window.ui" sha256="df07e3e03b88f9b097ad7d65efa15923d339db83b9d4a7c015a312a71f8c9685">
|
||||
<requires>CmbNotificationListView</requires>
|
||||
<requires>CmbScrolledWindow</requires>
|
||||
<requires>CmbObjectEditor</requires>
|
||||
<requires>CmbSignalEditor</requires>
|
||||
<requires>CmbAccessibleEditor</requires>
|
||||
<requires>CmbFragmentEditor</requires>
|
||||
<requires>CmbUIEditor</requires>
|
||||
<requires>CmbUIRequiresEditor</requires>
|
||||
<requires>CmbCSSEditor</requires>
|
||||
<requires>CmbGResourceEditor</requires>
|
||||
<requires>CmbTypeChooser</requires>
|
||||
<requires>CmbView</requires>
|
||||
<requires>CmbSourceView</requires>
|
||||
<requires>CmbListView</requires>
|
||||
<css-provider>app/cambalache.css</css-provider>
|
||||
</ui>
|
||||
</cambalache-project>
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* cambalache.css
|
||||
*
|
||||
* Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Author: Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
CmbView {
|
||||
background-color: @theme_base_color;
|
||||
}
|
||||
|
||||
popover.cmb-icon-chooser iconview:not(:selected) {
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
button.hidden,
|
||||
CmbPropertyLabel.hidden > box > image {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in-out 0;
|
||||
}
|
||||
|
||||
button.hidden:hover,
|
||||
CmbPropertyLabel.hidden:hover > box > image {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
popover.cmb-binding-popover button.close,
|
||||
list.notifications button.close {
|
||||
padding: unset;
|
||||
margin: unset;
|
||||
border: unset;
|
||||
border-radius: 50%;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
CmbPropertyLabel {
|
||||
min-width:unset;
|
||||
min-height: unset;
|
||||
padding: unset;
|
||||
margin: unset;
|
||||
border: unset;
|
||||
background: unset;
|
||||
box-shadow: unset;
|
||||
outline: unset;
|
||||
}
|
||||
|
||||
CmbPropertyLabel > box > label {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
CmbPropertyLabel:focus > box > label {
|
||||
/*
|
||||
FIXME: use focus_border_color
|
||||
$focus_border_color: if($variant == 'light', transparentize($selected_bg_color, 0.5), transparentize($selected_bg_color, 0.3));
|
||||
*/
|
||||
outline-color: color-mix(in srgb, var(--accent-bg-color) 60%, transparent);
|
||||
outline-offset: -2px;
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
CmbPropertyLabel.modified > box > label {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
CmbPropertyLabel.warning > box > label {
|
||||
text-decoration: underline wavy @warning_color;
|
||||
}
|
||||
|
||||
listview.cmb-list-view {
|
||||
background-color: @theme_bg_color;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row {
|
||||
padding: 2px 8px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row:drop(active):not(.drop-after):not(.drop-before) {
|
||||
outline: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row.drop-before:drop(active) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-top: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row.drop-after:drop(active) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
border-bottom: 2px solid color-mix(in srgb, @theme_bg_color 80%, black);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row > treeexpander.cmb-path > expander {
|
||||
-gtk-icon-source: -gtk-icontheme("folder-symbolic");
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row > treeexpander.cmb-path > expander:checked {
|
||||
-gtk-icon-source: -gtk-icontheme("folder-open-symbolic");
|
||||
}
|
||||
|
||||
listview.cmb-list-view > row > treeexpander.cmb-unsaved-path > expander {
|
||||
-gtk-icon-source: -gtk-icontheme("view-list-symbolic");
|
||||
}
|
||||
|
||||
button.compact {
|
||||
padding: 2px 4px;
|
||||
font-weight: normal;
|
||||
}
|
@ -1,31 +1,12 @@
|
||||
<?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>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_base.sql</file>
|
||||
<file>cmb_project.sql</file>
|
||||
<file>cmb_history.sql</file>
|
||||
<file>gobject-2.0.sql</file>
|
||||
<file>gtk+-3.0.sql</file>
|
||||
<file>gtk-4.0.sql</file>
|
||||
<file>cmb_signal_editor.ui</file>
|
||||
<file>cmb_type_chooser.ui</file>
|
||||
<file>cmb_type_chooser_widget.ui</file>
|
||||
<file>cmb_ui_editor.ui</file>
|
||||
<file>cmb_view.ui</file>
|
||||
<file>control/cmb_file_button.ui</file>
|
||||
<file>control/cmb_translatable_widget.ui</file>
|
||||
<file>db/cmb_base.sql</file>
|
||||
<file>db/cmb_history.sql</file>
|
||||
<file>db/cmb_project.sql</file>
|
||||
<file>icons/scalable/actions/binded-symbolic.svg</file>
|
||||
<file>icons/scalable/actions/bind-symbolic.svg</file>
|
||||
<file>cmb_notification_list_row.ui</file>
|
||||
</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")
|
@ -1,43 +1,29 @@
|
||||
#
|
||||
# Cambalache Object wrappers base class
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# 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
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import gi
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class CmbBase(GObject.GObject):
|
||||
project = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
display_name = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
project = GObject.Property(type=GObject.GObject, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def db_get(self, query, pk):
|
||||
c = self.project.db.execute(query, pk)
|
||||
c = self.project.conn.cursor()
|
||||
c.execute(query, pk)
|
||||
row = c.fetchone()
|
||||
c.close()
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def db_set(self, query, pk, value):
|
||||
self.project.db.execute(query, (value,) + pk)
|
||||
c = self.project.conn.cursor()
|
||||
c.execute(query, (value, ) + pk)
|
||||
c.close()
|
||||
|
198
cambalache/cmb_base.sql
Normal file
198
cambalache/cmb_base.sql
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Base Data Model for Cambalache Project
|
||||
*
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
*
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
|
||||
/** Common Data Model **/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS license (
|
||||
license_id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
license_text TEXT NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Library
|
||||
*
|
||||
* Support for different libraries
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS library (
|
||||
library_id TEXT PRIMARY KEY,
|
||||
version TEXT NOT NULL,
|
||||
shared_library TEXT,
|
||||
license_id TEXT REFERENCES license,
|
||||
license_text TEXT
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS library_license_id_fk ON library (license_id);
|
||||
|
||||
|
||||
/* Library dependecies
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS library_dependency (
|
||||
library_id TEXT REFERENCES library,
|
||||
dependency_id TEXT REFERENCES library,
|
||||
PRIMARY KEY(library_id, dependency_id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Library targeteable versions
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS library_version (
|
||||
library_id TEXT REFERENCES library,
|
||||
version TEXT,
|
||||
PRIMARY KEY(library_id, version)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Type
|
||||
*
|
||||
* Base table to keep type information
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS type (
|
||||
type_id TEXT PRIMARY KEY,
|
||||
|
||||
parent_id TEXT REFERENCES type,
|
||||
library_id TEXT REFERENCES library,
|
||||
get_type TEXT,
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
abstract BOOLEAN,
|
||||
layout TEXT CHECK (layout IN ('manager', 'child'))
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS type_parent_id_fk ON type (parent_id);
|
||||
CREATE INDEX IF NOT EXISTS type_library_id_fk ON type (library_id);
|
||||
|
||||
/* Add fundamental types */
|
||||
INSERT INTO type (type_id) VALUES
|
||||
('object'), ('interface'), ('enum'), ('flags'), ('gtype'),
|
||||
('gchar'), ('guchar'), ('gchararray'),
|
||||
('gboolean'),
|
||||
('gint'), ('guint'), ('glong'), ('gulong'), ('gint64'), ('guint64'),
|
||||
('gfloat'), ('gdouble'),
|
||||
('gpointer'), ('gboxed'), ('gparam'), ('gvariant');
|
||||
|
||||
|
||||
/* Type Interfaces
|
||||
*
|
||||
* Keep a list of interfaces implemented by type
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS type_iface (
|
||||
type_id TEXT,
|
||||
iface_id TEXT REFERENCES type,
|
||||
PRIMARY KEY(type_id, iface_id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Enumerations
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS type_enum (
|
||||
type_id TEXT REFERENCES type,
|
||||
name TEXT,
|
||||
nick TEXT,
|
||||
value INTEGER,
|
||||
identifier TEXT,
|
||||
doc TEXT,
|
||||
PRIMARY KEY(type_id, name)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Flags
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS type_flags (
|
||||
type_id TEXT REFERENCES type,
|
||||
name TEXT,
|
||||
nick TEXT,
|
||||
value INTEGER,
|
||||
identifier TEXT,
|
||||
doc TEXT,
|
||||
PRIMARY KEY(type_id, name)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Type Tree
|
||||
*
|
||||
* VIEW of ancestors and ifaces by type
|
||||
*/
|
||||
CREATE VIEW IF NOT EXISTS type_tree AS
|
||||
WITH RECURSIVE ancestor(type_id, generation, parent_id) AS (
|
||||
SELECT type_id, 1, parent_id FROM type
|
||||
WHERE parent_id IS NOT NULL AND
|
||||
parent_id != 'interface' AND
|
||||
parent_id != 'enum' AND
|
||||
parent_id != 'flags'
|
||||
UNION ALL
|
||||
SELECT ancestor.type_id, generation + 1, type.parent_id
|
||||
FROM type JOIN ancestor ON type.type_id = ancestor.parent_id
|
||||
WHERE type.parent_id IS NOT NULL
|
||||
)
|
||||
SELECT * FROM ancestor
|
||||
UNION
|
||||
SELECT ancestor.type_id, 0, type_iface.iface_id
|
||||
FROM ancestor JOIN type_iface
|
||||
WHERE ancestor.type_id = type_iface.type_id
|
||||
ORDER BY type_id,generation;
|
||||
|
||||
|
||||
/* Property
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS property (
|
||||
owner_id TEXT REFERENCES type,
|
||||
property_id TEXT NOT NULL,
|
||||
|
||||
type_id TEXT REFERENCES type,
|
||||
writable BOOLEAN,
|
||||
construct_only BOOLEAN,
|
||||
default_value TEXT,
|
||||
minimum TEXT,
|
||||
maximum TEXT,
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
PRIMARY KEY(owner_id, property_id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS property_type_id_fk ON property (type_id);
|
||||
|
||||
/* Check property:owner_id is not fundamental */
|
||||
CREATE TRIGGER IF NOT EXISTS on_property_before_insert_check BEFORE INSERT ON property
|
||||
BEGIN
|
||||
SELECT
|
||||
CASE
|
||||
WHEN (SELECT parent_id FROM type WHERE type_id=NEW.owner_id) IS NULL THEN
|
||||
RAISE (ABORT,'owner_id is not an object type')
|
||||
END;
|
||||
END;
|
||||
|
||||
|
||||
/* Signal
|
||||
*
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS signal (
|
||||
owner_id TEXT REFERENCES type,
|
||||
signal_id TEXT NOT NULL,
|
||||
|
||||
version TEXT,
|
||||
deprecated_version TEXT,
|
||||
detailed BOOLEAN,
|
||||
PRIMARY KEY(owner_id, signal_id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
/* Check signal:owner_id is not fundamental */
|
||||
CREATE TRIGGER IF NOT EXISTS on_signal_before_insert_check BEFORE INSERT ON signal
|
||||
BEGIN
|
||||
SELECT
|
||||
CASE
|
||||
WHEN (SELECT parent_id FROM type WHERE type_id=NEW.owner_id) IS NULL THEN
|
||||
RAISE (ABORT,'owner_id is not an object type')
|
||||
END;
|
||||
END;
|
||||
|
@ -1,86 +0,0 @@
|
||||
#
|
||||
# Blueprint compiler integration functions
|
||||
#
|
||||
# Copyright (C) 2025 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import io
|
||||
|
||||
try:
|
||||
import blueprintcompiler as bp
|
||||
from blueprintcompiler import parser, tokenizer
|
||||
from blueprintcompiler.decompiler import decompile_string
|
||||
from blueprintcompiler.outputs import XmlOutput
|
||||
except Exception:
|
||||
bp = None
|
||||
|
||||
|
||||
class CmbBlueprintError(Exception):
|
||||
def __init__(self, message, errors=[]):
|
||||
super().__init__(message)
|
||||
self.errors = errors
|
||||
|
||||
|
||||
class CmbBlueprintUnsupportedError(CmbBlueprintError):
|
||||
pass
|
||||
|
||||
|
||||
class CmbBlueprintMissingError(CmbBlueprintError):
|
||||
def __init__(self):
|
||||
super().__init__("blueprintcompiler is not available")
|
||||
|
||||
|
||||
def cmb_blueprint_decompile(data: str) -> str:
|
||||
if bp is None:
|
||||
raise CmbBlueprintMissingError()
|
||||
|
||||
try:
|
||||
retval = decompile_string(data)
|
||||
except bp.decompiler.UnsupportedError as e:
|
||||
raise CmbBlueprintUnsupportedError(str(e))
|
||||
except Exception as e:
|
||||
raise CmbBlueprintError(str(e))
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def cmb_blueprint_compile(data: str) -> str:
|
||||
if bp is None:
|
||||
raise CmbBlueprintMissingError()
|
||||
|
||||
tokens = tokenizer.tokenize(data)
|
||||
ast, errors, warnings = parser.parse(tokens)
|
||||
|
||||
if errors:
|
||||
f = io.StringIO("")
|
||||
errors.pretty_print("temp", data, f)
|
||||
f.seek(0)
|
||||
raise CmbBlueprintError(f.read(), errors=errors)
|
||||
|
||||
if ast is None:
|
||||
raise CmbBlueprintError("AST is None")
|
||||
|
||||
# Ignore warnings
|
||||
|
||||
retval = XmlOutput().emit(ast)
|
||||
return retval.encode()
|
||||
|
@ -1,141 +0,0 @@
|
||||
#
|
||||
# CmbContextMenu - Cambalache UI Editor
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from gi.repository import GObject, GLib, Gio, Gdk, Gtk
|
||||
from cambalache import _
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_context_menu.ui")
|
||||
class CmbContextMenu(Gtk.PopoverMenu):
|
||||
__gtype_name__ = "CmbContextMenu"
|
||||
|
||||
enable_theme = GObject.Property(
|
||||
type=bool,
|
||||
default=False,
|
||||
flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
target_tk = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
main_section = Gtk.Template.Child()
|
||||
add_submenu = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.theme_submenu = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify::target-tk", self.__on_target_tk_notify)
|
||||
|
||||
def __on_target_tk_notify(self, obj, pspec):
|
||||
self.__populate_css_theme_box()
|
||||
self.__update_add_submenu()
|
||||
|
||||
def __update_add_submenu(self):
|
||||
if self.target_tk not in ["gtk-4.0", "gtk+-3.0"]:
|
||||
return
|
||||
|
||||
types = [
|
||||
"GtkBox",
|
||||
"GtkGrid",
|
||||
"GtkExpander",
|
||||
"GtkRevealer",
|
||||
"GtkOverlay",
|
||||
]
|
||||
|
||||
if self.target_tk == "gtk+-3.0":
|
||||
types += [
|
||||
"GtkAligment",
|
||||
"GtkEventBox"
|
||||
]
|
||||
else:
|
||||
types += [
|
||||
"GtkGraphicsOffload",
|
||||
]
|
||||
|
||||
self.add_submenu.remove_all()
|
||||
|
||||
for gtype in sorted(types):
|
||||
item = Gio.MenuItem()
|
||||
item.set_label(gtype)
|
||||
item.set_action_and_target_value("win.add_parent", GLib.Variant("s", gtype))
|
||||
self.add_submenu.append_item(item)
|
||||
|
||||
def __populate_css_theme_box(self):
|
||||
gtk_path = "gtk-3.0"
|
||||
|
||||
if not self.enable_theme or self.target_tk not in ["gtk-4.0", "gtk+-3.0"]:
|
||||
return
|
||||
|
||||
if self.target_tk == "gtk-4.0":
|
||||
gtk_path = "gtk-4.0"
|
||||
# FIXME: whats the real default theme for gtk4?
|
||||
themes = ["Default"]
|
||||
else:
|
||||
themes = ["Adwaita", "HighContrast", "HighContrastInverse"]
|
||||
|
||||
if self.theme_submenu is None:
|
||||
self.theme_submenu = Gio.Menu()
|
||||
self.main_section.prepend_submenu(_("CSS theme"), self.theme_submenu)
|
||||
|
||||
# Remove all items from theme submenu
|
||||
self.theme_submenu.remove_all()
|
||||
|
||||
dirs = []
|
||||
|
||||
dirs += GLib.get_system_data_dirs()
|
||||
dirs.append(GLib.get_user_data_dir())
|
||||
|
||||
# Add /themes to every dir
|
||||
dirs = list(map(lambda d: os.path.join(d, "themes"), dirs))
|
||||
|
||||
# Append ~/.themes
|
||||
dirs.append(os.path.join(GLib.get_home_dir(), ".themes"))
|
||||
|
||||
for path in dirs:
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
for theme in os.listdir(path):
|
||||
tpath = os.path.join(path, theme, gtk_path, "gtk.css")
|
||||
if os.path.exists(tpath):
|
||||
themes.append(theme)
|
||||
|
||||
# Dedup and sort
|
||||
themes = list(dict.fromkeys(themes))
|
||||
|
||||
for theme in sorted(themes):
|
||||
item = Gio.MenuItem()
|
||||
item.set_label(theme)
|
||||
item.set_action_and_target_value("win.workspace_theme", GLib.Variant("s", theme))
|
||||
self.theme_submenu.append_item(item)
|
||||
|
||||
def popup_at(self, x, y):
|
||||
r = Gdk.Rectangle()
|
||||
r.x, r.y = (x, y)
|
||||
r.width = r.height = 0
|
||||
self.set_pointing_to(r)
|
||||
self.popup()
|
@ -1,57 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_context_menu.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gio" version="2.0"/>
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<menu id="menu_model">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.cut</attribute>
|
||||
<attribute name="label" translatable="yes">Cut</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.copy</attribute>
|
||||
<attribute name="label" translatable="yes">Copy</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.paste</attribute>
|
||||
<attribute name="label" translatable="yes">Paste</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.delete</attribute>
|
||||
<attribute name="label" translatable="yes">Delete</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="action">win.add_object</attribute>
|
||||
<attribute name="label" translatable="yes">Add object here</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.add_object_toplevel</attribute>
|
||||
<attribute name="label" translatable="yes">Add object as toplevel</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.remove_parent</attribute>
|
||||
<attribute name="label">Remove parent</attribute>
|
||||
</item>
|
||||
<submenu id="add_submenu">
|
||||
<attribute name="label">Add parent</attribute>
|
||||
</submenu>
|
||||
<item>
|
||||
<attribute name="action">win.clear</attribute>
|
||||
<attribute name="label" translatable="yes">Clear Properties</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="action">win.documentation</attribute>
|
||||
<attribute name="label" translatable="yes">Read Documentation</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="main_section"/>
|
||||
</menu>
|
||||
<template class="CmbContextMenu" parent="GtkPopoverMenu">
|
||||
<property name="menu-model">menu_model</property>
|
||||
</template>
|
||||
</interface>
|
@ -1,161 +0,0 @@
|
||||
#
|
||||
# Cambalache CSS wrapper
|
||||
#
|
||||
# Copyright (C) 2022 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_objects_base import CmbBaseCSS
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbCSS(CmbBaseCSS):
|
||||
__gsignals__ = {
|
||||
"file-changed": (GObject.SignalFlags.RUN_FIRST, None, ()),
|
||||
}
|
||||
|
||||
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||
css = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._path = None
|
||||
self._monitor = None
|
||||
self.__saving = False
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
self.load_css()
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
if pspec.name not in ["css"]:
|
||||
self.project._css_changed(self, pspec.name)
|
||||
|
||||
if pspec.name == "filename":
|
||||
self.load_css()
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls, css_id, filename):
|
||||
return os.path.basename(filename) if filename else _("Unnamed CSS {css_id}").format(css_id=css_id)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
return CmbCSS.get_display_name(self.css_id, self.filename)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def priority(self):
|
||||
retval = self.db_get("SELECT priority FROM css WHERE css_id=?;", (self.css_id,))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@priority.setter
|
||||
def _set_priority(self, value):
|
||||
self.db_set("UPDATE css SET priority=? WHERE css_id=?;", (self.css_id,), value if value != 0 else None)
|
||||
|
||||
@GObject.Property(type=object)
|
||||
def provider_for(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
retval = []
|
||||
for row in c.execute("SELECT ui_id FROM css_ui WHERE css_id=? ORDER BY ui_id;", (self.css_id,)):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __on_css_file_changed(self, file_monitor, file, other_file, event_type):
|
||||
if event_type != Gio.FileMonitorEvent.CHANGES_DONE_HINT:
|
||||
return
|
||||
|
||||
if self.__saving:
|
||||
self.__saving = False
|
||||
return
|
||||
else:
|
||||
self.emit("file-changed")
|
||||
|
||||
def load_css(self):
|
||||
if not self.project or not self.filename:
|
||||
return False
|
||||
|
||||
dirname = os.path.dirname(self.project.filename)
|
||||
path = os.path.join(dirname, self.filename)
|
||||
|
||||
if os.path.exists(path):
|
||||
self._path = path
|
||||
with open(path) as fd:
|
||||
self.css = fd.read()
|
||||
fd.close()
|
||||
|
||||
if self._monitor:
|
||||
self._monitor.cancel()
|
||||
|
||||
gfile = Gio.File.new_for_path(path)
|
||||
self._monitor = gfile.monitor(Gio.FileMonitorFlags.NONE, None)
|
||||
self._monitor.connect("changed", self.__on_css_file_changed)
|
||||
|
||||
return True
|
||||
else:
|
||||
self._path = None
|
||||
|
||||
return False
|
||||
|
||||
def save_css(self):
|
||||
if not self.project or not self.filename:
|
||||
return
|
||||
|
||||
needs_load = False
|
||||
|
||||
if self._path is None:
|
||||
dirname = os.path.dirname(self.project.filename)
|
||||
self._path = os.path.join(dirname, self.filename)
|
||||
needs_load = True
|
||||
|
||||
self.__saving = True
|
||||
with open(self._path, "w") as fd:
|
||||
fd.write(self.css)
|
||||
|
||||
if needs_load:
|
||||
self.notify("filename")
|
||||
|
||||
def add_ui(self, ui):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get("SELECT count(css_id) FROM css_ui WHERE css_id=? AND ui_id=?;", (self.css_id, ui.ui_id))
|
||||
|
||||
if count == 0:
|
||||
c.execute("INSERT INTO css_ui (css_id, ui_id) VALUES (?, ?);", (self.css_id, ui.ui_id))
|
||||
|
||||
c.close()
|
||||
|
||||
self.notify("provider_for")
|
||||
|
||||
def remove_ui(self, ui):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
c.execute("DELETE FROM css_ui WHERE css_id=? AND ui_id=?;", (self.css_id, ui.ui_id))
|
||||
c.close()
|
||||
|
||||
self.notify("provider_for")
|
@ -1,200 +0,0 @@
|
||||
#
|
||||
# CmbCSSEditor - Cambalache CSS Editor
|
||||
#
|
||||
# Copyright (C) 2022-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
from cambalache import utils, _
|
||||
from .cmb_css import CmbCSS
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_css_editor.ui")
|
||||
class CmbCSSEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbCSSEditor"
|
||||
|
||||
filename = Gtk.Template.Child()
|
||||
priority = Gtk.Template.Child()
|
||||
is_global = Gtk.Template.Child()
|
||||
|
||||
ui_menu_button = Gtk.Template.Child()
|
||||
ui_box = Gtk.Template.Child()
|
||||
infobar = Gtk.Template.Child()
|
||||
save_button = Gtk.Template.Child()
|
||||
view = Gtk.Template.Child()
|
||||
|
||||
fields = [("filename", "cmb-value"), ("priority", "value"), ("is_global", "active")]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self._bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
self.priority.set_range(0, 10000)
|
||||
self.priority.set_increments(10, 100)
|
||||
|
||||
@GObject.Property(type=CmbCSS)
|
||||
def object(self):
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self._object:
|
||||
return
|
||||
|
||||
for binding in self._bindings:
|
||||
binding.unbind()
|
||||
|
||||
if self._object:
|
||||
self._object.project.disconnect_by_func(self.__on_ui_added_removed)
|
||||
self._object.disconnect_by_func(self.__on_provider_for_notify)
|
||||
self._object.disconnect_by_func(self.__on_css_notify)
|
||||
self._object.disconnect_by_func(self.__on_file_changed)
|
||||
|
||||
self._bindings = []
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj is None:
|
||||
self.set_sensitive(False)
|
||||
return
|
||||
|
||||
self.filename.dirname = obj.project.dirname
|
||||
self.set_sensitive(True)
|
||||
|
||||
for field, target in self.fields:
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
field,
|
||||
getattr(self, field),
|
||||
target,
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
binding = GObject.Object.bind_property(
|
||||
obj, "css", self.view, "text", GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
obj.project.connect("ui-added", self.__on_ui_added_removed)
|
||||
obj.project.connect("ui-removed", self.__on_ui_added_removed)
|
||||
obj.connect("notify::provider-for", self.__on_provider_for_notify)
|
||||
obj.connect("notify::css", self.__on_css_notify)
|
||||
obj.connect("file-changed", self.__on_file_changed)
|
||||
|
||||
self.__update_provider_for()
|
||||
self.__update_ui_button_label()
|
||||
|
||||
@Gtk.Template.Callback("on_save_button_clicked")
|
||||
def __on_save_button_clicked(self, button):
|
||||
self._object.save_css()
|
||||
self.infobar.set_revealed(False)
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
@Gtk.Template.Callback("on_infobar_response")
|
||||
def __on_infobar_response(self, infobar, response_id):
|
||||
if response_id == Gtk.ResponseType.OK:
|
||||
self.__load_filename()
|
||||
|
||||
self.infobar.set_revealed(False)
|
||||
|
||||
def __update_provider_for(self):
|
||||
# Remove all css_ui check buttons
|
||||
for child in utils.widget_get_children(self.ui_box):
|
||||
self.ui_box.remove(child)
|
||||
|
||||
if self._object is None:
|
||||
return
|
||||
|
||||
ui_list = self._object.project.get_ui_list()
|
||||
provider_for = self._object.provider_for
|
||||
|
||||
# Generate a check button for each UI
|
||||
for ui in ui_list:
|
||||
check = Gtk.CheckButton(
|
||||
label=ui.display_name, active=ui.ui_id in provider_for, halign=Gtk.Align.START, visible=True
|
||||
)
|
||||
check.connect("toggled", self.__on_check_button_toggled, ui)
|
||||
self.ui_box.append(check)
|
||||
|
||||
def __on_file_changed(self, obj):
|
||||
self.infobar.set_revealed(True)
|
||||
self.save_button.set_sensitive(True)
|
||||
|
||||
def __load_filename(self):
|
||||
if not self.object or not self.object.load_css():
|
||||
self.save_button.set_sensitive(False)
|
||||
|
||||
def __on_check_button_toggled(self, button, ui):
|
||||
if button.props.active:
|
||||
self.object.add_ui(ui)
|
||||
else:
|
||||
self.object.remove_ui(ui)
|
||||
|
||||
self.__update_ui_button_label()
|
||||
|
||||
def __update_ui_button_label(self):
|
||||
n = 0
|
||||
first_one = None
|
||||
child = self.ui_box.get_first_child()
|
||||
|
||||
while child is not None:
|
||||
if child.props.active:
|
||||
n += 1
|
||||
|
||||
if first_one is None:
|
||||
first_one = child
|
||||
|
||||
child = child.get_next_sibling()
|
||||
|
||||
if first_one is None:
|
||||
self.ui_menu_button.props.label = _("None")
|
||||
else:
|
||||
self.ui_menu_button.props.label = f"{first_one.props.label} + {n - 1}" if n > 1 else first_one.props.label
|
||||
|
||||
def __on_ui_added_removed(self, project, ui):
|
||||
self.__update_provider_for()
|
||||
|
||||
def __on_provider_for_notify(self, obj, pspec):
|
||||
self.__update_provider_for()
|
||||
|
||||
def __on_css_notify(self, obj, pspec):
|
||||
self.save_button.set_sensitive(True)
|
||||
|
||||
@GObject.Signal(
|
||||
flags=GObject.SignalFlags.RUN_LAST,
|
||||
return_type=bool,
|
||||
arg_types=(),
|
||||
accumulator=GObject.signal_accumulator_true_handled,
|
||||
)
|
||||
def remove_css(self):
|
||||
if self.object:
|
||||
self.object.project.remove_css(self.object)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbCSSEditor, "CmbCSSEditor")
|
@ -1,203 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_css_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbCSSEditor" parent="GtkGrid">
|
||||
<property name="column-spacing">3</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Filename:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Priority:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Global:</property>
|
||||
<property name="tooltip-text" translatable="yes">This provider will be used in all UI.</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="ui_menu_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="popover">
|
||||
<object class="GtkPopover">
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="propagate-natural-width">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ui_box">
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">3</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="priority">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="is_global">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Provider for:</property>
|
||||
<property name="tooltip-text" translatable="yes">List of UI where this provider will be used</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes"><small>Note: CSS files need to be loaded at runtime</small></property>
|
||||
<property name="use-markup">1</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="sensitive">0</property>
|
||||
<property name="tooltip-text" translatable="yes">Save CSS file</property>
|
||||
<property name="valign">end</property>
|
||||
<signal name="clicked" handler="on_save_button_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">document-save-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="infobar">
|
||||
<property name="message-type">warning</property>
|
||||
<property name="revealed">0</property>
|
||||
<property name="show-close-button">1</property>
|
||||
<signal name="response" handler="on_infobar_response"/>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="reload_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Reload</property>
|
||||
<property name="receives-default">1</property>
|
||||
<property name="valign">end</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="hexpand">1</property>
|
||||
<property name="spacing">16</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="label" translatable="yes">The file changed on disk.</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="focusable">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
<child>
|
||||
<object class="CmbSourceView" id="view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">css</property>
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="vexpand-set">True</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
3120
cambalache/cmb_db.py
3120
cambalache/cmb_db.py
File diff suppressed because it is too large
Load Diff
@ -1,294 +0,0 @@
|
||||
#
|
||||
# CmbDBInspector
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GLib, GObject, Gio, Gtk
|
||||
from cambalache import CmbProject
|
||||
|
||||
|
||||
class CmbDBTable(GObject.Object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__properties = {}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def do_get_property(self, prop):
|
||||
# TODO: read from DB directly
|
||||
if not prop.name.startswith("cmb-int-") and prop.name not in self.__properties_set__:
|
||||
raise AttributeError('unknown property %s' % prop.name)
|
||||
return self.__properties[prop.name]
|
||||
|
||||
def do_set_property(self, prop, value):
|
||||
# TODO: only store PK values when using DB
|
||||
if not prop.name.startswith("cmb-int-") and prop.name not in self.__properties_set__:
|
||||
raise AttributeError('unknown property %s' % prop.name)
|
||||
self.__properties[prop.name] = value
|
||||
self.notify(prop.name)
|
||||
|
||||
|
||||
class CmbDBStore(GObject.GObject, Gio.ListModel):
|
||||
__gtype_name__ = 'CmbDBStore'
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, ItemClass, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__item_class = ItemClass
|
||||
self.__history_index = None
|
||||
self._objects = []
|
||||
|
||||
def do_get_item(self, position):
|
||||
self.__check_refresh()
|
||||
return self._objects[position] if position < len(self._objects) else None
|
||||
|
||||
def do_get_item_type(self):
|
||||
return self.__item_class
|
||||
|
||||
def do_get_n_items(self):
|
||||
self.__check_refresh()
|
||||
return len(self._objects)
|
||||
|
||||
def __check_refresh(self):
|
||||
history_index = self.project.history_index
|
||||
|
||||
# Nothing to update if history did not changed
|
||||
if history_index == self.__history_index:
|
||||
return
|
||||
|
||||
ItemClass = self.__item_class
|
||||
properties = ItemClass.__properties__
|
||||
int_properties = ItemClass.__int_properties__
|
||||
table = ItemClass.__table__
|
||||
needs_update = False
|
||||
|
||||
# Basic optimization, only update if something changed in this table
|
||||
# TODO: this could be optimized more by check command to know exactly which row changed
|
||||
if self.__history_index is None or table.startswith("history") or table in ["global", "__profile__"]:
|
||||
needs_update = True
|
||||
else:
|
||||
change_table = table[7:] if table.startswith("history_") else table
|
||||
|
||||
# TODO: detect command compression
|
||||
for row in self.project.db.execute(
|
||||
"SELECT table_name FROM history WHERE history_id >= ? ORDER BY history_id;", (self.__history_index, )
|
||||
):
|
||||
table_name, = row
|
||||
if table_name == change_table:
|
||||
needs_update = True
|
||||
break
|
||||
|
||||
self.__history_index = history_index
|
||||
|
||||
if not needs_update:
|
||||
return
|
||||
|
||||
# Emit signal to clear model
|
||||
n_items = len(self._objects)
|
||||
if n_items:
|
||||
self._objects = []
|
||||
self.items_changed(0, n_items, 0)
|
||||
|
||||
if len(ItemClass.__pk__):
|
||||
pk_columns = ",".join(ItemClass.__pk__)
|
||||
else:
|
||||
pk_columns = "rowid"
|
||||
|
||||
for row in self.project.db.execute(f"SELECT * FROM {table} ORDER BY {pk_columns};"):
|
||||
item = ItemClass()
|
||||
for i, val in enumerate(row):
|
||||
property_id = properties[i]
|
||||
if property_id in int_properties:
|
||||
item.set_property(f"cmb-int-{property_id}", val if val is not None else 0)
|
||||
item.set_property(property_id, val)
|
||||
|
||||
self._objects.append(item)
|
||||
|
||||
# Emit signal to populate model
|
||||
self.items_changed(0, 0, len(self._objects))
|
||||
|
||||
|
||||
class TableView(Gtk.ColumnView):
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, ItemClass, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.show_row_separators = True
|
||||
self.props.show_column_separators = True
|
||||
self.props.reorderable = False
|
||||
self.__model = None
|
||||
self.__filter_model = None
|
||||
self.__item_class = ItemClass
|
||||
|
||||
for property_id in ItemClass.__properties__:
|
||||
factory = Gtk.SignalListItemFactory()
|
||||
factory.connect("setup", self._on_factory_setup)
|
||||
factory.connect("bind", self._on_factory_bind, property_id)
|
||||
factory.connect("unbind", self._on_factory_unbind)
|
||||
|
||||
col = Gtk.ColumnViewColumn(title=property_id, factory=factory)
|
||||
|
||||
if property_id in ItemClass.__int_properties__:
|
||||
property_expression = Gtk.PropertyExpression.new(ItemClass, None, f"cmb-int-{property_id}")
|
||||
sorter = Gtk.NumericSorter()
|
||||
else:
|
||||
property_expression = Gtk.PropertyExpression.new(ItemClass, None, property_id)
|
||||
sorter = Gtk.StringSorter()
|
||||
col.props.resizable = True
|
||||
col.props.expand = True
|
||||
|
||||
sorter.set_expression(property_expression)
|
||||
col.set_sorter(sorter)
|
||||
self.append_column(col)
|
||||
|
||||
# TODO: keep track of project changes only while we are showing this model
|
||||
self.connect("map", self.__on_map)
|
||||
self.project.connect("changed", self.__on_project_changed)
|
||||
|
||||
def __update_label(self, item, label, property_id):
|
||||
val = str(item.get_property(property_id))
|
||||
label.set_text(val if val else "")
|
||||
|
||||
def __on_item_notify(self, item, pspec, label):
|
||||
self.__update_label(item, label, pspec.name)
|
||||
|
||||
def _on_factory_setup(self, factory, list_item):
|
||||
label = Gtk.Inscription()
|
||||
list_item.set_child(label)
|
||||
|
||||
def _on_factory_bind(self, factory, list_item, property_id):
|
||||
label = list_item.get_child()
|
||||
item = list_item.get_item()
|
||||
|
||||
self.__update_label(item, label, property_id)
|
||||
item.connect(f"notify::{property_id}", self.__on_item_notify, label)
|
||||
|
||||
def _on_factory_unbind(self, factory, list_item):
|
||||
item = list_item.get_item()
|
||||
item.disconnect_by_func(self.__on_item_notify)
|
||||
|
||||
def __on_map(self, w):
|
||||
# Trigger check refresh
|
||||
if self.__model is not None:
|
||||
self.__model.get_n_items()
|
||||
return
|
||||
|
||||
# Load model when widget is shown
|
||||
self.__model = CmbDBStore(self.__item_class, project=self.project)
|
||||
self.__filter_model = Gtk.SortListModel(model=self.__model, sorter=self.get_sorter())
|
||||
self.set_model(Gtk.NoSelection(model=self.__filter_model))
|
||||
|
||||
def __on_project_changed(self, project):
|
||||
# Trigger check refresh
|
||||
if self.__model is not None and self.is_visible():
|
||||
self.__model.get_n_items()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_db_inspector.ui")
|
||||
class CmbDBInspector(Gtk.Box):
|
||||
__gtype_name__ = "CmbDBInspector"
|
||||
|
||||
stack = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__project = None
|
||||
self.__table_classes = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("map", self.__on_map)
|
||||
|
||||
@GObject.Property(type=CmbProject)
|
||||
def project(self):
|
||||
return self.__project
|
||||
|
||||
@project.setter
|
||||
def _set_project(self, project):
|
||||
self.__project = project
|
||||
|
||||
def __init_tables(self):
|
||||
db = self.project.db
|
||||
self.__table_classes = {}
|
||||
|
||||
for row in db.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"):
|
||||
table, = row
|
||||
|
||||
if table.startswith("sqlite_"):
|
||||
continue
|
||||
|
||||
klass = self.__class_from_table(table)
|
||||
self.__table_classes[table] = klass
|
||||
|
||||
def _metadata_from_table(self, table):
|
||||
db = self.project.db
|
||||
properties = []
|
||||
gproperties = {}
|
||||
int_properties = set()
|
||||
pk_list = []
|
||||
|
||||
for row in db.execute(f"PRAGMA table_info({table});"):
|
||||
col = row[1]
|
||||
col_type = row[2]
|
||||
pk = row[5]
|
||||
|
||||
name = col.replace("_", "-")
|
||||
properties.append(name)
|
||||
if col_type == "INTEGER":
|
||||
int_properties.add(name)
|
||||
gproperties[f"cmb-int-{name}"] = (int, "", "", GLib.MININT, GLib.MAXINT, 0, GObject.ParamFlags.READWRITE)
|
||||
|
||||
gproperties[name] = (str, "", "", None, GObject.ParamFlags.READWRITE)
|
||||
|
||||
if pk:
|
||||
pk_list.append(col)
|
||||
|
||||
return properties, gproperties, int_properties, pk_list
|
||||
|
||||
def __class_from_table(self, table):
|
||||
class_name = f"CmbDBTable_{table}"
|
||||
properties, gproperties, int_properties, pk = self._metadata_from_table(table)
|
||||
klass = type(class_name, (CmbDBTable,), dict(
|
||||
__table__=table,
|
||||
__gproperties__=gproperties,
|
||||
__properties__=properties,
|
||||
__properties_set__=set(properties),
|
||||
__int_properties__=int_properties,
|
||||
__pk__=pk)
|
||||
)
|
||||
return klass
|
||||
|
||||
def __populate_stack(self):
|
||||
for table, klass in self.__table_classes.items():
|
||||
sw = Gtk.ScrolledWindow(
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
propagate_natural_width=True,
|
||||
propagate_natural_height=True)
|
||||
view = TableView(klass, project=self.__project)
|
||||
sw.set_child(view)
|
||||
self.stack.add_titled(sw, table, table)
|
||||
|
||||
def __on_map(self, w):
|
||||
if self.__table_classes is None and self.__project is not None:
|
||||
self.__init_tables()
|
||||
self.__populate_stack()
|
@ -1,21 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_db_inspector.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbDBInspector" parent="GtkBox">
|
||||
<child>
|
||||
<object class="GtkStackSidebar">
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="halign">start</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<property name="valign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,163 +0,0 @@
|
||||
#
|
||||
# CmbDBmigration - Cambalache DataBase Migration functions
|
||||
#
|
||||
# Copyright (C) 2021-2023 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
|
||||
def ensure_columns_for_0_7_5(table, data):
|
||||
if table == "object":
|
||||
# Append position column
|
||||
return [row + (None,) for row in data]
|
||||
elif table in ["object_property", "object_layout_property"]:
|
||||
# Append translation_context, translation_comments columns
|
||||
return [row + (None, None) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def migrate_table_data_to_0_7_5(c, table, data):
|
||||
if table == "object":
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY parent_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NOT NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def ensure_columns_for_0_9_0(table, data):
|
||||
if table == "object_property":
|
||||
# Append inline_object_id column
|
||||
return [row + (None,) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def migrate_table_data_to_0_9_0(c, table, data):
|
||||
if table == "object_property":
|
||||
# Remove all object properties with a 0 as value
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM temp.object_property AS op
|
||||
WHERE value = 0 AND
|
||||
(SELECT property_id FROM temp.property WHERE owner_id=op.owner_id AND property_id=op.property_id AND is_object)
|
||||
IS NOT NULL;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def ensure_columns_for_0_11_2(table, data):
|
||||
if table in ["object", "ui"]:
|
||||
# Append custom_text column
|
||||
return [row + (None,) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_11_4(table, data):
|
||||
if table == "object_property":
|
||||
# Append bind_[source_id owner_id property_id flags] column
|
||||
return [row + (None, None, None, None) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_13_1(table, data):
|
||||
if table == "object_data":
|
||||
# Append translatable, translation_context, translation_comments columns
|
||||
return [row + (None, None, None) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_columns_for_0_17_3(table, data):
|
||||
if table == "object":
|
||||
# Append custom_child_fragment column
|
||||
return [row + (None,) for row in data]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def migrate_table_data_to_0_17_3(c, table, data):
|
||||
if table in ["object_property", "object_layout_property", "object_data"]:
|
||||
c.executescript(
|
||||
f"""
|
||||
UPDATE temp.{table} SET translatable=1
|
||||
WHERE translatable IS NOT NULL AND lower(translatable) IN (1, 'y', 'yes', 't', 'true');
|
||||
UPDATE temp.{table} SET translatable=NULL
|
||||
WHERE translatable IS NOT NULL AND translatable != 1;
|
||||
"""
|
||||
)
|
||||
|
||||
if table == "object_signal":
|
||||
for prop in ["swap", "after"]:
|
||||
c.executescript(
|
||||
f"""
|
||||
UPDATE temp.object_signal SET {prop}=1
|
||||
WHERE {prop} IS NOT NULL AND lower({prop}) IN (1, 'y', 'yes', 't', 'true');
|
||||
UPDATE temp.object_signal SET {prop}=NULL WHERE {prop} IS NOT NULL AND after != 1;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def migrate_table_data_to_0_91_3(c, table, data):
|
||||
# Ensure every object has a position
|
||||
if table == "object":
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id, parent_id ORDER BY position, object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NOT NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE temp.object SET position=new.position - 1
|
||||
FROM (
|
||||
SELECT row_number() OVER (PARTITION BY ui_id ORDER BY object_id) position, ui_id, object_id
|
||||
FROM temp.object
|
||||
WHERE parent_id IS NULL
|
||||
) AS new
|
||||
WHERE temp.object.ui_id=new.ui_id AND temp.object.object_id=new.object_id;
|
||||
"""
|
||||
)
|
@ -1,112 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import inspect
|
||||
import sqlite3
|
||||
|
||||
|
||||
class CmbProfileConnection(sqlite3.Connection):
|
||||
def __init__(self, path, **kwargs):
|
||||
super().__init__(path, **kwargs)
|
||||
|
||||
self.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS __profile__ (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
query TEXT NOT NULL,
|
||||
plan TEXT,
|
||||
executions INTEGER NOT NULL DEFAULT 1,
|
||||
total_time INTEGER NOT NULL DEFAULT 0,
|
||||
average_time INTEGER NOT NULL DEFAULT 0,
|
||||
min_time INTEGER NOT NULL DEFAULT 0,
|
||||
max_time INTEGER NOT NULL DEFAULT 0,
|
||||
callers JSONB
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
# Striped querys PK dictionary
|
||||
self._querys = {}
|
||||
|
||||
# Populate querys
|
||||
for row in super().execute("SELECT id, query FROM __profile__;"):
|
||||
id, query = row
|
||||
self._querys[query] = id
|
||||
|
||||
def cursor(self):
|
||||
return super(CmbProfileConnection, self).cursor(CmbProfileCursor)
|
||||
|
||||
def execute(self, *args):
|
||||
start = time.monotonic_ns()
|
||||
retval = super().execute(*args)
|
||||
self.log_query(time.monotonic_ns() - start, *args)
|
||||
return retval
|
||||
|
||||
def log_query(self, exec_time, *args):
|
||||
query = args[0].strip()
|
||||
|
||||
if query.startswith("CREATE") or query.startswith("PRAGMA"):
|
||||
return
|
||||
|
||||
caller = inspect.getframeinfo(inspect.stack()[2][0])
|
||||
file = os.path.basename(caller.filename).removesuffix('.py')
|
||||
function = caller.function
|
||||
# Use a different dot to avoid json syntax error
|
||||
caller_id = f"{file}․{function}:{caller.lineno}"
|
||||
|
||||
if file == "cmb_db" and function == "execute":
|
||||
caller = inspect.getframeinfo(inspect.stack()[3][0])
|
||||
file = os.path.basename(caller.filename).removesuffix('.py')
|
||||
caller_id = f"{file}․{caller.function}:{caller.lineno} {caller_id}"
|
||||
|
||||
# Convert from nano seconds to micro seconds
|
||||
exec_time = int(exec_time / 1000)
|
||||
pk_id = self._querys.get(query, None)
|
||||
|
||||
if pk_id is None:
|
||||
# Get query plan
|
||||
if len(args) > 1:
|
||||
c = super().execute(f"EXPLAIN QUERY PLAN {query}", args[1])
|
||||
else:
|
||||
c = super().execute(f"EXPLAIN QUERY PLAN {query}")
|
||||
|
||||
# Convert plan to a string
|
||||
plan = []
|
||||
for row in c:
|
||||
plan.append(" ".join(str(row)))
|
||||
plan = "\n".join(plan)
|
||||
|
||||
# Create new query entry in profile table
|
||||
c = super().execute(
|
||||
"""
|
||||
INSERT INTO __profile__(query, plan, total_time, average_time, min_time, max_time, callers)
|
||||
VALUES(?, ?, ?, ?, ?, ?, json(?))
|
||||
RETURNING id;
|
||||
""",
|
||||
(query, plan, exec_time, exec_time, exec_time, exec_time, f"""{{"{caller_id}": 1}}""")
|
||||
)
|
||||
pk_id = c.fetchone()[0]
|
||||
self._querys[query] = pk_id
|
||||
else:
|
||||
# Increment number of executions of this query
|
||||
super().execute(
|
||||
f"""
|
||||
UPDATE __profile__
|
||||
SET
|
||||
executions=executions+1,
|
||||
total_time=total_time+?,
|
||||
average_time=total_time/executions,
|
||||
min_time=min(min_time, ?),
|
||||
max_time=max(max_time, ?),
|
||||
callers=json_set(callers, '$.{caller_id}', callers->'$.{caller_id}' + 1)
|
||||
WHERE id=?;
|
||||
""",
|
||||
(exec_time, exec_time, exec_time, pk_id)
|
||||
)
|
||||
|
||||
|
||||
class CmbProfileCursor(sqlite3.Cursor):
|
||||
def execute(self, *args):
|
||||
start = time.monotonic_ns()
|
||||
retval = super().execute(*args)
|
||||
self.connection.log_query(time.monotonic_ns() - start, *args)
|
||||
return retval
|
@ -1,86 +0,0 @@
|
||||
#
|
||||
# CmbFragmentEditor - Cambalache CSS Editor
|
||||
#
|
||||
# Copyright (C) 2022-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
from .cmb_object import CmbObject
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_fragment_editor.ui")
|
||||
class CmbFragmentEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbFragmentEditor"
|
||||
|
||||
view = Gtk.Template.Child()
|
||||
child_view = Gtk.Template.Child()
|
||||
switcher = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self.__bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
def object(self):
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self._object:
|
||||
return
|
||||
|
||||
for binding in self.__bindings:
|
||||
binding.unbind()
|
||||
|
||||
self.__bindings = []
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj is None:
|
||||
return
|
||||
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
"custom-fragment",
|
||||
self.view,
|
||||
"text",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self.__bindings.append(binding)
|
||||
|
||||
# Only objects have child fragments
|
||||
if type(obj) is CmbObject and obj.parent:
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
"custom-child-fragment",
|
||||
self.child_view,
|
||||
"text",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self.__bindings.append(binding)
|
||||
|
||||
self.switcher.set_visible(True)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbFragmentEditor, "CmbFragmentEditor")
|
@ -1,61 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_fragment_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbFragmentEditor" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Extra fragments:</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="fragment_stack">
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<child>
|
||||
<object class="CmbSourceView" id="view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">fragment</property>
|
||||
<property name="title" translatable="yes">Fragment</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<child>
|
||||
<object class="CmbSourceView" id="child_view">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="lang">xml</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">child_fragment</property>
|
||||
<property name="title" translatable="yes">Child Fragment</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher" id="switcher">
|
||||
<property name="halign">center</property>
|
||||
<property name="stack">fragment_stack</property>
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,151 +0,0 @@
|
||||
#
|
||||
# Cambalache GResource wrapper
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_objects_base import CmbBaseGResource
|
||||
from .cmb_list_error import CmbListError
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbGResource(CmbBaseGResource, Gio.ListModel):
|
||||
|
||||
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._last_known = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbGResource<{self.resource_type}> id={self.gresource_id}"
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
resource_type = self.resource_type
|
||||
|
||||
if (resource_type == "gresources" and pspec.name == "gresources-filename") or \
|
||||
(resource_type == "gresource" and pspec.name == "gresource-prefix") or \
|
||||
(resource_type == "file" and pspec.name == "file-filename"):
|
||||
obj.notify("display-name")
|
||||
|
||||
self.project._gresource_changed(self, pspec.name)
|
||||
|
||||
@GObject.Property(type=CmbBaseGResource)
|
||||
def parent(self):
|
||||
if self.resource_type in ["gresource", "file"]:
|
||||
return self.project.get_gresource_by_id(self.parent_id)
|
||||
|
||||
return None
|
||||
|
||||
@GObject.Property(type=CmbBaseGResource)
|
||||
def gresources_bundle(self):
|
||||
resource_type = self.resource_type
|
||||
if resource_type == "gresource":
|
||||
return self.parent
|
||||
elif resource_type == "file":
|
||||
return self.parent.parent
|
||||
|
||||
return self
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
resource_type = self.resource_type
|
||||
|
||||
if resource_type == "gresources":
|
||||
gresources_filename = self.gresources_filename
|
||||
if gresources_filename:
|
||||
basename, relpath = self.project._get_basename_relpath(self.gresources_filename)
|
||||
return basename
|
||||
|
||||
return _("Unnamed GResource {id}").format(id=self.gresource_id)
|
||||
elif resource_type == "gresource":
|
||||
gresource_prefix = self.gresource_prefix
|
||||
return gresource_prefix if gresource_prefix else _("Unprefixed resource {id}").format(id=self.gresource_id)
|
||||
elif resource_type == "file":
|
||||
file_filename = self.file_filename
|
||||
return file_filename if file_filename else _("Unnamed file {id}").format(id=self.gresource_id)
|
||||
|
||||
# GListModel helpers
|
||||
def _save_last_known_parent_and_position(self):
|
||||
self._last_known = (self.parent, self.position)
|
||||
|
||||
def _update_new_parent(self):
|
||||
parent = self.parent
|
||||
position = self.position
|
||||
|
||||
# Emit GListModel signal to update model
|
||||
if parent:
|
||||
parent.items_changed(position, 0, 1)
|
||||
parent.notify("n-items")
|
||||
|
||||
self._last_known = None
|
||||
|
||||
def _remove_from_old_parent(self):
|
||||
if self._last_known is None:
|
||||
return
|
||||
|
||||
parent, position = self._last_known
|
||||
|
||||
# Emit GListModel signal to update model
|
||||
if parent:
|
||||
parent.items_changed(position, 1, 0)
|
||||
parent.notify("n-items")
|
||||
|
||||
self._last_known = None
|
||||
|
||||
# GListModel iface
|
||||
def do_get_item(self, position):
|
||||
gresource_id = self.gresource_id
|
||||
key = self.db_get(
|
||||
"SELECT gresource_id FROM gresource WHERE parent_id=? AND position=?;",
|
||||
(gresource_id, position)
|
||||
)
|
||||
|
||||
if key is not None:
|
||||
return self.project.get_gresource_by_id(key)
|
||||
|
||||
# This should not happen
|
||||
return CmbListError()
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBaseGResource
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
if self.resource_type in ["gresources", "gresource"]:
|
||||
retval = self.db_get("SELECT COUNT(gresource_id) FROM gresource WHERE parent_id=?;", (self.gresource_id, ))
|
||||
return retval if retval is not None else 0
|
||||
else:
|
||||
return 0
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
||||
|
@ -1,117 +0,0 @@
|
||||
#
|
||||
# CmbGResourceEditor - Cambalache GResource Editor
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_gresource import CmbGResource
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_gresource_editor.ui")
|
||||
class CmbGResourceEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbGResourceEditor"
|
||||
|
||||
stack = Gtk.Template.Child()
|
||||
|
||||
gresources_filename = Gtk.Template.Child()
|
||||
gresource_prefix = Gtk.Template.Child()
|
||||
file_filename = Gtk.Template.Child()
|
||||
file_compressed = Gtk.Template.Child()
|
||||
file_preprocess = Gtk.Template.Child()
|
||||
file_alias = Gtk.Template.Child()
|
||||
|
||||
fields = [
|
||||
("gresources", "gresources_filename", "cmb-value"),
|
||||
("gresource", "gresource_prefix", "cmb-value"),
|
||||
("file", "file_filename", "cmb-value"),
|
||||
("file", "file_compressed", "active"),
|
||||
("file", "file_preprocess", "cmb-value"),
|
||||
("file", "file_alias", "cmb-value"),
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self._bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=CmbGResource)
|
||||
def object(self):
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self._object:
|
||||
return
|
||||
|
||||
for binding in self._bindings:
|
||||
binding.unbind()
|
||||
|
||||
self._bindings = []
|
||||
self._object = obj
|
||||
|
||||
if obj is None:
|
||||
self.set_sensitive(False)
|
||||
for for_type, field, target in self.fields:
|
||||
widget = getattr(self, field)
|
||||
target_prop = getattr(widget, target)
|
||||
|
||||
if isinstance(target_prop, int):
|
||||
setattr(widget, target, 0)
|
||||
else:
|
||||
setattr(widget, target, None)
|
||||
return
|
||||
|
||||
resource_type = obj.resource_type
|
||||
self.set_sensitive(True)
|
||||
self.stack.set_visible_child_name(resource_type)
|
||||
self.gresources_filename.dirname = obj.project.dirname
|
||||
self.file_filename.dirname = obj.project.dirname
|
||||
|
||||
for for_type, field, target in self.fields:
|
||||
if resource_type != for_type:
|
||||
continue
|
||||
|
||||
binding = GObject.Object.bind_property(
|
||||
obj,
|
||||
field,
|
||||
getattr(self, field),
|
||||
target,
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
@Gtk.Template.Callback("on_add_gresource_button_clicked")
|
||||
def __on_add_gresource_button_clicked(self, button):
|
||||
self._object.project.add_gresource("gresource", parent_id=self._object.gresource_id)
|
||||
|
||||
@Gtk.Template.Callback("on_add_file_button_clicked")
|
||||
def __on_add_file_button_clicked(self, button):
|
||||
self._object.project.add_gresource("file", parent_id=self._object.gresource_id)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbGResourceEditor, "CmbGResourceEditor")
|
@ -1,241 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_gresource_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.12"/>
|
||||
<template class="CmbGResourceEditor" parent="GtkBox">
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkGrid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Filename</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="gresources_filename">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">1</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">* This resource file need to be compiled and loaded at runtime</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_gresource_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="label">GResource</property>
|
||||
<signal name="clicked" handler="on_add_gresource_button_clicked"/>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">1</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">gresources</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkGrid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Prefix</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbEntry" id="gresource_prefix">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">* Files defined inside this will be available at gresource://prefix</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">2</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">1</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_file_button">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">file</property>
|
||||
<signal name="clicked" handler="on_add_file_button_clicked"/>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">1</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">gresource</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="child">
|
||||
<object class="GtkGrid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Filename</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Compressed</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Preprocess</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Alias</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="file_compressed">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbEntry" id="file_preprocess">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbEntry" id="file_alias">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="file_filename">
|
||||
<property name="use-open">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">0</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="name">file</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,26 +1,9 @@
|
||||
/*
|
||||
* Data Model for Cambalache Project History
|
||||
*
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
*
|
||||
* 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
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -41,11 +24,8 @@ CREATE TABLE history (
|
||||
command TEXT NOT NULL,
|
||||
range_id INTEGER REFERENCES history,
|
||||
table_name TEXT,
|
||||
columns JSON,
|
||||
message TEXT,
|
||||
table_pk JSON,
|
||||
new_values JSON,
|
||||
old_values JSON
|
||||
column_name TEXT,
|
||||
message TEXT
|
||||
);
|
||||
|
||||
/* This trigger will update PUSH/POP range and data automatically on POP */
|
@ -1,113 +0,0 @@
|
||||
#
|
||||
# Cambalache Layout Property wrapper
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# 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 . import utils
|
||||
|
||||
|
||||
class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
object = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.version_warning = None
|
||||
|
||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||
self.library_id = owner_info.library_id
|
||||
self._update_version_warning()
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbLayoutProperty<{self.object.type_id} {self.info.owner_id}:{self.property_id}>"
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
obj = self.object
|
||||
self.project._object_layout_property_changed(obj.parent, obj, self)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def value(self):
|
||||
c = self.project.db.execute(
|
||||
"""
|
||||
SELECT value
|
||||
FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
if value is None or value == self.info.default_value:
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
value = None
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"""
|
||||
SELECT count(value)
|
||||
FROM object_layout_property
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_layout_property
|
||||
SET value=?
|
||||
WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(value, self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id),
|
||||
)
|
||||
else:
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_layout_property (ui_id, object_id, child_id, owner_id, property_id, value)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value),
|
||||
)
|
||||
|
||||
c.close()
|
||||
|
||||
def _update_version_warning(self):
|
||||
target = self.object.ui.get_target(self.library_id)
|
||||
return utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.property_id)
|
@ -1,57 +0,0 @@
|
||||
#
|
||||
# Cambalache Library Info wrapper
|
||||
#
|
||||
# Copyright (C) 2022-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
from .cmb_objects_base import CmbBaseLibraryInfo
|
||||
|
||||
|
||||
class CmbLibraryInfo(CmbBaseLibraryInfo):
|
||||
third_party = GObject.Property(type=bool, default=False, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.object_types = self.__init_object_types()
|
||||
self.min_version = self.__init_min_version()
|
||||
|
||||
def __init_object_types(self):
|
||||
prefix = self.prefix
|
||||
prefix_len = len(prefix)
|
||||
retval = []
|
||||
|
||||
for row in self.project.db.execute("SELECT type_id FROM type WHERE library_id=?", (self.library_id,)):
|
||||
(type_id,) = row
|
||||
if type_id.startswith(prefix):
|
||||
# Remove Prefix from type name
|
||||
retval.append(type_id[prefix_len:])
|
||||
|
||||
return retval
|
||||
|
||||
def __init_min_version(self):
|
||||
row = self.project.db.execute(
|
||||
"SELECT MIN_VERSION(version) FROM library_version WHERE library_id=?;", (self.library_id,)
|
||||
).fetchone()
|
||||
|
||||
return row[0] if row is not None else None
|
@ -1,42 +0,0 @@
|
||||
#
|
||||
# Cambalache GListModel error item
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
from .cmb_base import CmbBase
|
||||
|
||||
|
||||
# This class is used by GListModel implementations when they do not know which item to return
|
||||
class CmbListError(CmbBase):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
return "list error"
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
return 0
|
||||
|
35
cambalache/cmb_list_store.py
Normal file
35
cambalache/cmb_list_store.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# CmbListStore - Cambalache List Store
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import io
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
|
||||
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):
|
||||
GObject.GObject.__init__(self, **kwargs)
|
||||
|
||||
data = self.project._get_table_data(self.table)
|
||||
self.set_column_types(data['types'])
|
||||
self._populate()
|
||||
|
||||
def _populate(self):
|
||||
c = self.project.conn.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>
|
@ -1,746 +0,0 @@
|
||||
#
|
||||
# Cambalache Object wrapper
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
|
||||
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):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
__gsignals__ = {
|
||||
"property-changed": (GObject.SignalFlags.RUN_FIRST, None, (CmbProperty,)),
|
||||
"layout-property-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.GObject, CmbLayoutProperty)),
|
||||
"signal-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"signal-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"signal-changed": (GObject.SignalFlags.RUN_FIRST, None, (CmbSignal,)),
|
||||
"data-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbObjectData,)),
|
||||
"data-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbObjectData,)),
|
||||
"child-reordered": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObject, int, int)),
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__properties = None
|
||||
self.__properties_dict = None
|
||||
self.__layout = None
|
||||
self.__layout_dict = None
|
||||
self.__signals = None
|
||||
self.__signals_dict = None
|
||||
self.__data = None
|
||||
self.__data_dict = None
|
||||
self.inline_property_id = None
|
||||
self.version_warning = None
|
||||
self.__is_template = False
|
||||
|
||||
self._last_known = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
self.__update_inline_property_id()
|
||||
self.__update_version_warning()
|
||||
self.ui.connect("notify", self._on_ui_notify)
|
||||
self.ui.connect("library-changed", self._on_ui_library_changed)
|
||||
|
||||
def __bool__(self):
|
||||
# Override Truth Value Testing to ensure that CmbObject objects evaluates to True even if it does not have children
|
||||
return True
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
prop = CmbProperty(
|
||||
object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info,
|
||||
)
|
||||
|
||||
# List of property
|
||||
self.__properties.append(prop)
|
||||
|
||||
# Dictionary of properties
|
||||
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:
|
||||
return
|
||||
|
||||
# parent_id is stored in the DB so its better to cache it
|
||||
parent_id = self.parent_id
|
||||
for property_name in property_info:
|
||||
info = property_info[property_name]
|
||||
|
||||
prop = CmbLayoutProperty(
|
||||
object=self,
|
||||
project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=parent_id,
|
||||
child_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info,
|
||||
)
|
||||
|
||||
self.__layout.append(prop)
|
||||
|
||||
# Dictionary of properties
|
||||
self.__layout_dict[property_name] = prop
|
||||
|
||||
def _property_changed(self, prop):
|
||||
self.emit("property-changed", prop)
|
||||
self.project._object_property_changed(self, prop)
|
||||
|
||||
def _layout_property_changed(self, prop):
|
||||
parent = self.project.get_object_by_id(self.ui_id, self.parent_id)
|
||||
self.emit("layout-property-changed", parent, prop)
|
||||
self.project._object_layout_property_changed(parent, self, prop)
|
||||
|
||||
def __add_signal_object(self, signal):
|
||||
self.__populate_signals()
|
||||
self.__signals.append(signal)
|
||||
self.__signals_dict[signal.signal_pk] = signal
|
||||
self.emit("signal-added", signal)
|
||||
self.project._object_signal_added(self, signal)
|
||||
|
||||
signal.connect("notify", self.__on_signal_notify)
|
||||
|
||||
def __on_signal_notify(self, signal, pspec):
|
||||
self.emit("signal-changed", signal)
|
||||
self.project._object_signal_changed(self, signal)
|
||||
|
||||
def __add_data_object(self, data):
|
||||
if data.get_id_string() in self.data_dict:
|
||||
return
|
||||
|
||||
self.__data.append(data)
|
||||
self.__data_dict[data.get_id_string()] = data
|
||||
self.emit("data-added", data)
|
||||
self.project._object_data_added(self, data)
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
if pspec.name == "parent-id":
|
||||
self.__populate_layout_properties()
|
||||
|
||||
self.project._object_changed(self, pspec.name)
|
||||
|
||||
def __populate_signals(self):
|
||||
if self.__signals is not None:
|
||||
return
|
||||
self.__signals = []
|
||||
self.__signals_dict = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate signals
|
||||
for row in c.execute("SELECT * FROM object_signal WHERE ui_id=? AND object_id=?;", (self.ui_id, self.object_id)):
|
||||
self.__add_signal_object(CmbSignal.from_row(self.project, *row))
|
||||
|
||||
def __populate_data(self):
|
||||
if self.__data is not None:
|
||||
return
|
||||
self.__data = []
|
||||
self.__data_dict = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate data
|
||||
for row in c.execute(
|
||||
"SELECT * FROM object_data WHERE ui_id=? AND object_id=? AND parent_id IS NULL;",
|
||||
(self.ui_id, self.object_id),
|
||||
):
|
||||
self.__add_data_object(CmbObjectData.from_row(self.project, *row))
|
||||
|
||||
def __populate_layout_properties(self):
|
||||
parent_id = self.parent_id
|
||||
|
||||
# FIXME: delete is anything is set?
|
||||
self.__layout = []
|
||||
self.__layout_dict = {}
|
||||
|
||||
if parent_id > 0:
|
||||
parent = self.project.get_object_by_id(self.ui_id, parent_id)
|
||||
for owner_id in [parent.type_id] + parent.info.hierarchy:
|
||||
self.__populate_layout_properties_from_type(f"{owner_id}LayoutChild")
|
||||
|
||||
def __populate_layout(self):
|
||||
if self.__layout is None:
|
||||
self.__populate_layout_properties()
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def parent_id(self):
|
||||
retval = self.db_get(
|
||||
"SELECT parent_id FROM object WHERE (ui_id, object_id) IS (?, ?);",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
),
|
||||
)
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@parent_id.setter
|
||||
def _set_parent_id(self, value):
|
||||
new_parent_id = value if value != 0 else None
|
||||
old_parent_id = self.parent_id if self.parent_id != 0 else None
|
||||
|
||||
if old_parent_id == new_parent_id:
|
||||
return
|
||||
|
||||
# Save old parent and position
|
||||
self._save_last_known_parent_and_position()
|
||||
|
||||
project = self.project
|
||||
ui_id = self.ui_id
|
||||
object_id = self.object_id
|
||||
|
||||
if new_parent_id is None:
|
||||
new_position = self.db_get(
|
||||
"SELECT MAX(position)+1 FROM object WHERE ui_id=? AND parent_id IS NULL",
|
||||
(ui_id, )
|
||||
)
|
||||
else:
|
||||
new_position = self.db_get(
|
||||
"SELECT MAX(position)+1 FROM object WHERE ui_id=? AND parent_id=?",
|
||||
(ui_id, new_parent_id)
|
||||
)
|
||||
|
||||
project.db.execute(
|
||||
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
||||
(new_parent_id, new_position or 0, ui_id, object_id)
|
||||
)
|
||||
|
||||
# Update children positions in old parent
|
||||
project.db.update_children_position(ui_id, old_parent_id)
|
||||
|
||||
# Update GListModel
|
||||
self._remove_from_old_parent()
|
||||
self._update_new_parent()
|
||||
|
||||
self.__populate_layout_properties()
|
||||
|
||||
@GObject.Property(type=CmbUI)
|
||||
def ui(self):
|
||||
return self.project.get_object_by_id(self.ui_id)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
def parent(self):
|
||||
return self.project.get_object_by_id(self.ui_id, self.parent_id)
|
||||
|
||||
def _add_signal(self, signal_pk, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
signal = CmbSignal(
|
||||
project=self.project,
|
||||
signal_pk=signal_pk,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
signal_id=signal_id,
|
||||
handler=handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after,
|
||||
)
|
||||
|
||||
self.__add_signal_object(signal)
|
||||
|
||||
return signal
|
||||
|
||||
def add_signal(self, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
try:
|
||||
c = self.project.db.cursor()
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_signal (ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, owner_id, signal_id, handler, detail, user_data, swap, after),
|
||||
)
|
||||
signal_pk = c.lastrowid
|
||||
c.close()
|
||||
self.project.db.commit()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error adding signal handler {owner_id}:{signal_id} {handler} to object {self.ui_id}.{{self.object_id}} {e}"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
return self._add_signal(
|
||||
signal_pk,
|
||||
owner_id,
|
||||
signal_id,
|
||||
handler,
|
||||
detail=detail,
|
||||
user_data=user_data if user_data is not None else 0,
|
||||
swap=swap,
|
||||
after=after,
|
||||
)
|
||||
|
||||
def _remove_signal(self, signal):
|
||||
self.__signals.remove(signal)
|
||||
del self.__signals_dict[signal.signal_pk]
|
||||
|
||||
self.emit("signal-removed", signal)
|
||||
self.project._object_signal_removed(self, signal)
|
||||
|
||||
def remove_signal(self, signal):
|
||||
try:
|
||||
self.project.db.execute("DELETE FROM object_signal WHERE signal_pk=?;", (signal.signal_pk,))
|
||||
self.project.db.commit()
|
||||
except Exception as e:
|
||||
handler = f"{signal.owner_id}:{signal.signal_id} {signal.handler}"
|
||||
logger.warning(f"Error removing signal handler {handler} from object {self.ui_id}.{{self.object_id}} {e}")
|
||||
return False
|
||||
else:
|
||||
self._remove_signal(signal)
|
||||
return True
|
||||
|
||||
def _add_data(self, owner_id, data_id, id, info=None):
|
||||
data = CmbObjectData(
|
||||
project=self.project,
|
||||
object=self,
|
||||
info=info,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
)
|
||||
self.__add_data_object(data)
|
||||
return data
|
||||
|
||||
def add_data(self, data_key, value=None, comment=None):
|
||||
try:
|
||||
value = str(value) if value is not None else None
|
||||
taginfo = self.info.get_data_info(data_key)
|
||||
owner_id = taginfo.owner_id
|
||||
data_id = taginfo.data_id
|
||||
id = self.project.db.object_add_data(self.ui_id, self.object_id, owner_id, data_id, value, None, comment)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding data {data_key} {e}")
|
||||
return None
|
||||
else:
|
||||
return self._add_data(owner_id, data_id, id, info=taginfo)
|
||||
|
||||
def _remove_data(self, data):
|
||||
if data.get_id_string() not in self.data_dict:
|
||||
return
|
||||
|
||||
self.__data.remove(data)
|
||||
del self.__data_dict[data.get_id_string()]
|
||||
|
||||
self.emit("data-removed", data)
|
||||
self.project._object_data_removed(self, data)
|
||||
|
||||
def remove_data(self, data):
|
||||
try:
|
||||
assert data.get_id_string() in self.data_dict
|
||||
|
||||
self.project.history_push(
|
||||
_("Remove {key} from {name}").format(key=data.info.key, name=self.display_name_type)
|
||||
)
|
||||
|
||||
self.project.db.execute(
|
||||
"DELETE FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=?;",
|
||||
(self.ui_id, self.object_id, data.owner_id, data.data_id, data.id),
|
||||
)
|
||||
self.project.db.commit()
|
||||
self.project.history_pop()
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error removing data {data}: {e}")
|
||||
return False
|
||||
else:
|
||||
self._remove_data(data)
|
||||
return True
|
||||
|
||||
def reorder_child(self, child, position):
|
||||
if child is None:
|
||||
logger.warning("child has to be a CmbObject")
|
||||
return
|
||||
|
||||
if self.ui_id != child.ui_id or self.object_id != child.parent_id:
|
||||
logger.warning(f"{child} is not children of {self}")
|
||||
return
|
||||
|
||||
old_position = child.position
|
||||
old_list_position = child.list_position
|
||||
if old_position == position:
|
||||
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)
|
||||
)
|
||||
|
||||
db = self.project.db
|
||||
|
||||
# Consider this children
|
||||
#
|
||||
# label 0
|
||||
# button 1
|
||||
# entry 2
|
||||
# switch 3
|
||||
# toggle 4
|
||||
|
||||
# Disable check so we can set position temporally to -1
|
||||
db.ignore_check_constraints = True
|
||||
db.execute("UPDATE object SET position=-1 WHERE ui_id=? AND object_id=?;", (self.ui_id, child.object_id))
|
||||
|
||||
# Make room for new position
|
||||
for select_stmt, update_stmt in [
|
||||
(
|
||||
"""
|
||||
SELECT ui_id, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=? AND position <= ? AND position > ?
|
||||
ORDER BY position ASC
|
||||
""",
|
||||
"UPDATE object SET position=position - 1 WHERE ui_id=? AND object_id=?;"
|
||||
),
|
||||
(
|
||||
"""
|
||||
SELECT ui_id, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=? AND position >= ? AND position < ?
|
||||
ORDER BY position DESC
|
||||
""",
|
||||
"UPDATE object SET position=position + 1 WHERE ui_id=? AND object_id=?;"
|
||||
),
|
||||
]:
|
||||
for row in db.execute(select_stmt, (self.ui_id, self.object_id, position, old_position)):
|
||||
db.execute(update_stmt, tuple(row))
|
||||
|
||||
# Set new position
|
||||
db.execute("UPDATE object SET position=? WHERE ui_id=? AND object_id=?;", (position, self.ui_id, child.object_id))
|
||||
|
||||
db.ignore_check_constraints = False
|
||||
|
||||
list_position = child.list_position
|
||||
|
||||
self.project._ignore_selection = True
|
||||
# Emit GListModel signals
|
||||
if position < old_position:
|
||||
self.items_changed(list_position, 0, 1)
|
||||
self.items_changed(old_list_position+1, 1, 0)
|
||||
else:
|
||||
if old_list_position != list_position:
|
||||
self.items_changed(old_list_position, 1, 0)
|
||||
self.items_changed(list_position, 0, 1)
|
||||
|
||||
self.project._ignore_selection = False
|
||||
|
||||
self.project.history_pop()
|
||||
self.emit("child-reordered", child, old_position, position)
|
||||
self.project._object_child_reordered(self, child, old_position, position)
|
||||
|
||||
def clear_properties(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
name = self.name
|
||||
name = name if name is not None else self.type_id
|
||||
self.project.history_push(_("Clear object {name} properties").format(name=name))
|
||||
|
||||
properties = []
|
||||
for row in c.execute(
|
||||
"SELECT property_id FROM object_property WHERE ui_id=? AND object_id=?;", (self.ui_id, self.object_id)
|
||||
):
|
||||
properties.append(row[0])
|
||||
|
||||
# Remove all properties from this object
|
||||
c.execute("DELETE FROM object_property WHERE ui_id=? AND object_id=?;", (self.ui_id, self.object_id))
|
||||
|
||||
self.project.history_pop()
|
||||
c.close()
|
||||
|
||||
for prop_id in properties:
|
||||
prop = self.__properties_dict[prop_id]
|
||||
prop.notify("value")
|
||||
self._property_changed(prop)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name_type(self):
|
||||
return f"{self.type_id} {self.name}" if self.name else self.type_id
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
name = self.name or ""
|
||||
type_id = self.type_id
|
||||
parent_id = self.parent_id
|
||||
|
||||
if type_id in [GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]:
|
||||
prop = self.properties_dict["label"]
|
||||
label = prop.value or ""
|
||||
display_name = f"{type_id} <i>{label}</i>"
|
||||
elif not parent_id and self.ui.template_id == self.object_id:
|
||||
# Translators: This is used for Template classes in the object tree
|
||||
display_name = _("{name} (template)").format(name=name)
|
||||
else:
|
||||
inline_prop = self.inline_property_id
|
||||
internal = self.internal
|
||||
if inline_prop:
|
||||
display_name = f"{type_id} <b>{inline_prop}</b> <i>{name}</i>"
|
||||
elif internal:
|
||||
display_name = f"{type_id} <b>{internal}</b> <i>{name}</i>"
|
||||
else:
|
||||
display_name = f"{type_id} <i>{name}</i>"
|
||||
|
||||
if self.version_warning:
|
||||
return f'<span underline="error">{display_name}</span>'
|
||||
else:
|
||||
return display_name
|
||||
|
||||
def __update_version_warning(self):
|
||||
target = self.ui.get_target(self.info.library_id)
|
||||
self.version_warning = utils.get_version_warning(target, self.info.version, self.info.deprecated_version, self.type_id)
|
||||
|
||||
def _on_ui_notify(self, obj, pspec):
|
||||
property_id = pspec.name
|
||||
|
||||
if property_id == "template-id":
|
||||
was_template = self.__is_template
|
||||
self.__is_template = obj.template_id == self.object_id
|
||||
|
||||
if was_template or self.__is_template:
|
||||
self.notify("display-name")
|
||||
self.notify("display-name-type")
|
||||
|
||||
def _on_ui_library_changed(self, ui, library_id):
|
||||
self.__update_version_warning()
|
||||
|
||||
self.__populate_properties()
|
||||
self.__populate_layout()
|
||||
|
||||
# Update properties directly, to avoid having to connect too many times to this signal
|
||||
for props in [self.__properties, self.__layout]:
|
||||
for prop in props:
|
||||
if prop.library_id == library_id:
|
||||
prop._update_version_warning()
|
||||
|
||||
# GListModel helpers
|
||||
def _save_last_known_parent_and_position(self):
|
||||
self._last_known = (self.parent, self.list_position)
|
||||
|
||||
def _update_new_parent(self):
|
||||
parent = self.parent
|
||||
position = self.list_position
|
||||
|
||||
# Emit GListModel signal to update model
|
||||
if parent:
|
||||
parent.items_changed(position, 0, 1)
|
||||
parent.notify("n-items")
|
||||
else:
|
||||
ui = self.ui
|
||||
ui.items_changed(position, 0, 1)
|
||||
ui.notify("n-items")
|
||||
|
||||
self._last_known = None
|
||||
|
||||
def _remove_from_old_parent(self):
|
||||
if self._last_known is None:
|
||||
return
|
||||
|
||||
parent, position = self._last_known
|
||||
|
||||
# Emit GListModel signal to update model
|
||||
if parent:
|
||||
parent.items_changed(position, 1, 0)
|
||||
parent.notify("n-items")
|
||||
else:
|
||||
ui = self.ui
|
||||
ui.items_changed(position, 1, 0)
|
||||
ui.notify("n-items")
|
||||
|
||||
self._last_known = None
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def list_position(self):
|
||||
ui_id = self.ui_id
|
||||
|
||||
if self.parent_id:
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT rownum-1
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=?
|
||||
)
|
||||
WHERE object_id=?;
|
||||
""",
|
||||
(ui_id, self.parent_id, self.object_id)
|
||||
)
|
||||
else:
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT rownum-1
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id IS NULL
|
||||
)
|
||||
WHERE object_id=?;
|
||||
""",
|
||||
(ui_id, self.object_id)
|
||||
)
|
||||
|
||||
return retval
|
||||
|
||||
# GListModel iface
|
||||
def do_get_item(self, position):
|
||||
ui_id = self.ui_id
|
||||
|
||||
# This query should use index object_ui_id_parent_id_position_idx
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT object_id
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id=?
|
||||
)
|
||||
WHERE rownum=?;
|
||||
""",
|
||||
(ui_id, self.object_id, position+1)
|
||||
)
|
||||
if retval is not None:
|
||||
return self.project.get_object_by_id(ui_id, retval)
|
||||
|
||||
# This should not happen
|
||||
return CmbListError()
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBaseObject
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
if self.project is None:
|
||||
return 0
|
||||
|
||||
retval = self.db_get("SELECT COUNT(object_id) FROM object WHERE ui_id=? AND parent_id=?;", (self.ui_id, self.object_id))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
@ -1,213 +0,0 @@
|
||||
#
|
||||
# Cambalache Object Data wrapper
|
||||
#
|
||||
# Copyright (C) 2022-2023 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
from .cmb_objects_base import CmbBaseObjectData
|
||||
from .cmb_type_info import CmbTypeDataInfo
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbObjectData(CmbBaseObjectData):
|
||||
__gsignals__ = {
|
||||
"data-added": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObjectData,)),
|
||||
"data-removed": (GObject.SignalFlags.RUN_FIRST, None, (CmbBaseObjectData,)),
|
||||
"arg-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
parent = GObject.Property(type=CmbBaseObjectData, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
object = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
||||
info = GObject.Property(type=CmbTypeDataInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.args = []
|
||||
self.children = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
if self.info is None:
|
||||
type_info = self.project.type_info.get(self.owner_id, None)
|
||||
if type_info:
|
||||
self.info = type_info.find_data_info(self.data_id)
|
||||
|
||||
if self.object is None:
|
||||
self.object = self.project.get_object_by_id(self.ui_id, self.object_id)
|
||||
|
||||
self.__populate_children()
|
||||
self.connect("notify", self._on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbObjectData<{self.owner_id}:{self.info.key}> obj={self.ui_id}:{self.object_id} data={self.data_id}:{self.id}"
|
||||
|
||||
def get_id_string(self):
|
||||
return f"{self.owner_id}.{self.id}"
|
||||
|
||||
def get_arg(self, key):
|
||||
c = self.project.db.execute(
|
||||
"SELECT value FROM object_data_arg WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def _on_notify(self, obj, pspec):
|
||||
if pspec.name in ["value"]:
|
||||
self.project._object_data_changed(self)
|
||||
|
||||
def _arg_changed(self, key):
|
||||
self.emit("arg-changed", key)
|
||||
self.project._object_data_arg_changed(self, key)
|
||||
|
||||
def set_arg(self, key, value):
|
||||
# Prevent potential infinite recursion
|
||||
val = self.get_arg(key)
|
||||
if val == value:
|
||||
return
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
try:
|
||||
if value is None:
|
||||
c.execute(
|
||||
"""
|
||||
DELETE FROM object_data_arg
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"""
|
||||
SELECT count(value) FROM object_data_arg
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_data_arg SET value=?
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND data_id=? AND id=? AND key=?;
|
||||
""",
|
||||
(str(value), self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key),
|
||||
)
|
||||
else:
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_data_arg (ui_id, object_id, owner_id, data_id, id, key, value)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.data_id, self.id, key, str(value)),
|
||||
)
|
||||
|
||||
self._arg_changed(key)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error setting arg {key}={value}: {e}")
|
||||
|
||||
c.close()
|
||||
|
||||
def __add_child(self, child):
|
||||
if child in self.children:
|
||||
return
|
||||
|
||||
self.children.append(child)
|
||||
self.object.data_dict[child.get_id_string()] = child
|
||||
self.emit("data-added", child)
|
||||
self.project._object_data_data_added(self, child)
|
||||
|
||||
def _remove_child(self, child):
|
||||
self.children.remove(child)
|
||||
del self.object.data_dict[child.get_id_string()]
|
||||
self.emit("data-removed", child)
|
||||
self.project._object_data_data_removed(self, child)
|
||||
|
||||
def _add_child(self, owner_id, data_id, id, info=None):
|
||||
new_data = CmbObjectData(
|
||||
project=self.project,
|
||||
object=self.object,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
data_id=data_id,
|
||||
id=id,
|
||||
parent=self,
|
||||
info=info,
|
||||
)
|
||||
self.__add_child(new_data)
|
||||
return new_data
|
||||
|
||||
def __populate_children(self):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Populate children
|
||||
for row in c.execute(
|
||||
"SELECT * FROM object_data WHERE ui_id=? AND object_id=? AND owner_id=? AND parent_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.id),
|
||||
):
|
||||
obj = CmbObjectData.from_row(self.project, *row)
|
||||
obj.parent = self
|
||||
self.__add_child(obj)
|
||||
|
||||
def add_data(self, data_key, value=None, comment=None):
|
||||
try:
|
||||
value = str(value) if value is not None else None
|
||||
taginfo = self.info.children.get(data_key)
|
||||
owner_id = taginfo.owner_id
|
||||
data_id = taginfo.data_id
|
||||
id = self.project.db.object_add_data(self.ui_id, self.object_id, owner_id, data_id, value, self.id, comment)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error adding child data {data_key}: {e}")
|
||||
return None
|
||||
else:
|
||||
return self._add_child(owner_id, data_id, id, taginfo)
|
||||
|
||||
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
|
||||
else:
|
||||
self._remove_child(data)
|
||||
return True
|
@ -1,293 +0,0 @@
|
||||
#
|
||||
# CmbObjectDataEditor - Cambalache Object Data Editor
|
||||
#
|
||||
# Copyright (C) 2022-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_type_info import CmbTypeDataInfo
|
||||
from .cmb_object_data import CmbObjectData
|
||||
from .control import cmb_create_editor
|
||||
from cambalache import _
|
||||
|
||||
|
||||
# Everyone knows that debugging is twice as hard as writing a program in the first place.
|
||||
# So if you’re as clever as you can be when you write it, how will you ever debug it?
|
||||
# -- Brian Kernighan, 1974
|
||||
#
|
||||
# TODO: rewrite this!
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_object_data_editor.ui")
|
||||
class CmbObjectDataEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbObjectDataEditor"
|
||||
|
||||
info = GObject.Property(type=CmbTypeDataInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
label = Gtk.Template.Child()
|
||||
top_box = Gtk.Template.Child()
|
||||
add_child = Gtk.Template.Child()
|
||||
add_only_child = Gtk.Template.Child()
|
||||
remove_button = Gtk.Template.Child()
|
||||
grid = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__data = None
|
||||
|
||||
self.__size_group = None
|
||||
self.__value_editor = None
|
||||
self.__arg_editors = {}
|
||||
self.__editors = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__update_view()
|
||||
|
||||
@Gtk.Template.Callback("on_add_only_child_clicked")
|
||||
def __on_add_only_child_clicked(self, button):
|
||||
info = self.data.info if self.data else self.info
|
||||
|
||||
# Do not add a menu if there is only one child type
|
||||
child_info = list(info.children.values())[0]
|
||||
self.__on_child_button_clicked(button, child_info)
|
||||
|
||||
@Gtk.Template.Callback("on_remove_clicked")
|
||||
def __on_remove_clicked(self, button):
|
||||
if self.info:
|
||||
self.object.remove_data(self.__data)
|
||||
elif self.__data:
|
||||
self.__data.parent.remove_data(self.__data)
|
||||
|
||||
@GObject.Property(type=GObject.Object)
|
||||
def object(self):
|
||||
return self.__object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, value):
|
||||
if self.__object:
|
||||
self.__object.disconnect_by_func(self.__on_data_added)
|
||||
self.__object.disconnect_by_func(self.__on_data_removed)
|
||||
|
||||
self.__object = value
|
||||
|
||||
if self.__object:
|
||||
self.__object.connect("data-added", self.__on_data_added)
|
||||
self.__object.connect("data-removed", self.__on_data_removed)
|
||||
|
||||
@GObject.Property(type=CmbObjectData)
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
@data.setter
|
||||
def _set_data(self, value):
|
||||
if self.__data:
|
||||
self.__data.disconnect_by_func(self.__on_data_data_added)
|
||||
self.__data.disconnect_by_func(self.__on_data_data_removed)
|
||||
self.__data.disconnect_by_func(self.__on_data_arg_changed)
|
||||
|
||||
# Clear old editors
|
||||
for editor in self.__editors:
|
||||
self.grid.remove(editor)
|
||||
self.__editors = []
|
||||
|
||||
self.__data = value
|
||||
|
||||
if self.__data:
|
||||
self.__data.connect("data-added", self.__on_data_data_added)
|
||||
self.__data.connect("data-removed", self.__on_data_data_removed)
|
||||
self.__data.connect("arg-changed", self.__on_data_arg_changed)
|
||||
|
||||
def __update_arg(self, key):
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
editor = self.__arg_editors.get(key, None)
|
||||
if editor:
|
||||
val = self.data.get_arg(key)
|
||||
|
||||
# Only update if there is a change
|
||||
if val != editor.cmb_value:
|
||||
editor.cmb_value = val
|
||||
|
||||
def __on_data_data_added(self, parent, data):
|
||||
self.__add_data_editor(data)
|
||||
|
||||
def __on_data_data_removed(self, parent, data):
|
||||
self.__remove_data_editor(data)
|
||||
|
||||
def __on_data_arg_changed(self, data, key):
|
||||
self.__update_arg(key)
|
||||
|
||||
def __on_data_added(self, obj, data):
|
||||
if self.info and self.data is None and self.info == data.info:
|
||||
self.data = data
|
||||
self.__update_view()
|
||||
|
||||
def __on_data_removed(self, obj, data):
|
||||
self.__remove_data_editor(data)
|
||||
|
||||
def __ensure_object_data(self, history_message):
|
||||
if self.data:
|
||||
return False
|
||||
|
||||
self.object.project.history_push(history_message)
|
||||
self.data = self.object.add_data(self.info.key)
|
||||
|
||||
if self.__value_editor:
|
||||
GObject.Object.bind_property(
|
||||
self.data,
|
||||
"value",
|
||||
self.__value_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
return True
|
||||
|
||||
def __on_child_button_clicked(self, button, info):
|
||||
msg = _("Add {key}").format(key=info.key)
|
||||
history_pushed = self.__ensure_object_data(msg)
|
||||
self.data.add_data(info.key)
|
||||
if history_pushed:
|
||||
self.object.project.history_pop()
|
||||
|
||||
def __context_menu_new(self, info):
|
||||
popover = Gtk.Popover(position=Gtk.PositionType.BOTTOM)
|
||||
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, visible=True, spacing=4, border_width=4)
|
||||
|
||||
# Add children types
|
||||
for child in info.children:
|
||||
child_info = info.children[child]
|
||||
button = Gtk.ModelButton(label=_("Add {key}").format(key=child_info.key), visible=True)
|
||||
button.connect("clicked", self.__on_child_button_clicked, child_info)
|
||||
box.append(button)
|
||||
|
||||
popover.set_child(box)
|
||||
|
||||
return popover
|
||||
|
||||
def __on_arg_notify(self, obj, pspec, info):
|
||||
msg = _("Set {key} to {value}").format(key=info.key, value=obj.cmb_value)
|
||||
history_pushed = self.__ensure_object_data(msg)
|
||||
self.data.set_arg(info.key, obj.cmb_value)
|
||||
if history_pushed:
|
||||
self.object.project.history_pop()
|
||||
|
||||
def __add(self, editor, label=None):
|
||||
neditors = len(self.__editors)
|
||||
|
||||
if label:
|
||||
self.grid.attach(label, 0, neditors, 1, 1)
|
||||
self.__size_group.add_widget(label)
|
||||
|
||||
self.grid.attach(editor, 1, neditors, 1, 1)
|
||||
|
||||
self.__editors.append(editor)
|
||||
|
||||
def __add_data_editor(self, data):
|
||||
editor = CmbObjectDataEditor(visible=True, hexpand=True, margin_start=16, object=self.object, data=data)
|
||||
self.__add(editor)
|
||||
|
||||
def __remove_data_editor(self, data):
|
||||
if self.__data == data:
|
||||
self.data = None
|
||||
return
|
||||
|
||||
for editor in self.__editors:
|
||||
if data == editor.data:
|
||||
self.grid.remove(editor)
|
||||
self.__editors.remove(editor)
|
||||
break
|
||||
|
||||
def __update_view(self):
|
||||
if self.data is None and self.info is None:
|
||||
return
|
||||
|
||||
info = self.data.info if self.data else self.info
|
||||
project = self.__object.project
|
||||
|
||||
if info is None:
|
||||
return
|
||||
|
||||
nchildren = len(info.children)
|
||||
|
||||
self.remove_button.props.tooltip_text = _("Remove {key}").format(key=info.key)
|
||||
|
||||
# Add a menu if there is more than one child type
|
||||
if nchildren > 1:
|
||||
self.add_child.props.popover = self.__context_menu_new(info)
|
||||
self.add_child.set_visible(True)
|
||||
elif nchildren:
|
||||
key = list(info.children.keys())[0]
|
||||
self.add_only_child.props.tooltip_text = _("Add {key}").format(key=key)
|
||||
self.add_only_child.set_visible(True)
|
||||
|
||||
# Item name
|
||||
self.label.props.label = info.key
|
||||
|
||||
self.__size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
|
||||
self.__size_group.add_widget(self.label)
|
||||
|
||||
# Value
|
||||
if info.type_id:
|
||||
editor = cmb_create_editor(project, info.type_id, data=self.data, parent=self.object)
|
||||
self.__value_editor = editor
|
||||
|
||||
if self.data:
|
||||
GObject.Object.bind_property(
|
||||
self.data,
|
||||
"value",
|
||||
self.__value_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
self.top_box.append(editor)
|
||||
|
||||
nargs = len(info.args)
|
||||
|
||||
# Arguments
|
||||
for arg in info.args:
|
||||
arg_info = info.args[arg]
|
||||
|
||||
editor = cmb_create_editor(project, arg_info.type_id, parent=self.object)
|
||||
self.__arg_editors[arg_info.key] = editor
|
||||
|
||||
# Initialize value
|
||||
self.__update_arg(arg_info.key)
|
||||
|
||||
# Listen for editor value changes and update argument
|
||||
editor.connect("notify::cmb-value", self.__on_arg_notify, arg_info)
|
||||
|
||||
# Special case items with one argument and no value (like styles)
|
||||
if nargs == 1 and not info.type_id:
|
||||
self.label.props.label = f"{info.key} {arg_info.key}"
|
||||
self.top_box.append(editor)
|
||||
else:
|
||||
label = Gtk.Label(visible=True, label=arg_info.key, xalign=1)
|
||||
self.__add(editor, label)
|
||||
|
||||
# Current children
|
||||
if self.data:
|
||||
for child in self.data.children:
|
||||
self.__add_data_editor(child)
|
@ -1,76 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_object_data_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbObjectDataEditor" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="top_box">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<signal name="clicked" handler="on_remove_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="borderless"/>
|
||||
</style>
|
||||
<style>
|
||||
<class name="hidden"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="add_child">
|
||||
<property name="halign">end</property>
|
||||
<property name="visible">0</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_only_child">
|
||||
<property name="focusable">1</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="visible">0</property>
|
||||
<signal name="clicked" handler="on_add_only_child_clicked"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="borderless"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="grid">
|
||||
<property name="column-spacing">4</property>
|
||||
<property name="row-spacing">4</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,318 +1,342 @@
|
||||
#
|
||||
# CmbObjectEditor - Cambalache Object Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# 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
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
import io
|
||||
import gi
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_object_data_editor import CmbObjectDataEditor
|
||||
from .control import CmbEntry, CmbChildTypeComboBox, cmb_create_editor
|
||||
from .cmb_property_label import CmbPropertyLabel
|
||||
from cambalache import _
|
||||
from .constants import EXTERNAL_TYPE
|
||||
from . import utils
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GLib, GObject, Gtk
|
||||
|
||||
from .cmb_objects import CmbObject, CmbTypeInfo
|
||||
|
||||
|
||||
class CmbEntry(Gtk.Entry):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::text', self._on_text_notify)
|
||||
|
||||
def _on_text_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_value(self, value):
|
||||
self.props.text = value if value is not None else ''
|
||||
|
||||
|
||||
class CmbSpinButton(Gtk.SpinButton):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::value', self._on_text_notify)
|
||||
self.props.halign=Gtk.Align.START
|
||||
self.props.numeric=True
|
||||
self.props.width_chars=8
|
||||
|
||||
def _on_text_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.property(type=str)
|
||||
def cmb_value(self):
|
||||
# FIXME: value should always use C locale
|
||||
if self.props.digits == 0:
|
||||
return str(int(self.props.value))
|
||||
else:
|
||||
# NOTE: round() to avoid setting numbers like 0.7000000000000001
|
||||
return str(round(self.props.value, 15))
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_value(self, value):
|
||||
self.props.value = float(value)
|
||||
|
||||
|
||||
class CmbSwitch(Gtk.Switch):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('notify::active', self._on_notify)
|
||||
self.props.halign=Gtk.Align.START
|
||||
|
||||
def _on_notify(self, obj, pspec):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.property(type=str)
|
||||
def cmb_value(self):
|
||||
return 'True' if self.props.active else 'False'
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_value(self, value):
|
||||
if value is not None:
|
||||
val = value.lower()
|
||||
self.props.active = True if val == 'true' or val == 'yes' else False
|
||||
else:
|
||||
self.props.active = False
|
||||
|
||||
|
||||
class CmbComboBox(Gtk.ComboBox):
|
||||
text_column = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.connect('changed', self._on_changed)
|
||||
|
||||
renderer_text = Gtk.CellRendererText()
|
||||
self.pack_start(renderer_text, True)
|
||||
self.add_attribute(renderer_text, "text", self.text_column)
|
||||
|
||||
def _on_changed(self, obj):
|
||||
self.notify('cmb-value')
|
||||
|
||||
@GObject.property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.active_id
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_value(self, value):
|
||||
self.props.active_id = value
|
||||
|
||||
|
||||
class CmbFlagsEntry(Gtk.Entry):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
id_column = GObject.Property(type=int, default = 0, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
text_column = GObject.Property(type=int, default = 1, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
value_column = GObject.Property(type=int, default = 2, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY )
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.editable = False
|
||||
self.props.secondary_icon_name = 'document-edit-symbolic'
|
||||
|
||||
self.connect('icon-release', self._on_icon_release)
|
||||
|
||||
self._init_popover()
|
||||
|
||||
def _init_popover(self):
|
||||
self.flags = {}
|
||||
self._checks = {}
|
||||
self._popover = Gtk.Popover(relative_to=self)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
box.pack_start(Gtk.Label(label=f'<b>{self.info.type_id}</b>',
|
||||
use_markup=True),
|
||||
False, True, 4)
|
||||
box.pack_start(Gtk.Separator(), False, False, 0)
|
||||
sw = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER,
|
||||
propagate_natural_height=True,
|
||||
max_content_height=360)
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
sw.add(vbox)
|
||||
box.pack_start(sw, True, True, 0)
|
||||
|
||||
for row in self.info.flags:
|
||||
flag = row[self.text_column]
|
||||
flag_id = row[self.id_column]
|
||||
|
||||
check = Gtk.CheckButton(label=flag)
|
||||
check.connect('toggled', self._on_check_toggled, flag_id)
|
||||
vbox.pack_start(check, False, True, 4)
|
||||
self._checks[flag_id] = check
|
||||
|
||||
box.show_all()
|
||||
self._popover.add(box)
|
||||
|
||||
def _on_check_toggled(self, check, flag_id):
|
||||
self.flags[flag_id] = check.props.active
|
||||
self.props.text = self._to_string()
|
||||
self.notify('cmb-value')
|
||||
|
||||
def _on_icon_release(self, obj, pos, event):
|
||||
self._popover.popup()
|
||||
|
||||
def _to_string(self):
|
||||
retval = None
|
||||
for row in self.info.flags:
|
||||
flag_id = row[self.id_column]
|
||||
if self.flags.get(flag_id, False):
|
||||
retval = flag_id if retval is None else f'{retval} | {flag_id}'
|
||||
|
||||
return retval if retval is not None else ''
|
||||
|
||||
@GObject.property(type=str)
|
||||
def cmb_value(self):
|
||||
return self.props.text if self.props.text != '' else None
|
||||
|
||||
@cmb_value.setter
|
||||
def _set_value(self, value):
|
||||
self.props.text = value if value is not None else ''
|
||||
|
||||
self.flags = {}
|
||||
for check in self._checks:
|
||||
self._checks[check].props.active = False
|
||||
|
||||
if value:
|
||||
tokens = [t.strip() for t in value.split('|')]
|
||||
|
||||
for row in self.info.flags:
|
||||
flag = row[self.text_column]
|
||||
flag_id = row[self.id_column]
|
||||
|
||||
check = self._checks.get(flag_id, None)
|
||||
if check:
|
||||
val = flag_id in tokens
|
||||
check.props.active = val
|
||||
self.flags[flag_id] = val
|
||||
|
||||
|
||||
class CmbObjectEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbObjectEditor"
|
||||
__gtype_name__ = 'CmbObjectEditor'
|
||||
|
||||
layout = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, default=False)
|
||||
layout = GObject.Property(type=bool,
|
||||
flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
|
||||
default=False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__id_label = None
|
||||
self.__template_switch = None
|
||||
self.__bindings = []
|
||||
self._object = None
|
||||
|
||||
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):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=6)
|
||||
box.add(Gtk.Label(label='Object Id:'))
|
||||
|
||||
def __create_id_editor(self):
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
|
||||
|
||||
# Label
|
||||
self.__id_label = Gtk.Label(label=_("Object Id"), halign=Gtk.Align.START)
|
||||
|
||||
# Id/Class entry
|
||||
entry = CmbEntry()
|
||||
self.bind_property(
|
||||
self.__object,
|
||||
"name",
|
||||
entry,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
grid.attach(self.__id_label, 0, 0, 1, 1)
|
||||
grid.attach(entry, 1, 0, 1, 1)
|
||||
|
||||
# Template check
|
||||
if self.__object and self.__object.parent_id == 0:
|
||||
is_template = self.__object.object_id == self.__object.ui.template_id
|
||||
tooltip_text = _("Switch between object and template")
|
||||
derivable = self.__object.info.derivable
|
||||
|
||||
if not derivable:
|
||||
tooltip_text = _("{type} is not derivable.").format(type=self.__object.info.type_id)
|
||||
|
||||
label = Gtk.Label(label=_("Template"), halign=Gtk.Align.START, tooltip_text=tooltip_text, sensitive=derivable)
|
||||
self.__template_switch = Gtk.Switch(
|
||||
active=is_template, halign=Gtk.Align.START, tooltip_text=tooltip_text, sensitive=derivable
|
||||
)
|
||||
|
||||
self.__template_switch.connect("notify::active", self.__on_template_switch_notify)
|
||||
self.__update_template_label()
|
||||
|
||||
grid.attach(label, 0, 1, 1, 1)
|
||||
grid.attach(self.__template_switch, 1, 1, 1, 1)
|
||||
|
||||
return grid
|
||||
|
||||
def __on_shortcut_button_clicked(self, button, type_id):
|
||||
obj = self.__object
|
||||
obj.project.add_object(obj.ui_id, type_id, parent_id=obj.object_id)
|
||||
|
||||
def __create_child_shortcuts(self, info):
|
||||
box = Gtk.FlowBox(visible=True, hexpand=True, selection_mode=Gtk.SelectionMode.NONE)
|
||||
|
||||
label = Gtk.Label(label=_("Add"), xalign=0, visible=True)
|
||||
box.append(label)
|
||||
|
||||
for type_id in info.child_type_shortcuts:
|
||||
button = Gtk.Button(label=type_id, visible=True)
|
||||
button.connect("clicked", self.__on_shortcut_button_clicked, type_id)
|
||||
box.append(button)
|
||||
GObject.Object.bind_property(self._object, 'name',
|
||||
entry, 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
|
||||
box.pack_start(entry, True, True, 0)
|
||||
return box
|
||||
|
||||
def __update_template_label(self):
|
||||
istmpl = self.__object.ui.template_id == self.__object.object_id
|
||||
self.__id_label.props.label = _("Type Name") if istmpl else _("Object Id")
|
||||
|
||||
def __on_template_switch_notify(self, switch, pspec):
|
||||
self.__object.ui.template_id = self.__object.object_id if switch.props.active else 0
|
||||
self.__update_template_label()
|
||||
|
||||
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 __create_child_type_editor(self):
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||
|
||||
box.append(Gtk.Label(label=_("Child Type"), width_chars=8))
|
||||
|
||||
combo = CmbChildTypeComboBox(object=self.__object)
|
||||
|
||||
self.bind_property(
|
||||
self.__object,
|
||||
"type",
|
||||
combo,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
box.append(combo)
|
||||
return box
|
||||
|
||||
def __update_view(self):
|
||||
for child in utils.widget_get_children(self):
|
||||
def _update_view(self):
|
||||
for child in self.get_children():
|
||||
self.remove(child)
|
||||
|
||||
if self.__object is None:
|
||||
if self._object is None:
|
||||
return
|
||||
|
||||
obj = self.__object
|
||||
parent = obj.parent
|
||||
is_builtin = obj.info.is_builtin
|
||||
# ID
|
||||
if not self.layout:
|
||||
self.add(self._create_id_editor())
|
||||
|
||||
if self.layout:
|
||||
if parent is None or is_builtin:
|
||||
return
|
||||
owner_id = None
|
||||
grid = None
|
||||
i = 0
|
||||
|
||||
# Child Type input
|
||||
if parent.info.has_child_types():
|
||||
self.append(self.__create_child_type_editor())
|
||||
else:
|
||||
# ID
|
||||
self.append(self.__create_id_editor())
|
||||
# Properties
|
||||
properties = self._object.layout if self.layout else self._object.properties
|
||||
for prop in properties:
|
||||
if owner_id != prop.owner_id:
|
||||
owner_id = prop.owner_id
|
||||
expander = Gtk.Expander(label=f'<b>{owner_id}</b>',
|
||||
use_markup=True,
|
||||
expanded=True)
|
||||
grid = Gtk.Grid(hexpand=True,
|
||||
margin_start=16,
|
||||
row_spacing=4,
|
||||
column_spacing=4)
|
||||
expander.add(grid)
|
||||
self.add(expander)
|
||||
i = 0
|
||||
|
||||
if obj.type_id == EXTERNAL_TYPE:
|
||||
label = Gtk.Label(
|
||||
label=_(
|
||||
"This object will not be exported, it is only used to make a reference to it. \
|
||||
It has to be exposed by your application with GtkBuilder expose_object method."
|
||||
),
|
||||
halign=Gtk.Align.START,
|
||||
margin_top=8,
|
||||
xalign=0,
|
||||
wrap=True,
|
||||
)
|
||||
self.append(label)
|
||||
self.show()
|
||||
return
|
||||
label = Gtk.Label(label=prop.property_id,
|
||||
xalign=0)
|
||||
editor = self._create_editor_for_property(prop)
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
info = parent.info if self.layout and parent else obj.info
|
||||
for owner_id in [info.type_id] + info.hierarchy:
|
||||
if self.layout:
|
||||
owner_id = f"{owner_id}LayoutChild"
|
||||
self.show_all()
|
||||
|
||||
info = obj.project.type_info.get(owner_id, None)
|
||||
|
||||
if info is None:
|
||||
continue
|
||||
|
||||
# Editor count
|
||||
i = 0
|
||||
|
||||
# Grid for all properties and custom data editors
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
|
||||
|
||||
# Add shortcuts
|
||||
if not self.layout and len(info.child_type_shortcuts):
|
||||
shortcuts = self.__create_child_shortcuts(info)
|
||||
shortcuts.props.margin_start = 14
|
||||
grid.attach(shortcuts, 0, i, 2, 1)
|
||||
i += 1
|
||||
|
||||
# Properties
|
||||
properties = obj.layout_dict if self.layout else obj.properties_dict
|
||||
for property_id in info.properties:
|
||||
prop = properties.get(property_id, None)
|
||||
|
||||
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(
|
||||
prop,
|
||||
"value",
|
||||
editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
if self.layout:
|
||||
label = CmbPropertyLabel(layout_prop=prop)
|
||||
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
|
||||
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
for data_key in info.data:
|
||||
data = None
|
||||
|
||||
# Find data
|
||||
for d in obj.data:
|
||||
if d.info.key == data_key:
|
||||
data = d
|
||||
break
|
||||
|
||||
editor = CmbObjectDataEditor(
|
||||
visible=True,
|
||||
hexpand=True,
|
||||
object=obj,
|
||||
data=data,
|
||||
info=info.data[data_key],
|
||||
)
|
||||
|
||||
grid.attach(editor, 0, i, 2, 1)
|
||||
i += 1
|
||||
|
||||
# Continue if class had no editors to add
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
# Create expander/revealer to pack editor grid
|
||||
expander = Gtk.Expander(label=f"<b>{owner_id}</b>", use_markup=True, expanded=True)
|
||||
revealer = Gtk.Revealer(reveal_child=True)
|
||||
expander.connect("notify::expanded", self.__on_expander_expanded, revealer)
|
||||
revealer.set_child(grid)
|
||||
self.append(expander)
|
||||
self.append(revealer)
|
||||
|
||||
self.show()
|
||||
|
||||
def __on_object_ui_notify(self, obj, pspec):
|
||||
if pspec.name == "template-id" and self.__template_switch:
|
||||
self.__template_switch.set_active(obj.props.template_id != 0)
|
||||
|
||||
def __on_object_notify(self, obj, pspec):
|
||||
if pspec.name == "parent-id":
|
||||
self.__update_view()
|
||||
|
||||
@GObject.Property(type=CmbObject)
|
||||
@GObject.property(type=CmbObject)
|
||||
def object(self):
|
||||
return self.__object
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if obj == self.__object:
|
||||
return
|
||||
self._object = obj
|
||||
self._update_view()
|
||||
|
||||
if self.__object:
|
||||
self.__object.disconnect_by_func(self.__on_object_notify)
|
||||
self.__object.ui.disconnect_by_func(self.__on_object_ui_notify)
|
||||
def _get_min_max_for_type(self, type_id):
|
||||
if type_id == 'gchar':
|
||||
return (GLib.MININT8, GLib.MAXINT8)
|
||||
elif type_id == 'guchar':
|
||||
return (0, GLib.MAXUINT8)
|
||||
elif type_id == 'gint':
|
||||
return (GLib.MININT, GLib.MAXINT)
|
||||
elif type_id == 'guint':
|
||||
return (0, GLib.MAXUINT)
|
||||
elif type_id == 'glong':
|
||||
return (GLib.MINLONG, GLib.MAXLONG)
|
||||
elif type_id == 'gulong':
|
||||
return (0, GLib.MAXULONG)
|
||||
elif type_id == 'gint64':
|
||||
return (GLib.MININT64, GLib.MAXINT64)
|
||||
elif type_id == 'guint64':
|
||||
return (0, GLib.MAXUINT64)
|
||||
elif type_id == 'gfloat':
|
||||
return (GLib.MINFLOAT, GLib.MAXFLOAT)
|
||||
elif type_id == 'gdouble':
|
||||
return (GLib.MINDOUBLE, GLib.MAXDOUBLE)
|
||||
|
||||
for binding in self.__bindings:
|
||||
binding.unbind()
|
||||
def _create_editor_for_property(self, prop):
|
||||
editor = None
|
||||
|
||||
self.__bindings = []
|
||||
if prop.info is not None:
|
||||
info = prop.info
|
||||
type_id = info.type_id
|
||||
tinfo = self._object.project._type_info.get(type_id, None)
|
||||
|
||||
self.__object = obj
|
||||
if type_id == 'gboolean':
|
||||
editor = CmbSwitch()
|
||||
elif type_id == 'gchar' or type_id == 'guchar' or \
|
||||
type_id == 'gint' or type_id == 'guint' or \
|
||||
type_id == 'glong' or type_id == 'gulong' or \
|
||||
type_id == 'gint64' or type_id == 'guint64'or \
|
||||
type_id == 'gfloat' or type_id == 'gdouble':
|
||||
|
||||
if obj:
|
||||
obj.connect("notify", self.__on_object_notify)
|
||||
obj.ui.connect("notify", self.__on_object_ui_notify)
|
||||
digits = 0
|
||||
step_increment = 1
|
||||
minimum, maximum = self._get_min_max_for_type(type_id)
|
||||
if info.minimum is not None:
|
||||
minimum = info.minimum
|
||||
if info.maximum is not None:
|
||||
maximum = info.maximum
|
||||
|
||||
self.__update_view()
|
||||
if type_id == 'gfloat' or type_id == 'gdouble':
|
||||
digits = 4
|
||||
step_increment = 0.1
|
||||
|
||||
adjustment = Gtk.Adjustment(lower=float(minimum),
|
||||
upper=float(maximum),
|
||||
step_increment=step_increment,
|
||||
page_increment=10)
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbObjectEditor, "CmbObjectEditor")
|
||||
editor = CmbSpinButton(digits=digits,
|
||||
adjustment=adjustment)
|
||||
elif tinfo:
|
||||
if tinfo.parent_id == 'enum':
|
||||
editor = CmbComboBox(model=tinfo.enum,
|
||||
id_column=0,
|
||||
text_column=1)
|
||||
elif tinfo.parent_id == 'flags':
|
||||
editor = CmbFlagsEntry(info=tinfo)
|
||||
|
||||
if editor is None:
|
||||
editor = CmbEntry(hexpand=True,
|
||||
placeholder_text=f'<{type_id}>')
|
||||
|
||||
GObject.Object.bind_property(prop, 'value',
|
||||
editor, 'cmb-value',
|
||||
GObject.BindingFlags.SYNC_CREATE |
|
||||
GObject.BindingFlags.BIDIRECTIONAL)
|
||||
return editor
|
||||
|
275
cambalache/cmb_objects.py
Normal file
275
cambalache/cmb_objects.py
Normal file
@ -0,0 +1,275 @@
|
||||
#
|
||||
# Cambalache Object wrappers
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_objects_base import *
|
||||
|
||||
|
||||
class CmbUI(CmbBaseUI):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def template_id(self):
|
||||
retval = self.db_get('SELECT template_id FROM ui WHERE (ui_id) IS (?);',
|
||||
(self.ui_id, ))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@template_id.setter
|
||||
def _set_template_id(self, value):
|
||||
self.db_set('UPDATE ui SET template_id=? WHERE (ui_id) IS (?);',
|
||||
(self.ui_id, ), value if value != 0 else None)
|
||||
|
||||
|
||||
class CmbProperty(CmbBaseProperty):
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._init = True
|
||||
super().__init__(**kwargs)
|
||||
self._init = False
|
||||
|
||||
@GObject.property(type=str)
|
||||
def value(self):
|
||||
c = self.project.conn.execute("SELECT value FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
c = self.project.conn.cursor()
|
||||
|
||||
if value is None or value == self.info.default_value:
|
||||
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:
|
||||
c.execute("SELECT count(ui_id) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id))
|
||||
count = c.fetchone()[0]
|
||||
|
||||
if count > 0:
|
||||
c.execute("UPDATE object_property SET value=? WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(value, self.ui_id, self.object_id, self.owner_id, self.property_id))
|
||||
else:
|
||||
c.execute("INSERT INTO object_property (ui_id, object_id, owner_id, property_id, value) VALUES (?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id, value))
|
||||
|
||||
if self._init == False:
|
||||
self.project._object_property_changed(self.ui_id, self.object_id, self.property_id)
|
||||
|
||||
c.close()
|
||||
|
||||
|
||||
class CmbLayoutProperty(CmbBaseLayoutProperty):
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._init = True
|
||||
super().__init__(**kwargs)
|
||||
self._init = False
|
||||
|
||||
@GObject.property(type=str)
|
||||
def value(self):
|
||||
c = self.project.conn.execute("SELECT value FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id,
|
||||
self.object_id,
|
||||
self.child_id,
|
||||
self.owner_id,
|
||||
self.property_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
c = self.project.conn.cursor()
|
||||
|
||||
if value is None or value == self.info.default_value:
|
||||
c.execute("DELETE FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
else:
|
||||
c.execute("SELECT count(ui_id) FROM object_layout_property WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
count = c.fetchone()[0]
|
||||
|
||||
if count > 0:
|
||||
c.execute("UPDATE object_layout_property SET value=? WHERE ui_id=? AND object_id=? AND child_id=? AND owner_id=? AND property_id=?;",
|
||||
(value, self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id))
|
||||
else:
|
||||
c.execute("INSERT INTO object_layout_property (ui_id, object_id, child_id, owner_id, property_id, value) VALUES (?, ?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, self.child_id, self.owner_id, self.property_id, value))
|
||||
|
||||
if self._init == False:
|
||||
self.project._object_layout_property_changed(self.ui_id,
|
||||
self.object_id,
|
||||
self.child_id,
|
||||
self.property_id)
|
||||
|
||||
c.close()
|
||||
|
||||
|
||||
class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
def __init__(self, **kwargs):
|
||||
self.hierarchy = []
|
||||
self.signals = []
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if self.parent_id == 'enum':
|
||||
self.enum = self._init_enum_flags('enum')
|
||||
elif self.parent_id == 'flags':
|
||||
self.flags = self._init_enum_flags('flags')
|
||||
|
||||
def _init_enum_flags(self, name):
|
||||
retval = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT)
|
||||
|
||||
for row in self.project.conn.execute(f'SELECT name, nick, value FROM type_{name} WHERE type_id=?', (self.type_id,)):
|
||||
retval.append(row)
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
class CmbObject(CmbBaseObject):
|
||||
info = GObject.Property(type=CmbTypeInfo, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
__gsignals__ = {
|
||||
'signal-added': (GObject.SIGNAL_RUN_FIRST, None, (CmbSignal, )),
|
||||
|
||||
'signal-removed': (GObject.SIGNAL_RUN_FIRST, None, (CmbSignal, ))
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.properties = []
|
||||
self.layout = []
|
||||
self.signals = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if self.project is None:
|
||||
return
|
||||
|
||||
self._populate_properties()
|
||||
|
||||
def _populate_type_properties(self, name):
|
||||
property_info = self.project.get_type_properties(name)
|
||||
if property_info is None:
|
||||
return
|
||||
|
||||
for property_name in property_info:
|
||||
info = property_info[property_name]
|
||||
|
||||
prop = CmbProperty(project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info)
|
||||
|
||||
self.properties.append(prop)
|
||||
|
||||
def _populate_properties(self):
|
||||
self._populate_type_properties(self.type_id)
|
||||
for parent_id in self.info.hierarchy:
|
||||
self._populate_type_properties(parent_id)
|
||||
|
||||
def _populate_layout_properties(self, name):
|
||||
property_info = self.project.get_type_properties(name)
|
||||
if property_info is None:
|
||||
return
|
||||
|
||||
for property_name in property_info:
|
||||
info = property_info[property_name]
|
||||
|
||||
prop = CmbLayoutProperty(project=self.project,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.parent_id,
|
||||
child_id=self.object_id,
|
||||
owner_id=name,
|
||||
property_id=info.property_id,
|
||||
info=info)
|
||||
|
||||
self.layout.append(prop)
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def parent_id(self):
|
||||
retval = self.db_get('SELECT parent_id FROM object WHERE (ui_id, object_id) IS (?, ?);',
|
||||
(self.ui_id, self.object_id, ))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@parent_id.setter
|
||||
def _set_parent_id(self, value):
|
||||
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)
|
||||
|
||||
if value > 0:
|
||||
parent = self.project._get_object_by_id(self.ui_id, value)
|
||||
self._populate_layout_properties(f'{parent.type_id}LayoutChild')
|
||||
else:
|
||||
self.layout = []
|
||||
|
||||
def _add_signal(self, signal_pk, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
signal = CmbSignal(project=self.project,
|
||||
signal_pk=signal_pk,
|
||||
ui_id=self.ui_id,
|
||||
object_id=self.object_id,
|
||||
owner_id=owner_id,
|
||||
signal_id=signal_id,
|
||||
handler=handler,
|
||||
detail=detail,
|
||||
user_data=user_data,
|
||||
swap=swap,
|
||||
after=after)
|
||||
self.signals.append(signal)
|
||||
self.emit('signal-added', signal)
|
||||
self.project._object_signal_added(self, signal)
|
||||
|
||||
return signal
|
||||
|
||||
def add_signal(self, owner_id, signal_id, handler, detail=None, user_data=0, swap=False, after=False):
|
||||
try:
|
||||
c = self.project.conn.cursor()
|
||||
c.execute("INSERT INTO object_signal (ui_id, object_id, owner_id, signal_id, handler, detail, user_data, swap, after) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
(self.ui_id, self.object_id, owner_id, signal_id, handler, detail, user_data, swap, after))
|
||||
signal_pk = c.lastrowid
|
||||
c.close()
|
||||
self.project.conn.commit()
|
||||
except Exception as e:
|
||||
print('add_signal', e)
|
||||
return None
|
||||
else:
|
||||
return self._add_signal(signal_pk,
|
||||
owner_id,
|
||||
signal_id,
|
||||
handler,
|
||||
detail=detail,
|
||||
user_data=user_data,
|
||||
swap=swap,
|
||||
after=after)
|
||||
|
||||
def _remove_signal(self, signal):
|
||||
self.signals.remove(signal)
|
||||
self.emit('signal-removed', signal)
|
||||
self.project._object_signal_removed(self, signal)
|
||||
|
||||
def remove_signal(self, signal):
|
||||
try:
|
||||
self.project.conn.execute("DELETE FROM object_signal WHERE signal_pk=?;",
|
||||
(signal.signal_pk, ))
|
||||
self.project.conn.commit()
|
||||
except Exception as e:
|
||||
print('remove_signal', e)
|
||||
return False
|
||||
else:
|
||||
self._remove_signal(signal)
|
||||
return True
|
File diff suppressed because it is too large
Load Diff
@ -1,118 +0,0 @@
|
||||
#
|
||||
# CmbPath
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gio
|
||||
|
||||
from .cmb_base import CmbBase
|
||||
from cambalache import _, getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbPath(CmbBase, Gio.ListModel):
|
||||
__gtype_name__ = "CmbPath"
|
||||
|
||||
path_parent = GObject.Property(type=CmbBase, flags=GObject.ParamFlags.READWRITE)
|
||||
path = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# GListModel
|
||||
self.__items = []
|
||||
self.__path_items = {}
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbPath<{self.path}>"
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
return self.path or _("{n} unsaved files").format(n=self.n_items)
|
||||
|
||||
def get_path_item(self, directory):
|
||||
return self.__path_items.get(directory, None)
|
||||
|
||||
def add_item(self, item):
|
||||
if item in self.__items:
|
||||
return
|
||||
|
||||
display_name = item.display_name
|
||||
is_path = isinstance(item, CmbPath)
|
||||
|
||||
if is_path:
|
||||
self.__path_items[item.path] = item
|
||||
|
||||
i = 0
|
||||
for list_item in self.__items:
|
||||
if is_path:
|
||||
if not isinstance(list_item, CmbPath):
|
||||
break
|
||||
|
||||
if display_name < list_item.display_name:
|
||||
break
|
||||
elif not isinstance(list_item, CmbPath) and display_name < list_item.display_name:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
item.path_parent = self
|
||||
self.__items.insert(i, item)
|
||||
self.items_changed(i, 0, 1)
|
||||
|
||||
if not self.path:
|
||||
self.notify("display-name")
|
||||
|
||||
def remove_item(self, item):
|
||||
if item not in self.__items:
|
||||
return
|
||||
|
||||
if isinstance(item, CmbPath) and item.path in self.__path_items:
|
||||
del self.__path_items[item.path]
|
||||
|
||||
item.path_parent = None
|
||||
i = self.__items.index(item)
|
||||
self.__items.pop(i)
|
||||
self.items_changed(i, 1, 0)
|
||||
|
||||
if not self.path:
|
||||
self.notify("display-name")
|
||||
|
||||
# GListModel iface
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
return len(self.__items)
|
||||
|
||||
def do_get_item(self, position):
|
||||
return self.__items[position] if position < len(self.__items) else None
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBase
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
||||
|
@ -1,142 +0,0 @@
|
||||
#
|
||||
# CmbPollNotificationView
|
||||
#
|
||||
# Copyright (C) 2025 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import datetime
|
||||
|
||||
from cambalache import _, getLogger
|
||||
from gi.repository import GObject, Gtk
|
||||
from . import utils
|
||||
from .cmb_notification import CmbPollNotification
|
||||
from .cmb_poll_option_check import CmbPollOptionCheck
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_notification_view.ui")
|
||||
class CmbPollNotificationView(Gtk.Box):
|
||||
__gtype_name__ = "CmbPollNotificationView"
|
||||
|
||||
notification = GObject.Property(
|
||||
type=CmbPollNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
|
||||
# Poll
|
||||
title_label = Gtk.Template.Child()
|
||||
description_label = Gtk.Template.Child()
|
||||
option_box = Gtk.Template.Child()
|
||||
total_label = Gtk.Template.Child()
|
||||
end_date_label = Gtk.Template.Child()
|
||||
refresh_button = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__option_checks = []
|
||||
self.__updating = False
|
||||
super().__init__(**kwargs)
|
||||
|
||||
notification = self.notification
|
||||
poll = notification.poll
|
||||
active = utils.utcnow() < poll.end_date
|
||||
|
||||
self.title_label.props.label = f"<b>{poll.title}</b>"
|
||||
self.description_label.props.label = poll.description
|
||||
|
||||
close_msg = _("<small>• Closes on {date}</small>") if active else _("<small>• Closed on {date}</small>")
|
||||
end_date = datetime.datetime.fromtimestamp(poll.end_date)
|
||||
self.end_date_label.props.label = close_msg.format(date=end_date.strftime("%x"))
|
||||
self.end_date_label.props.tooltip_text = end_date.strftime("%c")
|
||||
|
||||
first_check = None
|
||||
n_option = 0
|
||||
for option in poll.options:
|
||||
button = CmbPollOptionCheck(option=option, sensitive=active)
|
||||
|
||||
if poll.allowed_votes == 1:
|
||||
if first_check is None:
|
||||
first_check = button
|
||||
else:
|
||||
button.set_group(first_check)
|
||||
|
||||
button.connect("toggled", self.__on_check_button_toggled, n_option)
|
||||
|
||||
self.__option_checks.append(button)
|
||||
self.option_box.append(button)
|
||||
n_option += 1
|
||||
|
||||
self.__update_results()
|
||||
notification.connect("notify", self.__on_poll_notify)
|
||||
|
||||
def __on_check_button_toggled(self, button, n_option):
|
||||
allowed_votes = self.notification.poll.allowed_votes
|
||||
|
||||
if self.__updating or (allowed_votes == 1 and not button.props.active):
|
||||
return
|
||||
|
||||
votes = []
|
||||
for i, check in enumerate(self.__option_checks):
|
||||
if check.props.active:
|
||||
votes.append(i)
|
||||
|
||||
if allowed_votes > 1:
|
||||
not_done = len(votes) < allowed_votes
|
||||
for i, check in enumerate(self.__option_checks):
|
||||
if not_done:
|
||||
check.set_sensitive(True)
|
||||
elif not check.props.active:
|
||||
check.set_sensitive(False)
|
||||
|
||||
self.notification.center.poll_vote(self.notification, votes)
|
||||
|
||||
def __on_poll_notify(self, notification, pspec):
|
||||
if pspec.name in ["my-votes", "results"]:
|
||||
self.__update_results()
|
||||
|
||||
def __update_results(self):
|
||||
notification = self.notification
|
||||
results = notification.results
|
||||
my_votes = notification.my_votes
|
||||
|
||||
if not results or not my_votes:
|
||||
self.total_label.props.label = ""
|
||||
for check in self.__option_checks:
|
||||
check.fraction = -1
|
||||
return
|
||||
|
||||
self.__updating = True
|
||||
|
||||
votes = results.votes
|
||||
total = results.total
|
||||
|
||||
for i, check in enumerate(self.__option_checks):
|
||||
check.set_active(i in my_votes)
|
||||
check.fraction = votes[i] / total if total else 0
|
||||
|
||||
self.total_label.props.label = _("<small>• {total} vote</small>").format(total=results.total)
|
||||
|
||||
self.__updating = False
|
||||
|
||||
@Gtk.Template.Callback("on_refresh_button_clicked")
|
||||
def __on_refresh_button_clicked(self, button):
|
||||
self.notification.center.poll_refresh_results(self.notification)
|
||||
|
@ -1,64 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_poll_notification_view.ui -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbPollNotificationView" parent="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="halign">start</property>
|
||||
<property name="use-markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="description_label">
|
||||
<property name="halign">start</property>
|
||||
<property name="use-markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="option_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<property name="vexpand-set">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="refresh_button">
|
||||
<property name="child">
|
||||
<object class="GtkLabel">
|
||||
<property name="label"><small>Refresh</small></property>
|
||||
<property name="use-markup">True</property>
|
||||
</object>
|
||||
</property>
|
||||
<signal name="clicked" handler="on_refresh_button_clicked"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
<class name="compact"/>
|
||||
<class name="link"/>
|
||||
<class name="text-button"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="total_label">
|
||||
<property name="use-markup">True</property>
|
||||
<style>
|
||||
<class name="link"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="end_date_label">
|
||||
<property name="use-markup">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,76 +0,0 @@
|
||||
#
|
||||
# CmbPollOptionCheck
|
||||
#
|
||||
# Copyright (C) 2025 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from cambalache import getLogger
|
||||
from gi.repository import GLib, GObject, Gtk
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_option_check.ui")
|
||||
class CmbPollOptionCheck(Gtk.CheckButton):
|
||||
__gtype_name__ = "CmbPollOptionCheck"
|
||||
|
||||
label = Gtk.Template.Child()
|
||||
bar = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__fraction = None
|
||||
self.__tick_id = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def option(self):
|
||||
return self.label.props.label
|
||||
|
||||
@option.setter
|
||||
def _set_option(self, option):
|
||||
self.label.props.label = option
|
||||
|
||||
@GObject.Property(type=float)
|
||||
def fraction(self):
|
||||
return self.__fraction
|
||||
|
||||
@fraction.setter
|
||||
def _set_fraction(self, fraction):
|
||||
self.__fraction = fraction
|
||||
|
||||
if fraction < 0:
|
||||
self.bar.props.visible = False
|
||||
else:
|
||||
self.bar.props.visible = True
|
||||
if self.__tick_id is None:
|
||||
self.__tick_id = self.add_tick_callback(self.__update_fraction)
|
||||
|
||||
def __update_fraction(self, widget, frame_clock):
|
||||
if self.bar.props.fraction < self.__fraction:
|
||||
self.bar.props.fraction = min(self.__fraction, self.bar.props.fraction + 0.08)
|
||||
elif self.bar.props.fraction > self.__fraction:
|
||||
self.bar.props.fraction = max(self.__fraction, self.bar.props.fraction - 0.08)
|
||||
else:
|
||||
self.__tick_id = None
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
return GLib.SOURCE_CONTINUE
|
@ -1,23 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<interface>
|
||||
<!-- interface-name r.ui -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<template class="CmbPollOptionCheck" parent="GtkCheckButton">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="halign">start</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="bar"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
File diff suppressed because it is too large
Load Diff
134
cambalache/cmb_project.sql
Normal file
134
cambalache/cmb_project.sql
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Data Model for Cambalache Project
|
||||
*
|
||||
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
|
||||
*
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*/
|
||||
|
||||
/* Project global data
|
||||
*
|
||||
*/
|
||||
|
||||
CREATE TABLE global (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
) WITHOUT ROWID;
|
||||
|
||||
/* UI
|
||||
*
|
||||
*/
|
||||
CREATE TABLE ui (
|
||||
ui_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
template_id INTEGER,
|
||||
|
||||
name TEXT UNIQUE,
|
||||
filename TEXT UNIQUE,
|
||||
description TEXT,
|
||||
copyright TEXT,
|
||||
authors TEXT,
|
||||
license_id TEXT REFERENCES license,
|
||||
translation_domain TEXT,
|
||||
FOREIGN KEY(ui_id, template_id) REFERENCES object(ui_id, object_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX ui_license_id_fk ON ui (license_id);
|
||||
|
||||
|
||||
/* UI library version target
|
||||
*
|
||||
*/
|
||||
CREATE TABLE ui_library (
|
||||
ui_id INTEGER REFERENCES ui ON DELETE CASCADE,
|
||||
library_id TEXT,
|
||||
version TEXT,
|
||||
PRIMARY KEY(ui_id, library_id),
|
||||
FOREIGN KEY(library_id, version) REFERENCES library_version
|
||||
) WITHOUT ROWID;
|
||||
|
||||
|
||||
/* Object
|
||||
*
|
||||
* TODO: check type_id is an object
|
||||
*/
|
||||
CREATE TABLE object (
|
||||
ui_id INTEGER REFERENCES ui ON DELETE CASCADE,
|
||||
object_id INTEGER,
|
||||
|
||||
type_id TEXT NOT NULL REFERENCES type,
|
||||
name TEXT,
|
||||
parent_id INTEGER,
|
||||
PRIMARY KEY(ui_id, object_id),
|
||||
FOREIGN KEY(ui_id, parent_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX object_type_id_fk ON object (type_id);
|
||||
CREATE INDEX object_parent_id_fk ON object (ui_id, parent_id);
|
||||
|
||||
|
||||
/* Object Property
|
||||
*
|
||||
* TODO: check owner_id is in object_id.type_id type tree
|
||||
*/
|
||||
CREATE TABLE object_property (
|
||||
ui_id INTEGER REFERENCES ui ON DELETE CASCADE,
|
||||
object_id INTEGER,
|
||||
owner_id TEXT,
|
||||
property_id TEXT,
|
||||
|
||||
value TEXT,
|
||||
translatable BOOLEAN,
|
||||
PRIMARY KEY(ui_id, object_id, owner_id, property_id),
|
||||
FOREIGN KEY(ui_id, object_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(owner_id, property_id) REFERENCES property
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX object_property_object_fk ON object_property (ui_id, object_id);
|
||||
CREATE INDEX object_property_property_fk ON object_property (owner_id, property_id);
|
||||
|
||||
|
||||
/* Object Child Property
|
||||
*
|
||||
* TODO: check owner_id is in object_id.type_id type tree
|
||||
*/
|
||||
CREATE TABLE object_layout_property (
|
||||
ui_id INTEGER REFERENCES ui ON DELETE CASCADE,
|
||||
object_id INTEGER,
|
||||
child_id INTEGER,
|
||||
owner_id TEXT,
|
||||
property_id TEXT,
|
||||
|
||||
value TEXT,
|
||||
translatable BOOLEAN,
|
||||
PRIMARY KEY(ui_id, object_id, child_id, owner_id, property_id),
|
||||
FOREIGN KEY(ui_id, object_id) REFERENCES object ON DELETE CASCADE,
|
||||
FOREIGN KEY(ui_id, child_id) REFERENCES object(ui_id, object_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(owner_id, property_id) REFERENCES property
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX object_layout_property_child_property_fk ON object_layout_property (owner_id, property_id);
|
||||
|
||||
|
||||
/* Object Signal
|
||||
*
|
||||
* TODO: check owner_id is in object_id.type_id type tree
|
||||
*/
|
||||
CREATE TABLE object_signal (
|
||||
signal_pk INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ui_id INTEGER REFERENCES ui ON DELETE CASCADE,
|
||||
object_id INTEGER,
|
||||
owner_id TEXT,
|
||||
signal_id TEXT,
|
||||
|
||||
handler TEXT NOT NULL,
|
||||
detail TEXT,
|
||||
user_data INTEGER,
|
||||
swap BOOLEAN,
|
||||
after BOOLEAN,
|
||||
FOREIGN KEY(ui_id, object_id) REFERENCES object ON DELETE CASCADE,
|
||||
FOREIGN KEY(owner_id, signal_id) REFERENCES signal
|
||||
);
|
||||
|
||||
CREATE INDEX object_signal_object_fk ON object (ui_id, object_id);
|
||||
CREATE INDEX object_signal_signal_fk ON object_signal (owner_id, signal_id);
|
||||
|
@ -1,299 +0,0 @@
|
||||
#
|
||||
# Cambalache Property wrapper
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# 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 . import utils
|
||||
from cambalache import _, getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbProperty(CmbBaseProperty):
|
||||
object = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._init = True
|
||||
super().__init__(**kwargs)
|
||||
self._init = False
|
||||
self.version_warning = None
|
||||
|
||||
owner_info = self.project.type_info.get(self.info.owner_id, None)
|
||||
self.library_id = owner_info.library_id
|
||||
self._update_version_warning()
|
||||
|
||||
self.connect("notify", self.__on_notify)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbProperty<{self.object.type_id} {self.info.owner_id}:{self.property_id}>"
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
self.project._object_property_changed(self.object, self)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def value(self):
|
||||
c = self.project.db.execute(
|
||||
"SELECT value FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
)
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else self.info.default_value
|
||||
|
||||
@value.setter
|
||||
def _set_value(self, value):
|
||||
self.__update_values(value, self.translatable, self.translation_context, self.translation_comments, self.bind_property)
|
||||
|
||||
@GObject.Property(type=bool, default=False)
|
||||
def translatable(self):
|
||||
return super().translatable
|
||||
|
||||
@translatable.setter
|
||||
def _set_translatable(self, value):
|
||||
self.__update_values(self.value, value, self.translation_context, self.translation_comments, self.bind_property)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def translation_context(self):
|
||||
return self.db_get(
|
||||
"SELECT translation_context FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
),
|
||||
)
|
||||
|
||||
@translation_context.setter
|
||||
def _set_translation_context(self, value):
|
||||
self.__update_values(self.value, self.translatable, value, self.translation_comments, self.bind_property)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def translation_comments(self):
|
||||
return self.db_get(
|
||||
"SELECT translation_comments FROM object_property WHERE (ui_id, object_id, owner_id, property_id) IS (?, ?, ?, ?);",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
),
|
||||
)
|
||||
|
||||
@translation_comments.setter
|
||||
def _set_translation_comments(self, value):
|
||||
self.__update_values(self.value, self.translatable, self.translation_context, value, self.bind_property)
|
||||
|
||||
def reset(self):
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Unset {obj} {prop} {prop_type}").format(**self.__get_msgs()))
|
||||
|
||||
self.project.db.execute(
|
||||
"DELETE FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
)
|
||||
self.__update_internal_child()
|
||||
|
||||
if self.info.internal_child:
|
||||
self.project.history_pop()
|
||||
|
||||
def __update_internal_child(self):
|
||||
internal_info = self.info.internal_child
|
||||
if internal_info and internal_info.internal_parent_id:
|
||||
logger.warning("Adding an internal child within an internal child automatically is not implemented")
|
||||
return
|
||||
elif internal_info is None:
|
||||
return
|
||||
|
||||
value = self.value
|
||||
child_id = self.db_get(
|
||||
"SELECT object_id FROM object WHERE ui_id=? AND parent_id=? AND internal=?",
|
||||
(self.ui_id, self.object_id, internal_info.internal_child_id)
|
||||
)
|
||||
|
||||
if value and not child_id:
|
||||
self.project.add_object(
|
||||
self.ui_id,
|
||||
internal_info.internal_type,
|
||||
parent_id=self.object_id,
|
||||
internal=internal_info.internal_child_id
|
||||
)
|
||||
elif child_id:
|
||||
internal_child = self.project.get_object_by_id(self.ui_id, child_id)
|
||||
if internal_child:
|
||||
self.project.remove_object(internal_child, allow_internal_removal=True)
|
||||
|
||||
def __get_msgs(self, value=None):
|
||||
return {
|
||||
"obj": self.object.display_name_type,
|
||||
"prop": self.property_id,
|
||||
"prop_type": _("property"),
|
||||
"value": str(value)
|
||||
}
|
||||
|
||||
def __update_values(self, value, translatable, translation_context, translation_comments, bind_property):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
bind_source_id, bind_owner_id, bind_property_id = (None, None, None)
|
||||
if bind_property:
|
||||
bind_source_id = bind_property.object.object_id
|
||||
bind_owner_id = bind_property.owner_id
|
||||
bind_property_id = bind_property.property_id
|
||||
|
||||
if (
|
||||
(value is None or value == self.info.default_value or (self.info.is_object and value == 0)) and
|
||||
bind_property is None and
|
||||
not translatable and
|
||||
translation_context is None and
|
||||
translation_comments is None
|
||||
):
|
||||
self.reset()
|
||||
else:
|
||||
if (
|
||||
value == self.value and
|
||||
translatable == self.translatable and
|
||||
translation_context == self.translation_context and
|
||||
translation_comments == self.translation_comments and
|
||||
bind_source_id == self.bind_source_id and
|
||||
bind_owner_id == self.bind_owner_id and
|
||||
bind_property_id == self.bind_property_id
|
||||
):
|
||||
return
|
||||
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"SELECT count(ui_id) FROM object_property WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
)
|
||||
|
||||
if count:
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Update {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||
|
||||
c.execute(
|
||||
"""
|
||||
UPDATE object_property
|
||||
SET
|
||||
value=?,
|
||||
translatable=?, translation_context=?, translation_comments=?,
|
||||
bind_source_id=?, bind_owner_id=?, bind_property_id=?
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(
|
||||
value,
|
||||
translatable,
|
||||
translation_context,
|
||||
translation_comments,
|
||||
bind_source_id,
|
||||
bind_owner_id,
|
||||
bind_property_id,
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
),
|
||||
)
|
||||
else:
|
||||
if self.info.internal_child:
|
||||
self.project.history_push(_("Set {obj} {prop} {prop_type} to {value}").format(**self.__get_msgs(value)))
|
||||
|
||||
c.execute(
|
||||
"""
|
||||
INSERT INTO object_property
|
||||
(
|
||||
ui_id, object_id, owner_id, property_id,
|
||||
value,
|
||||
translatable, translation_context, translation_comments,
|
||||
bind_source_id, bind_owner_id, bind_property_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
""",
|
||||
(
|
||||
self.ui_id,
|
||||
self.object_id,
|
||||
self.owner_id,
|
||||
self.property_id,
|
||||
value,
|
||||
translatable,
|
||||
translation_context,
|
||||
translation_comments,
|
||||
bind_source_id,
|
||||
bind_owner_id,
|
||||
bind_property_id,
|
||||
),
|
||||
)
|
||||
|
||||
self.__update_internal_child()
|
||||
|
||||
if self.info.internal_child:
|
||||
self.project.history_pop()
|
||||
|
||||
if self._init is False:
|
||||
self.object._property_changed(self)
|
||||
|
||||
c.close()
|
||||
|
||||
@GObject.Property(type=CmbBaseProperty)
|
||||
def bind_property(self):
|
||||
c = self.project.db.cursor()
|
||||
row = c.execute(
|
||||
"""
|
||||
SELECT bind_source_id, bind_property_id
|
||||
FROM object_property
|
||||
WHERE ui_id=? AND object_id=? AND owner_id=? AND property_id=?;
|
||||
""",
|
||||
(self.ui_id, self.object_id, self.owner_id, self.property_id),
|
||||
).fetchone()
|
||||
|
||||
if row:
|
||||
bind_source_id, bind_property_id = row
|
||||
source = self.project.get_object_by_id(self.ui_id, bind_source_id) if bind_property_id else None
|
||||
return source.properties_dict.get(bind_property_id, None) if source else None
|
||||
|
||||
return None
|
||||
|
||||
@bind_property.setter
|
||||
def _set_bind_property(self, bind_property):
|
||||
self.__update_values(self.value, self.translatable, self.translation_context, self.translation_comments, bind_property)
|
||||
self.project._object_property_binding_changed(self.object, self)
|
||||
|
||||
def _update_version_warning(self):
|
||||
target = self.object.ui.get_target(self.library_id)
|
||||
warning = utils.get_version_warning(
|
||||
target, self.info.version, self.info.deprecated_version, self.property_id
|
||||
) or ""
|
||||
|
||||
if self.project.target_tk == "gtk-4.0" and self.info.type_id == "GFile":
|
||||
target = self.object.ui.get_target("gtk")
|
||||
if target is not None:
|
||||
version = utils.parse_version(target)
|
||||
if version is None or utils.version_cmp(version, (4, 16, 0)) < 0:
|
||||
if len(warning):
|
||||
warning += "\n"
|
||||
warning += _("Warning: GFile uri needs to be absolute for Gtk < 4.16")
|
||||
|
||||
self.version_warning = warning if len(warning) else None
|
@ -1,66 +0,0 @@
|
||||
#
|
||||
# Cambalache Property Type Info wrapper
|
||||
#
|
||||
# Copyright (C) 2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject
|
||||
from .cmb_objects_base import CmbBasePropertyInfo
|
||||
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbPropertyInfo(CmbBasePropertyInfo):
|
||||
internal_child = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.is_a11y = CmbPropertyInfo.type_is_accessible(self.owner_id)
|
||||
self.a11y_property_id = CmbPropertyInfo.accessible_property_remove_prefix(self.owner_id, self.property_id)
|
||||
|
||||
@classmethod
|
||||
def type_is_accessible(cls, owner_id):
|
||||
return owner_id in [
|
||||
"CmbAccessibleProperty",
|
||||
"CmbAccessibleRelation",
|
||||
"CmbAccessibleState",
|
||||
"CmbAccessibleAction"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def accessible_property_remove_prefix(cls, owner_id, property_id):
|
||||
prefix = {
|
||||
"CmbAccessibleProperty": "cmb-a11y-property-",
|
||||
"CmbAccessibleRelation": "cmb-a11y-relation-",
|
||||
"CmbAccessibleState": "cmb-a11y-state-",
|
||||
"CmbAccessibleAction": "cmb-a11y-action-"
|
||||
}.get(owner_id, None)
|
||||
|
||||
if prefix is None:
|
||||
return None
|
||||
|
||||
# A11y property name without prefix
|
||||
return property_id.removeprefix(prefix)
|
||||
|
@ -1,270 +0,0 @@
|
||||
#
|
||||
# CmbPropertyLabel
|
||||
#
|
||||
# Copyright (C) 2023-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from .cmb_property import CmbProperty
|
||||
from .cmb_layout_property import CmbLayoutProperty
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
from .control import CmbObjectChooser, CmbFlagsEntry
|
||||
from cambalache import _
|
||||
|
||||
|
||||
class CmbPropertyLabel(Gtk.Button):
|
||||
__gtype_name__ = "CmbPropertyLabel"
|
||||
|
||||
prop = GObject.Property(type=CmbProperty, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
layout_prop = GObject.Property(
|
||||
type=CmbLayoutProperty, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
|
||||
)
|
||||
bindable = GObject.Property(type=bool, default=True, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if not self.prop and not self.layout_prop:
|
||||
raise Exception("CmbPropertyLabel requires prop or layout_prop to be set")
|
||||
return
|
||||
|
||||
self.props.focus_on_click = False
|
||||
self.label = Gtk.Label(halign=Gtk.Align.START, valign=Gtk.Align.CENTER)
|
||||
box = Gtk.Box()
|
||||
|
||||
# Update label status
|
||||
if self.prop:
|
||||
self.bind_icon = Gtk.Image(icon_size=Gtk.IconSize.NORMAL)
|
||||
box.append(self.bind_icon)
|
||||
|
||||
# A11y properties are prefixed to avoid clashes, do not show prefix here
|
||||
self.label.props.label = self.prop.info.a11y_property_id if self.prop.info.is_a11y else self.prop.property_id
|
||||
|
||||
self.__update_property_label()
|
||||
self.prop.connect("notify::value", lambda o, p: self.__update_property_label())
|
||||
|
||||
if self.bindable:
|
||||
self.connect("clicked", self.__on_bind_button_clicked)
|
||||
|
||||
if self.layout_prop:
|
||||
self.bind_icon = None
|
||||
self.label.props.label = self.layout_prop.property_id
|
||||
self.__update_layout_property_label()
|
||||
self.layout_prop.connect("notify::value", lambda o, p: self.__update_layout_property_label())
|
||||
|
||||
box.append(self.label)
|
||||
self.set_child(box)
|
||||
|
||||
def __update_label(self, prop):
|
||||
if prop.value != prop.info.default_value:
|
||||
self.add_css_class("modified")
|
||||
else:
|
||||
self.remove_css_class("modified")
|
||||
|
||||
msg = prop.version_warning
|
||||
self.set_tooltip_text(msg)
|
||||
|
||||
if msg:
|
||||
self.add_css_class("warning")
|
||||
else:
|
||||
self.remove_css_class("warning")
|
||||
|
||||
def __update_layout_property_label(self):
|
||||
self.__update_label(self.layout_prop)
|
||||
|
||||
def __update_property_label(self):
|
||||
self.__update_label(self.prop)
|
||||
|
||||
if not self.bindable:
|
||||
return
|
||||
|
||||
if self.prop.bind_property_id:
|
||||
self.bind_icon.props.icon_name = "binded-symbolic"
|
||||
self.remove_css_class("hidden")
|
||||
else:
|
||||
self.bind_icon.props.icon_name = "bind-symbolic"
|
||||
self.add_css_class("hidden")
|
||||
|
||||
def __on_object_editor_notify(self, object_editor, pspec, property_editor):
|
||||
object_id = object_editor.cmb_value
|
||||
if object_id:
|
||||
property_editor.object = self.prop.project.get_object_by_id(self.prop.ui_id, int(object_id))
|
||||
|
||||
def __on_property_editor_changed(self, combo):
|
||||
bind_source, bind_property = self.__find_bind_source_property(combo.object.object_id, combo.props.active_id)
|
||||
self.prop.bind_property = bind_property
|
||||
self.__update_property_label()
|
||||
|
||||
def __find_bind_source_property(self, bind_source_id, bind_property_id):
|
||||
bind_source = self.prop.project.get_object_by_id(self.prop.ui_id, bind_source_id) if bind_source_id else None
|
||||
bind_property = bind_source.properties_dict.get(bind_property_id, None) if bind_source else None
|
||||
|
||||
return bind_source, bind_property
|
||||
|
||||
def __on_clear_clicked(self, button, popover):
|
||||
self.prop.bind_property = None
|
||||
self.prop.bind_flags = None
|
||||
self.__update_property_label()
|
||||
popover.popdown()
|
||||
|
||||
def __on_close_clicked(self, button, popover):
|
||||
popover.popdown()
|
||||
|
||||
def __on_bind_button_clicked(self, button):
|
||||
popover = Gtk.Popover(position=Gtk.PositionType.LEFT, css_classes=["cmb-binding-popover"])
|
||||
popover.set_parent(self)
|
||||
|
||||
label = Gtk.Label(label="<b>Property Binding</b>", use_markup=True, halign=Gtk.Align.START, hexpand=True)
|
||||
close = Gtk.Button(icon_name="window-close-symbolic", halign=Gtk.Align.END, css_classes=["close"])
|
||||
close.connect("clicked", self.__on_close_clicked, popover)
|
||||
|
||||
box = Gtk.Box(hexpand=True)
|
||||
box.append(label)
|
||||
box.append(close)
|
||||
|
||||
grid = Gtk.Grid(hexpand=True, row_spacing=6, column_spacing=6)
|
||||
grid.attach(box, 0, 0, 2, 1)
|
||||
|
||||
# Get bind property to initialize inputs
|
||||
bind_source, bind_property = self.__find_bind_source_property(self.prop.bind_source_id, self.prop.bind_property_id)
|
||||
|
||||
# Create Property editor
|
||||
property_editor = CmbPropertyChooser(object=bind_source, target_info=self.prop.info)
|
||||
property_editor.connect("changed", self.__on_property_editor_changed)
|
||||
|
||||
# Update active_id after letting the object populate the properties
|
||||
if bind_property:
|
||||
property_editor.props.active_id = bind_property.property_id
|
||||
|
||||
# Object editor (it does not set the object directly to CmbProperty, just choose the object in the prop chooser)
|
||||
object_editor = CmbObjectChooser(parent=self.prop.object, cmb_value=bind_source.object_id if bind_source else 0)
|
||||
object_editor.connect("notify::cmb-value", self.__on_object_editor_notify, property_editor)
|
||||
|
||||
# Flags editor
|
||||
binding_flags_info = self.prop.project.type_info.get("GBindingFlags", None)
|
||||
flags_editor = CmbFlagsEntry(info=binding_flags_info)
|
||||
GObject.Object.bind_property(
|
||||
self.prop,
|
||||
"bind_flags",
|
||||
flags_editor,
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
|
||||
i = 1
|
||||
for prop_label, editor in [("source", object_editor), ("property", property_editor), ("flags", flags_editor)]:
|
||||
label = Gtk.Label(label=prop_label, xalign=0)
|
||||
|
||||
grid.attach(label, 0, i, 1, 1)
|
||||
grid.attach(editor, 1, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
clear = Gtk.Button(label=_("Clear"), halign=Gtk.Align.END)
|
||||
clear.connect("clicked", self.__on_clear_clicked, popover)
|
||||
grid.attach(clear, 0, i, 2, 1)
|
||||
|
||||
object_editor.grab_focus()
|
||||
|
||||
popover.set_child(grid)
|
||||
popover.popup()
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbPropertyLabel, "CmbPropertyLabel")
|
||||
|
||||
|
||||
class CmbPropertyChooser(Gtk.ComboBoxText):
|
||||
__gtype_name__ = "CmbPropertyChooser"
|
||||
|
||||
object = GObject.Property(type=CmbObject, flags=GObject.ParamFlags.READWRITE)
|
||||
target_info = GObject.Property(type=CmbPropertyInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.__populate()
|
||||
self.connect("notify::object", self.__on_object_notify)
|
||||
|
||||
def __on_object_notify(self, obj, pspec):
|
||||
self.__populate()
|
||||
|
||||
def __populate(self):
|
||||
self.remove_all()
|
||||
|
||||
if self.object is None:
|
||||
return
|
||||
|
||||
target_info = self.target_info
|
||||
target_type = target_info.type_id
|
||||
target_type_info = self.object.project.type_info.get(target_type, None)
|
||||
target_is_object = target_info.is_object
|
||||
target_is_iface = target_type_info.parent_id == "interface" if target_type_info else False
|
||||
|
||||
for prop in sorted(self.object.properties, key=lambda p: p.property_id):
|
||||
info = prop.info
|
||||
|
||||
if info.is_a11y:
|
||||
continue
|
||||
|
||||
# Ignore construct only properties
|
||||
if info.construct_only:
|
||||
continue
|
||||
|
||||
source_type_info = self.object.project.type_info.get(info.type_id, None)
|
||||
source_is_object = info.is_object
|
||||
source_is_iface = source_type_info.parent_id == "interface" if source_type_info else False
|
||||
|
||||
if target_is_object or target_is_iface:
|
||||
# Ignore non object properties
|
||||
if not source_is_object and not source_is_iface:
|
||||
continue
|
||||
|
||||
# Ignore object properties of a different type
|
||||
if source_type_info and not source_type_info.is_a(target_info.type_id):
|
||||
continue
|
||||
elif source_is_object or source_is_iface:
|
||||
continue
|
||||
|
||||
# Enums and Flags has to be the same type
|
||||
if target_type_info and target_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
|
||||
continue
|
||||
|
||||
if source_type_info and source_type_info.parent_id in ["flags", "enum"] and info.type_id != target_type:
|
||||
continue
|
||||
|
||||
compatible = info.type_id == target_type
|
||||
|
||||
if not compatible:
|
||||
try:
|
||||
gtype_id = GObject.type_from_name(info.type_id)
|
||||
gtarget_id = GObject.type_from_name(target_type)
|
||||
if gtype_id and gtarget_id:
|
||||
compatible = GObject.Value.type_compatible(gtype_id, gtarget_id)
|
||||
if not compatible:
|
||||
compatible = GObject.Value.type_transformable(gtype_id, gtarget_id)
|
||||
except Exception as e: # noqa F841
|
||||
self.append(prop.property_id, prop.property_id + "*")
|
||||
continue
|
||||
|
||||
if compatible:
|
||||
self.append(prop.property_id, prop.property_id)
|
@ -1,36 +1,21 @@
|
||||
#
|
||||
# CmbSignalEditor - Cambalache Signal Editor
|
||||
#
|
||||
# Copyright (C) 2021-2024 Juan Pablo Ugarte
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# 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
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk, Pango
|
||||
import os
|
||||
import gi
|
||||
|
||||
from .cmb_object import CmbObject
|
||||
from . import utils
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_objects import CmbObject
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Col(Enum):
|
||||
SIGNAL = 0
|
||||
OWNER_ID = 1
|
||||
@ -41,12 +26,11 @@ class Col(Enum):
|
||||
SWAP = 6
|
||||
AFTER = 7
|
||||
INFO = 8
|
||||
WARNING_MESSAGE = 9
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_signal_editor.ui")
|
||||
@Gtk.Template(resource_path='/ar/xjuan/Cambalache/cmb_signal_editor.ui')
|
||||
class CmbSignalEditor(Gtk.Box):
|
||||
__gtype_name__ = "CmbSignalEditor"
|
||||
__gtype_name__ = 'CmbSignalEditor'
|
||||
|
||||
treeview = Gtk.Template.Child()
|
||||
treestore = Gtk.Template.Child()
|
||||
@ -66,34 +50,42 @@ class CmbSignalEditor(Gtk.Box):
|
||||
self._object = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.signal_id_column.set_cell_data_func(self.signal_id, self.__signal_id_data_func, None)
|
||||
self.handler_column.set_cell_data_func(self.handler, self.__data_func, Col.HANDLER.value)
|
||||
self.user_data_column.set_cell_data_func(self.user_data, self.__data_func, Col.USER_DATA.value)
|
||||
self.swap_column.set_cell_data_func(self.swap, self.__data_func, Col.SWAP.value)
|
||||
self.after_column.set_cell_data_func(self.after, self.__data_func, Col.AFTER.value)
|
||||
self.signal_id_column.set_cell_data_func(self.signal_id,
|
||||
self._signal_id_data_func,
|
||||
None)
|
||||
self.handler_column.set_cell_data_func(self.handler,
|
||||
self._data_func,
|
||||
Col.HANDLER.value)
|
||||
self.user_data_column.set_cell_data_func(self.user_data,
|
||||
self._data_func,
|
||||
Col.USER_DATA.value)
|
||||
self.swap_column.set_cell_data_func(self.swap,
|
||||
self._data_func,
|
||||
Col.SWAP.value)
|
||||
self.after_column.set_cell_data_func(self.after,
|
||||
self._data_func,
|
||||
Col.AFTER.value)
|
||||
|
||||
@GObject.Property(type=CmbObject)
|
||||
@GObject.property(type=CmbObject)
|
||||
def object(self):
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
if self._object:
|
||||
if self._object is not None:
|
||||
self.treestore.clear()
|
||||
self._object.disconnect_by_func(self.__on_signal_added)
|
||||
self._object.disconnect_by_func(self.__on_signal_removed)
|
||||
self._object.disconnect_by_func(self.__on_signal_changed)
|
||||
self._object.disconnect_by_func(self._on_signal_added)
|
||||
self._object.disconnect_by_func(self._on_signal_removed)
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj:
|
||||
self.__populate_treestore()
|
||||
self._object.connect("signal-added", self.__on_signal_added)
|
||||
self._object.connect("signal-removed", self.__on_signal_removed)
|
||||
self._object.connect("signal-changed", self.__on_signal_changed)
|
||||
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)
|
||||
|
||||
@Gtk.Template.Callback("on_handler_edited")
|
||||
def __on_handler_edited(self, renderer, path, new_text):
|
||||
@Gtk.Template.Callback('on_handler_edited')
|
||||
def _on_handler_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
@ -109,14 +101,14 @@ class CmbSignalEditor(Gtk.Box):
|
||||
else:
|
||||
self._object.remove_signal(signal)
|
||||
|
||||
@Gtk.Template.Callback("on_detail_edited")
|
||||
def __on_detail_edited(self, renderer, path, new_text):
|
||||
@Gtk.Template.Callback('on_detail_edited')
|
||||
def _on_detail_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
if signal is not None:
|
||||
if len(new_text) > 0:
|
||||
tokens = new_text.split("::")
|
||||
tokens = new_text.split('::')
|
||||
if len(tokens) == 2 and len(tokens[1]) > 0:
|
||||
signal.detail = tokens[1]
|
||||
else:
|
||||
@ -126,33 +118,21 @@ class CmbSignalEditor(Gtk.Box):
|
||||
|
||||
self.treestore[iter_][Col.DETAIL.value] = signal.detail
|
||||
|
||||
@Gtk.Template.Callback("on_user_data_edited")
|
||||
def __on_user_data_edited(self, renderer, path, new_text):
|
||||
@Gtk.Template.Callback('on_user_data_edited')
|
||||
def _on_user_data_edited(self, renderer, path, new_text):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
if signal is not None:
|
||||
if len(new_text) > 0:
|
||||
data_obj = self._object.project.get_object_by_name(signal.ui_id, new_text)
|
||||
|
||||
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
|
||||
signal.user_data = int(new_text)
|
||||
self.treestore[iter_][Col.USER_DATA.value] = str(signal.user_data)
|
||||
else:
|
||||
signal.user_data = 0
|
||||
signal.swap = False
|
||||
self.treestore[iter_][Col.USER_DATA.value] = ""
|
||||
self.treestore[iter_][Col.USER_DATA.value] = ''
|
||||
|
||||
@Gtk.Template.Callback("on_swap_toggled")
|
||||
def __on_swap_toggled(self, renderer, path):
|
||||
@Gtk.Template.Callback('on_swap_toggled')
|
||||
def _on_swap_toggled(self, renderer, path):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
@ -160,8 +140,8 @@ class CmbSignalEditor(Gtk.Box):
|
||||
signal.swap = not self.treestore[iter_][Col.SWAP.value]
|
||||
self.treestore[iter_][Col.SWAP.value] = signal.swap
|
||||
|
||||
@Gtk.Template.Callback("on_after_toggled")
|
||||
def __on_after_toggled(self, renderer, path):
|
||||
@Gtk.Template.Callback('on_after_toggled')
|
||||
def _on_after_toggled(self, renderer, path):
|
||||
iter_ = self.treestore.get_iter(path)
|
||||
signal = self.treestore[iter_][Col.SIGNAL.value]
|
||||
|
||||
@ -169,63 +149,62 @@ class CmbSignalEditor(Gtk.Box):
|
||||
signal.after = not self.treestore[iter_][Col.AFTER.value]
|
||||
self.treestore[iter_][Col.AFTER.value] = signal.after
|
||||
|
||||
def __on_signal_added(self, obj, signal):
|
||||
def _on_signal_added(self, obj, signal):
|
||||
for row in self.treestore:
|
||||
if row[Col.OWNER_ID.value] == signal.owner_id:
|
||||
for child in row.iterchildren():
|
||||
if child[Col.SIGNAL.value] is None and child[Col.SIGNAL_ID.value] == signal.signal_id:
|
||||
self.treestore.insert_before(
|
||||
row.iter,
|
||||
child.iter,
|
||||
(
|
||||
signal,
|
||||
signal.owner_id,
|
||||
signal.signal_id,
|
||||
signal.detail,
|
||||
signal.handler,
|
||||
str(signal.user_data),
|
||||
signal.swap,
|
||||
signal.after,
|
||||
child[Col.INFO.value],
|
||||
None,
|
||||
),
|
||||
)
|
||||
if child[Col.SIGNAL.value] is None and \
|
||||
child[Col.SIGNAL_ID.value] == signal.signal_id:
|
||||
self.treestore.insert_before(row.iter,
|
||||
child.iter,
|
||||
(signal,
|
||||
signal.owner_id,
|
||||
signal.signal_id,
|
||||
signal.detail,
|
||||
signal.handler,
|
||||
str(signal.user_data),
|
||||
signal.swap,
|
||||
signal.after,
|
||||
child[Col.INFO.value]))
|
||||
break
|
||||
|
||||
def __on_signal_removed(self, obj, signal):
|
||||
def _on_signal_removed(self, obj, signal):
|
||||
for row in self.treestore:
|
||||
for child in row.iterchildren():
|
||||
if child[Col.SIGNAL.value] == signal:
|
||||
self.treestore.remove(child.iter)
|
||||
return
|
||||
|
||||
def __on_signal_changed(self, obj, signal):
|
||||
for row in self.treestore:
|
||||
for child in row.iterchildren():
|
||||
if child[Col.SIGNAL.value] == signal:
|
||||
child[Col.DETAIL.value] = signal.detail
|
||||
child[Col.HANDLER.value] = signal.handler
|
||||
child[Col.USER_DATA.value] = str(signal.user_data)
|
||||
child[Col.SWAP.value] = signal.swap
|
||||
child[Col.AFTER.value] = signal.after
|
||||
return
|
||||
|
||||
def __populate_from_type(self, info, target):
|
||||
def _populate_from_type(self, info):
|
||||
if len(info.signals) == 0:
|
||||
return None
|
||||
|
||||
parent = self.treestore.append(None, (None, info.type_id, info.type_id, None, None, None, False, False, None, None))
|
||||
for signal_id in info.signals:
|
||||
signal = info.signals[signal_id]
|
||||
msg = utils.get_version_warning(target, signal.version, signal.deprecated_version, signal.signal_id)
|
||||
self.treestore.append(parent, (None, info.type_id, signal.signal_id, None, None, None, False, False, signal, msg))
|
||||
parent = self.treestore.append(None,
|
||||
(None,
|
||||
info.type_id,
|
||||
info.type_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
None))
|
||||
for signal in info.signals:
|
||||
self.treestore.append(parent,
|
||||
(None,
|
||||
info.type_id,
|
||||
signal.signal_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
signal))
|
||||
return parent
|
||||
|
||||
def __populate_treestore(self):
|
||||
target = self._object.ui.get_target(self._object.info.library_id)
|
||||
|
||||
def _populate_treestore(self):
|
||||
# Populate object type signals
|
||||
parent = self.__populate_from_type(self._object.info, target)
|
||||
parent = self._populate_from_type(self._object.info)
|
||||
|
||||
# Expand object type signals
|
||||
if parent:
|
||||
@ -233,32 +212,29 @@ class CmbSignalEditor(Gtk.Box):
|
||||
|
||||
# Populate all hierarchy signals
|
||||
for type_id in self._object.info.hierarchy:
|
||||
info = self._object.project.type_info.get(type_id, None)
|
||||
info = self._object.project._type_info.get(type_id, None)
|
||||
if info:
|
||||
self.__populate_from_type(info, target)
|
||||
self._populate_from_type(info)
|
||||
|
||||
# Populate object signals
|
||||
for signal in self._object.signals:
|
||||
self.__on_signal_added(self._object, signal)
|
||||
self._on_signal_added(self._object, signal)
|
||||
|
||||
def __signal_id_data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
def _signal_id_data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
info = tree_model[iter_][Col.INFO.value]
|
||||
signal_id = tree_model[iter_][Col.SIGNAL_ID.value]
|
||||
warning = tree_model[iter_][Col.WARNING_MESSAGE.value]
|
||||
|
||||
if info and info.detailed:
|
||||
if info is not None and info.detailed:
|
||||
detail = tree_model[iter_][Col.DETAIL.value]
|
||||
signal = tree_model[iter_][Col.SIGNAL.value]
|
||||
|
||||
cell.props.editable = False if signal is None else True
|
||||
cell.props.text = f"{signal_id}::{detail}" if detail is not None else signal_id
|
||||
cell.props.text = f'{signal_id}::{detail}' if detail is not None else signal_id
|
||||
else:
|
||||
cell.props.editable = False
|
||||
cell.props.text = signal_id
|
||||
|
||||
cell.props.underline = Pango.Underline.ERROR if warning else Pango.Underline.NONE
|
||||
|
||||
def __data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
def _data_func(self, tree_column, cell, tree_model, iter_, column):
|
||||
info = tree_model[iter_][Col.INFO.value]
|
||||
signal = tree_model[iter_][Col.SIGNAL.value]
|
||||
|
||||
@ -273,10 +249,3 @@ class CmbSignalEditor(Gtk.Box):
|
||||
else:
|
||||
cell.props.sensitive = True
|
||||
|
||||
if signal and column == Col.USER_DATA.value:
|
||||
user_data = signal.user_data
|
||||
if user_data:
|
||||
data_obj = self._object.project.get_object_by_id(signal.ui_id, user_data)
|
||||
cell.props.text = data_obj.name if data_obj else ""
|
||||
else:
|
||||
cell.props.text = ""
|
||||
|
@ -1,13 +1,10 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_signal_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<object class="GtkEntryCompletion" id="handler_entrycompletion">
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
@ -15,38 +12,53 @@
|
||||
</object>
|
||||
<object class="GtkTreeStore" id="treestore">
|
||||
<columns>
|
||||
<!-- column-name signal -->
|
||||
<column type="GObject"/>
|
||||
<!-- column-name owner_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name signal_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name detail -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name handler -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name user_data -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name swap -->
|
||||
<column type="gboolean"/>
|
||||
<!-- column-name after -->
|
||||
<column type="gboolean"/>
|
||||
<!-- column-name info -->
|
||||
<column type="GObject"/>
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<template class="CmbSignalEditor" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="treeview">
|
||||
<property name="focusable">1</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">treestore</property>
|
||||
<property name="tooltip-column">9</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="signal_id_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min-width">64</property>
|
||||
<property name="resizable">1</property>
|
||||
<property name="title" translatable="yes">Signal</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="signal_id">
|
||||
<signal name="edited" handler="on_detail_edited"/>
|
||||
<signal name="edited" handler="on_detail_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
@ -55,17 +67,16 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="handler_column">
|
||||
<property name="expand">1</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="min-width">64</property>
|
||||
<property name="resizable">1</property>
|
||||
<property name="title" translatable="yes">Handler</property>
|
||||
<property name="expand">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="handler">
|
||||
<property name="editable">1</property>
|
||||
<property name="editable">True</property>
|
||||
<property name="placeholder-text"><Enter callback></property>
|
||||
<signal name="edited" handler="on_handler_edited"/>
|
||||
<signal name="edited" handler="on_handler_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
@ -77,11 +88,10 @@
|
||||
<property name="title" translatable="yes">Data</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="user_data">
|
||||
<property name="editable">1</property>
|
||||
<property name="editable">True</property>
|
||||
<property name="placeholder-text"><object></property>
|
||||
<signal name="edited" handler="on_user_data_edited"/>
|
||||
<signal name="edited" handler="on_user_data_edited" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
@ -93,9 +103,8 @@
|
||||
<property name="title" translatable="yes">Swap</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="swap">
|
||||
<signal name="toggled" handler="on_swap_toggled"/>
|
||||
<signal name="toggled" handler="on_swap_toggled" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="active">6</attribute>
|
||||
</attributes>
|
||||
@ -107,9 +116,8 @@
|
||||
<property name="title" translatable="yes">After</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="after">
|
||||
<signal name="toggled" handler="on_after_toggled"/>
|
||||
<signal name="toggled" handler="on_after_toggled" swapped="no"/>
|
||||
</object>
|
||||
<!-- Custom child fragments -->
|
||||
<attributes>
|
||||
<attribute name="active">7</attribute>
|
||||
</attributes>
|
||||
@ -117,10 +125,13 @@
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="vexpand">1</property>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
@ -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)
|
83
cambalache/cmb_tree_view.py
Normal file
83
cambalache/cmb_tree_view.py
Normal file
@ -0,0 +1,83 @@
|
||||
#
|
||||
# CmbTreeView - Cambalache Tree View
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
|
||||
#
|
||||
# Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
#
|
||||
|
||||
import os
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_objects import CmbUI, CmbObject
|
||||
|
||||
|
||||
class CmbTreeView(Gtk.TreeView):
|
||||
__gtype_name__ = 'CmbTreeView'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._project = None
|
||||
self._selection = self.get_selection()
|
||||
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)
|
||||
|
||||
def _name_cell_data_func(self, column, cell, model, iter_, data):
|
||||
obj = model.get_value(iter_, 0)
|
||||
|
||||
if type(obj) == CmbObject:
|
||||
name = obj.name or ''
|
||||
cell.set_property('text', f'{name}({obj.type_id})')
|
||||
elif type(obj) == CmbUI:
|
||||
cell.set_property('text', obj.filename)
|
||||
|
||||
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 = self.props.model
|
||||
|
||||
if self._project:
|
||||
self._project.connect('selection-changed', self._on_project_selection_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
|
||||
|
||||
if len(selection) > 0:
|
||||
_iter = project.get_iter_from_object(selection[0])
|
||||
path = project.get_path(_iter)
|
||||
self.expand_to_path(path)
|
||||
self._selection.select_iter(_iter)
|
||||
else:
|
||||
self._selection.unselect_all()
|
||||
|
||||
def _on_selection_changed(self, selection):
|
||||
project, _iter = selection.get_selected()
|
||||
|
||||
|
||||
if _iter is not None:
|
||||
obj = project.get_value(_iter, 0)
|
||||
project.set_selection([obj])
|
@ -1,91 +0,0 @@
|
||||
#
|
||||
# CmbTypeChooserBar - Cambalache Type Chooser Bar
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_type_chooser.ui")
|
||||
class CmbTypeChooser(Gtk.Box):
|
||||
__gtype_name__ = "CmbTypeChooser"
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
"chooser-popup": (GObject.SignalFlags.RUN_LAST, None, (GObject.Object,)),
|
||||
"chooser-popdown": (GObject.SignalFlags.RUN_LAST, None, (GObject.Object,)),
|
||||
}
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE)
|
||||
selected_type = GObject.Property(type=CmbTypeInfo, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
type_label = Gtk.Template.Child()
|
||||
content = Gtk.Template.Child()
|
||||
all = Gtk.Template.Child()
|
||||
toplevel = Gtk.Template.Child()
|
||||
layout = Gtk.Template.Child()
|
||||
control = Gtk.Template.Child()
|
||||
display = Gtk.Template.Child()
|
||||
model = Gtk.Template.Child()
|
||||
extra = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._choosers = [self.all, self.toplevel, self.layout, self.control, self.display, self.model, self.extra]
|
||||
|
||||
self.connect("notify::project", self.__on_project_notify)
|
||||
self.connect("notify::selected-type", self.__on_selected_type_notify)
|
||||
|
||||
for chooser in self._choosers:
|
||||
chooser.connect("type-selected", lambda o, t: self.emit("type-selected", t))
|
||||
chooser.connect("notify::visible", self.__on_chooser_visible_notify)
|
||||
|
||||
def __on_project_notify(self, object, pspec):
|
||||
project = self.project
|
||||
self.selected_type = None
|
||||
|
||||
for chooser in self._choosers:
|
||||
chooser.project = project
|
||||
|
||||
def __on_selected_type_notify(self, object, pspec):
|
||||
project_target = self.project.target_tk if self.project else ""
|
||||
self.type_label.props.label = self.selected_type.type_id if self.selected_type else project_target
|
||||
|
||||
def __on_chooser_visible_notify(self, obj, pspec):
|
||||
if obj.props.visible:
|
||||
self.emit("chooser-popup", obj)
|
||||
else:
|
||||
self.emit("chooser-popdown", obj)
|
||||
|
||||
def select_type_id(self, type_id):
|
||||
info = self.project.type_info.get(type_id, None)
|
||||
if info:
|
||||
self.selected_type = info
|
||||
self.emit("type-selected", info)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooser, "CmbTypeChooser")
|
@ -1,147 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<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>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="control">
|
||||
<property name="category">control</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="display">
|
||||
<property name="category">display</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="extra">
|
||||
<property name="uncategorized-only">True</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="layout">
|
||||
<property name="category">layout</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="model">
|
||||
<property name="category">model</property>
|
||||
</object>
|
||||
<object class="CmbTypeChooserPopover" id="toplevel">
|
||||
<property name="category">toplevel</property>
|
||||
</object>
|
||||
<template class="CmbTypeChooser" parent="GtkBox">
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="content">
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="type_chooser_all">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">all</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="type_label">
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">1</property>
|
||||
<property name="sensitive">0</property>
|
||||
<attributes>
|
||||
<attribute name="style" value="italic"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="type_chooser_gtk">
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">toplevel</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for toplevels/windows">Toplevel</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">layout</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for container widgets like GtkBox grid">Layout</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">control</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for control widget like buttons, entries">Control</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">display</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for display widgets (label, image)">Display</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focus-on-click">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">model</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes" comments="Widget group for model objects (ListStore, TextBuffer)">Model</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="focusable">1</property>
|
||||
<property name="popover">extra</property>
|
||||
<property name="receives-default">1</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">pan-down-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,68 +0,0 @@
|
||||
#
|
||||
# CmbTypeChooserPopover - Cambalache Type Chooser Popover
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from .cmb_type_chooser_widget import CmbTypeChooserWidget
|
||||
|
||||
|
||||
class CmbTypeChooserPopover(Gtk.Popover):
|
||||
__gtype_name__ = "CmbTypeChooserPopover"
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
}
|
||||
|
||||
project = GObject.Property(type=CmbProject, flags=GObject.ParamFlags.READWRITE)
|
||||
category = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._chooser = CmbTypeChooserWidget()
|
||||
self._chooser.connect("type-selected", self.__on_type_selected)
|
||||
|
||||
self.set_child(self._chooser)
|
||||
self.set_default_widget(self._chooser)
|
||||
|
||||
for prop in [
|
||||
"project",
|
||||
"category",
|
||||
"uncategorized_only",
|
||||
"show_categories",
|
||||
"parent_type_id",
|
||||
"derived_type_id",
|
||||
]:
|
||||
GObject.Object.bind_property(self, prop, self._chooser, prop, GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
def __on_type_selected(self, chooser, info):
|
||||
self.emit("type-selected", info)
|
||||
self.popdown()
|
@ -1,208 +0,0 @@
|
||||
#
|
||||
# CmbTypeChooserWidget - Cambalache Type Chooser Widget
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GLib, GObject, Gio, Gtk
|
||||
|
||||
from .cmb_project import CmbProject
|
||||
from .cmb_type_info import CmbTypeInfo
|
||||
from . import constants
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_type_chooser_widget.ui")
|
||||
class CmbTypeChooserWidget(Gtk.Box):
|
||||
__gtype_name__ = "CmbTypeChooserWidget"
|
||||
|
||||
__gsignals__ = {
|
||||
"type-selected": (GObject.SignalFlags.RUN_LAST, None, (CmbTypeInfo,)),
|
||||
}
|
||||
|
||||
category = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
uncategorized_only = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
show_categories = GObject.Property(type=bool, flags=GObject.ParamFlags.READWRITE, default=False)
|
||||
parent_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
derived_type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
scrolledwindow = Gtk.Template.Child()
|
||||
listview = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__project = None
|
||||
self._search_text = ""
|
||||
self.__model = None
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.connect("map", self.__on_map)
|
||||
|
||||
def __on_map(self, widget):
|
||||
root = widget.get_root()
|
||||
|
||||
if root is not None:
|
||||
height = root.get_allocated_height() - 100
|
||||
if height > 460:
|
||||
height = height * 0.7
|
||||
|
||||
self.scrolledwindow.set_max_content_height(height)
|
||||
return False
|
||||
|
||||
def __type_info_should_append(self, info):
|
||||
retval = False
|
||||
|
||||
if not info.instantiable or info.layout not in [None, "container"]:
|
||||
return False
|
||||
|
||||
if info.category == "hidden":
|
||||
return False
|
||||
|
||||
if self.parent_type_id != "":
|
||||
retval = self.project._check_can_add(info.type_id, self.parent_type_id)
|
||||
else:
|
||||
retval = (
|
||||
info.category is None
|
||||
if self.uncategorized_only
|
||||
else (self.category != "" and info.category == self.category) or self.category == ""
|
||||
)
|
||||
|
||||
if retval and self.derived_type_id != "":
|
||||
retval = info.is_a(self.derived_type_id)
|
||||
|
||||
return retval
|
||||
|
||||
def __model_from_project(self, project):
|
||||
if project is None:
|
||||
return None
|
||||
|
||||
categories = {
|
||||
"toplevel": _("Toplevel"),
|
||||
"layout": _("Layout"),
|
||||
"control": _("Control"),
|
||||
"display": _("Display"),
|
||||
"model": _("Model"),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
infos = []
|
||||
|
||||
for key in project.type_info:
|
||||
# Ignore types with no name, just in case
|
||||
if key:
|
||||
infos.append(project.type_info[key])
|
||||
else:
|
||||
logger.warning("Tried to create a TypeInfo without a name")
|
||||
|
||||
infos = sorted(infos, key=lambda i: (order.get(i.category, 99), i.type_id))
|
||||
show_categories = self.show_categories
|
||||
last_category = None
|
||||
|
||||
for i in infos:
|
||||
if not self.__type_info_should_append(i):
|
||||
continue
|
||||
|
||||
# Append category
|
||||
if show_categories and last_category != i.category:
|
||||
last_category = i.category
|
||||
category = categories.get(i.category, _("Others"))
|
||||
store.append(CmbTypeInfo(type_id=f"<i><b>▾ {category}</b></i>"))
|
||||
|
||||
store.append(i)
|
||||
|
||||
# Special case External object type
|
||||
if show_categories or self.uncategorized_only:
|
||||
store.append(project.type_info[constants.EXTERNAL_TYPE])
|
||||
|
||||
return filter_model
|
||||
|
||||
@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_type_info_added)
|
||||
self.__project.disconnect_by_func(self.__on_type_info_removed)
|
||||
|
||||
self.__project = project
|
||||
|
||||
self.__model = self.__model_from_project(project)
|
||||
self.listview.set_model(Gtk.NoSelection(model=self.__model))
|
||||
|
||||
if project:
|
||||
project.connect("type-info-added", self.__on_type_info_added)
|
||||
project.connect("type-info-removed", self.__on_type_info_removed)
|
||||
|
||||
@Gtk.Template.Callback("on_searchentry_activate")
|
||||
def __on_searchentry_activate(self, entry):
|
||||
search_text = entry.props.text
|
||||
|
||||
info = self.project.type_info.get(search_text, None)
|
||||
if info:
|
||||
self.emit("type-selected", info)
|
||||
|
||||
@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)
|
||||
|
||||
@Gtk.Template.Callback("on_listview_activate")
|
||||
def __on_listview_activate(self, listview, position):
|
||||
info = self.__model.get_item(position)
|
||||
|
||||
if info is not None and info.project:
|
||||
self.emit("type-selected", info)
|
||||
|
||||
def __custom_filter_func(self, info, data):
|
||||
return info.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
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbTypeChooserWidget, "CmbTypeChooserWidget")
|
@ -1,52 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.95.0 -->
|
||||
<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>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="searchentry">
|
||||
<signal name="activate" handler="on_searchentry_activate"/>
|
||||
<signal name="search-changed" handler="on_searchentry_search_changed"/>
|
||||
</object>
|
||||
</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>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,377 +0,0 @@
|
||||
#
|
||||
# Cambalache Type Info wrapper
|
||||
#
|
||||
# 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
|
||||
# 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_objects_base import (
|
||||
CmbBaseTypeInfo,
|
||||
CmbBaseTypeDataInfo,
|
||||
CmbBaseTypeDataArgInfo,
|
||||
CmbBaseTypeInternalChildInfo,
|
||||
CmbTypeChildInfo,
|
||||
CmbSignalInfo,
|
||||
)
|
||||
from .cmb_property_info import CmbPropertyInfo
|
||||
|
||||
from .constants import EXTERNAL_TYPE, GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE
|
||||
|
||||
from cambalache import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbTypeDataArgInfo(CmbBaseTypeDataArgInfo):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeDataArgInfo<{self.owner_id}>::{self.key}"
|
||||
|
||||
|
||||
class CmbTypeDataInfo(CmbBaseTypeDataInfo):
|
||||
def __init__(self, **kwargs):
|
||||
self.args = {}
|
||||
self.children = {}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeDataArgInfo<{self.owner_id}>::{self.key}"
|
||||
|
||||
|
||||
class CmbTypeInternalChildInfo(CmbBaseTypeInternalChildInfo):
|
||||
def __init__(self, **kwargs):
|
||||
self.children = {}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeInternalChildInfo<{self.type_id}>::{self.internal_child_id}"
|
||||
|
||||
|
||||
class CmbTypeInfo(CmbBaseTypeInfo):
|
||||
__gtype_name__ = "CmbTypeInfo"
|
||||
|
||||
type_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
||||
parent_id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT)
|
||||
parent = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
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":
|
||||
self.enum = self.__init_enum_flags("enum")
|
||||
elif self.parent_id == "flags":
|
||||
self.flags = self.__init_enum_flags("flags")
|
||||
|
||||
self.child_types = self.__init_child_type()
|
||||
|
||||
self.is_object = self.is_a("GObject")
|
||||
|
||||
self.instantiable = self.is_object and not self.abstract
|
||||
|
||||
self.is_menu_builtin = self.type_id in [GMENU_TYPE, GMENU_SECTION_TYPE, GMENU_SUBMENU_TYPE, GMENU_ITEM_TYPE]
|
||||
self.is_builtin = self.is_menu_builtin or self.type_id in [EXTERNAL_TYPE]
|
||||
|
||||
def __str__(self):
|
||||
return f"CmbTypeInfo<{self.type_id}>"
|
||||
|
||||
def __init_hierarchy(self):
|
||||
retval = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"""
|
||||
WITH RECURSIVE ancestor(type_id, generation, parent_id) AS (
|
||||
SELECT type_id, 1, parent_id
|
||||
FROM type
|
||||
WHERE parent_id IS NOT NULL AND
|
||||
parent_id != 'interface' AND
|
||||
parent_id != 'enum' AND
|
||||
parent_id != 'flags' AND
|
||||
type_id=?
|
||||
UNION ALL
|
||||
SELECT ancestor.type_id, generation + 1, type.parent_id
|
||||
FROM type JOIN ancestor ON type.type_id = ancestor.parent_id
|
||||
WHERE type.parent_id IS NOT NULL AND type.parent_id != 'object' AND ancestor.type_id=?
|
||||
)
|
||||
SELECT parent_id, generation FROM ancestor
|
||||
UNION
|
||||
SELECT type_iface.iface_id, 0
|
||||
FROM ancestor JOIN type_iface
|
||||
WHERE ancestor.type_id = type_iface.type_id
|
||||
ORDER BY generation;
|
||||
""",
|
||||
(self.type_id, self.type_id),
|
||||
):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
|
||||
return retval
|
||||
|
||||
def __init_interfaces(self):
|
||||
retval = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute("SELECT iface_id FROM type_iface WHERE type_id=? ORDER BY iface_id;", (self.type_id,)):
|
||||
retval.append(row[0])
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __init_properties_signals(self, Klass, table):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(f"SELECT * FROM {table} WHERE owner_id=? ORDER BY {table}_id;", (self.type_id,)):
|
||||
retval[row[1]] = Klass.from_row(self.project, *row)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __type_get_data(self, owner_id, data_id, parent_id, key, type_id, translatable):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# Recurse children
|
||||
for row in c.execute("SELECT * FROM type_data WHERE owner_id=? AND parent_id=?;", (owner_id, data_id)):
|
||||
_key = row[3]
|
||||
children[_key] = self.__type_get_data(*row)
|
||||
|
||||
c.close()
|
||||
|
||||
retval.args = args
|
||||
retval.children = children
|
||||
|
||||
return retval
|
||||
|
||||
def __init_data(self):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"SELECT * FROM type_data WHERE parent_id IS NULL AND owner_id=? ORDER BY data_id;", (self.type_id,)
|
||||
):
|
||||
key = row[3]
|
||||
retval[key] = self.__type_get_data(*row)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __type_get_internal_child(self, type_id, internal_child_id, internal_parent_id, internal_type, creation_property_id):
|
||||
retval = CmbTypeInternalChildInfo.from_row(
|
||||
self.project,
|
||||
type_id,
|
||||
internal_child_id,
|
||||
internal_parent_id,
|
||||
internal_type,
|
||||
creation_property_id
|
||||
)
|
||||
children = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
|
||||
# Recurse children
|
||||
for row in c.execute(
|
||||
"SELECT * FROM type_internal_child WHERE type_id=? AND internal_parent_id=?;",
|
||||
(type_id, internal_child_id)
|
||||
):
|
||||
key = row[1]
|
||||
children[key] = self.__type_get_internal_child(*row)
|
||||
|
||||
c.close()
|
||||
|
||||
retval.children = children
|
||||
|
||||
# Internal child back reference in property
|
||||
if creation_property_id:
|
||||
if creation_property_id in self.properties:
|
||||
self.properties[creation_property_id].internal_child = retval
|
||||
|
||||
return retval
|
||||
|
||||
def __init_internal_children(self):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"SELECT * FROM type_internal_child WHERE type_id=? AND internal_parent_id IS NULL ORDER BY internal_child_id;",
|
||||
(self.type_id,)
|
||||
):
|
||||
key = row[1]
|
||||
retval[key] = self.__type_get_internal_child(*row)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __init_child_constraint(self):
|
||||
retval = {}
|
||||
shortcuts = []
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute(
|
||||
"SELECT child_type_id, allowed, shortcut FROM type_child_constraint WHERE type_id=?;", (self.type_id,)
|
||||
):
|
||||
child_type_id, allowed, shortcut = row
|
||||
retval[child_type_id] = allowed
|
||||
if shortcut:
|
||||
shortcuts.append(child_type_id)
|
||||
|
||||
c.close()
|
||||
return retval, shortcuts
|
||||
|
||||
def __init_child_type(self):
|
||||
retval = {}
|
||||
|
||||
c = self.project.db.cursor()
|
||||
for row in c.execute("SELECT * FROM type_child_type WHERE type_id=?;", (self.type_id,)):
|
||||
type_id, child_type, max_children, linked_property_id = row
|
||||
retval[child_type] = CmbTypeChildInfo(
|
||||
project=self.project,
|
||||
type_id=type_id,
|
||||
child_type=child_type,
|
||||
max_children=max_children if max_children else 0,
|
||||
linked_property_id=linked_property_id,
|
||||
)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def __init_enum_flags(self, name):
|
||||
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,)):
|
||||
retval.append(row)
|
||||
|
||||
c.close()
|
||||
return retval
|
||||
|
||||
def is_a(self, type_id):
|
||||
return self.type_id == type_id or type_id in self.hierarchy
|
||||
|
||||
def get_data_info(self, name):
|
||||
parent = self
|
||||
while parent:
|
||||
if name in parent.data:
|
||||
return parent.data[name]
|
||||
|
||||
parent = parent.parent
|
||||
|
||||
return None
|
||||
|
||||
def find_data_info(self, data_id):
|
||||
def find_child_info(info, data_id):
|
||||
for name in info.children:
|
||||
child_info = info.children[name]
|
||||
if child_info.data_id == data_id:
|
||||
return child_info
|
||||
|
||||
retval = find_child_info(child_info, data_id)
|
||||
if retval:
|
||||
return retval
|
||||
|
||||
for name in self.data:
|
||||
info = self.data[name]
|
||||
if info.data_id == data_id:
|
||||
return info
|
||||
|
||||
retval = find_child_info(info, data_id)
|
||||
if retval:
|
||||
return retval
|
||||
|
||||
return None
|
||||
|
||||
def has_child_types(self):
|
||||
parent = self
|
||||
while parent:
|
||||
if parent.child_types:
|
||||
return True
|
||||
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)
|
@ -1,222 +0,0 @@
|
||||
#
|
||||
# Cambalache UI wrapper
|
||||
#
|
||||
# Copyright (C) 2021 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
from gi.repository import GObject, Gio
|
||||
|
||||
from .cmb_path import CmbPath
|
||||
from .cmb_list_error import CmbListError
|
||||
from .cmb_objects_base import CmbBaseUI, CmbBaseObject
|
||||
from cambalache import getLogger, _
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CmbUI(CmbBaseUI, Gio.ListModel):
|
||||
__gsignals__ = {
|
||||
"library-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
||||
}
|
||||
|
||||
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
|
||||
|
||||
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,))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
@template_id.setter
|
||||
def _set_template_id(self, value):
|
||||
self.db_set("UPDATE ui SET template_id=? WHERE (ui_id) IS (?);", (self.ui_id,), value if value != 0 else None)
|
||||
|
||||
def __on_notify(self, obj, pspec):
|
||||
self.project._ui_changed(self, pspec.name)
|
||||
|
||||
# Update display name if one of the following properties changed
|
||||
if pspec.name in ["filename", "template-id"]:
|
||||
self.notify("display-name")
|
||||
|
||||
def list_libraries(self):
|
||||
retval = {}
|
||||
|
||||
for row in self.project.db.execute(
|
||||
"""
|
||||
SELECT DISTINCT t.library_id, NULL
|
||||
FROM object AS o, type AS t
|
||||
WHERE t.library_id IS NOT NULL AND o.ui_id=? AND o.type_id = t.type_id
|
||||
UNION
|
||||
SELECT library_id, version FROM ui_library WHERE ui_id=?
|
||||
""",
|
||||
(self.ui_id, self.ui_id),
|
||||
).fetchall():
|
||||
library_id, version = row
|
||||
|
||||
versions = []
|
||||
for row in self.project.db.execute(
|
||||
"SELECT version FROM library_version WHERE library_id=? ORDER BY version COLLATE version DESC;", (library_id,)
|
||||
).fetchall():
|
||||
versions.append(row[0])
|
||||
|
||||
retval[library_id] = {"target": version, "versions": versions}
|
||||
|
||||
return retval
|
||||
|
||||
def get_library(self, library_id):
|
||||
c = self.project.db.execute("SELECT version FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id))
|
||||
row = c.fetchone()
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def _library_changed(self, lib):
|
||||
self.emit("library-changed", lib)
|
||||
self.project._ui_library_changed(self, lib)
|
||||
|
||||
def set_library(self, library_id, version, comment=None):
|
||||
c = self.project.db.cursor()
|
||||
|
||||
try:
|
||||
if version is None:
|
||||
c.execute("DELETE FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id))
|
||||
else:
|
||||
# Do not use REPLACE INTO, to make sure both INSERT and UPDATE triggers are used
|
||||
count = self.db_get(
|
||||
"SELECT count(version) FROM ui_library WHERE ui_id=? AND library_id=?;", (self.ui_id, library_id)
|
||||
)
|
||||
|
||||
if count:
|
||||
c.execute(
|
||||
"UPDATE ui_library SET version=?, comment=? WHERE ui_id=? AND library_id=?;",
|
||||
(str(version), comment, self.ui_id, library_id),
|
||||
)
|
||||
else:
|
||||
c.execute(
|
||||
"INSERT INTO ui_library (ui_id, library_id, version, comment) VALUES (?, ?, ?, ?);",
|
||||
(self.ui_id, library_id, str(version), comment),
|
||||
)
|
||||
|
||||
self._library_changed(library_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"{self} Error setting library {library_id}={version}: {e}")
|
||||
|
||||
c.close()
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls, ui_id, filename):
|
||||
return os.path.basename(filename) if filename else _("Unnamed {ui_id}").format(ui_id=ui_id)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def display_name(self):
|
||||
filename = self.filename
|
||||
template_id = self.template_id
|
||||
|
||||
if filename is None and template_id:
|
||||
template = self.project.get_object_by_id(self.ui_id, template_id)
|
||||
if template:
|
||||
return template.name
|
||||
|
||||
return CmbUI.get_display_name(self.ui_id, filename)
|
||||
|
||||
def __get_infered_target(self, library_id):
|
||||
ui_id = self.ui_id
|
||||
|
||||
row = self.project.db.execute(
|
||||
"""
|
||||
WITH lib_version(version) AS (
|
||||
SELECT t.version
|
||||
FROM object AS o, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.type_id = t.type_id AND t.version IS NOT NULL
|
||||
UNION
|
||||
SELECT p.version
|
||||
FROM object_property AS o, property AS p, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.owner_id = t.type_id AND o.owner_id = p.owner_id
|
||||
AND p.version IS NOT NULL
|
||||
UNION
|
||||
SELECT s.version
|
||||
FROM object_signal AS o, signal AS s, type AS t
|
||||
WHERE t.library_id=? AND o.ui_id=? AND o.owner_id = t.type_id AND o.owner_id = s.owner_id
|
||||
AND s.version IS NOT NULL
|
||||
)
|
||||
SELECT MAX_VERSION(version) FROM lib_version;
|
||||
""",
|
||||
(library_id, ui_id, library_id, ui_id, library_id, ui_id),
|
||||
).fetchone()
|
||||
|
||||
return row[0] if row is not None else None
|
||||
|
||||
def get_target(self, library_id):
|
||||
target = self.get_library(library_id)
|
||||
if target is None:
|
||||
target = self.__get_infered_target(library_id)
|
||||
|
||||
if target is None:
|
||||
info = self.project.library_info.get(library_id, None)
|
||||
if info:
|
||||
return info.min_version
|
||||
|
||||
return target
|
||||
|
||||
# GListModel iface
|
||||
def do_get_item(self, position):
|
||||
ui_id = self.ui_id
|
||||
|
||||
# This query should use auto index from UNIQUE constraint
|
||||
retval = self.db_get(
|
||||
"""
|
||||
SELECT object_id
|
||||
FROM (
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY position ASC) rownum, object_id
|
||||
FROM object
|
||||
WHERE ui_id=? AND parent_id IS NULL
|
||||
)
|
||||
WHERE rownum=?;
|
||||
""",
|
||||
(ui_id, position+1)
|
||||
)
|
||||
if retval is not None:
|
||||
return self.project.get_object_by_id(ui_id, retval)
|
||||
|
||||
# This should not happen
|
||||
return CmbListError()
|
||||
|
||||
def do_get_item_type(self):
|
||||
return CmbBaseObject
|
||||
|
||||
@GObject.Property(type=int)
|
||||
def n_items(self):
|
||||
retval = self.db_get("SELECT COUNT(object_id) FROM object WHERE ui_id=? AND parent_id IS NULL;", (self.ui_id,))
|
||||
return retval if retval is not None else 0
|
||||
|
||||
def do_get_n_items(self):
|
||||
return self.n_items
|
@ -1,137 +0,0 @@
|
||||
#
|
||||
# CmbUIEditor - Cambalache UI Editor
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from cambalache import _
|
||||
from .cmb_ui import CmbUI
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_ui_editor.ui")
|
||||
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()
|
||||
authors = Gtk.Template.Child()
|
||||
translation_domain = Gtk.Template.Child()
|
||||
comment = Gtk.Template.Child()
|
||||
|
||||
fields = ["filename", "template_id", "description", "copyright", "authors", "translation_domain", "comment"]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._object = None
|
||||
self._bindings = []
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@GObject.Property(type=CmbUI)
|
||||
def object(self):
|
||||
return self._object
|
||||
|
||||
@object.setter
|
||||
def _set_object(self, obj):
|
||||
for binding in self._bindings:
|
||||
binding.unbind()
|
||||
|
||||
self._bindings = []
|
||||
|
||||
self._object = obj
|
||||
|
||||
if obj is None:
|
||||
self.set_sensitive(False)
|
||||
for field in self.fields:
|
||||
widget = getattr(self, field)
|
||||
|
||||
if type(widget.cmb_value) is int:
|
||||
widget.cmb_value = 0
|
||||
else:
|
||||
widget.cmb_value = None
|
||||
return
|
||||
|
||||
self.set_sensitive(True)
|
||||
self.template_id.object = obj
|
||||
self.filename.dirname = obj.project.dirname
|
||||
|
||||
# Set some default name
|
||||
self.filename.unnamed_filename = _("unnamed.ui")
|
||||
if not obj.filename and obj.template_id:
|
||||
template = obj.project.get_object_by_id(obj.ui_id, obj.template_id)
|
||||
if template:
|
||||
self.filename.unnamed_filename = f"{template.name}.ui".lower()
|
||||
|
||||
for field in self.fields:
|
||||
binding = obj.bind_property(
|
||||
field,
|
||||
getattr(self, field),
|
||||
"cmb-value",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
if obj.project.target_tk == "gtk-4.0":
|
||||
self.filename.mime_types = "application/x-gtk-builder;text/x-blueprint"
|
||||
|
||||
# filename -> format
|
||||
binding = obj.bind_property(
|
||||
"filename",
|
||||
self.format,
|
||||
"selected",
|
||||
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
|
||||
transform_to=self.__filename_to_format,
|
||||
transform_from=self.__format_to_filename,
|
||||
user_data=obj
|
||||
)
|
||||
self._bindings.append(binding)
|
||||
|
||||
self.format.show()
|
||||
self.format.set_sensitive(bool(obj.filename))
|
||||
else:
|
||||
self.filename.mime_types = "application/x-gtk-builder;application/x-glade"
|
||||
self.format.hide()
|
||||
|
||||
def __filename_to_format(self, binding, source_value, ui):
|
||||
if not source_value:
|
||||
self.format.props.sensitive = False
|
||||
return 0
|
||||
self.format.props.sensitive = True
|
||||
|
||||
return 1 if source_value.endswith(".blp") else 0
|
||||
|
||||
def __format_to_filename(self, binding, target_value, ui):
|
||||
if not ui.filename:
|
||||
self.format.props.sensitive = False
|
||||
return None
|
||||
self.format.props.sensitive = True
|
||||
|
||||
return os.path.splitext(ui.filename)[0] + (".blp" if target_value == 1 else ".ui")
|
||||
|
||||
|
||||
Gtk.WidgetClass.set_css_name(CmbUIEditor, "CmbUIEditor")
|
@ -1,213 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Created with Cambalache 0.97.1 -->
|
||||
<interface>
|
||||
<!-- interface-name cmb_ui_editor.ui -->
|
||||
<!-- interface-copyright Juan Pablo Ugarte -->
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="CmbTextBuffer" id="authors"/>
|
||||
<object class="CmbTextBuffer" id="comment"/>
|
||||
<object class="CmbTextBuffer" id="copyright"/>
|
||||
<object class="CmbTextBuffer" id="description"/>
|
||||
<template class="CmbUIEditor" parent="GtkGrid">
|
||||
<property name="column-spacing">3</property>
|
||||
<property name="row-spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Filename:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Description:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Copyright:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Authors:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Domain:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbFileButton" id="filename">
|
||||
<property name="hexpand">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">0</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbEntry" id="translation_domain">
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="placeholder-text" translatable="yes"><translation domain></property>
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">6</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">description</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">3</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">authors</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">5</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="CmbToplevelChooser" id="template_id">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="derivable-only">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="visible">True</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Template:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">2</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">copyright</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">4</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Comment:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">7</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="child">
|
||||
<object class="GtkTextView">
|
||||
<property name="buffer">comment</property>
|
||||
<property name="focusable">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="max-content-height">256</property>
|
||||
<property name="min-content-height">96</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="row">7</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">Format:</property>
|
||||
<layout>
|
||||
<property name="column">0</property>
|
||||
<property name="row">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkDropDown" id="format">
|
||||
<property name="halign">start</property>
|
||||
<property name="model">
|
||||
<object class="GtkStringList">
|
||||
<property name="strings">Gtk Builder
|
||||
Blueprint</property>
|
||||
</object>
|
||||
</property>
|
||||
<layout>
|
||||
<property name="column">1</property>
|
||||
<property name="column-span">1</property>
|
||||
<property name="row">1</property>
|
||||
<property name="row-span">1</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -1,113 +0,0 @@
|
||||
#
|
||||
# CmbUIRequiresEditor - Cambalache UI Requires Editor
|
||||
#
|
||||
# Copyright (C) 2023-2024 Juan Pablo Ugarte
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation;
|
||||
# version 2.1 of the License.
|
||||
#
|
||||
# library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
# Authors:
|
||||
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-only
|
||||
#
|
||||
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
from .cmb_ui import CmbUI
|
||||
from . import utils
|
||||
|
||||
|
||||
class CmbUIRequiresEditor(Gtk.Grid):
|
||||
__gtype_name__ = "CmbUIRequiresEditor"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__object = None
|
||||
self.__combos = {}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.column_spacing = 4
|
||||
self.props.row_spacing = 4
|
||||
|
||||
@GObject.Property(type=CmbUI)
|
||||
def object(self):
|
||||
return self.__object
|
||||
|
||||
@object.setter
|
||||
def _set__object(self, obj):
|
||||
if obj == self.__object:
|
||||
return
|
||||
|
||||
if self.__object:
|
||||
self.__object.project.disconnect_by_func(self.__on_project_added_removed)
|
||||
self.__object.disconnect_by_func(self.__on_library_changed)
|
||||
|
||||
self.__object = obj
|
||||
self.set_sensitive(obj is not None)
|
||||
|
||||
if obj:
|
||||
self.__object.project.connect("object-added", self.__on_project_added_removed)
|
||||
self.__object.project.connect("object-removed", self.__on_project_added_removed)
|
||||
self.__object.connect("library-changed", self.__on_library_changed)
|
||||
self.__update()
|
||||
|
||||
def __on_project_added_removed(self, project, obj):
|
||||
self.__update()
|
||||
|
||||
def __on_library_changed(self, ui, lib):
|
||||
combo = self.__combos.get(lib, None)
|
||||
if combo:
|
||||
combo.set_active_id(ui.get_library(lib))
|
||||
|
||||
def __update(self):
|
||||
self.__combos = {}
|
||||
|
||||
for child in utils.widget_get_children(self):
|
||||
self.remove(child)
|
||||
|
||||
if self.__object is None:
|
||||
return
|
||||
|
||||
i = 0
|
||||
for library_id, data in self.__object.list_libraries().items():
|
||||
label = Gtk.Label(label=library_id, visible=True, halign=Gtk.Align.START)
|
||||
combo = self.__combobox_new(library_id, **data)
|
||||
|
||||
# Keep a reference by library_id
|
||||
self.__combos[library_id] = combo
|
||||
|
||||
# Append to grid
|
||||
self.attach(label, 1, i, 1, 1)
|
||||
self.attach(combo, 2, i, 1, 1)
|
||||
i += 1
|
||||
|
||||
def __on_combobox_changed(self, combo, library_id):
|
||||
if self.__object:
|
||||
self.__object.set_library(library_id, combo.get_active_id())
|
||||
|
||||
def __combobox_new(self, library_id, versions=[], target=None):
|
||||
combo = Gtk.ComboBoxText(visible=True)
|
||||
|
||||
combo.append(None, "")
|
||||
|
||||
for version in versions:
|
||||
combo.append(version, version)
|
||||
|
||||
if target:
|
||||
combo.set_active_id(target)
|
||||
|
||||
combo.connect("changed", self.__on_combobox_changed, library_id)
|
||||
|
||||
return combo
|
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