Compare commits

...

1351 Commits
0.5.9 ... main

Author SHA1 Message Date
Juan Pablo Ugarte
53062b7d3c
Rolling 0.97.3 2025-06-22 16:41:07 -04:00
Juan Pablo Ugarte
9aaace82f7
Update Casilda dependency to 0.9.0
Remove merengue GSK_RENDERER var to enable GL client rendering.
2025-06-22 16:29:57 -04:00
Juan Pablo Ugarte
bbb2ce2bf9
cmb_init_dev add missing repository directory 2025-06-22 16:29:12 -04:00
Juan Pablo Ugarte
dd90c3fc2b
Bump to 0.97.2 2025-05-24 16:47:08 -04:00
Juan Pablo Ugarte
8742945f3f
CmbObjectEditor: allways pass info to data editors 2025-05-24 16:46:00 -04:00
Juan Pablo Ugarte
06c62349a7
CmbObjectDataEditor: rever changes made to fix data removal. 2025-05-24 16:45:31 -04:00
Juan Pablo Ugarte
bc6163b1ac
CmbObject: wrap remove_data() history commands 2025-05-24 16:44:44 -04:00
Juan Pablo Ugarte
ac74e23fde
CmbObjectData: group remove_data() history commands 2025-05-24 16:43:41 -04:00
Juan Pablo Ugarte
2e29c016f7
CmbNotification: log request error as info instead of warning 2025-05-24 16:43:01 -04:00
Juan Pablo Ugarte
b572a7eae3
Add blueprint support
- CmbProject: add blueprint load/save support
 - CmbWindow: show blueprint compiler errors on save
 - CmbUIEditor: add format dropdown
 - CmbView: show blueprint source code instead of XML
 - Add blueprint tests

Closes issue #80 "Support for blueprint file format"
2025-05-23 08:48:02 -04:00
Juan Pablo Ugarte
f96d19ab64
CmbDB: add private flags to control boolean and enum output 2025-05-21 19:08:34 -04:00
Juan Pablo Ugarte
680f9d3243
CmbFileButton: add mime_types, accept_label and unnamed_filename properties 2025-05-21 19:05:59 -04:00
Juan Pablo Ugarte
6fa7f22c9c
CmbTypeInfo: add use_nick param to enum_get_value_as_string() 2025-05-21 19:03:47 -04:00
Juan Pablo Ugarte
6ac16ca8c0
tests/test_import_export.py: Use parametrize 2025-05-21 19:01:53 -04:00
Juan Pablo Ugarte
e2dbb15a18
CmbSourceView: fix lang getter 2025-05-21 19:01:01 -04:00
Juan Pablo Ugarte
c57891a3c8
CmbUI: update display-name on filename or template-id change 2025-05-21 19:01:01 -04:00
Juan Pablo Ugarte
ccdf5d6ef0
CmbVersionNotificationView: do not show read more button if there is no link 2025-05-15 18:28:40 -04:00
Juan Pablo Ugarte
bb39873cd5
Rolling 0.97.1 2025-05-14 17:22:30 -04:00
Juan Pablo Ugarte
9529bfaf76
CmbObjectDataEditor: fix object data remove. 2025-05-14 17:17:53 -04:00
Juan Pablo Ugarte
13db1c20c2
CmbProject: fix GResource list model update 2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
963cee5eec
CmbResource: misc improvements
- Notify display-name when underlying props changes
 - Add private functions to update parent data needed to keep
   list model in sync
2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
08a73f9c28
CmbResourceEditor: use open for choosing a file 2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
02935555bf
CmbDB: add update_gresource_children_position() 2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
befb056d2c
CmbFileButton: add use_open property 2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
9cd6e05d5f
run-dev.py: Misc cleanups
- Make tests work without any special env set
 - Remove .local.env bash env
 - Simplify coverage script
2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
57a3f3832a
CmbNotification: generate UUID locally 2025-05-14 11:40:44 -04:00
Juan Pablo Ugarte
8c80fc5d3d
Bump to 0.97.0 - Development version 2025-04-21 07:25:10 -04:00
Juan Pablo Ugarte
7baf191c0b
Rolling 0.96.0 2025-04-20 10:09:40 -04:00
Juan Pablo Ugarte
8d170bab39
Update catalogs to SDK 48 2025-04-20 09:51:56 -04:00
Juan Pablo Ugarte
b592367640
cmb_catalog_gen: fix private library loading 2025-04-20 09:51:56 -04:00
Juan Pablo Ugarte
d41c08b68a
MrgApplication: fix css provider on load 2025-04-13 10:03:54 -04:00
Juan Pablo Ugarte
7de36972dd
data/cambalache-project.dtd: update format 2025-04-13 10:03:54 -04:00
Juan Pablo Ugarte
6b1ae2ad75
CmbProject: project file misc cleanups
- Add Cambalache version comment
 - Do not output cambalache version for unsaved ui files
 - Only save files when hash changes
2025-04-13 10:03:54 -04:00
Juan Pablo Ugarte
296d7767f9
Rolling 0.95.1
Test version for notifications
2025-04-03 20:10:21 -04:00
Juan Pablo Ugarte
e6e55da821
CmbNotificationListView: hide list view if model is empty 2025-04-03 20:10:08 -04:00
Juan Pablo Ugarte
530c211262
CmbNotification: disable notifications when using memory settings backend 2025-04-03 18:53:21 -04:00
Juan Pablo Ugarte
175d1d1b61
Port to SDK 48 and misc build cleanups
- Install private typelibs together with private libraries
 - Make Gtk3, handy and webkit optional
2025-03-27 20:53:20 -04:00
Juan Pablo Ugarte
1401c0c480
CmbNotifiaction: use keyfile to store settings if defaults to memory. 2025-03-11 11:21:12 -03:00
Juan Pablo Ugarte
acadc33f02
Tests: ensure tests dont get notification from backend 2025-03-09 12:46:38 -03:00
Juan Pablo Ugarte
9d8794cfb0
CmbNotification: add GSettings backend to user agent 2025-03-09 12:42:37 -03:00
Juan Pablo Ugarte
011c6dc082
Gtk Catalog: mark GtkComboBoxText item as translatable
Fix issue #273 "GtkComboBoxText items gets their translatable property removed"
2025-02-23 10:55:02 -03:00
Juan Pablo Ugarte
258f04825b
CmbNotification: minor cleanups
Add unit tests
2025-02-21 16:12:27 -03:00
Juan Pablo Ugarte
61893998dd
CmbWindow: add notification system 2025-02-16 17:10:50 -03:00
Juan Pablo Ugarte
7baa2cfbbe
CmbNotificationListView: add notification list view 2025-02-15 17:42:18 -03:00
Juan Pablo Ugarte
60f3e49672
CmbPollNotificationView: add poll notification view 2025-02-15 17:41:48 -03:00
Juan Pablo Ugarte
7213b0351f
CmbVersionNotificationView: add view for version notifications 2025-02-15 17:41:15 -03:00
Juan Pablo Ugarte
0709c8487b
CmbMessageNotificationView: add view for message notifications 2025-02-15 17:40:48 -03:00
Juan Pablo Ugarte
b2d95c576d
Utils: add utcnow() helper 2025-02-15 17:40:01 -03:00
Juan Pablo Ugarte
211d13b14a
CmbNotificationCenter: add main entry point to get app notifications 2025-02-15 17:38:51 -03:00
Juan Pablo Ugarte
5e16c964ee
MrgGtkLabel: handle use-markup property 2025-02-10 21:44:41 -03:00
Juan Pablo Ugarte
bda30ef76e
CmbView: use theme bg color on theme change
Should fix issue #272 "Background of compositor does not change colors, when adwaita colors are changed"
2025-02-06 19:19:03 -03:00
Juan Pablo Ugarte
8858257f6d
CmbView: fix merengue write command
- Loop until all data is written

Fix issue #269 "Failed to display some element of a validated ui file"
2025-01-29 21:41:33 -05:00
Juan Pablo Ugarte
14d0b3dd39
MrgApplication: add payload size to debug messages 2025-01-29 21:36:49 -05:00
Juan Pablo Ugarte
05bdb5d98e
CmbDB: strip object properties values 2025-01-29 21:35:43 -05:00
Juan Pablo Ugarte
af6258fe8a
MrgGtkMenu: improve window handling
Do not create custom window if menu is already attached to a widget.
2025-01-21 18:46:58 -05:00
Juan Pablo Ugarte
5de6214da2
MrgSelection: improve selection handling
- Select on button press instead of release
 - Do not consume initial events for GtkWindow and GtkHeaderBar
   This allows to move the window even if its not selected

Should close issue #267 "Make drag'n'drop of top-level more intuitive"
2025-01-21 18:39:51 -05:00
Juan Pablo Ugarte
4c8cbda88d
MrgApplication: removed unused preselected_widget member 2025-01-21 18:39:51 -05:00
Juan Pablo Ugarte
04ec6758f0
Catalogs: remove deprecated is-position flag 2025-01-17 19:17:20 -05:00
Juan Pablo Ugarte
ecf21c544d
CmbDB: cleanup is_position logic
- Do not try to unify position layout properties with object position.
 - Add special case for Gtk3 to generate placeholders using layout position property.

Fix issue #265 "GtkButtonBox shows too many buttons"
2025-01-17 18:57:04 -05:00
Juan Pablo Ugarte
a9c05ef202
CmbObject: remove position_layout_property logic 2025-01-17 18:56:35 -05:00
Juan Pablo Ugarte
df1dd90146
CmbLayoutProperty: remove is_position logic 2025-01-17 18:54:28 -05:00
Juan Pablo Ugarte
477c45a32c
cmb-catalog-gen: remove is-position logic 2025-01-17 18:52:56 -05:00
Juan Pablo Ugarte
99d58df208
Data Model: deprecate object is_model column 2025-01-17 18:52:23 -05:00
Juan Pablo Ugarte
5f69f152a1
CmbListError: add n_items property 2025-01-17 18:51:57 -05:00
Juan Pablo Ugarte
45f6cf920b
CmbProperty: ensure internal child tied to a property is created or removed.
Close issue #266 "Error "Unknown internal child: entry (6)" with particular GTK 3 UI file"
2025-01-16 18:05:41 -05:00
Juan Pablo Ugarte
425aed1872
CmbProject: fix api to allow creating internal objects 2025-01-16 18:05:06 -05:00
Juan Pablo Ugarte
0d93f151bf
CmbDB: Do not automatically create internal child that depend on a property 2025-01-16 18:04:40 -05:00
Juan Pablo Ugarte
a2945f2de2
CmbTypeInfo: backfill property info internal child property. 2025-01-16 18:03:28 -05:00
Juan Pablo Ugarte
34bdc5e530
CmbPropertyInfo: add internal_child property 2025-01-16 18:03:09 -05:00
Juan Pablo Ugarte
dcc4b0a199
Gtk Catalog: set GtkComboBox entry internal child creation property id.
GtkComboBox only creates entry internal child is has-entry is true
2025-01-16 18:01:15 -05:00
Juan Pablo Ugarte
3f23b4a9e7
Data Model: add creation_property_id to type_internal_child
This data is to known if there is a property that handles the
creation of the internal child.
2025-01-16 18:00:21 -05:00
Juan Pablo Ugarte
4db249bf28
CmbProject: ignore negative positions on undo/redo
Fix issue #264 "Error undoing removal of parent GtkGrid"
2025-01-15 19:00:55 -05:00
Juan Pablo Ugarte
8b02d13e01
run-dev.sh: Bind translation domain
This fixes running dev script in different languages.
2025-01-15 19:00:55 -05:00
Juan Pablo Ugarte
31b93cef82 Merge branch 'finnish' into 'main'
Finnish translation

See merge request jpu/cambalache!64
2025-01-15 14:30:01 +00:00
Erwinjitsu
c7885f5ab6 Finnish translation 2025-01-15 14:30:01 +00:00
Juan Pablo Ugarte
9a0db6c2f3
CmbProperty: fix property reset
Fix issue #263 "Translatable setting resets when label field is empty"
2025-01-10 18:16:47 -05:00
Juan Pablo Ugarte
cb1aed17b8
CmbListStore: remove unused file 2025-01-09 13:58:27 -05:00
Juan Pablo Ugarte
09d118dbc0
CmbDB: fix comment serialization 2025-01-06 17:46:14 -05:00
Juan Pablo Ugarte
829dc50bae
CmbWindow: make project name required when creating a new one 2025-01-06 17:46:14 -05:00
Juan Pablo Ugarte
94cce882aa
CmbDB: set swapped on signal import 2025-01-05 10:39:05 -05:00
Juan Pablo Ugarte
f0c1543982
CmbWindow: add back open dialog filter 2025-01-05 10:07:02 -05:00
Juan Pablo Ugarte
fb930b36da
cmb-catalog-gen: fix private library path 2025-01-05 14:51:20 -05:00
Juan Pablo Ugarte
48c004a1e6
Add private lib directory to GI repository path
Close issue #259 "Install private shared libraries in sub directories of the main library path"
2025-01-05 14:36:17 -05:00
Benson Muite
f3662b8095
Install private shared libraries in subdirectories 2025-01-05 10:13:19 -05:00
Juan Pablo Ugarte
986e3705b4
CmbApplication: fix open and import actionn parameters
Set empty string target_tk to None before importing file.

Close issue #255 "Unable to open files via the UI in a KDE Plasma session"
2025-01-04 15:17:46 -05:00
Juan Pablo Ugarte
e30b93506b
CmbDB: fix signal swap export when object is set.
Fix issue #260 "Wrong default for Swap setting in signals"
2025-01-03 19:17:16 -05:00
Juan Pablo Ugarte
907c4938a0
CmbWindow: ask user for gtk version on import
Ask user when importing a file with an undetermined target toolkit.

Close issue #255 "Unable to open files via the UI in a KDE Plasma session"
2025-01-03 17:28:23 -05:00
Juan Pablo Ugarte
874a506f47
CmbDB: add support for internal children
- Automatically create internal children when adding a new object
 - Do not export internal children when empty

Close issue #54 "Add support for internal children"
2024-12-28 15:27:32 -05:00
Juan Pablo Ugarte
2623583f3a
CmbWindow: show exception messages on delete action 2024-12-28 15:21:04 -05:00
Juan Pablo Ugarte
1f709586fb
CmbProject: refuse to delete an internal child 2024-12-28 15:21:04 -05:00
Juan Pablo Ugarte
6e1935d9fb
CmbObject: show internal name in display_name 2024-12-28 15:21:04 -05:00
Juan Pablo Ugarte
bc74332ecf
CmbTypeInfo: add internal_children member 2024-12-28 15:21:04 -05:00
Juan Pablo Ugarte
7aaabca392
CmbSourceView: make text property return None on empty string 2024-12-28 15:21:03 -05:00
Juan Pablo Ugarte
53854f6615
Data Model: add internal children support
- Add type_internal_child table
 - Extend xml catalog to define internal children
 - Infer internal child type from instance
 - Add internal children for varous types
2024-12-28 15:21:03 -05:00
Juan Pablo Ugarte
edee67cc72
Move coverage script to root 2024-12-22 12:34:46 -05:00
Juan Pablo Ugarte
937211565c
Update test images reference 2024-12-22 12:25:50 -05:00
Juan Pablo Ugarte
c6c278444f
tests/test_merengue_xml.py: fix project import_file() return values 2024-12-22 12:25:00 -05:00
Juan Pablo Ugarte
56b8af7f97
CmbProject: populate objects for old project 2024-12-22 12:24:23 -05:00
Juan Pablo Ugarte
526b880187
CmbDB: add warning if type info is not found 2024-12-22 12:23:49 -05:00
Juan Pablo Ugarte
e8cf3eb578
CmbShortcuts: add general section
Add missing shortcuts
2024-12-22 11:57:57 -05:00
Juan Pablo Ugarte
0e4d55eaa2
CmbObjectDataEditor: make grid not expand 2024-12-22 11:57:29 -05:00
Juan Pablo Ugarte
627c7a4fa9
Adw Catalog: disable AdwWindow titlebar and child properties 2024-12-22 11:26:01 -05:00
Juan Pablo Ugarte
7f6cd3c005
CmbFlagsEntry: add separator property 2024-12-22 11:19:07 -05:00
Juan Pablo Ugarte
ffdeb0c6c0
Metainfo: add unreleased notes 2024-12-15 16:52:15 -05:00
Juan Pablo Ugarte
d3e52c3b3b
CmbWindow: Unify ui, css and gresource import in one action. 2024-12-15 16:52:15 -05:00
Juan Pablo Ugarte
bfe13f87d4
CmbListView: improve CSS
- Use navitagion-sidebar CSS class as base
 - Add drop target CSS using outline and top/bottom border
2024-12-15 16:52:15 -05:00
Juan Pablo Ugarte
e4a6ae3e36
CmbProject: update path hierarchy on filename change 2024-12-15 16:52:15 -05:00
Juan Pablo Ugarte
e242742a29
CmbListView: port CmbColumnView to GtkListView
Show files in their directory structure
2024-12-14 10:24:44 -05:00
Juan Pablo Ugarte
458e93abce
CmbFileButton: add new control to edit filenames 2024-12-13 21:19:09 -05:00
Juan Pablo Ugarte
1c0d1a686a
Port Cambalache project to new version 2024-12-13 16:14:50 -05:00
Juan Pablo Ugarte
0ce1286acc
CmbProject: load UI in topological order
Add template-class and template-dependency arguments to ui element.
2024-12-13 16:14:50 -05:00
Juan Pablo Ugarte
f6ffc64988
CmbWindow: add GResource support
Close issue #145 "Consider Cambalache to manage resource description file for building the resource bundle"
2024-12-13 16:14:50 -05:00
Juan Pablo Ugarte
5ca4aa2377
CmbProject: Add support for GResource
Moved load and save functionality from CmbDB.
Remove unused export functions
2024-12-13 16:14:48 -05:00
Juan Pablo Ugarte
2468a22178
CmbDB: Add support for GResource
Remove project load and save functionality.
2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
3b6b8023af
CmbGResourceEditor: add new basic editor 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
afd241b7ea
CmbApplication: deprecate export-all option 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
23c6576514
CmbColumnView: add support for CmbGResource 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
4c219cd58a
CmbShortcuts: remove export shortcut 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
39b47123dd
CmbTutorial: remove mention to export 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
f941661e49
CmbUI, CmbUIEditor: remove export and remove buttons 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
572bf8f1ac
CmbCSSEditor: remove remove button 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
097ccce2f7
CmbGResource: add new object wrapper for new gresource table
Add new table to store GResource data in DB
2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
530ed75c79
cambalache/utils.py: add xml utilities and FileHash class 2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
be19adfba0
CmbDB: change project file format.
Store reference to XML files instead of exporting them.

Originally the import/export process was separated because Cambalache
did not supported all GtkBuilder features and I was trying to avoid
users assume that they could manually edit the XML files and everything
would keep working.

Now thanks to XML fragments fallback support, Cambalache will honor most
of the GtkBuilder features including custom biuildable iface tags

Main benefits of the new format are:
 - No need to export files.
 - Saving the project will update XML UI files
 - Merge friendly
2024-12-13 16:13:37 -05:00
Juan Pablo Ugarte
cf7a91ceb4
Data Model: improve history table
- Replace column_name TEXT with columns JSON

This allows to keep track of updates of multiple columns at the same
time, which is needed for unique indexes with more than one column.

This fixes error undoing a parent change like in D&D
2024-12-08 16:11:07 -05:00
Juan Pablo Ugarte
c88712db76
Gtk Catalog: add action child type to GtkDialog 2024-12-10 21:12:57 -05:00
Juan Pablo Ugarte
9ea01bdec7 Merge branch 'use-adw-about-dialog' into 'main'
Use AdwAboutDialog

See merge request jpu/cambalache!62
2024-12-02 02:49:17 +00:00
lo-vely
6b323e4475 Use AdwAboutDialog 2024-11-29 12:53:01 +02:00
Juan Pablo Ugarte
ee660f396e
CmbProject: cleanup UI and CSS display name on undo/redo messages 2024-11-26 18:42:58 -05:00
Juan Pablo Ugarte
92cc81894a
CmbUI, CmbCSS: add get_display_name() class method 2024-11-26 18:38:48 -05:00
Juan Pablo Ugarte
db70b1c2b8
Update test image ref 2024-11-26 18:04:06 -05:00
Juan Pablo Ugarte
9a91a2a085
Update translation files 2024-11-26 17:54:15 -05:00
Juan Pablo Ugarte
f4c662263d
CmbView: do not run merengue commands until merengue started.
Fix issue #253 "Error updating UI 1: gtk-builder-error-quark: .:8:1 Invalid object type 'AdwApplicationWindow' (6)"
2024-11-26 17:52:44 -05:00
Juan Pablo Ugarte
f5886a1727
run-dev.sh: pass arguments 2024-11-26 17:52:16 -05:00
Juan Pablo Ugarte
38bbf4e15f
CmbProject: fix warning on undo/redo message generation 2024-11-26 17:51:31 -05:00
Juan Pablo Ugarte
654e8739e9
Rolling 0.94.0 2024-11-25 18:30:12 -05:00
Juan Pablo Ugarte
bbb5c1b6ed
po/es.po: update translation 2024-11-25 18:26:50 -05:00
Juan Pablo Ugarte
15f08c470c
Update translation files 2024-11-25 18:26:09 -05:00
Juan Pablo Ugarte
8a253fcf67
CmbDB: fix object references in object data
Fix issue #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"
2024-11-24 10:19:24 -05:00
Juan Pablo Ugarte
1477b33c7b
CmbView: fix payload lenght calculation.
Calculate lenght after encoding to binary. (chars != bytes)
2024-11-24 10:17:38 -05:00
Juan Pablo Ugarte
69875d1fdb
MrgGtkDialog: add gtk 3 workaround to size allocation error.
Fix issue #251 "GTK 3 message dialog from specific .ui file rendered incorrectly"
2024-11-24 09:37:14 -05:00
Juan Pablo Ugarte
6b7ebb71d1
CmbView: change update-ui command payload
Do not write payload in just one line since it could get too big.
2024-11-21 22:25:04 -05:00
Juan Pablo Ugarte
8621dbf1e3
CmbPropertyLabel: fix close button css 2024-11-21 22:24:30 -05:00
Juan Pablo Ugarte
d210ec2193
CmbPropertyLabel: add focus outline
- Improve binding popover, add close button
2024-11-21 21:51:56 -05:00
Juan Pablo Ugarte
c197d14f6f
CmbTypeChooserWidget: migrate to GListStore 2024-11-21 18:57:13 -05:00
Juan Pablo Ugarte
42a21f8b49
MrgGtkWidget: do not add widget to window if it has a parent. 2024-11-21 18:49:21 -05:00
Juan Pablo Ugarte
d474122539
CmbView: add show_merengue property
Add property to show merengue xml in inspector
2024-11-21 18:48:12 -05:00
Juan Pablo Ugarte
168d6a09cf
CmbProject: improve get_object_by_key()
Check if key is numeric
2024-11-21 18:47:43 -05:00
Juan Pablo Ugarte
731c47903c
CmbDB: improve a11y property loading for gtk 3
- Ignore AtkObject:: prefix on a11ly properties
 - Give error is a11y property is unknown

Fix issue #250 "Error trying to import specific (LibreOffice) GTK 3 .ui file: "'NoneType has no attribute 'type_id'""
2024-11-21 18:41:16 -05:00
Juan Pablo Ugarte
fb37f3d831
CmbDB: fix add_object() position logic
Ensure position is not alrready used to prevent unique constraint errors.
2024-11-17 17:55:57 -05:00
Juan Pablo Ugarte
9b21bc15ab
CmbDB: add library to (external) type 2024-11-17 17:20:33 -05:00
Juan Pablo Ugarte
9fa28bebd4
DataModel: copy type iface on custom template creation
On conflict do nothing to alloy editing existing types ui files.
2024-11-17 17:18:53 -05:00
Juan Pablo Ugarte
d494ffeeec
CmbUI: add export() method 2024-11-13 23:07:27 -05:00
Juan Pablo Ugarte
27e7f75606
Update translation files
Updated spanish translation
2024-11-13 22:23:03 -05:00
Juan Pablo Ugarte
ed77ab5bc9
CmbApplication: fix empty filename on project creation. 2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
2e981adcaf
CmbWindow: add user feedback on UI export 2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
4b7aadb056
CmbUIEditor: improve signals
Remove default signal implementation and removed accumulator.
2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
5461d4030b
CmbProject: add return value to export() method
Return true if file was exported or not.
2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
4842a23663
Tests: add gtk 3 a11y test
Fix gtk 4 a11y test to match roles properties.
2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
162b4a0730
CmbDB: fix gtk3 a11y import 2024-11-13 22:21:59 -05:00
Juan Pablo Ugarte
9afe9a289d
CmbAccessibleEditor: hide a11y properties depending on role. 2024-11-09 22:33:47 -05:00
Juan Pablo Ugarte
142df82ff4
CmbDB: ignore a11y properties depending on role 2024-11-09 22:33:18 -05:00
Juan Pablo Ugarte
f9b90d9fdc
CmbWindow: check if filenames are mounted in the document portal
Fix issue #240 "Do not show cryptic paths for imported ui files (flatpak)"
2024-11-08 17:35:50 -05:00
Juan Pablo Ugarte
483b7e201e
tests: Update image commit sha 2024-11-06 22:19:01 -05:00
Juan Pablo Ugarte
38a50780e0
CmbDB: fix a11y test
Fix object a11y reference lists after import.
2024-11-06 22:19:01 -05:00
Juan Pablo Ugarte
1af92433e1
Tests: add gtk4 a11y import/export test 2024-11-06 22:19:01 -05:00
Juan Pablo Ugarte
1a60f42a77
CmbObject: fix tests
Fix data initialization in add_data() and remove_data()
2024-11-06 22:19:01 -05:00
Juan Pablo Ugarte
e6acd909c4
cmb-catalog-gen: make a11y metadata stable
Order list to make sure they do not change each time we run it.
2024-11-06 22:19:01 -05:00
Juan Pablo Ugarte
af0bdfe8f9
CmbProject: add a11y properties undo/redo messages support 2024-11-06 22:18:30 -05:00
Juan Pablo Ugarte
6771499cad
CmbDB: add support for CmbAccessibleList property type
Add custom support for loading and exporting a11y reference list properties.
2024-11-06 19:36:31 -05:00
Juan Pablo Ugarte
e5edade840
CmbObjectListEditor: new editor to choose a list of objects
Basic object list editor where you can write one object id per line.
2024-11-05 20:37:11 -05:00
Juan Pablo Ugarte
70dd8f311d
cmb-catalog-gen: use new CmbAccessibleList
Use new custom type for a11y properties that are a reference list.
2024-11-05 20:35:25 -05:00
Juan Pablo Ugarte
4b366b67fe
CmbPropertyInfo: add type_is_accessible() and accessible_property_remove_prefix() 2024-11-05 20:31:02 -05:00
Juan Pablo Ugarte
7f100a1bfd
CmbPropertyLabel: do not show a11y properties on binding popover 2024-11-05 20:29:45 -05:00
Juan Pablo Ugarte
7883ac4c51
Adwaita: disable AdwApplicationWindow::titlebar property
Fix issue #202 "cambalache crashes when using"
2024-11-04 17:37:13 -05:00
Juan Pablo Ugarte
9605d7aecf
CmbObjectEditor: make disabled properties insensitive. 2024-11-04 17:36:30 -05:00
Juan Pablo Ugarte
6561a6d9f1
cmb-catalog-gen: add support for disable properties 2024-11-04 17:35:42 -05:00
Juan Pablo Ugarte
fb8dd9422a
Data Model: add disabled column to property table 2024-11-01 20:13:17 -04:00
Juan Pablo Ugarte
f3d3858d9d
Catalogs: update catalogs with new property overrides.
Fix issue #203 "AdwActionRow : wrong default for activatable property"
2024-11-01 20:34:14 -04:00
Juan Pablo Ugarte
ef4cacca8c
cmb-catalog-gen: load external catalog properties
This is needed to override parent properties
2024-11-01 20:34:13 -04:00
Juan Pablo Ugarte
ddde4c5b4a
CmbApplication: simplify open project logic
Fix issue #241 "Handle adding widgets in empty workspace"
2024-11-01 20:34:12 -04:00
Juan Pablo Ugarte
6a5923d98b
ar.xjuan.Cambalache.json: identation from gnome-builder 2024-10-30 18:35:56 -04:00
Juan Pablo Ugarte
78e9c945d5
CmbWindow: use different file filter for gtk 3 and 4
Fix issue #236 "`Import` menu operation is not clear"
2024-10-30 18:34:29 -04:00
Juan Pablo Ugarte
14f45babb8
CmbAccessibleEditor: fix warning when selection a non widget object. 2024-10-30 18:33:44 -04:00
Juan Pablo Ugarte
473d8d1d58
CmbObjectData: fix parent initialization.
This fixes error trying to remove a data item from a loaded project.
2024-10-30 18:32:41 -04:00
Juan Pablo Ugarte
5d4ad6acff
CmbUI: fix bug in do_get_item()
Make sure we use row number instead of position.
2024-10-30 18:31:17 -04:00
Juan Pablo Ugarte
bdda000998
MrgAdwDialog: use placeholder
Close issue #234 "Hold <alt> to create object in place is not clear"
2024-10-30 18:29:47 -04:00
Juan Pablo Ugarte
5e52ee3b6b
Catalogs: update catalogs with support for overriden properties. 2024-10-30 18:28:35 -04:00
Juan Pablo Ugarte
be2f8e6332
CmbObjectEditor: do not show overriden properties, let the original class show it. 2024-10-30 18:27:21 -04:00
Juan Pablo Ugarte
5179476882
cmb-catalog-gen: add support for property overrides.
Override properties that change the default value of a parent class property.
2024-10-30 18:25:24 -04:00
Juan Pablo Ugarte
546992a157
Data Model: add original_owner_id column to property table
Add column to support property override.
This way a subclass can change a property default.
2024-10-30 18:23:27 -04:00
Juan Pablo Ugarte
42dbfe4bbc
CmbDB: import interface domain. 2024-10-29 17:23:23 -04:00
Juan Pablo Ugarte
17000fcf09
CmbAccessibleEditor: add role property
Filter a11y properties based on metadata
2024-10-25 21:34:39 -04:00
Juan Pablo Ugarte
9b64282ff9
CmbEnumComboBox: make model property non construct only 2024-10-25 21:34:03 -04:00
Juan Pablo Ugarte
5853f94bac
CmbDB: load a11y metadata 2024-10-25 21:33:35 -04:00
Juan Pablo Ugarte
0bbb0004dc
Update catalogs with a11y metadata 2024-10-25 21:32:58 -04:00
Juan Pablo Ugarte
b09e64efd2
cmb-catalog-gen: generate a11y metadata for gtk catalog
Use WAI ARIA specification documentation to generate metadata
needed to know which properties are available in which roles.
2024-10-25 21:32:34 -04:00
Juan Pablo Ugarte
6c06483261
CmbDB: add support for Gtk 3 a11y 2024-10-25 16:21:29 -04:00
Juan Pablo Ugarte
0a51869abc
CmbAccessibleEditor, CmbObject, CmbPropertyInfo: support CmbAccessibleAction
Add support for gtk 3 accessible support ifaces
2024-10-25 16:21:29 -04:00
Juan Pablo Ugarte
34608d0347
cmb-catalog-gen: add Atk and gtk 3 accessible custom properties 2024-10-25 16:21:29 -04:00
Juan Pablo Ugarte
6cfba4725b
MrgGtkGrid: remove get_position() method
Fix GtkGrid position duplicated id issue.
2024-10-23 18:32:19 -04:00
Juan Pablo Ugarte
1176ba4c4f CmbApplication: add app.quit shortcut
Close issue #242 "Support quit via Ctrl + Q"
2024-10-14 17:31:22 -04:00
Juan Pablo Ugarte
49b6aac7f3 CmbWindow: change preview button to hide placeholder
Close issue #239 "Preview feature is not clear"
2024-10-14 17:31:09 -04:00
Juan Pablo Ugarte
d21afce466 Accessibility: prefix properties to avoid name clash.
Prefix a11y props with cmb-a11y-[property|relation|state]
2024-10-14 17:23:05 -04:00
Juan Pablo Ugarte
8aacff8862 CmbWindow: do not use project dir as initial dir for file chooser
Close issue #235 "Remember last saved / open location"
2024-10-13 11:09:18 -04:00
Juan Pablo Ugarte
7d810921c4 CmbObject: improve display name for menu items 2024-10-13 10:55:24 -04:00
Juan Pablo Ugarte
39571d9a8e CmbWindow: improve main menu import item
Make it explicit that import is for UI files

Close issue #236 "`Import` menu operation is not clear"
2024-10-13 10:43:31 -04:00
Juan Pablo Ugarte
0f38117095 CmbObject: improve display name
Close issue #233 "Widget tree is confusing"
2024-10-13 10:37:49 -04:00
Juan Pablo Ugarte
b7d19e6e89 Bump to version 0.93.0
This also bumps project file version since now we support a11y properties
2024-10-13 10:12:32 -04:00
Juan Pablo Ugarte
588fdc99c0 CmbWindow: add a11y support
Add a11y object editor

Close issue #137 "Add accessibility support"
2024-10-13 10:09:38 -04:00
Juan Pablo Ugarte
8ce6694883 CmbView: ignore a11y properties updates
a11y properties need a new command since they are not really properties.
2024-10-13 10:08:00 -04:00
Juan Pablo Ugarte
062782eeb6 CmbDB: add a11y import/export support 2024-10-13 10:05:40 -04:00
Juan Pablo Ugarte
b2ab6cbe51 CmbAccessibleEditor: add a11y editor
Add editor for a11y properties
2024-10-13 10:05:13 -04:00
Juan Pablo Ugarte
5268517b72 CmbBooleanUndefined: add new control widget
New control widget to edit boolean properties that can also be undefined.
2024-10-13 10:02:37 -04:00
Juan Pablo Ugarte
28e53a270e CmbPropertyInfo: add is_a11y member
Add member to quickly identify properties related to a11y
2024-10-13 10:01:28 -04:00
Juan Pablo Ugarte
942d1a2c52 CmbObject: add a11y properties to GtkWidget objects
A11y iface is not part of widget hierarchy so it wont show up
as regular properties.
2024-10-13 09:54:24 -04:00
Juan Pablo Ugarte
91a4e2ddee Catalogs: update gtk4 catalog with a11y ifaces 2024-10-13 09:52:57 -04:00
Juan Pablo Ugarte
c76b5df13e cmb-catalog-gen: add accessibility support
Create CmbAccessible[Property|Relation|State] interfaces with properties
taken from GtkAccessible enumerations.
2024-10-13 09:47:16 -04:00
Juan Pablo Ugarte
dd777b6971 CmbDB: misc improvements
- Add root tag check on import
 - Do not output templates properties for merengue
 - Output template real object id for merengue
