Open Tracker License
Terms and Conditions
Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice applies to all licensees
and shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Be Incorporated shall not be
used in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from Be Incorporated.
Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
of Be Incorporated in the United States and other countries. Other brand product
names are registered trademarks or trademarks of their respective holders.
All rights reserved.
*/
/
/ File: ColumnListView.cpp
/
/ Description: Experimental multi-column list view.
/
/ Copyright 2000+, Be Incorporated, All Rights Reserved
/ By Jeff Bush
/
*******************************************************************************/
#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;
}
of the rows must belong to the same parent row.
*/
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); // Using color set above
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);
}
}
selection, but does not trigger any UI update.
*/
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);
}
}
}
}
but will make no adjustments to the UI elements such as the scrollbar.
Returns the index of the row at which the row was added.
*/
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;
}
`_rect` returning true if the row is currently visible.
*/
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;
}