From 17151cddcd25ab089f5d2ffcc9ec680ba6cd57bf Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 14 Feb 2017 14:16:37 -0300 Subject: [PATCH] Move mnemonic key as a property of ui::Widget In this was we can process the text string just one time to remove the character preceded by '&' that will be finally acts as a mnemonic. This simplifies the rendering and text measure code too. --- src/app/app_menus.cpp | 10 ++-- src/app/commands/cmd_keyboard_shortcuts.cpp | 15 +++--- src/app/ui/button_set.cpp | 5 +- src/app/ui/color_button.cpp | 2 +- src/app/ui/skin/skin_theme.cpp | 52 ++++++++++-------- src/app/ui/skin/skin_theme.h | 2 +- src/app/widget_loader.cpp | 6 ++- src/ui/alert.cpp | 3 +- src/ui/button.cpp | 2 +- src/ui/graphics.cpp | 59 +++++++++------------ src/ui/graphics.h | 10 ++-- src/ui/menu.cpp | 4 +- src/ui/theme.cpp | 3 +- src/ui/widget.cpp | 44 ++++++++++----- src/ui/widget.h | 16 ++++-- 15 files changed, 131 insertions(+), 102 deletions(-) diff --git a/src/app/app_menus.cpp b/src/app/app_menus.cpp index f9a733857..ffbb14444 100644 --- a/src/app/app_menus.cpp +++ b/src/app/app_menus.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -231,12 +231,14 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem) // Create the item AppMenuItem* menuitem = new AppMenuItem(elem->Attribute("text"), command, params); if (!menuitem) - return NULL; + return nullptr; - /* has it a ID? */ + menuitem->processMnemonicFromText(); + + // Has it a ID? const char* id = elem->Attribute("id"); if (id) { - /* recent list menu */ + // Recent list menu if (strcmp(id, "recent_list") == 0) { m_recentListMenuitem = menuitem; } diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp index 59fe1ef8d..523aa0897 100644 --- a/src/app/commands/cmd_keyboard_shortcuts.cpp +++ b/src/app/commands/cmd_keyboard_shortcuts.cpp @@ -214,7 +214,7 @@ private: g->drawUIText(text(), fg, bg, gfx::Point( bounds.x + m_level*16 * guiscale(), - bounds.y + 2*guiscale())); + bounds.y + 2*guiscale()), 0); if (m_key && !m_key->accels().empty()) { std::string buf; @@ -292,12 +292,13 @@ private: const char* label = "x"; m_deleteButton->setBgColor(gfx::ColorNone); - m_deleteButton->setBounds(gfx::Rect( - itemBounds.x + itemBounds.w + 2*guiscale(), - itemBounds.y, - Graphics::measureUITextLength( - label, font()) + 4*guiscale(), - itemBounds.h)); + m_deleteButton->setBounds( + gfx::Rect( + itemBounds.x + itemBounds.w + 2*guiscale(), + itemBounds.y, + Graphics::measureUITextLength( + label, font()) + 4*guiscale(), + itemBounds.h)); m_deleteButton->setText(label); invalidate(); diff --git a/src/app/ui/button_set.cpp b/src/app/ui/button_set.cpp index cf83ed4c6..03b537bd1 100644 --- a/src/app/ui/button_set.cpp +++ b/src/app/ui/button_set.cpp @@ -138,8 +138,7 @@ void ButtonSet::Item::onPaint(ui::PaintEvent& ev) if (hasText()) { g->setFont(font()); - g->drawUIText(text(), fg, gfx::ColorNone, textRc.origin(), - false); + g->drawUIText(text(), fg, gfx::ColorNone, textRc.origin(), 0); } } @@ -159,7 +158,7 @@ bool ButtonSet::Item::onProcessMessage(ui::Message* msg) if (isEnabled() && hasText()) { KeyMessage* keymsg = static_cast(msg); bool mnemonicPressed = (msg->altPressed() && - mnemonicCharPressed(keymsg)); + isMnemonicPressed(keymsg)); if (mnemonicPressed || (hasFocus() && keymsg->scancode() == kKeySpace)) { diff --git a/src/app/ui/color_button.cpp b/src/app/ui/color_button.cpp index 9080c390e..7a74de708 100644 --- a/src/app/ui/color_button.cpp +++ b/src/app/ui/color_button.cpp @@ -230,7 +230,7 @@ void ColorButton::onPaint(PaintEvent& ev) gfx::Rect textrc; getTextIconInfo(NULL, &textrc); - g->drawUIText(text(), textcolor, gfx::ColorNone, textrc.origin()); + g->drawUIText(text(), textcolor, gfx::ColorNone, textrc.origin(), 0); } void ColorButton::onClick(Event& ev) diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 5c44d4c6b..8907c83e9 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -947,7 +947,8 @@ void SkinTheme::paintCheckBox(PaintEvent& ev) } // Text - drawText(g, NULL, ColorNone, ColorNone, widget, text, 0); + drawText(g, nullptr, ColorNone, ColorNone, widget, text, 0, + widget->mnemonic()); // Paint the icon if (iconInterface) @@ -1136,7 +1137,7 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget) drawText( g, widget->getSuffix().c_str(), colors.entrySuffix(), ColorNone, - widget, sufBounds, 0); + widget, sufBounds, 0, 0); } } @@ -1227,7 +1228,7 @@ void SkinTheme::paintListItem(ui::PaintEvent& ev) if (widget->hasText()) { bounds.shrink(widget->border()); - drawText(g, NULL, fg, bg, widget, bounds, 0); + drawText(g, nullptr, fg, bg, widget, bounds, 0, 0); } } @@ -1298,7 +1299,8 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev) Rect pos = bounds; if (!bar) pos.offset(widget->childSpacing()/2, 0); - drawText(g, NULL, fg, ColorNone, widget, pos, 0); + drawText(g, nullptr, fg, ColorNone, widget, pos, 0, + widget->mnemonic()); // For menu-box if (!bar) { @@ -1335,7 +1337,7 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev) std::string buf = appMenuItem->key()->accels().front().toString(); widget->setAlign(RIGHT | MIDDLE); - drawText(g, buf.c_str(), fg, ColorNone, widget, pos, 0); + drawText(g, buf.c_str(), fg, ColorNone, widget, pos, 0, 0); widget->setAlign(old_align); } } @@ -1375,7 +1377,7 @@ void SkinTheme::paintRadioButton(PaintEvent& ev) } // Text - drawText(g, NULL, ColorNone, ColorNone, widget, text, 0); + drawText(g, nullptr, ColorNone, ColorNone, widget, text, 0, widget->mnemonic()); // Icon if (iconInterface) @@ -1417,9 +1419,9 @@ void SkinTheme::paintSeparator(ui::PaintEvent& ev) bounds.y + bounds.h/2 - h/2, widget->textWidth(), h); - drawText(g, NULL, - colors.separatorLabel(), BGCOLOR, - widget, r, 0); + drawText(g, nullptr, + colors.separatorLabel(), BGCOLOR, + widget, r, 0, widget->mnemonic()); } } @@ -1522,18 +1524,18 @@ void SkinTheme::paintSlider(PaintEvent& ev) { IntersectClip clip(g, Rect(rc.x, rc.y, x-rc.x, rc.h)); if (clip) { - drawText(g, NULL, - colors.sliderFullText(), ColorNone, - widget, rc, 0); + drawText(g, nullptr, + colors.sliderFullText(), ColorNone, + widget, rc, 0, widget->mnemonic()); } } { IntersectClip clip(g, Rect(x+1, rc.y, rc.w-(x-rc.x+1), rc.h)); if (clip) { - drawText(g, NULL, - colors.sliderEmptyText(), - ColorNone, widget, rc, 0); + drawText(g, nullptr, + colors.sliderEmptyText(), + ColorNone, widget, rc, 0, widget->mnemonic()); } } @@ -1816,7 +1818,7 @@ gfx::Color SkinTheme::getWidgetBgColor(Widget* widget) void SkinTheme::drawText(Graphics* g, const char *t, gfx::Color fg_color, gfx::Color bg_color, Widget* widget, const Rect& rc, - int selected_offset) + int selected_offset, int mnemonic) { if (t || widget->hasText()) { Rect textrc; @@ -1869,18 +1871,22 @@ void SkinTheme::drawText(Graphics* g, const char *t, gfx::Color fg_color, gfx::C if (clip) { if (!widget->isEnabled()) { // Draw white part - g->drawUIText(t, + g->drawUIText( + t, colors.background(), gfx::ColorNone, - textrc.origin() + Point(guiscale(), guiscale())); + textrc.origin() + Point(guiscale(), guiscale()), + mnemonic); } - g->drawUIText(t, + g->drawUIText( + t, (!widget->isEnabled() ? - colors.disabled(): - (gfx::geta(fg_color) > 0 ? fg_color : - colors.text())), - bg_color, textrc.origin()); + colors.disabled(): + (gfx::geta(fg_color) > 0 ? fg_color : + colors.text())), + bg_color, textrc.origin(), + mnemonic); } } } diff --git a/src/app/ui/skin/skin_theme.h b/src/app/ui/skin/skin_theme.h index 0547958be..b576f803d 100644 --- a/src/app/ui/skin/skin_theme.h +++ b/src/app/ui/skin/skin_theme.h @@ -135,7 +135,7 @@ namespace app { gfx::Color getWidgetBgColor(ui::Widget* widget); void drawText(ui::Graphics* g, const char *t, gfx::Color fg_color, gfx::Color bg_color, ui::Widget* widget, const gfx::Rect& rc, - int selected_offset); + int selected_offset, int mnemonic); void drawEntryText(ui::Graphics* g, ui::Entry* widget); void paintIcon(ui::Widget* widget, ui::Graphics* g, ui::IButtonIcon* iconInterface, int x, int y); diff --git a/src/app/widget_loader.cpp b/src/app/widget_loader.cpp index 5b472676b..293a3eb08 100644 --- a/src/app/widget_loader.cpp +++ b/src/app/widget_loader.cpp @@ -475,8 +475,9 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget } // Was the widget created? - if (widget) + if (widget) { fillWidgetWithXmlElementAttributesWithChildren(elem, root, widget); + } return widget; } @@ -587,6 +588,9 @@ void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, } } } + + // Assign widget mnemonic from the character preceded by a '&' + widget->processMnemonicFromText(); } void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget) diff --git a/src/ui/alert.cpp b/src/ui/alert.cpp index 29380ee51..9f630dd93 100644 --- a/src/ui/alert.cpp +++ b/src/ui/alert.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -158,6 +158,7 @@ void Alert::processString(std::string& buf) else if (button) { char buttonId[256]; Button* button_widget = new Button(item); + button_widget->processMnemonicFromText(); button_widget->setMinSize(gfx::Size(60*guiscale(), 0)); m_buttons.push_back(button_widget); diff --git a/src/ui/button.cpp b/src/ui/button.cpp index 122f5e3a0..a75a0a767 100644 --- a/src/ui/button.cpp +++ b/src/ui/button.cpp @@ -101,7 +101,7 @@ bool ButtonBase::onProcessMessage(Message* msg) if (isEnabled()) { bool mnemonicPressed = ((msg->altPressed() || msg->cmdPressed()) && - mnemonicCharPressed(keymsg)); + isMnemonicPressed(keymsg)); // For kButtonWidget if (m_behaviorType == kButtonWidget) { diff --git a/src/ui/graphics.cpp b/src/ui/graphics.cpp index b4bec2aa9..a0b4b7e15 100644 --- a/src/ui/graphics.cpp +++ b/src/ui/graphics.cpp @@ -237,11 +237,11 @@ namespace { class DrawUITextDelegate : public she::DrawTextDelegate { public: DrawUITextDelegate(she::Surface* surface, - she::Font* font, const bool drawUnderscore) + she::Font* font, const int mnemonic) : m_surface(surface) , m_font(font) - , m_drawUnderscore(drawUnderscore) - , m_underscoreNext(false) { + , m_mnemonic(std::tolower(mnemonic)) + , m_underscoreColor(gfx::ColorNone) { } gfx::Rect bounds() const { return m_bounds; } @@ -253,19 +253,15 @@ public: gfx::Color& bg, bool& drawChar, bool& moveCaret) override { - if (m_underscoreNext) - m_underscoreColor = fg; - if (!m_surface) drawChar = false; - - moveCaret = true; - if (chr == '&') { // TODO change this with other character, maybe '_' or configurable - auto it2 = it; - ++it2; - if (it2 != end && *it2 != '&') { - m_underscoreNext = true; - moveCaret = false; + else { + if (m_mnemonic && std::tolower(chr) == m_mnemonic) { + m_underscoreColor = fg; + m_mnemonic = 0; // Just one time + } + else { + m_underscoreColor = gfx::ColorNone; } } } @@ -276,40 +272,36 @@ public: } void postDrawChar(const gfx::Rect& charBounds) override { - if (m_underscoreNext) { - m_underscoreNext = false; - if (m_drawUnderscore) { - // TODO underscore height = guiscale() should be configurable from ui::Theme - int dy = 0; - if (m_font->type() == she::FontType::kTrueType) // TODO use other method to locate the underline - dy += guiscale(); - gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy, - charBounds.w, guiscale()); - m_surface->fillRect(m_underscoreColor, underscoreBounds); - m_bounds |= underscoreBounds; - } + if (!gfx::is_transparent(m_underscoreColor)) { + // TODO underscore height = guiscale() should be configurable from ui::Theme + int dy = 0; + if (m_font->type() == she::FontType::kTrueType) // TODO use other method to locate the underline + dy += guiscale(); + gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy, + charBounds.w, guiscale()); + m_surface->fillRect(m_underscoreColor, underscoreBounds); + m_bounds |= underscoreBounds; } } private: she::Surface* m_surface; she::Font* m_font; - bool m_drawUnderscore; - bool m_underscoreNext; + int m_mnemonic; gfx::Color m_underscoreColor; gfx::Rect m_bounds; }; } -void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, - bool drawUnderscore) +void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, + const gfx::Point& pt, const int mnemonic) { she::SurfaceLock lock(m_surface); int x = m_dx+pt.x; int y = m_dy+pt.y; - DrawUITextDelegate delegate(m_surface, m_font, drawUnderscore); + DrawUITextDelegate delegate(m_surface, m_font, mnemonic); she::draw_text(m_surface, m_font, base::utf8_const_iterator(str.begin()), base::utf8_const_iterator(str.end()), @@ -318,7 +310,8 @@ void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, dirty(delegate.bounds()); } -void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, int align) +void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, + const gfx::Rect& rc, const int align) { doUIStringAlgorithm(str, fg, bg, rc, align, true); } @@ -333,7 +326,7 @@ gfx::Size Graphics::measureUIText(const std::string& str) // static int Graphics::measureUITextLength(const std::string& str, she::Font* font) { - DrawUITextDelegate delegate(nullptr, font, false); + DrawUITextDelegate delegate(nullptr, font, 0); she::draw_text(nullptr, font, base::utf8_const_iterator(str.begin()), base::utf8_const_iterator(str.end()), diff --git a/src/ui/graphics.h b/src/ui/graphics.h index b2f054d8c..c6397552c 100644 --- a/src/ui/graphics.h +++ b/src/ui/graphics.h @@ -86,13 +86,9 @@ namespace ui { const base::utf8_const_iterator& end, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, she::DrawTextDelegate* delegate); - - void drawText(const std::string& str, gfx::Color fg, gfx::Color bg, - const gfx::Point& pt); - void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, - const gfx::Point& pt, bool drawUnderscore = true); - void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, - const gfx::Rect& rc, int align); + void drawText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt); + void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, const int mnemonic); + void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, const int align); gfx::Size measureUIText(const std::string& str); static int measureUITextLength(const std::string& str, she::Font* font); diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp index 9bb233040..2e446338d 100644 --- a/src/ui/menu.cpp +++ b/src/ui/menu.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -1218,7 +1218,7 @@ static MenuItem* check_for_letter(Menu* menu, const KeyMessage* keymsg) continue; MenuItem* menuitem = static_cast(child); - if (menuitem->mnemonicCharPressed(keymsg)) + if (menuitem->isMnemonicPressed(keymsg)) return menuitem; } return NULL; diff --git a/src/ui/theme.cpp b/src/ui/theme.cpp index d19dafcb1..53260fdcb 100644 --- a/src/ui/theme.cpp +++ b/src/ui/theme.cpp @@ -152,7 +152,8 @@ void Theme::paintLayer(Graphics* g, Widget* widget, layer.color(), gfx::ColorNone, gfx::Point(rc.x+rc.w/2-textSize.w/2, - rc.y+rc.h/2-textSize.h/2), true); + rc.y+rc.h/2-textSize.h/2), + widget->mnemonic()); } break; diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 000e992d9..afb5c408f 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -70,6 +70,7 @@ Widget::Widget(WidgetType type) , m_bounds(0, 0, 0, 0) , m_parent(nullptr) , m_sizeHint(nullptr) + , m_mnemonic(0) , m_minSize(0, 0) , m_maxSize(INT_MAX, INT_MAX) , m_childSpacing(0) @@ -1260,12 +1261,12 @@ bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type) return false; } -bool Widget::hasFocus() +bool Widget::hasFocus() const { return hasFlags(HAS_FOCUS); } -bool Widget::hasMouse() +bool Widget::hasMouse() const { return hasFlags(HAS_MOUSE); } @@ -1275,24 +1276,43 @@ bool Widget::hasMouseOver() return (this == pick(get_mouse_position())); } -bool Widget::hasCapture() +bool Widget::hasCapture() const { return hasFlags(HAS_CAPTURE); } -int Widget::mnemonicChar() const +void Widget::setMnemonic(int mnemonic) { - if (hasText()) { - for (int c=0; m_text[c]; ++c) - if ((m_text[c] == '&') && (m_text[c+1] != '&')) - return std::tolower(m_text[c+1]); - } - return 0; + m_mnemonic = mnemonic; } -bool Widget::mnemonicCharPressed(const KeyMessage* keyMsg) const +void Widget::processMnemonicFromText(int escapeChar) { - int chr = mnemonicChar(); + std::string newText; + if (!m_text.empty()) + newText.reserve(m_text.size()); + + for (base::utf8_const_iterator + it(m_text.begin()), + end(m_text.end()); it != end; ++it) { + if (*it == escapeChar) { + ++it; + if (it == end) { + break; // Ill-formed string (it ends with escape character) + } + else if (*it != escapeChar) { + setMnemonic(*it); + } + } + newText.push_back(*it); + } + + setText(newText); +} + +bool Widget::isMnemonicPressed(const KeyMessage* keyMsg) const +{ + int chr = std::tolower(mnemonic()); return ((chr) && ((chr == std::tolower(keyMsg->unicodeChar())) || diff --git a/src/ui/widget.h b/src/ui/widget.h index 605572a0a..6d24678a6 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -339,10 +339,10 @@ namespace ui { void captureMouse(); void releaseMouse(); - bool hasFocus(); - bool hasMouse(); + bool hasFocus() const; + bool hasMouse() const; bool hasMouseOver(); - bool hasCapture(); + bool hasCapture() const; // Offer the capture to widgets of the given type. Returns true if // the capture was passed to other widget. @@ -350,10 +350,15 @@ namespace ui { // Returns lower-case letter that represet the mnemonic of the widget // (the underscored character, i.e. the letter after & symbol). - int mnemonicChar() const; + int mnemonic() const { return m_mnemonic; } + void setMnemonic(int mnemonic); + + // Assigns mnemonic from the character preceded by the given + // escapeChar ('&' by default). + void processMnemonicFromText(int escapeChar = '&'); // Returns true if the mnemonic character is pressed. - bool mnemonicCharPressed(const ui::KeyMessage* keyMsg) const; + bool isMnemonicPressed(const ui::KeyMessage* keyMsg) const; protected: // =============================================================== @@ -399,6 +404,7 @@ namespace ui { WidgetsList m_children; // Sub-widgets Widget* m_parent; // Who is the parent? gfx::Size* m_sizeHint; + int m_mnemonic; // Keyboard shortcut to access this widget like Alt+mnemonic // Widget size limits gfx::Size m_minSize, m_maxSize;