2024-10-10 17:28:36 -04:00
Juan Pablo Ugarte
2164536c44 CmbProject: fix cut and paste commands
Properly update list model one cut and paste.
2024-10-07 18:18:15 -04:00
Juan Pablo Ugarte
6ef20fc74b CmbView: cleanup merengue process restart.
Simplify merengue restart process.

Fix issue #232 "Crashes when restarting workspace"
2024-10-08 08:30:59 -04:00
Juan Pablo Ugarte
f9e69a68dd CmbDB: fix template export for merengue
- Make sure template xml is merged with derived widgets
 - Fix layout property output for parent classes
2024-10-02 15:13:41 -04:00
Juan Pablo Ugarte
4619c8b95c CmbView: update xml on selection change 2024-10-02 15:13:41 -04:00
Juan Pablo Ugarte
32b4ab2141 CmbObject: fix layout properties
Check for layout properties in the whole hierarchy.
2024-10-02 15:13:41 -04:00
Juan Pablo Ugarte
24261e6bf1 CmbWindow: fix window state saving 2024-10-02 15:13:41 -04:00
Juan Pablo Ugarte
98312780b1 Rolling 0.92.0 2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
1300521cc5 Update translations 2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
0e6267b5cb Update metainfo file with release notes for 0.92 2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
77e5298b0a README.md: update
- Fix reference to run-dev.sh
 - Update Tools section
2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
aeb8ef9b36 CmbDB: only output placeholders for GtkBox
Fix issue with GtkOverlay
2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
2510f317c9 run-tests.sh: set HOME to ensure empty recent list 2024-09-26 17:24:13 -04:00
Juan Pablo Ugarte
a9f754c30f Flatpak: fix wlroots branch/tag 2024-09-25 16:49:53 -04:00
Juan Pablo Ugarte
5d5f0fba89 Catalogs: improve Adwaita catalog
- Set lots of categories
 - Mark AdwButtonRow icon name props
2024-09-25 16:49:53 -04:00
Juan Pablo Ugarte
632d1d7e24 MrgAdwDialog: add proper support
Create a AdwWindow and use adw_dialog_present() to add the dialog

Fix issue #231 "Workspace will crash with inserting Some Adw objects"
2024-09-25 16:49:53 -04:00
Juan Pablo Ugarte
a3f5e0cf1c Update spanish translation 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
891a6d414b CmbDB: fix history_update()
Store column mapping to know wich value to update
2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
03594263e9 Update cambalache project file 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
22b5bb9245 CmbView: add restart workspace button under error message 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
b404066054 CmbUIEditor: fix remove icon 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
d07c34ca22 SUPPORTERS.md: Update list 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
184cd035e2 CmbDB: bump format version to 0.91.3
Fix object position on projects with multiple UI files.
2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
03c6dbf2ad cambalache.pot: Update translations 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
87df6d1fda Metainfo: update release notes for 0.92.0 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
b729bb16e8 CmbProject: fix undo/redo message on DELETE 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
cfbae798a7 po/filter_translatable.py: add tiny script to update POTFILES 2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
8f28eeb07b Bump dependencies versions
- Casilda to 0.2.0
 - Gtk to 4.16
 - Adwaita to 1.6
2024-09-25 16:48:24 -04:00
Juan Pablo Ugarte
611fe64281 Tests: use CmbProject api for import and check if there is any warning 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
307b5beff5 CmbProject: fix undo/redo msg 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
54534a9566 CmbDB: fix wrong xml error reporting loading menus with custom attr.
Store full row on history update.
2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
17f229c030 Update diagrams with references to Casilda and cmb-catalog-gen 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
a9391bb801 Tests: add new test for enum and flags importing 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
8153419845 CmbEnumComboBox: use new enum_get_value_as_string() on setter 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
4ac1f622d6 CmbDB: convert enum and flags values to nicks on import 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
28b0ea54e4 CmbTypeInfo: add enum_get_value_as_string() and flags_get_value_as_string() 2024-09-22 10:07:47 -04:00
Juan Pablo Ugarte
c0f2f32697 Update test image reference 2024-09-19 22:11:15 -04:00
Juan Pablo Ugarte
cc40e31515 Update catalogs to SDK 47 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
7b6de971ed Update SDK to version 47 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
52105677ae cmb-catalog-gen: minor regressions fixes and improvements 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
f3a24ee140 Remove runtime dependency on broadway 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
e162dbb592 Remove ununsed TODO.md and minor Readme update 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
22ecab12f4 Add Changelog file 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
706eb0f571 cmb_create_editor() use text view for GBytes properties
CmbTextView: make text view bigger by default
2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
dc07b5d6c8 CmbDB: use CDATA for GBytes properties
Close issue #230 "Exporting byte data messes encoding (libxml)"
2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
a9b14cdeb2 Add SPDX-License-Identifier 2024-09-19 21:57:36 -04:00
Juan Pablo Ugarte
91c4cf1a1e CmbPorject, CmbUI, CmbObject: implement __bool__()
Since these objects are a list (GListModel) they can evaluate to False
if they are an empty list breaking existing code like

if project:
    project.connect()

To prevent this we implement __bool__ and always return True
2024-09-12 15:44:56 -04:00
Juan Pablo Ugarte
785277c43e Dockerfile: update build 2024-09-12 15:30:24 -04:00
Juan Pablo Ugarte
60b8d8ffd0 CmbSpinButton: do not notify cmb-value on setter 2024-09-12 15:30:24 -04:00
Juan Pablo Ugarte
6381b90300 CmbObjectEditor: unbind previous object bindings 2024-09-12 15:30:24 -04:00
Juan Pablo Ugarte
7ddbbe1f5d CmbOject: update layout position properties.
Finished handling position layout properties. (Gtk3 GtkBox::position)
2024-09-12 15:30:24 -04:00
Juan Pablo Ugarte
f02ce9fe09 CmbProject, CmbDB: unify history tables
Added table_pk, new_values and old_values columns to history table.
Replaced auto generated extra history tables with new history table columns.
2024-09-12 15:30:04 -04:00
Juan Pablo Ugarte
6db7158900 Makefile: Automatically install flathub beta repor and Sdk 2024-09-12 11:45:36 -04:00
Juan Pablo Ugarte
1bcc8cb123 README.md remove all mentions to broadway 2024-09-12 11:45:36 -04:00
Juan Pablo Ugarte
a174e956d0 CmbDB: load catalogs from catalogsdir
Do not rellt only in XDG_DATA_DIRS env.
Always load catalogs from intallation prefix.
2024-09-05 14:50:28 -04:00
Juan Pablo Ugarte
a66733cbc8 cambalache: fix loglevel name 2024-09-05 14:50:28 -04:00
Juan Pablo Ugarte
5b82235037 MrgGtkGrid: fix child position calculation 2024-09-05 14:50:28 -04:00
Juan Pablo Ugarte
496040af97 utils: fix get_pointer() coordinates
Properly translated coordinates
2024-09-05 14:50:28 -04:00
Juan Pablo Ugarte
d68b53ff62 Update tests images 2024-09-04 17:36:29 -04:00
Juan Pablo Ugarte
34317f0ea7 CmbWindow: fix recent menu on flatpak 2024-09-04 17:34:06 -04:00
Juan Pablo Ugarte
afaecba268 ar.xjuan.Cambalache.json: Move to Sdk 47 beta
Update catalogs
2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
144a35a552 cmb-catalog-gen: make cambalache-db internal tool public
Install new tool in order for 3rd parties to create catalogs more easily.
2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
ca5db9ebd4 CmbObjectEditor: fix tempalte switch issue 2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
bdf1abdc33 CmbToplevelChooser: add (None) option using a GtkFlattenListModel 2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
85af7e8e0c DB Migration: fix widget position convertion to 0.91.2 2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
64f383b4d2 CmbObject: notify display name props on ui template change 2024-09-04 14:46:32 -04:00
Juan Pablo Ugarte
4915f19f9f Rolling 0.91.3 2024-08-23 14:58:07 -04:00
Juan Pablo Ugarte
0155d16a04 CmbWindow: add save check on project close. 2024-08-23 10:37:36 -04:00
Juan Pablo Ugarte
3a446d9f25 Bump Casilda dependency to 0.1.1 2024-08-22 17:49:32 -04:00
Juan Pablo Ugarte
2603e4dd7c MrgGtkWidget, MrgGtkWindow: fix app_id prefix
CasildaCompositor only store window state if app_id starts with Casilda:
2024-08-22 17:48:18 -04:00
Juan Pablo Ugarte
69aa84bc6f CmbColumnView: improve D&D 2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
130218c688 CmbProject: fix undo/redo stack
Properly update GLisModel on undo/redo.
2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
d528bd7ae5 CmbWindow: catch undo redo exceptions 2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
3881e493b3 CmbDBProfile: implement custom sqlite connection for profiling
Intercept all sqlite queries and store stats on __profile__ table.
2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
684bf27475 CmbDBInspector: update history tables 2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
599c1f7350 CmbUI: implement __str__ 2024-08-21 18:25:46 -04:00
Juan Pablo Ugarte
3dbb9ce55a Merge branch 'add-missing-cmb-list-error' into 'main'
Meson: add missing cmb_list_error.py file

See merge request jpu/cambalache!61
2024-08-20 19:29:37 +00:00
sid
04678015fb Meson: add missing cmb_list_error.py file 2024-08-17 19:53:58 +01:00
Juan Pablo Ugarte
10d67a882c Tests: update and fix unit tests 2024-08-15 17:45:27 -04:00
Juan Pablo Ugarte
3bd8d6489b CmbDBInspector: add column sorting support 2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
da2739ad12 CmbDB: add db profile feature
Use custom sqlite3 connection and cursor wrapper classes to log
queries in __profile__ table.

This is enabled with CAMBALACHE_DEBUG=db-profile env variable.
2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
befa213795 CmbColumnView, CmbOjectEditor, CmbSignalEditor, CmbView, CmbToplevelChooser: adapt to new API changes 2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
ce7c8f5296 CmbProject, CmbUI, CmbObject: implement GListModel iface
Implement GListModel directly from sqlite DB instead of
maintaing another tree list.
2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
670f96bac8 CmbDB: change object position column semantics
- Make position unique by ui_id, parent_id
 - Add update_children_position() method
2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
4a6200e88a CmbWindow: misc fixes
Update remove-parent action on selection change
2024-08-15 17:28:49 -04:00
Juan Pablo Ugarte
e76c592da9 Merge branch 'update-readme-flatpak' into 'main'
README.md: Add additional instructions for building flatpak

See merge request jpu/cambalache!60
2024-08-15 13:43:17 +00:00
Juan Pablo Ugarte
d1fcce7b8e Merge branch 'add-casilda-subproject' into 'main'
Add casilda as meson subproject

See merge request jpu/cambalache!59
2024-08-15 13:43:05 +00:00
sid
eb180a3de3 README.md: Add additional instructions for building flatpak 2024-08-13 18:21:47 +01:00
sid
2c72303ba0 Add casilda as meson subproject
Fixes https://gitlab.gnome.org/jpu/cambalache/-/issues/227
2024-08-13 18:17:47 +01:00
Juan Pablo Ugarte
66141f4ed2 CmbView: reimplement error message and context menu
Adapt to API removal from CasildaCompositor
2024-08-07 09:07:14 -04:00
Juan Pablo Ugarte
d2f5649cae CmbContextMenu: add GtkGraphicsOffload parent option 2024-08-07 09:06:38 -04:00
Juan Pablo Ugarte
84f34c0a49 run-test.sh update env vars 2024-08-05 19:04:15 -04:00
Juan Pablo Ugarte
28f470a3ce CmbToplevelChooser: add workaround for template autoselect bug 2024-08-05 19:03:42 -04:00
Juan Pablo Ugarte
d0c04302fd CmbView: start using Casilda library
Use CasildaCompositor instead of CmbCompositor
2024-08-05 19:02:24 -04:00
Juan Pablo Ugarte
42073e5d99 CmbDB: improve catalog loading
Load catalogs from GLib.get_system_data_dirs()/cambalache/catalogs and
~/.cambalache/catalogs

Fix issue #11 "Support 3rd party libraries"
2024-08-04 21:01:17 -04:00
Juan Pablo Ugarte
6670670f18 tools/cmb_init_dev.py: major cleanup
Since we have to build libs with meson, rely on it to build resources,
install catalogs, etc.
2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
975416463e Catalogs: fix dependencies
Ensure catalogs have the right dependency
2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
e7a6fd4a92 CmbColumnView: fix warning on project close 2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
fca2cc7e92 Merengue: load GtkStackPage support only for Gtk 4 2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
e3fa216276 CmbProject: fix runtime warning 2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
2c79bbfbb9 CmbWindow: update selected UI on start 2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
e4fc463359 cambalache-db: add --show-property-overrides option
Add option to show which properties should have defaults overrides
2024-08-04 20:46:16 -04:00
Juan Pablo Ugarte
386afb930a CmbProperty: add warning message for GFile properties
Fix issue #224 "GtkPicture:file property does not work out of the box"
2024-07-31 18:23:39 -04:00
Juan Pablo Ugarte
3853d901e6 CmbLibraryInfo: add third_party property
CmbView only load namespace if third_party is true.
2024-07-31 18:08:45 -04:00
Juan Pablo Ugarte
2121fe087d CmbDBInspector: add CmbDBStore class
Improve code a bit by implementing a GListModel and updating on CmbProject::changed
2024-07-31 18:05:26 -04:00
Juan Pablo Ugarte
ff670f4081 CmbObjectChooser: add support for drop target
Fix issue #219 "Move existing widgets / hierarchy sections into property fields"
2024-07-29 18:06:21 -04:00
Juan Pablo Ugarte
b50cd494d0 CmbColumnView: replaced GtkTreeView with GtkColumnView
- Reimplement drag and drop logic

Fix issue #225 "Cambalache crashes"
2024-07-29 18:06:00 -04:00
Juan Pablo Ugarte
132d6ab934 Merge branch 'jhbuild-run' into 'main'
run-dev: Support running in JHBUILD

See merge request jpu/cambalache!57
2024-07-22 18:46:47 +00:00
Juan Pablo Ugarte
acfac45485 Merge branch 'gbsneto/adwstackpage' into 'main'
Adw Catalog: Add child constraint to AdwViewStack

See merge request jpu/cambalache!58
2024-07-22 18:46:12 +00:00
Georges Basile Stavracas Neto
95c2662086 Adw Catalog: Add child constraint to AdwViewStack
Add an AdwViewStack child constraint, and also a default widget to
AdwViewStackPage.
2024-07-22 12:43:41 -06:00
Marco Trevisan (Treviño)
81166070fa run-dev: Support running in JHBUILD 2024-07-22 20:18:08 +02:00
Juan Pablo Ugarte
e01bf6c996 Use master SDK 2024-07-22 14:03:02 -04:00
Juan Pablo Ugarte
eaae071c8c run-tests.sh: add wrapper for pytest 2024-07-20 14:40:53 -04:00
Juan Pablo Ugarte
1723fcfa1a CmbFileEntry: port to GtkFileDialog 2024-07-18 16:58:13 -04:00
Juan Pablo Ugarte
882bebebd0 Make X11 a soft requirement.
Only use x11 apis if available
2024-07-15 17:01:52 -04:00
Juan Pablo Ugarte
3f77956ff9 CmbObjectChooser: Improve API
- Replace prop property with project, is_inline, inline_object_id,
   inline_property_id and type_id props
 - Adapted CmbObjectDataEditor to new api

Fix issue #223 "Cannot add widgets to GtkSizeGroup"
2024-07-09 17:36:21 -04:00
Juan Pablo Ugarte
935a7ab768 CmbDB: output object name for object data arguments 2024-07-08 19:02:17 -04:00
Juan Pablo Ugarte
59e64e77bf CmbWindow: add shortcut to debug action 2024-07-08 19:01:41 -04:00
Juan Pablo Ugarte
785ae90126 CmbObjectDataEditor: do not expand content grid 2024-07-08 19:01:15 -04:00
Juan Pablo Ugarte
f6e880c2d5 README.md: Improve build instructions
- Be more explicit that the official way of running this is using Flatpak
 - Add note for non supported OS like Windows and MacOS
2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
b8361b1a77 Gtk Catalog: allow instantiating GtkWidget.
Allow to use GtkWidget as a template parent.

Fix issue #222 "cannot create instance of abstract (non-instantiatable) type 'GtkWidget'"
2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
1159dd00a9 CmbView: add data model casic inspector.
Add custom inspector to replace using sqlitebrowser.
2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
a5d2d020f6 Merengue: properly set app_id on wrapper window for non window objects 2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
51b5d6ba02 Merengue: Add basic GtkStackPage support
Show actual stack child when a stack page is selected.
2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
59ac1d432f tools/cmb_init_dev.py: meson run detection improvement
Use build.ninja file to determine if meson ran successfuly.
2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
6e610c77d8 Update to wlroots master branch 2024-07-05 17:11:04 -04:00
Juan Pablo Ugarte
3eee2b4205 CmbTutorial: update tutorial to new headerbar 2024-06-26 17:42:52 -04:00
Juan Pablo Ugarte
48d7835041 CmbWindow: cleanup headerbar
- Make buttons flat
 - Use AdwWindowTitle
 - Removed new project, save and save as buttons from headerbar
 - Set window title to project base name
 - Make window title and subtitle italic when project has changes
2024-06-26 17:40:19 -04:00
Juan Pablo Ugarte
f41f6ae14c CmbView: use lock file for merengue socket
Use atexit callback to remove socket directory.
2024-06-26 17:38:25 -04:00
Juan Pablo Ugarte
8636c05b29 Add metainfo file to app resources 2024-06-26 17:36:17 -04:00
Juan Pablo Ugarte
e3f7b13876 CmbCompositor: clamp pointer position to widget geometry
This prevents the user moving a window completely outside the view.
2024-06-26 17:17:56 -04:00
Juan Pablo Ugarte
6cc1b0ead1 CmbEntry + others: add value chech on several control widgets
Avoid triggering a notify signal for cmb-value if there is no change.
2024-06-26 17:02:43 -04:00
Juan Pablo Ugarte
070aa9a180 CmbObjectDataEditor: do not set value if there is no change
Fix issue #220 "BUG: Typing cursor for style classes always in the front of style entries."
2024-06-26 17:01:46 -04:00
Juan Pablo Ugarte
a56e1fc74f CmbTranslatableWidget: revert UI changes
UI was deleted by mistake
2024-06-21 13:28:11 -04:00
Juan Pablo Ugarte
60e8d34f00 Rolling 0.91.1 2024-06-20 17:21:46 -04:00
Juan Pablo Ugarte
a38bfa323c Update metainfo file
- Add brand colors
 - Fix summary and autho warnings
2024-06-19 18:17:13 -04:00
Juan Pablo Ugarte
a2e381b114 CmbCompositor: fix warnings 2024-06-13 20:30:52 -04:00
Juan Pablo Ugarte
0aae1d0407 CmbApplication, CmbWindow: port to adwaita
Close issue #215 "Port UI to LibAdwaita"
2024-06-13 20:27:16 -04:00
Juan Pablo Ugarte
39776c533e cambalache.cmb: commit cambalache's cambalache project.
Update all ui files.
2024-06-13 20:26:11 -04:00
Juan Pablo Ugarte
119fb2a475 CmbWindow: handle opening builder files directly.
Fix issue #213 "Cannot open .ui file created using Gnome Builder"
2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
1e1d1badb9 CmbView: improve merengue comunication
Use GSocketService
2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
41ce52a212 Cambalache Utils: add content_type_guess() 2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
f2bd967a00 CmbCompositor: move socket file to temp directory 2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
ccfb7ae737 run-dev.sh: cleanup run dev script
- Return exit code from system call to meson and ninja commands
 - Replaced python script with shell script to setup environment more easily
 - Fix meson warnings
2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
6ced75e831 Set warning level to 2 and fix warnings 2024-06-07 18:46:45 -04:00
Juan Pablo Ugarte
ad89e6b9ca README.md: update dependencies 2024-05-09 17:41:48 -03:00
Juan Pablo Ugarte
3671c2d398 Replace WebkitWebView with new CmbCompositor widget. 2024-05-09 17:41:48 -03:00
Juan Pablo Ugarte
0f6a64525b CmbPrivate: add cmb_private_widget_set_application_id()
Add function to set app id
2024-05-09 17:41:48 -03:00
Juan Pablo Ugarte
47c1462ab0 libcambalache: add CmbCompositor widget
Add new C library for widgets better implemented in C
2024-05-09 17:41:48 -03:00
Juan Pablo Ugarte
ab59c8a8cc Bump development version 2024-05-09 17:41:48 -03:00
Juan Pablo Ugarte
a7936d60b0 Update meson dependencies to match SDK 46
Fix issue #216 "Cambalache 0.90.2 Segment faults"
2024-05-09 17:40:56 -03:00
Juan Pablo Ugarte
7d0e466ccf Webkit Catalog: add WebKitWebContext type 2024-04-08 09:53:51 -04:00
Juan Pablo Ugarte
3582d51bc8 Rolling 0.90.2 2024-04-03 22:43:02 -04:00
Juan Pablo Ugarte
3c1e7d11f5 MrgAdwDialog: add basic support for MrgAdwDialog 2024-04-03 22:43:02 -04:00
Juan Pablo Ugarte
e20d163a15 Update SUPPORTERS.md 2024-03-29 09:13:45 -04:00
Juan Pablo Ugarte
285434c123 update-supporters: add support for liberapay csv format 2024-03-29 09:13:13 -04:00
Juan Pablo Ugarte
59766f9cc1 Catalogs: update to SDK 46 2024-03-29 08:49:27 -04:00
Juan Pablo Ugarte
4f42200b7e Rolling 0.90.1 2024-03-25 17:40:38 -04:00
Juan Pablo Ugarte
6435a924c4 Cambalache: add workaround for icon search path on flatpak 2024-03-25 17:39:06 -04:00
Juan Pablo Ugarte
cd8c77d406 metainfo: update for sdk 46 changes 2024-03-25 17:38:39 -04:00
Juan Pablo Ugarte
7aeb9419e7 meson: use gnome.post_install() 2024-03-25 17:38:13 -04:00
Juan Pablo Ugarte
8203bda340 CmbWindow: made about dialog modal 2024-03-25 17:36:45 -04:00
Juan Pablo Ugarte
3dc22c9925 meson: move data icons to their own directory 2024-03-25 17:36:09 -04:00
Juan Pablo Ugarte
abadecda45 Rolling 0.90.0 2024-03-24 11:39:39 -04:00
Juan Pablo Ugarte
d5c72f6a15 Catalogs: update webkit catalog to SDK 46 2024-03-24 11:32:08 -04:00
Juan Pablo Ugarte
d2e0139b0a Bump SDK version to 46 2024-03-24 11:29:39 -04:00
Juan Pablo Ugarte
f3752d12f9 CmbProject: add add_parent() and remove_parent()
Fix issue #212 "[Feature] add parent"
2024-03-15 17:13:50 -04:00
Juan Pablo Ugarte
3900156456 CmbWindow: add remove_parent and add_parent actions 2024-03-11 18:01:56 -04:00
Juan Pablo Ugarte
b8ba3bbec0 CmbTreeView: set target_tk on context menu 2024-03-11 18:01:22 -04:00
Juan Pablo Ugarte
f6e59fbef4 CmbObject: clear layout properties on populate 2024-03-11 18:00:21 -04:00
Juan Pablo Ugarte
de2095e29e CmbContextMenu: add remove parent and add parent submenu 2024-03-11 17:59:34 -04:00
Juan Pablo Ugarte
450a4f00ac Flake8: fix warnings 2024-03-11 17:58:37 -04:00
Juan Pablo Ugarte
1f22224003 CmbDB: add missing data migration for format 0.17.3
Replace translatable "yes" with 1
2024-03-04 15:03:15 -05:00
Juan Pablo Ugarte
a5e03e693b CmbLayoutproperty: fix layout signal project emmision 2024-03-03 17:34:11 -05:00
Juan Pablo Ugarte
471477b4ce Merengue meson: install webkit plugin 2024-03-03 17:34:11 -05:00
Juan Pablo Ugarte
367a6bdf53 CmbSignalEditor: update state from new signal-changed signal
Fix issue #207 "Adding or changing data to signal doesn't activate 'Save' button"
2024-02-29 17:15:22 -05:00
Juan Pablo Ugarte
17728ace99 CmbProject: improve undo-redo stack
- Add support for signal change propagation
 - Improve undo/redo error messages for property update

Fix issue #184 "Headerbar save button not enabled when "translatable" checkbox's state is changed"
2024-02-29 17:14:32 -05:00
Juan Pablo Ugarte
705162d571 CmbProperty, CmbLayoutProperty: trigger project signal on notify 2024-02-29 17:07:23 -05:00
Juan Pablo Ugarte
439418807d CmbObject: add signal-changed signal 2024-02-29 17:06:44 -05:00
Juan Pablo Ugarte
9b630afcd5 Tests: update images submodule 2024-02-28 17:15:07 -05:00
Juan Pablo Ugarte
4b1f60a988 Tests: fix CmbWindow tests
Do not run on weston
2024-02-28 17:05:41 -05:00
Juan Pablo Ugarte
5a6e9f7b46 Tests: update fragment tests 2024-02-28 17:05:24 -05:00
Juan Pablo Ugarte
973c575908 CmbProject: fix error on object remove 2024-02-28 17:04:43 -05:00
Juan Pablo Ugarte
dafe8dbd78 CmbProject: object remove cleanup
Delete custom template instances on template object delete.
2024-02-22 18:18:13 -05:00
Juan Pablo Ugarte
315ca0cfb0 Merengue: add mrg_webkit module
- Modify WebKit2 (Gtk3) module for Webkit (Gtk4)
2024-02-15 18:12:09 -05:00
Juan Pablo Ugarte
a350cbb143 CmbProject: fix dnd errors 2024-02-15 18:12:09 -05:00
Juan Pablo Ugarte
6adce49744 UI files: open/export all UI files with Cambalache 2024-02-15 18:12:09 -05:00
Juan Pablo Ugarte
706d68b476 CmbFragmentEditor: add support for custom fragments 2024-02-15 18:12:09 -05:00
Juan Pablo Ugarte
9c12a62f36 Data Model: add object custom_child_fragment column
- Add type check to boolean columns
 - Add support for importing/exporting custom_child_fragment
 - Add typing to __node_get("translatable:bool")
   Make sure all boolean nodes have the type anotation
 - Bump project version to 0.17.3
2024-02-15 18:11:52 -05:00
Juan Pablo Ugarte
59ab9fae66 MrgGtkPopover: only create window if popover is not triggered from the UI 2024-02-15 18:00:02 -05:00
Juan Pablo Ugarte
964c788a2a CmbUI: improve get_display_name()
Return template name if it has no file set
2024-02-15 17:59:15 -05:00
Juan Pablo Ugarte
6b716b5dfc CmbTypeChooserWidget: set sw max size on map 2024-02-15 17:58:34 -05:00
Juan Pablo Ugarte
4b4f79ba49 CmbCssEditor: use popover for ui list 2024-02-15 17:56:46 -05:00
Juan Pablo Ugarte
f9299fb044 Merge branch 'cs-update' into 'main'
Fix and update Czech translation

See merge request jpu/cambalache!56
2024-02-14 16:27:40 +00:00
AsciiWolf
5649959c2a Update Czech translation 2024-02-13 00:06:52 +01:00
Juan Pablo Ugarte
e280c283d3 Merge branch 'update-appdata' into 'main'
data: Update screenshots and captions

See merge request jpu/cambalache!55
2024-02-12 22:29:15 +00:00
Martin Abente Lahaye
e672787fe3 data: Update screenshots and captions 2024-02-12 16:35:26 -03:00
Juan Pablo Ugarte
947462afa0 Rolling 0.17.2 2024-02-12 11:41:01 -05:00
Juan Pablo Ugarte
b09338642d Super massive commit to port to Gtk 4
Fix issue #40 "Update GUI to GTK 4"
2024-02-12 11:32:53 -05:00
Juan Pablo Ugarte
ba726349fb CmbDB: add support to load properties and signals from project. 2024-02-12 09:24:18 -05:00
Juan Pablo Ugarte
26e87f052c Gtk Catalog: add GtkBin and mark GtkCheckButton:label as translatable 2024-01-22 15:50:17 -05:00
Juan Pablo Ugarte
50153ffe76 Adw Catalog: add child types for AdwToolbarView
Fix issue #201 "AdwToolbarView needs special child types"
2024-01-22 09:27:22 -05:00
Juan Pablo Ugarte
69ceba6f42 MrgGtkBin: fix missing window placeholder 2024-01-22 09:27:22 -05:00
Juan Pablo Ugarte
72831b416b MrgGtkWidget: fix non toplevel widgets visivility 2024-01-22 09:27:22 -05:00
Juan Pablo Ugarte
cc8a773b40 cambalache-db: add support to override abstract type flag 2024-01-22 09:27:22 -05:00
Juan Pablo Ugarte
8081765541 Merge branch 'splitbutton' into 'main'
Mark AdwSplitButton.dropdown-tooltip translatable.

See merge request jpu/cambalache!51
2023-12-11 17:00:57 +00:00
Danial Behzadi
57c34d9050
Mark AdwSplitButton.dropdown-tooltip translatable. 2023-12-11 14:53:27 +03:30
Juan Pablo Ugarte
dad433dc72 postinstall: only precompile destdir
Fix issue #196 "postinstall.py is trying to modify files in prefix."
2023-12-04 06:25:06 -03:00
Juan Pablo Ugarte
1fe0680802 CmbDB: Do not "fix" object references on paste
Fix issue #199 "Copy and pasting messes references between widgets"
2023-12-04 06:13:12 -03:00
Juan Pablo Ugarte
bf0c00739f Data Model: clear object references when object is deleted 2023-12-04 06:12:07 -03:00
Juan Pablo Ugarte
1123ca8315 Adwaita: mark AdwMessageDialog as toplevel. 2023-10-30 18:38:47 -04:00
Juan Pablo Ugarte
2b0f5bc5b7 Adwaita: make AdwMessageDialog heading and body properties as translatable. 2023-10-30 18:35:44 -04:00
Juan Pablo Ugarte
4962ad41b8 CmbProject: fix object loading order
Sort by object id before creating CmbObject wrappers to ensure parents are always present.
2023-10-15 11:03:48 -04:00
Juan Pablo Ugarte
813dbfd986 Readme: fix meson url
Fix issue #194
2023-10-08 16:03:10 -04:00
Juan Pablo Ugarte
7a57a3accf Remove glade catalog.
It is time to use Cambalache
2023-09-24 15:54:17 -04:00
Juan Pablo Ugarte
0918dbbca5 Bump to development version 2023-09-24 15:53:55 -04:00
Juan Pablo Ugarte
dfe1124160 Rolling 0.16.0 2023-09-24 10:55:03 -04:00
Juan Pablo Ugarte
e3f096213b cambalache init: call Gtk.init()
Call gtk init to print error if display is not available.
2023-09-24 10:49:18 -04:00
Juan Pablo Ugarte
14e7809b86 Catalogs: mark all properties containing 'title' as translatable
Fix issue #188 "Missing translatable property for Gtk.ColumnViewColumn.title"
2023-09-24 10:42:17 -04:00
Juan Pablo Ugarte
8e97c8072e Tests images: update commit 2023-09-24 10:19:59 -04:00
Juan Pablo Ugarte
55c6be335a .gitignore: add __pycache__ 2023-09-24 10:06:04 -04:00
Juan Pablo Ugarte
4f08633565 CmbDB: ignore non menu object types in menu export
Fix issue #190 "Unassigned local variable"
2023-09-24 10:02:52 -04:00
Juan Pablo Ugarte
a525c0e25d Catalogs: update catalogs to SDK 45 libraries 2023-09-24 09:51:42 -04:00
Juan Pablo Ugarte
218ad6e8c9 ar.xjuan.Cambalache.json: bump to SDK 45
Move to development version 0.15.0
2023-09-24 09:51:12 -04:00
Juan Pablo Ugarte
1983d303b2 cambalache-db: add posibe translatable properties notification 2023-09-24 09:49:31 -04:00
Juan Pablo Ugarte
12fa07212a Rolling 0.14.0 2023-09-07 18:10:39 -04:00
Juan Pablo Ugarte
ee6b85a879 CmbObjectEditor: left align shortcuts flow box 2023-09-07 18:08:03 -04:00
Juan Pablo Ugarte
8b534cf680 Gtk Catalog: add GtkStackPage shortcut
This will add a button to create a page from GtkPage properties
2023-09-07 18:08:03 -04:00
Juan Pablo Ugarte
98a93e23bf cambalache-db: add support for <children-contraints> 2023-09-07 18:08:03 -04:00
Juan Pablo Ugarte
57fbde8d63 CmbDB: export inteface domain.
Fix issue #186 "Missing domain translation in exported UI XML file"
2023-09-06 08:55:34 -04:00
Juan Pablo Ugarte
c588e8dc92 CmbProject: remove unused get_library_latest() 2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
8bb9ea75fd CmbUI: change get_target() return value
Return library minimun version needed for UI
2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
e934d353fa CmbLiraryInfo: add min_version 2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
2e26f5fd33 CmbDB: rework library requirement query
Improve query to get the minimum library required for the UI to work
unless specified by user.
2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
d02050c075 Merengue: fix object_get_builder_id() for gtk4 non buildable objects. 2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
03f1309e72 CmbProject: add object to the end of the tree model 2023-09-04 11:52:53 -04:00
Anders Jonsson
dd7502c7c9 Add Swedish translation 2023-09-04 11:52:53 -04:00
Juan Pablo Ugarte
04a03fb1ae CmbProject: add support for 'not-inline-object' import errors 2023-09-04 11:22:30 -04:00
Juan Pablo Ugarte
c63c114cd0 CmbDB: fix export_signal() and import_property()
Fix error reporting when signal's owner id is not found.
Support xml files with the same property defined more than once, use latest value.

Fix issue #185 "Unable to import certain files converted from GTK3 to GTK4"
2023-09-04 11:21:27 -04:00
Juan Pablo Ugarte
ef4fbbfd08 Merge branch 'it_localization' into 'main'
Italian localization

See merge request jpu/cambalache!50
2023-09-01 13:47:42 +00:00
KL-B0
c727fb2d13 Update and enhance italian localization 2023-08-31 21:59:46 +02:00
Juan Pablo Ugarte
3feea166d9 Merge branch 'mark-translatable' into 'main'
data: mark a string translatable

