Skip to content

Commit 71ce1f1

Browse files
Rigbyfab4claude
andcommitted
fix(bq_driver): keep the Advanced Options dialog within the screen
The Advanced Options dialog asked for a fixed 720x525 window and centred it on the raw screen metrics. On a display whose usable height is smaller than that -- a 1280x720 panel at 150% scaling leaves 672px of work area -- the lower part of the dialog, including the OK and Cancel buttons, fell below the screen edge with no way to reach it. Adding the allowed-projects pick list made this worse by growing the layout, but the dialog already did not fit such a display before that. Clamp the window height to the desktop work area and give the window a vertical scrollbar, so a layout taller than the available space stays fully reachable. Scrolling moves the child controls via ScrollWindow, which keeps the existing absolute control positions untouched. The scroll range is derived from the laid-out content height and refreshed on WM_SIZE; the scrollbar, mouse wheel, page keys and thumb dragging are all handled. Widen the frame by the scrollbar width. The scrollbar is carved out of the client area, so without this every right-aligned control loses those pixels and the edit boxes are clipped. Accumulate mouse wheel movement rather than dividing each message by WHEEL_DELTA. High-resolution wheels and precision trackpads report fractions of a notch, which integer division rounded away to nothing, so those devices could not scroll the dialog at all. Also honour the system SPI_GETWHEELSCROLLLINES setting instead of assuming three lines. Also centre the window inside the work area rather than the full screen, so it is not positioned partly behind the taskbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 1e5d043 commit 71ce1f1

2 files changed

Lines changed: 198 additions & 16 deletions

File tree

google/cloud/odbc/bq_driver/internal/driver_adv_opt_form.cc

Lines changed: 181 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ std::string AdvanceOptions::max_retries_ = std::to_string(kDefaultMaxRetries);
6868
std::string AdvanceOptions::private_service_connect_uris_;
6969
std::string AdvanceOptions::enable_gcd_;
7070
std::string AdvanceOptions::universe_domain_;
71+
int AdvanceOptions::scroll_pos_ = 0;
72+
int AdvanceOptions::wheel_remainder_ = 0;
7173

7274
std::string const kLanguageDialect = "SQLDialect";
7375
std::string const kLargeResultsDatasetId = "LargeResultsDatasetId";
@@ -112,6 +114,14 @@ int const kEditBoxHeight = 17;
112114
int const kinputComboBoxXAxis = 237;
113115
int const KComboBoxHeight = 100;
114116

