mirror of https://github.com/aseprite/aseprite.git
				
				
				
			
		
			
				
	
	
		
			772 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			772 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
| // Aseprite UI Library
 | |
| // Copyright (C) 2018-2023  Igara Studio S.A.
 | |
| // Copyright (C) 2001-2017  David Capello
 | |
| //
 | |
| // This file is released under the terms of the MIT license.
 | |
| // Read LICENSE.txt for more information.
 | |
| 
 | |
| #ifdef HAVE_CONFIG_H
 | |
|   #include "config.h"
 | |
| #endif
 | |
| 
 | |
| #include "ui/combobox.h"
 | |
| 
 | |
| #include "gfx/size.h"
 | |
| #include "os/font.h"
 | |
| #include "ui/button.h"
 | |
| #include "ui/entry.h"
 | |
| #include "ui/fit_bounds.h"
 | |
| #include "ui/listbox.h"
 | |
| #include "ui/listitem.h"
 | |
| #include "ui/manager.h"
 | |
| #include "ui/message.h"
 | |
| #include "ui/resize_event.h"
 | |
| #include "ui/scale.h"
 | |
| #include "ui/size_hint_event.h"
 | |
| #include "ui/system.h"
 | |
| #include "ui/theme.h"
 | |
| #include "ui/view.h"
 | |
| #include "ui/window.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| namespace ui {
 | |
| 
 | |
| using namespace gfx;
 | |
| 
 | |
| class ComboBoxButton : public Button {
 | |
| public:
 | |
|   ComboBoxButton() : Button("") { setFocusStop(false); }
 | |
| };
 | |
| 
 | |
| class ComboBoxEntry : public Entry {
 | |
| public:
 | |
|   ComboBoxEntry(ComboBox* comboBox) : Entry(256, ""), m_comboBox(comboBox) {}
 | |
| 
 | |
| protected:
 | |
|   bool onProcessMessage(Message* msg) override;
 | |
|   void onPaint(PaintEvent& ev) override;
 | |
|   void onChange() override;
 | |
| 
 | |
| private:
 | |
|   ComboBox* m_comboBox;
 | |
| };
 | |
| 
 | |
