Initial commit

Supports type system and interface data model.
Basic history (undo/redo stack) implementation with triggers.
This commit is contained in:
Juan Pablo Ugarte 2020-12-09 19:14:53 -03:00
commit 51d4185cd8
5 changed files with 408 additions and 0 deletions

8
COPYING Normal file
View File

@ -0,0 +1,8 @@
Cambalache UI Maker
Copyright (C) 2020-2021 Juan Pablo Ugarte - All Rights Reserved
Unauthorized copying of this project, via any medium is strictly prohibited.
This application is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# cambalache-db
Cambalache Data Model generation tool

View File

@ -0,0 +1,67 @@
/*
* CambalacheDB - Data Model for Cambalache
*
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
*
* Unauthorized copying of this file, via any medium is strictly prohibited.
*/
/* Test model */
INSERT INTO type (type_id, parent) VALUES
('GtkWidget', 'object'),
('GtkWindow', 'GtkWidget'),
('GtkImage', 'GtkWidget'),
('GtkBox', 'GtkWidget'),
('GtkLabel', 'GtkWidget'),
('GtkButton', 'GtkWidget'),
('GtkToggleButton', 'GtkButton');
INSERT INTO property (owner_id, property_id, type_id) VALUES
('GtkWidget', 'name', 'string'),
('GtkWidget', 'parent', 'GtkWidget'),
('GtkImage', 'file', 'string'),
('GtkBox', 'orientation', 'enum'),
('GtkLabel', 'label', 'string'),
('GtkButton', 'label', 'string'),
('GtkButton', 'image', 'GtkImage'),
('GtkToggleButton', 'active', 'boolean');
INSERT INTO child_property (owner_id, property_id, type_id) VALUES
('GtkBox', 'position', 'int'),
('GtkBox', 'expand', 'boolean'),
('GtkBox', 'fill', 'boolean');
INSERT INTO signal (owner_id, signal_id) VALUES
('GtkWidget', 'event'),
('GtkBox', 'add'),
('GtkBox', 'remove'),
('GtkButton', 'clicked'),
('GtkToggleButton', 'toggled');
/* Test Project */
INSERT INTO object (type_id, name, parent_id) VALUES
('GtkWindow', 'main', NULL),
('GtkBox', 'box', 1),
('GtkLabel', 'label', 2),
('GtkButton', 'button', 2);
INSERT INTO object_property (object_id, owner_id, property_id, value) VALUES
(3, 'GtkLabel', 'label', 'Hello World'),
(4, 'GtkButton', 'label', 'Click Me');
INSERT INTO object_child_property (object_id, child_id, owner_id, property_id, value) VALUES
(1, 3, 'GtkBox', 'position', 1),
(1, 3, 'GtkBox', 'expand', 1),
(1, 4, 'GtkBox', 'position', 2),
(1, 4, 'GtkBox', 'fill', 0);
INSERT INTO object_signal (object_id, owner_id, signal_id, handler) VALUES
(4, 'GtkButton', 'clicked', 'on_button_clicked');
INSERT INTO interface (name, filename) VALUES ('Test UI', 'test.ui');
INSERT INTO interface_object (interface_id, object_id) VALUES (1, 1);

View File

@ -0,0 +1,124 @@
#
# CambalacheDB - Data Model for Cambalache
#
# Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
#
# Unauthorized copying of this file, via any medium is strictly prohibited.
#
import sys
import sqlite3
def db_create_history_table(c, table):
# Create Table
c.executescript(f'''
CREATE TABLE history_{table} AS SELECT * FROM {table} WHERE 0;
ALTER TABLE history_{table} ADD COLUMN history_id INTERGER REFERENCES history;
''')
# Get table columns
columns = None
old_values = None
new_values = None
pk_columns = None
pk_columns_values = None
for row in c.execute(f'PRAGMA table_info({table});'):
col = row[1]
col_type = row[2]
pk = row[5]
if columns == None:
columns = col
old_values = 'OLD.' + col
new_values = 'NEW.' + col
else:
columns += ', ' + col
old_values += ', OLD.' + col
new_values += ', NEW.' + col
if pk:
if pk_columns == None:
pk_columns = col
pk_columns_values = 'NEW.' + col
else:
pk_columns += ', ' + col
pk_columns_values += ', NEW.' + col
# Create history triggers
# INSERT Trigger
c.execute(f'''
CREATE TRIGGER on_{table}_insert AFTER INSERT ON {table}
BEGIN
INSERT INTO history (command, table_name) VALUES ('INSERT', '{table}');
INSERT INTO history_{table} (history_id, {columns})
VALUES (last_insert_rowid(), {new_values});
END;
''')
# DELETE Trigger
c.execute(f'''
CREATE TRIGGER on_{table}_delete AFTER DELETE ON {table}
BEGIN
INSERT INTO history (command, table_name) VALUES ('DELETE', '{table}');
INSERT INTO history_{table} (history_id, {columns})
VALUES (last_insert_rowid(), {old_values});
END;
''')
# UPDATE Trigger
c.execute(f'''
CREATE TRIGGER on_{table}_update AFTER UPDATE ON {table}
BEGIN
INSERT INTO history (command, table_name) VALUES ('UPDATE', '{table}');
INSERT INTO history_{table} (history_id, {columns})
VALUES (last_insert_rowid(), {new_values});
END;
''')
def db_create_history_tables(conn):
c = conn.cursor()
db_create_history_table(c, 'object')
db_create_history_table(c, 'object_property')
db_create_history_table(c, 'object_child_property')
db_create_history_table(c, 'object_signal')
db_create_history_table(c, 'interface')
db_create_history_table(c, 'interface_object')
conn.commit()
def row_diff_count(*args):
return len(args)
def db_create(filename):
# Create DB file
conn = sqlite3.connect(filename)
conn.create_function("row_diff_count", -1, row_diff_count, deterministic=True)
# Create DB tables
with open('cambalache-db.sql', 'r') as sql:
c = conn.cursor()
c.executescript(sql.read())
conn.commit()
sql.close()
db_create_history_tables(conn)
return conn
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Ussage: {sys.argv[0]} database.sqlite")
exit()
conn = db_create(sys.argv[1])
conn.commit()
conn.close()