See merge request jpu/cambalache!49
2023-08-30 15:24:51 +00:00
Sabri Ünal
84b91fd265 data: mark a string translatable
- Save and Export
2023-08-29 23:06:09 +03:00
Juan Pablo Ugarte
f8bc950397 Dockerfile: Add docker image recipe. 2023-08-10 18:10:33 -04:00
Juan Pablo Ugarte
239c16221d CmbWindow, CmbFileEntry: use Gtk.FileChooserNative
Start thinking in other OSes
2023-08-10 18:09:25 -04:00
Juan Pablo Ugarte
5dd33b62dc Tests: add test for templates 2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
c010d9e95f CmbDB: fix custom templates id output for merengue
Fix issue mentioned in issue #177 "Cambalache does not show anything for my derived template"
2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
fa843acef6 CmbSignalEditor: add version checks and tooltips 2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
1d3e6720de CmbTreeView: show version warnings
- Use underline for objects with version warnings
 - Use tooltip to show message
2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
e6fa06d4b3 CmbPropertyLabel: use new version_warning member to show errors to user. 2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
698980edb8 CmbObject, CmbProperty, CmbLayoutProperty: add version_warning member
Add member that holds any version warning like deprecation,

utils: add get_version_warning()
2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
4a0b011397 CmbObject: add inline_property_id
Replace CmbProject treemodel column with CmbObject property.
2023-07-27 20:11:22 -04:00
Juan Pablo Ugarte
455ec30530 Merge branch 'typo-fixes' into 'main'
Fix typos

See merge request jpu/cambalache!47
2023-07-27 14:51:45 +00:00
Anders Jonsson
f262d78660 Fix typos 2023-07-26 23:51:58 +02:00
Juan Pablo Ugarte
fbf57fab1e CmbProject: assume custom templates are derivable.
Fix issue #177 "Panel is not derivable"
2023-07-24 11:21:57 -04:00
Juan Pablo Ugarte
b473881e97 CmbTreeView: improve inline child display performance
Use CmbProject inline_property column instead of listening to
all project changes.
2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
8ee474ecd6 CmbProject: add inline_property as a new column 2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
e657f7fdf6 CmbIconNameEntry: make icon model a class member
Create icon model only once and reuse class member in each instance.
This helps speedups things specially when selecting objects with
many iconname properties.
2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
4bf5a0447c postinstall.py: precompile all python files 2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
cfab3b1dd6 CmbPropertyLabel: use new CmbUI get_target() method 2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
52e2f126f0 CmbUI: add get_target() 2023-07-20 17:04:08 -04:00
Juan Pablo Ugarte
03a491c620 Linter fixes 2023-07-18 09:27:16 -04:00
Juan Pablo Ugarte
39c4bb5671 control: add control widget submodule
Move all classes used as property editors to their own files in control submodule.
2023-07-18 09:27:16 -04:00
Juan Pablo Ugarte
587d81a362 CmbToplevelChooser: fix warning when closing empty project 2023-07-12 17:15:08 -04:00
Juan Pablo Ugarte
cdcc360c47 CmbWindow: fix error reporting 2023-07-12 17:14:31 -04:00
Juan Pablo Ugarte
02c851faed Tests: add import export test case for menu and GtkStringList 2023-07-11 22:56:07 -04:00
Juan Pablo Ugarte
1747e9b47d CmbObjectEditor: disable bindings for builtin objects (menu) 2023-07-11 22:55:32 -04:00
Juan Pablo Ugarte
abb4b96eef CmbObjectDataEditor: enable translatable custom data 2023-07-11 22:54:37 -04:00
Juan Pablo Ugarte
d234172daa CmbWindow: hide layout, signals and fragments editor for all builtin types (menu) 2023-07-11 22:53:56 -04:00
Juan Pablo Ugarte
1eaf58343b cmb_create_editor(): add support for translatable custom data 2023-07-11 22:52:38 -04:00
Juan Pablo Ugarte
2653e6ba13 CmbPropertyLabel: add bindable property 2023-07-11 22:51:25 -04:00
Juan Pablo Ugarte
8f8a2c1d9b Data Model: add translatable columns for custom data
- Add translatable column to type_data and translatable, translation_context
   and translation_comments to object_data
 - Add support for menu custom attributes and links
2023-07-11 22:51:03 -04:00
Juan Pablo Ugarte
6e3b07ce6e Tests: add template with inline object test 2023-07-10 15:27:17 -04:00
Juan Pablo Ugarte
8cf45b6868 CmbDB: fix template output
Fix output for templates with inline object properties
2023-07-10 15:26:18 -04:00
Juan Pablo Ugarte
e261d794f2 Meson: add missing file 2023-07-06 10:50:16 -04:00
Juan Pablo Ugarte
9c3d4c9b13 CmbObjectDataEditor: fix bug when self.data is none
Use Project from object instead of data.
2023-07-05 18:50:52 -04:00
Juan Pablo Ugarte
c5973fd4bf CmbProject: ident a couple of sql queries. 2023-07-05 18:50:20 -04:00
Juan Pablo Ugarte
73359349bd CmbTypeInfo: implement __str__ method 2023-07-05 18:49:36 -04:00
Juan Pablo Ugarte
1b9dfad9bb Tests: add test for requires screen 2023-06-30 17:17:21 -04:00
Juan Pablo Ugarte
31821598f6 CmbPropertyLabel: show deprecated and version mismatchs
Use text decoration and tooltip to show deprecations and version mismatches
2023-06-30 16:54:42 -04:00
Juan Pablo Ugarte
a5f06f140f CmbUIRequiresEditor: handle library updates 2023-06-30 16:53:53 -04:00
Juan Pablo Ugarte
07c0bd8102 CmbProject: add ui-library-changed signal
Implement undo/redo for ui_library table
2023-06-30 16:52:54 -04:00
Juan Pablo Ugarte
1e87132d14 CmbProperty, CmbLayoutProperty: add library_id member
Add convenience member to know in which library this property is defined.
2023-06-30 16:52:01 -04:00
Juan Pablo Ugarte
f92bf330a1 CmbUI: add library-changed signal
Fix list_libraries() order using custom collation
2023-06-30 16:43:03 -04:00
Juan Pablo Ugarte
cd98197bda CmbDB: move parse_version and version_cmp functions to utils.py 2023-06-30 16:38:06 -04:00
Juan Pablo Ugarte
67deb6fe5b CmbWindow: add CmbUIRequiresEditor to UI stack.
Add a way to edit UI requirements.
Undo/Redo support missing.
2023-06-29 10:53:45 -04:00
Juan Pablo Ugarte
2175ed5cff CmbObjectEditor: add child type shortcuts
Add buttons to create new children to (menu) type.

Fix issue #51 "menu element not supported"
2023-06-28 17:16:03 -04:00
Juan Pablo Ugarte
b96bbd5266 CmbDB: add support for <menu> element
Implement menu element using a non valid type name (menu)
2023-06-28 17:16:03 -04:00
Juan Pablo Ugarte
259201578e DB: add type_child_constraint table
- Add child_constraint and child_type_shortcuts with data from new table to CmbTypeInfo
2023-06-28 17:16:03 -04:00
Juan Pablo Ugarte
410718975d Gio Catalog: add GMenuModel type 2023-06-28 17:16:03 -04:00
Juan Pablo Ugarte
3b22616b3d Bump to development version 0.13 2023-06-28 17:16:03 -04:00
Juan Pablo Ugarte
8961e53662 Tests: add test for menu 2023-06-23 17:57:23 -04:00
Juan Pablo Ugarte
163f6d0ca6 Translations: Update pot files and es translation 2023-06-23 16:10:01 -04:00
Juan Pablo Ugarte
66fb328722 Tests: add simple test case to load an old format to test migrations 2023-06-23 16:09:32 -04:00
Juan Pablo Ugarte
c170999aa7 Merge branch 'fix-translatable' into 'main'
UI: Mark two strings untranslatable

See merge request jpu/cambalache!46
2023-06-20 15:40:15 +00:00
Sabri Ünal
5153c97cea UI: Mark two strings untranslatable
These symbols do not need to be marked as translatable.
2023-06-19 16:10:47 +03:00
Juan Pablo Ugarte
8d4787571b CmbDB: fix migration function
Fix issue #173 "Cambalache 0.12.0 can't open 0.10.3 project"
2023-06-17 16:04:38 -04:00
Juan Pablo Ugarte
8e94e3b5ec CmbWindow: update copyright date. 2023-06-17 16:04:10 -04:00
Juan Pablo Ugarte
d3b15c274a Rolling 0.12.0 - New Features release! 2023-06-16 10:58:46 -04:00
Juan Pablo Ugarte
e13dd3915e CmbDB: rename is_inline_object to disable_inline_object
- Make all object properties by default inline on Gtk 4 and add option to disable if needed.
 - Disable some inline object properties in GtkWindow

Fix issue #171 "Extended support for inline objects"
2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
c5ee0a4a3a CmbObjectEditor: make it derive from GtkScrolledWindow
- Add message to explain external objects

CmbWindow: hide layout, signals and fragments tabs if selected object
is an external type.
2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
95eeae42fc CmbDB: add support for external objects
- Improve format guessing function.
 - If target can not be guessed assume its the same as the project.
2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
362f391184 run-dev.py: pass command line arguments. 2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
64896ce632 CmbProject: fix get_ui_by_filename() when project does not have a filename. 2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
9f15decaa5 cambalache-db: add GtkShortcutTrigger and GtkShortcutAction as builtin types
- cmb_create_editor: Use CmbEntry for GtkShortcut[Trigger|Action]
2023-06-16 10:42:08 -04:00
Juan Pablo Ugarte
8a9a233afb Merge branch 'update-german-translation' into 'main'
Update German translation

See merge request jpu/cambalache!45
2023-06-15 20:01:41 +00:00
PhilProg
edaed7883e Update German translation 2023-06-13 17:41:50 +00:00
Juan Pablo Ugarte
43a407898f CmbObjectDataEditor: fix arg update function. 2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
df1330708d CmbView: update ui on object data changes 2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
e1fa7c061b CmbProject: Add support for object data undo/redo updates
Add object-property-binding-changed and object-data-changed signals
2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
7e9d7b51c1 CmbObjectData: expose _arg_changed() 2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
a21d3c6f48 CmbObject: add get_display_name() 2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
a60c9373d1 Data Model: fix object_property
Replace bind_source_id ON DELETE with a trigger on object table since
ON DELETE SET NULL affects all columns of the FK including ui_id
which can not be NULL.
2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
776486da40 CmbDB: fix clipboard_paste()
Disable FK while pasting.
2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
53daabfb1c README.md: Add make command reference 2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
d9c5ca83b3 CmbDB: misc cleanups
Use str.join() instead of for loop
2023-06-12 17:14:28 -04:00
Juan Pablo Ugarte
3113cca765 Merge branch 'hub-main-patch-14153' into 'main'
The id in the metainfo never has `.desktop`

See merge request jpu/cambalache!44
2023-06-12 15:01:33 +00:00
Juan Pablo Ugarte
922f0bb9a0 Adwaita Catalog: add AdwFlap child types 2023-06-11 15:54:08 -04:00
Hubert Figuière
0bb3397dd2 The id in the metainfo never has .desktop 2023-06-11 19:32:23 +00:00
Juan Pablo Ugarte
2b0452ae46 Use https url for submodules to allow everybody to build the flatpak 2023-06-09 11:46:09 -04:00
Juan Pablo Ugarte
c7f0349bdb Makefile: add clean and veryclean targets 2023-06-08 17:05:21 -04:00
Juan Pablo Ugarte
5f320420e6 CmbPropertyLabel: sort properties list.
Improve property compatibility tests.
2023-06-08 16:55:49 -04:00
Juan Pablo Ugarte
1de35f71e8 CmbView: update xml on project change 2023-06-07 17:05:35 -04:00
Juan Pablo Ugarte
377b926970 Gtk Catalog: fix gtk3 target and add GtkStringList custom tags
Fix issue #168 "Is there a way to add string items to a GtkStringList?"
2023-06-07 16:53:41 -04:00
Juan Pablo Ugarte
2a6bb674f7 cambalache-db: check for target in type tag and ignore undefined types 2023-06-07 16:45:59 -04:00
Juan Pablo Ugarte
ae32a803b0 CmbObjectEditor: Use CmbObjectChooser for iface types. 2023-06-07 16:01:33 -04:00
Juan Pablo Ugarte
23b1dc7bd8 Gio catalog: add supoprt for GListModel and GListStore types
Fix issue #167 "Gtk*Selection models are missing the model property"
2023-06-07 16:00:37 -04:00
Juan Pablo Ugarte
dfe2beba2a CmbToplevelChooser: fix filter
Set filter function before setting filter.
2023-06-06 16:37:17 -04:00
Juan Pablo Ugarte
c18d8d8923 Tests: integrate tests with meson
- No need for run-test.sh wrapper script.
 - tests module init launches the compositor and closes is at exit.
 - Individual tests can be run standalone using pystes as shebang.
 - Added coverage.sh wrapper script to run tests with coverage.
 - pytest on root level work out of the box.
 - Added tools/cmb_init_dev.py
2023-06-06 16:36:39 -04:00
Juan Pablo Ugarte
4cd218a6b5 CmbView: pass MERENGUE_DEV_ENV to merengue environment. 2023-06-06 16:36:39 -04:00
Juan Pablo Ugarte
70a3c78aa5 Update tests images commit 2023-06-01 16:56:01 -04:00
Juan Pablo Ugarte
b7eb758d3a Tests: improved gui tests
Added more tests and improve them by using gtk api to control the UI
instead of relying too much in Cmb API
2023-06-01 16:52:52 -04:00
Juan Pablo Ugarte
38c8950f63 Tests: add coverage support 2023-06-01 16:51:56 -04:00
Juan Pablo Ugarte
fff6c74632 CmbWindow: rename property_stack to object_stack 2023-06-01 16:50:47 -04:00
Juan Pablo Ugarte
c1d4054319 CmbView: use quit command instead of killing the process
This will enable collecting coverage report from Merengue process.
Fix various deprecations warnings
2023-06-01 16:49:18 -04:00
Juan Pablo Ugarte
1f3915467a MrgApplication: add quit command 2023-06-01 16:49:03 -04:00
Juan Pablo Ugarte
159feeae6c Tests: add cambalache-test-images submodule 2023-05-27 19:31:22 -04:00
Juan Pablo Ugarte
578168f9b8 tests: add GUI tests
Add basic GUI tests for main window, new project, UI editor and Property editor.
2023-05-27 19:22:04 -04:00
Juan Pablo Ugarte
360ca94546 CmbPropertyLabel: add layout_prop property
Add support for layout properties.
2023-05-27 19:21:24 -04:00
Juan Pablo Ugarte
ffb817a81a CmbTypeChooser: add select_type_id() 2023-05-24 17:03:39 -04:00
Juan Pablo Ugarte
62ad42b1b2 run-dev.sh: move rundev code to tests module 2023-05-23 23:37:47 -04:00
Juan Pablo Ugarte
42e5c1b06d CmbApplication: rename open() as open_project() 2023-05-23 23:20:51 -04:00
Juan Pablo Ugarte
c6bc714d0a run-dev.sh: move core function to tests/ 2023-05-23 23:16:25 -04:00
Juan Pablo Ugarte
6cd5d76511 CmbTutor, CmbWindow: fix use of deprecated flags 2023-05-23 23:14:44 -04:00
Juan Pablo Ugarte
bc9e05a620 CmbDB: fix tests.
etree.Element can not be used with isinstance
2023-05-23 16:36:11 -04:00
Juan Pablo Ugarte
ad622ff62a Tests: black update 2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
653690388e CmbObjectEditor: use CmbPropertyLabel
This add binding support.
2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
9ea7bb827c CmbPropertyLabel: initial implementation
Create widget to show a property name and show bind popover on click.
2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
2d9d9b5887 CmbObjectChooser, CmbFlagsEntry: minor fixes
- Make sure CmbObjectChooser can be used without setting "prop"
2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
3174179efc CmbProperty: add bind_property property
Add property to save bind_source_id, bind_owner_id and bind_property_id all at once.
2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
b9e7ba7eee cambalache: add icon path from resource
Add bind-symbolic and binded-symbolic icons
2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
3036adfe01 Tests: add bindings test case. 2023-05-23 09:57:02 -04:00
Juan Pablo Ugarte
9f6af6aac7 DataModel: add property bind columns to property table
Add support for property bindings.
Bump file format version to 0.11.4
2023-05-23 09:33:29 -04:00
Juan Pablo Ugarte
37afcb94cc Adwaita: mark content property of Window and ApplicationWindow as inline object
Help with issue #171
2023-05-19 18:02:59 -04:00
Juan Pablo Ugarte
160370e5c3 Tools: Fix cmb_utils_get_iface_properties()
- Use gtk_builder_get_type_from_name() instead of g_type_from_name()
 - Update catalogs to include missing iface properties.

Closes issue #170 "Support for actions (GtkActionable, menu models)"
Closes issue #169 "[main] GtkOrientable is missing in GtkBox properties (maybe in others too)"
2023-05-18 20:07:47 -04:00
Juan Pablo Ugarte
d151cb00de Add test case for merengue xml format 2023-05-12 22:44:29 -04:00
Juan Pablo Ugarte
0243a6fd29 run-dev.py: fix compile_private() 2023-05-09 21:19:02 -04:00
Juan Pablo Ugarte
b3635dab1d Tests: update tests 2023-05-09 21:08:26 -04:00
Juan Pablo Ugarte
3197be57a0 Update catalogs 2023-05-09 21:08:26 -04:00
Juan Pablo Ugarte
c06ee26b97 Gtk Catalog: mark GtkStackPage::child as required
Add default workspace value for child property.

Fix issue #163 "Add named object to Gtk.Stack"
2023-05-09 21:08:26 -04:00
Juan Pablo Ugarte
d6468eab96 Data Model: add required and workspace-default property
Update cambalache-db
2023-05-09 21:08:26 -04:00
Juan Pablo Ugarte
bbc1f5a5b1 CmbDB: fix export object for templates
Make sure we ignore object ids when exporting templates in merengue mode.

Fix for #166 "Allow external Widget or/and from another ui template"
2023-05-06 16:12:47 -04:00
Juan Pablo Ugarte
345aee9510 Data Model: add check to on_ui_template_id_update_add_type trigger
Ignore update when NEW value is the same as OLD
2023-05-06 15:48:45 -04:00
Juan Pablo Ugarte
cbf445b971 CmbObjectEditor: update template switch on template_id notify
This will make the update work on undo/redo
2023-05-06 15:48:12 -04:00
Juan Pablo Ugarte
f129e1394e CmbProject: ignore templates with out a name. 2023-05-06 15:46:36 -04:00
Juan Pablo Ugarte
d8abadd1b1 Meson: Add post install script deps checking
This should help with issue #164
2023-05-06 15:34:19 -04:00
Juan Pablo Ugarte
a5d65f5305 Add flake8 linter and fix all issues. 2023-05-01 17:23:37 -04:00
Juan Pablo Ugarte
83e7acc139 Start using black to format code 2023-04-30 15:28:11 -04:00
Juan Pablo Ugarte
db304f6112 CmbDB: fix history compression
Compress INSERT and UPDATE
2023-04-29 18:01:59 -04:00
Juan Pablo Ugarte
3be532f5af CmbFileEntry: add support for GFile type properties 2023-04-21 17:26:27 -04:00
Juan Pablo Ugarte
1ab85b3844 db-codegen: do not mark GFile as object types
GFile can not be created as a regular object it is supported natively by GtkBuilder
2023-04-21 17:24:57 -04:00
Juan Pablo Ugarte
2b4bb34913 tools/cmb-preview.py: add simple preview tool 2023-04-21 17:24:12 -04:00
Juan Pablo Ugarte
e14fa9d036 CmbProject: ensure filename extension 2023-04-06 09:32:23 -04:00
Juan Pablo Ugarte
70bbb90dd4 CmbWindow: fix project data debug for unnamed projects 2023-03-31 19:16:28 -04:00
Juan Pablo Ugarte
b1cfa48725 Mrg[GtkMenu|GtkPopover]: fix workspace error
Do not chainup object_changed() method
2023-03-31 16:35:48 -04:00
Juan Pablo Ugarte
695263eaa8 Merengue: fix resource loading when project path is undefined. 2023-03-24 17:27:50 -04:00
Juan Pablo Ugarte
0510f02600 Gtk Catalogs: add GtkFileFilter custom tags
Add test case for GtkFileFilter import/export
2023-03-24 17:27:50 -04:00
Juan Pablo Ugarte
d38aa94249 Bump to SDK 44 2023-03-24 17:27:50 -04:00
Juan Pablo Ugarte
0e356c87a1 Tests: add missing gtk4 tests for various customs tags. 2023-03-24 14:59:24 -04:00
Juan Pablo Ugarte
9c346bf074 CmbWindow: support for untitled projects
Allow user to create a project without choosing a file name.

Fix issue #160 "Faster prototyping"
2023-03-23 09:42:49 -04:00
Juan Pablo Ugarte
ed10cd7dc6 Gtk catalog: mark GtkPaned start|end-child properties as inline objects
Fixes issue #154 "GtkPaned: for properties to be set consistently, need to use start-child and end-child instead of <child>"
2023-03-02 18:50:29 -05:00
Juan Pablo Ugarte
19aa2c0d3f CmbProcess: ignore parent env vars present in custom vars
Fix issue #156 "GDK_BACKEND leaks to workspace process"
2023-03-02 18:19:16 -05:00
Juan Pablo Ugarte
7fde048bd2 Merge branch 'teeuwen-main-patch-49066' into 'main'
Add missing child type attributes to Gtk4 GtkActionBar

See merge request jpu/cambalache!43
2023-02-23 22:06:43 +00:00
B. Teeuwen
18429df363 Catalogs: add missing child type to Gtk4 GtkActionBar 2023-02-11 20:47:23 +00:00
Juan Pablo Ugarte
abc6654548 README.md: Update nanual installation instructions 2023-01-03 14:59:12 -05:00
Juan Pablo Ugarte
3b3fbc0978 Add fileformatversion to config 2023-01-03 14:48:56 -05:00
Juan Pablo Ugarte
3c0bc1cf99 Revert "Webkit2: update to version 6"
This reverts commit e8bcbf1144e1b2e87c8308835d9e5812670737dc.
2022-12-12 10:28:05 -05:00
Juan Pablo Ugarte
e07aece590 Split file format version from app version.
From now on file format version will be independent of the app version.
It will be bumped only on actual change and that will allow for older app
versions know if the format is not compatible.
2022-12-07 16:21:20 -05:00
Juan Pablo Ugarte
829966f823 Catalogs: update catalogs with missing ifaces 2022-12-05 18:36:14 -05:00
Juan Pablo Ugarte
5119257807 CmbDB: ignore requires for merengue
Avoid runtime error when using gtk development version
2022-12-05 18:35:24 -05:00
Juan Pablo Ugarte
c3d9b7f612 cambalache-db: include external ifaces
Do not ifnote ifaces defined in external catalogs.
2022-12-05 18:34:32 -05:00
Juan Pablo Ugarte
e8bcbf1144 Webkit2: update to version 6 2022-12-05 16:32:18 -05:00
Juan Pablo Ugarte
d1c521f398 Merge branch 'main' into 'main'
MS Windows README added, linked to main README

See merge request jpu/cambalache!42
2022-11-18 16:37:10 +00:00
Josh Moore
c333981001 MS Windows README added, linked to main README 2022-11-18 16:37:10 +00:00
Juan Pablo Ugarte
fc1e715f20 meson.build: add Webkit 5.0 dep for gtk 4 plugin
Close issue #148 "bug: preview display"
2022-11-08 08:38:18 -05:00
Juan Pablo Ugarte
43cf174c3e CmbObjectData: fix regression in add_data()
Return new data object in _add_child()
2022-10-17 16:57:31 -04:00
Juan Pablo Ugarte
ee8b5fb677 CmbUIEditor: add comment field 2022-10-01 11:03:01 -04:00
Juan Pablo Ugarte
e8f820fa96 CmbProject: create directories on export
Closes issue #143 "Support for nested files"
2022-10-01 11:02:03 -04:00
Juan Pablo Ugarte
7630a682cf CmbPropertyControls: ignore scroll event sofr spins and combos
Fix issue #146 "Scrolling a properties pane conflicts with mousewheel handling of property widgets"
2022-10-01 10:13:10 -04:00
Juan Pablo Ugarte
7b1d2b3171 CmbWindow: use about dialog response instead of just delete-event
Fix issue #147 "The "Close" button doesn't close the "About" dialog."
2022-09-29 15:41:27 -04:00
Juan Pablo Ugarte
210a6b18d0 CmbView: use CmbSourceView to show ui xml 2022-09-01 19:34:53 -04:00
Juan Pablo Ugarte
5712785f6c CmbObjectDataEditor: improve space for css classes
Add special case for items with one argument and no value.
Concatenate item key and argument ket and pack editor in value place
this halves the amount of space need in things like <styles>
2022-09-01 19:27:03 -04:00
Juan Pablo Ugarte
0a0a6b554e MrgGtkWidget: fix placeholder visibility in Gtk4 projects. 2022-08-30 18:17:27 -04:00
Juan Pablo Ugarte
a6ad36b97b CmbObjectEditor: use new object data editor for custom data. 2022-08-30 17:02:08 -04:00
Juan Pablo Ugarte
3cc22d53b0 CmbObjectDataEditor: add basic implementation
Add basic widget to edit object custom data recursively.
2022-08-30 17:01:28 -04:00
Juan Pablo Ugarte
e675e40417 cmb_property_controls: add cmb_create_editor()
Add function to create an editor from a type_id
2022-08-30 16:51:45 -04:00
Juan Pablo Ugarte
46466ba3df CmbProject: add object-data-added|removed and object-data-data-added|removed|changed signals.
Add internal api _append_object()

CmbObject: add data-added|removed signals
  Add object automaticaly to project

CmbObjectData: add data-added|removed and arg-changed signals

CmbTypeInfo: add find_data_info()
2022-08-30 16:47:21 -04:00
Juan Pablo Ugarte
92c37f3c01 CmbDB: do not export signals for merengue. 2022-08-25 16:45:50 -04:00
Juan Pablo Ugarte
3f92a16343 MrgGtkRevealer: fix object_changed() signature 2022-08-25 16:45:50 -04:00
Juan Pablo Ugarte
dba86194ca Merengue: add basic support for WebKitWebView 2022-08-25 16:43:49 -04:00
Juan Pablo Ugarte
f282628662 Catalogs: add webkit catalogs 2022-08-25 16:42:24 -04:00
Juan Pablo Ugarte
ec0a58e206 Bump SDK to master 2022-08-25 16:41:46 -04:00
Juan Pablo Ugarte
1509534736 Data Model: add hidden category 2022-08-25 16:39:00 -04:00
Juan Pablo Ugarte
190b1c1449 MrgGtkWidget: set all widgets visible. 2022-08-08 10:26:18 -04:00
Juan Pablo Ugarte
a0dc1fac94 MrgApplication: check directory exists before calling chdir() 2022-08-08 10:26:17 -04:00
Juan Pablo Ugarte
d4602b405b MrgGtkBox: only remove placeholders after the last child.
Fix issue #120 "Box doesn't remove empty space"
2022-07-31 17:24:09 -04:00
Juan Pablo Ugarte
c006071e06 MrgGtkNotebook: fix warning 2022-07-29 19:16:26 -04:00
Juan Pablo Ugarte
db7fc92fcd MrgController: replace object-changed signal with method
Use method and chain up to parent method.
2022-07-29 19:16:26 -04:00
Juan Pablo Ugarte
6362c4088f MrgApplication: ungrab pointer on widget selection
Fix issue #101 "Right clicking after deselcting button, brokes mouse input"
2022-07-29 19:16:26 -04:00
Juan Pablo Ugarte
14403c1113 Merengue: use new object-changed signal instead of connecting in each subclass 2022-07-28 18:20:58 -04:00
Juan Pablo Ugarte
89654ed0c7 MrgGtkNotebook: keep track of current page
Set back current page when object is rebuilt

Fix issue #96 "Window resize itself when cut content of notebook tab and go to first tab"
2022-07-28 18:20:58 -04:00
Juan Pablo Ugarte
647f9a77e3 MrgController: add object-changed signal
Make it easy for subclasses to do something when the object changes.
2022-07-28 18:20:58 -04:00
Juan Pablo Ugarte
81d90f1dfb CmbToplevelChooser: fix cmb_value getter 2022-07-28 17:28:02 -04:00
Juan Pablo Ugarte
f050a21234 CmbView: update ui on filename change
This is needed to reload images relative to filename.
2022-07-28 17:27:08 -04:00
Juan Pablo Ugarte
fad43c53a9 MrgApplication: add dirname property 2022-07-28 17:23:21 -04:00
Juan Pablo Ugarte
a3329e2d38 CmbWindow: remove hardcoded translators list
Set about dialog translators according to current locale.
2022-07-26 16:39:34 -04:00
Juan Pablo Ugarte
3944164e93 Merge branch 'it_translations' into 'main'
Update italian translations

See merge request jpu/cambalache!38
2022-07-26 20:35:47 +00:00
KLB0
4a772c5240 Update italian translations 2022-07-26 16:35:16 -04:00
Juan Pablo Ugarte
9c303a0421 Merge branch 'AdwWindowTitle' into 'main'
mark AdwWindowTitle translatable

See merge request jpu/cambalache!37
2022-07-25 15:05:24 +00:00
Danial Behzadi
11df90e0d3 mark AdwWindowTitle translatable 2022-07-25 15:05:24 +00:00
Juan Pablo Ugarte
e86bbfa121 Handy Catalog: remove enum types from model category 2022-07-22 18:40:02 -04:00
Juan Pablo Ugarte
8a345a6e32 Handy, Adwaita catalogs: categorize types
Add handy and adwaita categorization done by PhilProg.

Fix issue #138 "libadwaita widgets aren't categorized"
Fix issue #122 "Handy widgets not correctly categorized."
2022-07-22 18:26:24 -04:00
Juan Pablo Ugarte
5ad0a212d8 CmbUIEditor: set derivable_only on template toplevel chooser. 2022-07-22 17:01:03 -04:00
Juan Pablo Ugarte
c45380b226 CmbToplevelChooser: add derivable_only property 2022-07-22 17:01:03 -04:00
Juan Pablo Ugarte
bf9edb13ea CmbToplevelChooser: use CmbProject as model 2022-07-22 17:01:03 -04:00
Juan Pablo Ugarte
9760818f17 CmbProject: minors improvements
- Add get_iter_from_object_id()
 - Update template object row on UI template_id change
 - Add column name to history UPDATE messages
2022-07-22 17:01:03 -04:00
Juan Pablo Ugarte
a0e0383489 CmbProject: fix template change update warnings. 2022-07-19 16:47:54 -04:00
Juan Pablo Ugarte
fb7d103a25 CmbObjectEditor: disable template switch if type is not derivable. 2022-07-19 16:47:17 -04:00
Juan Pablo Ugarte
2eaec260b2 Catalogs: update files with type derivable flag 2022-07-19 16:46:24 -04:00
Juan Pablo Ugarte
9078ad4d1a Data Model: add type derivable column
Add column to store if a type is derivable or not
2022-07-19 16:45:43 -04:00
Juan Pablo Ugarte
274e76def1 Meta info: remove cvs-browser url
Add it back with new SDK

Fix issue #136 "Can't build via Flatpak"
2022-07-19 09:53:48 -04:00
Juan Pablo Ugarte
716deccaff CmbView: update ui custom_fragment change 2022-07-18 16:19:23 -04:00
Juan Pablo Ugarte
8ff2028a33 test: add custom fragments import/export tests 2022-07-17 15:55:45 -04:00
Juan Pablo Ugarte
888a5328eb CmbDB: import/export custom fragments
Load unknown tags at root and object level as custom fragments.
2022-07-17 15:53:51 -04:00
Juan Pablo Ugarte
a8a3402c41 CmbWindow: add object fragment editor 2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
a286ef29eb gladecambalache: update catalog 2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
9d520c5b60 CmbFragmentEditor: add basic xml fragment editor 2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
1c67b2e8b0 CmbCssEditor: use new CmbSourceView lang prop 2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
218326d655 CmbSourceView: add lang property
Add property to set language.
2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
d46d2c28d9 Data Model: add ui and object custom_fragment column
Add column to store custom fragments for objects and UI
This will be used as a fallback for any custom tag not supported by the app.
2022-07-17 15:20:11 -04:00
Juan Pablo Ugarte
b4c9abe166 MrgGtkWidget: fix regression
Hide wrapper window on null object
2022-07-14 17:13:24 -04:00
Juan Pablo Ugarte
4c3b147abe CmbWindow: make gtk4 the default 2022-07-14 17:13:09 -04:00
Juan Pablo Ugarte
9db210dac5 Bump version because of templates and css data model changes 2022-07-14 16:52:52 -04:00
Juan Pablo Ugarte
9c17441c8b CmbApplication: ask user before quitting with unsaved changes. 2022-07-14 16:51:41 -04:00
Juan Pablo Ugarte
dbcf8dcc12 CmbWindow: update last saved index on save.
This should disable save action after saving.
2022-07-14 16:50:32 -04:00
Juan Pablo Ugarte
6a96a21e12 CmbObjectEditor: simplify template selection
Add template switch after object id entry.
2022-07-14 16:49:48 -04:00
Juan Pablo Ugarte
5c488ab1f5 CmbTypeChooserWidget: Handle adding and removing types at runtime.
Udate automaticaly from project type-info signals.
2022-07-14 16:46:31 -04:00
Juan Pablo Ugarte
1f94367776 CmbProject: add signals for types
Add type-info-added, type-info-removed, type-info-changed and ui-changed signals
2022-07-14 16:44:59 -04:00
Juan Pablo Ugarte
37007c5914 CmbDB: add support for template types
Export a copy of the template for merengue

Rework db_backup() to move_to_fs()
2022-07-14 16:42:42 -04:00
Juan Pablo Ugarte
9d652323f3 Project Data Model: add triggers to update template types
Automatically update type from ui template
2022-07-14 16:41:41 -04:00
Juan Pablo Ugarte
323c3369ee CmbTypeInfo: make type_id and parent_id editable 2022-07-14 16:35:32 -04:00
Juan Pablo Ugarte
6ef196ed34 Base model: make all references to type on update cascade.
Prepare for types changing names.
2022-07-14 16:33:19 -04:00
Juan Pablo Ugarte
e51ff3b44b CmbWindow: remove quit action 2022-07-14 16:31:35 -04:00
Juan Pablo Ugarte
a0f1f77fef Adwaita, Handy: make translatables strings and inline objects
Fixes issue #135 "List of string properties that should be translatable in Adw"
2022-07-14 16:27:34 -04:00
Juan Pablo Ugarte
a2d4b0f1ec MrgGtkWidget: do not destroy wrapper window setting object to None
There seems to be a bug in gtk 4 broadway backend that makes the new window
smaller than the child allocation.

