#include "ColumnListView.h"
#include <typeinfo>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <Application.h>
#include <Bitmap.h>
#include <ControlLook.h>
#include <Cursor.h>
#include <Debug.h>
#include <GraphicsDefs.h>
#include <LayoutUtils.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <ScrollBar.h>
#include <String.h>
#include <SupportDefs.h>
#include <Window.h>
#include <ObjectListPrivate.h>
#include "ObjectList.h"
#define DOUBLE_BUFFERED_COLUMN_RESIZE 1
#define SMART_REDRAW 1
#define DRAG_TITLE_OUTLINE 1
#define CONSTRAIN_CLIPPING_REGION 1
#define LOWER_SCROLLBAR 0
namespace BPrivate {
static const unsigned char kDownSortArrow8x8[] = {
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff
};
static const unsigned char kUpSortArrow8x8[] = {
0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff
};
static const unsigned char kDownSortArrow8x8Invert[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff
};
static const unsigned char kUpSortArrow8x8Invert[] = {
0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static const float kTintedLineTint = 1.04;
static const float kTintedLineTintDark = 0.90;
static const float kMinTitleHeight = 16.0;
static const float kMinRowHeight = 16.0;
static const float kTitleSpacing = 1.4;
static const float kRowSpacing = 1.4;
static const float kLatchWidth = 15.0;
static const int32 kMaxDepth = 1024;
static const float kLeftMargin = kLatchWidth;
static const float kRightMargin = 8;
static const float kOutlineLevelIndent = kLatchWidth;
static const float kColumnResizeAreaWidth = 10.0;
static const float kRowDragSensitivity = 5.0;
static const float kDoubleClickMoveSensitivity = 4.0;
static const float kSortIndicatorWidth = 9.0;
static const float kDropHighlightLineHeight = 2.0;
static const uint32 kToggleColumn = 'BTCL';
#ifdef DOUBLE_BUFFERED_COLUMN_RESIZE
class ColumnResizeBufferView : public BView
{
public:
ColumnResizeBufferView();
virtual ~ColumnResizeBufferView();
void UpdateMaxWidth(float width);
void UpdateMaxHeight(float height);
bool Lock();
void Unlock();
const BBitmap* Bitmap();
private:
void _InitBitmap();
void _FreeBitmap();
BBitmap* fDrawBuffer;
};
#endif
class BRowContainer : public BObjectList<BRow>
{
};
class TitleView : public BView {
typedef BView _inherited;
public:
TitleView(BRect frame, OutlineView* outlineView,
BList* visibleColumns, BList* sortColumns,
BColumnListView* masterView,
uint32 resizingMode);
virtual ~TitleView();
void ColumnAdded(BColumn* column);
void ColumnResized(BColumn* column, float oldWidth);
void SetColumnVisible(BColumn* column, bool visible);
virtual void Draw(BRect updateRect);
virtual void ScrollTo(BPoint where);
virtual void MessageReceived(BMessage* message);
virtual void MouseDown(BPoint where);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage);
virtual void MouseUp(BPoint where);
virtual void FrameResized(float width, float height);
void MoveColumn(BColumn* column, int32 index);
void SetColumnFlags(column_flags flags);
void SetEditMode(bool state)
{ fEditMode = state; }
float MarginWidth() const;
private:
void GetTitleRect(BColumn* column, BRect* _rect);
int32 FindColumn(BPoint where, float* _leftEdge);
void FixScrollBar(bool scrollToFit);
void DragSelectedColumn(BPoint where);
void ResizeSelectedColumn(BPoint where,
bool preferred = false);
void ComputeDragBoundries(BColumn* column,
BPoint where);
void DrawTitle(BView* view, BRect frame,
BColumn* column, bool depressed);
float _VirtualWidth() const;
OutlineView* fOutlineView;
BList* fColumns;
BList* fSortColumns;
BRect fVisibleRect;
enum {
INACTIVE,
RESIZING_COLUMN,
PRESSING_COLUMN,
DRAG_COLUMN_INSIDE_TITLE,
DRAG_COLUMN_OUTSIDE_TITLE
} fCurrentState;
BPopUpMenu* fColumnPop;
BColumnListView* fMasterView;
bool fEditMode;
int32 fColumnFlags;
BColumn* fSelectedColumn;
BRect fSelectedColumnRect;
bool fResizingFirstColumn;
BPoint fClickPoint;
float fLeftDragBoundry;
float fRightDragBoundry;
BPoint fCurrentDragPosition;
BBitmap* fUpSortArrow;
BBitmap* fDownSortArrow;
BCursor* fResizeCursor;
BCursor* fMinResizeCursor;
BCursor* fMaxResizeCursor;
BCursor* fColumnMoveCursor;
};
class OutlineView : public BView {
typedef BView _inherited;
public:
OutlineView(BRect, BList* visibleColumns,
BList* sortColumns,
BColumnListView* listView);
virtual ~OutlineView();
virtual void Draw(BRect);
const BRect& VisibleRect() const;
void RedrawColumn(BColumn* column, float leftEdge,
bool isFirstColumn);
void StartSorting();
float GetColumnPreferredWidth(BColumn* column);
void AddRows(BList* rows, int32 index, BRow* parentRow);
void AddRow(BRow*, int32 index, BRow* TheRow);
BRow* CurrentSelection(BRow* lastSelected) const;
void ToggleFocusRowSelection(bool selectRange);
void ToggleFocusRowOpen();
void ChangeFocusRow(bool up, bool updateSelection,
bool addToCurrentSelection);
void MoveFocusToVisibleRect();
void ExpandOrCollapse(BRow* parent, bool expand);
void RemoveRows(BList* rows);
void RemoveRow(BRow*);
BRowContainer* RowList();
void UpdateRow(BRow*);
bool FindParent(BRow* row, BRow** _parent,
bool* _isVisible);
int32 IndexOf(BRow* row);
void Deselect(BRow*);
void AddToSelection(BRow*);
void DeselectAll();
BRow* FocusRow() const;
void SetFocusRow(BRow* row, bool select);
BRow* FindRow(float ypos, int32* _indent,
float* _top);
bool FindRect(const BRow* row, BRect* _rect);
void ScrollTo(const BRow* row);
void Clear();
void SetSelectionMode(list_view_type type);
list_view_type SelectionMode() const;
void SetMouseTrackingEnabled(bool);
void FixScrollBar(bool scrollToFit);
void SetEditMode(bool state)
{ fEditMode = state; }
virtual void FrameResized(float width, float height);
virtual void ScrollTo(BPoint where);
virtual void MouseDown(BPoint where);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage);
virtual void MouseUp(BPoint where);
virtual void MessageReceived(BMessage* message);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
ColumnResizeBufferView* ResizeBufferView();
#endif
private:
bool SortList(BRowContainer* list, bool isVisible);
static int32 DeepSortThreadEntry(void* outlineView);
void DeepSort();
void SelectRange(BRow* start, BRow* end);
int32 CompareRows(BRow* row1, BRow* row2);
int32 AddRowToParentOnly(BRow* row, int32 index,
BRow* parent);
int32 AddSorted(BRowContainer* list, BRow* row);
void RecursiveDeleteRows(BRowContainer* list,
bool owner);
void InvalidateCachedPositions();
bool FindVisibleRect(BRow* row, BRect* _rect);
bool RemoveRowFromSelectionOnly(BRow* row);
BList* fColumns;
BList* fSortColumns;
float fItemsHeight;
BRowContainer fRows;
BRect fVisibleRect;
#if DOUBLE_BUFFERED_COLUMN_RESIZE
ColumnResizeBufferView* fResizeBufferView;
#endif
BRow* fFocusRow;
BRect fFocusRowRect;
BRow* fRollOverRow;
BRow fSelectionListDummyHead;
BRow* fLastSelectedItem;
BRow* fFirstSelectedItem;
thread_id fSortThread;
int32 fNumSorted;
bool fSortCancelled;
enum CurrentState {
INACTIVE,
LATCH_CLICKED,
ROW_CLICKED,
DRAGGING_ROWS
};
CurrentState fCurrentState;
BColumnListView* fMasterView;
list_view_type fSelectionMode;
bool fTrackMouse;
BField* fCurrentField;
BRow* fCurrentRow;
BColumn* fCurrentColumn;
bool fMouseDown;
BRect fFieldRect;
int32 fCurrentCode;
bool fEditMode;
BPoint fClickPoint;
bool fDragging;
int32 fClickCount;
BRow* fTargetRow;
float fTargetRowTop;
BRect fLatchRect;
float fDropHighlightY;
friend class RecursiveOutlineIterator;
};
class RecursiveOutlineIterator {
public:
RecursiveOutlineIterator(
BRowContainer* container,
bool openBranchesOnly = true);
BRow* CurrentRow() const;
int32 CurrentLevel() const;
void GoToNext();
private:
struct {
BRowContainer* fRowSet;
int32 fIndex;
int32 fDepth;
} fStack[kMaxDepth];
int32 fStackIndex;
BRowContainer* fCurrentList;
int32 fCurrentListIndex;
int32 fCurrentListDepth;
bool fOpenBranchesOnly;
};
}
using namespace BPrivate;
#ifdef DOUBLE_BUFFERED_COLUMN_RESIZE
ColumnResizeBufferView::ColumnResizeBufferView()
: BView(BRect(0, 0, 600, 35), "double_buffer_view", B_FOLLOW_ALL_SIDES, 0), fDrawBuffer(NULL)
{
_InitBitmap();
}
ColumnResizeBufferView::~ColumnResizeBufferView()
{
_FreeBitmap();
}
void
ColumnResizeBufferView::UpdateMaxWidth(float width)
{
Lock();
BRect bounds = Bounds();
Unlock();
if (width > bounds.Width()) {
Lock();
ResizeTo(width, bounds.Height());
Unlock();
_InitBitmap();
}
}
void
ColumnResizeBufferView::UpdateMaxHeight(float height)
{
Lock();
BRect bounds = Bounds();
Unlock();
if (height > bounds.Height()) {
Lock();
ResizeTo(bounds.Width(), height);
Unlock();
_InitBitmap();
}
}
bool
ColumnResizeBufferView::Lock()
{
return fDrawBuffer->Lock();
}
void
ColumnResizeBufferView::Unlock()
{
fDrawBuffer->Unlock();
}
const BBitmap*
ColumnResizeBufferView::Bitmap()
{
return fDrawBuffer;
}
void
ColumnResizeBufferView::_InitBitmap()
{
_FreeBitmap();
fDrawBuffer = new BBitmap(Bounds(), B_RGB32, true);
fDrawBuffer->Lock();
fDrawBuffer->AddChild(this);
fDrawBuffer->Unlock();
}
void
ColumnResizeBufferView::_FreeBitmap()
{
if (fDrawBuffer) {
fDrawBuffer->Lock();
fDrawBuffer->RemoveChild(this);
fDrawBuffer->Unlock();
delete fDrawBuffer;
fDrawBuffer = NULL;
}
}
#endif
BField::BField()
{
}
BField::~BField()
{
}
void
BColumn::MouseMoved(BColumnListView* , BRow* ,
BField* , BRect , BPoint,
uint32 , int32 )
{
}
void
BColumn::MouseDown(BColumnListView* , BRow* ,
BField* , BRect , BPoint ,
uint32 )
{
}
void
BColumn::MouseUp(BColumnListView* , BRow* , BField* )
{
}
BRow::BRow()
:
fChildList(NULL),
fIsExpanded(false),
fHeight(std::max(kMinRowHeight,
ceilf(be_plain_font->Size() * kRowSpacing))),
fNextSelected(NULL),
fPrevSelected(NULL),
fParent(NULL),
fList(NULL)
{
}
BRow::BRow(float height)
:
fChildList(NULL),
fIsExpanded(false),
fHeight(height),
fNextSelected(NULL),
fPrevSelected(NULL),
fParent(NULL),
fList(NULL)
{
}
BRow::~BRow()
{
while (true) {
BField* field = (BField*) fFields.RemoveItem((int32)0);
if (field == 0)
break;
delete field;
}
}
bool
BRow::HasLatch() const
{
return fChildList != 0;
}
int32
BRow::CountFields() const
{
return fFields.CountItems();
}
BField*
BRow::GetField(int32 index)
{
return (BField*)fFields.ItemAt(index);
}
const BField*
BRow::GetField(int32 index) const
{
return (const BField*)fFields.ItemAt(index);
}
void
BRow::SetField(BField* field, int32 logicalFieldIndex)
{
if (fFields.ItemAt(logicalFieldIndex) != 0)
delete (BField*)fFields.RemoveItem(logicalFieldIndex);
if (NULL != fList) {
ValidateField(field, logicalFieldIndex);
Invalidate();
}
fFields.AddItem(field, logicalFieldIndex);
}
float
BRow::Height() const
{
return fHeight;
}
bool
BRow::IsExpanded() const
{
return fIsExpanded;
}
bool
BRow::IsSelected() const
{
return fPrevSelected != NULL;
}
void
BRow::Invalidate()
{
if (fList != NULL)
fList->InvalidateRow(this);
}
void
BRow::ValidateFields() const
{
for (int32 i = 0; i < CountFields(); i++)
ValidateField(GetField(i), i);
}
void
BRow::ValidateField(const BField* field, int32 logicalFieldIndex) const
{
BColumn* column = NULL;
int32 items = fList->CountColumns();
for (int32 i = 0 ; i < items; ++i) {
column = fList->ColumnAt(i);
if(column->LogicalFieldNum() == logicalFieldIndex )
break;
}
if (column == NULL) {
BString dbmessage("\n\n\tThe parent BColumnListView does not have "
"\n\ta BColumn at the logical field index ");
dbmessage << logicalFieldIndex << ".\n";
puts(dbmessage.String());
} else {
if (!column->AcceptsField(field)) {
BString dbmessage("\n\n\tThe BColumn of type ");
dbmessage << typeid(*column).name() << "\n\tat logical field index "
<< logicalFieldIndex << "\n\tdoes not support the field type "
<< typeid(*field).name() << ".\n\n";
debugger(dbmessage.String());
}
}
}
BColumn::BColumn(float width, float minWidth, float maxWidth, alignment align)
:
fWidth(width),
fMinWidth(minWidth),
fMaxWidth(maxWidth),
fVisible(true),
fList(0),
fShowHeading(true),
fAlignment(align)
{
}
BColumn::~BColumn()
{
}
float
BColumn::Width() const
{
return fWidth;
}
void
BColumn::SetWidth(float width)
{
fWidth = width;
}
float
BColumn::MinWidth() const
{
return fMinWidth;
}
float
BColumn::MaxWidth() const
{
return fMaxWidth;
}
void
BColumn::DrawTitle(BRect, BView*)
{
}
void
BColumn::DrawField(BField*, BRect, BView*)
{
}
int
BColumn::CompareFields(BField*, BField*)
{
return 0;
}
void
BColumn::GetColumnName(BString* into) const
{
*into = "(Unnamed)";
}
float
BColumn::GetPreferredWidth(BField* field, BView* parent) const
{
return fWidth;
}
bool
BColumn::IsVisible() const
{
return fVisible;
}
void
BColumn::SetVisible(bool visible)
{
if (fList && (fVisible != visible))
fList->SetColumnVisible(this, visible);
}
bool
BColumn::ShowHeading() const
{
return fShowHeading;
}
void
BColumn::SetShowHeading(bool state)
{
fShowHeading = state;
}
alignment
BColumn::Alignment() const
{
return fAlignment;
}
void
BColumn::SetAlignment(alignment align)
{
fAlignment = align;
}
bool
BColumn::WantsEvents() const
{
return fWantsEvents;
}
void
BColumn::SetWantsEvents(bool state)
{
fWantsEvents = state;
}
int32
BColumn::LogicalFieldNum() const
{
return fFieldID;
}
bool
BColumn::AcceptsField(const BField*) const
{
return true;
}
BColumnListView::BColumnListView(BRect rect, const char* name,
uint32 resizingMode, uint32 flags, border_style border,
bool showHorizontalScrollbar)
:
BView(rect, name, resizingMode,
flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
fStatusView(NULL),
fSelectionMessage(NULL),
fSortingEnabled(true),
fLatchWidth(kLatchWidth),
fBorderStyle(border),
fShowingHorizontalScrollBar(showHorizontalScrollbar)
{
_Init();
}
BColumnListView::BColumnListView(const char* name, uint32 flags,
border_style border, bool showHorizontalScrollbar)
:
BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
fStatusView(NULL),
fSelectionMessage(NULL),
fSortingEnabled(true),
fLatchWidth(kLatchWidth),
fBorderStyle(border),
fShowingHorizontalScrollBar(showHorizontalScrollbar)
{
_Init();
}
BColumnListView::~BColumnListView()
{
while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
delete column;
}
bool
BColumnListView::InitiateDrag(BPoint, bool)
{
return false;
}
void
BColumnListView::MessageDropped(BMessage*, BPoint)
{
}
void
BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
{
fOutlineView->ExpandOrCollapse(row, Open);
}
status_t
BColumnListView::Invoke(BMessage* message)
{
if (message == 0)
message = Message();
return BInvoker::Invoke(message);
}
void
BColumnListView::ItemInvoked()
{
Invoke();
}
void
BColumnListView::SetInvocationMessage(BMessage* message)
{
SetMessage(message);
}
BMessage*
BColumnListView::InvocationMessage() const
{
return Message();
}
uint32
BColumnListView::InvocationCommand() const
{
return Command();
}
BRow*
BColumnListView::FocusRow() const
{
return fOutlineView->FocusRow();
}
void
BColumnListView::SetFocusRow(int32 Index, bool Select)
{
SetFocusRow(RowAt(Index), Select);
}
void
BColumnListView::SetFocusRow(BRow* row, bool Select)
{
fOutlineView->SetFocusRow(row, Select);
}
void
BColumnListView::SetMouseTrackingEnabled(bool Enabled)
{
fOutlineView->SetMouseTrackingEnabled(Enabled);
}
list_view_type
BColumnListView::SelectionMode() const
{
return fOutlineView->SelectionMode();
}
void
BColumnListView::Deselect(BRow* row)
{
fOutlineView->Deselect(row);
}
void
BColumnListView::AddToSelection(BRow* row)
{
fOutlineView->AddToSelection(row);
}
void
BColumnListView::DeselectAll()
{
fOutlineView->DeselectAll();
}
BRow*
BColumnListView::CurrentSelection(BRow* lastSelected) const
{
return fOutlineView->CurrentSelection(lastSelected);
}
void
BColumnListView::SelectionChanged()
{
if (fSelectionMessage)
Invoke(fSelectionMessage);
}
void
BColumnListView::SetSelectionMessage(BMessage* message)
{
if (fSelectionMessage == message)
return;
delete fSelectionMessage;
fSelectionMessage = message;
}
BMessage*
BColumnListView::SelectionMessage()
{
return fSelectionMessage;
}
uint32
BColumnListView::SelectionCommand() const
{
if (fSelectionMessage)
return fSelectionMessage->what;
return 0;
}
void
BColumnListView::SetSelectionMode(list_view_type mode)
{
fOutlineView->SetSelectionMode(mode);
}
void
BColumnListView::SetSortingEnabled(bool enabled)
{
fSortingEnabled = enabled;
fSortColumns.MakeEmpty();
fTitleView->Invalidate();
}
bool
BColumnListView::SortingEnabled() const
{
return fSortingEnabled;
}
void
BColumnListView::SetSortColumn(BColumn* column, bool add, bool ascending)
{
if (!SortingEnabled())
return;
if (!add)
fSortColumns.MakeEmpty();
if (!fSortColumns.HasItem(column))
fSortColumns.AddItem(column);
column->fSortAscending = ascending;
fTitleView->Invalidate();
fOutlineView->StartSorting();
}
void
BColumnListView::ClearSortColumns()
{
fSortColumns.MakeEmpty();
fTitleView->Invalidate();
}
void
BColumnListView::AddStatusView(BView* view)
{
BRect bounds = Bounds();
float width = view->Bounds().Width();
if (width > bounds.Width() / 2)
width = bounds.Width() / 2;
fStatusView = view;
Window()->BeginViewTransaction();
fHorizontalScrollBar->ResizeBy(-(width + 1), 0);
fHorizontalScrollBar->MoveBy((width + 1), 0);
AddChild(view);
BRect viewRect(bounds);
viewRect.right = width;
viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT;
if (fBorderStyle == B_PLAIN_BORDER)
viewRect.OffsetBy(1, -1);
else if (fBorderStyle == B_FANCY_BORDER)
viewRect.OffsetBy(2, -2);
view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM);
view->ResizeTo(viewRect.Width(), viewRect.Height());
view->MoveTo(viewRect.left, viewRect.top);
Window()->EndViewTransaction();
}
BView*
BColumnListView::RemoveStatusView()
{
if (fStatusView) {
float width = fStatusView->Bounds().Width();
Window()->BeginViewTransaction();
fStatusView->RemoveSelf();
fHorizontalScrollBar->MoveBy(-width, 0);
fHorizontalScrollBar->ResizeBy(width, 0);
Window()->EndViewTransaction();
}
BView* view = fStatusView;
fStatusView = 0;
return view;
}
void
BColumnListView::AddColumn(BColumn* column, int32 logicalFieldIndex)
{
ASSERT(column != NULL);
column->fList = this;
column->fFieldID = logicalFieldIndex;
for (int32 index = 0; index < fColumns.CountItems(); index++) {
BColumn* existingColumn = (BColumn*) fColumns.ItemAt(index);
if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) {
RemoveColumn(existingColumn);
break;
}
}
if (column->Width() < column->MinWidth())
column->SetWidth(column->MinWidth());
else if (column->Width() > column->MaxWidth())
column->SetWidth(column->MaxWidth());
fColumns.AddItem((void*) column);
fTitleView->ColumnAdded(column);
}
void
BColumnListView::MoveColumn(BColumn* column, int32 index)
{
ASSERT(column != NULL);
fTitleView->MoveColumn(column, index);
}
void
BColumnListView::RemoveColumn(BColumn* column)
{
if (fColumns.HasItem(column)) {
SetColumnVisible(column, false);
if (Window() != NULL)
Window()->UpdateIfNeeded();
fColumns.RemoveItem(column);
}
}
int32
BColumnListView::CountColumns() const
{
return fColumns.CountItems();
}
BColumn*
BColumnListView::ColumnAt(int32 field) const
{
return (BColumn*) fColumns.ItemAt(field);
}
BColumn*
BColumnListView::ColumnAt(BPoint point) const
{
float left = MAX(kLeftMargin, LatchWidth());
for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
if (column == NULL || !column->IsVisible())
continue;
float right = left + column->Width();
if (point.x >= left && point.x <= right)
return column;
left = right + 1;
}
return NULL;
}
void
BColumnListView::SetColumnVisible(BColumn* column, bool visible)
{
fTitleView->SetColumnVisible(column, visible);
}
void
BColumnListView::SetColumnVisible(int32 index, bool isVisible)
{
BColumn* column = ColumnAt(index);
if (column != NULL)
column->SetVisible(isVisible);
}
bool
BColumnListView::IsColumnVisible(int32 index) const
{
BColumn* column = ColumnAt(index);
if (column != NULL)
return column->IsVisible();
return false;
}
void
BColumnListView::SetColumnFlags(column_flags flags)
{
fTitleView->SetColumnFlags(flags);
}
void
BColumnListView::ResizeColumnToPreferred(int32 index)
{
BColumn* column = ColumnAt(index);
if (column == NULL)
return;
float width = fOutlineView->GetColumnPreferredWidth(column);
float oldWidth = column->Width();
column->SetWidth(width);
fTitleView->ColumnResized(column, oldWidth);
fOutlineView->Invalidate();
}
void
BColumnListView::ResizeAllColumnsToPreferred()
{
int32 count = CountColumns();
for (int32 i = 0; i < count; i++)
ResizeColumnToPreferred(i);
}
const BRow*
BColumnListView::RowAt(int32 Index, BRow* parentRow) const
{
if (parentRow == 0)
return fOutlineView->RowList()->ItemAt(Index);
return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
}
BRow*
BColumnListView::RowAt(int32 Index, BRow* parentRow)
{
if (parentRow == 0)
return fOutlineView->RowList()->ItemAt(Index);
return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
}
const BRow*
BColumnListView::RowAt(BPoint point) const
{
float top;
int32 indent;
return fOutlineView->FindRow(point.y, &indent, &top);
}
BRow*
BColumnListView::RowAt(BPoint point)
{
float top;
int32 indent;
return fOutlineView->FindRow(point.y, &indent, &top);
}
bool
BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
{
return fOutlineView->FindRect(row, outRect);
}
bool
BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
{
return fOutlineView->FindParent(row, _parent, _isVisible);
}
int32
BColumnListView::IndexOf(BRow* row)
{
return fOutlineView->IndexOf(row);
}
int32
BColumnListView::CountRows(BRow* parentRow) const
{
if (parentRow == 0)
return fOutlineView->RowList()->CountItems();
if (parentRow->fChildList)
return parentRow->fChildList->CountItems();
else
return 0;
}
void
BColumnListView::AddRow(BRow* row, BRow* parentRow)
{
AddRow(row, -1, parentRow);
}
void
BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
{
row->fChildList = 0;
row->fList = this;
row->ValidateFields();
fOutlineView->AddRow(row, index, parentRow);
}
void
BColumnListView::AddRows(BList* rows, int32 index, BRow* parent)
{
for (int32 i = rows->CountItems() - 1; i >= 0; i--) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
row->fChildList = 0;
row->fList = this;
row->ValidateFields();
}
fOutlineView->AddRows(rows, index, parent);
}
void
BColumnListView::RemoveRow(BRow* row)
{
fOutlineView->RemoveRow(row);
row->fList = NULL;
}
void
BColumnListView::RemoveRows(BList* rows)
{
fOutlineView->RemoveRows(rows);
for (int32 i = rows->CountItems() - 1; i >= 0; i--) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
row->fList = NULL;
}
}
void
BColumnListView::UpdateRow(BRow* row)
{
fOutlineView->UpdateRow(row);
}
bool
BColumnListView::SwapRows(int32 index1, int32 index2, BRow* parentRow1,
BRow* parentRow2)
{
BRow* row1 = NULL;
BRow* row2 = NULL;
BRowContainer* container1 = NULL;
BRowContainer* container2 = NULL;
if (parentRow1 == NULL)
container1 = fOutlineView->RowList();
else
container1 = parentRow1->fChildList;
if (container1 == NULL)
return false;
if (parentRow2 == NULL)
container2 = fOutlineView->RowList();
else
container2 = parentRow2->fChildList;
if (container2 == NULL)
return false;
row1 = container1->ItemAt(index1);
if (row1 == NULL)
return false;
row2 = container2->ItemAt(index2);
if (row2 == NULL)
return false;
container1->ReplaceItem(index2, row1);
container2->ReplaceItem(index1, row2);
BRect rect1;
BRect rect2;
BRect rect;
fOutlineView->FindRect(row1, &rect1);
fOutlineView->FindRect(row2, &rect2);
rect = rect1 | rect2;
fOutlineView->Invalidate(rect);
return true;
}
void
BColumnListView::ScrollTo(const BRow* row)
{
fOutlineView->ScrollTo(row);
}
void
BColumnListView::ScrollTo(BPoint point)
{
fOutlineView->ScrollTo(point);
}
void
BColumnListView::Clear()
{
fOutlineView->Clear();
}
void
BColumnListView::InvalidateRow(BRow* row)
{
BRect updateRect;
GetRowRect(row, &updateRect);
if (fOutlineView->VisibleRect().Intersects(updateRect))
fOutlineView->Invalidate(updateRect);
}
void
BColumnListView::SetFont(const BFont* font, uint32 mask)
{
fOutlineView->SetFont(font, mask);
fTitleView->SetFont(font, mask);
}
void
BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font,
uint32 mask)
{
switch (font_num) {
case B_FONT_ROW:
fOutlineView->SetFont(font, mask);
break;
case B_FONT_HEADER:
fTitleView->SetFont(font, mask);
break;
default:
ASSERT(false);
break;
}
}
void
BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const
{
switch (font_num) {
case B_FONT_ROW:
fOutlineView->GetFont(font);
break;
case B_FONT_HEADER:
fTitleView->GetFont(font);
break;
default:
ASSERT(false);
break;
}
}
void
BColumnListView::SetColor(ColumnListViewColor colorIndex, const rgb_color color)
{
if ((int)colorIndex < 0) {
ASSERT(false);
colorIndex = (ColumnListViewColor)0;
}
if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
ASSERT(false);
colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
}
fColorList[colorIndex] = color;
fCustomColors = true;
}
void
BColumnListView::ResetColors()
{
fCustomColors = false;
_UpdateColors();
Invalidate();
}
rgb_color
BColumnListView::Color(ColumnListViewColor colorIndex) const
{
if ((int)colorIndex < 0) {
ASSERT(false);
colorIndex = (ColumnListViewColor)0;
}
if ((int)colorIndex >= (int)B_COLOR_TOTAL) {
ASSERT(false);
colorIndex = (ColumnListViewColor)(B_COLOR_TOTAL - 1);
}
return fColorList[colorIndex];
}
void
BColumnListView::SetHighColor(rgb_color color)
{
BView::SetHighColor(color);
}
void
BColumnListView::SetSelectionColor(rgb_color color)
{
fColorList[B_COLOR_SELECTION] = color;
fCustomColors = true;
}
void
BColumnListView::SetBackgroundColor(rgb_color color)
{
fColorList[B_COLOR_BACKGROUND] = color;
fCustomColors = true;
fOutlineView->Invalidate();
}
void
BColumnListView::SetEditColor(rgb_color color)
{
fColorList[B_COLOR_EDIT_BACKGROUND] = color;
fCustomColors = true;
}
const rgb_color
BColumnListView::SelectionColor() const
{
return fColorList[B_COLOR_SELECTION];
}
const rgb_color
BColumnListView::BackgroundColor() const
{
return fColorList[B_COLOR_BACKGROUND];
}
const rgb_color
BColumnListView::EditColor() const
{
return fColorList[B_COLOR_EDIT_BACKGROUND];
}
BPoint
BColumnListView::SuggestTextPosition(const BRow* row,
const BColumn* inColumn) const
{
BRect rect(GetFieldRect(row, inColumn));
font_height fh;
fOutlineView->GetFontHeight(&fh);
float baseline = floor(rect.top + fh.ascent
+ (rect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
return BPoint(rect.left + 8, baseline);
}
BRect
BColumnListView::GetFieldRect(const BRow* row, const BColumn* inColumn) const
{
BRect rect;
GetRowRect(row, &rect);
if (inColumn != NULL) {
float leftEdge = MAX(kLeftMargin, LatchWidth());
for (int index = 0; index < fColumns.CountItems(); index++) {
BColumn* column = (BColumn*) fColumns.ItemAt(index);
if (column == NULL || !column->IsVisible())
continue;
if (column == inColumn) {
rect.left = leftEdge;
rect.right = rect.left + column->Width();
break;
}
leftEdge += column->Width() + 1;
}
}
return rect;
}
void
BColumnListView::SetLatchWidth(float width)
{
fLatchWidth = width;
Invalidate();
}
float
BColumnListView::LatchWidth() const
{
return fLatchWidth;
}
void
BColumnListView::DrawLatch(BView* view, BRect rect, LatchType position, BRow*)
{
const int32 rectInset = 4;
int32 sideLen = rect.IntegerWidth();
if (sideLen > rect.IntegerHeight())
sideLen = rect.IntegerHeight();
int32 halfWidth = rect.IntegerWidth() / 2;
int32 halfHeight = rect.IntegerHeight() / 2;
int32 halfSide = sideLen / 2;
float left = rect.left + halfWidth - halfSide;
float top = rect.top + halfHeight - halfSide;
BRect itemRect(left, top, left + sideLen, top + sideLen);
itemRect.OffsetBy(0, -1);
itemRect.InsetBy(rectInset, rectInset);
if ((itemRect.IntegerWidth() % 2) == 1) {
itemRect.right += 1;
itemRect.bottom += 1;
}
rgb_color highColor = view->HighColor();
if (highColor.IsLight())
view->SetHighColor(make_color(0, 0, 0));
else
view->SetHighColor(make_color(255, 255, 255));
switch (position) {
case B_OPEN_LATCH:
view->StrokeRect(itemRect);
view->StrokeLine(
BPoint(itemRect.left + 2,
(itemRect.top + itemRect.bottom) / 2),
BPoint(itemRect.right - 2,
(itemRect.top + itemRect.bottom) / 2));
break;
case B_PRESSED_LATCH:
view->StrokeRect(itemRect);
view->StrokeLine(
BPoint(itemRect.left + 2,
(itemRect.top + itemRect.bottom) / 2),
BPoint(itemRect.right - 2,
(itemRect.top + itemRect.bottom) / 2));
view->StrokeLine(
BPoint((itemRect.left + itemRect.right) / 2,
itemRect.top + 2),
BPoint((itemRect.left + itemRect.right) / 2,
itemRect.bottom - 2));
view->InvertRect(itemRect);
break;
case B_CLOSED_LATCH:
view->StrokeRect(itemRect);
view->StrokeLine(
BPoint(itemRect.left + 2,
(itemRect.top + itemRect.bottom) / 2),
BPoint(itemRect.right - 2,
(itemRect.top + itemRect.bottom) / 2));
view->StrokeLine(
BPoint((itemRect.left + itemRect.right) / 2,
itemRect.top + 2),
BPoint((itemRect.left + itemRect.right) / 2,
itemRect.bottom - 2));
break;
case B_NO_LATCH:
default:
break;
}
view->SetHighColor(highColor);
}
void
BColumnListView::MakeFocus(bool isFocus)
{
if (fBorderStyle != B_NO_BORDER) {
Invalidate();
fHorizontalScrollBar->SetBorderHighlighted(isFocus);
fVerticalScrollBar->SetBorderHighlighted(isFocus);
}
BView::MakeFocus(isFocus);
}
void
BColumnListView::MessageReceived(BMessage* message)
{
if (message->what == B_MOUSE_WHEEL_CHANGED) {
bool handled;
if (message->FindBool("be:clvhandled", &handled) != B_OK) {
message->AddBool("be:clvhandled", true);
fOutlineView->MessageReceived(message);
return;
}
} else if (message->what == B_COLORS_UPDATED) {
_UpdateColors();
}
BView::MessageReceived(message);
}
void
BColumnListView::KeyDown(const char* bytes, int32 numBytes)
{
char key = bytes[0];
switch (key) {
case B_RIGHT_ARROW:
case B_LEFT_ARROW:
{
if ((modifiers() & B_SHIFT_KEY) != 0) {
float minVal, maxVal;
fHorizontalScrollBar->GetRange(&minVal, &maxVal);
float smallStep, largeStep;
fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
float oldVal = fHorizontalScrollBar->Value();
float newVal = oldVal;
if (key == B_LEFT_ARROW)
newVal -= smallStep;
else if (key == B_RIGHT_ARROW)
newVal += smallStep;
if (newVal < minVal)
newVal = minVal;
else if (newVal > maxVal)
newVal = maxVal;
fHorizontalScrollBar->SetValue(newVal);
} else {
BRow* focusRow = fOutlineView->FocusRow();
if (focusRow == NULL)
break;
bool isExpanded = focusRow->HasLatch()
&& focusRow->IsExpanded();
switch (key) {
case B_LEFT_ARROW:
if (isExpanded)
fOutlineView->ToggleFocusRowOpen();
else if (focusRow->fParent != NULL) {
fOutlineView->DeselectAll();
fOutlineView->SetFocusRow(focusRow->fParent, true);
fOutlineView->ScrollTo(focusRow->fParent);
}
break;
case B_RIGHT_ARROW:
if (!isExpanded)
fOutlineView->ToggleFocusRowOpen();
else
fOutlineView->ChangeFocusRow(false, true, false);
break;
}
}
break;
}
case B_DOWN_ARROW:
fOutlineView->ChangeFocusRow(false,
(modifiers() & B_CONTROL_KEY) == 0,
(modifiers() & B_SHIFT_KEY) != 0);
break;
case B_UP_ARROW:
fOutlineView->ChangeFocusRow(true,
(modifiers() & B_CONTROL_KEY) == 0,
(modifiers() & B_SHIFT_KEY) != 0);
break;
case B_PAGE_UP:
case B_PAGE_DOWN:
{
float minValue, maxValue;
fVerticalScrollBar->GetRange(&minValue, &maxValue);
float smallStep, largeStep;
fVerticalScrollBar->GetSteps(&smallStep, &largeStep);
float currentValue = fVerticalScrollBar->Value();
float newValue = currentValue;
if (key == B_PAGE_UP)
newValue -= largeStep;
else
newValue += largeStep;
if (newValue > maxValue)
newValue = maxValue;
else if (newValue < minValue)
newValue = minValue;
fVerticalScrollBar->SetValue(newValue);
if (modifiers() & B_OPTION_KEY)
fOutlineView->MoveFocusToVisibleRect();
break;
}
case B_ENTER:
Invoke();
break;
case B_SPACE:
fOutlineView->ToggleFocusRowSelection(
(modifiers() & B_SHIFT_KEY) != 0);
break;
case '+':
fOutlineView->ToggleFocusRowOpen();
break;
default:
BView::KeyDown(bytes, numBytes);
}
}
void
BColumnListView::AttachedToWindow()
{
if (!Messenger().IsValid())
SetTarget(Window());
if (SortingEnabled()) fOutlineView->StartSorting();
}
void
BColumnListView::WindowActivated(bool active)
{
fOutlineView->Invalidate();
Invalidate();
BView::WindowActivated(active);
}
void
BColumnListView::Draw(BRect updateRect)
{
BRect rect = Bounds();
uint32 flags = 0;
if (IsFocus() && Window()->IsActive())
flags |= BControlLook::B_FOCUSED;
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
BRect verticalScrollBarFrame;
if (!fVerticalScrollBar->IsHidden())
verticalScrollBarFrame = fVerticalScrollBar->Frame();
BRect horizontalScrollBarFrame;
if (!fHorizontalScrollBar->IsHidden())
horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
if (fBorderStyle == B_NO_BORDER) {
rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
SetHighColor(borderColor);
StrokeLine(BPoint(rect.left, rect.bottom),
BPoint(rect.left, rect.top));
StrokeLine(BPoint(rect.left + 1, rect.top),
BPoint(rect.right, rect.top));
}
be_control_look->DrawScrollViewFrame(this, rect, updateRect,
verticalScrollBarFrame, horizontalScrollBarFrame,
base, fBorderStyle, flags);
if (fStatusView != NULL) {
rect = Bounds();
BRegion region(rect & fStatusView->Frame().InsetByCopy(-2, -2));
ConstrainClippingRegion(®ion);
rect.bottom = fStatusView->Frame().top - 1;
be_control_look->DrawScrollViewFrame(this, rect, updateRect,
BRect(), BRect(), base, fBorderStyle, flags);
}
}
void
BColumnListView::SaveState(BMessage* message)
{
message->MakeEmpty();
for (int32 i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
message->AddInt32("ID", column->fFieldID);
message->AddFloat("width", column->fWidth);
message->AddBool("visible", column->fVisible);
}
message->AddBool("sortingenabled", fSortingEnabled);
if (fSortingEnabled) {
for (int32 i = 0; BColumn* column = (BColumn*)fSortColumns.ItemAt(i);
i++) {
message->AddInt32("sortID", column->fFieldID);
message->AddBool("sortascending", column->fSortAscending);
}
}
}
void
BColumnListView::LoadState(BMessage* message)
{
int32 id;
for (int i = 0; message->FindInt32("ID", i, &id) == B_OK; i++) {
for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
if (column->fFieldID == id) {
MoveColumn(column, i);
float width;
if (message->FindFloat("width", i, &width) == B_OK)
column->SetWidth(width);
bool visible;
if (message->FindBool("visible", i, &visible) == B_OK)
column->SetVisible(visible);
}
}
}
bool b;
if (message->FindBool("sortingenabled", &b) == B_OK) {
SetSortingEnabled(b);
for (int k = 0; message->FindInt32("sortID", k, &id) == B_OK; k++) {
for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
j++) {
if (column->fFieldID == id) {
bool value;
if (message->FindBool("sortascending", k, &value) == B_OK)
SetSortColumn(column, true, value);
}
}
}
}
}
void
BColumnListView::SetEditMode(bool state)
{
fOutlineView->SetEditMode(state);
fTitleView->SetEditMode(state);
}
void
BColumnListView::Refresh()
{
if (LockLooper()) {
Invalidate();
fOutlineView->FixScrollBar (true);
fOutlineView->Invalidate();
Window()->UpdateIfNeeded();
UnlockLooper();
}
}
BSize
BColumnListView::MinSize()
{
BSize size;
size.width = 100;
size.height = std::max(kMinTitleHeight,
ceilf(be_plain_font->Size() * kTitleSpacing))
+ 4 * B_H_SCROLL_BAR_HEIGHT;
if (!fHorizontalScrollBar->IsHidden())
size.height += fHorizontalScrollBar->Frame().Height() + 1;
return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}
BSize
BColumnListView::PreferredSize()
{
BSize size = MinSize();
size.height += ceilf(be_plain_font->Size()) * 20;
int32 count = CountColumns();
if (count > 0) {
BRect titleRect;
BRect outlineRect;
BRect vScrollBarRect;
BRect hScrollBarRect;
_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
hScrollBarRect);
size.width = titleRect.left - Bounds().left;
size.width += Bounds().right - titleRect.right;
size.width += fTitleView->MarginWidth();
for (int32 i = 0; i < count; i++) {
BColumn* column = ColumnAt(i);
if (column != NULL)
size.width += column->Width();
}
}
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
}
BSize
BColumnListView::MaxSize()
{
BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
}
void
BColumnListView::LayoutInvalidated(bool descendants)
{
}
void
BColumnListView::DoLayout()
{
if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
return;
BRect titleRect;
BRect outlineRect;
BRect vScrollBarRect;
BRect hScrollBarRect;
_GetChildViewRects(Bounds(), titleRect, outlineRect, vScrollBarRect,
hScrollBarRect);
fTitleView->MoveTo(titleRect.LeftTop());
fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
fOutlineView->MoveTo(outlineRect.LeftTop());
fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
vScrollBarRect.Height());
if (fStatusView != NULL) {
BSize size = fStatusView->MinSize();
float hScrollBarHeight = fHorizontalScrollBar->Frame().Height();
if (size.height > hScrollBarHeight)
size.height = hScrollBarHeight;
if (size.width > Bounds().Width() / 2)
size.width = floorf(Bounds().Width() / 2);
BPoint offset(hScrollBarRect.LeftTop());
if (fBorderStyle == B_PLAIN_BORDER) {
offset += BPoint(0, 1);
} else if (fBorderStyle == B_FANCY_BORDER) {
offset += BPoint(-1, 2);
size.height -= 1;
}
fStatusView->MoveTo(offset);
fStatusView->ResizeTo(size.width, size.height);
hScrollBarRect.left = offset.x + size.width + 1;
}
fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
hScrollBarRect.Height());
fOutlineView->FixScrollBar(true);
}
void
BColumnListView::_Init()
{
SetViewColor(B_TRANSPARENT_32_BIT);
BRect bounds(Bounds());
if (bounds.Width() <= 0)
bounds.right = 100;
if (bounds.Height() <= 0)
bounds.bottom = 100;
fCustomColors = false;
_UpdateColors();
BRect titleRect;
BRect outlineRect;
BRect vScrollBarRect;
BRect hScrollBarRect;
_GetChildViewRects(bounds, titleRect, outlineRect, vScrollBarRect,
hScrollBarRect);
fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
AddChild(fOutlineView);
fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
AddChild(fTitleView);
fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
AddChild(fVerticalScrollBar);
fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
AddChild(fHorizontalScrollBar);
if (!fShowingHorizontalScrollBar)
fHorizontalScrollBar->Hide();
fOutlineView->FixScrollBar(true);
}
void
BColumnListView::_UpdateColors()
{
if (fCustomColors)
return;
fColorList[B_COLOR_BACKGROUND] = ui_color(B_LIST_BACKGROUND_COLOR);
fColorList[B_COLOR_TEXT] = ui_color(B_LIST_ITEM_TEXT_COLOR);
fColorList[B_COLOR_ROW_DIVIDER] = tint_color(
ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_2_TINT);
fColorList[B_COLOR_SELECTION] = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
fColorList[B_COLOR_SELECTION_TEXT] =
ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
fColorList[B_COLOR_NON_FOCUS_SELECTION] =
ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
fColorList[B_COLOR_EDIT_BACKGROUND] = tint_color(
ui_color(B_LIST_SELECTED_BACKGROUND_COLOR), B_DARKEN_1_TINT);
fColorList[B_COLOR_EDIT_BACKGROUND].alpha = 180;
fColorList[B_COLOR_EDIT_TEXT] = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
fColorList[B_COLOR_HEADER_BACKGROUND] = ui_color(B_CONTROL_BACKGROUND_COLOR);
fColorList[B_COLOR_HEADER_TEXT] = ui_color(B_PANEL_TEXT_COLOR);
fColorList[B_COLOR_SEPARATOR_LINE] = ui_color(B_LIST_ITEM_TEXT_COLOR);
fColorList[B_COLOR_SEPARATOR_BORDER] = ui_color(B_LIST_ITEM_TEXT_COLOR);
}
void
BColumnListView::_GetChildViewRects(const BRect& bounds, BRect& titleRect,
BRect& outlineRect, BRect& vScrollBarRect, BRect& hScrollBarRect)
{
const float vScrollBarWidth = be_control_look->GetScrollBarWidth(B_VERTICAL),
hScrollBarHeight = be_control_look->GetScrollBarWidth(B_HORIZONTAL);
titleRect = bounds;
titleRect.bottom = titleRect.top + std::max(kMinTitleHeight,
ceilf(be_plain_font->Size() * kTitleSpacing));
#if !LOWER_SCROLLBAR
titleRect.right -= vScrollBarWidth;
#endif
outlineRect = bounds;
outlineRect.top = titleRect.bottom + 1.0;
outlineRect.right -= vScrollBarWidth;
if (fShowingHorizontalScrollBar)
outlineRect.bottom -= hScrollBarHeight;
vScrollBarRect = bounds;
#if LOWER_SCROLLBAR
vScrollBarRect.top += std::max(kMinTitleHeight,
ceilf(be_plain_font->Size() * kTitleSpacing));
#endif
vScrollBarRect.left = vScrollBarRect.right - vScrollBarWidth;
if (fShowingHorizontalScrollBar)
vScrollBarRect.bottom -= hScrollBarHeight;
hScrollBarRect = bounds;
hScrollBarRect.top = hScrollBarRect.bottom - hScrollBarHeight;
hScrollBarRect.right -= vScrollBarWidth;
if (fBorderStyle == B_PLAIN_BORDER || fBorderStyle == B_NO_BORDER) {
titleRect.InsetBy(1, 0);
titleRect.OffsetBy(0, 1);
outlineRect.InsetBy(1, 1);
} else if (fBorderStyle == B_FANCY_BORDER) {
titleRect.InsetBy(2, 0);
titleRect.OffsetBy(0, 2);
outlineRect.InsetBy(2, 2);
vScrollBarRect.OffsetBy(-1, 0);
#if LOWER_SCROLLBAR
vScrollBarRect.top += 2;
vScrollBarRect.bottom -= 1;
#else
vScrollBarRect.InsetBy(0, 1);
#endif
hScrollBarRect.OffsetBy(0, -1);
hScrollBarRect.InsetBy(1, 0);
}
}
TitleView::TitleView(BRect rect, OutlineView* horizontalSlave,
BList* visibleColumns, BList* sortColumns, BColumnListView* listView,
uint32 resizingMode)
:
BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS),
fOutlineView(horizontalSlave),
fColumns(visibleColumns),
fSortColumns(sortColumns),
fVisibleRect(rect.OffsetToCopy(0, 0)),
fCurrentState(INACTIVE),
fColumnPop(NULL),
fMasterView(listView),
fEditMode(false),
fColumnFlags(B_ALLOW_COLUMN_MOVE | B_ALLOW_COLUMN_RESIZE
| B_ALLOW_COLUMN_POPUP | B_ALLOW_COLUMN_REMOVE)
{
SetViewColor(B_TRANSPARENT_COLOR);
fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
fResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST_WEST);
fMinResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_EAST);
fMaxResizeCursor = new BCursor(B_CURSOR_ID_RESIZE_WEST);
fColumnMoveCursor = new BCursor(B_CURSOR_ID_MOVE);
FixScrollBar(true);
}
TitleView::~TitleView()
{
delete fColumnPop;
fColumnPop = NULL;
delete fUpSortArrow;
delete fDownSortArrow;
delete fResizeCursor;
delete fMaxResizeCursor;
delete fMinResizeCursor;
delete fColumnMoveCursor;
}
void
TitleView::ColumnAdded(BColumn* column)
{
#ifdef DOUBLE_BUFFERED_COLUMN_RESIZE
fOutlineView->ResizeBufferView()->UpdateMaxWidth(column->MaxWidth());
#endif
FixScrollBar(false);
Invalidate();
}
void
TitleView::ColumnResized(BColumn* column, float oldWidth)
{
FixScrollBar(false);
Invalidate();
}
void
TitleView::SetColumnVisible(BColumn* column, bool visible)
{
if (column->fVisible == visible)
return;
if (visible)
column->fVisible = visible;
BRect titleInvalid;
GetTitleRect(column, &titleInvalid);
column->fVisible = visible;
BRect outlineInvalid(fOutlineView->VisibleRect());
outlineInvalid.left = titleInvalid.left;
titleInvalid.right = outlineInvalid.right;
Invalidate(titleInvalid);
fOutlineView->Invalidate(outlineInvalid);
FixScrollBar(false);
}
void
TitleView::GetTitleRect(BColumn* findColumn, BRect* _rect)
{
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
int32 numColumns = fColumns->CountItems();
for (int index = 0; index < numColumns; index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (!column->IsVisible())
continue;
if (column == findColumn) {
_rect->Set(leftEdge, 0, leftEdge + column->Width(),
fVisibleRect.bottom);
return;
}
leftEdge += column->Width() + 1;
}
TRESPASS();
}
int32
TitleView::FindColumn(BPoint position, float* _leftEdge)
{
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
int32 numColumns = fColumns->CountItems();
for (int index = 0; index < numColumns; index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (!column->IsVisible())
continue;
if (leftEdge > position.x)
break;
if (position.x >= leftEdge
&& position.x <= leftEdge + column->Width()) {
*_leftEdge = leftEdge;
return index;
}
leftEdge += column->Width() + 1;
}
return 0;
}
void
TitleView::FixScrollBar(bool scrollToFit)
{
BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
if (hScrollBar == NULL)
return;
float virtualWidth = _VirtualWidth();
if (virtualWidth > fVisibleRect.Width()) {
hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
float maxScrollBarValue = virtualWidth - fVisibleRect.Width();
if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) {
hScrollBar->SetRange(0.0, maxScrollBarValue);
hScrollBar->SetSteps(50, fVisibleRect.Width());
}
} else if (hScrollBar->Value() == 0.0) {
hScrollBar->SetRange(0.0, 0.0);
}
}
void
TitleView::DragSelectedColumn(BPoint position)
{
float invalidLeft = fSelectedColumnRect.left;
float invalidRight = fSelectedColumnRect.right;
float leftEdge;
int32 columnIndex = FindColumn(position, &leftEdge);
fSelectedColumnRect.OffsetTo(leftEdge, 0);
MoveColumn(fSelectedColumn, columnIndex);
fSelectedColumn->fVisible = true;
ComputeDragBoundries(fSelectedColumn, position);
GetTitleRect(fSelectedColumn, &fSelectedColumnRect);
invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft);
invalidRight = MAX(fSelectedColumnRect.right, invalidRight);
Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom));
fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight,
fOutlineView->VisibleRect().bottom));
DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
}
void
TitleView::MoveColumn(BColumn* column, int32 index)
{
fColumns->RemoveItem((void*) column);
if (-1 == index) {
fColumns->AddItem((void*) column);
} else {
fColumns->AddItem((void*) column, index);
}
}
void
TitleView::SetColumnFlags(column_flags flags)
{
fColumnFlags = flags;
}
float
TitleView::MarginWidth() const
{
return MAX(kLeftMargin, fMasterView->LatchWidth()) + kRightMargin;
}
void
TitleView::ResizeSelectedColumn(BPoint position, bool preferred)
{
float minWidth = fSelectedColumn->MinWidth();
float maxWidth = fSelectedColumn->MaxWidth();
float oldWidth = fSelectedColumn->Width();
float originalEdge = fSelectedColumnRect.left + oldWidth;
if (preferred) {
float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
fSelectedColumn->SetWidth(width);
} else if (position.x > fSelectedColumnRect.left + maxWidth)
fSelectedColumn->SetWidth(maxWidth);
else if (position.x < fSelectedColumnRect.left + minWidth)
fSelectedColumn->SetWidth(minWidth);
else
fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
- originalEdge;
if (dX != 0) {
float columnHeight = fVisibleRect.Height();
BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
BRect movedRect(originalRect);
movedRect.OffsetBy(dX, 0);
BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
BRect destRect(sourceRect);
destRect.OffsetBy(fSelectedColumnRect.left, 0);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
ColumnResizeBufferView* bufferView = fOutlineView->ResizeBufferView();
bufferView->Lock();
DrawTitle(bufferView, sourceRect, fSelectedColumn, false);
bufferView->Sync();
bufferView->Unlock();
CopyBits(originalRect, movedRect);
DrawBitmap(bufferView->Bitmap(), sourceRect, destRect);
#else
CopyBits(originalRect, movedRect);
DrawTitle(this, destRect, fSelectedColumn, false);
#endif
BRect slaveSize = fOutlineView->VisibleRect();
BRect slaveSource(originalRect);
slaveSource.bottom = slaveSize.bottom;
BRect slaveDest(movedRect);
slaveDest.bottom = slaveSize.bottom;
fOutlineView->CopyBits(slaveSource, slaveDest);
fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left,
fResizingFirstColumn);
if (fSelectedColumn->Width() == minWidth)
SetViewCursor(fMinResizeCursor, true);
else if (fSelectedColumn->Width() == maxWidth)
SetViewCursor(fMaxResizeCursor, true);
else
SetViewCursor(fResizeCursor, true);
ColumnResized(fSelectedColumn, oldWidth);
}
}
void
TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
{
float previousColumnLeftEdge = -1000000.0;
float nextColumnRightEdge = 1000000.0;
bool foundColumn = false;
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
int32 numColumns = fColumns->CountItems();
for (int index = 0; index < numColumns; index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (!column->IsVisible())
continue;
if (column == findColumn) {
foundColumn = true;
continue;
}
if (foundColumn) {
nextColumnRightEdge = leftEdge + column->Width();
break;
} else
previousColumnLeftEdge = leftEdge;
leftEdge += column->Width() + 1;
}
float rightEdge = leftEdge + findColumn->Width();
fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
leftEdge);
fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
}
void
TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
{
BRect drawRect;
drawRect = rect;
font_height fh;
GetFontHeight(&fh);
float baseline = floor(drawRect.top + fh.ascent
+ (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
BRect bgRect = rect;
rgb_color base = fMasterView->Color(B_COLOR_HEADER_BACKGROUND);
view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
bgRect.bottom--;
bgRect.right--;
if (depressed)
base = tint_color(base, B_DARKEN_1_TINT);
be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
view->StrokeLine(rect.RightTop(), rect.RightBottom());
if (column == NULL)
return;
view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
BFont font;
GetFont(&font);
view->SetFont(&font);
int sortIndex = fSortColumns->IndexOf(column);
if (sortIndex >= 0) {
BPoint upperLeft(drawRect.right - kSortIndicatorWidth, baseline);
if (fSortColumns->CountItems() > 1) {
char str[256];
sprintf(str, "%d", sortIndex + 1);
const float w = view->StringWidth(str);
upperLeft.x -= w;
view->SetDrawingMode(B_OP_COPY);
view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth,
baseline));
view->DrawString(str);
}
float bmh = fDownSortArrow->Bounds().Height()+1;
view->SetDrawingMode(B_OP_OVER);
if (column->fSortAscending) {
BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
- fDownSortArrow->Bounds().IntegerHeight()) / 2);
view->DrawBitmapAsync(fDownSortArrow, leftTop);
} else {
BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()
- fUpSortArrow->Bounds().IntegerHeight()) / 2);
view->DrawBitmapAsync(fUpSortArrow, leftTop);
}
upperLeft.y = baseline - bmh + floor((fh.ascent + fh.descent - bmh) / 2);
if (upperLeft.y < drawRect.top)
upperLeft.y = drawRect.top;
drawRect.right = upperLeft.x - 2;
}
if (drawRect.right > drawRect.left) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion(drawRect);
view->PushState();
view->ConstrainClippingRegion(&clipRegion);
#endif
view->MovePenTo(BPoint(drawRect.left + 8, baseline));
view->SetDrawingMode(B_OP_OVER);
view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
column->DrawTitle(drawRect, view);
#if CONSTRAIN_CLIPPING_REGION
view->PopState();
#endif
}
}
float
TitleView::_VirtualWidth() const
{
float width = MarginWidth();
int32 count = fColumns->CountItems();
for (int32 i = 0; i < count; i++) {
BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
if (column->IsVisible())
width += column->Width();
}
return width;
}
void
TitleView::Draw(BRect invalidRect)
{
float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
for (int32 columnIndex = 0; columnIndex < fColumns->CountItems();
columnIndex++) {
BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
if (!column->IsVisible())
continue;
if (columnLeftEdge > invalidRect.right)
break;
if (columnLeftEdge + column->Width() >= invalidRect.left) {
BRect titleRect(columnLeftEdge, 0,
columnLeftEdge + column->Width(), fVisibleRect.Height());
DrawTitle(this, titleRect, column,
(fCurrentState == DRAG_COLUMN_INSIDE_TITLE
&& fSelectedColumn == column));
}
columnLeftEdge += column->Width() + 1;
}
if (columnLeftEdge <= invalidRect.right) {
BRect titleRect(columnLeftEdge, 0, Bounds().right + 2,
fVisibleRect.Height());
DrawTitle(this, titleRect, NULL, false);
}
if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) {
BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1,
fVisibleRect.Height());
DrawTitle(this, titleRect, NULL, false);
}
#if DRAG_TITLE_OUTLINE
if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) {
BRect dragRect(fSelectedColumnRect);
dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
if (dragRect.Intersects(invalidRect)) {
SetHighColor(0, 0, 255);
StrokeRect(dragRect);
}
}
#endif
}
void
TitleView::ScrollTo(BPoint position)
{
fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0);
fVisibleRect.OffsetTo(position.x, position.y);
float maxScrollBarValue = _VirtualWidth() - fVisibleRect.Width();
BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
float min, max;
hScrollBar->GetRange(&min, &max);
if (max != maxScrollBarValue && position.x > maxScrollBarValue)
FixScrollBar(true);
_inherited::ScrollTo(position);
}
void
TitleView::MessageReceived(BMessage* message)
{
if (message->what == kToggleColumn) {
int32 num;
if (message->FindInt32("be:field_num", &num) == B_OK) {
for (int index = 0; index < fColumns->CountItems(); index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (column == NULL)
continue;
if (column->LogicalFieldNum() == num)
column->SetVisible(!column->IsVisible());
}
}
return;
}
BView::MessageReceived(message);
}
void
TitleView::MouseDown(BPoint position)
{
if (fEditMode)
return;
int32 buttons = 1;
Window()->CurrentMessage()->FindInt32("buttons", &buttons);
if (buttons == B_SECONDARY_MOUSE_BUTTON
&& (fColumnFlags & B_ALLOW_COLUMN_POPUP)) {
if (fColumnPop == NULL)
fColumnPop = new BPopUpMenu("Columns", false, false);
fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true);
BMessenger me(this);
for (int index = 0; index < fColumns->CountItems(); index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (column == NULL)
continue;
BString name;
column->GetColumnName(&name);
BMessage* message = new BMessage(kToggleColumn);
message->AddInt32("be:field_num", column->LogicalFieldNum());
BMenuItem* item = new BMenuItem(name.String(), message);
item->SetMarked(column->IsVisible());
item->SetTarget(me);
fColumnPop->AddItem(item);
}
BPoint screenPosition = ConvertToScreen(position);
BRect sticky(screenPosition, screenPosition);
sticky.InsetBy(-5, -5);
fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true);
return;
}
fResizingFirstColumn = true;
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
for (int index = 0; index < fColumns->CountItems(); index++) {
BColumn* column = (BColumn*)fColumns->ItemAt(index);
if (column == NULL || !column->IsVisible())
continue;
if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
break;
float rightEdge = leftEdge + column->Width();
if (column->ShowHeading()) {
if (position.x > rightEdge - kColumnResizeAreaWidth / 2
&& position.x < rightEdge + kColumnResizeAreaWidth / 2
&& column->MaxWidth() > column->MinWidth()
&& (fColumnFlags & B_ALLOW_COLUMN_RESIZE) != 0) {
int32 clicks = 0;
fSelectedColumn = column;
fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
fVisibleRect.Height());
Window()->CurrentMessage()->FindInt32("clicks", &clicks);
if (clicks == 2 || buttons == B_TERTIARY_MOUSE_BUTTON) {
ResizeSelectedColumn(position, true);
fCurrentState = INACTIVE;
break;
}
fCurrentState = RESIZING_COLUMN;
fClickPoint = BPoint(position.x - rightEdge - 1,
position.y - fSelectedColumnRect.top);
SetMouseEventMask(B_POINTER_EVENTS,
B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
break;
}
fResizingFirstColumn = false;
if (position.x > leftEdge && position.x < rightEdge) {
fCurrentState = PRESSING_COLUMN;
fSelectedColumn = column;
fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
fVisibleRect.Height());
DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true);
fClickPoint = BPoint(position.x - fSelectedColumnRect.left,
position.y - fSelectedColumnRect.top);
SetMouseEventMask(B_POINTER_EVENTS,
B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
break;
}
}
leftEdge = rightEdge + 1;
}
}
void
TitleView::MouseMoved(BPoint position, uint32 transit,
const BMessage* dragMessage)
{
if (fEditMode)
return;
switch (fCurrentState) {
case RESIZING_COLUMN:
ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
break;
case PRESSING_COLUMN: {
if (abs((int32)(position.x - (fClickPoint.x
+ fSelectedColumnRect.left))) > kColumnResizeAreaWidth
|| abs((int32)(position.y - (fClickPoint.y
+ fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
ComputeDragBoundries(fSelectedColumn, position);
SetViewCursor(fColumnMoveCursor, true);
#if DRAG_TITLE_OUTLINE
BRect invalidRect(fSelectedColumnRect);
invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
fCurrentDragPosition = position;
Invalidate(invalidRect);
#endif
}
} else {
if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
fSelectedColumn->SetVisible(false);
BRect dragRect(fSelectedColumnRect);
uint32 buttons;
GetMouse(&position, &buttons, false);
dragRect.OffsetTo(position.x - fClickPoint.x,
position.y - dragRect.Height() / 2);
BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
}
}
}
break;
}
case DRAG_COLUMN_INSIDE_TITLE: {
if (transit == B_EXITED_VIEW
&& (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) {
fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
fSelectedColumn->SetVisible(false);
BRect dragRect(fSelectedColumnRect);
uint32 buttons;
GetMouse(&position, &buttons, false);
dragRect.OffsetTo(position.x - fClickPoint.x,
position.y - fClickPoint.y);
BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT);
} else if (position.x < fLeftDragBoundry
|| position.x > fRightDragBoundry) {
DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
}
#if DRAG_TITLE_OUTLINE
BRect invalidRect(fSelectedColumnRect);
invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0);
if (position.x < fCurrentDragPosition.x)
invalidRect.left -= fCurrentDragPosition.x - position.x;
else
invalidRect.right += position.x - fCurrentDragPosition.x;
fCurrentDragPosition = position;
Invalidate(invalidRect);
#endif
break;
}
case DRAG_COLUMN_OUTSIDE_TITLE:
if (transit == B_ENTERED_VIEW) {
EndRectTracking();
fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
fSelectedColumn->SetVisible(true);
DragSelectedColumn(position - BPoint(fClickPoint.x, 0));
}
break;
case INACTIVE:
BColumn* resizeColumn = 0;
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
for (int index = 0; index < fColumns->CountItems(); index++) {
BColumn* column = (BColumn*) fColumns->ItemAt(index);
if (!column->IsVisible())
continue;
if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
break;
float rightEdge = leftEdge + column->Width();
if (position.x > rightEdge - kColumnResizeAreaWidth / 2
&& position.x < rightEdge + kColumnResizeAreaWidth / 2
&& column->MaxWidth() > column->MinWidth()) {
resizeColumn = column;
break;
}
leftEdge = rightEdge + 1;
}
if (resizeColumn) {
if (resizeColumn->Width() == resizeColumn->MinWidth())
SetViewCursor(fMinResizeCursor, true);
else if (resizeColumn->Width() == resizeColumn->MaxWidth())
SetViewCursor(fMaxResizeCursor, true);
else
SetViewCursor(fResizeCursor, true);
} else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
break;
}
}
void
TitleView::MouseUp(BPoint position)
{
if (fEditMode)
return;
switch (fCurrentState) {
case RESIZING_COLUMN:
ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
fCurrentState = INACTIVE;
FixScrollBar(false);
break;
case PRESSING_COLUMN: {
if (fMasterView->SortingEnabled()) {
if (fSortColumns->HasItem(fSelectedColumn)) {
if ((modifiers() & B_CONTROL_KEY) == 0
&& fSortColumns->CountItems() > 1) {
fSortColumns->MakeEmpty();
fSortColumns->AddItem(fSelectedColumn);
}
fSelectedColumn->fSortAscending
= !fSelectedColumn->fSortAscending;
} else {
if ((modifiers() & B_CONTROL_KEY) == 0)
fSortColumns->MakeEmpty();
fSortColumns->AddItem(fSelectedColumn);
fSelectedColumn->fSortAscending = true;
}
fOutlineView->StartSorting();
}
fCurrentState = INACTIVE;
Invalidate();
break;
}
case DRAG_COLUMN_INSIDE_TITLE:
fCurrentState = INACTIVE;
#if DRAG_TITLE_OUTLINE
Invalidate();
#else
Invalidate(fSelectedColumnRect);
#endif
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
break;
case DRAG_COLUMN_OUTSIDE_TITLE:
fCurrentState = INACTIVE;
EndRectTracking();
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
break;
default:
;
}
}
void
TitleView::FrameResized(float width, float height)
{
fVisibleRect.right = fVisibleRect.left + width;
fVisibleRect.bottom = fVisibleRect.top + height;
FixScrollBar(true);
}
OutlineView::OutlineView(BRect rect, BList* visibleColumns, BList* sortColumns,
BColumnListView* listView)
:
BView(rect, "outline_view", B_FOLLOW_ALL_SIDES,
B_WILL_DRAW | B_FRAME_EVENTS),
fColumns(visibleColumns),
fSortColumns(sortColumns),
fItemsHeight(0.0),
fVisibleRect(rect.OffsetToCopy(0, 0)),
fFocusRow(0),
fRollOverRow(0),
fLastSelectedItem(0),
fFirstSelectedItem(0),
fSortThread(B_BAD_THREAD_ID),
fCurrentState(INACTIVE),
fMasterView(listView),
fSelectionMode(B_MULTIPLE_SELECTION_LIST),
fTrackMouse(false),
fCurrentField(0),
fCurrentRow(0),
fCurrentColumn(0),
fMouseDown(false),
fCurrentCode(B_OUTSIDE_VIEW),
fEditMode(false),
fDragging(false),
fClickCount(0),
fDropHighlightY(-1)
{
SetViewColor(B_TRANSPARENT_COLOR);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
fResizeBufferView = new ColumnResizeBufferView();
#endif
FixScrollBar(true);
fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
}
OutlineView::~OutlineView()
{
#if DOUBLE_BUFFERED_COLUMN_RESIZE
delete fResizeBufferView;
#endif
Clear();
}
void
OutlineView::Clear()
{
DeselectAll();
RecursiveDeleteRows(&fRows, false);
fItemsHeight = 0.0;
FixScrollBar(true);
Invalidate();
}
void
OutlineView::SetSelectionMode(list_view_type mode)
{
DeselectAll();
fSelectionMode = mode;
}
list_view_type
OutlineView::SelectionMode() const
{
return fSelectionMode;
}
void
OutlineView::Deselect(BRow* row)
{
if (row == NULL)
return;
if (row->fNextSelected != 0) {
row->fNextSelected->fPrevSelected = row->fPrevSelected;
row->fPrevSelected->fNextSelected = row->fNextSelected;
row->fNextSelected = 0;
row->fPrevSelected = 0;
Invalidate();
}
}
void
OutlineView::AddToSelection(BRow* row)
{
if (row == NULL)
return;
if (row->fNextSelected == 0) {
if (fSelectionMode == B_SINGLE_SELECTION_LIST)
DeselectAll();
row->fNextSelected = fSelectionListDummyHead.fNextSelected;
row->fPrevSelected = &fSelectionListDummyHead;
row->fNextSelected->fPrevSelected = row;
row->fPrevSelected->fNextSelected = row;
BRect invalidRect;
if (FindVisibleRect(row, &invalidRect))
Invalidate(invalidRect);
}
}
void
OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
{
if (list == NULL)
return;
while (true) {
BRow* row = list->RemoveItemAt(0L);
if (row == 0)
break;
if (row->fChildList)
RecursiveDeleteRows(row->fChildList, true);
delete row;
}
if (isOwner)
delete list;
}
void
OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
{
if (!column)
return;
PushState();
font_height fh;
GetFontHeight(&fh);
float line = 0.0;
bool tintedLine = true;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
BRow* row = iterator.CurrentRow();
float rowHeight = row->Height();
if (line > fVisibleRect.bottom)
break;
tintedLine = !tintedLine;
if (line + rowHeight >= fVisibleRect.top) {
#if DOUBLE_BUFFERED_COLUMN_RESIZE
BRect sourceRect(0, 0, column->Width(), rowHeight);
#endif
BRect destRect(leftEdge, line, leftEdge + column->Width(),
line + rowHeight);
rgb_color highColor;
rgb_color lowColor;
if (row->fNextSelected != 0) {
if (fEditMode) {
highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
} else {
highColor = fMasterView->Color(B_COLOR_SELECTION);
lowColor = fMasterView->Color(B_COLOR_SELECTION);
}
} else {
highColor = fMasterView->Color(B_COLOR_BACKGROUND);
lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
}
if (tintedLine) {
if (lowColor.IsLight())
lowColor = tint_color(lowColor, kTintedLineTint);
else
lowColor = tint_color(lowColor, kTintedLineTintDark);
}
#if DOUBLE_BUFFERED_COLUMN_RESIZE
fResizeBufferView->Lock();
fResizeBufferView->SetHighColor(highColor);
fResizeBufferView->SetLowColor(lowColor);
BFont font;
GetFont(&font);
fResizeBufferView->SetFont(&font);
fResizeBufferView->FillRect(sourceRect, B_SOLID_LOW);
if (isFirstColumn) {
destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
- fMasterView->LatchWidth();
sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent
- fMasterView->LatchWidth();
LatchType pos = B_NO_LATCH;
if (row->HasLatch())
pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH;
BRect latchRect(sourceRect);
latchRect.right = latchRect.left + fMasterView->LatchWidth();
fMasterView->DrawLatch(fResizeBufferView, latchRect, pos, row);
}
BField* field = row->GetField(column->fFieldID);
if (field) {
BRect fieldRect(sourceRect);
if (isFirstColumn)
fieldRect.left += fMasterView->LatchWidth();
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion(fieldRect);
fResizeBufferView->PushState();
fResizeBufferView->ConstrainClippingRegion(&clipRegion);
#endif
fResizeBufferView->SetHighColor(fMasterView->Color(
row->fNextSelected ? B_COLOR_SELECTION_TEXT
: B_COLOR_TEXT));
float baseline = floor(fieldRect.top + fh.ascent
+ (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
fResizeBufferView->MovePenTo(fieldRect.left + 8, baseline);
column->DrawField(field, fieldRect, fResizeBufferView);
#if CONSTRAIN_CLIPPING_REGION
fResizeBufferView->PopState();
#endif
}
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
&& Window()->IsActive()) {
fResizeBufferView->SetHighColor(fMasterView->Color(
B_COLOR_ROW_DIVIDER));
fResizeBufferView->StrokeRect(BRect(-1, sourceRect.top,
10000.0, sourceRect.bottom));
}
fResizeBufferView->Sync();
fResizeBufferView->Unlock();
SetDrawingMode(B_OP_COPY);
DrawBitmap(fResizeBufferView->Bitmap(), sourceRect, destRect);
#else
SetHighColor(highColor);
SetLowColor(lowColor);
FillRect(destRect, B_SOLID_LOW);
BField* field = row->GetField(column->fFieldID);
if (field) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion(destRect);
PushState();
ConstrainClippingRegion(&clipRegion);
#endif
SetHighColor(fMasterView->Color(row->fNextSelected
? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
float baseline = floor(destRect.top + fh.ascent
+ (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
MovePenTo(destRect.left + 8, baseline);
column->DrawField(field, destRect, this);
#if CONSTRAIN_CLIPPING_REGION
PopState();
#endif
}
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
&& Window()->IsActive()) {
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
}
#endif
}
}
PopState();
}
void
OutlineView::Draw(BRect invalidBounds)
{
#if SMART_REDRAW
BRegion invalidRegion;
GetClippingRegion(&invalidRegion);
#endif
font_height fh;
GetFontHeight(&fh);
float line = 0.0;
bool tintedLine = true;
int32 numColumns = fColumns->CountItems();
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
BRow* row = iterator.CurrentRow();
if (line > invalidBounds.bottom)
break;
tintedLine = !tintedLine;
float rowHeight = row->Height();
if (line >= invalidBounds.top - rowHeight) {
bool isFirstColumn = true;
float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
rgb_color lowColor;
if (row->fNextSelected != 0) {
if (Window()->IsActive()) {
if (fEditMode)
lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
else
lowColor = fMasterView->Color(B_COLOR_SELECTION);
}
else
lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
} else
lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
if (tintedLine) {
if (lowColor.IsLight())
lowColor = tint_color(lowColor, kTintedLineTint);
else
lowColor = tint_color(lowColor, kTintedLineTintDark);
}
for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) {
BColumn* column = (BColumn*) fColumns->ItemAt(columnIndex);
if (!column->IsVisible())
continue;
if (!isFirstColumn && fieldLeftEdge > invalidBounds.right)
break;
if (fieldLeftEdge + column->Width() >= invalidBounds.left) {
BRect fullRect(fieldLeftEdge, line,
fieldLeftEdge + column->Width(), line + rowHeight);
bool clippedFirstColumn = false;
SetHighColor(lowColor);
BRect destRect(fullRect);
if (isFirstColumn) {
fullRect.left -= fMasterView->LatchWidth();
destRect.left += iterator.CurrentLevel()
* kOutlineLevelIndent;
if (destRect.left >= destRect.right) {
FillRect(BRect(0, line, fieldLeftEdge
+ column->Width(), line + rowHeight));
clippedFirstColumn = true;
}
FillRect(BRect(0, line, MAX(kLeftMargin,
fMasterView->LatchWidth()), line + row->Height()));
}
#if SMART_REDRAW
if (!clippedFirstColumn
&& invalidRegion.Intersects(fullRect)) {
#else
if (!clippedFirstColumn) {
#endif
FillRect(fullRect);
if (isFirstColumn) {
if (row == fTargetRow
&& fCurrentState == LATCH_CLICKED) {
BPoint pos;
uint32 buttons;
GetMouse(&pos, &buttons);
if (fLatchRect.Contains(pos)) {
fMasterView->DrawLatch(this, fLatchRect,
B_PRESSED_LATCH, fTargetRow);
} else {
fMasterView->DrawLatch(this, fLatchRect,
row->fIsExpanded ? B_OPEN_LATCH
: B_CLOSED_LATCH, fTargetRow);
}
} else {
LatchType pos = B_NO_LATCH;
if (row->HasLatch())
pos = row->fIsExpanded ? B_OPEN_LATCH
: B_CLOSED_LATCH;
fMasterView->DrawLatch(this,
BRect(destRect.left
- fMasterView->LatchWidth(),
destRect.top, destRect.left,
destRect.bottom), pos, row);
}
}
SetHighColor(fMasterView->HighColor());
SetLowColor(lowColor);
BField* field = row->GetField(column->fFieldID);
if (field) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion(destRect);
PushState();
ConstrainClippingRegion(&clipRegion);
#endif
SetHighColor(fMasterView->Color(
row->fNextSelected ? B_COLOR_SELECTION_TEXT
: B_COLOR_TEXT));
float baseline = floor(destRect.top + fh.ascent
+ (destRect.Height() + 1
- (fh.ascent+fh.descent)) / 2);
MovePenTo(destRect.left + 8, baseline);
column->DrawField(field, destRect, this);
#if CONSTRAIN_CLIPPING_REGION
PopState();
#endif
}
}
}
isFirstColumn = false;
fieldLeftEdge += column->Width() + 1;
}
if (fieldLeftEdge <= invalidBounds.right) {
SetHighColor(lowColor);
FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
line + rowHeight));
}
}
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
&& Window()->IsActive()) {
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
}
line += rowHeight + 1;
}
if (line <= invalidBounds.bottom) {
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
FillRect(BRect(invalidBounds.left, line, invalidBounds.right,
invalidBounds.bottom));
}
if (fDropHighlightY != -1) {
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
}
}
BRow*
OutlineView::FindRow(float ypos, int32* _rowIndent, float* _top)
{
if (_rowIndent && _top) {
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
BRow* row = iterator.CurrentRow();
if (line > ypos)
break;
float rowHeight = row->Height();
if (ypos <= line + rowHeight) {
*_top = line;
*_rowIndent = iterator.CurrentLevel();
return row;
}
line += rowHeight + 1;
}
}
return NULL;
}
void OutlineView::SetMouseTrackingEnabled(bool enabled)
{
fTrackMouse = enabled;
if (!enabled && fDropHighlightY != -1) {
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
fDropHighlightY = -1;
}
}
void
OutlineView::MouseDown(BPoint position)
{
if (!fEditMode)
fMasterView->MakeFocus(true);
bool reset_click_count = false;
int32 indent;
float rowTop;
BRow* row = FindRow(position.y, &indent, &rowTop);
if (row != NULL) {
bool handle_field = false;
BField* new_field = 0;
BRow* new_row = 0;
BColumn* new_column = 0;
BRect new_rect;
if (position.y >= 0) {
if (position.x >= 0) {
float x = 0;
for (int32 c = 0; c < fMasterView->CountColumns(); c++) {
new_column = fMasterView->ColumnAt(c);
if (!new_column->IsVisible())
continue;
if ((MAX(kLeftMargin, fMasterView->LatchWidth()) + x)
+ new_column->Width() >= position.x) {
if (new_column->WantsEvents()) {
new_field = row->GetField(c);
new_row = row;
FindRect(new_row,&new_rect);
new_rect.left = MAX(kLeftMargin,
fMasterView->LatchWidth()) + x;
new_rect.right = new_rect.left
+ new_column->Width() - 1;
handle_field = true;
}
break;
}
x += new_column->Width();
}
}
}
if (handle_field) {
fMouseDown = true;
fFieldRect = new_rect;
fCurrentColumn = new_column;
fCurrentRow = new_row;
fCurrentField = new_field;
fCurrentCode = B_INSIDE_VIEW;
BMessage* message = Window()->CurrentMessage();
int32 buttons = 1;
message->FindInt32("buttons", &buttons);
fCurrentColumn->MouseDown(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, buttons);
}
if (!fEditMode) {
fTargetRow = row;
fTargetRowTop = rowTop;
FindVisibleRect(fFocusRow, &fFocusRowRect);
float leftWidgetBoundry = indent * kOutlineLevelIndent
+ MAX(kLeftMargin, fMasterView->LatchWidth())
- fMasterView->LatchWidth();
fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry
+ fMasterView->LatchWidth(), rowTop + row->Height());
if (fLatchRect.Contains(position) && row->HasLatch()) {
fCurrentState = LATCH_CLICKED;
if (fTargetRow->fNextSelected != 0)
SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
else
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
FillRect(fLatchRect);
if (fLatchRect.Contains(position)) {
fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
row);
} else {
fMasterView->DrawLatch(this, fLatchRect,
fTargetRow->fIsExpanded ? B_OPEN_LATCH
: B_CLOSED_LATCH, row);
}
} else {
Invalidate(fFocusRowRect);
fFocusRow = fTargetRow;
FindVisibleRect(fFocusRow, &fFocusRowRect);
ASSERT(fTargetRow != 0);
if ((modifiers() & B_CONTROL_KEY) == 0)
DeselectAll();
if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0
&& fSelectionMode == B_MULTIPLE_SELECTION_LIST) {
SelectRange(fFirstSelectedItem, fTargetRow);
}
else {
if (fTargetRow->fNextSelected != 0) {
fTargetRow->fNextSelected->fPrevSelected
= fTargetRow->fPrevSelected;
fTargetRow->fPrevSelected->fNextSelected
= fTargetRow->fNextSelected;
fTargetRow->fPrevSelected = 0;
fTargetRow->fNextSelected = 0;
fFirstSelectedItem = NULL;
} else {
if (fSelectionMode == B_SINGLE_SELECTION_LIST)
DeselectAll();
fTargetRow->fNextSelected
= fSelectionListDummyHead.fNextSelected;
fTargetRow->fPrevSelected
= &fSelectionListDummyHead;
fTargetRow->fNextSelected->fPrevSelected = fTargetRow;
fTargetRow->fPrevSelected->fNextSelected = fTargetRow;
fFirstSelectedItem = fTargetRow;
}
Invalidate(BRect(fVisibleRect.left, fTargetRowTop,
fVisibleRect.right,
fTargetRowTop + fTargetRow->Height()));
}
fCurrentState = ROW_CLICKED;
if (fLastSelectedItem != fTargetRow)
reset_click_count = true;
fLastSelectedItem = fTargetRow;
fMasterView->SelectionChanged();
}
}
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
B_NO_POINTER_HISTORY);
} else if (fFocusRow != 0) {
FindVisibleRect(fFocusRow, &fFocusRowRect);
fFocusRow = 0;
Invalidate(fFocusRowRect);
}
if (reset_click_count)
fClickCount = 1;
else
Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
fClickPoint = position;
}
void
OutlineView::MouseMoved(BPoint position, uint32 ,
const BMessage* )
{
if (!fMouseDown) {
bool handle_field = false;
BField* new_field = 0;
BRow* new_row = 0;
BColumn* new_column = 0;
BRect new_rect(0,0,0,0);
if (position.y >=0 ) {
float top;
int32 indent;
BRow* row = FindRow(position.y, &indent, &top);
if (row && position.x >=0 ) {
float x=0;
for (int32 c=0;c<fMasterView->CountColumns();c++) {
new_column = fMasterView->ColumnAt(c);
if (!new_column->IsVisible())
continue;
if ((MAX(kLeftMargin,
fMasterView->LatchWidth()) + x) + new_column->Width()
> position.x) {
if(new_column->WantsEvents()) {
new_field = row->GetField(c);
new_row = row;
FindRect(new_row,&new_rect);
new_rect.left = MAX(kLeftMargin,
fMasterView->LatchWidth()) + x;
new_rect.right = new_rect.left
+ new_column->Width() - 1;
handle_field = true;
}
break;
}
x += new_column->Width();
}
}
}
if (handle_field) {
if (new_field != fCurrentField) {
if (fCurrentField) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 0,
fCurrentCode = B_EXITED_VIEW);
}
fCurrentColumn = new_column;
fCurrentRow = new_row;
fCurrentField = new_field;
fFieldRect = new_rect;
if (fCurrentField) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 0,
fCurrentCode = B_ENTERED_VIEW);
}
} else {
if (fCurrentField) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 0,
fCurrentCode = B_INSIDE_VIEW);
}
}
} else {
if (fCurrentField) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 0,
fCurrentCode = B_EXITED_VIEW);
fCurrentField = 0;
fCurrentColumn = 0;
fCurrentRow = 0;
}
}
} else {
if (fCurrentField) {
if (fFieldRect.Contains(position)) {
if (fCurrentCode == B_OUTSIDE_VIEW
|| fCurrentCode == B_EXITED_VIEW) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 1,
fCurrentCode = B_ENTERED_VIEW);
} else {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 1,
fCurrentCode = B_INSIDE_VIEW);
}
} else {
if (fCurrentCode == B_INSIDE_VIEW
|| fCurrentCode == B_ENTERED_VIEW) {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 1,
fCurrentCode = B_EXITED_VIEW);
} else {
fCurrentColumn->MouseMoved(fMasterView, fCurrentRow,
fCurrentField, fFieldRect, position, 1,
fCurrentCode = B_OUTSIDE_VIEW);
}
}
}
}
if (!fEditMode) {
switch (fCurrentState) {
case LATCH_CLICKED:
if (fTargetRow->fNextSelected != 0)
SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
else
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
FillRect(fLatchRect);
if (fLatchRect.Contains(position)) {
fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH,
fTargetRow);
} else {
fMasterView->DrawLatch(this, fLatchRect,
fTargetRow->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH,
fTargetRow);
}
break;
case ROW_CLICKED:
if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity
|| abs((int)(position.y - fClickPoint.y))
> kRowDragSensitivity) {
fCurrentState = DRAGGING_ROWS;
fMasterView->InitiateDrag(fClickPoint,
fTargetRow->fNextSelected != 0);
}
break;
case DRAGGING_ROWS:
#if 0
#else
if (fTrackMouse ) {
if (fVisibleRect.Contains(position)) {
float top;
int32 indent;
BRow* target = FindRow(position.y, &indent, &top);
if (target)
SetFocusRow(target, true);
}
}
break;
#endif
default: {
if (fTrackMouse ) {
if (fVisibleRect.Contains(position)) {
float top;
int32 indent;
BRow* target = FindRow(position.y, &indent, &top);
if (target == fRollOverRow)
break;
if (fRollOverRow) {
BRect rect;
FindRect(fRollOverRow, &rect);
Invalidate(rect);
}
fRollOverRow = target;
#if 0
SetFocusRow(fRollOverRow,false);
#else
PushState();
SetDrawingMode(B_OP_BLEND);
SetHighColor(255, 255, 255, 255);
BRect rect;
FindRect(fRollOverRow, &rect);
rect.bottom -= 1.0;
FillRect(rect);
PopState();
#endif
} else {
if (fRollOverRow) {
BRect rect;
FindRect(fRollOverRow, &rect);
Invalidate(rect);
fRollOverRow = NULL;
}
}
}
}
}
}
}
void
OutlineView::MouseUp(BPoint position)
{
if (fCurrentField) {
fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
fMouseDown = false;
}
if (fEditMode)
return;
switch (fCurrentState) {
case LATCH_CLICKED:
if (fLatchRect.Contains(position)) {
fMasterView->ExpandOrCollapse(fTargetRow,
!fTargetRow->fIsExpanded);
}
Invalidate(fLatchRect);
fCurrentState = INACTIVE;
break;
case ROW_CLICKED:
if (fClickCount > 1
&& abs((int)fClickPoint.x - (int)position.x)
< kDoubleClickMoveSensitivity
&& abs((int)fClickPoint.y - (int)position.y)
< kDoubleClickMoveSensitivity) {
fMasterView->ItemInvoked();
}
fCurrentState = INACTIVE;
break;
case DRAGGING_ROWS:
fCurrentState = INACTIVE;
default:
if (fDropHighlightY != -1) {
InvertRect(BRect(0,
fDropHighlightY - kDropHighlightLineHeight / 2,
1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
fDropHighlightY = -1;
}
}
}
void
OutlineView::MessageReceived(BMessage* message)
{
if (message->WasDropped()) {
fMasterView->MessageDropped(message,
ConvertFromScreen(message->DropPoint()));
} else {
BView::MessageReceived(message);
}
}
#if DOUBLE_BUFFERED_COLUMN_RESIZE
ColumnResizeBufferView*
OutlineView::ResizeBufferView()
{
return fResizeBufferView;
}
#endif
void
OutlineView::ChangeFocusRow(bool up, bool updateSelection,
bool addToCurrentSelection)
{
int32 indent;
float top;
float newRowPos = 0;
float verticalScroll = 0;
if (fFocusRow) {
newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4);
if (newRowPos < fVisibleRect.top + 20)
verticalScroll = newRowPos - 20;
else if (newRowPos > fVisibleRect.bottom - 20)
verticalScroll = newRowPos - fVisibleRect.Height() + 20;
} else
newRowPos = fVisibleRect.top + 2;
BRow* newRow = FindRow(newRowPos, &indent, &top);
if (newRow) {
if (fFocusRow) {
fFocusRowRect.right = 10000;
Invalidate(fFocusRowRect);
}
BRow* oldFocusRow = fFocusRow;
fFocusRow = newRow;
fFocusRowRect.top = top;
fFocusRowRect.left = 0;
fFocusRowRect.right = 10000;
fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height();
Invalidate(fFocusRowRect);
if (updateSelection) {
if (!addToCurrentSelection
|| fSelectionMode == B_SINGLE_SELECTION_LIST) {
DeselectAll();
}
if (fFocusRow->fNextSelected == 0) {
fFocusRow->fNextSelected
= fSelectionListDummyHead.fNextSelected;
fFocusRow->fPrevSelected = &fSelectionListDummyHead;
fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
} else if (oldFocusRow != NULL
&& fSelectionListDummyHead.fNextSelected == oldFocusRow
&& (((IndexOf(oldFocusRow->fNextSelected)
< IndexOf(oldFocusRow)) == up)
|| fFocusRow == oldFocusRow->fNextSelected)) {
fSelectionListDummyHead.fNextSelected
= oldFocusRow->fNextSelected;
if (fSelectionListDummyHead.fNextSelected != NULL) {
fSelectionListDummyHead.fNextSelected->fPrevSelected
= &fSelectionListDummyHead;
oldFocusRow->fNextSelected = NULL;
}
oldFocusRow->fPrevSelected = NULL;
}
fLastSelectedItem = fFocusRow;
}
} else
Invalidate(fFocusRowRect);
if (verticalScroll != 0) {
BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
float min, max;
vScrollBar->GetRange(&min, &max);
if (verticalScroll < min)
verticalScroll = min;
else if (verticalScroll > max)
verticalScroll = max;
vScrollBar->SetValue(verticalScroll);
}
if (newRow && updateSelection)
fMasterView->SelectionChanged();
}
void
OutlineView::MoveFocusToVisibleRect()
{
fFocusRow = 0;
ChangeFocusRow(true, true, false);
}
BRow*
OutlineView::CurrentSelection(BRow* lastSelected) const
{
BRow* row;
if (lastSelected == 0)
row = fSelectionListDummyHead.fNextSelected;
else
row = lastSelected->fNextSelected;
if (row == &fSelectionListDummyHead)
row = 0;
return row;
}
void
OutlineView::ToggleFocusRowSelection(bool selectRange)
{
if (fFocusRow == 0)
return;
if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST)
SelectRange(fLastSelectedItem, fFocusRow);
else {
if (fFocusRow->fNextSelected != 0) {
fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
fFocusRow->fPrevSelected = 0;
fFocusRow->fNextSelected = 0;
} else {
if (fSelectionMode == B_SINGLE_SELECTION_LIST)
DeselectAll();
fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected;
fFocusRow->fPrevSelected = &fSelectionListDummyHead;
fFocusRow->fNextSelected->fPrevSelected = fFocusRow;
fFocusRow->fPrevSelected->fNextSelected = fFocusRow;
}
}
fLastSelectedItem = fFocusRow;
fMasterView->SelectionChanged();
Invalidate(fFocusRowRect);
}
void
OutlineView::ToggleFocusRowOpen()
{
if (fFocusRow)
fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded);
}
void
OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
{
if (parentRow == NULL)
return;
if (parentRow->fIsExpanded == expand)
return;
parentRow->fIsExpanded = expand;
BRect parentRect;
if (FindRect(parentRow, &parentRect)) {
float subTreeHeight = 0.0;
if (parentRow->fIsExpanded)
for (RecursiveOutlineIterator iterator(parentRow->fChildList);
iterator.CurrentRow();
iterator.GoToNext()
)
{
subTreeHeight += iterator.CurrentRow()->Height()+1;
}
else
for (RecursiveOutlineIterator iterator(parentRow->fChildList);
iterator.CurrentRow();
iterator.GoToNext()
)
{
subTreeHeight -= iterator.CurrentRow()->Height()+1;
}
fItemsHeight += subTreeHeight;
if (FindRect(fFocusRow, &fFocusRowRect) == false) {
fFocusRow = parentRow;
FindRect(fFocusRow, &fFocusRowRect);
}
Invalidate(BRect(0, parentRect.top, fVisibleRect.right,
fVisibleRect.bottom));
FixScrollBar(false);
}
}
bool
OutlineView::RemoveRowFromSelectionOnly(BRow* row)
{
if (row->fNextSelected != 0) {
row->fNextSelected->fPrevSelected = row->fPrevSelected;
row->fPrevSelected->fNextSelected = row->fNextSelected;
row->fPrevSelected = 0;
row->fNextSelected = 0;
return true;
}
return false;
}
void
OutlineView::RemoveRow(BRow* row)
{
if (row == NULL)
return;
BRow* parentRow = NULL;
bool parentIsVisible = false;
FindParent(row, &parentRow, &parentIsVisible);
float subTreeHeight = 0.0f;
if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
subTreeHeight = row->Height() + 1;
if (row->fIsExpanded) {
for (RecursiveOutlineIterator iterator(row->fChildList);
iterator.CurrentRow(); iterator.GoToNext())
subTreeHeight += iterator.CurrentRow()->Height() + 1;
}
BRect invalid;
if (FindRect(row, &invalid)) {
invalid.bottom = Bounds().bottom;
if (invalid.IsValid())
Invalidate(invalid);
}
}
fItemsHeight -= subTreeHeight;
FixScrollBar(false);
int32 indent = 0;
float top = 0.0;
if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
if (fItemsHeight > fVisibleRect.Height())
ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
else
ScrollBy(0.0, -Bounds().top);
}
if (parentRow != NULL) {
parentRow->fChildList->RemoveItem(row);
if (parentRow->fChildList->CountItems() == 0) {
delete parentRow->fChildList;
parentRow->fChildList = 0;
BRect parentRowRect;
if (parentIsVisible && FindRect(parentRow, &parentRowRect))
Invalidate(parentRowRect);
}
} else
fRows.RemoveItem(row);
if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
fFocusRow = parentRow;
if (fFocusRow)
FindRect(fFocusRow, &fFocusRowRect);
}
if (RemoveRowFromSelectionOnly(row))
fMasterView->SelectionChanged();
fCurrentColumn = 0;
fCurrentRow = 0;
fCurrentField = 0;
}
void
OutlineView::RemoveRows(BList* rows)
{
if (rows->IsEmpty())
return;
BRow* parentRow = NULL;
int32 countRows = rows->CountItems();
for (int32 i = 0; i < countRows; i++) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
if (i == 0) {
parentRow = row->fParent;
} else if (parentRow != row->fParent) {
debugger("during bulk removal all rows must be from the same parent");
return;
}
}
bool parentIsVisible = parentRow == NULL;
BRect parentRowRect;
if (parentRow)
parentIsVisible = FindRect(parentRow, &parentRowRect);
float subTreesHeight = 0.0f;
if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
BRect invalidAll;
for (int32 i = 0; i < countRows; i++) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
subTreesHeight += row->Height() + 1;
if (row->fIsExpanded) {
for (RecursiveOutlineIterator iterator(row->fChildList);
iterator.CurrentRow(); iterator.GoToNext()) {
subTreesHeight += iterator.CurrentRow()->Height() + 1;
}
}
BRect invalid;
if (FindRect(row, &invalid)) {
if (!invalidAll.IsValid())
invalidAll = invalid;
else
invalidAll = invalidAll | invalid;
}
}
if (invalidAll.IsValid()) {
invalidAll.bottom = Bounds().bottom;
if (invalidAll.IsValid() && invalidAll.top < fVisibleRect.bottom)
Invalidate(invalidAll);
}
}
fItemsHeight -= subTreesHeight;
FixScrollBar(true);
int32 indent = 0;
float top = 0.0;
if (FindRow(fVisibleRect.top, &indent, &top) == NULL && ScrollBar(B_VERTICAL) != NULL) {
if (fItemsHeight > fVisibleRect.Height())
ScrollBy(0.0, fItemsHeight - fVisibleRect.Height() - Bounds().top);
else
ScrollBy(0.0, -Bounds().top);
}
if (parentRow != NULL) {
for (int32 i = 0; i < countRows; i++) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
parentRow->fChildList->RemoveItem(row);
}
if (parentRow->fChildList->CountItems() == 0) {
delete parentRow->fChildList;
parentRow->fChildList = 0;
BRect parentRowRect;
if (parentIsVisible && FindRect(parentRow, &parentRowRect))
Invalidate(parentRowRect);
}
} else {
for (int32 i = 0; i < countRows; i++) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
fRows.RemoveItem(row);
}
}
if (fFocusRow) {
if (fFocusRowRect.top < fVisibleRect.bottom)
Invalidate(fFocusRowRect);
if (fFocusRow && !FindRect(fFocusRow, &fFocusRowRect)) {
fFocusRow = parentRow;
if (fFocusRow)
FindRect(fFocusRow, &fFocusRowRect);
if (fFocusRowRect.top < fVisibleRect.bottom)
Invalidate(fFocusRowRect);
}
}
bool anyRowRemovedFromSelection = false;
for (int32 i = 0; i < countRows; i++) {
BRow* row = static_cast<BRow*>(rows->ItemAt(i));
if (RemoveRowFromSelectionOnly(row))
anyRowRemovedFromSelection = true;
}
if (anyRowRemovedFromSelection)
fMasterView->SelectionChanged();
fCurrentColumn = 0;
fCurrentRow = 0;
fCurrentField = 0;
}
BRowContainer*
OutlineView::RowList()
{
return &fRows;
}
void
OutlineView::UpdateRow(BRow* row)
{
if (row) {
BRow* parentRow = NULL;
bool parentIsVisible = false;
FindParent(row, &parentRow, &parentIsVisible);
BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList;
if(list) {
int32 rowIndex = list->IndexOf(row);
ASSERT(rowIndex >= 0);
ASSERT(list->ItemAt(rowIndex) == row);
bool rowMoved = false;
if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0)
rowMoved = true;
if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1),
row) < 0)
rowMoved = true;
if (rowMoved) {
SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
BRect invalidRect;
if (FindVisibleRect(row, &invalidRect))
Invalidate(invalidRect);
}
}
}
}
int32
OutlineView::AddRowToParentOnly(BRow* row, int32 index, BRow* parentRow)
{
row->fParent = parentRow;
if (fMasterView->SortingEnabled() && !fSortColumns->IsEmpty()) {
if (parentRow) {
if (parentRow->fChildList == NULL)
parentRow->fChildList = new BRowContainer;
return AddSorted(parentRow->fChildList, row);
}
return AddSorted(&fRows, row);
}
if (parentRow) {
if (parentRow->fChildList == 0)
parentRow->fChildList = new BRowContainer;
int32 parentRowCount = parentRow->fChildList->CountItems();
if (index < 0 || index > parentRowCount) {
parentRow->fChildList->AddItem(row);
return parentRowCount;
}
parentRow->fChildList->AddItem(row, index);
return index;
}
int32 rowCount = fRows.CountItems();
if (index < 0 || index >= rowCount) {
fRows.AddItem(row);
return rowCount;
}
fRows.AddItem(row, index);
return index;
}
void
OutlineView::AddRows(BList* addedRows, int32 index, BRow* parentRow)
{
if (addedRows->IsEmpty())
return;
bool parentRowEmptyOnEntry = true;
if (parentRow)
parentRowEmptyOnEntry = parentRow->fChildList->CountItems() == 0;
float maxRowHeight = 0.0f;
float sumRowHeight = 0.0f;
int32 countAddedRows = addedRows->CountItems();
int32 firstIndex = -1;
BRow* firstRow = NULL;
for (int32 i = 0; i < countAddedRows; i++) {
BRow* row = static_cast<BRow*>(addedRows->ItemAt(i));
int insertedIndex = AddRowToParentOnly(row, index, parentRow);
if (insertedIndex >= 0) {
if (firstIndex < 0 || insertedIndex <= firstIndex) {
firstIndex = insertedIndex;
firstRow = row;
}
float rowHeight = row->Height();
sumRowHeight += rowHeight;
if (rowHeight > maxRowHeight)
maxRowHeight = rowHeight;
}
}
#ifdef DOUBLE_BUFFERED_COLUMN_RESIZE
ResizeBufferView()->UpdateMaxHeight(maxRowHeight);
#endif
if (parentRow == 0 || parentRow->fIsExpanded)
fItemsHeight += (sumRowHeight + static_cast<float>(countAddedRows));
FixScrollBar(false);
BRect firstAddedRowRect;
const bool firstAddedRowIsInOpenBranch = FindRect(firstRow, &firstAddedRowRect);
if (firstAddedRowIsInOpenBranch && firstAddedRowRect.top < fVisibleRect.bottom) {
BRect invalidRect = firstAddedRowRect;
invalidRect.bottom = fItemsHeight;
Invalidate(invalidRect);
}
if (fFocusRow) {
if (fFocusRowRect.top < fVisibleRect.bottom)
Invalidate(fFocusRowRect);
FindRect(fFocusRow, &fFocusRowRect);
if (fFocusRowRect.top < fVisibleRect.bottom)
Invalidate(fFocusRowRect);
}
if (parentRow && parentRowEmptyOnEntry) {
BRect parentRect;
if (FindVisibleRect(parentRow, &parentRect))
Invalidate(parentRect);
}
}
void
OutlineView::AddRow(BRow* row, int32 Index, BRow* parentRow)
{
if (!row)
return;
AddRowToParentOnly(row, Index, parentRow);
#ifdef DOUBLE_BUFFERED_COLUMN_RESIZE
ResizeBufferView()->UpdateMaxHeight(row->Height());
#endif
if (parentRow == 0 || parentRow->fIsExpanded)
fItemsHeight += row->Height() + 1;
FixScrollBar(false);
BRect newRowRect;
const bool newRowIsInOpenBranch = FindRect(row, &newRowRect);
if (newRowIsInOpenBranch) {
if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
Invalidate(fFocusRowRect);
FindRect(fFocusRow, &fFocusRowRect);
Invalidate(fFocusRowRect);
}
if (fCurrentState == INACTIVE) {
if (newRowRect.bottom < fVisibleRect.top) {
BRect source(fVisibleRect);
BRect dest(fVisibleRect);
source.bottom -= row->Height() + 1;
dest.top += row->Height() + 1;
CopyBits(source, dest);
Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right,
fVisibleRect.top + newRowRect.Height()));
} else if (newRowRect.top < fVisibleRect.bottom) {
BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right,
fVisibleRect.bottom - newRowRect.Height());
BRect dest(source);
dest.OffsetBy(0, newRowRect.Height() + 1);
CopyBits(source, dest);
Invalidate(newRowRect);
}
} else {
BRect targetRect;
if (FindRect(fTargetRow, &targetRect)) {
float delta = targetRect.top - fTargetRowTop;
if (delta != 0) {
BRegion emptyRegion;
ConstrainClippingRegion(&emptyRegion);
PushState();
ScrollBy(0, delta);
PopState();
ConstrainClippingRegion(NULL);
fTargetRowTop += delta;
fClickPoint.y += delta;
fLatchRect.OffsetBy(0, delta);
}
}
}
}
BRect parentRect;
if (parentRow && parentRow->fChildList->CountItems() == 1
&& FindVisibleRect(parentRow, &parentRect))
Invalidate(parentRect);
}
void
OutlineView::FixScrollBar(bool scrollToFit)
{
BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
if (vScrollBar) {
if (fItemsHeight > fVisibleRect.Height()) {
float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
vScrollBar->SetRange(0.0, maxScrollBarValue);
vScrollBar->SetSteps(20.0, fVisibleRect.Height());
}
} else if (vScrollBar->Value() == 0.0 || fItemsHeight == 0.0)
vScrollBar->SetRange(0.0, 0.0);
}
}
int32
OutlineView::AddSorted(BRowContainer* list, BRow* row)
{
if (list && row) {
int32 lower = 0;
int32 upper = list->CountItems()-1;
while( lower < upper ) {
int32 middle = lower + (upper-lower+1)/2;
int32 cmp = CompareRows(row, list->ItemAt(middle));
if( cmp < 0 ) upper = middle-1;
else if( cmp > 0 ) lower = middle+1;
else lower = upper = middle;
}
if( upper < 0 ) upper = 0;
else if( upper < list->CountItems() ) {
if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++;
}
if (upper >= list->CountItems()) {
list->AddItem(row);
return list->CountItems() - 1;
}
list->AddItem(row, upper);
return upper;
}
return -1;
}
int32
OutlineView::CompareRows(BRow* row1, BRow* row2)
{
int32 itemCount (fSortColumns->CountItems());
if (row1 && row2) {
for (int32 index = 0; index < itemCount; index++) {
BColumn* column = (BColumn*) fSortColumns->ItemAt(index);
int comp = 0;
BField* field1 = (BField*) row1->GetField(column->fFieldID);
BField* field2 = (BField*) row2->GetField(column->fFieldID);
if (field1 && field2)
comp = column->CompareFields(field1, field2);
if (!column->fSortAscending)
comp = -comp;
if (comp != 0)
return comp;
}
}
return 0;
}
void
OutlineView::FrameResized(float width, float height)
{
fVisibleRect.right = fVisibleRect.left + width;
fVisibleRect.bottom = fVisibleRect.top + height;
FixScrollBar(true);
_inherited::FrameResized(width, height);
}
void
OutlineView::ScrollTo(BPoint position)
{
fVisibleRect.OffsetTo(position.x, position.y);
BScrollBar* vScrollBar = ScrollBar(B_VERTICAL);
float maxScrollBarValue = fItemsHeight - fVisibleRect.Height();
float min, max;
vScrollBar->GetRange(&min, &max);
if (max != maxScrollBarValue && position.y > maxScrollBarValue)
FixScrollBar(true);
_inherited::ScrollTo(position);
}
const BRect&
OutlineView::VisibleRect() const
{
return fVisibleRect;
}
bool
OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
{
if (row && _rect) {
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
if (iterator.CurrentRow() == row) {
_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
line + row->Height());
return line <= fVisibleRect.bottom;
}
line += iterator.CurrentRow()->Height() + 1;
}
}
return false;
}
bool
OutlineView::FindRect(const BRow* row, BRect* _rect)
{
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
if (iterator.CurrentRow() == row) {
_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
line + row->Height());
return true;
}
line += iterator.CurrentRow()->Height() + 1;
}
return false;
}
void
OutlineView::ScrollTo(const BRow* row)
{
BRect rect;
if (FindRect(row, &rect)) {
BRect bounds = Bounds();
if (rect.top < bounds.top)
ScrollTo(BPoint(bounds.left, rect.top));
else if (rect.bottom > bounds.bottom)
ScrollBy(0, rect.bottom - bounds.bottom);
}
}
void
OutlineView::DeselectAll()
{
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
if (line > fVisibleRect.bottom)
break;
BRow* row = iterator.CurrentRow();
if (line + row->Height() > fVisibleRect.top) {
if (row->fNextSelected != 0)
Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right,
line + row->Height()));
}
line += row->Height() + 1;
}
while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) {
BRow* row = fSelectionListDummyHead.fNextSelected;
row->fNextSelected->fPrevSelected = row->fPrevSelected;
row->fPrevSelected->fNextSelected = row->fNextSelected;
row->fNextSelected = 0;
row->fPrevSelected = 0;
}
}
BRow*
OutlineView::FocusRow() const
{
return fFocusRow;
}
void
OutlineView::SetFocusRow(BRow* row, bool Select)
{
if (row) {
if (Select)
AddToSelection(row);
if (fFocusRow == row)
return;
Invalidate(fFocusRowRect);
fTargetRow = fFocusRow = row;
FindVisibleRect(fFocusRow, &fFocusRowRect);
Invalidate(fFocusRowRect);
fFocusRowRect.right = 10000;
fMasterView->SelectionChanged();
}
}
bool
OutlineView::SortList(BRowContainer* list, bool isVisible)
{
if (list) {
BRow** items
= (BRow**) BObjectList<BRow>::Private(list).AsBList()->Items();
int32 numItems = list->CountItems();
int h;
for (h = 1; h < numItems / 9; h = 3 * h + 1)
;
for (;h > 0; h /= 3) {
for (int step = h; step < numItems; step++) {
BRow* temp = items[step];
int i;
for (i = step - h; i >= 0; i -= h) {
if (CompareRows(temp, items[i]) < 0)
items[i + h] = items[i];
else
break;
}
items[i + h] = temp;
}
}
if (isVisible) {
Invalidate();
InvalidateCachedPositions();
int lockCount = Window()->CountLocks();
for (int i = 0; i < lockCount; i++)
Window()->Unlock();
while (lockCount--)
if (!Window()->Lock())
return false;
}
}
return true;
}
int32
OutlineView::DeepSortThreadEntry(void* _outlineView)
{
((OutlineView*) _outlineView)->DeepSort();
return 0;
}
void
OutlineView::DeepSort()
{
struct stack_entry {
bool isVisible;
BRowContainer* list;
int32 listIndex;
} stack[kMaxDepth];
int32 stackTop = 0;
stack[stackTop].list = &fRows;
stack[stackTop].isVisible = true;
stack[stackTop].listIndex = 0;
fNumSorted = 0;
if (Window()->Lock() == false)
return;
bool doneSorting = false;
while (!doneSorting && !fSortCancelled) {
stack_entry* currentEntry = &stack[stackTop];
bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
if (!haveLock)
return ;
InvalidateCachedPositions();
if (fCurrentState != INACTIVE)
fCurrentState = INACTIVE;
bool foundNextList = false;
while (!foundNextList && !fSortCancelled) {
for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems();
index++) {
BRow* parentRow = currentEntry->list->ItemAt(index);
BRowContainer* childList = parentRow->fChildList;
if (childList != 0) {
currentEntry->listIndex = index + 1;
stackTop++;
ASSERT(stackTop < kMaxDepth);
stack[stackTop].listIndex = 0;
stack[stackTop].list = childList;
stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded);
foundNextList = true;
break;
}
}
if (!foundNextList) {
if (--stackTop < 0) {
doneSorting = true;
break;
}
currentEntry = &stack[stackTop];
}
}
}
Window()->Unlock();
}
void
OutlineView::StartSorting()
{
if (Window() == NULL)
return;
if (fSortThread != B_BAD_THREAD_ID) {
thread_info tinfo;
if (get_thread_info(fSortThread, &tinfo) == B_OK) {
int lockCount = Window()->CountLocks();
for (int i = 0; i < lockCount; i++)
Window()->Unlock();
fSortCancelled = true;
int32 status;
wait_for_thread(fSortThread, &status);
while (lockCount--)
if (!Window()->Lock())
return ;
}
}
fSortCancelled = false;
fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this);
resume_thread(fSortThread);
}
void
OutlineView::SelectRange(BRow* start, BRow* end)
{
if (!start || !end)
return;
if (start == end)
return;
RecursiveOutlineIterator iterator(&fRows, false);
while (iterator.CurrentRow() != 0) {
if (iterator.CurrentRow() == end) {
BRow* temp = start;
start = end;
end = temp;
break;
} else if (iterator.CurrentRow() == start)
break;
iterator.GoToNext();
}
while (true) {
BRow* row = iterator.CurrentRow();
if (row) {
if (row->fNextSelected == 0) {
row->fNextSelected = fSelectionListDummyHead.fNextSelected;
row->fPrevSelected = &fSelectionListDummyHead;
row->fNextSelected->fPrevSelected = row;
row->fPrevSelected->fNextSelected = row;
}
} else
break;
if (row == end)
break;
iterator.GoToNext();
}
Invalidate();
}
bool
OutlineView::FindParent(BRow* row, BRow** outParent, bool* outParentIsVisible)
{
bool result = false;
if (row != NULL && outParent != NULL) {
*outParent = row->fParent;
if (outParentIsVisible != NULL) {
*outParentIsVisible = true;
for (BRow* currentRow = row->fParent; currentRow != NULL;
currentRow = currentRow->fParent) {
if (!currentRow->fIsExpanded) {
*outParentIsVisible = false;
break;
}
}
}
result = *outParent != NULL;
}
return result;
}
int32
OutlineView::IndexOf(BRow* row)
{
if (row) {
if (row->fParent == 0)
return fRows.IndexOf(row);
ASSERT(row->fParent->fChildList);
return row->fParent->fChildList->IndexOf(row);
}
return B_ERROR;
}
void
OutlineView::InvalidateCachedPositions()
{
if (fFocusRow)
FindRect(fFocusRow, &fFocusRowRect);
}
float
OutlineView::GetColumnPreferredWidth(BColumn* column)
{
float preferred = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); BRow* row =
iterator.CurrentRow(); iterator.GoToNext()) {
BField* field = row->GetField(column->fFieldID);
if (field) {
float width = column->GetPreferredWidth(field, this)
+ iterator.CurrentLevel() * kOutlineLevelIndent;
preferred = max_c(preferred, width);
}
}
BString name;
column->GetColumnName(&name);
preferred = max_c(preferred, StringWidth(name));
if (preferred < column->MinWidth())
preferred = column->MinWidth();
else if (preferred > column->MaxWidth())
preferred = column->MaxWidth();
return preferred;
}
RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer* list,
bool openBranchesOnly)
:
fStackIndex(0),
fCurrentListIndex(0),
fCurrentListDepth(0),
fOpenBranchesOnly(openBranchesOnly)
{
if (list == 0 || list->CountItems() == 0)
fCurrentList = 0;
else
fCurrentList = list;
}
BRow*
RecursiveOutlineIterator::CurrentRow() const
{
if (fCurrentList == 0)
return 0;
return fCurrentList->ItemAt(fCurrentListIndex);
}
void
RecursiveOutlineIterator::GoToNext()
{
if (fCurrentList == 0)
return;
if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) {
fCurrentList = 0;
return;
}
BRow* currentRow = fCurrentList->ItemAt(fCurrentListIndex);
if(currentRow) {
if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly)
&& currentRow->fChildList->CountItems() > 0) {
if (fCurrentListIndex < fCurrentList->CountItems() - 1) {
fStack[fStackIndex].fRowSet = fCurrentList;
fStack[fStackIndex].fIndex = fCurrentListIndex + 1;
fStack[fStackIndex].fDepth = fCurrentListDepth;
fStackIndex++;
}
fCurrentList = currentRow->fChildList;
fCurrentListIndex = 0;
fCurrentListDepth++;
} else if (fCurrentListIndex < fCurrentList->CountItems() - 1)
fCurrentListIndex++;
else if (--fStackIndex >= 0) {
fCurrentList = fStack[fStackIndex].fRowSet;
fCurrentListIndex = fStack[fStackIndex].fIndex;
fCurrentListDepth = fStack[fStackIndex].fDepth;
} else
fCurrentList = 0;
}
}
int32
RecursiveOutlineIterator::CurrentLevel() const
{
return fCurrentListDepth;
}