Skip to content

Commit

Permalink
UI|Client: Added basic implementation for ChoiceWidget
Browse files Browse the repository at this point in the history
  • Loading branch information
skyjake committed Aug 17, 2013
1 parent 7958356 commit 107cd00
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 4 deletions.
36 changes: 36 additions & 0 deletions doomsday/client/include/ui/widgets/choicewidget.h
Expand Up @@ -20,17 +20,53 @@
#define DENG_CLIENT_CHOICEWIDGET_H

#include "buttonwidget.h"
#include "popupmenuwidget.h"
#include "actionitem.h"

/**
* Widget for choosing an item from a set of alternatives.
*
* The items of the widget should be ChoiceItem instances, or at least derived
* from ActionItem/ChoiceItem.
*
* The default opening direction for the popup is to the right.
*/
class ChoiceWidget : public ButtonWidget
{
Q_OBJECT

public:
/**
* The items of the widget are expected to be instanced of
* ChoiceWidget::Item or derived from it (or at least ui::ActionItem).
*/
class Item : public ui::ActionItem
{
public:
Item(de::String const &label, de::Image const &image = de::Image())
: ui::ActionItem(image, label) {}
};

public:
ChoiceWidget(de::String const &name = "");

ui::Context &items();

PopupMenuWidget &popup();

void setSelected(ui::Context::Pos pos);

ui::Context::Pos selected() const;

ui::Item const &selectedItem() const;

signals:
void selectionChanged(unsigned int pos);

private:
DENG2_PRIVATE(d)
};

typedef ChoiceWidget::Item ChoiceItem;

#endif // DENG_CLIENT_CHOICEWIDGET_H
146 changes: 142 additions & 4 deletions doomsday/client/src/ui/widgets/choicewidget.cpp
Expand Up @@ -20,12 +20,150 @@
#include "ui/widgets/popupmenuwidget.h"

using namespace de;
using namespace ui;

DENG2_PIMPL(ChoiceWidget)
{
Instance(Public *i) : Base(i)
{}
DENG2_PIMPL(ChoiceWidget),
DENG2_OBSERVES(Context, Addition),
DENG2_OBSERVES(Context, Removal),
DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation)
{
struct SelectAction : public de::Action
{
Instance *d;
ui::Item const &selItem;

SelectAction(Instance *inst, ui::Item const &item) : d(inst), selItem(item) {}

void trigger()
{
Action::trigger();
d->selected = d->items().find(selItem);
d->updateButtonWithSelection();
}

Action *duplicate() const
{
DENG2_ASSERT(false); // not needed
return 0;
}
};

PopupMenuWidget *choices;
Context::Pos selected; ///< One item is always selected.

Instance(Public *i) : Base(i), selected(0)
{
choices = new PopupMenuWidget;
choices->setAnchorAndOpeningDirection(self.hitRule(), ui::Right);
choices->menu().organizer().audienceForWidgetCreation += this;
self.add(choices);

updateButtonWithSelection();
}

void widgetCreatedForItem(GuiWidget &widget, ui::Item const &item)
{
if(widget.is<ButtonWidget>())
{
// Make sure the created buttons have an action that updates the
// selected item.
widget.as<ButtonWidget>().setAction(new SelectAction(this, item));
}
}

Context const &items() const
{
return choices->menu().items();
}

bool isValidSelection() const
{
return selected < items().size();
}

void contextItemAdded(Context::Pos id, ui::Item const &)
{
if(selected >= items().size())
{
// If the previous selection was invalid, make a valid one now.
selected = 0;

updateButtonWithSelection();
}

if(id <= selected)
{
// New item added before/at the selection.
selected++;
}
}

void contextItemBeingRemoved(Context::Pos id, ui::Item const &)
{
if(id <= selected && selected > 0)
{
selected--;
}

if(!isValidSelection())
{
updateButtonWithSelection();
}
}

void updateButtonWithSelection()
{
if(isValidSelection())
{
ui::Item const &item = items().at(selected);
self.setText(item.label());

ActionItem const *act = dynamic_cast<ActionItem const *>(&item);
if(act)
{
self.setImage(act->image());
}
}
else
{
// No valid selection.
self.setText("");
self.setImage(Image());
}

emit self.selectionChanged(selected);
}
};

ChoiceWidget::ChoiceWidget(String const &name) : ButtonWidget(name), d(new Instance(this))
{}

PopupMenuWidget &ChoiceWidget::popup()
{
return *d->choices;
}

void ChoiceWidget::setSelected(Context::Pos pos)
{
if(d->selected != pos)
{
d->selected = pos;
d->updateButtonWithSelection();
}
}

Context::Pos ChoiceWidget::selected() const
{
return d->selected;
}

Item const &ChoiceWidget::selectedItem() const
{
DENG2_ASSERT(d->isValidSelection());
return d->items().at(d->selected);
}

ui::Context &ChoiceWidget::items()
{
return d->choices->menu().items();
}
5 changes: 5 additions & 0 deletions doomsday/libdeng2/include/de/widgets/widget.h
Expand Up @@ -93,6 +93,11 @@ class DENG2_PUBLIC Widget
Widget(String const &name = "");
virtual ~Widget();

template <typename Type>
bool is() const {
return dynamic_cast<Type const *>(this) != 0;
}

template <typename Type>
Type &as() {
DENG2_ASSERT(dynamic_cast<Type *>(this) != 0);
Expand Down

0 comments on commit 107cd00

Please sign in to comment.