From d17771064afef26a4eaf916bedc4c4c5b8600686 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 12 Apr 2016 14:24:09 +0000 Subject: [PATCH] Ensure edit control font size matches font size of text being edited. Before this commit, the position of the edit box was adjusted by trial and error, as far as I can tell. This commit changes the positioning machinery for edit controls as follows: The coordinates passed to ShowTextEditControl/ShowGraphicsEditControl now denote: X the left bound, and Y the baseline. The font height passed to ShowGraphicsEditControl denotes the absolute font height in pixels, i.e. ascent plus descent. Platform-dependent code uses these coordinates, the font metrics for the font appropriate for the platform, and the knowledge of the decorations drawn around the text by the native edit control to position the edit control in a way that overlays the text inside the edit control with the rendered text. On OS X, GNU Unifont (of height 16) has metrics identical to Monaco (of height 15) and so as an exception, the edit control is nudged slightly for a pixel-perfect fit. Also, since the built-in vector font is proportional, this commit also switches the edit control font to proportional when editing constraints. --- src/cocoa/cocoamain.mm | 42 +++++++++++++++++++++++++----------- src/gtk/gtkmain.cpp | 49 ++++++++++++++++++++++++++++++------------ src/mouse.cpp | 6 +++++- src/solvespace.h | 2 +- src/textwin.cpp | 2 +- src/win32/w32main.cpp | 44 ++++++++++++++++++++++++------------- 6 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm index 6a905cc..9931706 100644 --- a/src/cocoa/cocoamain.mm +++ b/src/cocoa/cocoamain.mm @@ -133,7 +133,8 @@ void SolveSpace::ScheduleLater() { @property BOOL wantsBackingStoreScaling; @property(readonly, getter=isEditing) BOOL editing; -- (void)startEditing:(NSString*)text at:(NSPoint)origin; +- (void)startEditing:(NSString*)text at:(NSPoint)origin + withSize:(double)fontSize usingMonospace:(BOOL)isMonospace; - (void)stopEditing; - (void)didEdit:(NSString*)text; @end @@ -160,6 +161,8 @@ void SolveSpace::ScheduleLater() { editor = [[NSTextField alloc] init]; [editor setEditable:YES]; + [[editor cell] setUsesSingleLineMode:YES]; + [editor setBezeled:NO]; [editor setTarget:self]; [editor setAction:@selector(editorAction:)]; @@ -202,7 +205,6 @@ CONVERT(Rect) offscreen = new GLOffscreen; NSSize size = [self convertSizeToBacking:[self bounds].size]; - NSRect bounds = [self convertRectToBacking:[self bounds]]; offscreen->begin(size.width, size.height); [self drawGL]; @@ -225,12 +227,24 @@ CONVERT(Rect) @synthesize editing; -- (void)startEditing:(NSString*)text at:(NSPoint)origin { +- (void)startEditing:(NSString*)text at:(NSPoint)origin + withSize:(double)fontSize usingMonospace:(BOOL)isMonospace { if(!self->editing) { [self addSubview:editor]; self->editing = YES; } + NSFont *font; + if(isMonospace) + font = [NSFont fontWithName:@"Monaco" size:fontSize]; + else + font = [NSFont controlContentFontOfSize:fontSize]; + [editor setFont:font]; + + origin.x -= 3; /* left padding; no way to get it from NSTextField */ + origin.y -= [editor intrinsicContentSize].height; + origin.y += [editor baselineOffsetFromBottom]; + [editor setFrameOrigin:origin]; [editor setStringValue:text]; [self prepareEditor]; @@ -251,9 +265,10 @@ CONVERT(Rect) } - (void)prepareEditor { + CGFloat intrinsicContentHeight = [editor intrinsicContentSize].height; [editor setFrameSize:(NSSize){ - .width = 100, - .height = [editor intrinsicContentSize].height }]; + .width = intrinsicContentHeight * 12, + .height = intrinsicContentHeight }]; } - (void)didEdit:(NSString*)text { @@ -382,18 +397,20 @@ CONVERT(Rect) [super keyDown:event]; } -- (void)startEditing:(NSString*)text at:(NSPoint)xy { +- (void)startEditing:(NSString*)text at:(NSPoint)xy withSize:(double)fontSize { // Convert to ij (vs. xy) style coordinates NSSize size = [self convertSizeToBacking:[self bounds].size]; NSPoint point = { .x = xy.x + size.width / 2, - .y = xy.y - size.height / 2 + [editor intrinsicContentSize].height + .y = xy.y - size.height / 2 }; - [super startEditing:text at:[self convertPointFromBacking:point]]; + [super startEditing:text at:[self convertPointFromBacking:point] + withSize:fontSize usingMonospace:FALSE]; } - (void)didEdit:(NSString*)text { SolveSpace::SS.GW.EditControlDone([text UTF8String]); + [self setNeedsDisplay:YES]; } - (void)cancelOperation:(id)sender { @@ -487,9 +504,10 @@ bool FullScreenIsActive(void) { return [GWDelegate isFullscreen]; } -void ShowGraphicsEditControl(int x, int y, const std::string &str) { +void ShowGraphicsEditControl(int x, int y, int fontSize, const std::string &str) { [GWView startEditing:[NSString stringWithUTF8String:str.c_str()] - at:(NSPoint){(CGFloat)x, (CGFloat)y}]; + at:(NSPoint){(CGFloat)x, (CGFloat)y} + withSize:fontSize]; } void HideGraphicsEditControl(void) { @@ -937,8 +955,8 @@ SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel( - (void)startEditing:(NSString*)text at:(NSPoint)point { point = [self convertPointFromBacking:point]; - point.y = -point.y; - [super startEditing:text at:point]; + point.y = -point.y + 2; + [super startEditing:text at:point withSize:15.0 usingMonospace:TRUE]; [[self window] makeKeyWindow]; [[self window] makeFirstResponder:editor]; } diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index f6451c1..2c130f6 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -372,24 +372,44 @@ public: EditorOverlay(Gtk::Widget &underlay) : _underlay(underlay) { add(_underlay); - Pango::FontDescription desc; - desc.set_family("monospace"); - desc.set_size(7000); -#ifdef HAVE_GTK3 - _entry.override_font(desc); -#else - _entry.modify_font(desc); -#endif _entry.set_width_chars(30); _entry.set_no_show_all(true); + _entry.set_has_frame(false); add(_entry); _entry.signal_activate(). connect(sigc::mem_fun(this, &EditorOverlay::on_activate)); } - void start_editing(int x, int y, const std::string &val) { - move(_entry, x, y - 4); + void start_editing(int x, int y, int font_height, + bool is_monospace, const std::string &val) { + Pango::FontDescription font_desc; + font_desc.set_family(is_monospace ? "monospace" : "normal"); + font_desc.set_absolute_size(font_height * Pango::SCALE); + +#ifdef HAVE_GTK3 + _entry.override_font(font_desc); +#else + _entry.modify_font(font_desc); +#endif + + /* y coordinate denotes baseline */ + Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc); + y -= font_metrics.get_ascent() / Pango::SCALE; + +#ifdef HAVE_GTK3 + Gtk::Border border = _entry.get_style_context()->get_padding(); + move(_entry, x - border.get_left(), y - border.get_top()); +#else + /* We need _gtk_entry_effective_inner_border, but it's not + in the public API, so emulate its logic. */ + Gtk::Border border = { 2, 2, 2, 2 }, *style_border; + gtk_widget_style_get(GTK_WIDGET(_entry.gobj()), "inner-border", + &style_border, NULL); + if(style_border) border = *style_border; + move(_entry, x - border.left, y - border.top); +#endif + _entry.set_text(val); if(!_entry.is_visible()) { _entry.show(); @@ -721,16 +741,16 @@ bool FullScreenIsActive(void) { return GW->is_fullscreen(); } -void ShowGraphicsEditControl(int x, int y, const std::string &val) { +void ShowGraphicsEditControl(int x, int y, int fontHeight, const std::string &val) { Gdk::Rectangle rect = GW->get_widget().get_allocation(); // Convert to ij (vs. xy) style coordinates, // and compensate for the input widget height due to inverse coord int i, j; i = x + rect.get_width() / 2; - j = -y + rect.get_height() / 2 - 24; + j = -y + rect.get_height() / 2; - GW->get_overlay().start_editing(i, j, val); + GW->get_overlay().start_editing(i, j, fontHeight, /*is_monospace=*/false, val); } void HideGraphicsEditControl(void) { @@ -1414,7 +1434,8 @@ void SetMousePointerToHand(bool is_hand) { } void ShowTextEditControl(int x, int y, const std::string &val) { - TW->get_overlay().start_editing(x, y, val); + TW->get_overlay().start_editing(x, y, TextWindow::CHAR_HEIGHT, + /*is_monospace=*/true, val); } void HideTextEditControl(void) { diff --git a/src/mouse.cpp b/src/mouse.cpp index ee727c9..ae66dd7 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -1292,7 +1292,11 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { break; } } - ShowGraphicsEditControl((int)p2.x, (int)p2.y-4, edit_value); + hStyle hs = c->disp.style; + if(hs.v == 0) hs.v = Style::CONSTRAINT; + ShowGraphicsEditControl((int)p2.x, (int)p2.y, + ssglStrFontSize(Style::TextHeight(hs)) * scale, + edit_value); } } diff --git a/src/solvespace.h b/src/solvespace.h index cf2fd9f..1b0c0b8 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -235,7 +235,7 @@ void CheckMenuById(int id, bool checked); void RadioMenuById(int id, bool selected); void EnableMenuById(int id, bool enabled); -void ShowGraphicsEditControl(int x, int y, const std::string &str); +void ShowGraphicsEditControl(int x, int y, int fontHeight, const std::string &str); void HideGraphicsEditControl(void); bool GraphicsEditControlIsVisible(void); void ShowTextEditControl(int x, int y, const std::string &str); diff --git a/src/textwin.cpp b/src/textwin.cpp index e685869..2cb1798 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -88,7 +88,7 @@ void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) { int x = LEFT_MARGIN + CHAR_WIDTH*col; int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2); - ShowTextEditControl(x - 3, y + 2, str); + ShowTextEditControl(x, y + 18, str); } void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 33def55..f8caaf8 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -21,10 +21,6 @@ # undef uint32_t // thanks but no thanks #endif -// For the edit controls -#define EDIT_WIDTH 220 -#define EDIT_HEIGHT 21 - HINSTANCE Instance; HWND TextWnd; @@ -803,8 +799,29 @@ void SolveSpace::InvalidateText(void) InvalidateRect(TextWnd, NULL, false); } -static void ShowEditControl(HWND h, int x, int y, const std::wstring &s) { - MoveWindow(h, x, y, EDIT_WIDTH, EDIT_HEIGHT, true); +static void ShowEditControl(HWND h, int x, int y, int fontHeight, + bool isMonospace, const std::wstring &s) { + static HFONT hf; + if(hf) DeleteObject(hf); + hf = CreateFontW(-fontHeight, 0, 0, 0, + FW_REGULAR, false, false, false, ANSI_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial"); + if(hf) SendMessage(h, WM_SETFONT, (WPARAM)hf, false); + else SendMessage(h, WM_SETFONT, (WPARAM)(HFONT)GetStockObject(SYSTEM_FONT), false); + SendMessage(h, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, 0); + + TEXTMETRICW tm; + HDC hdc = GetDC(h); + SelectObject(hdc, hf); + GetTextMetrics(hdc, &tm); + ReleaseDC(h, hdc); + y -= tm.tmAscent; /* y coordinate denotes baseline */ + + RECT rc = { x, y, x + tm.tmAveCharWidth * 30, y + tm.tmHeight }; + AdjustWindowRectEx(&rc, 0, false, WS_EX_CLIENTEDGE); + + MoveWindow(h, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true); ShowWindow(h, SW_SHOW); if(!s.empty()) { SendMessage(h, WM_SETTEXT, 0, (LPARAM)s.c_str()); @@ -816,7 +833,8 @@ void SolveSpace::ShowTextEditControl(int x, int y, const std::string &str) { if(GraphicsEditControlIsVisible()) return; - ShowEditControl(TextEditControl, x, y, Widen(str)); + ShowEditControl(TextEditControl, x, y, TextWindow::CHAR_HEIGHT, + /*isMonospace=*/true, Widen(str)); } void SolveSpace::HideTextEditControl(void) { @@ -826,7 +844,8 @@ bool SolveSpace::TextEditControlIsVisible(void) { return IsWindowVisible(TextEditControl) ? true : false; } -void SolveSpace::ShowGraphicsEditControl(int x, int y, const std::string &str) +void SolveSpace::ShowGraphicsEditControl(int x, int y, int fontHeight, + const std::string &str) { if(GraphicsEditControlIsVisible()) return; @@ -835,11 +854,8 @@ void SolveSpace::ShowGraphicsEditControl(int x, int y, const std::string &str) x = x + (r.right - r.left)/2; y = (r.bottom - r.top)/2 - y; - // (x, y) are the bottom left, but the edit control is placed by its - // top left corner - y -= 20; - - ShowEditControl(GraphicsEditControl, x, y, Widen(str)); + ShowEditControl(GraphicsEditControl, x, y, fontHeight, + /*isMonospace=*/false, Widen(str)); } void SolveSpace::HideGraphicsEditControl(void) { @@ -1250,7 +1266,6 @@ static void CreateMainWindows(void) GraphicsEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, 50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL); - SendMessage(GraphicsEditControl, WM_SETFONT, (WPARAM)FixedFont, true); // The text window, with a comand line and some textual information // about the sketch. @@ -1277,7 +1292,6 @@ static void CreateMainWindows(void) TextEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, 50, 50, 100, 21, TextWnd, NULL, Instance, NULL); - SendMessage(TextEditControl, WM_SETFONT, (WPARAM)FixedFont, true); // Now that all our windows exist, set up gl contexts. CreateGlContext(TextWnd, &TextGl);