Files
haiku-beta6/src/kits/interface/ColumnListView.cpp
T
Jérôme Duval 5c99440e0f Added ColorTools and ColumnListView from Vision cvs repository
(ColorTools.h : 1.2, ColorTools.cpp : 1.4,
ColumnListView.h : 1.13, ColumnListView.cpp : 1.3)
Original code from OpenTracker


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@8984 a95241bf-73f2-0310-859d-f6bbb57e9c96
2004-09-16 20:42:34 +00:00

4177 lines
110 KiB
C++

/*
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 <typeinfo>
#include <stdio.h>
#include <stdlib.h>
#include <Application.h>
#include <Bitmap.h>
#if _INCLUDES_CLASS_CURSOR
#include <Cursor.h>
#endif
#include <Debug.h>
#include <GraphicsDefs.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <ScrollBar.h>
#include <String.h>
#include <Window.h>
#include "ObjectList.h"
#include "ColumnListView.h"
#include "ColorTools.h"
/*
#ifndef _ARCHIVE_DEFS_H
#include <archive_defs.h>
#endif
*/
#define DOUBLE_BUFFERED_COLUMN_RESIZE 1
#define SMART_REDRAW 1
#define DRAG_TITLE_OUTLINE 1
#define CONSTRAIN_CLIPPING_REGION 1
#define LOWER_SCROLLBAR 1
namespace BPrivate {
static const unsigned char kResizeCursorData[] = {
16, 1, 8, 8,
0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 9, 0x90, 0x19, 0x98, 0x39, 0x09c, 0x79, 0x9e,
0x79, 0x9e, 0x39, 0x9c, 0x19, 0x98, 0x9, 0x90, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0,
3, 0xc0, 3, 0xc0, 3, 0xc0, 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfa, 0x7f, 0xfe, 0xff, 0xff,
0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfa, 0x1f, 0xf8, 0xf, 0xf0, 3, 0xc0, 3, 0xc0, 3, 0xc0
};
static const unsigned char kMaxResizeCursorData[] = {
16, 1, 8, 8,
0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 9, 0x80, 0x19, 0x80, 0x39, 0x80, 0x79, 0x80,
0x79, 0x80, 0x39, 0x80, 0x19, 0x80, 0x9, 0x80, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0,
3, 0xc0, 3, 0xc0, 3, 0xc0, 0xf, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0, 0x7f, 0xc0, 0xff, 0xc0,
0xff, 0xc0, 0x7f, 0xc0, 0x3f, 0xc0, 0x1f, 0xc0, 0xf, 0xc0, 3, 0xc0, 3, 0xc0, 3, 0xc0
};
static const unsigned char kMinResizeCursorData[] = {
16, 1, 8, 8,
0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 1, 0x90, 1, 0x98, 1, 0x09c, 1, 0x9e,
1, 0x9e, 1, 0x9c, 1, 0x98, 1, 0x90, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0,
3, 0xc0, 3, 0xc0, 3, 0xc0, 3, 0xf0, 3, 0xf8, 3, 0xfa, 3, 0xfe, 3, 0xff,
3, 0xff, 3, 0xfe, 3, 0xfa, 3, 0xf8, 3, 0xf0, 3, 0xc0, 3, 0xc0, 3, 0xc0
};
static const unsigned char kColumnMoveCursorData[] = {
16, 1, 8, 8,
1, 0x80, 3, 0xc0, 7, 0xe0, 0, 0, 0, 0, 0x20, 4, 0x61, 0x86, 0xe3, 0xc7,
0xe3, 0xc7, 0x61, 0x86, 0x20, 4, 0, 0, 0, 0, 7, 0xe0, 3, 0xc0, 1, 0x80,
1, 0x80, 3, 0xc0, 7, 0xe0, 0, 0, 0, 0, 0x20, 4, 0x61, 0x86, 0xe3, 0xc7,
0xe3, 0xc7, 0x61, 0x86, 0x20, 4, 0, 0, 0, 0, 7, 0xe0, 3, 0xc0, 1, 0x80
};
static const unsigned char kUpSortArrow8x8[] = {
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 kDownSortArrow8x8[] = {
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 kUpSortArrow8x8Invert[] = {
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 kDownSortArrow8x8Invert[] = {
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 rgb_color kTitleColor = {215, 215, 215, 255};
static const rgb_color kTitleTextColor = { 0, 0, 0, 255 };
static const rgb_color kDefaultBackgroundColor = {236, 236, 236, 255};
static const rgb_color kRowDividerColor = {148, 148, 148, 255};
static const rgb_color kDefaultSelectionColor = {255, 255, 255, 255};
static const rgb_color kDefaultEditColor = {180, 180, 180, 180};
static const rgb_color kNonFocusSelectionColor = {220, 220, 220, 255};
*/
static const float kTitleHeight = 17.0;
static const float kLatchWidth = 15.0;
static const rgb_color kColor[B_COLOR_TOTAL] =
{
{236, 236, 236, 255}, // B_COLOR_BACKGROUND
{ 0, 0, 0, 255}, // B_COLOR_TEXT
{148, 148, 148, 255}, // B_COLOR_ROW_DIVIDER
{255, 255, 255, 255}, // B_COLOR_SELECTION
{ 0, 0, 0, 255}, // B_COLOR_SELECTION_TEXT
{220, 220, 220, 255}, // B_COLOR_NON_FOCUS_SELECTION
{180, 180, 180, 180}, // B_COLOR_EDIT_BACKGROUND
{ 0, 0, 0, 255}, // B_COLOR_EDIT_TEXT
{215, 215, 215, 255}, // B_COLOR_HEADER_BACKGROUND
{ 0, 0, 0, 255}, // B_COLOR_HEADER_TEXT
{ 0, 0, 0, 255}, // B_COLOR_SEPARATOR_LINE
{ 0, 0, 0, 255}, // B_COLOR_SEPARATOR_BORDER
};
static const int32 kMaxDepth = 1024;
static const float kLeftMargin = kLatchWidth;
static const float kRightMargin = kLatchWidth;
static const float kBottomMargin = kLatchWidth;
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';
class BRowContainer : public BObjectList<BRow>
{
};
class TitleView : public BView {
public:
TitleView(BRect, OutlineView*, BList *visibleColumns, BList *sortColumns,
BColumnListView *masterView, uint32 resizingMode);
~TitleView();
void ColumnAdded(BColumn*);
void SetColumnVisible(BColumn*, bool);
virtual void Draw(BRect rect);
virtual void ScrollTo(BPoint);
virtual void MessageReceived(BMessage*);
virtual void MouseDown(BPoint);
virtual void MouseMoved(BPoint, uint32, const BMessage*);
virtual void MouseUp(BPoint position);
virtual void FrameResized(float width, float height);
void MoveColumn(BColumn *column, int32 index);
void SetColumnFlags(column_flags flags);
void SetEditMode(bool state) { fEditMode = state; }
private:
void GetTitleRect(BColumn*, BRect *out_rect);
int32 FindColumn(BPoint, float *out_leftEdge);
void FixScrollBar(bool scrollToFit);
void DragSelectedColumn(BPoint);
void ResizeSelectedColumn(BPoint);
void ComputeDragBoundries(BColumn*, BPoint);
void DrawTitle(BView*, BRect, BColumn*, bool depressed);
OutlineView *fOutlineView;
BList *fColumns;
BList *fSortColumns;
float fColumnsWidth;
BRect fVisibleRect;
#if DOUBLE_BUFFERED_COLUMN_RESIZE
BBitmap *fDrawBuffer;
BView *fDrawBufferView;
#endif
enum {
INACTIVE,
RESIZING_COLUMN,
PRESSING_COLUMN,
DRAG_COLUMN_INSIDE_TITLE,
DRAG_COLUMN_OUTSIDE_TITLE
} fCurrentState;
BPopUpMenu *fColumnPop;
BColumnListView *fMasterView;
bool fEditMode;
int32 fColumnFlags;
// State information for resizing/dragging
BColumn *fSelectedColumn;
BRect fSelectedColumnRect;
bool fResizingFirstColumn;
BPoint fClickPoint; // offset within cell
float fLeftDragBoundry;
float fRightDragBoundry;
BPoint fCurrentDragPosition;
BBitmap *fUpSortArrow;
BBitmap *fDownSortArrow;
#if _INCLUDES_CLASS_CURSOR
BCursor *fResizeCursor;
BCursor *fMinResizeCursor;
BCursor *fMaxResizeCursor;
BCursor *fColumnMoveCursor;
#endif
typedef BView _inherited;
};
class OutlineView : public BView {
public:
OutlineView(BRect, BList *visibleColumns, BList *sortColumns, BColumnListView *listView);
~OutlineView();
virtual void Draw(BRect);
const BRect& VisibleRect() const;
void RedrawColumn(BColumn *column, float leftEdge, bool isFirstColumn);
void StartSorting();
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 RemoveRow(BRow*);
BRowContainer* RowList();
void UpdateRow(BRow*);
bool FindParent(BRow *row, BRow **out_parent, bool *out_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 *out_indent, float *out_top);
bool FindRect(const BRow *row, BRect *out_rect);
void ScrollTo(const BRow* Row);
void Clear();
void SetSelectionMode(list_view_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 pt);
virtual void MouseDown(BPoint);
virtual void MouseMoved(BPoint, uint32, const BMessage*);
virtual void MouseUp(BPoint);
virtual void MessageReceived(BMessage*);
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);
void AddSorted(BRowContainer *list, BRow *row);
void RecursiveDeleteRows(BRowContainer *list, bool owner);
void InvalidateCachedPositions();
bool FindVisibleRect(BRow *row, BRect *out_rect);
BList* fColumns;
BList* fSortColumns;
float fItemsHeight;
BRowContainer fRows;
BRect fVisibleRect;
#if DOUBLE_BUFFERED_COLUMN_RESIZE
BBitmap* fDrawBuffer;
BView* fDrawBufferView;
#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;
// State information for mouse/keyboard interaction
BPoint fClickPoint;
bool fDragging;
int32 fClickCount;
BRow *fTargetRow;
float fTargetRowTop;
BRect fLatchRect;
float fDropHighlightY;
friend class RecursiveOutlineIterator;
typedef BView _inherited;
};
class RecursiveOutlineIterator {
public:
RecursiveOutlineIterator(BRowContainer*, 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;
};
} // namespace BPrivate
using namespace BPrivate;
BField::BField()
{
}
BField::~BField()
{
}
// #pragma mark -
void BColumn::MouseMoved(BColumnListView */*parent*/, BRow */*row*/, BField */*field*/,
BRect /*field_rect*/, BPoint/*point*/, uint32 /*buttons*/, int32 /*code*/)
{
}
void BColumn::MouseDown( BColumnListView */*parent*/, BRow */*row*/, BField */*field*/,
BRect /*field_rect*/, BPoint /*point*/, uint32 /*buttons*/)
{
}
void BColumn::MouseUp(BColumnListView */*parent*/, BRow */*row*/, BField */*field*/)
{
}
// #pragma mark -
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(0L);
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);
BRect inv;
fList->GetRowRect(this, &inv);
fList->Invalidate(inv);
}
fFields.AddItem(field, logicalFieldIndex);
}
float BRow::Height() const
{
return fHeight;
}
bool BRow::IsExpanded() const
{
return fIsExpanded;
}
void
BRow::ValidateFields() const
{
for( int32 i = 0; i < CountFields(); i++ )
{
ValidateField(GetField(i), i);
}
}
void
BRow::ValidateField(const BField *field, int32 logicalFieldIndex) const
{
// The Fields may be moved by the user, but the logicalFieldIndexes
// do not change, so we need to map them over when checking the
// Field types.
BColumn* col = NULL;
int32 items = fList->CountColumns();
for( int32 i = 0 ; i < items; ++i )
{
col = fList->ColumnAt(i);
if( col->LogicalFieldNum() == logicalFieldIndex )
break;
}
if( NULL == col )
{
BString dbmessage("\n\n\tThe parent BColumnListView does not have "
"\n\ta BColumn at the logical field index ");
dbmessage << logicalFieldIndex << ".\n\n";
printf(dbmessage.String());
}
else
{
if( false == col->AcceptsField(field) )
{
BString dbmessage("\n\n\tThe BColumn of type ");
dbmessage << typeid(*col).name() << "\n\tat logical field index "
<< logicalFieldIndex << "\n\tdoes not support the field type "
<< typeid(*field).name() << ".\n\n";
debugger(dbmessage.String());
}
}
}
// #pragma mark -
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)";
}
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;
}
// #pragma mark -
BColumnListView::BColumnListView(BRect rect, const char *name, uint32 resizingMode,
uint32 drawFlags, border_style border, bool showHorizontalScrollbar)
: BView(rect, name, resizingMode, drawFlags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
fStatusView(0),
fSelectionMessage(0),
fSortingEnabled(true),
fLatchWidth(kLatchWidth),
fBorderStyle(border)
{
SetViewColor(B_TRANSPARENT_32_BIT);
BRect bounds(rect);
bounds.OffsetTo(0, 0);
for (int i = 0; i < (int)B_COLOR_TOTAL; i++)
fColorList[i] = kColor[i];
BRect titleRect(bounds);
titleRect.bottom = titleRect.top + kTitleHeight;
#if !LOWER_SCROLLBAR
titleRect.right -= B_V_SCROLL_BAR_WIDTH + 1;
#endif
BRect outlineRect(bounds);
outlineRect.top = titleRect.bottom + 1.0;
outlineRect.right -= B_V_SCROLL_BAR_WIDTH + 1;
if(showHorizontalScrollbar)
outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT + 1;
BRect vScrollBarRect(bounds);
#if LOWER_SCROLLBAR
vScrollBarRect.top += kTitleHeight;
#endif
vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH;
if(showHorizontalScrollbar)
vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
BRect hScrollBarRect(bounds);
hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT;
hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH;
// Adjust stuff so the border will fit.
if (fBorderStyle == B_PLAIN_BORDER) {
titleRect.InsetBy(1, 0);
titleRect.top++;
outlineRect.InsetBy(1, 0);
outlineRect.bottom--;
vScrollBarRect.OffsetBy(-1, 0);
vScrollBarRect.InsetBy(0, 1);
hScrollBarRect.OffsetBy(0, -1);
hScrollBarRect.InsetBy(1, 0);
} else if (fBorderStyle == B_FANCY_BORDER) {
titleRect.InsetBy(2, 0);
titleRect.top += 2;
outlineRect.InsetBy(2, 0);
outlineRect.bottom -= 2;
vScrollBarRect.OffsetBy(-2, 0);
vScrollBarRect.InsetBy(0, 2);
hScrollBarRect.OffsetBy(0, -2);
hScrollBarRect.InsetBy(2, 0);
}
fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
AddChild(fOutlineView);
// Adapt to correct resizing mode
uint32 fParentFlags = resizingMode;
// Always follow LEFT_RIGHT
uint32 fTitleFlags = B_FOLLOW_LEFT_RIGHT;
if ((fParentFlags & B_FOLLOW_TOP) && (fParentFlags & ~B_FOLLOW_BOTTOM)) {
fTitleFlags |= B_FOLLOW_TOP;
}
else if ((fParentFlags & B_FOLLOW_BOTTOM) && (fParentFlags & ~B_FOLLOW_TOP)) {
fTitleFlags |= B_FOLLOW_BOTTOM;
}
fTitleView = new TitleView(titleRect, fOutlineView, &fColumns, &fSortColumns, this, fTitleFlags);
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(!showHorizontalScrollbar)
fHorizontalScrollBar->Hide();
fOutlineView->FixScrollBar(true);
}
BColumnListView::~BColumnListView()
{
while (true) {
BColumn *column = (BColumn*) fColumns.RemoveItem(0L);
if (column == 0)
break;
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(); // Erase sort indicators
}
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(); // Erase sort indicators
}
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 != 0);
column->fList = this;
column->fFieldID = logicalFieldIndex;
// sanity check. If there is already a field with this ID, remove it.
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 != 0);
fTitleView->MoveColumn(column, index);
}
void BColumnListView::RemoveColumn(BColumn *column)
{
if (fColumns.HasItem(column)) {
SetColumnVisible(column, false);
Window()->UpdateIfNeeded();
fColumns.RemoveItem(column);
}
}
int32 BColumnListView::CountColumns() const
{
return fColumns.CountItems();
}
BColumn* BColumnListView::ColumnAt(int32 field) const
{
return (BColumn*) fColumns.ItemAt(field);
}
void BColumnListView::SetColumnVisible(BColumn *column, bool visible)
{
fTitleView->SetColumnVisible(column, visible);
}
void BColumnListView::SetColumnVisible(int32 index, bool isVisible)
{
BColumn *column = ColumnAt(index);
if (column)
column->SetVisible(isVisible);
}
bool BColumnListView::IsColumnVisible(int32 index) const
{
BColumn *column = ColumnAt(index);
if (column)
return column->IsVisible();
return false;
}
void BColumnListView::SetColumnFlags(column_flags flags)
{
fTitleView->SetColumnFlags(flags);
}
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 **out_parent, bool *out_isVisible) const
{
return fOutlineView->FindParent(row, out_parent, out_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::RemoveRow(BRow *row)
{
fOutlineView->RemoveRow(row);
row->fList = NULL;
}
void BColumnListView::UpdateRow(BRow *row)
{
fOutlineView->UpdateRow(row);
}
void BColumnListView::ScrollTo(const BRow* Row)
{
fOutlineView->ScrollTo(Row);
}
void BColumnListView::Clear()
{
fOutlineView->Clear();
}
void BColumnListView::SetFont(const BFont *font, uint32 mask)
{
// This method is deprecated.
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 color_num, const rgb_color color)
{
if ((int)color_num < 0)
{
ASSERT(false);
color_num = (ColumnListViewColor) 0;
}
if ((int)color_num >= (int)B_COLOR_TOTAL)
{
ASSERT(false);
color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1);
}
fColorList[color_num] = color;
}
rgb_color BColumnListView::Color(ColumnListViewColor color_num) const
{
if ((int)color_num < 0)
{
ASSERT(false);
color_num = (ColumnListViewColor) 0;
}
if ((int)color_num >= (int)B_COLOR_TOTAL)
{
ASSERT(false);
color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1);
}
return fColorList[color_num];
}
void BColumnListView::SetHighColor(rgb_color color)
{
BView::SetHighColor(color);
// fOutlineView->Invalidate(); // Redraw things with the new color
// Note that this will currently cause
// an infinite loop, refreshing over and over.
// A better solution is needed.
}
void BColumnListView::SetSelectionColor(rgb_color color)
{
fColorList[B_COLOR_SELECTION] = color;
}
void BColumnListView::SetBackgroundColor(rgb_color color)
{
fColorList[B_COLOR_BACKGROUND] = color;
fOutlineView->Invalidate(); // Repaint with new color
}
void BColumnListView::SetEditColor(rgb_color color)
{
fColorList[B_COLOR_EDIT_BACKGROUND] = color;
}
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;
GetRowRect(row, &rect);
if (inColumn) {
float leftEdge = MAX(kLeftMargin, LatchWidth());
for (int index = 0; index < fColumns.CountItems(); index++) {
BColumn *column = (BColumn*) fColumns.ItemAt(index);
if (!column->IsVisible())
continue;
if (column == inColumn) {
rect.left = leftEdge;
rect.right = rect.left + column->Width();
break;
}
leftEdge += column->Width() + 1;
}
}
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);
}
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;
view->SetHighColor(0, 0, 0);
// Make Square
int32 sideLen = rect.IntegerWidth();
if( sideLen > rect.IntegerHeight() )
{
sideLen = rect.IntegerHeight();
}
// Make Center
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);
// Why it is a pixel high? I don't know.
itemRect.OffsetBy(0, -1);
itemRect.InsetBy(rectInset, rectInset);
// Make it an odd number of pixels wide, the latch looks better this way
if (1 == (itemRect.IntegerWidth() % 2))
{
itemRect.right += 1;
itemRect.bottom += 1;
}
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:
// No drawing
break;
}
}
void BColumnListView::MakeFocus(bool isFocus)
{
Invalidate(); // Redraw focus marks around view
BView::MakeFocus(isFocus);
}
void BColumnListView::MessageReceived(BMessage *message)
{
// Propagate mouse wheel messages down to child, so that it can
// scroll. Note we have done so, so we don't go into infinite
// recursion if this comes back up here.
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;
}
}
BView::MessageReceived(message);
}
void BColumnListView::KeyDown(const char *bytes, int32 numBytes)
{
char c = bytes[0];
switch (c) {
case B_RIGHT_ARROW:
case B_LEFT_ARROW: {
float minVal, maxVal;
fHorizontalScrollBar->GetRange(&minVal, &maxVal);
float smallStep, largeStep;
fHorizontalScrollBar->GetSteps(&smallStep, &largeStep);
float oldVal = fHorizontalScrollBar->Value();
float newVal = oldVal;
if (c == B_LEFT_ARROW)
newVal -= smallStep;
else if (c == B_RIGHT_ARROW)
newVal += smallStep;
if (newVal < minVal)
newVal = minVal;
else if (newVal > maxVal)
newVal = maxVal;
fHorizontalScrollBar->SetValue(newVal);
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 (c == B_PAGE_UP)
newValue -= largeStep;
else
newValue += largeStep;
if (newValue > maxValue)
newValue = maxValue;
else if (newValue < minValue)
newValue = minValue;
fVerticalScrollBar->SetValue(newValue);
// Option + pgup or pgdn scrolls and changes the selection.
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();
// Focus and selection appearance changes with focus
Invalidate(); // Redraw focus marks around view
BView::WindowActivated(active);
}
void BColumnListView::Draw(BRect)
{
BRect rect = Bounds();
PushState();
BRect cornerRect(rect.right - B_V_SCROLL_BAR_WIDTH, rect.bottom - B_H_SCROLL_BAR_HEIGHT,
rect.right, rect.bottom);
if (fBorderStyle == B_PLAIN_BORDER) {
BView::SetHighColor(0, 0, 0);
StrokeRect(rect);
cornerRect.OffsetBy(-1, -1);
} else if (fBorderStyle == B_FANCY_BORDER) {
bool isFocus = IsFocus() && Window()->IsActive();
if (isFocus)
BView::SetHighColor(0, 0, 190); // Need to find focus color programatically
else
BView::SetHighColor(255, 255, 255);
StrokeRect(rect);
if (!isFocus)
BView::SetHighColor(184, 184, 184);
else
BView::SetHighColor(152, 152, 152);
rect.InsetBy(1,1);
StrokeRect(rect);
cornerRect.OffsetBy(-2, -2);
}
BView::SetHighColor(215, 215, 215); // fills lower right rect between scroll bars
FillRect(cornerRect);
PopState();
}
void BColumnListView::SaveState(BMessage *msg)
{
msg->MakeEmpty();
// Damn compiler issuing l43m incorrect warnings.
int i;
for (i = 0; ;i++)
{
BColumn *col = (BColumn*) fColumns.ItemAt(i);
if(!col)
break;
msg->AddInt32("ID",col->fFieldID);
msg->AddFloat("width",col->fWidth);
msg->AddBool("visible",col->fVisible);
}
msg->AddBool("sortingenabled",fSortingEnabled);
if(fSortingEnabled)
{
for (i = 0; ;i++)
{
BColumn *col = (BColumn*) fSortColumns.ItemAt(i);
if(!col)
break;
msg->AddInt32("sortID",col->fFieldID);
msg->AddBool("sortascending",col->fSortAscending);
}
}
}
void BColumnListView::LoadState(BMessage *msg)
{
for(int i=0;;i++)
{
int32 ID;
if(B_OK!=msg->FindInt32("ID",i,&ID))
break;
for(int j=0;;j++)
{
BColumn *col = (BColumn*) fColumns.ItemAt(j);
if(!col)
break;
if(col->fFieldID==ID)
{
// move this column to position 'i' and set its attributes
MoveColumn(col,i);
float f;
if(B_OK==msg->FindFloat("width",i,&f))
col->SetWidth(f);
bool b;
if(B_OK==msg->FindBool("visible",i,&b))
col->SetVisible(b);
}
}
}
bool b;
if(B_OK==msg->FindBool("sortingenabled",&b))
{
SetSortingEnabled(b);
// Damn compiler issuing l43m incorrect warnings.
for(int k=0;;k++)
{
int32 ID;
if(B_OK!=msg->FindInt32("sortID", k, &ID))
break;
for(int j=0;;j++)
{
BColumn *col = (BColumn*) fColumns.ItemAt(j);
if(!col)
break;
if(col->fFieldID==ID)
{
// add this column to the sort list
bool val;
if(B_OK==msg->FindBool("sortascending", k, &val))
SetSortColumn(col, true, val);
}
}
}
}
}
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();
}
}
// #pragma mark -
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),
fColumnsWidth(0),
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_32_BIT);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
// xxx this needs to be smart about the size of the backbuffer.
BRect doubleBufferRect(0, 0, 600, 35);
fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
B_FOLLOW_ALL_SIDES, 0);
fDrawBuffer->Lock();
fDrawBuffer->AddChild(fDrawBufferView);
fDrawBuffer->Unlock();
#endif
fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_COLOR_8_BIT);
fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_COLOR_8_BIT);
fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_COLOR_8_BIT);
fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_COLOR_8_BIT);
#if _INCLUDES_CLASS_CURSOR
fResizeCursor = new BCursor(kResizeCursorData);
fMinResizeCursor = new BCursor(kMinResizeCursorData);
fMaxResizeCursor = new BCursor(kMaxResizeCursorData);
fColumnMoveCursor = new BCursor(kColumnMoveCursorData);
#endif
FixScrollBar(true);
}
TitleView::~TitleView()
{
delete fColumnPop;
fColumnPop = NULL;
fDrawBuffer->Lock();
fDrawBufferView->RemoveSelf();
fDrawBuffer->Unlock();
delete fDrawBufferView;
delete fDrawBuffer;
delete fUpSortArrow;
delete fDownSortArrow;
#if _INCLUDES_CLASS_CURSOR
delete fResizeCursor;
delete fMaxResizeCursor;
delete fMinResizeCursor;
delete fColumnMoveCursor;
#endif
}
void TitleView::ColumnAdded(BColumn *column)
{
fColumnsWidth += column->Width() + 1;
FixScrollBar(false);
Invalidate();
}
// Could use a CopyBits here.
void TitleView::SetColumnVisible(BColumn *column, bool visible)
{
if (column->fVisible == visible)
return;
// If setting it visible, do this first so we can find its position
// to invalidate. If hiding it, do it last.
if (visible)
column->fVisible = visible;
BRect titleInvalid;
GetTitleRect(column, &titleInvalid);
// Now really set the visibility
column->fVisible = visible;
if (visible)
fColumnsWidth += column->Width();
else
fColumnsWidth -= column->Width();
BRect outlineInvalid(fOutlineView->VisibleRect());
outlineInvalid.left = titleInvalid.left;
titleInvalid.right = outlineInvalid.right;
Invalidate(titleInvalid);
fOutlineView->Invalidate(outlineInvalid);
}
void TitleView::GetTitleRect(BColumn *findColumn, BRect *out_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) {
out_rect->Set(leftEdge, 0, leftEdge + column->Width(), fVisibleRect.bottom);
return;
}
leftEdge += column->Width() + 1;
}
TRESPASS();
}
int32 TitleView::FindColumn(BPoint position, float *out_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()) {
*out_leftEdge = leftEdge;
return index;
}
leftEdge += column->Width() + 1;
}
return 0;
}
void TitleView::FixScrollBar(bool scrollToFit)
{
BScrollBar *hScrollBar = ScrollBar(B_HORIZONTAL);
if (hScrollBar) {
float virtualWidth = fColumnsWidth + MAX(kLeftMargin, fMasterView->LatchWidth()) +
kRightMargin * 2;
if (virtualWidth > fVisibleRect.Width()) {
hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
// Perform the little trick if the user is scrolled over too far.
// See OutlineView::FixScrollBar for a more in depth explanation
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); // disable scroll bar.
}
}
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);
// Redraw the new column 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)
{
// Re-add the column at the end of the list.
fColumns->AddItem((void*) column);
}
else
{
fColumns->AddItem((void*) column, index);
}
}
void TitleView::SetColumnFlags(column_flags flags)
{
fColumnFlags = flags;
}
void TitleView::ResizeSelectedColumn(BPoint position)
{
float minWidth = fSelectedColumn->MinWidth();
float maxWidth = fSelectedColumn->MaxWidth();
float originalEdge = fSelectedColumnRect.left + fSelectedColumn->Width();
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) {
BRect originalRect(originalEdge, 0, 1000000.0, fVisibleRect.Height());
BRect movedRect(originalRect);
movedRect.OffsetBy(dX, 0);
// Update the size of the title column
BRect sourceRect(0, 0, fSelectedColumn->Width(), fVisibleRect.Height());
BRect destRect(sourceRect);
destRect.OffsetBy(fSelectedColumnRect.left, 0);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
fDrawBuffer->Lock();
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_HEADER_BACKGROUND));
fDrawBufferView->FillRect(sourceRect);
DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
fDrawBufferView->Sync();
fDrawBuffer->Unlock();
CopyBits(originalRect, movedRect);
DrawBitmap(fDrawBuffer, sourceRect, destRect);
#else
CopyBits(originalRect, movedRect);
SetHighColor(fMasterView->Color(B_COLOR_HEADER_BACKGROUND));
FillRect(destRect);
DrawTitle(this, destRect, fSelectedColumn, false);
#endif
// Update the body view
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);
fColumnsWidth += dX;
// Update the cursor
#if _INCLUDES_CLASS_CURSOR
if (fSelectedColumn->Width() == minWidth)
SetViewCursor(fMinResizeCursor, false);
else if (fSelectedColumn->Width() == maxWidth)
SetViewCursor(fMaxResizeCursor, false);
else
SetViewCursor(fResizeCursor, false);
#endif
}
}
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;
rgb_color borderColor = mix_color(fMasterView->Color(B_COLOR_HEADER_BACKGROUND), make_color(0, 0, 0), 128);
rgb_color backgroundColor;
rgb_color bevelHigh;
rgb_color bevelLow;
// Want exterior borders to overlap.
rect.right += 1;
drawRect = rect;
drawRect.InsetBy(2, 2);
if (depressed) {
backgroundColor = mix_color(fMasterView->Color(B_COLOR_HEADER_BACKGROUND), make_color(0, 0, 0), 64);
bevelHigh = mix_color(backgroundColor, make_color(0, 0, 0), 64);
bevelLow = mix_color(backgroundColor, make_color(255, 255, 255), 128);
drawRect.left++;
drawRect.top++;
} else {
backgroundColor = fMasterView->Color(B_COLOR_HEADER_BACKGROUND);
bevelHigh = mix_color(backgroundColor, make_color(255, 255, 255), 192);
bevelLow = mix_color(backgroundColor, make_color(0, 0, 0), 64);
drawRect.bottom--;
drawRect.right--;
}
view->SetHighColor(borderColor);
view->StrokeRect(rect);
view->BeginLineArray(4);
view->AddLine(BPoint(rect.left+1, rect.top+1), BPoint(rect.right-1, rect.top+1), bevelHigh);
view->AddLine(BPoint(rect.left+1, rect.top+1), BPoint(rect.left+1, rect.bottom-1), bevelHigh);
view->AddLine(BPoint(rect.right-1, rect.top+1), BPoint(rect.right-1, rect.bottom-1), bevelLow);
view->AddLine(BPoint(rect.left+2, rect.bottom-1), BPoint(rect.right-1, rect.bottom-1), bevelLow);
view->EndLineArray();
font_height fh;
GetFontHeight(&fh);
float baseline = floor(drawRect.top + fh.ascent
+ (drawRect.Height()+1-(fh.ascent+fh.descent))/2);
view->SetHighColor(backgroundColor);
view->SetLowColor(backgroundColor);
view->FillRect(rect.InsetByCopy(2, 2));
// If no column given, nothing else to draw.
if (!column)
return;
view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
BFont font;
GetFont(&font);
view->SetFont(&font);
int sortIndex = fSortColumns->IndexOf(column);
if (sortIndex >= 0) {
// Draw sort notation.
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;
// Adjust title stuff for sort indicator
drawRect.right = upperLeft.x - 2;
}
if (drawRect.right > drawRect.left) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion;
clipRegion.Set(drawRect);
view->ConstrainClippingRegion(&clipRegion);
view->PushState();
#endif
view->MovePenTo(BPoint(drawRect.left+8, baseline));
view->SetDrawingMode(B_OP_COPY);
view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
column->DrawTitle(drawRect, view);
#if CONSTRAIN_CLIPPING_REGION
view->PopState();
view->ConstrainClippingRegion(NULL);
#endif
}
}
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;
}
// Bevels for right title margin
if (columnLeftEdge <= invalidRect.right) {
BRect titleRect(columnLeftEdge, 0, Bounds().right+2, fVisibleRect.Height());
DrawTitle(this, titleRect, NULL, false);
}
// Bevels for left title margin
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
// (Internal) Column Drag Indicator
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);
// Perform the little trick if the user is scrolled over too far.
// See OutlineView::ScrollTo for a more in depth explanation
float maxScrollBarValue = fColumnsWidth + MAX(kLeftMargin, fMasterView->LatchWidth()) +
kRightMargin * 2 - 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) continue;
if (column->LogicalFieldNum() == num) {
column->SetVisible(!column->IsVisible());
}
}
}
return;
} else {
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)) {
// Right mouse button -- bring up menu to show/hide columns.
if (!fColumnPop) 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) continue;
BString name;
column->GetColumnName(&name);
BMessage* msg = new BMessage(kToggleColumn);
msg->AddInt32("be:field_num", column->LogicalFieldNum());
BMenuItem* it = new BMenuItem(name.String(), msg);
it->SetMarked(column->IsVisible());
it->SetTarget(me);
fColumnPop->AddItem(it);
}
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->IsVisible())
continue;
if (leftEdge > position.x + kColumnResizeAreaWidth / 2)
break;
// Check for resizing a column
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)) {
fCurrentState = RESIZING_COLUMN;
fSelectedColumn = column;
fSelectedColumnRect.Set(leftEdge, 0, rightEdge, fVisibleRect.Height());
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS |
B_NO_POINTER_HISTORY);
break;
}
fResizingFirstColumn = false;
// Check for clicking on a column.
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 *)
{
if(fEditMode)
return;
// Handle column manipulation
switch (fCurrentState) {
case RESIZING_COLUMN:
ResizeSelectedColumn(position);
break;
case PRESSING_COLUMN: {
if (abs((int32)(position.x - (fClickPoint.x + fSelectedColumnRect.left))) > kColumnResizeAreaWidth
|| abs((int32)(position.y - (fClickPoint.y + fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
// User has moved the mouse more than the tolerable amount,
// initiate a drag.
if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) {
if(fColumnFlags & B_ALLOW_COLUMN_MOVE) {
fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
ComputeDragBoundries(fSelectedColumn, position);
#if _INCLUDES_CLASS_CURSOR
SetViewCursor(fColumnMoveCursor, false);
#endif
#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) {
// Dragged outside view
fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
fSelectedColumn->SetVisible(false);
BRect dragRect(fSelectedColumnRect);
// There is a race condition where the mouse may have moved by the
// time we get to handle this message. If the user drags a column very
// quickly, this results in the annoying bug where the cursor is outside
// of the rectangle that is being dragged around.
// Call GetMouse with the checkQueue flag set to false so we
// can get the most recent position of the mouse. This minimizes
// this problem (although it is currently not possible to completely
// eliminate it).
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)) {
// Dragged outside view
fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE;
fSelectedColumn->SetVisible(false);
BRect dragRect(fSelectedColumnRect);
// See explanation above.
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);
#if DRAG_TITLE_OUTLINE
// Set up the invalid rect to include the rect for the previous position
// of the drag rect, as well as the new one.
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) {
// Drag back into view
EndRectTracking();
fCurrentState = DRAG_COLUMN_INSIDE_TITLE;
fSelectedColumn->SetVisible(true);
DragSelectedColumn(position);
}
break;
case INACTIVE:
// Check for cursor changes if we are over the resize area for
// a column.
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;
}
// Update the cursor
#if _INCLUDES_CLASS_CURSOR
if (resizeColumn) {
if (resizeColumn->Width() == resizeColumn->MinWidth())
SetViewCursor(fMinResizeCursor, false);
else if (resizeColumn->Width() == resizeColumn->MaxWidth())
SetViewCursor(fMaxResizeCursor, false);
else
SetViewCursor(fResizeCursor, false);
} else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false);
#endif
break;
}
}
void TitleView::MouseUp(BPoint position)
{
if(fEditMode)
return;
switch (fCurrentState) {
case RESIZING_COLUMN:
ResizeSelectedColumn(position);
fCurrentState = INACTIVE;
FixScrollBar(false);
#if _INCLUDES_CLASS_CURSOR
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false);
#endif
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(); // xxx Can make this smaller
#else
Invalidate(fSelectedColumnRect);
#endif
#if _INCLUDES_CLASS_CURSOR
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false);
#endif
break;
case DRAG_COLUMN_OUTSIDE_TITLE:
fCurrentState = INACTIVE;
EndRectTracking();
#if _INCLUDES_CLASS_CURSOR
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false);
#endif
break;
default:
;
}
}
void TitleView::FrameResized(float width, float height)
{
fVisibleRect.right = fVisibleRect.left + width;
fVisibleRect.bottom = fVisibleRect.top + height;
FixScrollBar(true);
}
// #pragma mark -
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(24.),
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_32_BIT);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
// xxx this needs to be smart about the size of the buffer. Also, the buffer can
// be shared with the title's buffer.
BRect doubleBufferRect(0, 0, 600, 35);
fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true);
fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view",
B_FOLLOW_ALL_SIDES, 0);
fDrawBuffer->Lock();
fDrawBuffer->AddChild(fDrawBufferView);
fDrawBuffer->Unlock();
#endif
FixScrollBar(true);
fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead;
fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead;
}
OutlineView::~OutlineView()
{
fDrawBuffer->Lock();
fDrawBufferView->RemoveSelf();
fDrawBuffer->Unlock();
delete fDrawBufferView;
delete fDrawBuffer;
Clear();
}
void OutlineView::Clear()
{
DeselectAll(); // Make sure selection list doesn't point to deleted rows!
RecursiveDeleteRows(&fRows, false);
Invalidate();
fItemsHeight = 24.;
FixScrollBar(true);
}
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) {
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) {
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) {
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) {
font_height fh;
GetFontHeight(&fh);
float line = 0.0;
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;
if (line + rowHeight >= fVisibleRect.top) {
BRect sourceRect(0, 0, column->Width(), rowHeight);
BRect destRect(leftEdge, line, leftEdge + column->Width(), line + rowHeight);
#if DOUBLE_BUFFERED_COLUMN_RESIZE
fDrawBuffer->Lock();
if (row->fNextSelected != 0) {
if(fEditMode) {
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
} else {
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_SELECTION));
}
} else {
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND));
}
BFont font;
GetFont(&font);
fDrawBufferView->SetFont(&font);
fDrawBufferView->FillRect(sourceRect);
if (isFirstColumn) {
// If this is the first column, double buffer drawing the latch too.
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(fDrawBufferView, 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;
clipRegion.Set(fieldRect);
fDrawBufferView->ConstrainClippingRegion(&clipRegion);
fDrawBufferView->PushState();
#endif
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_TEXT));
float baseline = floor(fieldRect.top + fh.ascent
+ (fieldRect.Height()+1-(fh.ascent+fh.descent))/2);
fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
column->DrawField(field, fieldRect, fDrawBufferView);
#if CONSTRAIN_CLIPPING_REGION
fDrawBufferView->PopState();
fDrawBufferView->ConstrainClippingRegion(NULL);
#endif
}
if (fFocusRow == row) {
if(!fEditMode) {
fDrawBufferView->SetHighColor(0, 0, 0);
fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top, 10000.0, sourceRect.bottom - 1));
}
}
fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
// StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2));
// StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1));
fDrawBufferView->StrokeLine(BPoint(0, rowHeight), BPoint(Bounds().Width(), rowHeight));
fDrawBufferView->Sync();
fDrawBuffer->Unlock();
SetDrawingMode(B_OP_OVER);
DrawBitmap(fDrawBuffer, sourceRect, destRect);
#else
if (row->fNextSelected != 0) {
if(fEditMode) {
SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
}
else {
SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
SetLowColor(fMasterView->Color(B_COLOR_SELECTION));
}
} else {
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND));
}
FillRect(destRect);
BField *field = row->GetField(column->fFieldID);
if (field) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion;
clipRegion.Set(destRect);
ConstrainClippingRegion(&clipRegion);
PushState();
#endif
SetHighColor(fColorList[B_TEXT_COLOR]);
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();
ConstrainClippingRegion(NULL);
#endif
}
if (fFocusRow == row) {
if(!fEditMode) {
SetHighColor(0, 0, 0);
StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom - 1));
}
}
rgb_color color = HighColor();
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
// StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2));
// StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1));
StrokeLine(BPoint(0, line + rowHeight), BPoint(Bounds().Width(), line + rowHeight));
SetHighColor(color);
#endif
}
}
}
}
void OutlineView::Draw(BRect invalidBounds)
{
#if SMART_REDRAW
BRegion invalidRegion;
GetClippingRegion(&invalidRegion);
#endif
font_height fh;
GetFontHeight(&fh);
float line = 0.0;
int32 numColumns = fColumns->CountItems();
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
BRow *row = iterator.CurrentRow();
if (line > invalidBounds.bottom)
break;
float rowHeight = row->Height();
if (line > invalidBounds.top - rowHeight) {
bool isFirstColumn = true;
float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
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;
// This happens when a column is indented past the
// beginning of the next column.
if (row->fNextSelected != 0) {
if (Window()->IsActive()) {
if(fEditMode)
SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
else
SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
}
else
SetHighColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION));
} else
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
BRect destRect(fullRect);
if (isFirstColumn) {
fullRect.left -= fMasterView->LatchWidth();
destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent;
if (destRect.left >= destRect.right) {
// clipped
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
// Draw the latch widget if it has one.
if (isFirstColumn) {
if (row == fTargetRow && fCurrentState == LATCH_CLICKED) {
// Note that this only occurs if the user is holding
// down a latch while items are added in the background.
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);
}
}
if (row->fNextSelected != 0) {
if (Window()->IsActive()) {
if(fEditMode)
SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
else
SetLowColor(fMasterView->Color(B_COLOR_SELECTION));
}
else
SetLowColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION));
} else
SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND));
SetHighColor(fMasterView->HighColor());
// The master view just holds the high color for us.
BField *field = row->GetField(column->fFieldID);
if (field) {
#if CONSTRAIN_CLIPPING_REGION
BRegion clipRegion;
clipRegion.Set(destRect);
ConstrainClippingRegion(&clipRegion);
PushState();
#endif
SetHighColor(fMasterView->Color(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();
ConstrainClippingRegion(NULL);
#endif
}
}
}
isFirstColumn = false;
fieldLeftEdge += column->Width() + 1;
}
if (fieldLeftEdge <= invalidBounds.right) {
if (row->fNextSelected != 0) {
if (Window()->IsActive()) {
if(fEditMode)
SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
else
SetHighColor(fMasterView->Color(B_COLOR_SELECTION));
}
else
SetHighColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION));
} else
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
line + rowHeight));
}
}
if (fFocusRow == row && fMasterView->IsFocus() && Window()->IsActive()) {
if(!fEditMode) {
SetHighColor(0, 0, 0);
StrokeRect(BRect(0, line, 10000.0, line + rowHeight - 1));
}
}
rgb_color color = HighColor();
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
// StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2));
// StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1));
StrokeLine(BPoint(0, line + rowHeight), BPoint(Bounds().Width(), line + rowHeight));
SetHighColor(color);
line += rowHeight + 1;
}
if (line <= invalidBounds.bottom) {
SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND));
FillRect(BRect(invalidBounds.left, line, invalidBounds.right, invalidBounds.bottom));
}
// Draw the drop target line
if (fDropHighlightY != -1)
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000,
fDropHighlightY + kDropHighlightLineHeight / 2));
}
BRow* OutlineView::FindRow(float ypos, int32 *out_rowIndent, float *out_top)
{
if (out_rowIndent && out_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) {
*out_top = line;
*out_rowIndent = iterator.CurrentLevel();
return row;
}
line += rowHeight + 1;
}
}
return 0;
}
void OutlineView::SetMouseTrackingEnabled(bool enabled)
{
fTrackMouse = enabled;
if (!enabled && fDropHighlightY != -1) {
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000,
fDropHighlightY + kDropHighlightLineHeight / 2)); // Erase the old target line
fDropHighlightY = -1;
}
}
//
// Note that this interaction is not totally safe. If items are added to
// the list in the background, the widget rect will be incorrect, possibly
// resulting in drawing glitches. The code that adds items needs to be a little smarter
// about invalidating state.
//
void OutlineView::MouseDown(BPoint position)
{
if(!fEditMode)
fMasterView->MakeFocus(true);
// Check to see if the user is clicking on a widget to open a section
// of the list.
bool reset_click_count = false;
int32 indent;
float rowTop;
BRow *row = FindRow(position.y, &indent, &rowTop);
if (row) {
// Update fCurrentField
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();
}
}
}
// Handle mouse down
if(handle_field) {
fMouseDown = true;
fFieldRect = new_rect;
fCurrentColumn = new_column;
fCurrentRow = new_row;
fCurrentField = new_field;
fCurrentCode = B_INSIDE_VIEW;
fCurrentColumn->MouseDown(fMasterView,fCurrentRow,fCurrentField,fFieldRect,position,1);
}
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) {
if(fEditMode)
SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
else
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) {
// Unselect row
fTargetRow->fNextSelected->fPrevSelected = fTargetRow->fPrevSelected;
fTargetRow->fPrevSelected->fNextSelected = fTargetRow->fNextSelected;
fTargetRow->fPrevSelected = 0;
fTargetRow->fNextSelected = 0;
fFirstSelectedItem = NULL;
} else {
// Select row
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) {
// User clicked in open space, unhighlight focus row.
fFocusRow = 0;
Invalidate(fFocusRowRect);
}
// We stash the click counts here because the 'clicks' field
// is not in the CurrentMessage() when MouseUp is called... ;(
if (reset_click_count)
fClickCount = 1;
else
Window()->CurrentMessage()->FindInt32("clicks", &fClickCount);
fClickPoint = position;
} // end of MouseDown()
void OutlineView::MouseMoved(BPoint position, uint32 /*transit*/, const BMessage */*message*/)
{
if(!fMouseDown) {
// Update fCurrentField
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();
}
}
}
// Handle mouse moved
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) {
if(fEditMode)
SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND));
else
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
// falls through...
#else
if (fTrackMouse /*&& message*/) {
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 /*&& message*/) {
// Draw a highlight line...
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;
}
}
}
}
}
}
} // end of MouseMoved()
void OutlineView::MouseUp(BPoint position)
{
if(fCurrentField) {
fCurrentColumn->MouseUp(fMasterView,fCurrentRow,fCurrentField);
fMouseDown = false;
}
if(!fEditMode) {
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;
// Falls through
default:
if (fDropHighlightY != -1) {
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000,
fDropHighlightY + kDropHighlightLineHeight / 2)); // Erase the old target line
fDropHighlightY = -1;
}
}
}
} // end of MouseUp()
void OutlineView::MessageReceived(BMessage *message)
{
if (message->WasDropped()) {
fMasterView->MessageDropped(message, ConvertFromScreen(message->DropPoint()));
} else {
BView::MessageReceived(message);
}
}
void OutlineView::ChangeFocusRow(bool up, bool updateSelection, bool addToCurrentSelection)
{
int32 indent;
float top;
float newRowPos = 0;
float verticalScroll = 0;
if (fFocusRow) {
// A row currently has the focus, get information about it
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;
// no row is currently focused, set this to the top of the window
// so we will select the first visible item in the list.
BRow *newRow = FindRow(newRowPos, &indent, &top);
if (newRow) {
if (fFocusRow) {
fFocusRowRect.right = 10000;
Invalidate(fFocusRowRect);
}
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;
}
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) {
// Unselect row
fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected;
fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected;
fFocusRow->fPrevSelected = 0;
fFocusRow->fNextSelected = 0;
} else {
// Select row
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);
}
// xxx Could use CopyBits here to speed things up.
void OutlineView::ExpandOrCollapse(BRow* ParentRow, bool Expand)
{
if (ParentRow) {
if (ParentRow->fIsExpanded == Expand)
return;
ParentRow->fIsExpanded = Expand;
BRect parentRect;
if (FindRect(ParentRow, &parentRect)) {
// Determine my new height
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;
// Adjust focus row if necessary.
if (FindRect(fFocusRow, &fFocusRowRect) == false) {
// focus row is in a subtree that has collapsed, move it up to the parent.
fFocusRow = ParentRow;
FindRect(fFocusRow, &fFocusRowRect);
}
Invalidate(BRect(0, parentRect.top, fVisibleRect.right, fVisibleRect.bottom));
FixScrollBar(false);
}
}
}
void OutlineView::RemoveRow(BRow *row)
{
if (row) {
BRow *parentRow;
bool parentIsVisible;
float subTreeHeight = row->Height();
if (FindParent(row, &parentRow, &parentIsVisible)) {
// adjust height
if (parentIsVisible && (parentRow == 0 || parentRow->fIsExpanded)) {
if (row->fIsExpanded) {
for (RecursiveOutlineIterator iterator(row->fChildList);
iterator.CurrentRow(); iterator.GoToNext())
subTreeHeight += iterator.CurrentRow()->Height();
}
}
}
if (parentRow) {
if (parentRow->fIsExpanded)
fItemsHeight -= subTreeHeight + 1;
}
else {
fItemsHeight -= subTreeHeight + 1;
}
FixScrollBar(false);
if (parentRow)
parentRow->fChildList->RemoveItem(row);
else
fRows.RemoveItem(row);
if (parentRow != 0 && parentRow->fChildList->CountItems() == 0) {
delete parentRow->fChildList;
parentRow->fChildList = 0;
if (parentIsVisible)
Invalidate(); // xxx crude way of redrawing latch
}
if (parentIsVisible && (parentRow == 0 || parentRow->fIsExpanded))
Invalidate(); // xxx make me smarter.
// Adjust focus row if necessary.
if (fFocusRow && FindRect(fFocusRow, &fFocusRowRect) == false) {
// focus row is in a subtree that is gone, move it up to the parent.
fFocusRow = parentRow;
if (fFocusRow)
FindRect(fFocusRow, &fFocusRowRect);
}
// Remove this from the selection if necessary
if (row->fNextSelected != 0) {
row->fNextSelected->fPrevSelected = row->fPrevSelected;
row->fPrevSelected->fNextSelected = row->fNextSelected;
row->fPrevSelected = 0;
row->fNextSelected = 0;
fMasterView->SelectionChanged();
}
fCurrentColumn = 0;
fCurrentRow = 0;
fCurrentField = 0;
}
}
BRowContainer* OutlineView::RowList()
{
return &fRows;
}
void OutlineView::UpdateRow(BRow *row)
{
if (row) {
// Determine if this row has changed its sort order
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) {
// Sort location of this row has changed.
// Remove and re-add in the right spot
SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded));
} else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) {
BRect invalidRect;
if (FindVisibleRect(row, &invalidRect))
Invalidate(invalidRect);
}
}
}
}
void OutlineView::AddRow(BRow* Row, int32 Index, BRow* ParentRow)
{
if(Row) {
Row->fParent = ParentRow;
if (fMasterView->SortingEnabled()) {
// Ignore index here.
if (ParentRow) {
if (ParentRow->fChildList == 0)
ParentRow->fChildList = new BRowContainer;
AddSorted(ParentRow->fChildList, Row);
} else
AddSorted(&fRows, Row);
} else {
// Note, a -1 index implies add to end if sorting is not enabled
if (ParentRow) {
if (ParentRow->fChildList == 0)
ParentRow->fChildList = new BRowContainer;
if (Index < 0 || Index > ParentRow->fChildList->CountItems())
ParentRow->fChildList->AddItem(Row);
else
ParentRow->fChildList->AddItem(Row, Index);
} else {
if (Index < 0 || Index >= fRows.CountItems())
fRows.AddItem(Row);
else
fRows.AddItem(Row, Index);
}
}
if (ParentRow == 0 || ParentRow->fIsExpanded)
fItemsHeight += Row->Height() + 1;
FixScrollBar(false);
BRect newRowRect;
bool newRowIsInOpenBranch = FindRect(Row, &newRowRect);
if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) {
// The focus row has moved.
Invalidate(fFocusRowRect);
FindRect(fFocusRow, &fFocusRowRect);
Invalidate(fFocusRowRect);
}
if (newRowIsInOpenBranch) {
if (fCurrentState == INACTIVE) {
if (newRowRect.bottom < fVisibleRect.top) {
// The new row is totally above the current viewport, move
// everything down and redraw the first line.
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) {
// New item is somewhere in the current region. Scroll everything
// beneath it down and invalidate just the new row rect.
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);
} // otherwise, this is below the currently visible region
} else {
// Adding the item may have caused the item that the user is currently
// selected to move. This would cause annoying drawing and interaction
// bugs, as the position of that item is cached. If this happens, resize
// the scroll bar, then scroll back so the selected item is in view.
BRect targetRect;
if (FindRect(fTargetRow, &targetRect)) {
float delta = targetRect.top - fTargetRowTop;
if (delta != 0) {
// This causes a jump because ScrollBy will copy a chunk of the view.
// Since the actual contents of the view have been offset, we don't
// want this, we just want to change the virtual origin of the window.
// Constrain the clipping region so everything is clipped out so no
// copy occurs.
//
// xxx this currently doesn't work if the scroll bars aren't enabled.
// everything will still move anyway. A minor annoyance.
BRegion emptyRegion;
ConstrainClippingRegion(&emptyRegion);
PushState();
ScrollBy(0, delta);
PopState();
ConstrainClippingRegion(NULL);
fTargetRowTop += delta;
fClickPoint.y += delta;
fLatchRect.OffsetBy(0, delta);
}
}
}
}
// If the parent was previously childless, it will need to have a latch drawn.
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 + kBottomMargin) - fVisibleRect.Height();
vScrollBar->SetProportion(fVisibleRect.Height() / (fItemsHeight + kBottomMargin));
// If the user is scrolled down too far when makes the range smaller, the list
// will jump suddenly, which is undesirable. In this case, don't fix the scroll
// bar here. In ScrollTo, it checks to see if this has occured, and will
// fix the scroll bars sneakily if the user has scrolled up far enough.
if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) {
vScrollBar->SetRange(0.0, maxScrollBarValue);
vScrollBar->SetSteps(20.0, fVisibleRect.Height());
}
} else if (vScrollBar->Value() == 0.0)
vScrollBar->SetRange(0.0, 0.0); // disable scroll bar.
}
}
void OutlineView::AddSorted(BRowContainer *list, BRow *row)
{
if (list && row) {
// Find general vicinity with binary search.
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;
}
// At this point, 'upper' and 'lower' at the last found item.
// Arbitrarily use 'upper' and determine the final insertion
// point -- either before or after this item.
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); // Adding to end.
else
list->AddItem(row, upper); // Insert
}
}
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);
// In FixScrollBar, we might not have been able to change the size of
// the scroll bar because the user was scrolled down too far. Take
// this opportunity to sneak it in if we can.
BScrollBar *vScrollBar = ScrollBar(B_VERTICAL);
float maxScrollBarValue = (fItemsHeight + kBottomMargin) - 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 *out_rect)
{
if (row && out_rect) {
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
if (line > fVisibleRect.bottom)
break;
if (iterator.CurrentRow() == row) {
out_rect->Set(fVisibleRect.left, line, fVisibleRect.right, line + row->Height());
return true;
}
line += iterator.CurrentRow()->Height() + 1;
}
}
return false;
}
bool OutlineView::FindRect(const BRow *row, BRect *out_rect)
{
float line = 0.0;
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
iterator.GoToNext()) {
if (iterator.CurrentRow() == row) {
out_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( true == FindRect(Row, &rect) )
{
ScrollTo(BPoint(rect.left, rect.top));
}
}
void OutlineView::DeselectAll()
{
// Invalidate all selected rows
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;
}
// Set items not selected
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); // invalidate previous
fTargetRow = fFocusRow = Row;
FindVisibleRect(fFocusRow, &fFocusRowRect);
Invalidate(fFocusRowRect); // invalidate current
fFocusRowRect.right = 10000;
fMasterView->SelectionChanged();
}
}
bool OutlineView::SortList(BRowContainer *list, bool isVisible)
{
if (list) {
// Shellsort
BRow **items = (BRow**) 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; // Window is gone...
}
}
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];
// xxx Can make the invalidate area smaller by finding the rect for the
// parent item and using that as the top of the invalid rect.
bool haveLock = SortList(currentEntry->list, currentEntry->isVisible);
if (!haveLock)
return ; // window is gone.
// Fix focus rect.
InvalidateCachedPositions();
if (fCurrentState != INACTIVE)
fCurrentState = INACTIVE; // sorry...
// next list.
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) {
// back up
if (--stackTop < 0) {
doneSorting = true;
break;
}
currentEntry = &stack[stackTop];
}
}
}
Window()->Unlock();
}
void OutlineView::StartSorting()
{
// If this view is not yet attached to a window, don't start a sort thread!
if (Window() == NULL)
return;
if (fSortThread != B_BAD_THREAD_ID) {
thread_info tinfo;
if (get_thread_info(fSortThread, &tinfo) == B_OK) {
// Unlock window so this won't deadlock (sort thread is probably
// waiting to lock window).
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 ; // Window is gone...
}
}
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) // start is always selected when this is called
return;
RecursiveOutlineIterator iterator(&fRows, false);
while (iterator.CurrentRow() != 0) {
if (iterator.CurrentRow() == end) {
// reverse selection, swap to fix special case
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(); // xxx make invalidation smaller
}
bool OutlineView::FindParent(BRow *row, BRow **outParent, bool *out_parentIsVisible)
{
bool result = false;
if (row && outParent) {
*outParent = row->fParent;
// Walk up the parent chain to determine if this row is visible
bool isVisible = true;
for (BRow *currentRow = row->fParent; currentRow; currentRow = currentRow->fParent) {
if (!currentRow->fIsExpanded) {
isVisible = false;
break;
}
}
if (out_parentIsVisible) *out_parentIsVisible = isVisible;
result = (NULL != *outParent);
}
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);
}
// #pragma mark -
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) {
// Visit child.
// Put current list on the stack if it needs to be revisited.
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++; // next item in current list
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;
}