Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions linux/.config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ filter = 'package(tablepro-session)'
test-group = 'session'

# The app's docker tests are integration targets, and the large-catalog
# case in its library. Its other unit tests start no container, so they
# keep running in parallel.
# and diagram engine cases in its library. Its other unit tests start no
# container, so they keep running in parallel.
[[profile.default.overrides]]
filter = 'package(tablepro) & (kind(test) | test(/::large_catalog::/))'
filter = 'package(tablepro) & (kind(test) | test(/::large_catalog::/) | test(/::diagram::engines::/))'
test-group = 'app'

# These press real keys on the X display every test shares. A window
Expand Down
4 changes: 3 additions & 1 deletion linux/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,12 @@ pub enum WorkspaceTab {
Editor(EditorTabSlot), // SQL editor, free-form query
Structure(StructureTabSlot), // a new table's draft, or an existing table's definition
Table(TableTabSlot), // one (schema, table) in the browse grid
Diagram(DiagramTabSlot), // one schema's ER diagram
Key(KeyTabSlot), // one key of a key-value engine
}
```

A table's structure opens in a Structure tab of its own. Saving a new table closes its Structure tab and opens a Table tab on it.
A table's structure opens in a Structure tab of its own. Saving a new table closes its Structure tab and opens a Table tab on it. A schema's ER diagram opens in a Diagram tab, one per schema: it reads each table's columns, foreign keys and indexes on one connection lease, two tables at a time, and draws the cards and lines on a canvas of its own (`crates/app/src/ui/diagram/`), with a focusable `DiagramNode` where each card is so the keyboard and a screen reader reach the tables. Its record keeps the schema and where the user moved tables, so a restore reads the tables again, lays them out and puts the moved ones back.

Each tab is a gtk-rs widget with a composite template, which the `AdwTabView` adopts. The workspace keeps a `HashMap<Uuid, WorkspaceTab>` keyed by the tab's UUID; the canonical display order comes from `tab_view.pages()` (drag-reorderable). A `TabIndex` maps each `AdwTabPage` to its tab's UUID, so close and right-click actions can recover it. `TabFactory` builds every tab from a `TabSpec`, whether the sidebar, the overview's new-tab button, a restore or a reopen asks for it. Every tab's title comes from its `TabIdentity` through `resolve_tab_title`, and the window's from the tab in front through `resolve_window_title`. A tab that closing would lose something in shows `document-modified-symbolic` as its page icon, as GNOME Text Editor does; its title never changes for it.

Expand Down
1 change: 1 addition & 0 deletions linux/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions linux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ http = "1"
rcgen = { version = "0.14", default-features = false, features = ["crypto", "pem", "aws_lc_rs"] }
rustls-pki-types = { version = "1", features = ["std"] }
gtk4 = { version = "0.11", features = ["gnome_50"] }
# The PNG and SVG surfaces the ER diagram exports through; the crate is
# already in the tree through gtk4.
cairo-rs = { version = "0.22", features = ["png", "svg"] }
libadwaita = { version = "0.9", features = ["v1_9"] }
sourceview5 = { version = "0.11", features = ["v5_18"] }
gio = { version = "0.22", features = ["v2_88"] }
Expand Down
4 changes: 2 additions & 2 deletions linux/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The app runs on GNOME 50 ([ADR 0015](docs/decisions/0015-gnome-50-platform-floor
| Column widths | Kept per table; Widen Column and Narrow Column in the cell menu (Shift+Alt+Right and Shift+Alt+Left) change them from the keyboard |
| CSV import | Into an existing table, with a column mapping, in one write batch (one transaction where the engine has them), up to 64 MiB |
| Sidebar | Tables (partitioned ones included), views, materialized views and foreign tables by schema, with search and Refresh, read again after the editor runs DDL, Open in New Tab and Show CREATE TABLE; Edit Structure, Import CSV and Drop Table for tables |
| ER diagram | Show ER Diagram in a table's menu or on a schema's heading opens the schema's tables as cards and their foreign keys as lines in crow's foot notation, read two tables at a time with Stop. Pan, zoom, Best Fit, drag tables, Reset Layout, and PNG and SVG export. Each table is a list item the keyboard and a screen reader reach: Tab and the arrow keys move between tables, Enter opens one, Alt+arrows move it, and its description names its columns and the tables it joins |

### Structure

Expand Down Expand Up @@ -155,7 +156,6 @@ Not built yet, in no order.
- Functions, procedures, sequences and triggers in the sidebar. The drivers already list the first three.
- The CREATE INDEX statement for one index. Show CREATE TABLE lists them after the table on PostgreSQL, SQL Server and SQLite (with the triggers on SQLite).
- Reorder columns in the structure tab by dragging, with the ALTER previewed.
- An ER diagram.

### SQL editor

Expand Down Expand Up @@ -206,7 +206,7 @@ What has to hold before the first release, for a user on GNOME 50 with Ubuntu 26
| 5 | Edits data in place, and the changes commit correctly even when the connection or the app misbehaves | Write, row guard and commit resolution conformance on every engine, with the fault fakes and the grid save Docker test. `kill_during_save` kills TablePro halfway through a SQLite save: the file keeps none of it, and the next launch restores the session with its history, settings and saved tabs. A save into a SQLite file another program holds locked fails as `Busy(FileLocked)`, which says another program or an editor tab holds the file's lock, and leaves the file as it was whether the lock shows at `BEGIN IMMEDIATE` or at the `COMMIT` | Nothing |
| 6 | Trusts the app with credentials: TLS, SSH tunnels, no password in plain text | Verify full by default, the TLS conformance suite, the Secret Service, and statement logging silenced | An outside security review |
| 7 | Recovers when the network drops or the database restarts | Health monitoring, backoff and replay rules with their tests, a stopped container in the liveness tests, and a session test that restarts PostgreSQL: the read in flight runs again, the save in flight does not and leaves no row | Nothing |
| 8 | Works with a screen reader, the keyboard alone and larger text | Label audits of the picker, the connect pages, the workspace, the connect dialog and preferences, labelled cells and real-key tests. Sorting from the cell menu, named filter fields and cell editors, focus handed on when a control hides, and announced query results and errors. Columns resize from the keyboard, and a column sized by a key or a header drag draws at that width, also when it comes back | The manual pass in the backlog |
| 8 | Works with a screen reader, the keyboard alone and larger text | Label audits of the picker, the connect pages, the workspace, the connect dialog and preferences, labelled cells and real-key tests. Sorting from the cell menu, named filter fields and cell editors, focus handed on when a control hides, and announced query results and errors. Columns resize from the keyboard, and a column sized by a key or a header drag draws at that width, also when it comes back. The ER diagram's tables are focusable list items that name their columns and joins | The manual pass in the backlog |
| 9 | Sees no message that cannot be translated, even while only English ships | Every string the app writes is in the template. Core errors carry values the app words itself: CSV import, cell and JSON parsing, type and default syntax, the keyring, certificate files, unsupported features, the SSH host, username and jump host checks, a failed write of the saved connections, the SSH runtime directory checks, ssh's exit status, and an ssh that would not start. A panic, an export encoder failure, an SSH failure with no sentence of its own and a TLS failure the app does not classify show the app's words and log the detail | Nothing |
| 10 | Uses the app daily for a week without an unrecoverable failure | Nothing yet | A dated week of daily use |
| 11 | Works with a database of many schemas or tables, such as one schema per tenant, without the sidebar or completion freezing | The large-catalog case on PostgreSQL with 100 schemas of 500 tables and MySQL with one database of 50,000: the sidebar's first listing, its search a key at a time, and completion after `FROM `, a key typed into it and after a schema's dot each have a time bound, none may stall the main loop past 100 ms, and the catalog has a bound on resident peak growth ([testing.md](docs/testing.md#large-catalog-case)) | Nothing |
Expand Down
1 change: 1 addition & 0 deletions linux/changelog.d/3817.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ER diagram of a schema's tables and foreign keys, with pan, zoom, movable tables and PNG and SVG export.
1 change: 1 addition & 0 deletions linux/crates/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ thiserror.workspace = true
gio.workspace = true
gio-unix.workspace = true
gtk4.workspace = true
cairo-rs.workspace = true
libadwaita.workspace = true
sourceview5.workspace = true
glib.workspace = true
Expand Down
90 changes: 87 additions & 3 deletions linux/crates/app/src/application/accelerators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use super::{Accelerator, AcceleratorScope, ShortcutListing, ShortcutOwner, Short
use AcceleratorScope::{Application, Component, Local, Stock};
use ShortcutListing::{Listed, Unlisted};
use ShortcutOwner::{
BrowseGrid, BrowseTab, CellEditor, FilterStrip, GridCell, HistoryRow, PickerPage, PickerRow, SidebarList,
SqlEditor, SqlEditorTab, StructureTab,
BrowseGrid, BrowseTab, CellEditor, DiagramCanvas, DiagramTab, FilterStrip, GridCell, HistoryRow, PickerPage,
PickerRow, SidebarList, SqlEditor, SqlEditorTab, StructureTab,
};
use ShortcutSection::{BrowseTable, DataGrid, General, TableStructure, Tabs};
use ShortcutSection::{BrowseTable, DataGrid, ErDiagram, General, TableStructure, Tabs};

const BUBBLE: gtk::PropagationPhase = gtk::PropagationPhase::Bubble;
/// GtkTextView binds Ctrl+/ to select-all, and a bubble controller on
Expand Down Expand Up @@ -312,6 +312,82 @@ pub const ACCELERATORS: &[Accelerator] = &[
Unlisted,
),
local(PickerRow, BUBBLE, "picker-row.menu", &["Menu", "<Shift>F10"], Unlisted),
// The GNOME HIG's zoom keys, with Ctrl+= for a keyboard where + needs
// Shift.
local(
DiagramTab,
BUBBLE,
"diagram.zoom-in",
&["<Primary>plus", "<Primary>equal"],
Listed(ErDiagram),
),
local(
DiagramTab,
BUBBLE,
"diagram.zoom-out",
&["<Primary>minus"],
Listed(ErDiagram),
),
local(
DiagramTab,
BUBBLE,
"diagram.zoom-reset",
&["<Primary>0"],
Listed(ErDiagram),
),
// As in Image Viewer. Nothing in the tab takes text.
local(DiagramTab, BUBBLE, "diagram.zoom-fit", &["f"], Listed(ErDiagram)),
local(
DiagramTab,
BUBBLE,
"diagram.refresh",
&["F5", "<Primary>r"],
Listed(ErDiagram),
),
local(
DiagramCanvas,
BUBBLE,
"diagram.open-table",
&["Return"],
Listed(ErDiagram),
),
local(
DiagramCanvas,
BUBBLE,
"diagram.context-menu",
&["Menu", "<Shift>F10"],
Unlisted,
),
// The arrow keys alone move the focus from table to table, as they
// do between any widgets.
local(
DiagramCanvas,
BUBBLE,
"diagram.move-table-left",
&["<Alt>Left"],
Listed(ErDiagram),
),
local(
DiagramCanvas,
BUBBLE,
"diagram.move-table-right",
&["<Alt>Right"],
Listed(ErDiagram),
),
local(
DiagramCanvas,
BUBBLE,
"diagram.move-table-up",
&["<Alt>Up"],
Listed(ErDiagram),
),
local(
DiagramCanvas,
BUBBLE,
"diagram.move-table-down",
&["<Alt>Down"],
Listed(ErDiagram),
),
local(
PickerPage,
BUBBLE,
Expand Down Expand Up @@ -367,6 +443,8 @@ pub const fn controller_name(owner: ShortcutOwner) -> &'static str {
SidebarList => "tablepro-sidebar-list-shortcuts",
HistoryRow => "tablepro-history-row-shortcuts",
PickerRow => "tablepro-picker-row-shortcuts",
DiagramTab => "tablepro-diagram-tab-shortcuts",
DiagramCanvas => "tablepro-diagram-canvas-shortcuts",
PickerPage => "tablepro-picker-page-shortcuts",
}
}
Expand Down Expand Up @@ -461,6 +539,11 @@ pub(crate) fn keypad_twins(key: gdk::Key) -> &'static [gdk::Key] {
gdk::Key::Delete => &[gdk::Key::KP_Delete],
gdk::Key::Left => &[gdk::Key::KP_Left],
gdk::Key::Right => &[gdk::Key::KP_Right],
gdk::Key::Up => &[gdk::Key::KP_Up],
gdk::Key::Down => &[gdk::Key::KP_Down],
gdk::Key::plus => &[gdk::Key::KP_Add],
gdk::Key::minus => &[gdk::Key::KP_Subtract],
gdk::Key::_0 => &[gdk::Key::KP_0],
_ => &[],
}
}
Expand Down Expand Up @@ -751,6 +834,7 @@ mod tests {
(ShortcutSection::DataGrid, "section-data-grid"),
(ShortcutSection::SqlEditor, "section-sql-editor"),
(ShortcutSection::TableStructure, "section-table-structure"),
(ShortcutSection::ErDiagram, "section-er-diagram"),
];
let mut placed: Vec<(adw::ShortcutsItem, ShortcutSection)> = Vec::new();
for (section, id) in sections {
Expand Down
4 changes: 4 additions & 0 deletions linux/crates/app/src/application/shortcut_owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub enum ShortcutOwner {
HistoryRow,
/// A saved connection's row in the picker.
PickerRow,
/// The whole ER diagram tab, its bottom bar included.
DiagramTab,
/// The diagram's canvas, where a table has the keyboard focus.
DiagramCanvas,
/// The picker as a whole, its search included.
PickerPage,
}
1 change: 1 addition & 0 deletions linux/crates/app/src/application/shortcut_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ pub enum ShortcutSection {
DataGrid,
SqlEditor,
TableStructure,
ErDiagram,
}
5 changes: 3 additions & 2 deletions linux/crates/app/src/ui/a11y/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use crate::test_support::{
ButtonFace, button_face, descendants, has_accessible_label, test_backend_records_properties,
};
use crate::ui::{
PreferencesDialog, SessionWindow, browse_tab, connect_dialog, editor, export, filter_strip, history_dialog,
preferences_dialog, session_window, ssh_section, structure_tab, welcome_view, workspace,
PreferencesDialog, SessionWindow, browse_tab, connect_dialog, diagram, editor, export, filter_strip,
history_dialog, preferences_dialog, session_window, ssh_section, structure_tab, welcome_view, workspace,
};

/// Every class the app's templates build. A template whose class is
Expand All @@ -20,6 +20,7 @@ fn template_classes() -> Vec<glib::Type> {
browse_tab::PagerBar::static_type(),
browse_tab::PendingChangesBar::static_type(),
connect_dialog::ConnectDialog::static_type(),
diagram::DiagramTab::static_type(),
editor::SqlEditor::static_type(),
export::ExportOperationsButton::static_type(),
filter_strip::FilterRuleRow::static_type(),
Expand Down
Loading
Loading