aseprite/src/ui/combobox.cpp
David Capello ad0b28bd63 Change Window() ctor to avoid ambiguity (DesktopWindow, WithoutTitleBar, or WithTitleBar)
In this way we know at the moment of the creation of the window if it
will need the close button.

Issue #280.
https://github.com/aseprite/aseprite/pull/6
2013-11-23 17:47:57 -03:00

550 lines
11 KiB
C++

// Aseprite UI Library
// Copyright (C) 2001-2013 David Capello
//
// This source file is distributed under MIT license,
// please read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "base/compiler_specific.h"
#include "gfx/size.h"
#include "ui/ui.h"
#include <allegro.h>
namespace ui {
using namespace gfx;
class ComboBoxButton : public Button {
public:
ComboBoxButton() : Button("") {
setFocusStop(false);
}
void onPaint(PaintEvent& ev) OVERRIDE {
getTheme()->paintComboBoxButton(ev);
}
};
class ComboBoxEntry : public Entry
{
public:
ComboBoxEntry(ComboBox* comboBox)
: Entry(256, ""),
m_comboBox(comboBox) {
}
protected:
bool onProcessMessage(Message* msg) OVERRIDE;
void onPaint(PaintEvent& ev) OVERRIDE;
private:
ComboBox* m_comboBox;
};
class ComboBoxListBox : public ListBox
{
public:
ComboBoxListBox(ComboBox* comboBox)
: m_comboBox(comboBox)
{
for (ComboBox::ListItems::iterator
it = comboBox->begin(), end = comboBox->end(); it != end; ++it)
addChild(*it);
}
void clean()
{
// Remove all added items so ~Widget() don't delete them.
while (getLastChild())
removeChild(getLastChild());
ASSERT(getChildren().empty());
selectChild(NULL);
}
protected:
bool onProcessMessage(Message* msg) OVERRIDE;
void onChangeSelectedItem() 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 = NULL;
m_selected = 0;
m_editable = false;
m_clickopen = true;
m_casesensitive = true;
// TODO this separation should be from the Theme*
this->child_spacing = 0;
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()
{
removeAllItems();
}
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;
}
bool ComboBox::isEditable()
{
return m_editable;
}
bool ComboBox::isClickOpen()
{
return m_clickopen;
}
bool ComboBox::isCaseSensitive()
{
return m_casesensitive;
}
int ComboBox::addItem(ListItem* item)
{
bool sel_first = m_items.empty();
m_items.push_back(item);
if (sel_first)
setSelectedItemIndex(0);
return m_items.size()-1;
}
int ComboBox::addItem(const base::string& text)
{
return addItem(new ListItem(text));
}
void ComboBox::insertItem(int itemIndex, ListItem* 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 base::string& text)
{
insertItem(itemIndex, new ListItem(text));
}
void ComboBox::removeItem(ListItem* item)
{
ListItems::iterator 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::removeItem(int itemIndex)
{
ASSERT(itemIndex >= 0 && (size_t)itemIndex < m_items.size());
ListItem* item = m_items[itemIndex];
m_items.erase(m_items.begin() + itemIndex);
delete item;
}
void ComboBox::removeAllItems()
{
ListItems::iterator it, end = m_items.end();
for (it = m_items.begin(); it != end; ++it)
delete *it;
m_items.clear();
}
int ComboBox::getItemCount() const
{
return m_items.size();
}
ListItem* ComboBox::getItem(int itemIndex)
{
if (itemIndex >= 0 && (size_t)itemIndex < m_items.size()) {
return m_items[itemIndex];
}
else
return NULL;
}
const base::string& ComboBox::getItemText(int itemIndex) const
{
if (itemIndex >= 0 && (size_t)itemIndex < m_items.size()) {
ListItem* item = m_items[itemIndex];
return item->getText();
}
else {
// Returns the text of the combo-box (it should be empty).
ASSERT(getText().empty());
return getText();
}
}
void ComboBox::setItemText(int itemIndex, const base::string& text)
{
ASSERT(itemIndex >= 0 && (size_t)itemIndex < m_items.size());
ListItem* item = m_items[itemIndex];
item->setText(text);
}
int ComboBox::findItemIndex(const base::string& text)
{
int itemIndex = 0;
ListItems::iterator it, end = m_items.end();
for (it = m_items.begin(); it != end; ++it) {
ListItem* item = *it;
if ((m_casesensitive && item->getText() == text) ||
(!m_casesensitive && item->getText() == text)) {
return itemIndex;
}
itemIndex++;
}
return -1;
}
ListItem* ComboBox::getSelectedItem() const
{
return (!m_items.empty() ? m_items[m_selected]: NULL);
}
void ComboBox::setSelectedItem(ListItem* item)
{
ListItems::iterator it = std::find(m_items.begin(), m_items.end(), item);
ASSERT(it != m_items.end());
if (it != m_items.end())
setSelectedItemIndex(std::distance(m_items.begin(), it));
}
int ComboBox::getSelectedItemIndex() const
{
return (!m_items.empty() ? m_selected: -1);
}
void ComboBox::setSelectedItemIndex(int itemIndex)
{
if (itemIndex >= 0 && (size_t)itemIndex < m_items.size()) {
m_selected = itemIndex;
ListItems::iterator it = m_items.begin() + itemIndex;
ListItem* item = *it;
m_entry->setText(item->getText());
}
}
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 (m_window)
m_window->moveWindow(getListBoxPos());
break;
case kMouseDownMessage:
if (m_window != NULL) {
if (!View::getView(m_listbox)->hasMouse()) {
closeListBox();
return true;
}
}
break;
}
return Widget::onProcessMessage(msg);
}
void ComboBox::onResize(ResizeEvent& ev)
{
gfx::Rect bounds = ev.getBounds();
setBoundsQuietly(bounds);
// Button
Size buttonSize = m_button->getPreferredSize();
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));
}
void ComboBox::onPreferredSize(PreferredSizeEvent& ev)
{
Size reqSize(0, 0);
Size entrySize = m_entry->getPreferredSize();
// Get the text-length of every item and put in 'w' the maximum value
ListItems::iterator it, end = m_items.end();
for (it = m_items.begin(); it != end; ++it) {
int item_w =
2*jguiscale()+
text_length(getFont(), (*it)->getText().c_str())+
10*jguiscale();
reqSize.w = MAX(reqSize.w, item_w);
}
reqSize.w += entrySize.w;
reqSize.h += entrySize.h;
Size buttonSize = m_button->getPreferredSize();
reqSize.w += buttonSize.w;
reqSize.h = MAX(reqSize.h, buttonSize.h);
ev.setPreferredSize(reqSize);
}
bool ComboBoxEntry::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kKeyDownMessage:
if (hasFocus()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
KeyScancode scancode = keymsg->scancode();
if (!m_comboBox->isEditable()) {
if (scancode == kKeySpace ||
scancode == kKeyEnter ||
scancode == kKeyEnterPad) {
m_comboBox->switchListBox();
return true;
}
}
else {
if (scancode == kKeyEnter ||
scancode == kKeyEnterPad) {
m_comboBox->switchListBox();
return true;
}
}
}
break;
case kMouseDownMessage:
if (m_comboBox->isClickOpen()) {
m_comboBox->switchListBox();
}
if (m_comboBox->isEditable()) {
getManager()->setFocus(this);
}
else
return true;
break;
}
return Entry::onProcessMessage(msg);
}
void ComboBoxEntry::onPaint(PaintEvent& ev)
{
getTheme()->paintComboBoxEntry(ev);
}
bool ComboBoxListBox::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kMouseUpMessage: {
int index = m_comboBox->getSelectedItemIndex();
if (isValidItem(index))
m_comboBox->onChange();
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;
}
return ListBox::onProcessMessage(msg);
}
void ComboBoxListBox::onChangeSelectedItem()
{
ListBox::onChangeSelectedItem();
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(Event& ev)
{
switchListBox();
}
void ComboBox::openListBox()
{
if (!m_window) {
m_window = new Window(Window::WithoutTitleBar);
View* view = new View();
m_listbox = new ComboBoxListBox(this);
m_window->setOnTop(true);
jwidget_noborders(m_window);
Widget* viewport = view->getViewport();
int size = getItemCount();
jwidget_set_min_size
(viewport,
m_button->getBounds().x2() - m_entry->getBounds().x - view->border_width.l - view->border_width.r,
+viewport->border_width.t
+(2*jguiscale()+jwidget_get_text_height(m_listbox))*MID(1, size, 16)+
+viewport->border_width.b);
m_window->addChild(view);
view->attachToView(m_listbox);
m_listbox->selectIndex(m_selected);
m_window->remapWindow();
gfx::Rect rc = getListBoxPos();
m_window->positionWindow(rc.x, rc.y);
getManager()->addMessageFilter(kMouseDownMessage, this);
m_window->openWindow();
getManager()->setFocus(m_listbox);
}
}
void ComboBox::closeListBox()
{
if (m_window) {
m_listbox->clean();
m_window->closeWindow(this);
delete m_window; // window, frame
m_window = NULL;
getManager()->removeMessageFilter(kMouseDownMessage, this);
getManager()->setFocus(m_entry);
}
}
void ComboBox::switchListBox()
{
if (!m_window)
openListBox();
else
closeListBox();
}
gfx::Rect ComboBox::getListBoxPos() const
{
gfx::Rect rc(gfx::Point(m_entry->getBounds().x,
m_entry->getBounds().y2()),
gfx::Point(m_button->getBounds().x2(),
m_entry->getBounds().y2()+m_window->getBounds().h));
if (rc.y2() > JI_SCREEN_H)
rc.offset(0, -(rc.h + m_entry->getBounds().h));
return rc;
}
void ComboBox::onChange()
{
Change();
}
} // namespace ui