diff --git a/CONTEXT.md b/CONTEXT.md index ac1d011..68d6d48 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -39,9 +39,9 @@ architecture, the modules, and the tests all refer to the same things. query editor, and the last folder they exported to. Persisted between runs. - **Zoom** — a scale factor the user applies to the text of a pane, in discrete steps away from the size that pane was built with. Step 0 is the untouched - size. The query editor and the Tree each carry their own Zoom, so one can be - enlarged without the other. Part of the window state, so both are persisted - between runs. + size. There are two: one that the query editor and the result Messages pane + share, and one that the Tree carries, so either side can be enlarged without + the other. Part of the window state, so both are persisted between runs. ## Modules (current shape) @@ -62,9 +62,9 @@ architecture, the modules, and the tests all refer to the same things. progress to the GUI thread, surfaces cancel and completion. - **ZoomPresenter** — owns one Zoom and applies it to the widgets registered with it, turning the zoom gestures over those widgets into steps. MainWindow - keeps one per independently zoomable pane. Ctrl+wheel needs no routing - because a presenter only watches its own widgets; a keyboard zoom goes to - the presenter that holds the focus. + keeps one per independently zoomable group of panes. Ctrl+wheel needs no + routing because a presenter only watches its own widgets; a keyboard zoom + goes to the presenter that holds the focus. - **SessionManager** — persists and restores the Session and window state. - **MainWindow** — Qt shell. Wires the modules to menu actions and the UI form. Does not contain business logic. diff --git a/README.md b/README.md index 2c96baf..77c3d0f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ making database operations seamless and straightforward. - Export database schema as CREATE TABLE statements - Export data as an SQL script containing INSERT statements or as CSV files - Zoom the query editor and database tree independently, each with its own size, - using Ctrl +/- on the focused pane or Ctrl + mouse wheel over either one + using Ctrl +/- on the focused pane or Ctrl + mouse wheel over it. The query + result Messages pane zooms together with the query editor, and answers both + gestures itself - Desktop color theme awareness with automatic switching between dark/light themes - Command line interface for automation and scripting diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 103085f..9d13878 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -19,9 +19,13 @@ MainWindow::MainWindow(IDatabase *database, QWidget *parent) : ui->setupUi(this); ui->splitterMain->setStretchFactor(1, 3); ui->splitterQueryTab->setStretchFactor(1, 1); - // Two presenters, so each pane carries its own zoom level. + // Two presenters, so the editor and the Tree zoom independently of one + // another. The messages pane rides on the editor's rather than carrying a + // third: it reports on the query the user is looking at, so the two read + // as one surface and stay at a single size. this->editorZoom = std::make_unique(this); this->editorZoom->addTarget(ui->textEdit); + this->editorZoom->addTarget(ui->queryResultMessagesTextEdit); this->treeZoom = std::make_unique(this); this->treeZoom->addTarget(ui->treeWidget); diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 017213a..b4a85b1 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include @@ -50,6 +53,29 @@ class MainWindowTest : public ::testing::Test { editor->setPlainText(sql); } + // Zoom is driven the way the user drives it, through the menu actions, + // rather than by reaching for the presenters the window keeps private. + static void zoomIn(const MainWindow &window) { + auto *action = window.findChild("actionZoom_In"); + ASSERT_NE(action, nullptr); + action->trigger(); + } + + static void resetZoom(const MainWindow &window) { + auto *action = window.findChild("actionReset_Zoom"); + ASSERT_NE(action, nullptr); + action->trigger(); + } + + // The measure ZoomPresenter works in. A widget that inherits its font, or + // that was sized in pixels, reports no point size of its own, so reading + // pointSizeF() straight off it can yield -1 and turn a correct scaling + // into a failed comparison. + static double pointSize(const QWidget *widget) { + const double size = widget->font().pointSizeF(); + return size > 0 ? size : QFontInfo(widget->font()).pointSizeF(); + } + std::unique_ptr db; }; @@ -96,3 +122,29 @@ TEST_F(MainWindowTest, ACreateRefreshesTheTree) { EXPECT_TRUE(tableNames(window).contains("made_by_the_window")); } + +// The messages pane rides on the editor's zoom, so one zoom moves both and +// they stay proportional to the sizes they were built with. +TEST_F(MainWindowTest, ZoomingTheEditorZoomsTheResultMessagesPane) { + const MainWindow window(db.get()); + auto *editor = window.findChild("textEdit"); + auto *messages = window.findChild("queryResultMessagesTextEdit"); + ASSERT_NE(editor, nullptr); + ASSERT_NE(messages, nullptr); + + // The window restores whatever zoom the last run left behind, so start + // from a known step rather than from that. + resetZoom(window); + const double editorBefore = pointSize(editor); + const double messagesBefore = pointSize(messages); + + zoomIn(window); + + EXPECT_GT(pointSize(editor), editorBefore); + EXPECT_NEAR(pointSize(messages), + messagesBefore * (pointSize(editor) / editorBefore), 0.001); + + // The window persists its zoom step as it is torn down, so hand the next + // test the same starting point this one was given. + resetZoom(window); +}