| class ComboBoxListBox : public ListBox {
 | |
| public:
 | |
|   ComboBoxListBox(ComboBox* comboBox) : m_comboBox(comboBox)
 | |
|   {
 | |
|     for (auto item : *comboBox) {
 | |
|       if (item->parent())
 | |
|         item->parent()->removeChild(item);
 | |
|       addChild(item);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void clean()
 | |
|   {
 | |
|     // Remove all added items so ~Widget() don't delete them.
 | |
|     removeAllChildren();
 | |
|     selectChild(nullptr);
 | |
|   }
 | |
| 
 | |
| protected:
 | |
|   bool onProcessMessage(Message* msg) override;
 | |
|   void onChange() override;
 | |
| 
 | |
| private:
 | |
|   bool isValidItem(int index) const { return (index >= 0 && index < m_comboBox->getItemCount()); }
 | |
| 
 | |
|   ComboBox* m_comboBox;
 | |
| };
 | |
| 
 | |
| ComboBox::ComboBox()
 | |
|   : Widget(kComboBoxWidget)
 | |
|   , m_entry(new ComboBoxEntry(this))
 | |
|   , m_button(new ComboBoxButton())
 | |
|   , m_window(nullptr)
 | |
|   , m_listbox(nullptr)
 | |
|   , m_selected(-1)
 | |
|   , m_editable(false)
 | |
|   , m_clickopen(true)
 | |
|   , m_casesensitive(true)
 | |
|   , m_filtering(false)
 | |
|   , m_useCustomWidget(false)
 | |
| {
 | |
|   m_entry->setExpansive(true);
 | |
| 
 | |
|   // When the "m_button" is clicked ("Click" signal) call onButtonClick() method
 | |
|   m_button->Click.connect(&ComboBox::onButtonClick, this);
 | |
| 
 | |
|   addChild(m_entry);
 | |
|   addChild(m_button);
 | |
| 
 | |
|   setFocusStop(true);
 | |
|   setEditable(m_editable);
 | |
| 
 | |
|   initTheme();
 | |
| }
 | |
| 
 | |
| ComboBox::~ComboBox()
 | |
| {
 | |
|   removeMessageFilters();
 | |
|   deleteAllItems();
 | |
| }
 | |
| 
 | |
| void ComboBox::setEditable(bool state)
 | |
| {
 | |
|   m_editable = state;
 | |
| 
 | |
|   if (state) {
 | |
|     m_entry->setReadOnly(false);
 | |
|     m_entry->showCaret();
 | |
|   }
 | |
|   else {
 | |
|     m_entry->setReadOnly(true);
 | |
|     m_entry->hideCaret();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::setClickOpen(bool state)
 | |
| {
 | |
|   m_clickopen = state;
 | |
| }
 | |
| 
 | |
| void ComboBox::setCaseSensitive(bool state)
 | |
| {
 | |
|   m_casesensitive = state;
 | |
| }
 | |
| 
 | |
| void ComboBox::setUseCustomWidget(bool state)
 | |
| {
 | |
|   m_useCustomWidget = state;
 | |
| }
 | |
| 
 | |
| int ComboBox::addItem(Widget* item)
 | |
| {
 | |
|   bool sel_first = m_items.empty();
 | |
| 
 | |
|   m_items.push_back(item);
 | |
| 
 | |
|   if (sel_first && !isEditable())
 | |
|     setSelectedItemIndex(0);
 | |
| 
 | |
|   return m_items.size() - 1;
 | |
| }
 | |
| 
 | |
| int ComboBox::addItem(const std::string& text)
 | |
| {
 | |
|   return addItem(new ListItem(text));
 | |
| }
 | |
| 
 | |
| void ComboBox::insertItem(int itemIndex, Widget* item)
 | |
| {
 | |
|   bool sel_first = m_items.empty();
 | |
| 
 | |
|   m_items.insert(m_items.begin() + itemIndex, item);
 | |
| 
 | |
|   if (sel_first)
 | |
|     setSelectedItemIndex(0);
 | |
| }
 | |
| 
 | |
| void ComboBox::insertItem(int itemIndex, const std::string& text)
 | |
| {
 | |
|   insertItem(itemIndex, new ListItem(text));
 | |
| }
 | |
| 
 | |
| void ComboBox::removeItem(Widget* item)
 | |
| {
 | |
|   auto it = std::find(m_items.begin(), m_items.end(), item);
 | |
|   ASSERT(it != m_items.end());
 | |
|   if (it != m_items.end())
 | |
|     m_items.erase(it);
 | |
| 
 | |
|   // Do not delete the given "item"
 | |
| }
 | |
| 
 | |
| void ComboBox::deleteItem(int itemIndex)
 | |
| {
 | |
|   ASSERT(itemIndex >= 0 && (std::size_t)itemIndex < m_items.size());
 | |
| 
 | |
|   Widget* item = m_items[itemIndex];
 | |
| 
 | |
|   m_items.erase(m_items.begin() + itemIndex);
 | |
|   delete item;
 | |
| }
 | |
| 
 | |
| void ComboBox::deleteAllItems()
 | |
| {
 | |
|   // Delete all items back to front, in this way Widget::removeChild()
 | |
|   // doesn't have to use linear search to update m_parentIndex of all
 | |
|   // other children.
 | |
|   auto end = m_items.rend();
 | |
|   for (auto it = m_items.rbegin(); it != end; ++it)
 | |
|     delete *it; // widget
 | |
| 
 | |
|   m_items.clear();
 | |
|   m_selected = -1;
 | |
| }
 | |
| 
 | |
| int ComboBox::getItemCount() const
 | |
| {
 | |
|   return m_items.size();
 | |
| }
 | |
| 
 | |
| Widget* ComboBox::getItem(const int itemIndex) const
 | |
| {
 | |
|   if (itemIndex >= 0 && (std::size_t)itemIndex < m_items.size()) {
 | |
|     return m_items[itemIndex];
 | |
|   }
 | |
|   else
 | |
|     return nullptr;
 | |
| }
 | |
| 
 | |
| const std::string& ComboBox::getItemText(int itemIndex) const
 | |
| {
 | |
|   if (itemIndex >= 0 && (std::size_t)itemIndex < m_items.size()) {
 | |
|     Widget* item = m_items[itemIndex];
 | |
|     return item->text();
 | |
|   }
 | |
|   else {
 | |
|     // Returns the text of the combo-box (it should be empty).
 | |
|     ASSERT(text().empty());
 | |
|     return text();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::setItemText(int itemIndex, const std::string& text)
 | |
| {
 | |
|   ASSERT(itemIndex >= 0 && (std::size_t)itemIndex < m_items.size());
 | |
| 
 | |
|   Widget* item = m_items[itemIndex];
 | |
|   item->setText(text);
 | |
| }
 | |
| 
 | |
| int ComboBox::findItemIndex(const std::string& text) const
 | |
| {
 | |
|   int i = 0;
 | |
|   for (const Widget* item : m_items) {
 | |
|     if ((m_casesensitive && item->text() == text) || (!m_casesensitive && item->text() == text)) {
 | |
|       return i;
 | |
|     }
 | |
|     i++;
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| int ComboBox::findItemIndexByValue(const std::string& value) const
 | |
| {
 | |
|   int i = 0;
 | |
|   for (const Widget* item : m_items) {
 | |
|     if (auto listItem = dynamic_cast<const ListItem*>(item)) {
 | |
|       if (listItem->getValue() == value)
 | |
|         return i;
 | |
|     }
 | |
|     ++i;
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| Widget* ComboBox::getSelectedItem() const
 | |
| {
 | |
|   return getItem(m_selected);
 | |
| }
 | |
| 
 | |
| void ComboBox::setSelectedItem(Widget* item)
 | |
| {
 | |
|   auto it = std::find(m_items.begin(), m_items.end(), item);
 | |
|   if (it != m_items.end())
 | |
|     setSelectedItemIndex(std::distance(m_items.begin(), it));
 | |
|   else if (m_selected >= 0) {
 | |
|     m_selected = -1;
 | |
|     onChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| int ComboBox::getSelectedItemIndex() const
 | |
| {
 | |
|   return (!m_items.empty() ? m_selected : -1);
 | |
| }
 | |
| 
 | |
| void ComboBox::setSelectedItemIndex(int itemIndex)
 | |
| {
 | |
|   if (itemIndex >= 0 && (std::size_t)itemIndex < m_items.size() && m_selected != itemIndex) {
 | |
|     m_selected = itemIndex;
 | |
| 
 | |
|     auto it = m_items.begin() + itemIndex;
 | |
|     Widget* item = *it;
 | |
|     m_entry->setText(item->text());
 | |
|     if (isEditable())
 | |
|       m_entry->setCaretToEnd();
 | |
| 
 | |
|     onChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| std::string ComboBox::getValue() const
 | |
| {
 | |
|   if (isEditable())
 | |
|     return m_entry->text();
 | |
|   int index = getSelectedItemIndex();
 | |
|   if (index >= 0) {
 | |
|     if (auto listItem = dynamic_cast<ListItem*>(m_items[index]))
 | |
|       return listItem->getValue();
 | |
|   }
 | |
|   return std::string();
 | |
| }
 | |
| 
 | |
| void ComboBox::setValue(const std::string& value)
 | |
| {
 | |
|   if (isEditable()) {
 | |
|     m_entry->setText(value);
 | |
|     if (hasFocus())
 | |
|       m_entry->selectAllText();
 | |
|   }
 | |
|   else {
 | |
|     int index = findItemIndexByValue(value);
 | |
|     if (index >= 0)
 | |
|       setSelectedItemIndex(index);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Entry* ComboBox::getEntryWidget()
 | |
| {
 | |
|   return m_entry;
 | |
| }
 | |
| 
 | |
| Button* ComboBox::getButtonWidget()
 | |
| {
 | |
|   return m_button;
 | |
| }
 | |
| 
 | |
| bool ComboBox::onProcessMessage(Message* msg)
 | |
| {
 | |
|   switch (msg->type()) {
 | |
|     case kCloseMessage: closeListBox(); break;
 | |
| 
 | |
|     case kWinMoveMessage:
 | |
|       // If we mouse the parent window, we close the list box popup.
 | |
|       closeListBox();
 | |
|       break;
 | |
| 
 | |
|     case kKeyDownMessage:
 | |
|       if (m_window) {
 | |
|         KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
 | |
|         KeyScancode scancode = keymsg->scancode();
 | |
| 
 | |
|         // If the popup is opened
 | |
|         if (scancode == kKeyEsc) {
 | |
|           closeListBox();
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kMouseDownMessage:
 | |
|       if (m_window) {
 | |
|         if (View::getView(m_listbox)->hasMouse()) {
 | |
|           // As we are filtering the kMouseDownMessage, and the
 | |
|           // ListBox has the mouse, we "return false" here to say "we
 | |
|           // are not interested in this mouse message, it will be
 | |
|           // processed by the ListBox itself". In other case, if we
 | |
|           // "break" and call Widget::onProcessMessage(), the message
 | |
|           // will be propagated to the parent window and could be used
 | |
|           // to move the parent window (instead of clicking a listbox
 | |
|           // item of the popup m_window).
 | |
|           return false;
 | |
|         }
 | |
|         else {
 | |
|           MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
 | |
| 
 | |
|           // Use the nativeWindow() from the mouseMsg before we close
 | |
|           // the listbox because the mouseMsg->display() could be from
 | |
|           // the same popup.
 | |
|           const gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(
 | |
|             mouseMsg->position());
 | |
| 
 | |
|           closeListBox();
 | |
| 
 | |
|           Widget* pick = manager()->pickFromScreenPos(screenPos);
 | |
|           if (pick && pick->hasAncestor(this))
 | |
|             return true;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kFocusEnterMessage:
 | |
|       // Here we focus the entry field only if the combobox is
 | |
|       // editable and receives the focus in a direct way (e.g. when
 | |
|       // the window was just opened and the combobox is the first
 | |
|       // child or has the "focus magnet" flag enabled.)
 | |
|       if ((isEditable()) && (manager()->getFocus() == this)) {
 | |
|         m_entry->requestFocus();
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return Widget::onProcessMessage(msg);
 | |
| }
 | |
| 
 | |
| void ComboBox::onInitTheme(InitThemeEvent& ev)
 | |
| {
 | |
|   Widget::onInitTheme(ev);
 | |
|   if (m_window) {
 | |
|     m_window->initTheme();
 | |
|     m_window->noBorderNoChildSpacing();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::onResize(ResizeEvent& ev)
 | |
| {
 | |
|   gfx::Rect bounds = ev.bounds();
 | |
|   setBoundsQuietly(bounds);
 | |
| 
 | |
|   // Button
 | |
|   Size buttonSize = m_button->sizeHint();
 | |
|   m_button->setBounds(Rect(bounds.x2() - buttonSize.w, bounds.y, buttonSize.w, bounds.h));
 | |
| 
 | |
|   // Entry
 | |
|   m_entry->setBounds(Rect(bounds.x, bounds.y, bounds.w - buttonSize.w, bounds.h));
 | |
| 
 | |
|   putSelectedItemAsCustomWidget();
 | |
| }
 | |
| 
 | |
| void ComboBox::onSizeHint(SizeHintEvent& ev)
 | |
| {
 | |
|   Size reqSize(0, 0);
 | |
| 
 | |
|   // Calculate the max required width depending on the text-length of
 | |
|   // each item.
 | |
|   for (const auto& item : m_items)
 | |
|     reqSize |= Entry::sizeHintWithText(m_entry, item->text());
 | |
| 
 | |
|   Size buttonSize = m_button->sizeHint();
 | |
|   reqSize.w += buttonSize.w;
 | |
|   reqSize.h = std::max(reqSize.h, buttonSize.h);
 | |
| 
 | |
|   ev.setSizeHint(reqSize);
 | |
| }
 | |
| 
 | |
| bool ComboBoxEntry::onProcessMessage(Message* msg)
 | |
| {
 | |
|   switch (msg->type()) {
 | |
|     case kKeyDownMessage:
 | |
|       if (hasFocus()) {
 | |
|         KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
 | |
|         KeyScancode scancode = keymsg->scancode();
 | |
| 
 | |
|         // In a non-editable ComboBox
 | |
|         if (!m_comboBox->isEditable()) {
 | |
|           if (scancode == kKeySpace || scancode == kKeyEnter || scancode == kKeyEnterPad) {
 | |
|             m_comboBox->switchListBox();
 | |
|             return true;
 | |
|           }
 | |
|         }
 | |
|         // In a editable ComboBox
 | |
|         else {
 | |
|           if (scancode == kKeyUp || scancode == kKeyDown || scancode == kKeyPageUp ||
 | |
|               scancode == kKeyPageDown) {
 | |
|             if (m_comboBox->m_listbox && m_comboBox->m_listbox->isVisible()) {
 | |
|               m_comboBox->m_listbox->requestFocus();
 | |
|               m_comboBox->m_listbox->sendMessage(msg);
 | |
|               return true;
 | |
|             }
 | |
|           }
 | |
|           else if (scancode == kKeyEnter || scancode == kKeyEnterPad) {
 | |
|             m_comboBox->onEnterOnEditableEntry();
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kMouseDownMessage:
 | |
|       if (m_comboBox->isClickOpen() &&
 | |
|           (!m_comboBox->isEditable() || !m_comboBox->m_items.empty())) {
 | |
|         m_comboBox->switchListBox();
 | |
|       }
 | |
| 
 | |
|       if (m_comboBox->isEditable()) {
 | |
|         requestFocus();
 | |
|       }
 | |
|       else {
 | |
|         captureMouse();
 | |
|         return true;
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kMouseUpMessage:
 | |
|       if (hasCapture())
 | |
|         releaseMouse();
 | |
|       break;
 | |
| 
 | |
|     case kMouseMoveMessage:
 | |
|       if (hasCapture()) {
 | |
|         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
 | |
|         gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(
 | |
|           mouseMsg->position());
 | |
|         Widget* pick = manager()->pickFromScreenPos(screenPos);
 | |
|         Widget* listbox = m_comboBox->m_listbox;
 | |
| 
 | |
|         if (pick != nullptr && (pick == listbox || pick->hasAncestor(listbox))) {
 | |
|           releaseMouse();
 | |
| 
 | |
|           MouseMessage mouseMsg2(kMouseDownMessage,
 | |
|                                  *mouseMsg,
 | |
|                                  mouseMsg->positionForDisplay(pick->display()));
 | |
|           mouseMsg2.setRecipient(pick);
 | |
|           mouseMsg2.setDisplay(pick->display());
 | |
|           pick->sendMessage(&mouseMsg2);
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kFocusEnterMessage: {
 | |
|       bool result = Entry::onProcessMessage(msg);
 | |
|       if (m_comboBox && m_comboBox->isEditable() && m_comboBox->m_listbox &&
 | |
|           m_comboBox->m_listbox->isVisible()) {
 | |
|         // In case that the ListBox is visible and the focus is
 | |
|         // obtained by the Entry field, we set the carret at the end
 | |
|         // of the text. We don't select the whole text so the user can
 | |
|         // delete the last caracters using backspace and complete the
 | |
|         // item name.
 | |
|         setCaretToEnd();
 | |
|       }
 | |
|       return result;
 | |
|     }
 | |
| 
 | |
|     case kFocusLeaveMessage:
 | |
|       if (m_comboBox->isEditable() && m_comboBox->m_window &&
 | |
|           !View::getView(m_comboBox->m_listbox)->hasMouse()) {
 | |
|         m_comboBox->closeListBox();
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return Entry::onProcessMessage(msg);
 | |
| }
 | |
| 
 | |
| void ComboBoxEntry::onPaint(PaintEvent& ev)
 | |
| {
 | |
|   theme()->paintComboBoxEntry(ev);
 | |
| }
 | |
| 
 | |
| void ComboBoxEntry::onChange()
 | |
| {
 | |
|   Entry::onChange();
 | |
|   if (m_comboBox && m_comboBox->isEditable()) {
 | |
|     m_comboBox->onEntryChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool ComboBoxListBox::onProcessMessage(Message* msg)
 | |
| {
 | |
|   switch (msg->type()) {
 | |
|     case kMouseUpMessage: m_comboBox->closeListBox(); return true;
 | |
| 
 | |
|     case kKeyDownMessage:
 | |
|       if (hasFocus()) {
 | |
|         KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
 | |
|         KeyScancode scancode = keymsg->scancode();
 | |
| 
 | |
|         if (scancode == kKeySpace || scancode == kKeyEnter || scancode == kKeyEnterPad) {
 | |
|           m_comboBox->closeListBox();
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case kFocusEnterMessage:
 | |
|       // If the ComboBox is editable, we prefer the focus in the Entry
 | |
|       // field (so the user can continue editing it).
 | |
|       if (m_comboBox->isEditable())
 | |
|         m_comboBox->getEntryWidget()->requestFocus();
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return ListBox::onProcessMessage(msg);
 | |
| }
 | |
| 
 | |
| void ComboBoxListBox::onChange()
 | |
| {
 | |
|   ListBox::onChange();
 | |
| 
 | |
|   int index = getSelectedIndex();
 | |
|   if (isValidItem(index))
 | |
|     m_comboBox->setSelectedItemIndex(index);
 | |
| }
 | |
| 
 | |
| // When the mouse is clicked we switch the visibility-status of the list-box
 | |
| void ComboBox::onButtonClick()
 | |
| {
 | |
|   switchListBox();
 | |
| }
 | |
| 
 | |
| void ComboBox::openListBox()
 | |
| {
 | |
|   if (!isEnabled() || m_window)
 | |
|     return;
 | |
| 
 | |
|   onBeforeOpenListBox();
 | |
| 
 | |
|   m_window = new Window(Window::WithoutTitleBar);
 | |
|   View* view = new View();
 | |
|   m_listbox = new ComboBoxListBox(this);
 | |
|   m_window->setAutoRemap(false);
 | |
|   m_window->setOnTop(true);
 | |
|   m_window->setWantFocus(false);
 | |
|   m_window->setSizeable(false);
 | |
|   m_window->setMoveable(false);
 | |
| 
 | |
|   Widget* viewport = view->viewport();
 | |
|   {
 | |
|     gfx::Rect entryBounds = m_entry->bounds();
 | |
|     gfx::Size size;
 | |
|     size.w = m_button->bounds().x2() - entryBounds.x - view->border().width();
 | |
|     size.h = viewport->border().height();
 | |
|     for (Widget* item : m_items)
 | |
|       if (!item->hasFlags(HIDDEN))
 | |
|         size.h += item->sizeHint().h;
 | |
| 
 | |
|     if (!get_multiple_displays()) {
 | |
|       const int maxVal = std::max(entryBounds.y, display()->size().h - entryBounds.y2()) -
 | |
|                          8 * guiscale();
 | |
|       size.h = std::clamp(size.h, textHeight(), maxVal);
 | |
|     }
 | |
| 
 | |
|     viewport->setMinSize(size);
 | |
|   }
 | |
| 
 | |
|   m_window->addChild(view);
 | |
|   view->attachToView(m_listbox);
 | |
| 
 | |
|   m_listbox->selectIndex(m_selected);
 | |
| 
 | |
|   initTheme();
 | |
|   m_window->remapWindow();
 | |
| 
 | |
|   updateListBoxPos();
 | |
|   m_window->openWindow();
 | |
| 
 | |
|   filterMessages();
 | |
| 
 | |
|   if (isEditable())
 | |
|     m_entry->requestFocus();
 | |
|   else
 | |
|     m_listbox->requestFocus();
 | |
| 
 | |
|   onOpenListBox();
 | |
| }
 | |
| 
 | |
| void ComboBox::closeListBox()
 | |
| {
 | |
|   if (m_window) {
 | |
|     removeMessageFilters();
 | |
| 
 | |
|     m_listbox->clean();
 | |
| 
 | |
|     m_window->closeWindow(this);
 | |
|     delete m_window; // window, frame
 | |
| 
 | |
|     m_window = nullptr;
 | |
|     m_listbox = nullptr;
 | |
| 
 | |
|     putSelectedItemAsCustomWidget();
 | |
|     m_entry->requestFocus();
 | |
| 
 | |
|     onCloseListBox();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::switchListBox()
 | |
| {
 | |
|   if (!m_window)
 | |
|     openListBox();
 | |
|   else
 | |
|     closeListBox();
 | |
| }
 | |
| 
 | |
| void ComboBox::updateListBoxPos()
 | |
| {
 | |
|   gfx::Rect entryBounds = m_entry->bounds();
 | |
|   gfx::Rect rc(gfx::Point(entryBounds.x, entryBounds.y2()),
 | |
|                gfx::Point(m_button->bounds().x2(), entryBounds.y2() + m_window->bounds().h));
 | |
| 
 | |
|   fit_bounds(display(),
 | |
|              m_window,
 | |
|              rc,
 | |
|              [this](const gfx::Rect& workarea,
 | |
|                     gfx::Rect& bounds,
 | |
|                     std::function<gfx::Rect(Widget*)> getWidgetBounds) {
 | |
|                if (bounds.y2() > workarea.y2())
 | |
|                  bounds.offset(0, -(bounds.h + getWidgetBounds(m_entry).h));
 | |
|              });
 | |
| }
 | |
| 
 | |
| void ComboBox::onChange()
 | |
| {
 | |
|   Change();
 | |
| }
 | |
| 
 | |
| void ComboBox::onEntryChange()
 | |
| {
 | |
|   // Do nothing
 | |
| }
 | |
| 
 | |
| void ComboBox::onBeforeOpenListBox()
 | |
| {
 | |
|   // Do nothing
 | |
| }
 | |
| 
 | |
| void ComboBox::onOpenListBox()
 | |
| {
 | |
|   OpenListBox();
 | |
| }
 | |
| 
 | |
| void ComboBox::onCloseListBox()
 | |
| {
 | |
|   CloseListBox();
 | |
| }
 | |
| 
 | |
| void ComboBox::onEnterOnEditableEntry()
 | |
| {
 | |
|   // Do nothing
 | |
| }
 | |
| 
 | |
| void ComboBox::filterMessages()
 | |
| {
 | |
|   if (!m_filtering) {
 | |
|     manager()->addMessageFilter(kMouseDownMessage, this);
 | |
|     manager()->addMessageFilter(kKeyDownMessage, this);
 | |
|     m_filtering = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::removeMessageFilters()
 | |
| {
 | |
|   if (m_filtering) {
 | |
|     manager()->removeMessageFilter(kMouseDownMessage, this);
 | |
|     manager()->removeMessageFilter(kKeyDownMessage, this);
 | |
|     m_filtering = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ComboBox::putSelectedItemAsCustomWidget()
 | |
| {
 | |
|   if (!useCustomWidget())
 | |
|     return;
 | |
| 
 | |
|   Widget* item = getSelectedItem();
 | |
|   if (item && item->parent() == nullptr) {
 | |
|     if (!m_listbox) {
 | |
|       item->setBounds(m_entry->childrenBounds());
 | |
|       m_entry->addChild(item);
 | |
|     }
 | |
|     else {
 | |
|       m_entry->removeChild(item);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| } // namespace ui
 |