haiku/src/apps/debuganalyzer/gui/HeaderView.cpp

1100 lines
18 KiB
C++

/*
* Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2009, Stephan Aßmus, superstippi@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "HeaderView.h"
#include <stdio.h>
#include <algorithm>
#include <new>
#include <ControlLook.h>
#include <LayoutUtils.h>
#include <Looper.h>
// #pragma mark - HeaderRenderer
HeaderRenderer::~HeaderRenderer()
{
}
void
HeaderRenderer::DrawHeaderBackground(BView* view, BRect frame, BRect updateRect,
uint32 flags)
{
BRect bgRect = frame;
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
bgRect.bottom--;
bgRect.right--;
// if ((flags & CLICKED) != 0)
// base = tint_color(base, B_DARKEN_1_TINT);
be_control_look->DrawButtonBackground(view, bgRect, updateRect, base, 0,
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
view->StrokeLine(frame.RightTop(), frame.RightBottom());
}
// #pragma mark - DefaultHeaderRenderer
DefaultHeaderRenderer::DefaultHeaderRenderer()
{
}
DefaultHeaderRenderer::~DefaultHeaderRenderer()
{
}
float
DefaultHeaderRenderer::HeaderHeight(BView* view, const Header* header)
{
BVariant value;
if (!header->GetValue(value))
return 0;
if (value.Type() == B_STRING_TYPE) {
font_height fontHeight;
view->GetFontHeight(&fontHeight);
return ceilf((fontHeight.ascent + fontHeight.descent) * 1.2f) + 2.0f;
} else {
// TODO: Support more value types.
return 0;
}
}
float
DefaultHeaderRenderer::PreferredHeaderWidth(BView* view, const Header* header)
{
BVariant value;
if (!header->GetValue(value))
return 0;
if (value.Type() == B_STRING_TYPE) {
float width = view->StringWidth(value.ToString());
return width + be_control_look->DefaultLabelSpacing() * 2.0f;
} else {
// TODO: Support more value types.
return 0;
}
}
void
DefaultHeaderRenderer::DrawHeader(BView* view, BRect frame, BRect updateRect,
const Header* header, uint32 flags)
{
DrawHeaderBackground(view, frame, updateRect, flags);
BVariant value;
if (!header->GetValue(value))
return;
frame.InsetBy(be_control_look->DefaultLabelSpacing(), 0);
if (value.Type() == B_STRING_TYPE) {
be_control_look->DrawLabel(view, value.ToString(), frame, updateRect,
view->LowColor(), 0);
}
}
// #pragma mark - HeaderListener
HeaderListener::~HeaderListener()
{
}
void
HeaderListener::HeaderWidthChanged(Header* header)
{
}
void
HeaderListener::HeaderWidthRestrictionsChanged(Header* header)
{
}
void
HeaderListener::HeaderValueChanged(Header* header)
{
}
void
HeaderListener::HeaderRendererChanged(Header* header)
{
}
// #pragma mark - Header
Header::Header(int32 modelIndex)
:
fWidth(100),
fMinWidth(0),
fMaxWidth(10000),
fPreferredWidth(100),
fValue(),
fRenderer(NULL),
fModelIndex(modelIndex),
fResizable(true)
{
}
Header::Header(float width, float minWidth, float maxWidth,
float preferredWidth, int32 modelIndex)
:
fWidth(width),
fMinWidth(minWidth),
fMaxWidth(maxWidth),
fPreferredWidth(preferredWidth),
fValue(),
fRenderer(NULL),
fModelIndex(modelIndex),
fResizable(true)
{
}
float
Header::Width() const
{
return fWidth;
}
float
Header::MinWidth() const
{
return fMinWidth;
}
float
Header::MaxWidth() const
{
return fMaxWidth;
}
float
Header::PreferredWidth() const
{
return fPreferredWidth;
}
void
Header::SetWidth(float width)
{
if (width != fWidth) {
fWidth = width;
NotifyWidthChanged();
}
}
void
Header::SetMinWidth(float width)
{
if (width != fMinWidth) {
fMinWidth = width;
NotifyWidthRestrictionsChanged();
}
}
void
Header::SetMaxWidth(float width)
{
if (width != fMaxWidth) {
fMaxWidth = width;
NotifyWidthRestrictionsChanged();
}
}
void
Header::SetPreferredWidth(float width)
{
if (width != fPreferredWidth) {
fPreferredWidth = width;
NotifyWidthRestrictionsChanged();
}
}
bool
Header::IsResizable() const
{
return fResizable;
}
void
Header::SetResizable(bool resizable)
{
if (resizable != fResizable) {
fResizable = resizable;
NotifyWidthRestrictionsChanged();
}
}
bool
Header::GetValue(BVariant& _value) const
{
_value = fValue;
return true;
}
void
Header::SetValue(const BVariant& value)
{
fValue = value;
NotifyValueChanged();
}
int32
Header::ModelIndex() const
{
return fModelIndex;
}
void
Header::SetModelIndex(int32 index)
{
fModelIndex = index;
}
HeaderRenderer*
Header::GetHeaderRenderer() const
{
return fRenderer;
}
void
Header::SetHeaderRenderer(HeaderRenderer* renderer)
{
if (renderer != fRenderer) {
fRenderer = renderer;
NotifyRendererChanged();
}
}
void
Header::AddListener(HeaderListener* listener)
{
fListeners.AddItem(listener);
}
void
Header::RemoveListener(HeaderListener* listener)
{
fListeners.RemoveItem(listener);
}
void
Header::NotifyWidthChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderListener* listener = fListeners.ItemAt(i);
listener->HeaderWidthChanged(this);
}
}
void
Header::NotifyWidthRestrictionsChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderListener* listener = fListeners.ItemAt(i);
listener->HeaderWidthRestrictionsChanged(this);
}
}
void
Header::NotifyValueChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderListener* listener = fListeners.ItemAt(i);
listener->HeaderValueChanged(this);
}
}
void
Header::NotifyRendererChanged()
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderListener* listener = fListeners.ItemAt(i);
listener->HeaderRendererChanged(this);
}
}
// #pragma mark - HeaderModelListener
HeaderModelListener::~HeaderModelListener()
{
}
void
HeaderModelListener::HeaderAdded(HeaderModel* model, int32 index)
{
}
void
HeaderModelListener::HeaderRemoved(HeaderModel* model, int32 index)
{
}
void
HeaderModelListener::HeaderMoved(HeaderModel* model, int32 fromIndex,
int32 toIndex)
{
}
// #pragma mark - HeaderModel
HeaderModel::HeaderModel()
{
}
HeaderModel::~HeaderModel()
{
}
int32
HeaderModel::CountHeaders() const
{
return fHeaders.CountItems();
}
Header*
HeaderModel::HeaderAt(int32 index) const
{
return fHeaders.ItemAt(index);
}
int32
HeaderModel::IndexOfHeader(Header* header) const
{
return fHeaders.IndexOf(header);
}
bool
HeaderModel::AddHeader(Header* header)
{
if (!fHeaders.AddItem(header))
return false;
NotifyHeaderAdded(fHeaders.CountItems() - 1);
return true;
}
Header*
HeaderModel::RemoveHeader(int32 index)
{
Header* header = fHeaders.RemoveItemAt(index);
if (header != NULL)
return NULL;
NotifyHeaderRemoved(index);
return header;
}
void
HeaderModel::RemoveHeader(Header* header)
{
RemoveHeader(fHeaders.IndexOf(header));
}
bool
HeaderModel::MoveHeader(int32 fromIndex, int32 toIndex)
{
int32 headerCount = fHeaders.CountItems();
if (fromIndex < 0 || fromIndex >= headerCount
|| toIndex < 0 || toIndex >= headerCount) {
return false;
}
if (fromIndex == toIndex)
return true;
Header* header = fHeaders.RemoveItemAt(fromIndex);
fHeaders.AddItem(header, toIndex);
// TODO: Might fail.
NotifyHeaderMoved(fromIndex, toIndex);
return true;
}
void
HeaderModel::AddListener(HeaderModelListener* listener)
{
fListeners.AddItem(listener);
}
void
HeaderModel::RemoveListener(HeaderModelListener* listener)
{
fListeners.RemoveItem(listener);
}
void
HeaderModel::NotifyHeaderAdded(int32 index)
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderModelListener* listener = fListeners.ItemAt(i);
listener->HeaderAdded(this, index);
}
}
void
HeaderModel::NotifyHeaderRemoved(int32 index)
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderModelListener* listener = fListeners.ItemAt(i);
listener->HeaderRemoved(this, index);
}
}
void
HeaderModel::NotifyHeaderMoved(int32 fromIndex, int32 toIndex)
{
for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
HeaderModelListener* listener = fListeners.ItemAt(i);
listener->HeaderMoved(this, fromIndex, toIndex);
}
}
// #pragma mark - HeaderEntry
struct HeaderView::HeaderEntry {
Header* header;
float position;
float width;
HeaderEntry(Header* header)
:
header(header)
{
}
};
// #pragma mark - States
class HeaderView::State {
public:
State(HeaderView* parent)
:
fParent(parent)
{
}
virtual ~State()
{
}
virtual void Entering(State* previousState)
{
}
virtual void Leaving(State* nextState)
{
}
virtual void MouseDown(BPoint where, uint32 buttons, uint32 modifiers)
{
}
virtual void MouseUp(BPoint where, uint32 buttons, uint32 modifiers)
{
}
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
}
protected:
HeaderView* fParent;
};
class HeaderView::DefaultState : public State {
public:
DefaultState(HeaderView* parent);
virtual void MouseDown(BPoint where, uint32 buttons,
uint32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage);
};
class HeaderView::ResizeState : public State {
public:
virtual void Entering(State* previousState);
virtual void Leaving(State* nextState);
ResizeState(HeaderView* parent,
int32 headerIndex, BPoint startPoint);
virtual void MouseUp(BPoint where, uint32 buttons,
uint32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage);
private:
Header* fHeader;
float fStartX;
float fStartWidth;
};
// #pragma mark - DefaultState
HeaderView::DefaultState::DefaultState(HeaderView* parent)
:
State(parent)
{
}
void
HeaderView::DefaultState::MouseDown(BPoint where, uint32 buttons,
uint32 modifiers)
{
HeaderModel* model = fParent->Model();
if (model == NULL)
return;
if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
return;
int32 headerIndex = fParent->HeaderIndexAt(where);
if (headerIndex < 0) {
int32 headerCount = model->CountHeaders();
if (headerCount == 0)
return;
headerIndex = headerCount - 1;
}
// Check whether the mouse is close to the left or the right side of the
// header.
BRect headerFrame = fParent->HeaderFrame(headerIndex);
if (fabs(headerFrame.left - where.x) <= 3) {
if (headerIndex == 0)
return;
headerIndex--;
} else if (fabs(headerFrame.right + 1 - where.x) > 3)
return;
// start resizing the header
fParent->_SwitchState(new ResizeState(fParent, headerIndex, where));
}
void
HeaderView::DefaultState::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
}
// #pragma mark - ResizeState
void
HeaderView::ResizeState::Entering(State* previousState)
{
fParent->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
}
void
HeaderView::ResizeState::Leaving(State* nextState)
{
fParent->SetEventMask(0, 0);
}
HeaderView::ResizeState::ResizeState(HeaderView* parent, int32 headerIndex,
BPoint startPoint)
:
State(parent),
fHeader(parent->Model()->HeaderAt(headerIndex)),
fStartX(startPoint.x),
fStartWidth(fHeader->Width())
{
}
void
HeaderView::ResizeState::MouseUp(BPoint where, uint32 buttons,
uint32 modifiers)
{
if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
fParent->_SwitchState(NULL);
}
void
HeaderView::ResizeState::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
float width = fStartWidth + where.x - fStartX;
width = std::max(width, fHeader->MinWidth());
width = std::min(width, fHeader->MaxWidth());
fHeader->SetWidth(width);
}
// #pragma mark - HeaderView
HeaderView::HeaderView()
:
BView("header view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
fModel(NULL),
fHeaderEntries(10, true),
fLayoutValid(false),
fDefaultState(new DefaultState(this)),
fState(fDefaultState)
{
HeaderModel* model = new(std::nothrow) HeaderModel;
BReference<HeaderModel> modelReference(model, true);
SetModel(model);
SetViewColor(B_TRANSPARENT_32_BIT);
}
HeaderView::~HeaderView()
{
SetModel(NULL);
_SwitchState(NULL);
delete fDefaultState;
}
void
HeaderView::Draw(BRect updateRect)
{
if (fModel == NULL)
return;
_ValidateHeadersLayout();
DefaultHeaderRenderer defaultRenderer;
float bottom = Bounds().Height();
float left = 0.0f;
for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
if (Header* header = fModel->HeaderAt(i)) {
HeaderRenderer* renderer = header->GetHeaderRenderer();
if (renderer == NULL)
renderer = &defaultRenderer;
BRect frame(entry->position, 0, entry->position + entry->width - 1,
bottom);
renderer->DrawHeader(this, frame, updateRect, header, 0);
// TODO: flags!
left = entry->position + entry->width;
}
}
// TODO: We are not using any custom renderer here.
defaultRenderer.DrawHeaderBackground(this,
BRect(left, 0, Bounds().right + 1, bottom), updateRect, 0);
}
BSize
HeaderView::MinSize()
{
_ValidateHeadersLayout();
return BLayoutUtils::ComposeSize(ExplicitMinSize(),
BSize(100, fPreferredHeight - 1));
}
BSize
HeaderView::MaxSize()
{
_ValidateHeadersLayout();
return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
BSize(B_SIZE_UNLIMITED, fPreferredHeight - 1));
}
BSize
HeaderView::PreferredSize()
{
_ValidateHeadersLayout();
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
BSize(fPreferredWidth - 1, fPreferredHeight - 1));
}
HeaderModel*
HeaderView::Model() const
{
return fModel;
}
void
HeaderView::MouseDown(BPoint where)
{
// get buttons and modifiers
BMessage* message = Looper()->CurrentMessage();
int32 buttons;
if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
buttons = 0;
int32 modifiers;
if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
modifiers = 0;
fState->MouseDown(where, buttons, modifiers);
}
void
HeaderView::MouseUp(BPoint where)
{
// get buttons and modifiers
BMessage* message = Looper()->CurrentMessage();
int32 buttons;
if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
buttons = 0;
int32 modifiers;
if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
modifiers = 0;
fState->MouseUp(where, buttons, modifiers);
}
void
HeaderView::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
fState->MouseMoved(where, transit, dragMessage);
}
status_t
HeaderView::SetModel(HeaderModel* model)
{
if (model == fModel)
return B_OK;
_SwitchState(NULL);
if (fModel != NULL) {
// remove all headers
for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++)
entry->header->RemoveListener(this);
fHeaderEntries.MakeEmpty();
fModel->RemoveListener(this);
fModel->ReleaseReference();
}
fModel = model;
if (fModel != NULL) {
fModel->AcquireReference();
fModel->AddListener(this);
// create header entries
int32 headerCount = fModel->CountHeaders();
for (int32 i = 0; i < headerCount; i++) {
HeaderEntry* entry = new(std::nothrow) HeaderEntry(
fModel->HeaderAt(i));
if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
delete entry;
return B_NO_MEMORY;
}
entry->header->AddListener(this);
}
}
_InvalidateHeadersLayout(0);
Invalidate();
return B_OK;
}
BRect
HeaderView::HeaderFrame(int32 index) const
{
float bottom = Bounds().Height();
if (HeaderEntry* entry = fHeaderEntries.ItemAt(index)) {
return BRect(entry->position, 0, entry->position + entry->width - 1,
bottom);
}
return BRect();
}
int32
HeaderView::HeaderIndexAt(BPoint point) const
{
float x = point.x;
for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
if (x >= entry->position && x < entry->position + entry->width)
return i;
}
return -1;
}
void
HeaderView::AddListener(HeaderViewListener* listener)
{
fListeners.AddItem(listener);
}
void
HeaderView::RemoveListener(HeaderViewListener* listener)
{
fListeners.RemoveItem(listener);
}
void
HeaderView::HeaderAdded(HeaderModel* model, int32 index)
{
if (Header* header = fModel->HeaderAt(index)) {
HeaderEntry* entry = new(std::nothrow) HeaderEntry(
fModel->HeaderAt(index));
if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
delete entry;
return;
}
header->AddListener(this);
_InvalidateHeadersLayout(index);
}
}
void
HeaderView::HeaderRemoved(HeaderModel* model, int32 index)
{
if (HeaderEntry* entry = fHeaderEntries.RemoveItemAt(index)) {
entry->header->RemoveListener(this);
_InvalidateHeadersLayout(index);
}
}
void
HeaderView::HeaderMoved(HeaderModel* model, int32 fromIndex, int32 toIndex)
{
_InvalidateHeadersLayout(std::min(fromIndex, toIndex));
}
void
HeaderView::HeaderWidthChanged(Header* header)
{
_HeaderPropertiesChanged(header, true, true);
}
void
HeaderView::HeaderWidthRestrictionsChanged(Header* header)
{
// TODO:...
}
void
HeaderView::HeaderValueChanged(Header* header)
{
_HeaderPropertiesChanged(header, true, false);
}
void
HeaderView::HeaderRendererChanged(Header* header)
{
_HeaderPropertiesChanged(header, true, true);
}
void
HeaderView::_HeaderPropertiesChanged(Header* header, bool redrawNeeded,
bool relayoutNeeded)
{
if (!redrawNeeded && !relayoutNeeded)
return;
int32 index = fModel->IndexOfHeader(header);
if (relayoutNeeded)
_InvalidateHeadersLayout(index);
else if (redrawNeeded)
_InvalidateHeaders(index, index + 1);
}
void
HeaderView::_InvalidateHeadersLayout(int32 firstIndex)
{
if (!fLayoutValid)
return;
fLayoutValid = false;
InvalidateLayout();
Invalidate();
}
void
HeaderView::_InvalidateHeaders(int32 firstIndex, int32 endIndex)
{
Invalidate();
// TODO: Be less lazy!
}
void
HeaderView::_ValidateHeadersLayout()
{
if (fLayoutValid)
return;
DefaultHeaderRenderer defaultRenderer;
int32 headerCount = fHeaderEntries.CountItems();
float position = 0;
fPreferredWidth = 0;
fPreferredHeight = 0;
for (int32 i = 0; i < headerCount; i++) {
HeaderEntry* entry = fHeaderEntries.ItemAt(i);
entry->position = position;
if (Header* header = fModel->HeaderAt(i)) {
entry->width = header->Width();
fPreferredWidth += header->PreferredWidth();
} else
entry->width = 0;
position = entry->position + entry->width;
if (Header* header = fModel->HeaderAt(i)) {
HeaderRenderer* renderer = header->GetHeaderRenderer();
if (renderer == NULL)
renderer = &defaultRenderer;
float height = renderer->HeaderHeight(this, header);
if (height > fPreferredHeight)
fPreferredHeight = height;
}
}
fLayoutValid = true;
}
void
HeaderView::_SwitchState(State* newState)
{
if (newState == NULL)
newState = fDefaultState;
fState->Leaving(newState);
newState->Entering(fState);
if (fState != fDefaultState)
delete fState;
fState = newState;
}
// #pragma mark - HeaderViewListener
HeaderViewListener::~HeaderViewListener()
{
}