Fix issue #133 "Gtk.Grid Collapses so Not All Children Can be Seen"
2022-07-12 16:13:07 -04:00
Juan Pablo Ugarte
1b9bad42c4 Merge branch 'additional-app-metadata' into 'main'
Add additional appstream metadata

See merge request jpu/cambalache!36
2022-07-11 15:15:41 +00:00
PhilProg
425e7e18df Add additional appstream metadata 2022-07-11 15:15:41 +00:00
Juan Pablo Ugarte
b552b6cca0 CmbObjectEditor: use CmbTextView for GStrv properties
Fix issue #130 "GtkAboutDialog missing properties"
2022-07-11 09:21:11 -04:00
Juan Pablo Ugarte
d4f0b1eea6 Catalogs: fix support for Gstrv properties 2022-07-11 09:21:11 -04:00
Juan Pablo Ugarte
4acc33db43 CmbTextView: add new property control for multi line text properties 2022-07-11 09:21:11 -04:00
Juan Pablo Ugarte
4cfae101c0 CmbObjectEditor: add support for GdkPixbuf
Add new property control CmbPixbufEntry
2022-07-08 08:54:52 -04:00
Juan Pablo Ugarte
53899b44fb CmbView, MrgApplication: add dirname param to update-ui command 2022-07-08 08:54:52 -04:00
Juan Pablo Ugarte
29a572c836 Catalogs: update is_object property column 2022-07-08 08:54:52 -04:00
Juan Pablo Ugarte
138cf07c28 cambalache-db: mark object properties 2022-07-08 08:54:52 -04:00
Juan Pablo Ugarte
ba04e4ffd2 Data Model: remove trigger that updates property is_object column 2022-07-08 08:54:52 -04:00
Juan Pablo Ugarte
6c3522b844 CmbCSSEditor: set save button insensitive 2022-07-01 12:28:00 -04:00
Juan Pablo Ugarte
ef55b93016 CmbCSS: improve save function
Support saving to new file
2022-07-01 12:27:07 -04:00
Juan Pablo Ugarte
4bab64cb61 CmbProject: use get_display_name() to get css file display name 2022-07-01 12:26:05 -04:00
Juan Pablo Ugarte
d9e146c784 MrgCssProvider: support Gtk 4 api 2022-07-01 12:25:09 -04:00
Juan Pablo Ugarte
0594db0f9f Bump version to 0.11.0 2022-07-01 09:40:57 -04:00
Juan Pablo Ugarte
d3dd16e4a4 CmbPropertyControls: fix GtkSource gi required version 2022-07-01 09:40:31 -04:00
Juan Pablo Ugarte
575f696ac7 CmbWindow: add add-css action
Add menu button to add a new css file to project
2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
a12097b15e CmbView: add support for css commands 2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
99a4a5e59e CmbTreeView: add support for CmbCSS 2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
c5cf16b78b CmbUI: add get_display_name() 2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
35cc977064 CmbCSS, CmbCssEditor: add css object and editor type
Add CSS object wrapper and editor
2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
8ab9a5a91a CmbProject: add css api
Add css-added|removed|changed signals
Add get_ui_list(), add_css(), remove_css() and get_css_providers()
2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
ba440b38ca MrgApplication: add ccs command support
Add add_css_provider, remove_css_provider and update_css_provider commands
2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
d53f5c7c98 MrgCssProvider: add css class to handle css functionality 2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
08c36354fb CmbDB: add add_css() 2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
8ba7bcf748 Data Model: add ccs and css_ui tables
Add new wrapper class CmbBaseCSS
2022-06-28 18:57:13 -04:00
Juan Pablo Ugarte
4e43175b80 Merge branch 'nullndvoid-main-patch-25712' into 'main'
Fix simple spelling mistake in tutorial

See merge request jpu/cambalache!35
2022-06-28 15:41:13 +00:00
Jacob Hinchliffe
84577e6f9d Fix simple spelling mistake in tutorial 2022-06-27 21:41:11 +00:00
Juan Pablo Ugarte
aef7386a98 CmbTypeChooser: fix visual regression. 2022-06-27 16:41:22 -04:00
Juan Pablo Ugarte
0d7a50d900 CmbWindow, MrgPlaceholder: fix preview initialization
We need to invert the value of preview when setting placeholder visibility
2022-06-24 17:35:59 -04:00
Juan Pablo Ugarte
989499c74c CmbWindow: add extra shortcuts for adding/removing placeholders
Add use KP Insert/Delete and plus, minus and KP Add and PK substract to add and remove placeholders
2022-06-24 17:34:10 -04:00
Juan Pablo Ugarte
a5a7ffc190 Merge branch 'main' into 'main'
Add French translation

See merge request jpu/cambalache!34
2022-06-20 14:49:56 +00:00
rene-coty
08c986536f Added fr 2022-06-18 22:25:48 +00:00
rene-coty
bbb75c7217 Added French Translation 2022-06-18 22:24:50 +00:00
Juan Pablo Ugarte
94f45a2a48 CmbWindow: show number of files exported on export
Fix issue #123 "Export should be more user-friendly"
2022-06-18 18:02:35 -04:00
Juan Pablo Ugarte
af3a0e508f CmbProject: return number of files exported in export() 2022-06-18 18:02:34 -04:00
Juan Pablo Ugarte
596ee6a420 CmbWindow: add preview button in type chooser bar
Add button to enable preview mode, placeholders will be hidden.

Fix issue #113 "Add button/toggle to disable the placeholders and make the window look like it would look as an app"
2022-06-18 17:52:56 -04:00
Juan Pablo Ugarte
c95a1e448f CmbTypeChooser: move label to the end
To be able to add extra widgets before the label
2022-06-18 17:52:56 -04:00
Juan Pablo Ugarte
294a60fda6 CmbView: add preview property
Sync preview property with merengue
2022-06-18 17:52:56 -04:00
Juan Pablo Ugarte
7fd7794d54 MrgPlaceholder: connect to MrgApplication::preview property
And hide if in preview mode.
2022-06-18 17:52:16 -04:00
Juan Pablo Ugarte
c928ec0e83 MrgApplication: add preview property 2022-06-18 17:52:16 -04:00
Juan Pablo Ugarte
371dd9496b MrgApplication: add catch for namespace loading. 2022-06-17 19:22:40 -04:00
Juan Pablo Ugarte
6f36e8dfdb Add Handy and Adwaita dependencies.
Fix issue #121 "Adding handy fails silently without libhandy installed"
2022-06-17 19:13:25 -04:00
Juan Pablo Ugarte
2072dcd736 Rolling 0.10.2 2022-06-16 19:25:04 -04:00
Juan Pablo Ugarte
a6b07c43ef CmbProject: emit object change after reordering children.
This fix reordering workspace update on gtk 4 projects.
2022-06-16 19:24:30 -04:00
Juan Pablo Ugarte
ffebdee4dd CmbLayoutProperty: fix regression 2022-06-16 18:35:57 -04:00
Juan Pablo Ugarte
52e40ac3eb Rolling 0.10.1 2022-06-15 16:09:27 -04:00
Juan Pablo Ugarte
e24dbffa8c CmbDB: fix regression
Fix add_object() sql statement for inline object properties.
2022-06-15 16:09:01 -04:00
Juan Pablo Ugarte
d916f4080c Rolling 0.10.0 - 3rd party libs release! 2022-06-15 15:07:38 -04:00
Juan Pablo Ugarte
dc50e58427 CmbWindow: add new translators 2022-06-15 15:07:38 -04:00
Juan Pablo Ugarte
400c9c2a7e SUPPORTERS.md: update supporters 2022-06-15 15:01:43 -04:00
Juan Pablo Ugarte
6db9eee4e3 CmbDB: add project version check.
Make sure we do not try to load a project from a future version.
2022-06-13 16:05:46 -04:00
Juan Pablo Ugarte
04f9f1ea80 CmbObjectEditor: fix regression in layout properties listing.
Make sure *LayoutChild types are used for layout properties.
2022-06-09 16:46:16 -04:00
Juan Pablo Ugarte
762e31eaa7 CmbLayoutProperty: fix regression in setter
Make sure we onyl try to convert to int position properties.
2022-06-09 16:44:51 -04:00
Juan Pablo Ugarte
e478605d42 CmbDB: improve copy/paste name mapping. 2022-06-09 16:11:26 -04:00
Juan Pablo Ugarte
7089cf4e15 Update translations. 2022-06-08 14:41:55 -04:00
Juan Pablo Ugarte
7c60f1024a Add Dutch trannslation to release notes 2022-06-08 14:41:34 -04:00
Juan Pablo Ugarte
1074557e3c CmbWindow: implement clear action
Implement action to clear all properties of the selected objects
2022-06-07 11:42:48 -04:00
Juan Pablo Ugarte
120b6214a1 CmbObject: add clear_properties() 2022-06-07 11:42:27 -04:00
Juan Pablo Ugarte
34f6c0bef5 Metainfo: fix release date for 0.10.0 2022-06-07 11:35:20 -04:00
Juan Pablo Ugarte
3bd699d333 Update es translation 2022-06-07 11:35:04 -04:00
Juan Pablo Ugarte
ef612968ae Merge branch 'pervoj/update-cs' into 'main'
Update Czech translation

See merge request jpu/cambalache!33
2022-06-06 15:31:31 +00:00
Vojtěch Perník
f96f98a3bf Update Czech translation 2022-06-06 16:30:02 +02:00
Juan Pablo Ugarte
7f93da56bc Merge branch 'update-german-translation' into 'main'
Update German translation (for 0.10)

See merge request jpu/cambalache!31
2022-06-06 13:19:17 +00:00
Juan Pablo Ugarte
f18f850c63 Merge branch 'add-dutch-translation' into 'main'
Add Dutch translation

See merge request jpu/cambalache!32
2022-06-06 13:19:03 +00:00
Gert
6d56605480 Add Dutch translation 2022-06-05 20:38:18 +02:00
PhilProg
2dce43be41 Update German translation 2022-06-03 20:09:41 +00:00
Juan Pablo Ugarte
827ebe5e97 Update po files with new translations. 2022-06-02 14:45:55 -04:00
Juan Pablo Ugarte
8fef98b7c9 Add releases notes for 0.10.0 2022-06-02 14:37:32 -04:00
Juan Pablo Ugarte
665a0d8cb6 tests: add new undo unit test
Add basic undo test that reproduces issue #69
2022-06-01 19:21:10 -04:00
Juan Pablo Ugarte
db0896d9c6 CmbProject: clear history before push command.
Fix issue #69 "Undo and redo operations don't always match up"
2022-06-01 19:19:34 -04:00
Juan Pablo Ugarte
d9112daa0e CmbDB: add clear_history() method 2022-06-01 19:19:04 -04:00
Juan Pablo Ugarte
c882a23cdf CmbLayoutProperty, CmbObjectDataArg, CmbProperty, CmbUI: fix undo/redo stack
Make sure UPDATE trigger are fired by not using REPLACE INTO sql command
which is implemented as a DELETE and INSERT.
2022-06-01 19:18:26 -04:00
Juan Pablo Ugarte
d2a6670111 CmbApplication: add version and export all command line options 2022-06-01 19:15:22 -04:00
Juan Pablo Ugarte
d919f0cac7 CmbDB: improve clipboard paste function.
Rename object ids of pasted objects.
2022-05-24 19:12:41 -04:00
Juan Pablo Ugarte
8404b9b4ff CmbDB, CmbProject: reimplement clipboard functions
Use new approach to implement the clipboard, instead of saving
the object and properties to a DB table, simply export the object
we are copying and keep the XML representation, then import on paste.

Fix issue #115 "Cannot copy/paste widget"
2022-05-24 14:56:56 -04:00
Juan Pablo Ugarte
058ac961fa MrgGtkNotebook: handle label widgets
Fix issue #117 "Error `'NoneType' object has no attribute 'props'` when changing notebook tab"
2022-05-22 12:02:19 -04:00
Juan Pablo Ugarte
40a584777f MrgGtkNotebook: fix runtime error on gtk4
Fix issue #116 "Error when trying to click at Notebook content"
2022-05-20 16:26:03 -04:00
Juan Pablo Ugarte
5be1fd4874 CmbObjectEditor: rework __update_view()
Prepare function to add custom data editors by class.
2022-05-17 19:21:01 -04:00
Juan Pablo Ugarte
bda75ce51f CmbTypeInfo: add interfaces array 2022-05-17 19:20:29 -04:00
Juan Pablo Ugarte
5244407992 CmbView: ignore contruct-only flag for objects with a workspace-type defined
Workspace types should not have any construct only properties.
2022-05-16 19:05:01 -04:00
Juan Pablo Ugarte
6097c9686c MrgGBinding: add workspace type for GBinding
This will make merengue use MrgGBindingProxy object instead of a
regular GBinding to avoid GBinding terminating the process when
it is created without all the properties set.

Fix issue #110 "Screen flashing when creating GBinding"
2022-05-16 19:04:56 -04:00
Juan Pablo Ugarte
a228dcaa3e CmbDB: use workspace_type for merengue output 2022-05-16 18:52:37 -04:00
Juan Pablo Ugarte
634dc1ddf1 Catalogs: update catalogs
Set GBinding workspace object to MrgGBindingProxy
2022-05-16 18:51:34 -04:00
Juan Pablo Ugarte
8eb4f993b0 Data Model: add workspace-type column to type table
Add extra data to define a different type for the workspace process.
2022-05-16 18:50:00 -04:00
Juan Pablo Ugarte
da989ae3b5 CmbView: Do not restart merengue indefinitelly.
Stop auto restart if the process exited less than a second ago.
2022-05-16 17:20:11 -04:00
Juan Pablo Ugarte
ecca5e3a28 Cambalache Private: add function to overload GtkBuilder->get_type_from_name()
This is really hacky code that should be avoided in production code.

Committing just in case we ever really need it.
2022-05-16 17:20:11 -04:00
Juan Pablo Ugarte
2f601c6d3e Handy catalog: fix class prefix 2022-05-11 18:16:13 -04:00
Juan Pablo Ugarte
020f2cfc25 MrgGtkMenu: add basic support
Show menu in workspace.
2022-05-11 18:16:13 -04:00
Juan Pablo Ugarte
5f58f58963 MrgGtkPopover: use GtkMenuButton instead of button 2022-05-11 18:16:13 -04:00
Juan Pablo Ugarte
bc84a7bfde Gtk catalog: Move GtkMenu to toplevel category
Fix issue #109 "Cambalache adds to container GtkRecentChooserMenu even if prints that this won't happen"
2022-05-11 18:15:44 -04:00
Juan Pablo Ugarte
aa639ecc5c MrgGtkPopover: destroy window when object is unset
Fix issue #108 "Popovers stay on scene after deleting file which contains them"
2022-05-11 09:27:39 -04:00
Juan Pablo Ugarte
fef1aea320 MrgGtkNotebook: add basic GtkNotebook support
You can now use ctrl+ins to add a new page

Fix issue #98 "No way to add tab in Notebook"
2022-05-11 09:19:20 -04:00
Juan Pablo Ugarte
4ba3c65793 MrgGtkStack: fix warning on remove placeholder 2022-05-11 09:17:51 -04:00
Juan Pablo Ugarte
3b4d6fc621 Merengue: add popover file 2022-05-10 08:41:25 -04:00
Juan Pablo Ugarte
5796fbfeb8 CmbDB: fix __get_layout_property_owner()
Walk type hierarchy until we find the Layout child property.

Fix issue #104 "Error when trying to add children to buttonbox"
2022-05-10 08:35:43 -04:00
Juan Pablo Ugarte
612400c5ac MrgGtkPopover: add basic support
Fix issue #102 "Popovers are not visible"
2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
d57a7f1cab MrgGtkWidget: implement on_object_changed() 2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
e9a0d1dd9d MrgController: add on_object_changed()
Add method for subclasses to reimplement.
2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
7ef99636d3 MrgApplication: init controller toplevel prop before setting object. 2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
b182e71153 Gtk catalog: move GtkPopover to toplevel category 2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
811b90ca5c CmbWindow: Create objects in toplvel category as toplevels 2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
6dc45f95e5 MrgSelection: rename window property as container 2022-05-09 19:52:36 -04:00
Juan Pablo Ugarte
e29add189f Catalogs: add mising Handy meta data 2022-04-29 18:55:52 -03:00
Juan Pablo Ugarte
183925e7f2 Catalogs: Adwaita fix unique contrain error 2022-04-29 18:54:25 -03:00
Juan Pablo Ugarte
1ad27f76fe Catalogs: add basic metadata for Adwaita and Handy
Fix issue #105 "Child layout properties not available when parent is a subclass (AdwHeaderBar)"
2022-04-29 16:52:18 -03:00
Juan Pablo Ugarte
5765e36b4f CmbSignalEditor: use object name for user_data
Fix issue #100 "Signals get broken"
2022-04-29 16:27:08 -03:00
Juan Pablo Ugarte
c82cb48fb7 CmbDB: remove debug print 2022-04-29 12:34:00 -03:00
Juan Pablo Ugarte
fd366afc40 tests: fix position error
From now on we do not output position packing/layout properties.
2022-04-29 11:13:34 -03:00
Juan Pablo Ugarte
cd6c1db3cd CmbProject: fix tests 2022-04-29 11:05:46 -03:00
Juan Pablo Ugarte
d959f1a7e3 CmbWindow: print backtrace on file import 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
b10917f852 CmbProject: update treemodel on object position change. 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
dbe950b52f Data Model: make object position column 0 by default 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
bebd9d049f CmbProject: derive from GtkTreeModel
instead of implemeting all ifaces manually since "rows_reordered"
signal can not be used from python because new_order param is a gpointer
instead of a int array.
2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
e54f96fcc8 CmbProject: add child reorder support 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
b0d389db93 CmbDB: update object position importing layout properties 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
3571f15016 CmbLayoutProperty: Update object position for position properties. 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
ad0f5b9e69 CmbObject: add reorder_child()
Add function to reorder child.
2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
3b9ec8cc77 Catalogs: update catalogs with position property and changes in SDK 42 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
361ee652b4 cambalache-db: add support for position properties.
Add a way to know which layout property set the child order
(GtkBox position for example)
2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
a23b99ce3d cambalache-db: only execute module init() if it takes no arguments. 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
d474ba2d3c Data Model: add is_position column to property table 2022-04-29 10:59:36 -03:00
Juan Pablo Ugarte
b567711273 Merge branch 'italian-translation' into 'main'
Add Italian translation

See merge request jpu/cambalache!30
2022-04-27 12:36:54 +00:00
capaz
53036079c9 Update Toplevel translation 2022-04-23 23:33:04 +02:00
capaz
0f401f8e82 Enhance interactive presentation localization 2022-04-23 23:21:01 +02:00
capaz
d19db770ea Enhance italian localization 2022-04-23 22:56:28 +02:00
capaz
850b5eaf1d Add italian localization 2022-04-23 22:38:48 +02:00
Juan Pablo Ugarte
2710950520 CmbTreeView: make tree reorderable
Fix issue #59 "Reordering children in a parent"
2022-03-30 20:48:02 -03:00
Juan Pablo Ugarte
4e14edfea0 CmbProject: implement GtkTreeDrag[Source|Dest] ifaces
Add support for reordering children position.
2022-03-30 20:47:07 -03:00
Juan Pablo Ugarte
cfce62f158 MrgApplication: add suport for selection in update_ui command 2022-03-30 16:26:55 -03:00
Juan Pablo Ugarte
c1e19f0948 CmbView: add selection argument to update_ui command 2022-03-30 16:26:32 -03:00
Juan Pablo Ugarte
0a7c78c738 db-codegen: fix sql file paths 2022-03-30 16:25:33 -03:00
Juan Pablo Ugarte
316e5823e3 cambalache: move sql files to their own directory 2022-03-28 10:32:11 -03:00
Juan Pablo Ugarte
72ac43dd83 Rename plugins directory as catalogs 2022-03-28 10:32:00 -03:00
Juan Pablo Ugarte
6b9051cefe MrgAdw: add basic support
Add basic support for MrgAdwApplicationWindow, MrgAdwBin,
MrgAdwCarousel and MrgAdwWindow.

Fix issue #9 "Support for libadwaita and libhandy"
2022-03-25 17:27:59 -03:00
Juan Pablo Ugarte
d7eca6466b MrgGtk: load MrgGtkBin in both gtk versions 2022-03-25 17:27:59 -03:00
Juan Pablo Ugarte
8d5e41f75b MrgGtkWindow: add default size 2022-03-25 17:27:59 -03:00
Juan Pablo Ugarte
585f7846f5 Merengue: remove Controller suffix from Widget controllers class name 2022-03-25 17:27:59 -03:00
Juan Pablo Ugarte
0593c0b746 CmbDB: load adwaita catalog by default 2022-03-25 17:17:18 -03:00
Juan Pablo Ugarte
bd23ff4ecd libhandy: update catalog 2022-03-25 17:17:18 -03:00
Juan Pablo Ugarte
45c44a9d4a Add libadwaita catalog 2022-03-25 17:17:18 -03:00
Juan Pablo Ugarte
fbf411bf41 Bump to SDK version 42 2022-03-25 17:17:18 -03:00
Juan Pablo Ugarte
ff1476ff57 cambalache-db: init module if it has an init() function 2022-03-25 17:17:18 -03:00
Juan Pablo Ugarte
4e7159e0d9 CmbDB: export_ui() make sure we do not export the same requires mode than once 2022-03-24 17:17:57 -03:00
Juan Pablo Ugarte
c03355db9d MrgApplication: allow setting up null object properties 2022-03-23 18:32:53 -03:00
Juan Pablo Ugarte
f2e8e6686c MrgController: fix setting up object properties 2022-03-23 18:32:12 -03:00
Juan Pablo Ugarte
dc264de90f MrgApplication: ignore ImportError on plugin loading 2022-03-23 15:26:01 -03:00
Juan Pablo Ugarte
861e22692a MrgApplication: properly fix regression on toplevels
Use controller.object_id to check if its a toplevel or not
2022-03-23 11:41:52 -03:00
Juan Pablo Ugarte
7403a70f80 MrgHandy: add basic support for Handy
Initialize library and add basic support for HdyDeck, HdyLeaflet and HdySearchBar

MrgGtkBin: use controller.get_children() instead of object.get_child()
2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
b492a70247 MrgApplication: load merengue plugin when loading a namespace 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
d8b2f397d3 Merengue: LGPL css file 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
7af83d0098 CmbView: load ui after namespace on startup 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
0862848c40 CmbDB: cleanup requires output
Use requires from user selection first and complete the missing one from project data
Load libhandy catalog
2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
41cc046b55 Plugins: rename gdk-pixbuf catalog to gdkpixbuf 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
afd6b5d474 cambalache-db: use output name to infer library name 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
70b8b9d3ad Plugins: add Handy catalog 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
4daa4f358a CmbView: load 3rd party namespaces on merengue startup 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
2f63eca695 MrgApplication: add load_namespace command
Add command to load gi.repository namespace
2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
34cce308e9 CmbProject: add library_info dict
Add public member to get loaded libraries
2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
bbf4e5094b CmbLibraryInfo: add wrapper for library table 2022-03-23 11:24:41 -03:00
Juan Pablo Ugarte
01a827295c MrgGtkWidget: fix regression
Make sure non window toplevels are shown in workspace.
2022-03-22 19:05:41 -03:00
Juan Pablo Ugarte
c41a767bcc CmvView: handle construct only properties
Do not try to set a construct only property, reload UI instead.
2022-03-21 17:07:13 -03:00
Juan Pablo Ugarte
af14de7428 Catalogs: update construct only data 2022-03-21 17:03:09 -03:00
Juan Pablo Ugarte
78ce827f85 combalache-db: properly get construct only flag
Use contruct only flags instead of just construct
2022-03-21 17:02:20 -03:00
Juan Pablo Ugarte
4ff70a0216 CmbWindow: use gtk_show_uri() to open sqlite debug file 2022-03-21 17:01:45 -03:00
Juan Pablo Ugarte
acbf129dca CmbIconNameEntry: fix regression
Reverse cmb-value ->primary-icon-name binding source/target to make
sure property does not get unset on widget unref.
2022-03-21 11:52:49 -03:00
Juan Pablo Ugarte
ea272c4aad CmbTypeChooserWidget: simplify type population by using instantiable flag
This prevents adding types that derive from enum or flags like CmbIconName
2022-03-20 12:43:56 -03:00
Juan Pablo Ugarte
21c85f6caf CmbTypeInfo: add instantiable member
Add flag to know if type is instantiable (derived from GObject and is not abstract)
2022-03-20 12:43:07 -03:00
Juan Pablo Ugarte
bf5ae0a134 CmbDB: fix regression
Fix regression exporting inline objects
2022-03-20 12:32:49 -03:00
Juan Pablo Ugarte
884cb69620 CmbWindow: add warning if sqlitebrowser or xdf-open are not found
Fix issue #92 "'Debug Project Data' does nothing"
2022-03-20 12:26:51 -03:00
Juan Pablo Ugarte
fb50b837e6 CmbIconNameEntry: check icon filename exists 2022-03-20 12:20:15 -03:00
Juan Pablo Ugarte
ba2fd3a6d3 CmbObjectEditor: use CmbColorEntry for GdkColor and GdkRGBA 2022-03-20 12:09:37 -03:00
Juan Pablo Ugarte
d50be33972 CmbColorEntry: add new property editor
Add color editor
2022-03-20 12:09:37 -03:00
Juan Pablo Ugarte
39814c2316 Merengue: use CambalachePrivate
Use custom function to set object properties from a string.

This works around an error that prevents getting a GdkRGBA GValue from a string in python.
2022-03-20 12:09:37 -03:00
Juan Pablo Ugarte
686eec46af Add Cambalache Private library
Add introspectable shared library for adding bindings to functions
that do not work or can not be implemented in Python.
2022-03-20 12:09:37 -03:00
Juan Pablo Ugarte
543634cb4f CmbObjectEditor: use CmbIconName type for icon name properties
This makes icon name work for properties other than "icon-name"
2022-03-16 17:09:49 -03:00
Juan Pablo Ugarte
546fe2ceee Gtk plugin: mark icon name properties
Set icon name properties type to CmbIconName
2022-03-16 17:09:12 -03:00
Juan Pablo Ugarte
c66458458c Data Model: fix propert:is_object trigger 2022-03-16 17:07:56 -03:00
Juan Pablo Ugarte
2ffcfdd076 cambalache-db: add CmbIconName custom type support 2022-03-16 17:05:08 -03:00
Juan Pablo Ugarte
9cdb2eadb8 CmbIconNameEntry: add new property control
Add new control to select icon names.

Fix issue #85 "Provide icon selection for Button / Image"
2022-03-15 17:42:18 -03:00
Juan Pablo Ugarte
0e97aef8db Add icon naming spec list 2022-03-15 17:41:07 -03:00
Juan Pablo Ugarte
6fcaccf20a CmbTypeInfo: initialize public members
Add logic from CmbProject in the type info class directly.
2022-03-10 18:03:03 -03:00
Juan Pablo Ugarte
10271b80bc cambalache-db: set LayoutChild parent to GObject 2022-03-10 18:02:50 -03:00
Juan Pablo Ugarte
e85590b321 CmbProject: remove all CmbTypeInfo public member initialization 2022-03-10 18:01:27 -03:00
Juan Pablo Ugarte
97c71abc22 CmbDB: ignore object properties with value 0
Fix issue #91 "Unable to export"
2022-03-10 17:58:41 -03:00
Juan Pablo Ugarte
ce3385ef31 CmbProperty: ignore 0 value in object properties 2022-03-10 17:58:40 -03:00
Juan Pablo Ugarte
8ad9362118 CmbWindow: save & export improvements
- Add export shortcut <ctrl>+e
 - Update save action sensitivity depending on history index

Fix issue #63 "Allow automatically exporting on save (or make it easier to do so)"
2022-03-05 09:49:46 -05:00
Juan Pablo Ugarte
55be502f2f CmbProject: emit changed signal after updating undo history index 2022-03-04 17:02:36 -05:00
Juan Pablo Ugarte
99d3219898 CmbWindow: add msg for GtkAssistant and GtkStack 2022-03-04 13:15:54 -05:00
Juan Pablo Ugarte
02429f9155 MrgGtkAssistant: add basic support
Add support for GtkAssistant, note that Gtk 4 only support adding
GtkAssistantPage children but its not mentioned in the docs

Fix issue #78 "How to use GTKAssistant"
2022-03-04 13:12:07 -05:00
Juan Pablo Ugarte
55b3444a6c MrgGtkWindget: add find_child() helper function 2022-03-04 13:11:48 -05:00
Juan Pablo Ugarte
5ab813910f MrgApplication: fix show_widget()
Fix function hierarchy loop
2022-03-04 10:50:28 -05:00
Juan Pablo Ugarte
a2dfd29f46 MrgGtkWindow: cleanup object property
Use notify signal instead of overriding getter/setter in order for subclasses to work properly.
2022-03-04 10:48:41 -05:00
Juan Pablo Ugarte
7dde692bec CmbView: do not show internal id in xml 2022-03-04 10:47:08 -05:00
Juan Pablo Ugarte
f0dc8eda72 MrgGtkBin: cleanup init function 2022-03-02 22:31:25 -05:00
Juan Pablo Ugarte
899f2bc53d MrgGtkRevealer: reveal on init and child selection 2022-03-02 22:31:01 -05:00
Juan Pablo Ugarte
ccf01080ad MrgGtkExpander: expand on child selection 2022-03-02 22:30:27 -05:00
Juan Pablo Ugarte
686fcdd024 Gtk catalog: add GtkOverlay overlay child type 2022-03-02 22:29:34 -05:00
Juan Pablo Ugarte
76fa80119c CmbProject: allow creating GObject children on Gtk 4 2022-03-02 21:53:31 -05:00
Juan Pablo Ugarte
00a4a71801 MrgGtkStack: initial support for GtkStack
Support adding a new page with <ctrl>+Ins and show child on selection.

Fix issue #75 "How to use GtkStack"
2022-03-02 21:45:05 -05:00
Juan Pablo Ugarte
31909a287e MrgApplication: show selected widget
Add function to show a widget by calling show_child on the whole controller hierarchy.
2022-03-02 21:42:39 -05:00
Juan Pablo Ugarte
ab68876874 CmbWindow: implement add_object and add_object_toplevel actions
Implement new context menu actions

Fix issue #66
2022-03-02 17:29:12 -05:00
Juan Pablo Ugarte
e52228bb7f CmbContextMenu: add add_object and add_object_toplevel actions 2022-03-02 17:28:48 -05:00
Juan Pablo Ugarte
6e75b8149b Data Model: remove ui.name unique constrain
We do not need UI name to be unique, only filename.

Fix issue #90 "Cambalache fails to import valid glade/ui files"
2022-02-26 10:01:56 -05:00
Juan Pablo Ugarte
a5e511832a CmbView: update_ui if there is no selection. 2022-02-25 16:43:55 -05:00
Juan Pablo Ugarte
32d1471613 MrgApplication: clear selection on update_ui() if ui_id is 0 2022-02-25 16:43:55 -05:00
Juan Pablo Ugarte
3fabfcc939 CmbDB: return None on export_ui() if ui does not exists 2022-02-25 16:43:55 -05:00
Juan Pablo Ugarte
b7fcbe17b5 CmbWindow: fix warning on __on_project_selection_changed()
Fix issue #89 "Error `AttributeError: 'NoneType' object has no attribute 'info'` when deleting UI file"
2022-02-25 16:43:29 -05:00
Juan Pablo Ugarte
5e1c2a21c8 CmbTypeChooserWidget: ignore boxed types
GBoxed types are only needed as GObject properties.
2022-02-22 17:21:41 -05:00
Juan Pablo Ugarte
695efd94cf Merge branch 'pervoj/update-czech-translation' into 'main'
Update Czech translation

See merge request jpu/cambalache!29
2022-02-22 22:08:32 +00:00
Juan Pablo Ugarte
41476b3540 run-dev.py: generate locales directory
Create locales directory in .lc_messages for translations to work

Fix issue #83
2022-02-22 17:06:15 -05:00
Juan Pablo Ugarte
3b84d27ec4 CmbView: restart workspace on merengue crash
Make sure we restart merengue if it crashes

Fix issue #86 "Automatically restart merengue when merengue crashes"
2022-02-22 17:06:15 -05:00
Vojtěch Perník
67739e1306 Update Czech translation 2022-02-22 10:54:56 +00:00
Juan Pablo Ugarte
2281c03cfd Merge branch 'main' into 'main'
Added Ukrainian translation.

See merge request jpu/cambalache!28
2022-02-20 00:17:30 +00:00
Volodymyr M. Lisivka
34bc07ce2a Added Ukrainian translation. 2022-02-19 21:52:52 +02:00
Juan Pablo Ugarte
05e23527ac Gtk catalogs: update enum and flags defautl values 2022-02-19 11:11:26 -05:00
Juan Pablo Ugarte
933e4f73e0 cambalache-db: use C function to get enum and flags default value
This make it possible to have the values of external types
2022-02-19 11:10:26 -05:00
Juan Pablo Ugarte
b4e91c76ad plugins: update catalogs
- Update catalogs with boxed types properties
 - Add Gio catalog
 - Reorganize diractories

Fix issue #82 and #62
2022-02-18 18:01:22 -05:00
Juan Pablo Ugarte
77a7a8e18d CmbObjectEditor: misc cleanups 2022-02-18 17:56:47 -05:00
Juan Pablo Ugarte
b5d68b879b CmbObjectChooser: fix regression
Show object type for non inline objects
2022-02-18 17:55:10 -05:00
Juan Pablo Ugarte
97771191d4 Data Model: add boxed type 2022-02-18 17:54:00 -05:00
Juan Pablo Ugarte
92f57cc488 cambalache-db: add support for boxed properties 2022-02-18 17:51:25 -05:00
Juan Pablo Ugarte
c4a84e1cc7 plugins: update catalogs
Update catalogs to add support for more properties.
2022-02-17 19:51:05 -05:00
Juan Pablo Ugarte
25141414aa cambalache-db: add --external-catalogs option
Add support for loading external catalogs to know which extra types
are supported.
2022-02-17 19:47:39 -05:00
Juan Pablo Ugarte
c37ca3f9d5 CmbDB: add namespace and prefix to catalog/library 2022-02-17 19:46:32 -05:00
Juan Pablo Ugarte
c5aebd6ad4 run-dev.py: add links for new catalogs 2022-02-17 16:31:46 -05:00
Juan Pablo Ugarte
7dfa56fe38 CmbDB: load new catalogs (Gdk, Pango, etc) 2022-02-17 16:31:03 -05:00
Juan Pablo Ugarte
b798578f20 Plugins: add enum and flags types for GdkPixbuf, Pango, Gdk and Gsk 2022-02-17 16:29:39 -05:00
Juan Pablo Ugarte
21a072c720 cambalache-db: add --exclude-objects option
Add option to exclude all objects and interfaces.
2022-02-17 16:27:05 -05:00
Juan Pablo Ugarte
f4c3735530 MrgGtkCenterBoxController: add initial support 2022-02-16 18:39:39 -05:00
Juan Pablo Ugarte
5281ec366f MrgGtkWidgetController: add get_child_type() method
Add method to get child type from runtime object.
2022-02-16 18:39:30 -05:00
Juan Pablo Ugarte
2cf993877d CmbView: add child_type param to placeholder-[selected|activated] signals 2022-02-16 18:37:22 -05:00
Juan Pablo Ugarte
8ccf9dfa3c CmbProject: add child_type param to add_object() 2022-02-16 18:35:49 -05:00
Juan Pablo Ugarte
39cfe6a2c5 Gtk 4: add GtkCenterBox child types 2022-02-16 18:34:48 -05:00
Juan Pablo Ugarte
6f45ff4e39 Merengue: add basic support for GtkMenuItem 2022-02-15 18:10:42 -05:00
Juan Pablo Ugarte
4df62a30fc CmbObjectEditor: add support for child type
Add child type selector together with layout properties since
child type depends on the parent.