View File

@ -0,0 +1,205 @@
/*
* CambalacheDB - Data Model for Cambalache
*
* Copyright (C) 2020 Juan Pablo Ugarte - All Rights Reserved
*
* Unauthorized copying of this file, via any medium is strictly prohibited.
*/
PRAGMA foreign_keys = ON;
/** Common Data Model **/
CREATE TABLE license (
license_id TEXT PRIMARY KEY,
name TEXT,
license_text TEXT NOT NULL
);
/* Type
*
* Base table to keep type information
*/
CREATE TABLE type (
type_id TEXT PRIMARY KEY,
parent TEXT REFERENCES type,
get_type TEXT,
version TEXT,
deprecated_version TEXT
);
/* Add fundamental types */
INSERT INTO type (type_id) VALUES
('char'), ('uchar'), ('boolean'), ('int'), ('uint'), ('long'), ('ulong'),
('int64'), ('uint64'), ('enum'), ('flags'), ('float'), ('double'), ('string'),
('pointer'), ('boxed'), ('param'), ('object'), ('gtype'), ('variant');
/* Property
*
* TODO: Add check to make sure property:owner_id is not fundamental
*/
CREATE TABLE property (
owner_id TEXT REFERENCES type,
property_id TEXT NOT NULL,
type_id TEXT REFERENCES type,
writable BOOLEAN,
construct_only BOOLEAN,
default_value TEXT,
version TEXT,
deprecated_version TEXT,
PRIMARY KEY(owner_id, property_id)
);
CREATE TRIGGER property_check_owner_id BEFORE INSERT ON property
BEGIN
SELECT
CASE
WHEN (SELECT parent FROM type WHERE type_id=NEW.owner_id) IS NULL THEN
RAISE (ABORT,'owner_id is not an object type')
END;
END;
/* Child Property
*
*/
CREATE TABLE child_property (
owner_id TEXT REFERENCES type,
property_id TEXT NOT NULL,
type_id TEXT REFERENCES type,
writable BOOLEAN,
construct_only BOOLEAN,
default_value TEXT,
version TEXT,
deprecated_version TEXT,
PRIMARY KEY(owner_id, property_id)
);
/* Signal
*
* TODO: Add check to make sure signal:owner_type is not fundamental
*/
CREATE TABLE signal (
owner_id TEXT REFERENCES type(type_id),
signal_id TEXT NOT NULL,
version TEXT,
deprecated_version TEXT,
PRIMARY KEY(owner_id, signal_id)
);
/** Project Data Model **/
/* Object
*
*/
CREATE TABLE object (
object_id INTEGER PRIMARY KEY AUTOINCREMENT,
type_id TEXT NOT NULL REFERENCES type,
name TEXT UNIQUE,
parent_id INTEGER REFERENCES object
);
/* Object Property
*
*/
CREATE TABLE object_property (
object_id INTEGER REFERENCES object,
owner_id TEXT,
property_id TEXT,
value TEXT,
translatable BOOLEAN,
PRIMARY KEY(object_id, owner_id, property_id),
FOREIGN KEY(owner_id, property_id) REFERENCES property
);
/* Object Child Property
*
*/
CREATE TABLE object_child_property (
object_id INTEGER REFERENCES object,
child_id INTEGER REFERENCES object,
owner_id TEXT,
property_id TEXT,
value TEXT,
translatable BOOLEAN,
PRIMARY KEY(object_id, child_id, owner_id, property_id),
FOREIGN KEY(owner_id, property_id) REFERENCES child_property
);
/* Object Signal
*
*/
CREATE TABLE object_signal (
object_id INTEGER REFERENCES object,
owner_id TEXT,
signal_id TEXT,
handler TEXT NOT NULL,
detail TEXT,
user_data INTEGER REFERENCES object,
swap BOOLEAN,
after BOOLEAN,
PRIMARY KEY(object_id, owner_id, signal_id),
FOREIGN KEY(owner_id, signal_id) REFERENCES signal
);
/* Interface
*
*/
CREATE TABLE interface (
interface_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
description TEXT,
copyright TEXT,
authors TEXT,
license_id TEXT REFERENCES license,
filename TEXT,
translation_domain TEXT
);
/* Interface Object
*
* TODO: check objects are toplevels (have no parent)
*/
CREATE TABLE interface_object (
interface_id INTEGER REFERENCES interface,
object_id INTEGER REFERENCES object,
template TEXT,
PRIMARY KEY(interface_id, object_id)
);
/*
* Implement undo/redo stack with triggers
*
* We should be able to store the whole project history if we want to.
*
* history_* tables and triggers are auto generated to avoid copy/paste errors
*/
/* Main history table */
CREATE TABLE history (
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
command TEXT,
table_name TEXT
);