[aseprite] 28/308: Add search field in keyboard shortcuts dialog (fix #849)

Tobias Hansen thansen at moszumanska.debian.org
Tue Mar 8 02:44:48 UTC 2016


This is an automated email from the git hooks/post-receive script.

thansen pushed a commit to branch master
in repository aseprite.

commit 771a7ba467bd8f111c6bb58f63f1c369913ddeb0
Author: David Capello <davidcapello at gmail.com>
Date:   Mon Nov 30 15:08:18 2015 -0300

    Add search field in keyboard shortcuts dialog (fix #849)
    
    Changes:
    * Added "icon_search" part in the skin
    * Added app::SearchEntry widget
    * Fixed Separator widget to handle a custom background color, because
      now we use Separators inside a ListBox too
    * Added Entry::(on)getEntryTextBounds() to specify a customized area
      to show text (as SearchEntry needs space for search and close icons)
---
 data/skins/default/sheet.png                | Bin 14046 -> 14029 bytes
 data/skins/default/skin.xml                 |   3 +-
 data/widgets/keyboard_shortcuts.xml         |   4 ++
 src/app/CMakeLists.txt                      |   1 +
 src/app/commands/cmd_keyboard_shortcuts.cpp |  83 ++++++++++++++++++++-
 src/app/ui/search_entry.cpp                 | 107 ++++++++++++++++++++++++++++
 src/app/ui/search_entry.h                   |  31 ++++++++
 src/app/ui/skin/skin_theme.cpp              |  46 ++++++------
 src/app/widget_loader.cpp                   |   5 ++
 src/gen/ui_class.cpp                        |   1 +
 src/ui/entry.cpp                            |  34 ++++++---
 src/ui/entry.h                              |   2 +
 src/ui/separator.cpp                        |   4 +-
 13 files changed, 281 insertions(+), 40 deletions(-)

diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png
index 9ce7e63..b5da4d7 100644
Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ
diff --git a/data/skins/default/skin.xml b/data/skins/default/skin.xml
index 1e8fede..63a5576 100644
--- a/data/skins/default/skin.xml
+++ b/data/skins/default/skin.xml
@@ -407,7 +407,8 @@
     <part id="horizontal_symmetry"              x="160" y="240" w="13" h="13" />
     <part id="vertical_symmetry"                x="176" y="240" w="13" h="13" />
     <part id="icon_arrow_down"                  x="144" y="256" w="7" h="4" />
-    <part id="icon_close"                       x="153" y="256" w="7" h="7" />
+    <part id="icon_close"                       x="152" y="256" w="7" h="7" />
+    <part id="icon_search"                      x="160" y="256" w="8" h="8" />
   </parts>
 
   <stylesheet>
diff --git a/data/widgets/keyboard_shortcuts.xml b/data/widgets/keyboard_shortcuts.xml
index 1471a84..7dac927 100644
--- a/data/widgets/keyboard_shortcuts.xml
+++ b/data/widgets/keyboard_shortcuts.xml
@@ -5,6 +5,7 @@
     <vbox>
       <hbox expansive="true">
         <vbox>
+          <search id="search" magnet="true" />
           <view width="80" expansive="true">
             <listbox id="section" expansive="true" />
           </view>
@@ -14,6 +15,9 @@
           <button text="&Reset" id="reset_button" />
         </vbox>
         <vbox expansive="true">
+          <view id="search_view" expansive="true">
+            <listbox id="search_list" />
+          </view>
           <view id="menus_view" expansive="true">
             <listbox id="menus" />
           </view>
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index e6c23e2..a4d14f8 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -381,6 +381,7 @@ add_library(app-lib
   ui/preview_editor.cpp
   ui/recent_listbox.cpp
   ui/resources_listbox.cpp
+  ui/search_entry.cpp
   ui/select_accelerator.cpp
   ui/skin/button_icon_impl.cpp
   ui/skin/skin_part.cpp
diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp
index a62c6fe..3cc48b2 100644
--- a/src/app/commands/cmd_keyboard_shortcuts.cpp
+++ b/src/app/commands/cmd_keyboard_shortcuts.cpp
@@ -18,16 +18,21 @@
 #include "app/tools/tool.h"
 #include "app/ui/app_menuitem.h"
 #include "app/ui/keyboard_shortcuts.h"
+#include "app/ui/search_entry.h"
 #include "app/ui/select_accelerator.h"
 #include "app/ui/skin/skin_theme.h"
 #include "base/bind.h"
 #include "base/fs.h"
 #include "base/path.h"
+#include "base/scoped_value.h"
+#include "base/split_string.h"
+#include "base/string.h"
 #include "ui/graphics.h"
 #include "ui/listitem.h"
 #include "ui/paint_event.h"
 #include "ui/preferred_size_event.h"
 #include "ui/resize_event.h"
+#include "ui/separator.h"
 
 #include "keyboard_shortcuts.xml.h"
 
@@ -55,6 +60,8 @@ public:
     setBorder(border);
   }
 
+  Key* key() { return m_key; }
+
   void restoreKeys() {
     if (m_key && m_keyOrig)
       *m_key = *m_keyOrig;
@@ -280,7 +287,7 @@ private:
 
 class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {
 public:
-  KeyboardShortcutsWindow() {
+  KeyboardShortcutsWindow() : m_searchChange(false) {
     setAutoRemap(false);
 
     section()->addChild(new ListItem("Menus"));
@@ -288,6 +295,7 @@ public:
     section()->addChild(new ListItem("Tools"));
     section()->addChild(new ListItem("Action Modifiers"));
 
+    search()->Change.connect(Bind<void>(&KeyboardShortcutsWindow::onSearchChange, this));
     section()->Change.connect(Bind<void>(&KeyboardShortcutsWindow::onSectionChange, this));
     importButton()->Click.connect(Bind<void>(&KeyboardShortcutsWindow::onImport, this));
     exportButton()->Click.connect(Bind<void>(&KeyboardShortcutsWindow::onExport, this));
@@ -304,6 +312,8 @@ public:
 
 private:
   void deleteAllKeyItems() {
+    while (searchList()->getLastChild())
+      searchList()->removeChild(searchList()->getLastChild());
     while (menus()->getLastChild())
       menus()->removeChild(menus()->getLastChild());
     while (commands()->getLastChild())
@@ -375,12 +385,78 @@ private:
     this->actions()->sortItems();
 
     section()->selectIndex(0);
-    onSectionChange();
+    updateViews();
+  }
+
+  void fillSearchList(const std::string& search) {
+    while (searchList()->getLastChild())
+      searchList()->removeChild(searchList()->getLastChild());
+
+    std::vector<std::string> parts;
+    base::split_string(base::string_to_lower(search), parts, " ");
+
+    ListBox* listBoxes[] = { commands(), tools(), actions() };
+    int sectionIdx = 1;         // index 0 is menus, index 1 is commands
+    for (auto listBox : listBoxes) {
+      Separator* group = nullptr;
+
+      for (auto item : listBox->getChildren()) {
+        if (KeyItem* keyItem = dynamic_cast<KeyItem*>(item)) {
+          std::string itemText =
+            base::string_to_lower(keyItem->getText());
+          int matches = 0;
+
+          for (const auto& part : parts) {
+            if (itemText.find(part) != std::string::npos)
+              ++matches;
+          }
+
+          if (matches == int(parts.size())) {
+            if (!group) {
+              group = new Separator(
+                section()->getChildren()[sectionIdx]->getText(), HORIZONTAL);
+              group->setBgColor(SkinTheme::instance()->colors.background());
+
+              searchList()->addChild(group);
+            }
+
+            KeyItem* copyItem =
+              new KeyItem(keyItem->getText(),
+                          keyItem->key(), nullptr, 0);
+            searchList()->addChild(copyItem);
+          }
+        }
+      }
+
+      ++sectionIdx;
+    }
+  }
+
+  void onSearchChange() {
+    base::ScopedValue<bool> flag(m_searchChange, true, false);
+    std::string searchText = search()->getText();
+
+    if (searchText.empty())
+      section()->selectIndex(0);
+    else {
+      fillSearchList(searchText);
+      section()->selectChild(nullptr);
+    }
+
+    updateViews();
   }
 
   void onSectionChange() {
-    int section = this->section()->getSelectedIndex();
+    if (m_searchChange)
+      return;
 
+    search()->setText("");
+    updateViews();
+  }
+
+  void updateViews() {
+    int section = this->section()->getSelectedIndex();
+    searchView()->setVisible(section < 0);
     menusView()->setVisible(section == 0);
     commandsView()->setVisible(section == 1);
     toolsView()->setVisible(section == 2);
@@ -438,6 +514,7 @@ private:
   }
 
   std::vector<KeyItem*> m_allKeyItems;
+  bool m_searchChange;
 };
 
 class KeyboardShortcutsCommand : public Command {
diff --git a/src/app/ui/search_entry.cpp b/src/app/ui/search_entry.cpp
new file mode 100644
index 0000000..a7bb56c
--- /dev/null
+++ b/src/app/ui/search_entry.cpp
@@ -0,0 +1,107 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/ui/search_entry.h"
+
+#include "app/ui/skin/skin_theme.h"
+#include "she/surface.h"
+#include "ui/graphics.h"
+#include "ui/message.h"
+#include "ui/paint_event.h"
+#include "ui/preferred_size_event.h"
+
+namespace app {
+
+using namespace app::skin;
+using namespace gfx;
+using namespace ui;
+
+SearchEntry::SearchEntry()
+  : Entry(256, "")
+{
+}
+
+bool SearchEntry::onProcessMessage(ui::Message* msg)
+{
+  switch (msg->type()) {
+    case kMouseDownMessage: {
+      Rect closeBounds = getCloseIconBounds();
+      Point mousePos = static_cast<MouseMessage*>(msg)->position()
+        - getBounds().getOrigin();
+
+      if (closeBounds.contains(mousePos)) {
+        setText("");
+        onChange();
+        return true;
+      }
+      break;
+    }
+  }
+  return Entry::onProcessMessage(msg);
+}
+
+void SearchEntry::onPaint(ui::PaintEvent& ev)
+{
+  SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
+  theme->paintEntry(ev);
+
+  auto icon = theme->parts.iconSearch()->getBitmap(0);
+  Rect bounds = getClientBounds();
+  ev.getGraphics()->drawColoredRgbaSurface(
+    icon, theme->colors.text(),
+    bounds.x + border().left(),
+    bounds.y + bounds.h/2 - icon->height()/2);
+
+  if (!getText().empty()) {
+    icon = theme->parts.iconClose()->getBitmap(0);
+    ev.getGraphics()->drawColoredRgbaSurface(
+      icon, theme->colors.text(),
+      bounds.x + bounds.w - border().right() - childSpacing() - icon->width(),
+      bounds.y + bounds.h/2 - icon->height()/2);
+  }
+}
+
+void SearchEntry::onPreferredSize(PreferredSizeEvent& ev)
+{
+  Entry::onPreferredSize(ev);
+  Size sz = ev.getPreferredSize();
+
+  SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
+  auto icon = theme->parts.iconSearch()->getBitmap(0);
+  sz.h = MAX(sz.h, icon->height()+border().height());
+
+  ev.setPreferredSize(sz);
+}
+
+Rect SearchEntry::onGetEntryTextBounds() const
+{
+  SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
+  Rect bounds = Entry::onGetEntryTextBounds();
+  auto icon1 = theme->parts.iconSearch()->getBitmap(0);
+  auto icon2 = theme->parts.iconClose()->getBitmap(0);
+  bounds.x += childSpacing() + icon1->width();
+  bounds.w -= 2*childSpacing() + icon1->width() + icon2->width();
+  return bounds;
+}
+
+Rect SearchEntry::getCloseIconBounds() const
+{
+  SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
+  Rect bounds = getClientBounds();
+  auto icon = theme->parts.iconClose()->getBitmap(0);
+  bounds.x += bounds.w - border().right() - childSpacing() - icon->width();
+  bounds.y += bounds.h/2 - icon->height()/2;
+  bounds.w = icon->width();
+  bounds.h = icon->height();
+  return bounds;
+}
+
+} // namespace app
diff --git a/src/app/ui/search_entry.h b/src/app/ui/search_entry.h
new file mode 100644
index 0000000..dcdccfc
--- /dev/null
+++ b/src/app/ui/search_entry.h
@@ -0,0 +1,31 @@
+// Aseprite
+// Copyright (C) 2001-2015  David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifndef APP_UI_SEARCH_ENTRY_H_INCLUDED
+#define APP_UI_SEARCH_ENTRY_H_INCLUDED
+#pragma once
+
+#include "ui/entry.h"
+
+namespace app {
+
+  class SearchEntry : public ui::Entry {
+  public:
+    SearchEntry();
+
+  private:
+    bool onProcessMessage(ui::Message* msg) override;
+    void onPaint(ui::PaintEvent& ev) override;
+    void onPreferredSize(ui::PreferredSizeEvent& ev) override;
+    gfx::Rect onGetEntryTextBounds() const override;
+
+    gfx::Rect getCloseIconBounds() const;
+  };
+
+} // namespace app
+
+#endif
diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp
index 7481711..59fcda3 100644
--- a/src/app/ui/skin/skin_theme.cpp
+++ b/src/app/ui/skin/skin_theme.cpp
@@ -568,6 +568,7 @@ void SkinTheme::initWidget(Widget* widget)
         parts.sunkenNormal()->getBitmapN()->height(),
         parts.sunkenNormal()->getBitmapE()->width(),
         parts.sunkenNormal()->getBitmapS()->height());
+      widget->setChildSpacing(3 * scale);
       break;
 
     case kGridWidget:
@@ -650,17 +651,6 @@ void SkinTheme::initWidget(Widget* widget)
       else {
         BORDER4(4 * scale, 2 * scale, 1 * scale, 2 * scale);
       }
-
-      if (widget->hasText()) {
-        gfx::Border border = widget->border();
-
-        if (widget->getAlign() & TOP)
-          border.top(widget->getTextHeight());
-        else if (widget->getAlign() & BOTTOM)
-          border.bottom(widget->getTextHeight());
-
-        widget->setBorder(border);
-      }
       break;
 
     case kSliderWidget:
@@ -923,8 +913,9 @@ void SkinTheme::paintEntry(PaintEvent& ev)
     bg);
 
   // Draw the text
-  x = bounds.x + widget->border().left();
-  y = bounds.y + bounds.h/2 - widget->getTextHeight()/2;
+  bounds = widget->getEntryTextBounds();
+  x = bounds.x;
+  y = bounds.y;
 
   base::utf8_const_iterator utf8_it = base::utf8_const_iterator(textString.begin());
   int textlen = base::utf8_length(textString);
@@ -954,7 +945,7 @@ void SkinTheme::paintEntry(PaintEvent& ev)
     }
 
     w = g->measureChar(ch).w;
-    if (x+w > bounds.x2()-3)
+    if (x+w > bounds.x2()-widget->childSpacing()*guiscale())
       return;
 
     caret_x = x;
@@ -969,7 +960,7 @@ void SkinTheme::paintEntry(PaintEvent& ev)
   // Draw suffix if there is enough space
   if (!widget->getSuffix().empty()) {
     Rect sufBounds(x, y,
-                   bounds.x2()-3*guiscale()-x,
+                   bounds.x2()-widget->childSpacing()*guiscale()-x,
                    widget->getTextHeight());
     IntersectClip clip(g, sufBounds);
     if (clip) {
@@ -1228,22 +1219,27 @@ void SkinTheme::paintSeparator(ui::PaintEvent& ev)
   // background
   g->fillRect(BGCOLOR, bounds);
 
-  if (widget->getAlign() & HORIZONTAL)
-    drawHline(g, bounds, parts.separatorHorz().get());
+  if (widget->getAlign() & HORIZONTAL) {
+    int h = parts.separatorHorz()->getBitmap(0)->height();
+    drawHline(g, gfx::Rect(bounds.x, bounds.y+bounds.h/2-h/2,
+                           bounds.w, h),
+              parts.separatorHorz().get());
+  }
 
-  if (widget->getAlign() & VERTICAL)
-    drawVline(g, bounds, parts.separatorVert().get());
+  if (widget->getAlign() & VERTICAL) {
+    int w = parts.separatorVert()->getBitmap(0)->width();
+    drawVline(g, gfx::Rect(bounds.x+bounds.w/2-w/2, bounds.y,
+                           w, bounds.h),
+              parts.separatorVert().get());
+  }
 
   // text
   if (widget->hasText()) {
     int h = widget->getTextHeight();
     Rect r(
-      Point(
-        bounds.x + widget->border().left()/2 + h/2,
-        bounds.y + widget->border().top()/2 - h/2),
-      Point(
-        bounds.x2() - widget->border().right()/2 - h,
-        bounds.y2() - widget->border().bottom()/2 + h));
+      bounds.x + widget->border().left()/2 + h/2,
+      bounds.y + bounds.h/2 - h/2,
+      widget->getTextWidth(), h);
 
     drawTextString(g, NULL,
       colors.separatorLabel(), BGCOLOR,
diff --git a/src/app/widget_loader.cpp b/src/app/widget_loader.cpp
index 836461b..a536c07 100644
--- a/src/app/widget_loader.cpp
+++ b/src/app/widget_loader.cpp
@@ -17,6 +17,7 @@
 #include "app/ui/button_set.h"
 #include "app/ui/color_button.h"
 #include "app/ui/drop_down_button.h"
+#include "app/ui/search_entry.h"
 #include "app/ui/skin/skin_style_property.h"
 #include "app/ui/skin/skin_theme.h"
 #include "app/widget_not_found.h"
@@ -466,6 +467,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
       }
     }
   }
+  else if (elem_name == "search") {
+    if (!widget)
+      widget = new SearchEntry;
+  }
 
   // Was the widget created?
   if (widget)
diff --git a/src/gen/ui_class.cpp b/src/gen/ui_class.cpp
index 1f4f6f1..dcbeb74 100644
--- a/src/gen/ui_class.cpp
+++ b/src/gen/ui_class.cpp
@@ -64,6 +64,7 @@ static std::string convert_type(const std::string& name)
   if (name == "listbox") return "ui::ListBox";
   if (name == "panel") return "ui::Panel";
   if (name == "radio") return "ui::RadioButton";
+  if (name == "search") return "app::SearchEntry";
   if (name == "slider") return "ui::Slider";
   if (name == "splitter") return "ui::Splitter";
   if (name == "vbox") return "ui::VBox";
diff --git a/src/ui/entry.cpp b/src/ui/entry.cpp
index 5b0af39..4839551 100644
--- a/src/ui/entry.cpp
+++ b/src/ui/entry.cpp
@@ -178,6 +178,11 @@ void Entry::getEntryThemeInfo(int* scroll, int* caret, int* state,
   }
 }
 
+gfx::Rect Entry::getEntryTextBounds() const
+{
+  return onGetEntryTextBounds();
+}
+
 bool Entry::onProcessMessage(Message* msg)
 {
   switch (msg->type()) {
@@ -454,27 +459,37 @@ void Entry::onChange()
   Change();
 }
 
+gfx::Rect Entry::onGetEntryTextBounds() const
+{
+  gfx::Rect bounds = getClientBounds();
+  bounds.x += border().left();
+  bounds.y += bounds.h/2 - getTextHeight()/2;
+  bounds.w -= border().width();
+  bounds.h = getTextHeight();
+  return bounds;
+}
+
 int Entry::getCaretFromMouse(MouseMessage* mousemsg)
 {
   base::utf8_const_iterator utf8_begin = base::utf8_const_iterator(getText().begin());
   base::utf8_const_iterator utf8_end = base::utf8_const_iterator(getText().end());
-  int c, x, w, mx, caret = m_caret;
+  int caret = m_caret;
   int textlen = base::utf8_length(getText());
+  gfx::Rect bounds = getEntryTextBounds().offset(getBounds().getOrigin());
 
-  mx = mousemsg->position().x;
-  mx = MID(getBounds().x+border().left(),
-           mx,
-           getBounds().x2()-border().right()-1);
+  int mx = mousemsg->position().x;
+  mx = MID(bounds.x, mx, bounds.x2()-1);
 
-  x = getBounds().x + border().left();
+  int x = bounds.x;
 
   base::utf8_const_iterator utf8_it =
     (m_scroll < textlen ?
       utf8_begin + m_scroll:
       utf8_end);
 
-  for (c=m_scroll; utf8_it != utf8_end; ++c, ++utf8_it) {
-    w = getFont()->charWidth(*utf8_it);
+  int c = m_scroll;
+  for (; utf8_it != utf8_end; ++c, ++utf8_it) {
+    int w = getFont()->charWidth(*utf8_it);
     if (x+w >= getBounds().x2()-border().right())
       break;
     if ((mx >= x) && (mx < x+w)) {
@@ -485,8 +500,7 @@ int Entry::getCaretFromMouse(MouseMessage* mousemsg)
   }
 
   if (utf8_it == utf8_end) {
-    if ((mx >= x) &&
-        (mx <= getBounds().x2()-border().right()-1)) {
+    if ((mx >= x) && (mx < bounds.x2())) {
       caret = c;
     }
   }
diff --git a/src/ui/entry.h b/src/ui/entry.h
index 5cbd8e7..8394fa1 100644
--- a/src/ui/entry.h
+++ b/src/ui/entry.h
@@ -40,6 +40,7 @@ namespace ui {
     // for themes
     void getEntryThemeInfo(int* scroll, int* caret, int* state,
                            int* selbeg, int* selend);
+    gfx::Rect getEntryTextBounds() const;
 
     // Signals
     Signal0<void> Change;
@@ -53,6 +54,7 @@ namespace ui {
 
     // New Events
     virtual void onChange();
+    virtual gfx::Rect onGetEntryTextBounds() const;
 
   private:
     enum class EntryCmd {
diff --git a/src/ui/separator.cpp b/src/ui/separator.cpp
index 476bd9c..3cfc09c 100644
--- a/src/ui/separator.cpp
+++ b/src/ui/separator.cpp
@@ -45,8 +45,10 @@ void Separator::onPreferredSize(PreferredSizeEvent& ev)
     maxSize.h = MAX(maxSize.h, reqSize.h);
   }
 
-  if (hasText())
+  if (hasText()) {
     maxSize.w = MAX(maxSize.w, getTextWidth());
+    maxSize.h = MAX(maxSize.h, getTextHeight());
+  }
 
   int w = maxSize.w + border().width();
   int h = maxSize.h + border().height();

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git



More information about the Pkg-games-commits mailing list