Fix issue #68 "Trouble with GtkHeaderBar"
2022-02-15 18:10:17 -05:00
Juan Pablo Ugarte
a7f0e4e9a5 CmbChildTypeComboBox: add new property control.
Add widget to select child type
2022-02-15 17:46:41 -05:00
Juan Pablo Ugarte
620db60321 CmbView: update ui on object-changed signal 2022-02-15 17:46:07 -05:00
Juan Pablo Ugarte
6eddd9abf8 CmbObject: add support for CmbProject object-changed signal
Add parent helper property.
2022-02-15 17:45:15 -05:00
Juan Pablo Ugarte
8afadd2890 CmbProject: add object-changed signal
Add signal to keep track of object changes like name (id) or child type
2022-02-15 17:44:28 -05:00
Juan Pablo Ugarte
a3a48f9d3f CmbTypeInfo: add support for child types
Add child_types member and has_child_types() helper method.
2022-02-15 17:42:51 -05:00
Juan Pablo Ugarte
5d0e9a9972 Gtk plugin: add child type metadata
Add child type for GtkWindow, GtkMenuItem, GtkMenuToolButton,
GtkExpander, GtkFrame, GtkNotebook and GtkHeaderBar
2022-02-15 17:40:31 -05:00
Juan Pablo Ugarte
132f37936b cambalache-db: add support for type_child_type table 2022-02-15 17:37:19 -05:00
Juan Pablo Ugarte
3282920cd1 db-codegen: add CmbTypeChildInfo
Create wrapper object for type_child_type table
2022-02-15 17:36:13 -05:00
Juan Pablo Ugarte
414da2891d Data Model: add type_child_type table
Add table to store type special child types
2022-02-15 17:34:20 -05:00
Juan Pablo Ugarte
dd15070005 gladecambalache: add types needed for CmbUI
Add CmbEntry, CmbToplevelChooser and CmbTextBuffer
2022-02-15 17:23:32 -05:00
Juan Pablo Ugarte
b6386e490a Merengue: add initial support for GtkListBox
Fix issue #81 "No way to add rows to GtkListBox"
2022-02-15 17:21:24 -05:00
Juan Pablo Ugarte
3dfcb5cb62 tests: add CmbObject test 2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
2e60f409a8 CmbObject: add add_data() remove_data() function
Add API to add/ remove custom object data
2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
c9c8e87e72 CmbDB: add object_add_data() function 2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
ecebe1c2a0 CmbProperty: improve set function
Use one REPLACE INTO instead of INSERT and UPDATE queries
2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
d20ff7e0cf CmbUI: add set/get library api 2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
be6ef41b85 CmbObjectData: add object data wrapper class 2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
2296871f3d CmbTypeInfo: use data base model objects
Add get_data_info() function
2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
6b34abb2f6 Base models: add new object data base models
Add CmbBaseTypeDataInfo, CmbBaseTypeDataArgInfo and CmbBaseObjectData
2022-02-09 18:57:19 -05:00
Juan Pablo Ugarte
a5c83bcd21 CmbWindow: add user tips messages
Add tips on type chooser popup and GtkBox/GtkGrid selection.

Fix issue #79
2022-02-07 21:17:15 -05:00
Juan Pablo Ugarte
54d71a92cb CmbTypeChooser: add chooser-popup and chooser-popdown signals 2022-02-06 13:27:01 -05:00
Juan Pablo Ugarte
e44f587261 CmbObjectChooser: various improvements
- Fix bug unsetting object (set value to None instead of 0)
 - Update entry state on property change
2022-01-26 18:21:07 -05:00
Juan Pablo Ugarte
1fe71b9ee7 Tests: Add missing files 2022-01-25 16:50:08 -05:00
Juan Pablo Ugarte
70e0f957af CmbProject: add safeguard to get_target_from_ui_file()
Return None if lib can not be determied.
2022-01-25 16:36:58 -05:00
Juan Pablo Ugarte
26690a432c CmbDB: add extra check to _get_target_from_node()
Check if root node is <interface>
2022-01-25 16:36:18 -05:00
Juan Pablo Ugarte
98f5d9ab91 Tests: add inline_object and stack_page test cases 2022-01-21 17:24:03 -05:00
Juan Pablo Ugarte
af04f525ab Gtk catalogs: add inline child metadata
This adds support for inline objects in most bin like layout classes

Fix issue #47
2022-01-21 17:22:24 -05:00
Juan Pablo Ugarte
28b4dbffe6 cambalache-db: add <property> is-inline-object flag 2022-01-21 17:21:19 -05:00
Juan Pablo Ugarte
83079cf84b CmbTreeView: show property id id child is inline 2022-01-21 17:20:40 -05:00
Juan Pablo Ugarte
772c64d7b6 CmbObjectChooser: add support for inline objects
Add + icon to create inline object from the property editor
2022-01-21 17:19:36 -05:00
Juan Pablo Ugarte
26fcb30ba7 CmbTypeChooserWidget: add derived_type_id property 2022-01-21 17:18:55 -05:00
Juan Pablo Ugarte
87e4fc7e0b CmbDB, CmbProject: add support for inline objects
Bump to version 0.9.0 and add migration function
2022-01-21 17:17:37 -05:00
Juan Pablo Ugarte
a34b1be190 Data Model: add inline object columns
Add property.is_inline_object and object_property.inline_object_id
2022-01-21 17:10:44 -05:00
Juan Pablo Ugarte
4ca867da09 App: update logo-horizontal.svg 2022-01-21 17:00:56 -05:00
Juan Pablo Ugarte
4295ba7936 CmbObject: add properties_dict
Add properties dictionary for easier access by id
2022-01-21 17:00:29 -05:00
Juan Pablo Ugarte
6945c40866 Tests: fix tests
Add missing interface-name comment
2022-01-20 17:43:14 -05:00
Juan Pablo Ugarte
2acd3bcc8b CmbView: fix CmbProcess.stop() issue
Unset pid even if GLib.spawn_close_pid() fails as the process might no longer be running.
2022-01-13 15:25:29 -05:00
Juan Pablo Ugarte
31ff9b21c6 Merge branch 'update-german-translation' into 'main'
Update German translation (for 0.8.2)

See merge request jpu/cambalache!27
2022-01-13 20:21:31 +00:00
PhilProg
bdd7927b7a Update German translation 2022-01-13 19:47:59 +00:00
Juan Pablo Ugarte
6934b7a855 Update es translation 2022-01-11 21:41:55 -05:00
Juan Pablo Ugarte
52df922547 Update po files 2022-01-11 21:41:44 -05:00
Juan Pablo Ugarte
1617397966 Meta Info: add release notes for 0.8.2 2022-01-11 21:41:17 -05:00
Juan Pablo Ugarte
088a33e29c CmbView: add upadte_ui command toplevels support 2022-01-08 16:21:32 -05:00
Juan Pablo Ugarte
d984c182d0 MrgApplication: add update_ui toplevels param support
Add parameter to know which objects are toplevels, since in Gtk 4
we can not relly on get_parent() (Does not for an unexpanded GtkExpander)
2022-01-08 16:19:54 -05:00
Juan Pablo Ugarte
63aa080fb1 MrgController: add toplevel property 2022-01-08 16:19:30 -05:00
Juan Pablo Ugarte
43caf6eecc MrgControllerRegistry: replace new_controller_for_object() with new_controller_for_type() 2022-01-08 16:18:36 -05:00
Juan Pablo Ugarte
d43941ecd9 CmbDB: add get_toplevels() method 2022-01-08 16:17:40 -05:00
Juan Pablo Ugarte
a86954d652 MrgApplication: add apply_workarounds() method
Add method to patch runtime objects in one place.

Set GtkExpander to expanded to make child have parent set.
2022-01-07 19:25:05 -05:00
Juan Pablo Ugarte
4ef8a84a1d Merengue: fix placeholder support for Gtk 4 bin containers
AspectFrame, Expander, Frame, Revealer, ScrolledWindow, Viewport and Overlay
2022-01-07 19:25:05 -05:00
Juan Pablo Ugarte
fdffa42d47 MrgGtkWidget: fix warning in find_child_property() 2022-01-07 19:08:05 -05:00
Juan Pablo Ugarte
840886fc56 Merengue: delete remove_object command
Gtk 4 does not have a generic way to remove a child, instead we
simply recreate the UI like we do when we add a new widget.

Fix issue #67
2022-01-04 18:29:31 -05:00
Juan Pablo Ugarte
a4e142558d CmbTypeChooser: fix warning setting project to none 2022-01-01 10:48:50 -05:00
Juan Pablo Ugarte
caa3faf102 MrgGtkPaned: add placeholder support
Fix issue #73
2022-01-01 10:48:50 -05:00
Juan Pablo Ugarte
0fb1dbd3ab CmbWindow: improve clipboard actions sensitivity
Disable clipboard actions if focused widget is an entry, spin or text view.

Fix issue #58
2021-12-31 17:13:30 -05:00
Juan Pablo Ugarte
b83d420cff CmbTreeView: cell data text cleanup
- Mark missing translatable strings
 - Do not add space if the object has no id

Closses issue #72
2021-12-31 16:16:24 -05:00
Juan Pablo Ugarte
eaf9aab23a Rolling 0.8.1 2021-12-23 22:15:51 -05:00
Juan Pablo Ugarte
fe5fec9b2b Merge branch 'pervoj/update-czech-translation' into 'main'
Update Czech translation

See merge request jpu/cambalache!24
2021-12-24 01:09:02 +00:00
Juan Pablo Ugarte
6f3c2a2edf Merge branch 'update-german-translation' into 'main'
Update German translation

See merge request jpu/cambalache!25
2021-12-24 01:08:47 +00:00
PhilProg
4129c77b3b Update po/de.po 2021-12-23 21:46:57 +00:00
Vojtěch Perník
cd5bcf8306 Update Czech translation 2021-12-23 13:34:23 +00:00
Juan Pablo Ugarte
d883e09d74 Update es translation 2021-12-20 18:01:26 -05:00
Juan Pablo Ugarte
bcf07fd90f Update translations 2021-12-20 18:01:26 -05:00
Juan Pablo Ugarte
ee86c6baca Metainfo: add release notes for 0.8.1 2021-12-20 18:01:26 -05:00
Juan Pablo Ugarte
009d9e44d5 CmbView: show message in webview if broadwayd is missing.
Closses issue #53
2021-12-18 15:41:23 -05:00
Juan Pablo Ugarte
c4d434a570 README: add dependencies and manual instalation sections 2021-12-18 15:41:03 -05:00
Juan Pablo Ugarte
8e7871ed30 CmbTypeChooder: show target toolkit when there is not selected type 2021-12-18 12:19:19 -05:00
Juan Pablo Ugarte
3d52d88976 MrgGtkBox, MrgGtkGrid: improve placeholder creation
Inititalize placeholder size on container creation only if there is no children.

This avoid creating extra placeholders all over the place.

Fix issue #46
2021-12-18 12:16:45 -05:00
Juan Pablo Ugarte
5bd643bcd5 CmbDB: import and export interface data
Import interface data from UI comments (license, authors, etc)
Export UI data as comments.
2021-12-16 20:03:13 -05:00
Juan Pablo Ugarte
8c98b81fc5 DataModel: remove license table
We will use GtkLicense enum instead
2021-12-16 17:31:28 -05:00
Juan Pablo Ugarte
169f74b952 CmbDB: ignore placeholder tag on import 2021-12-15 19:00:20 -05:00
Juan Pablo Ugarte
368fc5398a CmbApplication: improve file open handling
Cambalache is associated with .cmb, .ui and .glade files but
it only opened projects files (.cmb) you had to manually import the others.

This add support for importing UI files in the current project or
create a new one if needed when trying to open a file.

If the file is already imported in the project, it will be selected.

Closes issue #45
2021-12-15 18:07:26 -05:00
Juan Pablo Ugarte
89f37703fe CmbWindow: improve and expose import_file() 2021-12-15 18:05:08 -05:00
Juan Pablo Ugarte
c24de0b5de CmbProject: improve import related API
- Return UI object in import_file() method
 - Add get_ui_by_filename()
 - Move get_target_from_ui_file() from CmbDB
2021-12-15 18:02:32 -05:00
Juan Pablo Ugarte
8ef2847557 CmbDB: expose private method CmbDB._get_target_from_node() 2021-12-15 18:02:06 -05:00
Juan Pablo Ugarte
7fa227a8da CmbWindow: simplify add ui action
Do not ask the user for a filename to add a new UI since now the
filename can be edited in the object editor.

Instead we simply add an untitled UI and let the user set a filename later.

Closes issue #50
2021-12-14 17:04:19 -05:00
Juan Pablo Ugarte
b0f04be486 CmbTreeView: show unnamed ui
Use "Unnamed {ui_id}" string for ui without a filename
2021-12-14 17:03:59 -05:00
Juan Pablo Ugarte
bdddd3a9d5 CmbProject: allow adding ui with no filename
Do not export ui without a filename
2021-12-14 16:53:07 -05:00
Juan Pablo Ugarte
a1da381f5d CmbWindow: support importing multiples files at once
Close issue #52
2021-12-14 16:34:39 -05:00
Juan Pablo Ugarte
9128e36fa2 CmbDB: make get_target_from_file() a little bit more robust
We really need to use a sax parser to get the target without having to parse the whole file

Close issue #49
2021-12-14 16:00:06 -05:00
Juan Pablo Ugarte
9ae021b5b3 CmbDB: handle unrecognized file format on import
Closes issue #48
2021-12-13 18:20:10 -05:00
Juan Pablo Ugarte
24d3b93d43 Rolling 0.8.0 - UX improvements Release! 2021-12-09 18:39:59 -05:00
Juan Pablo Ugarte
2bada2bff8 Fix typo 2021-12-09 18:30:19 -05:00
Juan Pablo Ugarte
b1464b723a CmbObjectEditor: fix template label regression
Update label on template check toggle
2021-12-09 18:25:04 -05:00
Juan Pablo Ugarte
f20b23b50f CmbWindow: fix regression
Enable delete action if a UI is selected.
2021-12-04 10:51:32 -05:00
Juan Pablo Ugarte
aa77954bf0 Merge branch 'pervoj/update-czech-translation' into 'main'
Update Czech translation

See merge request jpu/cambalache!23
2021-12-02 18:31:38 +00:00
Vojtěch Perník
a766d500ac Update Czech translation 2021-12-02 11:51:29 +01:00
Juan Pablo Ugarte
619d6cc2cd Readme: add matrix contact information 2021-12-01 17:56:03 -05:00
Juan Pablo Ugarte
c1ba544bf7 Fix typos on spanish translation 2021-12-01 17:55:34 -05:00
Juan Pablo Ugarte
5f7ac6cf67 Merge branch 'update-german-translation' into 'main'
Update German translation

See merge request jpu/cambalache!22
2021-11-30 16:31:09 +00:00
PhilProg
4962180ab1 Update German translation 2021-11-30 14:23:51 +00:00
Juan Pablo Ugarte
8d4840a5f3 Update pot file
Add comments to widget groups strings
2021-11-29 09:15:33 -05:00
Juan Pablo Ugarte
989b77bedd CmbTypeChooser: add widget groups comments for translators 2021-11-29 09:13:53 -05:00
Juan Pablo Ugarte
fad5be297e Add cmb_shortcuts.ui translations 2021-11-29 08:29:37 -05:00
Juan Pablo Ugarte
b57a32d521 CmbDB: Fix pot update
Translations are only picked up if we use 'yes' instead of 'True' for translatable attributes.
2021-11-29 08:27:37 -05:00
Juan Pablo Ugarte
b0e353e089 cambalache.in: remove builtint _ function
Builtin function is already created in module init.

This fix translations
2021-11-28 16:18:23 -05:00
Juan Pablo Ugarte
cdf65b754c Merge branch 'pervoj/cs-translation-update' into 'main'
Update Czech translation

See merge request jpu/cambalache!20
2021-11-28 20:44:38 +00:00
Juan Pablo Ugarte
bfc567de8c Merge branch 'update-german-translation' into 'main'
Update German translation

See merge request jpu/cambalache!19
2021-11-28 20:44:28 +00:00
Vojtěch Perník
33c12a9287 Update Czech translation 2021-11-28 17:09:19 +01:00
PhilProg
8c7d975ebd Update German translation 2021-11-28 15:28:37 +00:00
Juan Pablo Ugarte
29982b768f Update es translation 2021-11-27 15:29:09 -05:00
Juan Pablo Ugarte
8675786a0f po: cache Sponsors with Supporters 2021-11-27 15:04:03 -05:00
Juan Pablo Ugarte
dd64f154f7 gladecambalache: fix module 2021-11-27 15:01:28 -05:00
Juan Pablo Ugarte
affd753a8c CmbWindow: include all supporters 2021-11-27 15:01:22 -05:00
Juan Pablo Ugarte
19b847ce27 SUPPORTERS: update suporters list
From now one we simply list everyone that support the project.
2021-11-27 14:55:36 -05:00
Juan Pablo Ugarte
8298ca6d89 po: update translations files 2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
dc8ec3a505 CmbWindow: set about dialog translators not translatable.
Ohh the irony!
2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
42c065aa78 Readme: update supporters sections 2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
e5a3cb2909 Supporters: update list 2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
56bf7f2bc7 Metainfo: add next release notes 2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
c6e758a3ef po: add CmbContextMenu files 2021-11-26 18:48:46 -05:00
Juan Pablo Ugarte
5818d874d0 Merge branch 'main' into 'main'
Set XDG_DATA_DIRS according to freedesktop specification

See merge request jpu/cambalache!18
2021-11-26 22:59:45 +00:00
Juan Pablo Ugarte
14d1df39e8 CmbTranslatableWidget: fix regression
GtkTextBuffer is not working on Cambalache
2021-11-26 12:45:03 -05:00
Juan Pablo Ugarte
7cfca63840 po: add CmbShortcuts to translations 2021-11-26 12:42:55 -05:00
Juan Pablo Ugarte
52045452a0 Gtk plugin: add GtkShortcuts translatable properties. 2021-11-26 12:36:29 -05:00
Matsievskiy S.V
5a78e95b9e Set XDG_DATA_DIRS according to freedesktop specification 2021-11-26 10:15:18 +03:00
Juan Pablo Ugarte
6b62aae198 CmbTreeView: add right click context menu
Closes issue #33
2021-11-25 20:27:24 -05:00
Juan Pablo Ugarte
06715f5b10 CmbView: use CmbContextMenu 2021-11-25 20:27:24 -05:00
Juan Pablo Ugarte
b638c4cd16 CmbContextMenu: add new class for context menu
Create context menu class with common clipboard actions.
2021-11-25 20:27:24 -05:00
Juan Pablo Ugarte
0ccce72ddc CmbWindow: add donate page
Add donation page to show the differences between Liberapay and Patreon
and let the user choose which one to use.
2021-11-24 20:38:10 -05:00
Juan Pablo Ugarte
aef30cca5c CmbWindow: remove css theme combobox
Implement new tutorial interactive bits.
2021-11-24 18:35:02 -05:00
Juan Pablo Ugarte
4a26bf7506 CmbTutorial: update tutorial
Add more details about the type chooser and placeholders.
2021-11-24 18:35:02 -05:00
Juan Pablo Ugarte
8929458961 CmbTutor: use widget name and buildable id instead of class members 2021-11-24 18:35:02 -05:00
Juan Pablo Ugarte
0f9c4c57e0 CmbView: add css theme submenu to context menu. 2021-11-24 18:35:02 -05:00
Juan Pablo Ugarte
dca0e0776a CmbWindow: add contact menit item
Add contact to open matrix url.
2021-11-24 08:19:31 -05:00
Juan Pablo Ugarte
645f587abd Metainfo: add contact url 2021-11-24 08:19:31 -05:00
Juan Pablo Ugarte
0fee208e90 CmbWindow: add clipboard actions
Add copy, paste and cut actions with shortcuts.

Close issue #41
2021-11-23 19:56:29 -05:00
Juan Pablo Ugarte
67d843bdbf CmbProject: add copy() paste() and cut()
Implement clipboard methods
2021-11-23 19:56:03 -05:00
Juan Pablo Ugarte
949e24d358 CmbDB: add clipboard api
Add clipboard_copy() and clipboard_paste() methods

Use temp tables to store clipboard data.
2021-11-23 19:56:03 -05:00
Juan Pablo Ugarte
a68cab2d8c CmbObject: fix regression
Make _add_signal() and _remove_signal() accesible from other classes.
2021-11-23 19:56:03 -05:00
Juan Pablo Ugarte
dfb120384c MrgApplication: improve command reading from stdin
Use glib api to read data from stdin and use readline to get payload.
This makes things more realiable as we are not mixing python and glib api.
2021-11-23 19:56:03 -05:00
Juan Pablo Ugarte
3d4998d190 CmbView: simplify merengue command payload
Send payload in just one line escaped with g_strescape()
2021-11-23 19:56:03 -05:00
Juan Pablo Ugarte
e394c32899 CmbWindow: use state.window.* to save window state. 2021-11-23 19:44:44 -05:00
Juan Pablo Ugarte
f98a2eed76 GSettings schema: move window state to its own directory
Make room for future state keys like paned position.
2021-11-23 19:44:44 -05:00
Philipp Unger
d2614b1107 CmbWindow: add window size to gschema to restore its state
Closes issue #39
2021-11-23 19:44:27 -05:00
Juan Pablo Ugarte
57640a434c Use __ for "private" methods and properties 2021-11-20 18:37:25 -05:00
Juan Pablo Ugarte
2a9a8c2912 Merge branch 'use-buffer-from-glade' into 'main'
CmbTranslatableWidget: use buffer from glade file

See merge request jpu/cambalache!17
2021-11-19 21:39:31 +00:00
Philipp Unger
82ab6f5222 CmbTranslatableWidget: use buffer from glade file 2021-11-19 18:40:41 +01:00
Juan Pablo Ugarte
be4ab0cef6 CmbDB: fix add_object() child position
Child position starts from 0 not 1
2021-11-17 19:38:25 -05:00
Juan Pablo Ugarte
65be40a4d1 MrgGtkLabel: only use <label> placeholder text if label is empty string 2021-11-17 19:07:40 -05:00
Juan Pablo Ugarte
4ae4026fb9 po: add cambalache/cmb_translatable_widget.ui and cambalache/cmb_translatable_popover.py 2021-11-17 18:45:37 -05:00
Juan Pablo Ugarte
52b5224059 Meson: add missing cambalache files
Add new widget files to build.
2021-11-17 18:44:19 -05:00
Juan Pablo Ugarte
a4f46cdcec Merge branch 'translatable-properties' into 'main'
Add support for translatable properties

Closes #37

See merge request jpu/cambalache!12
2021-11-17 23:38:55 +00:00
Philipp Unger
0e16dcd462 CmbEntry: add support for translatabe properties
Create CmbTranslatablePopover widget on secondary icon activation.

Closes issue #37
2021-11-17 17:45:56 -05:00
Philipp Unger
27118ae68f CmbTranslatablePopover: add convenience widget
Convenience widget to show a translatable in a popover
2021-11-17 17:24:00 -05:00
Philipp Unger
6d0b69f798 CmbTranslatableWidget: add new widget to edit translatable properties 2021-11-17 17:23:37 -05:00
Juan Pablo Ugarte
746a587b16 CmbDB: add export support for translatables properties 2021-11-16 18:39:01 -05:00
Juan Pablo Ugarte
e38a460007 CmbDB: handle noneType position in export_object() 2021-11-16 18:29:32 -05:00
Juan Pablo Ugarte
d454c8b4dc tests: fix tests
GtkGrid layout props are always saved now.
2021-11-15 18:46:58 -05:00
Juan Pablo Ugarte
98dcab2978 Gtk plugin: mark translatable properties
Use Glade catalog metadata to mark translatable properties.

Close issue #38
2021-11-15 18:39:13 -05:00
Juan Pablo Ugarte
8406598365 cambalache-db.py: add support for property translatable flag 2021-11-15 18:38:32 -05:00
Juan Pablo Ugarte
5000142fcf Data Model: add translatable flag to property table 2021-11-15 18:38:32 -05:00
Juan Pablo Ugarte
e878dda360 Merge branch 'maximize-window-resize-editor-stack' into 'main'
Maximize window and resize editor stack

See merge request jpu/cambalache!15
2021-11-15 23:23:56 +00:00
Philipp Unger
e300002db6 add .vscode to .gitignore 2021-11-15 14:13:29 +01:00
Philipp Unger
dab18e53c6 maximize the window on startup and request width for editor_stack 2021-11-15 14:10:46 +01:00
Juan Pablo Ugarte
9a53e8ac35 Merge branch 'fix-merengue-build' into 'main'
meson: Fix typo on merengue's files list

See merge request jpu/cambalache!14
2021-11-14 00:40:58 +00:00
Martin Abente Lahaye
f49da5abcc meson: Fix typo on merengue's files list 2021-11-13 21:08:48 -03:00
Juan Pablo Ugarte
ebfee19596 CmbWindow: create object on placeholder activation.
Now you can doucle click on a placeholder and add a new object in
that place.
2021-11-12 16:28:46 -05:00
Juan Pablo Ugarte
c2f22a6438 MrgSelection: send activated command on placeholder double click. 2021-11-12 16:27:46 -05:00
Juan Pablo Ugarte
8856daa207 CmbTypeChooserWidget: add parent_type_id property
Add property to filter types that can not be children of parent.
2021-11-12 16:26:09 -05:00
Juan Pablo Ugarte
6f4bb154f5 MrgGtkWidget: fix child_get() for gtk 3 2021-11-12 16:24:24 -05:00
Juan Pablo Ugarte
9381bacfe7 MrgGtkLabel: add label controller
Ensure the label has some text so that its possible to select in the workspace.
2021-11-11 18:00:24 -05:00
Juan Pablo Ugarte
5e6b07aa96 Tests: fix tests 2021-11-10 18:30:38 -05:00
Juan Pablo Ugarte
22ba89c617 Flatpak: bump to Sdk 41
Bump Sdk version to have a new sqlite feature needed in cmb_db_migration.py
Add lxml dependency since its not included in 41.
2021-11-10 18:30:38 -05:00
Juan Pablo Ugarte
fcbd98cff6 CmbDB: add tostring() method 2021-11-10 18:30:38 -05:00
Juan Pablo Ugarte
a5bc3b834e merengue: cleanup hierarchy
Merge controller and utils submodules into merengue.
2021-11-10 18:30:38 -05:00
Juan Pablo Ugarte
295234b4e6 CmbWindow: add shortcuts window
Ensure delete action is only activated when the webview or treeview
have the focus to avoid breaking GtkEntry delete usage for example.
2021-11-10 18:30:38 -05:00
Juan Pablo Ugarte
751f6c0b84 run-dev.py: use version from meson.build 2021-11-08 18:19:35 -05:00
Juan Pablo Ugarte
73b8d01fb8 CmbWindow: use set_accels_for_action() to install shorcuts
Replace CSS bindings with GApplication.set_accels_for_action() to make shortcuts
work on the webview.
2021-11-08 18:13:21 -05:00
Juan Pablo Ugarte
4845492210 CmbWindow: force object creating if alt is pressed.
Now that we have placeholders to add objects we need a fallback option
for types that are still not supported or do not use placeholders.
2021-11-08 15:52:19 -05:00
Juan Pablo Ugarte
ea740757ec Merengue: add new controllers for GtkBin, GtkBox and GtkGrid
Add placeholder basic support for GtkBin, GtkBox and GtkGrid types.
2021-11-08 15:52:19 -05:00
Juan Pablo Ugarte
fa38a1dd3a MrgApplication: handle inline placeholders
Initialize controllers on placeholders created in update_ui command.
2021-11-08 15:52:19 -05:00
Juan Pablo Ugarte
0ed21ed458 CmbDB: rename export_ui() use_id param as merengue
Export placeholders in merengue mode.
2021-11-08 15:52:19 -05:00
Juan Pablo Ugarte
729b8f91e4 CmbProject: add position param to add_object()
We need to store children position to always ensure the same order in the xml
since some containers like GtkBox depend on it.
2021-11-08 12:06:39 -05:00
Juan Pablo Ugarte
1bc41ad53e DataModel: add new columns to object and properties tables
- object: Add column to store the position/order of widgets, needed specially for
container that depend on the order of the children.

- object_property, object_layout_property: Add translation_context and translation_comments
columns to store extra data for translations.
2021-11-08 12:06:19 -05:00
Juan Pablo Ugarte
706c99a584 MrgController: add ui_id and object_id convenience accessors 2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
cceae7997e Merengue utils: add gesture_click_new() and scroll_controller_new()
Add convenience functions to create GtkControllers
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
5eda94f1c6 MrgPlaceholder: add basic implementation
Add placeholder basic implementation.
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
9651a3dbbe CmbWindow: add support for placeholders
Create new object on selected placeholder.
Add bindings to add and remove placeholders.
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
3a112fff97 CmbTypeChooser: change type-selected signal param type
Use CmbTypeInfo in type-selected instead of type name
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
33f208d947 CmbView: add support for placeholder
Add placeholder-selected and placeholder-activated signals
Add add_placeholder() and remove_placeholder() methods
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
084551df9a CmbProject: add layout param to add_object()
Create command group when adding an object to support layout props
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
46d67b1ac2 CmbDB: add layout parameter to add_object()
Add option to create a new object with layout properties
2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
60019876db Bump to version 0.7.5 2021-11-04 19:24:58 -04:00
Juan Pablo Ugarte
cdaadeba1e Fix german translation
Close issue #36
2021-11-04 18:31:42 -04:00
Juan Pablo Ugarte
2b1f49b241 Merge branch 'update-german-translation' into 'main'
Updated german translation

See merge request jpu/cambalache!9
2021-11-04 16:41:50 +00:00
Juan Pablo Ugarte
58639e4b32 Merge branch 'fix-tutorial' into 'main'
Fix tutorial

See merge request jpu/cambalache!8
2021-11-04 16:40:55 +00:00
Philipp Unger
8f4ad68d42 updated german translation 2021-10-31 15:09:07 +01:00
Philipp Unger
c3a6c384a4 fix tutorial 2021-10-31 13:45:03 +01:00
Juan Pablo Ugarte
e0ab77a498 CmbTypeChooserWidget: add uncategorized_only property 2021-10-25 19:33:06 -04:00
Juan Pablo Ugarte
a36291afbc Update es translation 2021-10-25 19:33:06 -04:00
Juan Pablo Ugarte
a4f99f25a0 Update translation files 2021-10-25 19:33:06 -04:00
Juan Pablo Ugarte
22adbc1029 CmbTypeChooserWidget: add show_categories property 2021-10-25 19:33:06 -04:00
Juan Pablo Ugarte
d744ccd2c7 CmbTypeChooser: add support for Type category
Rename CmbTypeChooser as CmbTypeChooserWidget and add new
implementation with one button for each Type category.

Close issue #30
2021-10-25 19:32:44 -04:00
Juan Pablo Ugarte
1d82896e54 CmbProject: remove type_list member
Expose _type_info as type_info
2021-10-25 14:23:10 -04:00
Juan Pablo Ugarte
abc4d743b1 Gtk plugins: add type category metadata 2021-10-25 14:23:10 -04:00
Juan Pablo Ugarte
c6eb8e6c92 DataModel: add type category enum column 2021-10-25 14:23:10 -04:00
Juan Pablo Ugarte
684d900313 CmbWidow: fix introduction tutorial
Close issue #35
2021-10-23 17:56:31 -04:00
Juan Pablo Ugarte
353377dd11 CmbUIEditor: make filename entry expand 2021-10-20 19:10:50 -04:00
Juan Pablo Ugarte
5191c307b7 CmbWindow: replace type search with new chooser widget 2021-10-20 19:10:50 -04:00
Juan Pablo Ugarte
5642ddc3c1 gladecambalache: add CmbTypeChooser and CmbTypeChooserPopover 2021-10-20 19:10:50 -04:00
Juan Pablo Ugarte
0ed576c15a CmbTypeChooser: add type chooser widget
Implement new class to choose object type
2021-10-20 19:10:50 -04:00
Juan Pablo Ugarte
ba471615f3 CmbTreeView: improve type display
Make type italic and add show if an object is a template
2021-10-20 18:46:01 -04:00
Juan Pablo Ugarte
c82b362150 Update es translation 2021-10-20 18:33:20 -04:00
Juan Pablo Ugarte
8c64eb41bf po/cambalache.pot: update translations 2021-10-20 18:32:02 -04:00
Juan Pablo Ugarte
7635e9dd36 Fix translations strings
- Use gettext.install() to install _() as a builtin function
     - Never use _(f"string {var}") since python will format the string
       before calling gettext()

