mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-06-25 00:02:51 -04:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4f7272b2f9 | ||
|
eea5ff18ea |
11
CHANGELOG.md
11
CHANGELOG.md
@ -8,6 +8,17 @@ packaging changes like new dependencies or build system changes.
|
||||
Cambalache used even/odd minor numbers to differentiate between stable and
|
||||
development releases.
|
||||
|
||||
## 0.94.1
|
||||
|
||||
2024-11-26 - First bugfix release in the series!
|
||||
|
||||
- Fix issue with undoing a parent change.
|
||||
- Fix workspace initialization error
|
||||
- Fix warning on undo/redo message generation
|
||||
|
||||
### Issues
|
||||
|
||||
- #253 "Error updating UI 1: gtk-builder-error-quark: .:8:1 Invalid object type 'AdwApplicationWindow' (6)"
|
||||
|
||||
## 0.94.0
|
||||
|
||||
|
@ -158,10 +158,15 @@ class CmbDB(GObject.GObject):
|
||||
def history_delete(self, table, table_pk):
|
||||
self.execute(self.__history_commands[table]["DELETE"], table_pk)
|
||||
|
||||
def history_update(self, table, column, table_pk, values):
|
||||
command = self.__history_commands[table]["UPDATE"].format(column=column)
|
||||
i = self.__table_column_mapping[table][column]
|
||||
self.execute(command, [values[i]] + table_pk)
|
||||
def history_update(self, table, columns, table_pk, values):
|
||||
update_command = self.__history_commands[table]["UPDATE"]
|
||||
set_expression = f"({','.join(columns)}) = ({','.join(['?' for i in columns])})"
|
||||
|
||||
exp_vals = []
|
||||
for col in columns:
|
||||
exp_vals.append(values[self.__table_column_mapping[table][col]])
|
||||
|
||||
self.execute(update_command.format(set_expression=set_expression), exp_vals + table_pk)
|
||||
|
||||
def __create_history_triggers(self, c, table):
|
||||
# Get table columns
|
||||
@ -185,6 +190,7 @@ class CmbDB(GObject.GObject):
|
||||
"""
|
||||
self.__clear_history = clear_history
|
||||
|
||||
# Collect Column info
|
||||
i = 0
|
||||
column_mapping = {}
|
||||
for row in c.execute(f"PRAGMA table_info({table});"):
|
||||
@ -200,6 +206,28 @@ class CmbDB(GObject.GObject):
|
||||
|
||||
i += 1
|
||||
|
||||
# Collect unique constraint indexes
|
||||
unique_constraints_indexes = []
|
||||
for row in c.execute(f"PRAGMA index_list({table});"):
|
||||
n, name, is_unique, index_type, is_partial = row
|
||||
if is_unique and index_type == "u":
|
||||
unique_constraints_indexes.append(name)
|
||||
|
||||
# Collect unique constraints indexes with more than one non pk column
|
||||
unique_constraints = []
|
||||
for index_name in unique_constraints_indexes:
|
||||
index_columns = []
|
||||
|
||||
for row in c.execute(f"PRAGMA index_info({index_name});"):
|
||||
index_rank, table_rank, name = row
|
||||
if name not in pk_columns:
|
||||
index_columns.append(name)
|
||||
|
||||
if len(index_columns) > 1:
|
||||
unique_constraints.append(index_columns)
|
||||
|
||||
unique_constraints_flat = [i for constraints in unique_constraints for i in constraints]
|
||||
|
||||
# Map column index to column name
|
||||
self.__table_column_mapping[table] = column_mapping
|
||||
|
||||
@ -215,7 +243,7 @@ class CmbDB(GObject.GObject):
|
||||
self.__history_commands[table] = {
|
||||
"DELETE": f"DELETE FROM {table} WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
||||
"INSERT": f"INSERT INTO {table} ({columns}) VALUES ({columns_format});",
|
||||
"UPDATE": f"UPDATE {table} SET {{column}} = ? WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
||||
"UPDATE": f"UPDATE {table} SET {{set_expression}} WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
||||
}
|
||||
|
||||
# INSERT Trigger
|
||||
@ -249,8 +277,43 @@ class CmbDB(GObject.GObject):
|
||||
if len(pk_columns) == 0:
|
||||
return
|
||||
|
||||
# UPDATE Trigger for each non PK column unique indexes
|
||||
for columns in unique_constraints:
|
||||
underscore_columns = "_".join(columns)
|
||||
colon_columns = ",".join(columns)
|
||||
new_columns = ",".join(f"NEW.{col}" for col in columns)
|
||||
old_columns = ",".join(f"OLD.{col}" for col in columns)
|
||||
string_columns = ",".join(f"'{col}'" for col in columns)
|
||||
|
||||
c.execute(
|
||||
f"""
|
||||
CREATE TRIGGER on_{table}_update_{underscore_columns} AFTER UPDATE OF {colon_columns} ON {table}
|
||||
WHEN
|
||||
({new_columns}) IS NOT ({old_columns}) AND {history_is_enabled} AND
|
||||
(
|
||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS NOT json_array({old_pk_values})
|
||||
OR
|
||||
(
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS NOT ('UPDATE', '{table}', json_array({string_columns}))
|
||||
AND
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS NOT ('INSERT', '{table}', NULL)
|
||||
)
|
||||
)
|
||||
BEGIN
|
||||
{clear_history};
|
||||
INSERT INTO history (history_id, command, table_name, columns, table_pk, new_values, old_values)
|
||||
VALUES ({history_next_seq}, 'UPDATE', '{table}', json_array({string_columns}), json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
||||
END;
|
||||
"""
|
||||
)
|
||||
|
||||
# UPDATE Trigger for each non PK column
|
||||
for column in non_pk_columns:
|
||||
if column in unique_constraints_flat:
|
||||
continue
|
||||
|
||||
c.execute(
|
||||
f"""
|
||||
CREATE TRIGGER on_{table}_update_{column} AFTER UPDATE OF {column} ON {table}
|
||||
@ -260,17 +323,17 @@ class CmbDB(GObject.GObject):
|
||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS NOT json_array({old_pk_values})
|
||||
OR
|
||||
(
|
||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
||||
IS NOT ('UPDATE', '{table}', '{column}')
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS NOT ('UPDATE', '{table}', json_array('{column}'))
|
||||
AND
|
||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS NOT ('INSERT', '{table}', NULL)
|
||||
)
|
||||
)
|
||||
BEGIN
|
||||
{clear_history};
|
||||
INSERT INTO history (history_id, command, table_name, column_name, table_pk, new_values, old_values)
|
||||
VALUES ({history_next_seq}, 'UPDATE', '{table}', '{column}', json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
||||
INSERT INTO history (history_id, command, table_name, columns, table_pk, new_values, old_values)
|
||||
VALUES ({history_next_seq}, 'UPDATE', '{table}', json_array('{column}'), json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
||||
END;
|
||||
"""
|
||||
)
|
||||
@ -282,8 +345,8 @@ class CmbDB(GObject.GObject):
|
||||
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
||||
AND
|
||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
||||
IS ('UPDATE', '{table}', '{column}')
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS ('UPDATE', '{table}', json_array('{column}'))
|
||||
BEGIN
|
||||
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
||||
END;
|
||||
@ -297,7 +360,7 @@ class CmbDB(GObject.GObject):
|
||||
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
||||
AND
|
||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
||||
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||
IS ('INSERT', '{table}', NULL)
|
||||
BEGIN
|
||||
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
||||
|
@ -335,7 +335,7 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
||||
|
||||
project.db.execute(
|
||||
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
||||
(new_parent_id, new_position, ui_id, object_id)
|
||||
(new_parent_id, new_position or 0, ui_id, object_id)
|
||||
)
|
||||
|
||||
# Update children positions in old parent
|
||||
|
@ -418,6 +418,9 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
return n_files
|
||||
|
||||
def __selection_remove(self, obj):
|
||||
if obj not in self.__selection:
|
||||
return
|
||||
|
||||
try:
|
||||
self.__selection.remove(obj)
|
||||
except Exception:
|
||||
@ -790,11 +793,12 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
c = self.db.cursor()
|
||||
|
||||
# Get last command
|
||||
command, range_id, table, column, table_pk, old_values, new_values = c.execute(
|
||||
"SELECT command, range_id, table_name, column_name, table_pk, old_values, new_values FROM history WHERE history_id=?",
|
||||
(self.history_index, )
|
||||
command, range_id, table, columns, table_pk, old_values, new_values = c.execute(
|
||||
"SELECT command, range_id, table_name, columns, table_pk, old_values, new_values FROM history WHERE history_id=?",
|
||||
(self.history_index,),
|
||||
).fetchone()
|
||||
|
||||
columns = json.loads(columns) if columns else None
|
||||
table_pk = json.loads(table_pk) if table_pk else None
|
||||
old_values = json.loads(old_values) if old_values else None
|
||||
new_values = json.loads(new_values) if new_values else None
|
||||
@ -814,7 +818,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
else:
|
||||
self.db.history_insert(table, new_values)
|
||||
|
||||
self.__undo_redo_update_insert_delete(c, undo, command, table, column, table_pk, old_values, new_values)
|
||||
self.__undo_redo_update_insert_delete(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||
elif command == "DELETE":
|
||||
if table == "object":
|
||||
parent, position = get_object_position(c, old_values)
|
||||
@ -829,10 +833,10 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
else:
|
||||
self.db.history_delete(table, table_pk)
|
||||
|
||||
self.__undo_redo_update_insert_delete(c, undo, command, table, column, table_pk, old_values, new_values)
|
||||
self.__undo_redo_update_insert_delete(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||
elif command == "UPDATE":
|
||||
# Ignore parent_id since its changed together with position
|
||||
if update_objects is not None and table == "object" and column == "position":
|
||||
# parent_id and position have to change together because their are part of a unique index
|
||||
if update_objects is not None and table == "object" and "position" in columns and "parent_id" in columns:
|
||||
old_parent, old_position = get_object_position(c, old_values)
|
||||
new_parent, new_position = get_object_position(c, new_values)
|
||||
|
||||
@ -848,11 +852,11 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
update_objects.append((old_parent, old_position, 1, 0))
|
||||
|
||||
if undo:
|
||||
self.db.history_update(table, column, table_pk, old_values)
|
||||
self.db.history_update(table, columns, table_pk, old_values)
|
||||
else:
|
||||
self.db.history_update(table, column, table_pk, new_values)
|
||||
self.db.history_update(table, columns, table_pk, new_values)
|
||||
|
||||
self.__undo_redo_update_update(c, undo, command, table, column, table_pk, old_values, new_values)
|
||||
self.__undo_redo_update_update(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||
elif command == "PUSH" or command == "POP":
|
||||
pass
|
||||
else:
|
||||
@ -860,15 +864,14 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
|
||||
c.close()
|
||||
|
||||
def __undo_redo_update_update(self, c, undo, command, table, column, pk, old_values, new_values):
|
||||
if table is None:
|
||||
def __undo_redo_update_update(self, c, undo, command, table, columns, pk, old_values, new_values):
|
||||
if table is None or command != "UPDATE":
|
||||
return
|
||||
|
||||
for column in columns:
|
||||
# Update tree model and emit signals
|
||||
# We can not easily implement this using triggers because they are called
|
||||
# even if the transaction is rollback because of a FK constraint
|
||||
|
||||
if command == "UPDATE":
|
||||
if table == "object":
|
||||
obj = self.get_object_by_id(pk[0], pk[1])
|
||||
if obj:
|
||||
@ -911,7 +914,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
if obj:
|
||||
obj.notify(column)
|
||||
|
||||
def __undo_redo_update_insert_delete(self, c, undo, command, table, column, pk, old_values, new_values):
|
||||
def __undo_redo_update_insert_delete(self, c, undo, command, table, columns, pk, old_values, new_values):
|
||||
if table is None:
|
||||
return
|
||||
|
||||
@ -990,9 +993,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
def __undo_redo(self, undo):
|
||||
selection = self.get_selection()
|
||||
|
||||
command, range_id, table, column = self.db.execute(
|
||||
"SELECT command, range_id, table_name, column_name FROM history WHERE history_id=?",
|
||||
(self.history_index, )
|
||||
command, range_id, table = self.db.execute(
|
||||
"SELECT command, range_id, table_name FROM history WHERE history_id=?", (self.history_index,)
|
||||
).fetchone()
|
||||
|
||||
update_parents = []
|
||||
@ -1206,16 +1208,18 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
def get_msg(index):
|
||||
cmd = c.execute(
|
||||
"""
|
||||
SELECT command, range_id, table_name, column_name, message, old_values, new_values
|
||||
SELECT command, range_id, table_name, columns, message, old_values, new_values
|
||||
FROM history
|
||||
WHERE history_id=?
|
||||
""",
|
||||
(index,)
|
||||
(index,),
|
||||
).fetchone()
|
||||
|
||||
if cmd is None:
|
||||
return None
|
||||
command, range_id, table, column, message, old_values, new_values = cmd
|
||||
command, range_id, table, columns, message, old_values, new_values = cmd
|
||||
|
||||
columns = json.loads(columns) if columns else []
|
||||
|
||||
if message is not None:
|
||||
return message
|
||||
@ -1283,10 +1287,14 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
||||
.get(command, None)
|
||||
)
|
||||
|
||||
if msg is not None:
|
||||
msg = msg.format(**get_msg_vars(table, column, values))
|
||||
if msg is None:
|
||||
return None
|
||||
|
||||
return msg
|
||||
msgs = []
|
||||
for column in columns:
|
||||
msgs.append(msg.format(**get_msg_vars(table, column, values)))
|
||||
|
||||
return "\n".join(msgs) if len(msgs) > 1 else (msgs[0] if msgs else None)
|
||||
|
||||
undo_msg = get_msg(self.history_index)
|
||||
redo_msg = get_msg(self.history_index + 1)
|
||||
|
@ -41,7 +41,7 @@ CREATE TABLE history (
|
||||
command TEXT NOT NULL,
|
||||
range_id INTEGER REFERENCES history,
|
||||
table_name TEXT,
|
||||
column_name TEXT,
|
||||
columns JSON,
|
||||
message TEXT,
|
||||
table_pk JSON,
|
||||
new_values JSON,
|
||||
|
@ -14,6 +14,15 @@
|
||||
</p>
|
||||
</description>
|
||||
<releases>
|
||||
<release date="2024-12-13" version="0.94.1">
|
||||
<description>
|
||||
<p>First bugfix release in the series!</p>
|
||||
<ul>
|
||||
<li>Fix issue with undoing a parent change.</li>
|
||||
<li>Fix workspace initialization error (#253)</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release date="2024-11-27" version="0.94.0">
|
||||
<description>
|
||||
<p>Accessibility Release!</p>
|
||||
|
@ -1,6 +1,6 @@
|
||||
project(
|
||||
'cambalache', 'c',
|
||||
version: '0.94.0',
|
||||
version: '0.94.1',
|
||||
meson_version: '>= 0.64.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
Loading…
x
Reference in New Issue
Block a user