117+
// Full height of the laid-out controls. The window is clamped to the desktop
118+
// work area, so on a short or DPI-scaled display this is larger than the client
119+
// area and the difference is what scrolls.
120+
int const kContentHeight = kButtonY + 44 + kButtonHeight + 10;
121+
122+
// Pixels scrolled per scrollbar arrow click.
123+
int const kScrollLine = 20;
124+
115125
HWND AdvanceOptions::GetHwnd() const { return adv_hwnd; }
116126
AdvanceOptions::AdvanceOptions() : adv_hwnd(NULL) {}
117127
AdvanceOptions::~AdvanceOptions() {
@@ -470,6 +480,57 @@ void AdvanceOptions::CreateAdditionalControls(HFONT h_font) {
470480
// SendMessage(h_hyperlink, WM_SETFONT, (WPARAM)h_font, TRUE);
471481
}
472482

483+
void AdvanceOptions::UpdateScrollInfo(HWND hwnd) {
484+
RECT client = {};
485+
GetClientRect(hwnd, &client);
486+
int const page = client.bottom - client.top;
487+
488+
SCROLLINFO si = {};
489+
si.cbSize = sizeof(si);
490+
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
491+
si.nMin = 0;
492+
si.nMax = kContentHeight - 1;
493+
si.nPage = page;
494+
si.nPos = scroll_pos_;
495+
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
496+
497+
// Growing the window can leave us scrolled past the end; pull the content
498+
// back so there is never blank space below the last control.
499+
int const max_pos = (kContentHeight > page) ? kContentHeight - page : 0;
500+
if (scroll_pos_ > max_pos) {
501+
ScrollWindow(hwnd, 0, scroll_pos_ - max_pos, NULL, NULL);
502+
scroll_pos_ = max_pos;
503+
si.fMask = SIF_POS;
504+
si.nPos = scroll_pos_;
505+
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
506+
}
507+
}
508+
509+
void AdvanceOptions::ScrollTo(HWND hwnd, int new_pos) {
510+
RECT client = {};
511+
GetClientRect(hwnd, &client);
512+
int const page = client.bottom - client.top;
513+
int const max_pos = (kContentHeight > page) ? kContentHeight - page : 0;
514+
new_pos = std::max(0, std::min(new_pos, max_pos));
515+
if (new_pos == scroll_pos_) {
516+
return;
517+
}
518+
519+
int const delta = scroll_pos_ - new_pos;
520+
scroll_pos_ = new_pos;
521+
// ScrollWindow shifts the child controls along with the client area, which is
522+
// what makes absolutely-positioned controls scroll without repositioning each
523+
// one by hand.
524+
ScrollWindow(hwnd, 0, delta, NULL, NULL);
525+
526+
SCROLLINFO si = {};
527+
si.cbSize = sizeof(si);
528+
si.fMask = SIF_POS;
529+
si.nPos = scroll_pos_;
530+
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
531+
UpdateWindow(hwnd);
532+
}
533+
473534
void AdvanceOptions::PopulateAllowedProjectsListView(
474535
HWND h_list_view, std::vector<std::string> const& project_ids) {
475536
if (!h_list_view) {
@@ -875,17 +936,26 @@ LRESULT CALLBACK AdvanceOptions::AdvanceOptProc(HWND hwnd, UINT u_msg,
875936
if (HIWORD(w_param) != BN_CLICKED) {
876937
break;
877938
}
878-
// The credentials live on the parent DSN dialog; this dialog holds no
879-
// copy of them.
880-
HWND h_parent = GetParent(hwnd);
939+
// The credentials live on the main DSN dialog; this dialog holds no
940+
// copy of them. This is a top-level owned window rather than a child,
941+
// and GetParent only reports the owner for WS_POPUP windows, so ask
942+
// for the owner explicitly.
943+
HWND h_parent = GetWindow(hwnd, GW_OWNER);
944+
if (h_parent == NULL) {
945+
h_parent = GetParent(hwnd);
946+
}
947+
if (h_parent == NULL) {
948+
ShowErrorWindow(hwnd,
949+
"Internal error: cannot locate the main dialog to "
950+
"read the connection settings from.");
951+
break;
952+
}
881953
char key_file_buffer[1024] = {0};
882954
char auth_buffer[256] = {0};
883-
if (h_parent) {
884-
GetWindowText(GetDlgItem(h_parent, kIdcKeyfileEdit),
885-
key_file_buffer, sizeof(key_file_buffer));
886-
GetWindowText(GetDlgItem(h_parent, kIdcAuthBox), auth_buffer,
887-
sizeof(auth_buffer));
888-
}
955+
GetWindowText(GetDlgItem(h_parent, kIdcKeyfileEdit), key_file_buffer,
956+
sizeof(key_file_buffer));
957+
GetWindowText(GetDlgItem(h_parent, kIdcAuthBox), auth_buffer,
958+
sizeof(auth_buffer));
889959
if (auth_buffer[0] == '\0') {
890960
ShowErrorWindow(hwnd,
891961
"Select an OAuth mechanism on the main dialog "
@@ -930,6 +1000,76 @@ LRESULT CALLBACK AdvanceOptions::AdvanceOptProc(HWND hwnd, UINT u_msg,
9301000
}
9311001
break;
9321002
}
1003+
case WM_VSCROLL: {
1004+
RECT client = {};
1005+
GetClientRect(hwnd, &client);
1006+
int const page = client.bottom - client.top;
1007+
int pos = scroll_pos_;
1008+
switch (LOWORD(w_param)) {
1009+
case SB_TOP:
1010+
pos = 0;
1011+
break;
1012+
case SB_BOTTOM:
1013+
pos = kContentHeight;
1014+
break;
1015+
case SB_LINEUP:
1016+
pos -= kScrollLine;
1017+
break;
1018+
case SB_LINEDOWN:
1019+
pos += kScrollLine;
1020+
break;
1021+
case SB_PAGEUP:
1022+
pos -= page;
1023+
break;
1024+
case SB_PAGEDOWN:
1025+
pos += page;
1026+
break;
1027+
case SB_THUMBTRACK:
1028+
case SB_THUMBPOSITION: {
1029+
SCROLLINFO si = {};
1030+
si.cbSize = sizeof(si);
1031+
si.fMask = SIF_TRACKPOS;
1032+
if (GetScrollInfo(hwnd, SB_VERT, &si)) {
1033+
pos = si.nTrackPos;
1034+
}
1035+
break;
1036+
}
1037+
default:
1038+
break;
1039+
}
1040+
ScrollTo(hwnd, pos);
1041+
return 0;
1042+
}
1043+
case WM_MOUSEWHEEL: {
1044+
// Accumulate: precision trackpads and high-resolution wheels send deltas
1045+
// smaller than WHEEL_DELTA, which would round to zero on their own.
1046+
wheel_remainder_ += GET_WHEEL_DELTA_WPARAM(w_param);
1047+
int const notches = wheel_remainder_ / WHEEL_DELTA;
1048+
if (notches == 0) {
1049+
return 0;
1050+
}
1051+
wheel_remainder_ -= notches * WHEEL_DELTA;
1052+
1053+
// Honour the system "roll the mouse wheel to scroll" setting.
1054+
UINT lines_per_notch = 3;
1055+
if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_notch,
1056+
0)) {
1057+
lines_per_notch = 3;
1058+
}
1059+
RECT client = {};
1060+
GetClientRect(hwnd, &client);
1061+
int step = 0;
1062+
if (lines_per_notch == WHEEL_PAGESCROLL) {
1063+
step = client.bottom - client.top;
1064+
} else {
1065+
step = static_cast<int>(lines_per_notch) * kScrollLine;
1066+
}
1067+
ScrollTo(hwnd, scroll_pos_ - notches * step);
1068+
return 0;
1069+
}
1070+
case WM_SIZE:
1071+
UpdateScrollInfo(hwnd);
1072+
break;
9331073
case WM_KEYDOWN: // Capture global key presses
9341074
if (w_param == VK_ESCAPE) {
9351075
if (p_current_window) {
@@ -1047,17 +1187,40 @@ void AdvanceOptions::Show(HWND hwnd) {
10471187

10481188
RegisterClass(&wc_adv);
10491189

1050-
int window_width = 525;
1190+
scroll_pos_ = 0;
1191+
wheel_remainder_ = 0;
1192+
1193+
// The vertical scrollbar is carved out of the client area, so widen the frame
1194+
// by its width; otherwise every right-aligned control loses that many pixels
1195+
// and the edit boxes are clipped.
1196+
int window_width = 525 + GetSystemMetrics(SM_CXVSCROLL);
10511197
int window_height = 860;
1052-
int screen_width = GetSystemMetrics(SM_CXSCREEN);
1053-
int screen_height = GetSystemMetrics(SM_CYSCREEN);
1054-
int x_pos = (screen_width - window_width) / 2;
1055-
int y_pos = (screen_height - window_height) / 2;
1198+
1199+
// Never open taller than the desktop work area: on a short or DPI-scaled
1200+
// display the full control layout does not fit, and a window whose OK button
1201+
// sits below the screen edge cannot be dismissed. Whatever does not fit is
1202+
// reachable through the vertical scrollbar instead.
1203+
RECT work_area = {};
1204+
int work_left = 0;
1205+
int work_top = 0;
1206+
int work_width = GetSystemMetrics(SM_CXSCREEN);
1207+
int work_height = GetSystemMetrics(SM_CYSCREEN);
1208+
if (SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0)) {
1209+
work_left = work_area.left;
1210+
work_top = work_area.top;
1211+
work_width = work_area.right - work_area.left;
1212+
work_height = work_area.bottom - work_area.top;
1213+
}
1214+
if (window_height > work_height) {
1215+
window_height = work_height;
1216+
}
1217+
int x_pos = work_left + (work_width - window_width) / 2;
1218+
int y_pos = work_top + (work_height - window_height) / 2;
10561219

10571220
adv_hwnd = CreateWindowEx(
10581221
WS_EX_TOPMOST, CLASS_NAME, "Advanced Options",
1059-
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_DLGFRAME, x_pos, y_pos,
1060-
window_width, window_height, hwnd, NULL, g_hDllInstance, this);
1222+
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_DLGFRAME | WS_VSCROLL, x_pos,
1223+
y_pos, window_width, window_height, hwnd, NULL, g_hDllInstance, this);
10611224
if (adv_hwnd) {
10621225
HFONT h_font =
10631226
CreateFont(-10, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
@@ -1073,6 +1236,8 @@ void AdvanceOptions::Show(HWND hwnd) {
10731236
CreateAdditionalControls(h_font);
10741237
CreateButtons(h_font);
10751238

1239+
UpdateScrollInfo(adv_hwnd);
1240+
10761241
ShowWindow(adv_hwnd, SW_SHOW);
10771242
UpdateWindow(adv_hwnd);
10781243

google/cloud/odbc/bq_driver/internal/driver_adv_opt_form.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,23 @@ class AdvanceOptions {
143143
static std::string enable_gcd_;
144144
static std::string universe_domain_;
145145

146+
// Current vertical scroll offset, in pixels, of the control area. The dialog
147+
// is taller than the work area on small or scaled displays, so it scrolls.
148+
static int scroll_pos_;
149+
150+
// Leftover wheel movement smaller than one notch. High-resolution wheels and
151+
// precision trackpads report fractions of WHEEL_DELTA, which would otherwise
152+
// be rounded away to nothing.
153+
static int wheel_remainder_;
154+
155+
// Recompute the scrollbar range/page from the current client height. Call
156+
// after the controls are created and whenever the window is resized.
157+
static void UpdateScrollInfo(HWND hwnd);
158+
159+
// Scroll the control area to 'new_pos' pixels, clamped to the scrollable
160+
// range. Moves the child controls with it.
161+
static void ScrollTo(HWND hwnd, int new_pos);
162+
146163
// Fill the allowed-projects pick list with 'project_ids', keeping ticked any
147164
// id that is currently ticked as well as any id already in
148165
// 'allowed_projects_'. Used both when the dialog opens and when the user

0 commit comments

Comments
 (0)