Close issue #34
2021-10-20 18:28:16 -04:00
Juan Pablo Ugarte
357cca6525 Replace all print() with logger
Use logging module to log messages.
2021-10-15 23:00:34 -04:00
Juan Pablo Ugarte
e4d7bd1e84 CmbWindow: use UI editor
Use CmbUIEditor widget to edit the selected UI.
2021-10-15 17:32:28 -04:00
Juan Pablo Ugarte
7eb9c3831f gladecambalache: add new CmbUIEditor class 2021-10-15 17:32:03 -04:00
Juan Pablo Ugarte
435a4ede08 CmbUIEditor: implement new UI editor
Add object to edit UI properties.
2021-10-15 17:31:19 -04:00
Juan Pablo Ugarte
380b6d3a44 CmbProject: add export_ui()
Add function to export only one ui
2021-10-15 17:30:15 -04:00
Juan Pablo Ugarte
7bd07d7efe CmbObjectEditor: move controls to a different file
Fix issue with template checkbutton resetting template_id.
2021-10-15 17:27:46 -04:00
Juan Pablo Ugarte
3c1c383562 Use gettext package instead of locale 2021-10-12 17:27:02 -04:00
Juan Pablo Ugarte
6ad097ab79 CmbWindow: improve parsing error dialog
Print parsing errors details on stderr
2021-10-12 15:59:27 -04:00
Juan Pablo Ugarte
ba7740b677 CmbProject: return parsing errors strings on import_file() 2021-10-12 15:48:08 -04:00
Juan Pablo Ugarte
314d8b927e CmbDB: collect parsing errors in errors dict 2021-10-11 22:51:58 -04:00
Juan Pablo Ugarte
b064f11f25 CmbWindow: inform the user if there is any unsupported feature on import. 2021-10-03 12:16:37 -04:00
Juan Pablo Ugarte
a83c191678 CmbProject: add get_import_error_message() 2021-10-03 12:16:22 -04:00
Juan Pablo Ugarte
2aac9f5cd7 CmbDB: detect missing features on import
Make sure we iterate over all tag and attributes on import to detect
unsupported features.
Use a different filename if something is not supported to avoid
data loss.
2021-10-03 12:14:08 -04:00
Juan Pablo Ugarte
382a818e5b Tests: swap string comparison to have better output on difference 2021-10-03 12:13:16 -04:00
Juan Pablo Ugarte
39b851483d CmbView: print warning if broadway is not found
Close issue #31
2021-10-02 10:02:52 -04:00
Juan Pablo Ugarte
e7f807281e Tests: add customs tags tests for Gtk 3 2021-10-01 17:18:02 -04:00
Juan Pablo Ugarte
12969cfe54 CmbWindow: use just fg color to infer dark mode 2021-09-29 18:25:46 -04:00
Juan Pablo Ugarte
4b9c96ef95 Simplify logo 2021-09-29 16:24:44 -04:00
Juan Pablo Ugarte
43d1e98567 CmbWindow: handle dark themes
Detect dark themes on GtkSettings::gtk-theme-name notify and set dark css class

Use -gtk-recolor() to change logo colors for dark themes.
2021-09-27 20:42:57 -04:00
Juan Pablo Ugarte
97d7d38f6b MrgGtkWidget: destroy wrapper window if object is None
Fix issue where non windows where left behind when switching ui
2021-09-24 18:50:51 -04:00
Juan Pablo Ugarte
f04b36334f Tests: add more simple tests
Add children, packing/layout, signals and template tests.
2021-09-24 16:43:58 -04:00
Juan Pablo Ugarte
0843b2329b Meson: add pytest hook 2021-09-23 22:48:12 -04:00
Juan Pablo Ugarte
523d90eb28 MetaInfo: fix validation 2021-09-23 22:46:48 -04:00
Juan Pablo Ugarte
a8e6a6968f Tests: use tarket tk as get_original_exported_as_str() param
Remane gtk3/4 directories as gtk+-3.0 gtk-4.0
2021-09-23 22:26:36 -04:00
Juan Pablo Ugarte
bcd10147d2 Tests: remove old files 2021-09-23 22:18:50 -04:00
Fernando Ripoll
1953015103 Cmb*: fixed deprecation warnings
Fix PyGIDeprecationWarning:
     - GObject.property is deprecated; use GObject.Property instead
     - GObject.SIGNAL_RUN_LAST is deprecated; use GObject.SignalFlags.RUN_LAST instead
     - GObject.SIGNAL_RUN_FIRST is deprecated; use GObject.SignalFlags.RUN_FIRST instead
2021-09-23 22:17:31 -04:00
Fernando Ripoll
d7cdc1b5bc test: add first tests 2021-09-23 22:17:29 -04:00
Juan Pablo Ugarte
6020aec89b CmbDB: ignore "Created with Cambalache: comment on import 2021-09-23 20:36:16 -04:00
Juan Pablo Ugarte
e4b0ff32cb Update icons
Add new app and project placeholder icons.

Close issue #27
2021-09-23 17:21:35 -04:00
Juan Pablo Ugarte
6ec19bed0d CmbDB: support old project format
Old format root tag is <project> instead of <cambalache-project>

Close issue #29
2021-09-23 16:28:29 -04:00
Juan Pablo Ugarte
69b39dedbf Merge branch 'jtojnar-main-patch-42854' into 'main'
CmbWindow: fix filter MIME type

See merge request jpu/cambalache!7
2021-09-23 14:32:47 +00:00
Jan Tojnar
71007079bc CmbWindow: fix filter MIME type
It needs to match the one defined in the MIME file or the file chooser will be empty.
2021-09-23 10:06:19 +00:00
Juan Pablo Ugarte
2c3dce43fb Update es translation. 2021-09-21 20:33:20 -04:00
Juan Pablo Ugarte
3960d2e88d CmbDB: add gtk4-builder-tool note on import error.
Close issue #25
2021-09-21 20:15:25 -04:00
Juan Pablo Ugarte
e843d9b28e CmbWindow: use secondary text on present_message_to_user() 2021-09-21 20:15:24 -04:00
Juan Pablo Ugarte
a509e64d88 CmbDB: improve import error messages
Infer gtk target version for files without a requires tag.

Close issue #25
2021-09-20 18:33:20 -04:00
Juan Pablo Ugarte
2f32452ad1 CmbWindow: use basename in import error message. 2021-09-20 18:29:18 -04:00
Juan Pablo Ugarte
1ed26fbb2f MetaInfo: add info about releases 2021-09-20 17:25:17 -04:00
Juan Pablo Ugarte
783596472c MetaInfo: add donation url 2021-09-14 17:16:10 -04:00
Juan Pablo Ugarte
75fe43573d Rename project mime type to x-cambalache-project
Add magic rules even if they are ignored by flatpak.
2021-09-14 17:15:18 -04:00
Juan Pablo Ugarte
750a6ac10b Update gobject and gtk catalogs to new format 2021-09-14 17:14:36 -04:00
Juan Pablo Ugarte
219cfb2aeb data/cambalache-catalog.dtd, data/cambalache-project.dtd: Add DTD files 2021-09-14 17:13:56 -04:00
Juan Pablo Ugarte
13f9a35ca0 CmbDB: update formats
- Load catalog data from root tag.
 - Rename project root tag to cambalache-project
 - Save project DOCTYPE
2021-09-14 17:12:15 -04:00
Juan Pablo Ugarte
10547c3a19 cambalache-db: move library data to root tag.
Move data from library, library_version and library_dependencies to root tag.
2021-09-14 17:10:19 -04:00
Juan Pablo Ugarte
c34c2da017 CmbDB: fk cleanup
Add foreign_keys wrapper property to set/unset db FK support.
Allways commit before executing PRAGMA to make sure it takes effect.

This fixes ON DELETE CASCADE table constrains.
2021-09-05 11:35:32 -04:00
Juan Pablo Ugarte
1a1c50b717 run-dev.py: add support for catalogs dir 2021-09-03 20:44:15 -04:00
Juan Pablo Ugarte
8a236109b6 CmbDB: load data from catalogs files
Stop using SQL for catalog data
2021-09-03 18:57:41 -04:00
Juan Pablo Ugarte
b4f80453f3 plugins: add plugins directory
Add plugins directory with gobject and gtk catalogs
2021-09-03 18:57:41 -04:00
Juan Pablo Ugarte
d64d5a5ad7 cambalache-db: change output to xml
Use simple xml file format similar to .cmb files instead of plain sql
2021-09-03 18:57:41 -04:00
Juan Pablo Ugarte
9a4bd7b705 Delete gobject and gtk sql data files 2021-09-03 18:57:41 -04:00
Juan Pablo Ugarte
25c77f97f6 cambalache.in: remove unedeed print 2021-09-02 16:51:56 -04:00
Juan Pablo Ugarte
c07d5c67a7 CmbWindow: cleanup intro once it finished.
Make sure no intro callbacks are left behind after we are done
with the tutorial.

This fixes the bug that the intro would get triggered again on
new project creation or when adding a new window
2021-09-02 16:09:50 -04:00
Juan Pablo Ugarte
2bd83feafd CmbTutor: fix pause()
Make sure we do not emit 'hide-node' on pause()
2021-09-02 16:08:42 -04:00
Juan Pablo Ugarte
3c2b34795c po: Update es translation 2021-08-31 18:03:13 -04:00
Juan Pablo Ugarte
ad5d650f66 CmbWindow: unmark some strings 2021-08-31 18:02:00 -04:00
Juan Pablo Ugarte
803ad9ab42 Gtk+ Data: delete version data older than 3.0 2021-08-31 17:40:36 -04:00
Juan Pablo Ugarte
0865b4e5a9 cambalache-db: cleanup versions
Ignore versions olders than the library base version.
2021-08-31 17:39:43 -04:00
Juan Pablo Ugarte
16ab364e29 GObject Data: update library versions 2021-08-31 16:59:58 -04:00
Juan Pablo Ugarte
948b2f7bba cambalache-db: various improvements
- Use argsparse to parse command line arguments
- Inspect type, property and signal versions to infer library target versions
- Fix interface parent name
- Support parents from Gtk library
2021-08-31 16:59:50 -04:00
Juan Pablo Ugarte
f7691d89eb CmbObjectEditor: set modified css class on properties labels
Make modified properties label italic
2021-08-30 22:26:59 -04:00
Juan Pablo Ugarte
a1ce07b31b CmbProject: use new CmbObject private functions to emit property changed signals 2021-08-30 22:25:35 -04:00
Juan Pablo Ugarte
1b6b99959d CmbProperty, CmbLayoutProperty: add object property
Emit property-changed and layout-property-changed signals using CmbObject
private function instead of CmbProject
2021-08-30 22:25:23 -04:00
Juan Pablo Ugarte
17d6884216 CmbObject: add property-changed and layout-property-changed signals 2021-08-30 22:25:06 -04:00
Juan Pablo Ugarte
4f94a530ac MetaInfo: simplify summary
Close issue #23
2021-08-30 16:51:45 -04:00
Juan Pablo Ugarte
bbc605b5a6 CmbDB: Add support for save-always properties
We can not always rely on a property default value to know if it should
be serialized or not, since some properties value change at runtime
depending when or how the widget was created.

For example GtkGrid children property change depending when the child was added so
column and row should always be serialized.

GtkGrid data: mark column, row, left-attach, top-attach as save always.

Closses issue #10
2021-08-27 16:10:18 -04:00
Juan Pablo Ugarte
cbdff112bf Base Model: add property save_always column 2021-08-27 15:55:20 -04:00
Juan Pablo Ugarte
e764203949 Type Data: update gtk data
Update data to fix enum/flags names

GLib uses the c identifier as the enum/flag name not the name provided in Gir

Closes issue #22
2021-08-26 16:41:38 -04:00
Juan Pablo Ugarte
7ad27bed18 CmbObjectEditor: always use enum/flag nick instead of name 2021-08-26 16:41:38 -04:00
Juan Pablo Ugarte
99cc60f754 cambalache-db: use enum/flag identifier as name 2021-08-26 16:41:38 -04:00
Juan Pablo Ugarte
298927b066 DataModel: remove identifier column from enum/flags 2021-08-26 16:41:22 -04:00
Juan Pablo Ugarte
9cf9bbe1d7 CmbWindow: set license to lgpl-2-1-only 2021-08-26 16:36:37 -04:00
Juan Pablo Ugarte
4a07b2c2f4 Readme: update flatpak information 2021-08-24 16:10:05 -04:00
Juan Pablo Ugarte
d220d754dc Rolling 0.7.4 2021-08-24 14:44:02 -04:00
Juan Pablo Ugarte
9dc70469f9 Tools: add update-supporters script 2021-08-24 14:41:05 -04:00
Juan Pablo Ugarte
e78e96705b CmbDB: fix import regression
Use iterchildren() instead of iter()

Closes issue #19
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
05d4e62979 CmbDB: ensure requires tag on export
Make sure we always export a requires tag on export_ui()
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
e3e0576426 CmbView: sync webview bg with gtk theme
Closes issue #18
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
c7d60a5492 CmbDB: fix regression
Previous fix for object properties broke regular properties export
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
71da26ced9 Gtk Data: update object metadata 2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
0f0b2b0d3b cambalache-db: put custom data tags in its own tag
Update tools to work with current source modules
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
b7a3c02acb run-dev.py: update mime database
Make sure a mime is installed otherwise open will not work
if Cambalache is not installed
2021-08-24 14:35:13 -04:00
Juan Pablo Ugarte
d306cdf6bd CmbObjectEditor: cleanup object selector widget 2021-08-23 15:15:56 -04:00
Juan Pablo Ugarte
dafdc6cd00 CmbDB: fix object property id output
Properly use object name for object properties references.

Closes issue #17
2021-08-23 15:14:34 -04:00
Juan Pablo Ugarte
d08b182068 CmbWindow: add sponsors credit section
Add sponsors from SUPPORTERS.md file to about dialog credits section.
2021-08-21 10:20:08 -04:00
Juan Pablo Ugarte
0ceb344e9c README: add Flathub Beta, Contributing and Financial support sections
Add supporters file.
2021-08-21 10:18:28 -04:00
Juan Pablo Ugarte
7335dbc5ba meson: add python dependency check
Add custom check for all required python dependencies.
2021-08-20 19:01:29 -04:00
Juan Pablo Ugarte
0022230d8a Rolling 0.7.3
Add releases to meta info file
2021-08-18 18:45:53 -04:00
Juan Pablo Ugarte
0c2f78140f Rolling 0.7.2 2021-08-18 18:30:46 -04:00
Juan Pablo Ugarte
8889e7c0d3 Meta info: Fix screenshot captions for flathub 2021-08-18 18:29:52 -04:00
Juan Pablo Ugarte
908b7919fb Rolling 0.7.1 2021-08-18 18:16:00 -04:00
Juan Pablo Ugarte
037d48e7be Meta info: add more data
- License
 - screenshots
 - homepage
 - bugtracker
2021-08-18 18:14:43 -04:00
Juan Pablo Ugarte
094df1e841 Flatpak: Remove lxml dependency
lxml comes in Gnome sdk 40
2021-08-18 18:14:43 -04:00
Juan Pablo Ugarte
050d2e5132 Merge branch 'main' into 'main'
Update German translation

See merge request jpu/cambalache!4
2021-08-18 21:19:34 +00:00
PhilProg
7bf595a9fc Update German translation 2021-08-18 21:19:34 +00:00
Juan Pablo Ugarte
e58d070138 po: add explicit instructions to update pot file
Closes issue #16
2021-08-18 15:52:39 -04:00
Juan Pablo Ugarte
610371fade po: update translations
- Added extra notes on po/README.md
 - Update es translation
 - Added cmb_tutorial.py to pot files
2021-08-18 15:09:39 -04:00
Juan Pablo Ugarte
92f4b7ddfc CmbObjectEditor: add template check
Add check button to enable disable template
2021-08-17 18:50:38 -04:00
Juan Pablo Ugarte
a227fe2a69 CmbObject: add ui property
Add convenience accessor to ui object
2021-08-17 18:50:38 -04:00
Juan Pablo Ugarte
0da5095b83 CmbDB: add support for templates 2021-08-17 18:50:38 -04:00
Juan Pablo Ugarte
019d9a1260 CmbWindow: improve intro tutorial
Hide intro button when finished

Closes issue #3 and #8
2021-08-17 18:47:48 -04:00
Juan Pablo Ugarte
2738145632 CmbTutor: various improvements
- Make popup not modal
 - Do not advance node on pause
 - Fix widget inside popup visibility
 - Fix msg popup sensitivity
2021-08-17 18:47:48 -04:00
Juan Pablo Ugarte
1b5cdd09d9 Add GSettings schema
Add completed-intro flag setting

Closes issue #12
2021-08-17 17:46:48 -04:00
Juan Pablo Ugarte
55d50d1977 CmbWindow: lookup user themes in ~/.themes too
Closes issue #15
2021-08-17 17:37:08 -04:00
Juan Pablo Ugarte
66e3625773 CmbWindow: add gtk theme combobox
Add combobox to select workspace gtk theme.

Closes issue #15
2021-08-17 16:32:16 -04:00
Juan Pablo Ugarte
55de55b113 CmbView: add gtk-theme property
Add property to set which gtk theme the view should use.
2021-08-17 16:26:35 -04:00
Juan Pablo Ugarte
17f3b17fa2 merengue: add gtk_settings_set/get commands 2021-08-17 16:26:09 -04:00
Juan Pablo Ugarte
277e44da0a CmbView: make ui definition text view non editable
UI definition text view is not for edit.

Closes issue #13
2021-08-12 17:17:22 -04:00
Juan Pablo Ugarte
33f3c44b26 CmbObject: fix regression
Fix layout properties populate on object creation.
2021-08-09 17:21:33 -04:00
Juan Pablo Ugarte
cda5e89acb CmbWindow: add basic interactive tutorial 2021-08-08 15:40:22 -04:00
Juan Pablo Ugarte
e4f409b2dc CmbTutor: add object to create interactive tutorials
Ported GladeIntro to python.
2021-08-08 15:39:20 -04:00
Juan Pablo Ugarte
64b4ca09cc Rolling 0.7.0
Closes issue #7
2021-08-08 07:31:54 -04:00
Juan Pablo Ugarte
732efb4751 Add MimeType placeholder icon 2021-08-05 21:11:37 -04:00
Juan Pablo Ugarte
d30449a5ea Add x-cambalache mimetype 2021-08-04 20:21:15 -04:00
Juan Pablo Ugarte
bc44e88ddf CmbWindow: use "project" instead of file in open project dialog 2021-08-04 18:33:59 -04:00
Juan Pablo Ugarte
bb2897ac68 README: improve doc
- Add depenencies needed in running from sources section
 - Add link to flatpak setup page

Closes issue #5
2021-08-04 18:23:02 -04:00
Juan Pablo Ugarte
c0b8b936d0 Merge branch 'german-translation' into 'main'
Add German translation

See merge request jpu/cambalache!3
2021-08-04 21:19:50 +00:00
PhilProg
5aa2358de9 Add German translation 2021-08-04 14:56:08 +00:00
Juan Pablo Ugarte
a9c25f2c24 po/cambalache.pot: Add copyright notice 2021-08-03 16:16:17 -04:00
Juan Pablo Ugarte
732edb522b po/README.md: add translations readme
Closes issue #4
2021-08-03 16:14:55 -04:00
Juan Pablo Ugarte
4dc690cc98 Fix and update translations 2021-08-03 15:31:55 -04:00
Juan Pablo Ugarte
7842dfe4e9 Merge branch 'main' into 'main'
Add Czech translation

See merge request jpu/cambalache!2
2021-08-03 13:07:52 +00:00
Vojtěch Perník
2f65a4c18e Add Czech translation 2021-08-02 11:04:26 +02:00
Juan Pablo Ugarte
0cfbd81410 README: add logo
Convert datamodel and merengue diagrams to paths to avoid missing fonts issues.
2021-07-25 10:50:22 -03:00
Juan Pablo Ugarte
53f904afee CmbDB: do not use UPDATE FROM
UPDATE FROM because is not suported in the GNOME Sdk sqlite version.
2021-07-24 15:40:57 -03:00
Juan Pablo Ugarte
15dc32b3f6 CmbWindow: use xdg-open as fallback to open sqlite file 2021-07-24 15:25:03 -03:00
Juan Pablo Ugarte
c4f85d269c License missing files as LGPL v2.1 2021-07-23 09:00:00 -03:00
Juan Pablo Ugarte
94a7cb54f5 Restructure modules hierarchy
Improve Cambalache instalation in host filesystem.
Python module is instaled in sites-packages, executable in bindir and resources
in pkgdatadir.

 - Rename cambalacheui to cambalache
 - Move merengue inside cambalache module
 - Move src to cambalache/app
 - Do not install merengue in bindir
 - Install merengue in private directory (moduledir/priv)
2021-07-23 08:56:03 -03:00
Juan Pablo Ugarte
0010f92a37 Merge branch 'appdata' into 'main'
Changing /usr/share/appdata to /usr/share/metainfo

See merge request jpu/cambalache!1
2021-07-22 23:10:16 +00:00
Lyes Saadi
109066772c Changing /usr/share/appdata to /usr/share/metainfo 2021-07-22 23:10:16 +00:00
Juan Pablo Ugarte
c91562afbd Rolling 0.6.0 2021-07-21 11:34:42 -03:00
Juan Pablo Ugarte
c2085192cb License as LGPL version 2.1 only. 2021-07-21 11:34:42 -03:00
Juan Pablo Ugarte
f2a46d5c70 CmbDB: add basic project file format
Use xml with python tuples for table data.
2021-07-21 11:34:42 -03:00
Juan Pablo Ugarte
407ac9ab40 CmbWindow: add donate menu item 2021-07-20 22:58:20 -03:00
Juan Pablo Ugarte
f6670b4c43 CmbWindow: show version
TODO: add version to logo
2021-07-20 10:44:46 -03:00
Juan Pablo Ugarte
ef98e78bd5 Merengue: Add window wrapper for toplevel widgets 2021-07-20 09:53:37 -03:00
Juan Pablo Ugarte
d68fb35e94 Add logo by Franco Dodorico 2021-07-15 17:18:04 -03:00
Juan Pablo Ugarte
dc66113998 glade: fix module loading 2021-07-11 20:06:30 -03:00
Juan Pablo Ugarte
5d50447a82 CmbDB: include object data tables on save 2021-07-05 19:56:02 -03:00
Juan Pablo Ugarte
9933361e9b CmbObjectEditor: add basic support for object properties
Add bare minimun object property support, all it allows you is to type an
object name, no check no picker yet.
2021-07-05 19:22:10 -03:00
Juan Pablo Ugarte
98cd6fc234 CmbView: add is_object paremeter to object_property_changed command 2021-07-05 19:21:42 -03:00
Juan Pablo Ugarte
411ecb1fe2 CmbProject: add _get_object_by_name() 2021-07-05 19:20:26 -03:00
Juan Pablo Ugarte
d12cc85753 CmbDB: improve object id handling
Use object id in properties that reference an object
2021-07-05 17:28:21 -03:00
Juan Pablo Ugarte
8cad11c93e Merengue: use __cmb__ prefix in object id
Remove Utils.object_get_name()
2021-07-05 17:26:54 -03:00
Juan Pablo Ugarte
0953f039e2 CmbObjectEditor: Handle inf in CmbSpinButton set value function 2021-07-05 17:24:55 -03:00
Juan Pablo Ugarte
0be6231e75 CmbObjectEditor: fix inf/-inf handling
Fix error setting lower/upper adjustments values from pspec mix/max values
2021-06-24 19:17:57 -03:00
Juan Pablo Ugarte
b7fe899536 CmbSwitch: handle all possible true values
Handle '1', 't', 'y', 'true' and 'yes' as true values
2021-06-24 18:05:06 -03:00
Juan Pablo Ugarte
0adc10d044 CmbObjectEditor: use revealer with expander 2021-06-22 21:12:13 -03:00
Juan Pablo Ugarte
215486d04d DataModel: add is_object column to property table
Add helper column to property to when if the property is a object property or not
This way we can easily know if value points to an object id or not.
2021-06-22 20:37:47 -03:00
Juan Pablo Ugarte
de842852bb Merengue: allways check for Gtk version 4 instead of 3
Just in case we ever want to support Gtk 2 which is closer to 3 api wise.
2021-06-22 20:03:37 -03:00
Juan Pablo Ugarte
54f7fc23eb MrgGtkWidget: update toplevel state
Set toplevel state to backdrop when it is not selected.

Utils.object_get_id() check object_get_builder_id() retval
2021-06-22 09:22:28 -03:00
Juan Pablo Ugarte
df1ad6ee3f Merengue: improve widget selection.
Consume selection events to avoid propagating it to the widget.
First click selects widget then events propagate normaly.
This fixes combobox selection.
2021-06-21 17:47:36 -03:00
Juan Pablo Ugarte
d8c199b6b2 MrgGtkWindow: ignore modal property
Avoid GtkWindow:modal property in the workspace to allow working
with all windows
2021-06-18 20:42:13 -03:00
Juan Pablo Ugarte
ef0322c4ba MrgController: add selected property
Cleanup selection implementation by storing state in controller object
2021-06-18 20:41:02 -03:00
Juan Pablo Ugarte
222aa0b658 CmbDB: make export_ui return lxml tree object 2021-06-17 20:45:10 -03:00
Juan Pablo Ugarte
a0c89ad66b Merengue: fix widget selection
Fix object_get_id() to ignore token after +
2021-06-17 20:44:08 -03:00
Juan Pablo Ugarte
d3dbe6a91e CmbProject: create history tables for object_data and object_data_arg 2021-06-16 20:22:10 -03:00
Juan Pablo Ugarte
4479688d57 run-dev.py: improve dev script 2021-06-16 10:31:32 -03:00
Juan Pablo Ugarte
b7091ae8b0 Update Gtk 3 and 4 data 2021-06-15 19:19:33 -03:00
Juan Pablo Ugarte
e3125d79a7 cambalache-db: add gtk type extra data
Add common Gtk type extra data.
2021-06-15 19:19:33 -03:00
Juan Pablo Ugarte
13807c69ac CmbProject: populate type extra data
CmbDB: add type data import/export support
2021-06-15 19:19:22 -03:00
Juan Pablo Ugarte
bab4b77020 CmbTypeInfo: add data dict
Add dict of CmbTypeData to describe type extra data
2021-06-15 19:07:31 -03:00
Juan Pablo Ugarte
97348b15d0 DataModel: Add support for custom type data.
Add type_data, type_data_arg, object_data and object_data_arg tables

These tables allow us to store extra data for each type in a hierachical way.
It does not have any particular restrictions which means it is responsability
of the editor to create a valid structure.
2021-06-15 19:05:35 -03:00
Juan Pablo Ugarte
2173966d2c CmbDB: add suport for internal and child type
Add support for loading/saving internal child nsmes and child type.
2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
5fba1d80ce DataModel: add internal and type columns to object table
Update wrapper objects for object table
2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
4b86111aad MrgApplication: disable CSS animations
Disable animations to avoid high CPU ussage.

Improve selection_change() object check
2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
41a85c4213 Utils.object_get_builder_id: use g_object_get_data('gtk-builder-name')
Use C function to get data for non buildable objects
2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
7752351711 MrgControllerRegistry: make sure a controller is returned for GObjects 2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
2baed9f675 MgrApplication: only try to sef style class to widgets 2021-06-04 19:31:36 -03:00
Juan Pablo Ugarte
d15f2f7097 Add to do file 2021-06-04 19:06:46 -03:00
Juan Pablo Ugarte
642fd030c5 CmbProject: improve _add_ui() and _add_object()
Do not set properties backed in DB when creating CmbUI and CmbObject
object wrappers to avoid doing unnecessary DB calls.
This improves load and import speed.
2021-05-28 19:31:33 -03:00
Juan Pablo Ugarte
58c12b636f Type info cleanup
CmbTypeInfo: add properties dict and made signals a dict

CmbProject: replace  _property_info with _type_info[].properties

CmbDB: use type_info to get property and signal owner_id
This improves import time a lot by not using type_tree view at all.
2021-05-28 11:22:03 -03:00
Juan Pablo Ugarte
a7780a6b26 CmbWindow: update UI while importing file 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
cacec1107f CmbDB: add import_file() from CmbProject 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
ccfd048343 CmbView: moved export_ui() from CmbProject 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
d49c37728e CmbProject: improve import() method
Use methods from CmbDB to add ui and objects to DB.
This allows to improve file import by not emiting lots of
object-added signals.
2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
3a119f3f80 CmbDB: add add_ui() and add_object() methods 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
eac8c8ee69 Add comment support
Add support for comments in, ui, object, properties, layout properties and signals.
2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
f8b4737b89 Update es translation 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
c83c26ec64 CmbWindow: catch import_file() exceptions 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
80b269208e CmbProject: improve import_file()
Check import file target_tk matches projects
Mark translatable strings
2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
3c4ba66c46 Update to new CmbProject api
Replace project.conn with project.db
2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
bcc7f6b2c8 CmbProject: use new CmbDB class instead of sqlite3 2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
d9a7a85aff CmbDb: add DB wrapper object
New class to handle all DB related operations
2021-05-28 10:32:39 -03:00
Juan Pablo Ugarte
4244211396 CmbView: fix encoding of xml
This fixes non ascii characters in workspace.
2021-05-21 19:04:04 -03:00
Juan Pablo Ugarte
5ab8e5c61a Support Data: update data 2021-05-21 16:25:28 -03:00
Juan Pablo Ugarte
6b463b7d4f CmbObjectEditor: add support for gunichar
Use CmbEntry with 1 char limit for gunichar properties
Currently only GtkEntry:invisible-char
2021-05-21 16:25:28 -03:00
Juan Pablo Ugarte
fab1a13e97 DataModel: add gunichar base type
Remove unsupported types like gpointer

TODO: add support for some GBoxed and maybe GVariant
2021-05-21 16:25:28 -03:00
Juan Pablo Ugarte
cec9ce53b2 tools: improve property type detection
Use pspec name if possible to decide property type, needed for unichar
properties which real type is uint.

Ignore gboxed params for now

Gtk3: skip GtkAccessible object types

Fix issues with cambalache rename to cambalacheui
2021-05-21 16:25:28 -03:00
Juan Pablo Ugarte
f9debc11b8 Rolling 0.5.92 2021-05-20 20:47:37 -03:00
Juan Pablo Ugarte
5dde6a92d4 CmbApplication, CmbWindow: add uiname parameter to open() methods
Improve creating a new project when one is already open.
2021-05-20 19:25:21 -03:00
Juan Pablo Ugarte
ef5e3f4882 CmbView: fix calling broadwayd
Use proper paramenter to support more than one broadway display
at the same time.
2021-05-20 19:25:21 -03:00
Juan Pablo Ugarte
fe01ea88ae Reorganize project files
Rename cambalache to cambalacheui and install it as a python module
Install merengue and cambalache module in pkgdatadir
Add localization support with spanish translation
Add desktop file and appstream file
2021-05-20 19:25:21 -03:00
Juan Pablo Ugarte
bd28816875 CmbWindow: add margins to object editor 2021-05-19 12:30:36 -03:00
Juan Pablo Ugarte
d355734856 CmbObject: populate signals from project
Create signals from project data on object creation

CmbProject: populate object signals on import
2021-05-18 19:00:28 -03:00
Juan Pablo Ugarte
93249e74db CmbView: add extra check in merengue_command() 2021-05-14 10:36:11 -03:00
Juan Pablo Ugarte
95899e676f MrgController: add remove_object()
Add method to remove object from hierarchy
2021-05-11 19:36:09 -03:00
Juan Pablo Ugarte
5ef43da31c merengue: use object name as window title 2021-05-11 18:38:45 -03:00
Juan Pablo Ugarte
b6f298dbc3 CmbProject: append name to the end of id when exporting xml using id
Send object name in xml description file
2021-05-11 18:37:42 -03:00
Juan Pablo Ugarte
1916691942 Update library data
Remove get_type and add 'container' layout type for classes that
can have children
2021-05-10 23:02:29 -03:00
Juan Pablo Ugarte
26c41c544b DataModel: remove get_type from type table
This is not used and I do not plan on using it
2021-05-10 23:02:29 -03:00
Juan Pablo Ugarte
b915cbad5b cambalache-db: mark container classes
Mark every class that implements buildable->add_child as a container class
This way we know in which widgets can have chidren
2021-05-10 23:02:29 -03:00
Juan Pablo Ugarte
bc9006ddb1 CmbProject: add 'container' layout type
Add value in type.layout to diferentiate container classes
2021-05-10 23:02:29 -03:00
Juan Pablo Ugarte
47b0d2c127 Fix flatpak build and bump version 2021-05-07 17:38:09 -03:00
Juan Pablo Ugarte
490e3389a5 merengue: improve selection CSS
Special case window on gtk 3 and 4 to change shadow color
2021-05-07 17:24:21 -03:00
Juan Pablo Ugarte
58a260c297 MrgGtkWindowController: save window state
Save and restore size, position and maximized state

TODO: support position on Gtk 4
2021-05-07 16:58:46 -03:00
Juan Pablo Ugarte
0c90f5f31d MrgApplication: create GtkApplication derived class
Use a non unique GtkApplication, not really needed but cleaner
2021-05-06 18:19:29 -03:00
Juan Pablo Ugarte
6a4c82d154 cmb_application: remove objects and toplevels globals 2021-05-06 18:19:29 -03:00
Juan Pablo Ugarte
02cfc9d186 merengue: move most utils functions to controllers 2021-05-06 18:19:29 -03:00
Juan Pablo Ugarte
6700a0e84b merengue: Use mrg prefix instead of cmb 2021-05-06 17:07:23 -03:00
Juan Pablo Ugarte
3bef84229c merengue: add controller submodule
Add controller registry object to handle controllers automatically
2021-05-05 20:02:10 -03:00
Juan Pablo Ugarte
fcb5c36492 Merengue: marshal object property
Use CmbControler set_object_property() to sync properties to be able to
ignore properties that do not make sense like 'visibility'
2021-05-03 22:39:39 -03:00
Juan Pablo Ugarte
3cdec0d65c Merenngue: add object controller initial implementation
Move widget support logic to a submodule.
2021-05-03 00:24:34 -03:00
Juan Pablo Ugarte
933fc676c5 CmbView: add restart workspace context menu option
Add option to restart broadway and merengue processes
2021-04-28 18:51:26 -03:00
Juan Pablo Ugarte
14ccfe4077 CmbView: update xml only if textview is visible 2021-04-27 19:52:54 -03:00
Juan Pablo Ugarte
180fa0beb7 Split cmb_objects.py into multiple files 2021-04-27 19:37:08 -03:00
Juan Pablo Ugarte
26f95b2587 CmbProject: add _get_text_resource()
Add function to get text resources
2021-04-27 18:20:33 -03:00
Juan Pablo Ugarte
1f18769c7d CmbProject: fix history clear command 2021-04-27 18:15:57 -03:00
Juan Pablo Ugarte
a369ae1653 Merengue: fix object_removed command 2021-04-25 15:45:56 -03:00
Juan Pablo Ugarte
32e5f9f83a CmbView: ignore webview alert function and make sure oncontextmenu is not overridden 2021-04-25 15:44:58 -03:00
Juan Pablo Ugarte
2ac667fc8f CmbView: major cleanup
Improve process handling using a common class to run merengue and broadwayd
2021-04-25 12:22:09 -03:00
Juan Pablo Ugarte
3acd4ff66a CmbProject: use CmbProperty for object-property-changed and CmbLayoutProperty for
object-layout-property-changed signals
2021-04-25 12:11:44 -03:00
Juan Pablo Ugarte
c0a51586b4 Merengue: add support for selecting widgets from workspace
Move all gtk3/4 compatibility functions to utils.py
2021-04-25 12:04:09 -03:00
Juan Pablo Ugarte
3c0aae6d19 Ignore non writable properties
Remove writable column from property and update data base.
2021-04-20 10:47:34 -03:00
Juan Pablo Ugarte
392f96b8f3 CmbView: integrate Merengue viewer
Use a webview to connect to broadwayd to show a canvas with all the
widgets from the project created by Merengue process
2021-04-20 10:47:34 -03:00
Juan Pablo Ugarte
c12ef06bf2 Merengue: Add initial implementation of view process 2021-04-20 10:47:34 -03:00
Juan Pablo Ugarte
ea7970b4a1 CmbProject: add use_id param to export_ui()
Add option to export objects using object id from DB
2021-04-18 22:34:38 -03:00
Juan Pablo Ugarte
6813f1280f CmbWindow: add UI filename input to new project screen
Add an UI file by default based on project name
2021-04-12 23:30:15 -03:00
354 changed files with 70216 additions and 11322 deletions

18
.flake8 Normal file
View File

@ -0,0 +1,18 @@
[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
View File

@ -1,9 +1,28 @@
*.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
src/cambalache_app.gresource
src/config.py
tools/CmbUtils-0.1.gir
tools/CmbUtils-0.1.typelib
tools/libcmbutils.so
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

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tests/images"]
path = tests/images
url = https://gitlab.gnome.org/jpu/cambalache-test-images.git

323
CHANGELOG.md Normal file
View File

@ -0,0 +1,323 @@
# 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
View File

@ -1,8 +1,459 @@
Cambalache UI Maker
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 2020-2021 Juan Pablo Ugarte - All Rights Reserved
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.
Unauthorized copying of this project, via any medium is strictly prohibited.
[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
This application is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.

280
COPYING.GPL Normal file
View File

@ -0,0 +1,280 @@
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 Normal file
View File

@ -0,0 +1,60 @@
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 Normal file
View File

@ -0,0 +1,19 @@
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

16
README.mac.md Normal file
View File

@ -0,0 +1,16 @@
## 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
View File

@ -1,19 +1,196 @@
# Cambalache
![Cambalache](cambalache/app/images/logo-horizontal.svg)
UI Designer Library
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.
## cambalache-db
![Data Model Diagram](datamodel.svg)
Cambalache Data Model generation tool
To support multiple Gtk versions it renders the workspace out of process using
a custom wayland compositor widget based on wlroots.
## db-codegen
![Merengue Diagram](merengue.svg)
Tool to generate GObject classes from DB tables
## 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
```
## 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 cambalache.flatpak
flatpak install --user cambalache.flatpak
```
Or if you have `make` installed in your host
```
make install
```
Will create the flatpak repository, then the bundle and install it
Run as:
```
flatpak run --user ar.xjuan.Cambalache//master
```
## Manual installation
This is a regular meson package and can be installed the usual way.
```
# Configure project in _build directory
meson setup --wipe --prefix=~/.local _build .
# 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 Normal file
View File

@ -0,0 +1,127 @@
## 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.

26
SUPPORTERS.md Normal file
View File

@ -0,0 +1,26 @@
# 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

View File

@ -1,14 +1,17 @@
{
"app-id" : "ar.xjuan.Cambalache",
"runtime" : "org.gnome.Platform",
"runtime-version" : "40",
"runtime-version" : "48",
"sdk" : "org.gnome.Sdk",
"command" : "ar.xjuan.Cambalache",
"separate-locales" : false,
"command" : "cambalache",
"finish-args" : [
"--share=ipc",
"--share=network",
"--socket=fallback-x11",
"--socket=wayland",
"--filesystem=home"
"--filesystem=home",
"--device=dri"
],
"cleanup" : [
"/include",
@ -23,16 +26,65 @@
],
"modules" : [
{
"name": "lxml",
"buildsystem": "simple",
"build-commands": [
"pip3 install lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl"
"name" : "python3-lxml",
"buildsystem" : "simple",
"build-commands" : [
"pip3 install --exists-action=i --ignore-installed --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"lxml\" --no-build-isolation"
],
"sources": [
"sources" : [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/52/a5/98a73a83bb06d271c3f915a4a13f9613fdd8a685524f46d9733eebeb55ce/lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl",
"sha256": "2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644"
"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"
}
]
},
@ -44,9 +96,15 @@
{
"type" : "git",
"path" : ".",
"branch": "HEAD"
"branch" : "HEAD"
}
],
"config-opts" : [
"--libdir=lib"
]
}
]
],
"build-options" : {
"env" : { }
}
}

View File

@ -1,19 +1,107 @@
# Cambalache
#
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
# Copyright (C) 2021-2024 Juan Pablo Ugarte
#
# Unauthorized copying of this file, via any medium is strictly prohibited.
# 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 .config import *
from gi.repository import Gio
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'cambalache.gresource'))
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"))
resource._register()
from .cmb_objects import *
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_project import CmbProject
from .cmb_db_inspector import CmbDBInspector
from .cmb_view import CmbView
from .cmb_tree_view import CmbTreeView
from .cmb_list_view import CmbListView
from .cmb_notification import notification_center, CmbNotification, CmbNotificationCenter
from .cmb_notification_list_view import CmbNotificationListView
from .cmb_object_editor import CmbObjectEditor
from .cmb_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

View File

@ -0,0 +1 @@
../../SUPPORTERS.md

View File

@ -0,0 +1,34 @@
# 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

View File

@ -0,0 +1,16 @@
<?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>

View File

@ -0,0 +1,140 @@
/*
* 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;
}

46
cambalache/app/cambalache.in Executable file
View File

@ -0,0 +1,46 @@
#!@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)

View File

@ -0,0 +1,264 @@
#
# 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)

View File

@ -0,0 +1,44 @@
#
# 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

View File

@ -0,0 +1,111 @@
<?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">&lt;primary&gt;n</property>
<property name="title" translatable="yes">Create new project</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;o</property>
<property name="title" translatable="yes">Open a project</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;i</property>
<property name="title" translatable="yes">Import file</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;s</property>
<property name="title" translatable="yes">Save project</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;&lt;shift&gt;s</property>
<property name="title" translatable="yes">Save project as</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;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">&lt;primary&gt;question</property>
<property name="title" translatable="yes">Keyboard Shortcuts</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;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">&lt;primary&gt;Insert</property>
<property name="title" translatable="yes">Add slot/column</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;Delete</property>
<property name="title" translatable="yes">Remove slot/column</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;&lt;shift&gt;Insert</property>
<property name="title" translatable="yes">Add row</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primary&gt;&lt;shift&gt;Delete</property>
<property name="title" translatable="yes">Remove row</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

230
cambalache/app/cmb_tutor.py Normal file
View File

@ -0,0 +1,230 @@
#
# 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

View File

@ -0,0 +1,90 @@
#
# 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"),
]

1555
cambalache/app/cmb_window.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,877 @@
<?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">&lt;b&gt;
&lt;span size="18000"&gt;New project&lt;/span&gt;
&lt;/b&gt;</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">&lt;project basename&gt;</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">&lt;b&gt;Gtk 3&lt;/b&gt;</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">&lt;b&gt;Gtk 4&lt;/b&gt;</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">&lt;/&gt;</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">&lt;/&gt;</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>

View File

@ -0,0 +1,21 @@
<?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>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,12 @@
<?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>

After

Width:  |  Height:  |  Size: 728 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,10 @@
<?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>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,2 @@
<?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>

After

Width:  |  Height:  |  Size: 505 B

View File

@ -0,0 +1,33 @@
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
cambalache/app/metainfo.xml Symbolic link
View File

@ -0,0 +1 @@
../../data/ar.xjuan.Cambalache.metainfo.xml.in

290
cambalache/cambalache.cmb Normal file
View File

@ -0,0 +1,290 @@
<?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>

131
cambalache/cambalache.css Normal file
View File

@ -0,0 +1,131 @@
/*
* 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;
}

View File

@ -1,12 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<gresources>
<gresource prefix="/ar/xjuan/Cambalache">
<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>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_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>

View File

@ -0,0 +1,236 @@
#
# CmbAccessibleEditor - Cambalache Accessible Editor
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk
from .cmb_object import CmbObject
from .control import cmb_create_editor, CmbEnumComboBox
from .cmb_property_label import CmbPropertyLabel
from . import utils, _
class CmbAccessibleEditor(Gtk.Box):
__gtype_name__ = "CmbAccessibleEditor"
def __init__(self, **kwargs):
self.__object = None
self.__bindings = []
self.__accessibility_metadata = None
self.__role_filter_model = None
super().__init__(**kwargs)
self.props.orientation = Gtk.Orientation.VERTICAL
self.__role_box = Gtk.Box(spacing=6)
self.__role_box.append(Gtk.Label(label="accessible-role"))
self.__role_combobox = CmbEnumComboBox()
self.__role_box.append(self.__role_combobox)
self.append(self.__role_box)
self.__box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.append(self.__box)
def bind_property(self, *args):
binding = GObject.Object.bind_property(*args)
self.__bindings.append(binding)
return binding
def __on_expander_expanded(self, expander, pspec, revealer):
expanded = expander.props.expanded
if expanded:
revealer.props.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN
else:
revealer.props.transition_type = Gtk.RevealerTransitionType.SLIDE_UP
revealer.props.reveal_child = expanded
def __update_view(self):
for child in utils.widget_get_children(self.__box):
self.__box.remove(child)
if self.__object is None or not self.__object.info.is_a("GtkWidget"):
return
obj = self.__object
if obj.project.target_tk == "gtk-4.0":
if not self.__object.info.is_a("GtkWidget"):
self.__role_box.hide()
return
self.__role_box.show()
prop = self.__object.properties_dict["accessible-role"]
if prop.value in ["none", "presentation"]:
# No need to set properties if role none or presentation
return
role_data = self.__object.project.db.accessibility_metadata.get(prop.value, None)
if role_data:
role_properties = role_data["properties"]
role_states = role_data["states"]
else:
role_properties = None
role_states = None
a11y_data = [
("CmbAccessibleProperty", _("Properties"), len("cmb-a11y-properties"), role_properties),
("CmbAccessibleState", _("States"), len("cmb-a11y-states"), role_states),
("CmbAccessibleRelation", _("Relations"), None, None),
]
else:
type_actions = self.__object.project.db.accessibility_metadata.get(obj.type_id, [])
a11y_data = [
("CmbAccessibleAction", _("Actions"), len("cmb-a11y-actions"), type_actions),
("CmbAccessibleProperty", _("Properties"), None, None),
("CmbAccessibleRelation", _("Relations"), None, None),
]
self.__role_box.hide()
properties = obj.properties_dict
for owner_id, title, prefix_len, allowed_ids in a11y_data:
info = obj.project.type_info.get(owner_id, None)
if info is None:
continue
# Editor count
i = 0
# Grid for all editors
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
# Accessible iface properties
for property_id in info.properties:
# Ignore properties or status not for this role
if prefix_len and allowed_ids is not None and property_id[prefix_len:] not in allowed_ids:
continue
prop = properties.get(property_id, None)
if prop is None or prop.info is None:
continue
editor = cmb_create_editor(prop.project, prop.info.type_id, prop=prop)
if editor is None:
return None, None
self.bind_property(
prop,
"value",
editor,
"cmb-value",
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
)
label = CmbPropertyLabel(prop=prop, bindable=False)
grid.attach(label, 0, i, 1, 1)
grid.attach(editor, 1, i, 1, 1)
i += 1
if i == 0:
continue
# Create expander/revealer to pack editor grid
expander = Gtk.Expander(label=f"<b>{title}</b>", use_markup=True, expanded=True)
revealer = Gtk.Revealer(reveal_child=True)
expander.connect("notify::expanded", self.__on_expander_expanded, revealer)
revealer.set_child(grid)
self.__box.append(expander)
self.__box.append(revealer)
self.show()
def __on_object_property_changed_notify(self, obj, prop):
if prop.property_id == "accessible-role":
self.__update_view()
def __visible_func(self, model, iter, data):
if self.__accessibility_metadata is None:
return False
name, nick, value = model[iter]
role_data = self.__accessibility_metadata.get(nick, None)
if role_data:
# Ignore abstract roles
if nick != "none" and role_data.get("is_abstract", False):
return False
return True
@GObject.Property(type=CmbObject)
def object(self):
return self.__object
@object.setter
def _set_object(self, obj):
if obj == self.__object:
return
if self.__object and self.__object.info.is_a("GtkWidget"):
self.__object.disconnect_by_func(self.__on_object_property_changed_notify)
self.__role_combobox.props.model = None
self.__accessibility_metadata = None
for binding in self.__bindings:
binding.unbind()
self.__bindings = []
self.__object = obj
if self.__object and self.__object.info.is_a("GtkWidget"):
self.__object.connect("property-changed", self.__on_object_property_changed_notify)
self.__accessibility_metadata = self.__object.project.db.accessibility_metadata
a11y_info = self.__object.project.type_info.get("GtkAccessibleRole", None)
if a11y_info:
a11y_info = self.__object.project.type_info.get("GtkAccessibleRole", None)
self.__role_filter_model = Gtk.TreeModelFilter(child_model=a11y_info.enum)
self.__role_filter_model.set_visible_func(self.__visible_func)
self.__role_combobox.info = a11y_info
self.__role_combobox.props.model = self.__role_filter_model
prop = self.__object.properties_dict.get("accessible-role", None)
self.bind_property(
prop,
"value",
self.__role_combobox,
"cmb-value",
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
)
self.__update_view()
Gtk.WidgetClass.set_css_name(CmbAccessibleEditor, "CmbAccessibleEditor")

View File

@ -1,29 +1,43 @@
#
# Cambalache Object wrappers base class
#
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
# Copyright (C) 2021 Juan Pablo Ugarte
#
# Unauthorized copying of this file, via any medium is strictly prohibited.
# 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 gi
from gi.repository import GObject
class CmbBase(GObject.GObject):
project = GObject.Property(type=GObject.GObject, flags = GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
project = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
display_name = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def db_get(self, query, pk):
c = self.project.conn.cursor()
c.execute(query, pk)
c = self.project.db.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):
c = self.project.conn.cursor()
c.execute(query, (value, ) + pk)
c.close()
self.project.db.execute(query, (value,) + pk)

View File

@ -1,198 +0,0 @@
/*
* 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;

View File

@ -0,0 +1,86 @@
#
# Blueprint compiler integration functions
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
import io
try:
import blueprintcompiler as bp
from blueprintcompiler import parser, tokenizer
from blueprintcompiler.decompiler import decompile_string
from blueprintcompiler.outputs import XmlOutput
except Exception:
bp = None
class CmbBlueprintError(Exception):
def __init__(self, message, errors=[]):
super().__init__(message)
self.errors = errors
class CmbBlueprintUnsupportedError(CmbBlueprintError):
pass
class CmbBlueprintMissingError(CmbBlueprintError):
def __init__(self):
super().__init__("blueprintcompiler is not available")
def cmb_blueprint_decompile(data: str) -> str:
if bp is None:
raise CmbBlueprintMissingError()
try:
retval = decompile_string(data)
except bp.decompiler.UnsupportedError as e:
raise CmbBlueprintUnsupportedError(str(e))
except Exception as e:
raise CmbBlueprintError(str(e))
return retval
def cmb_blueprint_compile(data: str) -> str:
if bp is None:
raise CmbBlueprintMissingError()
tokens = tokenizer.tokenize(data)
ast, errors, warnings = parser.parse(tokens)
if errors:
f = io.StringIO("")
errors.pretty_print("temp", data, f)
f.seek(0)
raise CmbBlueprintError(f.read(), errors=errors)
if ast is None:
raise CmbBlueprintError("AST is None")
# Ignore warnings
retval = XmlOutput().emit(ast)
return retval.encode()

View File

@ -0,0 +1,141 @@
#
# 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()

View File

@ -0,0 +1,57 @@
<?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>

161
cambalache/cmb_css.py Normal file
View File

@ -0,0 +1,161 @@
#
# 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")

View File

@ -0,0 +1,200 @@
#
# 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")

View File

@ -0,0 +1,203 @@
<?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">&lt;small&gt;Note: CSS files need to be loaded at runtime&lt;/small&gt;</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 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,294 @@
#
# 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()

View File

@ -0,0 +1,21 @@
<?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>

View File

@ -0,0 +1,163 @@
#
# 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;
"""
)

View File

@ -0,0 +1,112 @@
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

View File

@ -0,0 +1,86 @@
#
# 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")

View File

@ -0,0 +1,61 @@
<?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>

151
cambalache/cmb_gresource.py Normal file
View File

@ -0,0 +1,151 @@
#
# Cambalache GResource wrapper
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gio
from .cmb_path import CmbPath
from .cmb_objects_base import CmbBaseGResource
from .cmb_list_error import CmbListError
from cambalache import _
class CmbGResource(CmbBaseGResource, Gio.ListModel):
path_parent = GObject.Property(type=CmbPath, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
self._last_known = None
super().__init__(**kwargs)
self.connect("notify", self.__on_notify)
def __bool__(self):
return True
def __str__(self):
return f"CmbGResource<{self.resource_type}> id={self.gresource_id}"
def __on_notify(self, obj, pspec):
resource_type = self.resource_type
if (resource_type == "gresources" and pspec.name == "gresources-filename") or \
(resource_type == "gresource" and pspec.name == "gresource-prefix") or \
(resource_type == "file" and pspec.name == "file-filename"):
obj.notify("display-name")
self.project._gresource_changed(self, pspec.name)
@GObject.Property(type=CmbBaseGResource)
def parent(self):
if self.resource_type in ["gresource", "file"]:
return self.project.get_gresource_by_id(self.parent_id)
return None
@GObject.Property(type=CmbBaseGResource)
def gresources_bundle(self):
resource_type = self.resource_type
if resource_type == "gresource":
return self.parent
elif resource_type == "file":
return self.parent.parent
return self
@GObject.Property(type=str)
def display_name(self):
resource_type = self.resource_type
if resource_type == "gresources":
gresources_filename = self.gresources_filename
if gresources_filename:
basename, relpath = self.project._get_basename_relpath(self.gresources_filename)
return basename
return _("Unnamed GResource {id}").format(id=self.gresource_id)
elif resource_type == "gresource":
gresource_prefix = self.gresource_prefix
return gresource_prefix if gresource_prefix else _("Unprefixed resource {id}").format(id=self.gresource_id)
elif resource_type == "file":
file_filename = self.file_filename
return file_filename if file_filename else _("Unnamed file {id}").format(id=self.gresource_id)
# GListModel helpers
def _save_last_known_parent_and_position(self):
self._last_known = (self.parent, self.position)
def _update_new_parent(self):
parent = self.parent
position = self.position
# Emit GListModel signal to update model
if parent:
parent.items_changed(position, 0, 1)
parent.notify("n-items")
self._last_known = None
def _remove_from_old_parent(self):
if self._last_known is None:
return
parent, position = self._last_known
# Emit GListModel signal to update model
if parent:
parent.items_changed(position, 1, 0)
parent.notify("n-items")
self._last_known = None
# GListModel iface
def do_get_item(self, position):
gresource_id = self.gresource_id
key = self.db_get(
"SELECT gresource_id FROM gresource WHERE parent_id=? AND position=?;",
(gresource_id, position)
)
if key is not None:
return self.project.get_gresource_by_id(key)
# This should not happen
return CmbListError()
def do_get_item_type(self):
return CmbBaseGResource
@GObject.Property(type=int)
def n_items(self):
if self.resource_type in ["gresources", "gresource"]:
retval = self.db_get("SELECT COUNT(gresource_id) FROM gresource WHERE parent_id=?;", (self.gresource_id, ))
return retval if retval is not None else 0
else:
return 0
def do_get_n_items(self):
return self.n_items

View File

@ -0,0 +1,117 @@
#
# CmbGResourceEditor - Cambalache GResource Editor
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gtk
from .cmb_gresource import CmbGResource
from cambalache import getLogger
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_gresource_editor.ui")
class CmbGResourceEditor(Gtk.Box):
__gtype_name__ = "CmbGResourceEditor"
stack = Gtk.Template.Child()
gresources_filename = Gtk.Template.Child()
gresource_prefix = Gtk.Template.Child()
file_filename = Gtk.Template.Child()
file_compressed = Gtk.Template.Child()
file_preprocess = Gtk.Template.Child()
file_alias = Gtk.Template.Child()
fields = [
("gresources", "gresources_filename", "cmb-value"),
("gresource", "gresource_prefix", "cmb-value"),
("file", "file_filename", "cmb-value"),
("file", "file_compressed", "active"),
("file", "file_preprocess", "cmb-value"),
("file", "file_alias", "cmb-value"),
]
def __init__(self, **kwargs):
self._object = None
self._bindings = []
super().__init__(**kwargs)
@GObject.Property(type=CmbGResource)
def object(self):
return self._object
@object.setter
def _set_object(self, obj):
if obj == self._object:
return
for binding in self._bindings:
binding.unbind()
self._bindings = []
self._object = obj
if obj is None:
self.set_sensitive(False)
for for_type, field, target in self.fields:
widget = getattr(self, field)
target_prop = getattr(widget, target)
if isinstance(target_prop, int):
setattr(widget, target, 0)
else:
setattr(widget, target, None)
return
resource_type = obj.resource_type
self.set_sensitive(True)
self.stack.set_visible_child_name(resource_type)
self.gresources_filename.dirname = obj.project.dirname
self.file_filename.dirname = obj.project.dirname
for for_type, field, target in self.fields:
if resource_type != for_type:
continue
binding = GObject.Object.bind_property(
obj,
field,
getattr(self, field),
target,
GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL,
)
self._bindings.append(binding)
@Gtk.Template.Callback("on_add_gresource_button_clicked")
def __on_add_gresource_button_clicked(self, button):
self._object.project.add_gresource("gresource", parent_id=self._object.gresource_id)
@Gtk.Template.Callback("on_add_file_button_clicked")
def __on_add_file_button_clicked(self, button):
self._object.project.add_gresource("file", parent_id=self._object.gresource_id)
Gtk.WidgetClass.set_css_name(CmbGResourceEditor, "CmbGResourceEditor")

View File

@ -0,0 +1,241 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name cmb_gresource_editor.ui -->
<!-- interface-copyright Juan Pablo Ugarte -->
<requires lib="gtk" version="4.12"/>
<template class="CmbGResourceEditor" parent="GtkBox">
<child>
<object class="GtkStack" id="stack">
<property name="transition-type">crossfade</property>
<child>
<object class="GtkStackPage">
<property name="child">
<object class="GtkGrid">
<property name="column-spacing">4</property>
<property name="row-spacing">4</property>
<child>
<object class="GtkLabel">
<property name="halign">end</property>
<property name="label" translatable="yes">Filename</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="CmbFileButton" id="gresources_filename">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">end</property>
<property name="label" translatable="yes">Add</property>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">* This resource file need to be compiled and loaded at runtime</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="add_gresource_button">
<property name="halign">start</property>
<property name="label">GResource</property>
<signal name="clicked" handler="on_add_gresource_button_clicked"/>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
</object>
</property>
<property name="name">gresources</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="child">
<object class="GtkGrid">
<property name="column-spacing">4</property>
<property name="row-spacing">4</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Prefix</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="CmbEntry" id="gresource_prefix">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">* Files defined inside this will be available at gresource://prefix</property>
<property name="valign">end</property>
<property name="vexpand">True</property>
<property name="wrap">True</property>
<property name="xalign">0.0</property>
<layout>
<property name="column">0</property>
<property name="column-span">2</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Add</property>
<layout>
<property name="column">0</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkButton" id="add_file_button">
<property name="halign">start</property>
<property name="label" translatable="yes">file</property>
<signal name="clicked" handler="on_add_file_button_clicked"/>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">1</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
</object>
</property>
<property name="name">gresource</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="child">
<object class="GtkGrid">
<property name="column-spacing">4</property>
<property name="row-spacing">4</property>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Filename</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Compressed</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Preprocess</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label" translatable="yes">Alias</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkSwitch" id="file_compressed">
<property name="halign">start</property>
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="CmbEntry" id="file_preprocess">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="CmbEntry" id="file_alias">
<property name="hexpand">True</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="CmbFileButton" id="file_filename">
<property name="use-open">True</property>
<layout>
<property name="column">1</property>
<property name="column-span">1</property>
<property name="row">0</property>
<property name="row-span">1</property>
</layout>
</object>
</child>
</object>
</property>
<property name="name">file</property>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,113 @@
#
# 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)

View File

@ -0,0 +1,57 @@
#
# 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

View File

@ -0,0 +1,42 @@
#
# 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

View File

@ -1,35 +0,0 @@
#
# 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()

260
cambalache/cmb_list_view.py Normal file
View File

@ -0,0 +1,260 @@
#
# CmbColumnView
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GLib, GObject, Gdk, Gtk
from .cmb_ui import CmbUI
from .cmb_object import CmbObject
from .cmb_gresource import CmbGResource
from .cmb_context_menu import CmbContextMenu
from .cmb_path import CmbPath
from .cmb_project import CmbProject
from .cmb_tree_expander import CmbTreeExpander
class CmbListView(Gtk.ListView):
__gtype_name__ = "CmbListView"
def __init__(self, **kwargs):
self.__project = None
self.__tree_model = None
self.__in_selection_change = False
self.single_selection = Gtk.SingleSelection()
super().__init__(**kwargs)
self.props.has_tooltip = True
self.props.hexpand = True
factory = Gtk.SignalListItemFactory()
factory.connect("setup", self._on_factory_setup)
factory.connect("bind", self._on_factory_bind)
factory.connect("unbind", self._on_factory_unbind)
self.props.factory = factory
self.single_selection.connect("notify::selected-item", self.__on_selected_item_notify)
self.set_model(self.single_selection)
gesture = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY)
gesture.connect("pressed", self.__on_button_press)
self.add_controller(gesture)
self.connect("activate", self.__on_activate)
self.add_css_class("navigation-sidebar")
self.add_css_class("cmb-list-view")
def __on_button_press(self, gesture, npress, x, y):
expander = self.__get_tree_expander(x, y)
if expander is None or npress != 1:
return False
# Select row at x,y
list_row = expander.get_list_row()
self.single_selection.set_selected(list_row.get_position())
menu = CmbContextMenu()
if self.__project:
menu.target_tk = self.__project.target_tk
menu.set_parent(self)
menu.popup_at(x, y)
return True
def __get_path_parent(self, obj):
if isinstance(obj, CmbObject):
parent = obj.parent
return parent if parent else obj.ui
elif isinstance(obj, CmbGResource):
return obj.path_parent if obj.resource_type == "gresources" else obj.parent
elif isinstance(obj, CmbProject):
return None
return obj.path_parent
def __get_object_ancestors(self, obj):
ancestors = set()
parent = self.__get_path_parent(obj)
while parent:
ancestors.add(parent)
parent = self.__get_path_parent(parent)
return ancestors
def __object_ancestor_expand(self, obj):
ancestors = self.__get_object_ancestors(obj)
i = 0
# Iterate over tree model
# NOTE: only visible/expanded rows are returned
list_row = self.__tree_model.get_row(i)
while list_row:
item = list_row.get_item()
# Return position if we reached the object row
if item == obj:
return i
elif item in ancestors:
# Expand row if its part of the hierarchy
list_row.set_expanded(True)
i += 1
list_row = self.__tree_model.get_row(i)
return None
def __on_project_selection_changed(self, p):
list_row = self.single_selection.get_selected_item()
current_selection = [list_row.get_item()] if list_row else []
selection = self.__project.get_selection()
if selection == current_selection:
return
self.__in_selection_change = True
if len(selection) > 0:
position = self.__object_ancestor_expand(selection[0])
if position is not None:
self.single_selection.select_item(position, True)
else:
self.single_selection.unselect_all()
else:
self.single_selection.unselect_all()
self.__in_selection_change = False
@GObject.Property(type=CmbProject)
def project(self):
return self.__project
@project.setter
def _set_project(self, project):
if self.__project:
self.__project.disconnect_by_func(self.__on_project_selection_changed)
self.__project = project
if project:
self.__tree_model = Gtk.TreeListModel.new(
project,
False,
False,
self.__tree_model_create_func,
None
)
self.single_selection.props.model = self.__tree_model
self.__project.connect("selection-changed", self.__on_project_selection_changed)
else:
self.single_selection.props.model = None
def __tree_model_create_func(self, item, data):
if isinstance(item, CmbObject):
return item
elif isinstance(item, CmbUI):
return item
elif isinstance(item, CmbGResource):
return item
elif isinstance(item, CmbPath):
return item
return None
def __on_selected_item_notify(self, single_selection, pspec):
if self.__in_selection_change or self.__project is None:
return
list_item = single_selection.get_selected_item()
position = single_selection.get_selected()
if list_item is None:
self.__project.set_selection([])
return
item = list_item.get_item()
self.activate_action("list.activate-item", GLib.Variant("u", position))
if item and not isinstance(item, CmbPath):
item = list_item.get_item()
self.__project.set_selection([item])
else:
self.__project.set_selection([])
def _on_factory_setup(self, factory, list_item):
expander = CmbTreeExpander()
list_item.set_child(expander)
def _on_factory_bind(self, factory, list_item):
row = list_item.get_item()
expander = list_item.get_child()
expander.set_list_row(row)
expander.update_bind()
def _on_factory_unbind(self, factory, list_item):
expander = list_item.get_child()
expander.clear_bind()
def __get_tree_expander(self, x, y):
pick = self.pick(x, y, Gtk.PickFlags.DEFAULT)
if pick is None:
return None
if isinstance(pick, Gtk.TreeExpander):
return pick
child = pick.get_first_child()
if child and isinstance(child, Gtk.TreeExpander):
return child
parent = pick.props.parent
if parent and isinstance(parent, Gtk.TreeExpander):
return parent
return None
def __on_activate(self, column_view, position):
item = self.__tree_model.get_item(position)
item.set_expanded(not item.get_expanded())
def do_query_tooltip(self, x, y, keyboard_mode, tooltip):
expander = self.__get_tree_expander(x, y)
if expander is None:
return False
obj = expander.get_item()
if isinstance(obj, CmbObject):
msg = obj.version_warning
if msg:
tooltip.set_text(msg)
return True
return False

View File

@ -0,0 +1,50 @@
#
# 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

View File

@ -0,0 +1,22 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name cmb_message_notification_view.ui -->
<requires lib="gtk" version="4.0"/>
<template class="CmbMessageNotificationView" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title_label">
<property name="halign">start</property>
<property name="use-markup">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="message_label">
<property name="halign">start</property>
<property name="use-markup">True</property>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,378 @@
#
# Cambalache notification system
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
import os
import json
import threading
import http.client
import time
import platform
from uuid import uuid4
from urllib.parse import urlparse
from .config import VERSION
from gi.repository import GObject, GLib, Gio, Gdk, Gtk, Adw, HarfBuzz
from cambalache import getLogger
from . import utils
logger = getLogger(__name__)
class CmbBaseData(GObject.GObject):
def __init__(self, **kwargs):
for prop in self.list_properties():
name = prop.name.replace("-", "_")
if name not in kwargs:
continue
value = kwargs[name]
if isinstance(value, dict) and prop.value_type in GTYPE_PTYHON:
Klass = GTYPE_PTYHON[prop.value_type]
kwargs[name] = Klass(**value)
super().__init__(**kwargs)
def dict(self):
retval = {}
for prop in self.list_properties():
name = prop.name.replace("-", "_")
value = self.get_property(prop.name)
if prop.value_type in GTYPE_PTYHON:
retval[name] = value.dict() if value else None
elif not isinstance(value, GObject.Object):
retval[name] = value
return retval
class CmbPollData(CmbBaseData):
id = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
title = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
description = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
options = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
allowed_votes = GObject.Property(type=int, default=1, flags=GObject.ParamFlags.READWRITE)
start_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
end_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
class CmbPollResult(CmbBaseData):
votes = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
total = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
GTYPE_PTYHON = {CmbPollData.__gtype__: CmbPollData, CmbPollResult.__gtype__: CmbPollResult}
class CmbNotification(CmbBaseData):
center = GObject.Property(type=GObject.Object, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
type = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
start_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
end_date = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
class CmbVersionNotification(CmbNotification):
version = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
release_notes = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
read_more_url = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
class CmbMessageNotification(CmbNotification):
title = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
message = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
class CmbPollNotification(CmbNotification):
poll = GObject.Property(type=CmbPollData, flags=GObject.ParamFlags.READWRITE)
results = GObject.Property(type=CmbPollResult, flags=GObject.ParamFlags.READWRITE)
my_votes = GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
class CmbNotificationCenter(GObject.GObject):
__gsignals__ = {
"new-notification": (GObject.SignalFlags.RUN_FIRST, None, (CmbNotification,)),
}
# Settings
enabled = GObject.Property(type=bool, default=True, flags=GObject.ParamFlags.READWRITE)
uuid = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
next_request = GObject.Property(type=int, flags=GObject.ParamFlags.READWRITE)
notifications = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
store = GObject.Property(type=Gio.ListStore, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.retry_interval = 2
self.user_agent = self.__get_user_agent()
self.store = Gio.ListStore(item_type=CmbNotification)
self.settings = Gio.Settings(schema_id="ar.xjuan.Cambalache.notification")
for prop in ["enabled", "uuid", "next-request", "notifications"]:
self.settings.bind(prop, self, prop.replace("-", "_"), Gio.SettingsBindFlags.DEFAULT)
# Disable notifications if settings backend is ephemeral
if GObject.type_name(Gio.SettingsBackend.get_default()) == "GMemorySettingsBackend":
logger.info("Disabling notifications")
self.enabled = False
self.__load_notifications()
backend = urlparse(os.environ.get("CMB_NOTIFICATION_URL", "https://xjuan.ar:1934"))
self.REQUEST_INTERVAL = 4 if backend.hostname == "localhost" else 24 * 60 * 60
if backend.scheme == "https":
logger.info(f"Backend: {backend.scheme}://{backend.hostname}:{backend.port}")
self.connection = http.client.HTTPSConnection(backend.hostname, backend.port, timeout=8)
else:
self.connection = None
logger.warning(f"{backend.scheme} is not supported, only HTTPS")
return
# Ensure we have a UUID
if not self.uuid:
self.uuid = str(uuid4())
logger.info(f"User Agent: {self.user_agent}")
logger.info(f"UUID: {self.uuid}")
self._get_notification()
def __get_container(self):
if "FLATPAK_ID" in os.environ:
return "flatpak"
elif "APPIMAGE" in os.environ:
return "appimage"
elif "SNAP" in os.environ:
return "snap"
return None
def __get_user_agent(self):
u = platform.uname()
platform_strings = []
table = str.maketrans({",": "\\,"})
if u.system == "Linux":
release = platform.freedesktop_os_release()
system = f"Linux {release['ID']}"
if "VERSION_ID" in release:
system += f" {release['VERSION_ID']}"
if "VERSION_CODENAME" in release:
system += f" {release['VERSION_CODENAME']}"
else:
system = u.system
display_type = GObject.type_name(Gdk.Display.get_default())
backend = display_type.removeprefix("Gdk").removesuffix("Display")
lang = HarfBuzz.language_to_string(HarfBuzz.language_get_default())
extra = []
# Container type
container = self.__get_container()
if container:
extra.append(f"container {container}")
# GSettings backend
settings_backend = Gio.SettingsBackend.get_default()
gsettings_backend = GObject.type_name(settings_backend).removesuffix("SettingsBackend")
for name, lib in [("GLib", GLib), ("Gtk", Gtk), ("Adw", Adw)]:
extra.append(f"{name} {lib.MAJOR_VERSION}.{lib.MINOR_VERSION}.{lib.MICRO_VERSION}")
# Ignore node name as that is private and irrelevant information
for string in [system, u.release, u.version, u.machine, backend, gsettings_backend]:
if not string:
continue
platform_strings.append(string.translate(table))
return f"Cambalache/{VERSION} ({', '.join(platform_strings)}; {'; '.join(extra)}; {lang})"
def __load_notifications(self):
self.store.remove_all()
if not self.notifications:
return
notifications = json.loads(self.notifications)
now = utils.utcnow()
for data in notifications:
if "end_date" in data and now > data["end_date"]:
continue
self.store.append(self.__notification_from_dict(data))
def __save_notifications(self):
notifications = []
for notification in self.store:
notifications.append(notification.dict())
# Store in GSettings
self.notifications = json.dumps(notifications, indent=2, sort_keys=True)
def __notification_from_dict(self, data):
ntype = data.get("type", None)
if ntype == "version":
return CmbVersionNotification(center=self, **data)
elif ntype == "message":
return CmbMessageNotification(center=self, **data)
elif ntype == "poll":
return CmbPollNotification(center=self, **data)
def __get_notification_idle(self, data):
logger.debug(f"Got notification response {json.dumps(data, indent=2, sort_keys=True)}")
if "notification" in data:
notification = self.__notification_from_dict(data["notification"])
self.store.insert(0, notification)
self.__save_notifications()
self.emit("new-notification", notification)
now = int(time.time())
self.next_request = now + self.REQUEST_INTERVAL
self._get_notification()
return GLib.SOURCE_REMOVE
def __get_notification_thread(self):
headers = {
"User-Agent": self.user_agent,
"x-cambalache-uuid": self.uuid,
}
try:
logger.info(f"GET /notification {headers=}")
self.connection.request("GET", "/notification", headers=headers)
response = self.connection.getresponse()
assert response.status == 200
# Reset retry interval
self.retry_interval = 8
data = response.read().decode()
logger.info(f"response={data}")
if data:
GLib.idle_add(self.__get_notification_idle, json.loads(data))
except Exception as e:
# If it fails we just wait a bit before retrying
self.retry_interval *= 2
self.retry_interval = min(self.retry_interval, 256)
logger.info(f"Request error {e}, retrying in {self.retry_interval}s")
GLib.timeout_add_seconds(self.retry_interval, self._get_notification)
self.connection.close()
def __run_in_thread(self, function, *args, **kwargs):
if not self.connection:
logger.warning("No connection defined")
return
if not self.enabled:
logger.info("Notifications disabled")
return
thread = threading.Thread(target=function, args=args, kwargs=kwargs, daemon=True)
thread.start()
def _get_notification(self):
now = int(time.time())
if now >= self.next_request:
self.__run_in_thread(self.__get_notification_thread)
else:
GLib.timeout_add_seconds(self.next_request - now, self._get_notification)
def __poll_vote_idle(self, data):
logger.debug(f"Got vote response {data}")
poll_uuid = data["uuid"]
results = data["results"]
for notification in self.store:
if isinstance(notification, CmbPollNotification) and notification.poll.id == poll_uuid:
notification.results = CmbPollResult(**results)
self.__save_notifications()
break
return GLib.SOURCE_REMOVE
def __poll_vote_exception_idle(self, poll_uuid):
for notification in self.store:
if isinstance(notification, CmbPollNotification) and notification.poll.id == poll_uuid:
notification.my_votes = []
break
return GLib.SOURCE_REMOVE
def __poll_vote_thread(self, method, poll_uuid, votes=None):
headers = {"User-Agent": self.user_agent, "x-cambalache-uuid": self.uuid, "Content-type": "application/json"}
try:
payload = json.dumps({"votes": votes}) if method == "POST" else None
self.connection.request(method, f"/poll/{poll_uuid}", payload, headers)
response = self.connection.getresponse()
assert response.status == 200
data = response.read().decode()
GLib.idle_add(self.__poll_vote_idle, json.loads(data))
except Exception as e:
logger.warning(f"Error voting {e}")
GLib.idle_add(self.__poll_vote_exception_idle, poll_uuid)
self.connection.close()
def poll_vote(self, notification: CmbPollNotification, votes: list[int]):
if self.uuid is None:
return
notification.my_votes = votes
self.__run_in_thread(self.__poll_vote_thread, "POST", notification.poll.id, votes)
def poll_refresh_results(self, notification: CmbPollNotification):
if self.uuid is None:
return
self.__run_in_thread(self.__poll_vote_thread, "GET", notification.poll.id)
def remove(self, notification: CmbNotification):
valid, position = self.store.find(notification)
if valid:
self.store.remove(position)
self.__save_notifications()
notification_center = CmbNotificationCenter()

View File

@ -0,0 +1,60 @@
#
# CmbNotificationListRow
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
import datetime
from cambalache import getLogger
from gi.repository import GObject, Gtk
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_notification_list_row.ui")
class CmbNotificationListRow(Gtk.ListBoxRow):
__gtype_name__ = "CmbNotificationListRow"
view = GObject.Property(type=Gtk.Widget, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY)
box = Gtk.Template.Child()
date_label = Gtk.Template.Child()
close_button = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.props.activatable = False
notification = self.view.notification
start_date = datetime.datetime.utcfromtimestamp(notification.start_date).strftime("%x")
self.date_label.set_label(f"<small>{start_date}</small>")
self.box.append(self.view)
@Gtk.Template.Callback("on_map")
def __on_map(self, w):
self.props.child.props.reveal_child = True
@Gtk.Template.Callback("on_close_button_clicked")
def __on_close_button_clicked(self, button):
notification = self.view.notification
notification.center.remove(notification)

View File

@ -0,0 +1,42 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name cmb_notification_list_row.ui -->
<requires lib="gtk" version="4.12"/>
<template class="CmbNotificationListRow" parent="GtkListBoxRow">
<signal name="map" handler="on_map"/>
<child>
<object class="GtkRevealer">
<child>
<object class="GtkBox" id="box">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="spacing">4</property>
<property name="vexpand-set">True</property>
<child>
<object class="GtkLabel" id="date_label">
<property name="halign">end</property>
<property name="hexpand">True</property>
<property name="use-markup">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="icon-name">window-close-symbolic</property>
<signal name="clicked" handler="on_close_button_clicked"/>
<style>
<class name="flat"/>
<class name="compact"/>
<class name="close"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,67 @@
#
# CmbNotificationListView
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from cambalache import getLogger
from gi.repository import GObject, Gtk
from .cmb_version_notification_view import CmbVersionNotificationView
from .cmb_message_notification_view import CmbMessageNotificationView
from .cmb_poll_notification_view import CmbPollNotificationView
from .cmb_notification_list_row import CmbNotificationListRow
from .cmb_notification import CmbNotificationCenter, CmbVersionNotification, CmbMessageNotification, CmbPollNotification
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_notification_list_view.ui")
class CmbNotificationListView(Gtk.Box):
__gtype_name__ = "CmbNotificationListView"
notification_center = GObject.Property(type=CmbNotificationCenter, flags=GObject.ParamFlags.READWRITE)
list_box = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect("notify::notification-center", self.__on_notification_center_notify)
def __on_notification_center_notify(self, obj, pspec):
self.list_box.bind_model(self.notification_center.store, self.__create_widget_func)
GObject.Object.bind_property(
self.notification_center.store,
"n-items",
self.list_box,
"visible",
GObject.BindingFlags.SYNC_CREATE,
)
def __create_widget_func(self, item):
if isinstance(item, CmbVersionNotification):
view = CmbVersionNotificationView(notification=item)
elif isinstance(item, CmbMessageNotification):
view = CmbMessageNotificationView(notification=item)
elif isinstance(item, CmbPollNotification):
view = CmbPollNotificationView(notification=item)
return CmbNotificationListRow(view=view)

View File

@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name cmb_notification_list_view.ui -->
<requires lib="gtk" version="4.6"/>
<template class="CmbNotificationListView" parent="GtkBox">
<property name="hexpand">True</property>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">True</property>
<property name="propagate-natural-height">True</property>
<property name="propagate-natural-width">True</property>
<child>
<object class="GtkListBox" id="list_box">
<property name="activate-on-single-click">False</property>
<property name="selection-mode">none</property>
<property name="show-separators">True</property>
<style>
<class name="notifications"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

746
cambalache/cmb_object.py Normal file
View File

@ -0,0 +1,746 @@
#
# 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

View File

@ -0,0 +1,213 @@
#
# 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

View File

@ -0,0 +1,293 @@
#
# 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 youre 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)

View File

@ -0,0 +1,76 @@
<?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>

View File

@ -1,342 +1,318 @@
#
# CmbObjectEditor - Cambalache Object Editor
#
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
# Copyright (C) 2021-2024 Juan Pablo Ugarte
#
# Unauthorized copying of this file, via any medium is strictly prohibited.
# 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
import gi
from gi.repository import GObject, Gtk
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
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
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.__object = None
self.__id_label = None
self.__template_switch = None
self.__bindings = []
super().__init__(**kwargs)
self.props.orientation = Gtk.Orientation.VERTICAL
def _create_id_editor(self):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
spacing=6)
box.add(Gtk.Label(label='Object Id:'))
def bind_property(self, *args):
binding = GObject.Object.bind_property(*args)
self.__bindings.append(binding)
return binding
def __create_id_editor(self):
grid = Gtk.Grid(hexpand=True, row_spacing=4, column_spacing=4)
# Label
self.__id_label = Gtk.Label(label=_("Object Id"), halign=Gtk.Align.START)
# Id/Class entry
entry = CmbEntry()
GObject.Object.bind_property(self._object, 'name',
entry, 'cmb-value',
GObject.BindingFlags.SYNC_CREATE |
GObject.BindingFlags.BIDIRECTIONAL)
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)
box.pack_start(entry, True, True, 0)
return box
def _update_view(self):
for child in self.get_children():
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):
self.remove(child)
if self._object is None:
if self.__object is None:
return
# ID
if not self.layout:
self.add(self._create_id_editor())
obj = self.__object
parent = obj.parent
is_builtin = obj.info.is_builtin
owner_id = None
grid = None
i = 0
if self.layout:
if parent is None or is_builtin:
return
# 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
# Child Type input
if parent.info.has_child_types():
self.append(self.__create_child_type_editor())
else:
# ID
self.append(self.__create_id_editor())
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
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
self.show_all()
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"
@GObject.property(type=CmbObject)
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)
def object(self):
return self._object
return self.__object
@object.setter
def _set_object(self, obj):
self._object = obj
self._update_view()
if obj == self.__object:
return
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)
if self.__object:
self.__object.disconnect_by_func(self.__on_object_notify)
self.__object.ui.disconnect_by_func(self.__on_object_ui_notify)
def _create_editor_for_property(self, prop):
editor = None
for binding in self.__bindings:
binding.unbind()
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.__bindings = []
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':
self.__object = obj
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
if obj:
obj.connect("notify", self.__on_object_notify)
obj.ui.connect("notify", self.__on_object_ui_notify)
if type_id == 'gfloat' or type_id == 'gdouble':
digits = 4
step_increment = 0.1
self.__update_view()
adjustment = Gtk.Adjustment(lower=float(minimum),
upper=float(maximum),
step_increment=step_increment,
page_increment=10)
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
Gtk.WidgetClass.set_css_name(CmbObjectEditor, "CmbObjectEditor")

View File

@ -1,275 +0,0 @@
#
# 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

118
cambalache/cmb_path.py Normal file
View File

@ -0,0 +1,118 @@
#
# CmbPath
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gio
from .cmb_base import CmbBase
from cambalache import _, getLogger
logger = getLogger(__name__)
class CmbPath(CmbBase, Gio.ListModel):
__gtype_name__ = "CmbPath"
path_parent = GObject.Property(type=CmbBase, flags=GObject.ParamFlags.READWRITE)
path = GObject.Property(type=str, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
# GListModel
self.__items = []
self.__path_items = {}
def __bool__(self):
return True
def __str__(self):
return f"CmbPath<{self.path}>"
@GObject.Property(type=str)
def display_name(self):
return self.path or _("{n} unsaved files").format(n=self.n_items)
def get_path_item(self, directory):
return self.__path_items.get(directory, None)
def add_item(self, item):
if item in self.__items:
return
display_name = item.display_name
is_path = isinstance(item, CmbPath)
if is_path:
self.__path_items[item.path] = item
i = 0
for list_item in self.__items:
if is_path:
if not isinstance(list_item, CmbPath):
break
if display_name < list_item.display_name:
break
elif not isinstance(list_item, CmbPath) and display_name < list_item.display_name:
break
i += 1
item.path_parent = self
self.__items.insert(i, item)
self.items_changed(i, 0, 1)
if not self.path:
self.notify("display-name")
def remove_item(self, item):
if item not in self.__items:
return
if isinstance(item, CmbPath) and item.path in self.__path_items:
del self.__path_items[item.path]
item.path_parent = None
i = self.__items.index(item)
self.__items.pop(i)
self.items_changed(i, 1, 0)
if not self.path:
self.notify("display-name")
# GListModel iface
@GObject.Property(type=int)
def n_items(self):
return len(self.__items)
def do_get_item(self, position):
return self.__items[position] if position < len(self.__items) else None
def do_get_item_type(self):
return CmbBase
def do_get_n_items(self):
return self.n_items

View File

@ -0,0 +1,142 @@
#
# CmbPollNotificationView
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
import datetime
from cambalache import _, getLogger
from gi.repository import GObject, Gtk
from . import utils
from .cmb_notification import CmbPollNotification
from .cmb_poll_option_check import CmbPollOptionCheck
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_notification_view.ui")
class CmbPollNotificationView(Gtk.Box):
__gtype_name__ = "CmbPollNotificationView"
notification = GObject.Property(
type=CmbPollNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
)
# Poll
title_label = Gtk.Template.Child()
description_label = Gtk.Template.Child()
option_box = Gtk.Template.Child()
total_label = Gtk.Template.Child()
end_date_label = Gtk.Template.Child()
refresh_button = Gtk.Template.Child()
def __init__(self, **kwargs):
self.__option_checks = []
self.__updating = False
super().__init__(**kwargs)
notification = self.notification
poll = notification.poll
active = utils.utcnow() < poll.end_date
self.title_label.props.label = f"<b>{poll.title}</b>"
self.description_label.props.label = poll.description
close_msg = _("<small>• Closes on {date}</small>") if active else _("<small>• Closed on {date}</small>")
end_date = datetime.datetime.fromtimestamp(poll.end_date)
self.end_date_label.props.label = close_msg.format(date=end_date.strftime("%x"))
self.end_date_label.props.tooltip_text = end_date.strftime("%c")
first_check = None
n_option = 0
for option in poll.options:
button = CmbPollOptionCheck(option=option, sensitive=active)
if poll.allowed_votes == 1:
if first_check is None:
first_check = button
else:
button.set_group(first_check)
button.connect("toggled", self.__on_check_button_toggled, n_option)
self.__option_checks.append(button)
self.option_box.append(button)
n_option += 1
self.__update_results()
notification.connect("notify", self.__on_poll_notify)
def __on_check_button_toggled(self, button, n_option):
allowed_votes = self.notification.poll.allowed_votes
if self.__updating or (allowed_votes == 1 and not button.props.active):
return
votes = []
for i, check in enumerate(self.__option_checks):
if check.props.active:
votes.append(i)
if allowed_votes > 1:
not_done = len(votes) < allowed_votes
for i, check in enumerate(self.__option_checks):
if not_done:
check.set_sensitive(True)
elif not check.props.active:
check.set_sensitive(False)
self.notification.center.poll_vote(self.notification, votes)
def __on_poll_notify(self, notification, pspec):
if pspec.name in ["my-votes", "results"]:
self.__update_results()
def __update_results(self):
notification = self.notification
results = notification.results
my_votes = notification.my_votes
if not results or not my_votes:
self.total_label.props.label = ""
for check in self.__option_checks:
check.fraction = -1
return
self.__updating = True
votes = results.votes
total = results.total
for i, check in enumerate(self.__option_checks):
check.set_active(i in my_votes)
check.fraction = votes[i] / total if total else 0
self.total_label.props.label = _("<small>• {total} vote</small>").format(total=results.total)
self.__updating = False
@Gtk.Template.Callback("on_refresh_button_clicked")
def __on_refresh_button_clicked(self, button):
self.notification.center.poll_refresh_results(self.notification)

View File

@ -0,0 +1,64 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name cmb_poll_notification_view.ui -->
<requires lib="gtk" version="4.0"/>
<template class="CmbPollNotificationView" parent="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title_label">
<property name="halign">start</property>
<property name="use-markup">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="description_label">
<property name="halign">start</property>
<property name="use-markup">True</property>
</object>
</child>
<child>
<object class="GtkBox" id="option_box">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">4</property>
<property name="vexpand-set">True</property>
<child>
<object class="GtkButton" id="refresh_button">
<property name="child">
<object class="GtkLabel">
<property name="label">&lt;small&gt;Refresh&lt;/small&gt;</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>

View File

@ -0,0 +1,76 @@
#
# CmbPollOptionCheck
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from cambalache import getLogger
from gi.repository import GLib, GObject, Gtk
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_poll_option_check.ui")
class CmbPollOptionCheck(Gtk.CheckButton):
__gtype_name__ = "CmbPollOptionCheck"
label = Gtk.Template.Child()
bar = Gtk.Template.Child()
def __init__(self, **kwargs):
self.__fraction = None
self.__tick_id = None
super().__init__(**kwargs)
@GObject.Property(type=str)
def option(self):
return self.label.props.label
@option.setter
def _set_option(self, option):
self.label.props.label = option
@GObject.Property(type=float)
def fraction(self):
return self.__fraction
@fraction.setter
def _set_fraction(self, fraction):
self.__fraction = fraction
if fraction < 0:
self.bar.props.visible = False
else:
self.bar.props.visible = True
if self.__tick_id is None:
self.__tick_id = self.add_tick_callback(self.__update_fraction)
def __update_fraction(self, widget, frame_clock):
if self.bar.props.fraction < self.__fraction:
self.bar.props.fraction = min(self.__fraction, self.bar.props.fraction + 0.08)
elif self.bar.props.fraction > self.__fraction:
self.bar.props.fraction = max(self.__fraction, self.bar.props.fraction - 0.08)
else:
self.__tick_id = None
return GLib.SOURCE_REMOVE
return GLib.SOURCE_CONTINUE

View File

@ -0,0 +1,23 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<!-- interface-name r.ui -->
<requires lib="gtk" version="4.0"/>
<template class="CmbPollOptionCheck" parent="GtkCheckButton">
<child>
<object class="GtkBox">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel" id="label">
<property name="halign">start</property>
</object>
</child>
<child>
<object class="GtkProgressBar" id="bar"/>
</child>
</object>
</child>
</template>
</interface>

File diff suppressed because it is too large Load Diff

View File

@ -1,134 +0,0 @@
/*
* 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);

299
cambalache/cmb_property.py Normal file
View File

@ -0,0 +1,299 @@
#
# 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

View File

@ -0,0 +1,66 @@
#
# Cambalache Property Type Info wrapper
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject
from .cmb_objects_base import CmbBasePropertyInfo
from cambalache import getLogger
logger = getLogger(__name__)
class CmbPropertyInfo(CmbBasePropertyInfo):
internal_child = GObject.Property(type=GObject.GObject, flags=GObject.ParamFlags.READWRITE)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.is_a11y = CmbPropertyInfo.type_is_accessible(self.owner_id)
self.a11y_property_id = CmbPropertyInfo.accessible_property_remove_prefix(self.owner_id, self.property_id)
@classmethod
def type_is_accessible(cls, owner_id):
return owner_id in [
"CmbAccessibleProperty",
"CmbAccessibleRelation",
"CmbAccessibleState",
"CmbAccessibleAction"
]
@classmethod
def accessible_property_remove_prefix(cls, owner_id, property_id):
prefix = {
"CmbAccessibleProperty": "cmb-a11y-property-",
"CmbAccessibleRelation": "cmb-a11y-relation-",
"CmbAccessibleState": "cmb-a11y-state-",
"CmbAccessibleAction": "cmb-a11y-action-"
}.get(owner_id, None)
if prefix is None:
return None
# A11y property name without prefix
return property_id.removeprefix(prefix)

View File

@ -0,0 +1,270 @@
#
# 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)

View File

@ -1,21 +1,36 @@
#
# CmbSignalEditor - Cambalache Signal Editor
#
# Copyright (C) 2021 Juan Pablo Ugarte - All Rights Reserved
# Copyright (C) 2021-2024 Juan Pablo Ugarte
#
# Unauthorized copying of this file, via any medium is strictly prohibited.
# 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 gi
from gi.repository import GObject, Gtk, Pango
gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gtk
from .cmb_objects import CmbObject
from .cmb_object import CmbObject
from . import utils
from enum import Enum
class Col(Enum):
SIGNAL = 0
OWNER_ID = 1
@ -26,11 +41,12 @@ 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()
@ -50,42 +66,34 @@ 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 is not None:
if self._object:
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_added)
self._object.disconnect_by_func(self.__on_signal_removed)
self._object.disconnect_by_func(self.__on_signal_changed)
self._object = obj
if obj is not None:
self._populate_treestore()
self._object.connect('signal-added', self._on_signal_added)
self._object.connect('signal-removed', self._on_signal_removed)
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)
@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]
@ -101,14 +109,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:
@ -118,21 +126,33 @@ 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:
signal.user_data = int(new_text)
self.treestore[iter_][Col.USER_DATA.value] = str(signal.user_data)
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
else:
signal.user_data = 0
self.treestore[iter_][Col.USER_DATA.value] = ''
signal.swap = False
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]
@ -140,8 +160,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]
@ -149,62 +169,63 @@ 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]))
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,
),
)
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 _populate_from_type(self, info):
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):
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))
for signal in info.signals:
self.treestore.append(parent,
(None,
info.type_id,
signal.signal_id,
None,
None,
None,
False,
False,
signal))
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))
return parent
def _populate_treestore(self):
def __populate_treestore(self):
target = self._object.ui.get_target(self._object.info.library_id)
# Populate object type signals
parent = self._populate_from_type(self._object.info)
parent = self.__populate_from_type(self._object.info, target)
# Expand object type signals
if parent:
@ -212,29 +233,32 @@ 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)
self.__populate_from_type(info, target)
# 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 is not None and info.detailed:
if info 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
def _data_func(self, tree_column, cell, tree_model, iter_, column):
cell.props.underline = Pango.Underline.ERROR if warning else Pango.Underline.NONE
def __data_func(self, tree_column, cell, tree_model, iter_, column):
info = tree_model[iter_][Col.INFO.value]
signal = tree_model[iter_][Col.SIGNAL.value]
@ -249,3 +273,10 @@ 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 = ""

View File

@ -1,10 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.95.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<!-- interface-name cmb_signal_editor.ui -->
<!-- interface-copyright Juan Pablo Ugarte -->
<requires lib="gtk" version="4.0"/>
<object class="GtkEntryCompletion" id="handler_entrycompletion">
<child>
<object class="GtkCellRendererText"/>
<!-- Custom child fragments -->
<attributes>
<attribute name="text">0</attribute>
</attributes>
@ -12,53 +15,38 @@
</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="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<property name="child">
<object class="GtkTreeView" id="treeview">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focusable">1</property>
<property name="model">treestore</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<property name="tooltip-column">9</property>
<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" swapped="no"/>
<signal name="edited" handler="on_detail_edited"/>
</object>
<!-- Custom child fragments -->
<attributes>
<attribute name="text">2</attribute>
</attributes>
@ -67,16 +55,17 @@
</child>
<child>
<object class="GtkTreeViewColumn" id="handler_column">
<property name="resizable">True</property>
<property name="expand">1</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">True</property>
<property name="editable">1</property>
<property name="placeholder-text">&lt;Enter callback&gt;</property>
<signal name="edited" handler="on_handler_edited" swapped="no"/>
<signal name="edited" handler="on_handler_edited"/>
</object>
<!-- Custom child fragments -->
<attributes>
<attribute name="text">4</attribute>
</attributes>
@ -88,10 +77,11 @@
<property name="title" translatable="yes">Data</property>
<child>
<object class="GtkCellRendererText" id="user_data">
<property name="editable">True</property>
<property name="editable">1</property>
<property name="placeholder-text">&lt;object&gt;</property>
<signal name="edited" handler="on_user_data_edited" swapped="no"/>
<signal name="edited" handler="on_user_data_edited"/>
</object>
<!-- Custom child fragments -->
<attributes>
<attribute name="text">5</attribute>
</attributes>
@ -103,8 +93,9 @@
<property name="title" translatable="yes">Swap</property>
<child>
<object class="GtkCellRendererToggle" id="swap">
<signal name="toggled" handler="on_swap_toggled" swapped="no"/>
<signal name="toggled" handler="on_swap_toggled"/>
</object>
<!-- Custom child fragments -->
<attributes>
<attribute name="active">6</attribute>
</attributes>
@ -116,8 +107,9 @@
<property name="title" translatable="yes">After</property>
<child>
<object class="GtkCellRendererToggle" id="after">
<signal name="toggled" handler="on_after_toggled" swapped="no"/>
<signal name="toggled" handler="on_after_toggled"/>
</object>
<!-- Custom child fragments -->
<attributes>
<attribute name="active">7</attribute>
</attributes>
@ -125,13 +117,10 @@
</object>
</child>
</object>
</child>
</property>
<property name="focusable">1</property>
<property name="vexpand">1</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</template>
</interface>

View File

@ -0,0 +1,266 @@
#
# CmbTreeExpander
#
# Copyright (C) 2024 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from gi.repository import GObject, Gdk, Gtk, Graphene
from .cmb_ui import CmbUI
from .cmb_object import CmbObject
from .cmb_css import CmbCSS
from .cmb_path import CmbPath
from cambalache import _
class CmbTreeExpander(Gtk.TreeExpander):
__gtype_name__ = "CmbTreeExpander"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__project = None
self.__drop_before = None
self.label = Gtk.Inscription(hexpand=True)
self.set_child(self.label)
self.__parent = None
self.__drag_source = None
self.__drop_target = None
def update_bind(self):
item = self.props.item
self.__parent = self.props.parent
# Drag source
self.__drag_source = Gtk.DragSource()
self.__drag_source.connect("prepare", self.__on_drag_prepare)
self.__drag_source.connect("drag-begin", self.__on_drag_begin)
self.__parent.add_controller(self.__drag_source)
# Drop target
self.__drop_target = Gtk.DropTarget.new(type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY)
self.__drop_target.set_gtypes([CmbObject, CmbUI])
self.__drop_target.connect("accept", self.__on_drop_accept)
self.__drop_target.connect("motion", self.__on_drop_motion)
self.__drop_target.connect("drop", self.__on_drop_drop)
self.__parent.add_controller(self.__drop_target)
# Handle label
self.__update_label(item)
item.connect("notify::display-name", self.__on_item_display_name_notify)
self.__project = item.project
if isinstance(item, CmbCSS):
self.props.hide_expander = True
return
elif isinstance(item, CmbPath):
self.props.hide_expander = False
self.add_css_class("cmb-path" if item.path else "cmb-unsaved-path")
return
self.props.hide_expander = item.props.n_items == 0
item.connect("notify::n-items", self.__on_item_n_items_notify)
def clear_bind(self):
item = self.props.item
if self.__parent:
self.__parent.remove_controller(self.__drag_source)
self.__parent.remove_controller(self.__drop_target)
self.__parent = None
self.__drag_source = None
self.__drop_target = None
self.remove_css_class("cmb-path")
self.remove_css_class("cmb-unsaved-path")
item.disconnect_by_func(self.__on_item_display_name_notify)
if isinstance(item, CmbCSS) or isinstance(item, CmbPath):
return
item.disconnect_by_func(self.__on_item_n_items_notify)
def __on_item_n_items_notify(self, item, pspec):
self.props.hide_expander = item.props.n_items == 0
def __on_item_display_name_notify(self, item, pspec):
self.__update_label(item)
def __update_label(self, item):
self.label.set_markup(item.props.display_name or "")
# Drop target callbacks
def __get_drop_before(self, widget, x, y):
h = widget.get_height()
if y < h/4:
return True
if y > h * 0.8:
return False
return None
def __on_object_drop_accept(self, drop, item):
origin_item = drop.get_drag()._item
if origin_item == item:
return None
if not isinstance(item, CmbObject):
return None
# Ignore if its the same parent
if origin_item.parent_id == item.object_id:
return None
return origin_item
def __on_drop_accept(self, target, drop):
item = self.props.item
origin_item = drop.get_drag()._item
if isinstance(item, CmbObject):
origin_item = self.__on_object_drop_accept(drop, item)
self.__drop_before = None
if origin_item is None or item.parent is None:
return False
return self.__project._check_can_add(origin_item.type_id, item.parent.type_id)
elif isinstance(item, CmbUI):
if origin_item == item:
return False
# Ignore if its the same UI and item is already a toplevel
if origin_item.ui_id == item.ui_id and origin_item.parent_id is None:
return False
return True
def __on_drop_motion(self, target, x, y):
item = self.props.item
if isinstance(item, CmbObject):
row_widget = target.get_widget()
drop_before = self.__get_drop_before(row_widget, x, y)
if self.__drop_before == drop_before:
return Gdk.DragAction.COPY
row_widget.remove_css_class("drop-before")
row_widget.remove_css_class("drop-after")
self.__drop_before = drop_before
if drop_before is not None:
row_widget.add_css_class("drop-before" if drop_before else "drop-after")
return Gdk.DragAction.COPY
def __on_drop_drop(self, target, origin_item, x, y):
row_widget = target.get_widget()
item = self.props.item
if isinstance(item, CmbObject):
drop_before = self.__get_drop_before(row_widget, x, y)
if drop_before is None:
# TODO: handle dragging from one UI to another
if origin_item.ui_id != item.ui_id:
return
if origin_item.parent_id != item.object_id:
self.__project.history_push(
_("Move {name} to {target}").format(name=origin_item.display_name, target=item.display_name)
)
origin_item.parent_id = item.object_id
self.__project.history_pop()
return
if origin_item.parent_id != item.parent_id:
if drop_before:
msg = _("Move {name} before {target}").format(name=origin_item.display_name, target=item.display_name)
else:
msg = _("Move {name} after {target}").format(name=origin_item.display_name, target=item.display_name)
self.__project.history_push(msg)
origin_item.parent_id = item.parent_id
else:
msg = None
parent = item.parent
origin_position = origin_item.position
target_position = item.position
if origin_position > target_position:
if drop_before:
position = item.position
else:
position = item.position + 1
else:
if drop_before:
position = item.position - 1
else:
position = item.position
parent.reorder_child(origin_item, position)
if msg:
self.__project.history_pop()
self.__project.set_selection([origin_item])
elif isinstance(item, CmbUI):
if origin_item.ui_id == item.ui_id:
self.__project.history_push(_("Move {name} as toplevel").format(name=origin_item.display_name))
origin_item.parent_id = 0
self.__project.history_pop()
else:
# TODO: Use copy/paste to move across UI files
pass
# Drag Source callbacks
def __on_drag_prepare(self, drag_source, x, y):
item = self.props.item
# Only CmbObject start a drag
if not isinstance(item, CmbObject):
return None
self.__drag_point = Graphene.Point()
self.__drag_point.x = x
self.__drag_point.y = y
return Gdk.ContentProvider.new_for_value(self.props.item)
def __on_drag_begin(self, drag_source, drag):
drag._item = self.props.item
valid, p = self.__parent.compute_point(self.label, self.__drag_point)
if valid:
drag_source.set_icon(Gtk.WidgetPaintable.new(self.label), p.x, p.y)

View File

@ -1,83 +0,0 @@
#
# 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])

View File

@ -0,0 +1,91 @@
#
# 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")

View File

@ -0,0 +1,147 @@
<?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>

View File

@ -0,0 +1,68 @@
#
# 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()

View File

@ -0,0 +1,208 @@
#
# 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")

View File

@ -0,0 +1,52 @@
<?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>

377
cambalache/cmb_type_info.py Normal file
View File

@ -0,0 +1,377 @@
#
# 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)

222
cambalache/cmb_ui.py Normal file
View File

@ -0,0 +1,222 @@
#
# 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

137
cambalache/cmb_ui_editor.py Normal file
View File

@ -0,0 +1,137 @@
#
# 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")

213
cambalache/cmb_ui_editor.ui Normal file
View File

@ -0,0 +1,213 @@
<?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">&lt;translation domain&gt;</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>

View File

@ -0,0 +1,113 @@
#
# 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

View File

@ -0,0 +1,57 @@
#
# CmbVersionNotificationView
#
# Copyright (C) 2025 Juan Pablo Ugarte
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Juan Pablo Ugarte <juanpablougarte@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-only
#
from cambalache import _, getLogger
from gi.repository import GObject, Gtk
from .cmb_notification import CmbVersionNotification
logger = getLogger(__name__)
@Gtk.Template(resource_path="/ar/xjuan/Cambalache/cmb_version_notification_view.ui")
class CmbVersionNotificationView(Gtk.Box):
__gtype_name__ = "CmbVersionNotificationView"
notification = GObject.Property(
type=CmbVersionNotification, flags=GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY
)
# Version
version_label = Gtk.Template.Child()
release_notes_label = Gtk.Template.Child()
read_more_button = Gtk.Template.Child()
def __init__(self, **kwargs):
super().__init__(**kwargs)
notification = self.notification
self.version_label.props.label = _("<b>Version {version} is available</b>").format(version=notification.version)
self.release_notes_label.props.label = notification.release_notes
if notification.read_more_url:
self.read_more_button.props.uri = notification.read_more_url
self.read_more_button.show()
else:
self.read_more_button.hide()

Some files were not shown because too many files have changed in this diff Show More