4888 lines
116 KiB
C++
4888 lines
116 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 "ColumnListView.h"
|
|
|
|
#include <typeinfo>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <Application.h>
|
|
#include <Bitmap.h>
|
|
#include <ControlLook.h>
|
|
#include <Cursor.h>
|
|
#include <Debug.h>
|
|
#include <GraphicsDefs.h>
|
|
#include <LayoutUtils.h>
|
|
#include <MenuItem.h>
|
|
#include <PopUpMenu.h>
|
|
#include <Region.h>
|
|
#include <ScrollBar.h>
|
|
#include <String.h>
|
|
#include <Window.h>
|
|
|
|
#include "ColorTools.h"
|
|
#include "ObjectList.h"
|
|
|
|
#define DOUBLE_BUFFERED_COLUMN_RESIZE 1
|
|
#define SMART_REDRAW 1
|
|
#define DRAG_TITLE_OUTLINE 1
|
|
#define CONSTRAIN_CLIPPING_REGION 1
|
|
#define LOWER_SCROLLBAR 0
|
|
|
|
namespace BPrivate {
|
|
|
|
static const unsigned char kResizeCursorData[] = {
|
|
16, 1, 8, 8,
|
|
0x03, 0xc0, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
|
|
0x1a, 0x58, 0x2a, 0x54, 0x4a, 0x52, 0x8a, 0x51,
|
|
0x8a, 0x51, 0x4a, 0x52, 0x2a, 0x54, 0x1a, 0x58,
|
|
0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x03, 0xc0,
|
|
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0,
|
|
0x1b, 0xd8, 0x3b, 0xdc, 0x7b, 0xde, 0xfb, 0xdf,
|
|
0xfb, 0xdf, 0x7b, 0xde, 0x3b, 0xdc, 0x1b, 0xd8,
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0
|
|
};
|
|
|
|
static const unsigned char kMaxResizeCursorData[] = {
|
|
16, 1, 8, 8,
|
|
0x03, 0xc0, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
|
|
0x1a, 0x40, 0x2a, 0x40, 0x4a, 0x40, 0x8a, 0x40,
|
|
0x8a, 0x40, 0x4a, 0x40, 0x2a, 0x40, 0x1a, 0x40,
|
|
0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x03, 0xc0,
|
|
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0,
|
|
0x1b, 0xc0, 0x3b, 0xc0, 0x7b, 0xc0, 0xfb, 0xc0,
|
|
0xfb, 0xc0, 0x7b, 0xc0, 0x3b, 0xc0, 0x1b, 0xc0,
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0
|
|
};
|
|
|
|
static const unsigned char kMinResizeCursorData[] = {
|
|
16, 1, 8, 8,
|
|
0x03, 0xc0, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
|
|
0x02, 0x58, 0x02, 0x54, 0x02, 0x52, 0x02, 0x51,
|
|
0x02, 0x51, 0x02, 0x52, 0x02, 0x54, 0x02, 0x58,
|
|
0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x03, 0xc0,
|
|
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0,
|
|
0x03, 0xd8, 0x03, 0xdc, 0x03, 0xde, 0x03, 0xdf,
|
|
0x03, 0xdf, 0x03, 0xde, 0x03, 0xdc, 0x03, 0xd8,
|
|
0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0, 0x03, 0xc0
|
|
};
|
|
|
|
static const unsigned char kColumnMoveCursorData[] = {
|
|
16, 1, 8, 8,
|
|
0x01, 0x80, 0x02, 0x40, 0x04, 0x20, 0x08, 0x10,
|
|
0x1e, 0x78, 0x2a, 0x54, 0x4e, 0x72, 0x80, 0x01,
|
|
0x80, 0x01, 0x4e, 0x72, 0x2a, 0x54, 0x1e, 0x78,
|
|
0x08, 0x10, 0x04, 0x20, 0x02, 0x40, 0x01, 0x80,
|
|
|
|
0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0,
|
|
0x1f, 0xf8, 0x3b, 0xdc, 0x7f, 0xfe, 0xff, 0xff,
|
|
0xff, 0xff, 0x7f, 0xfe, 0x3b, 0xdc, 0x1f, 0xf8,
|
|
0x0f, 0xf0, 0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80
|
|
};
|
|
|
|
static const unsigned char kDownSortArrow8x8[] = {
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
|
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff
|
|
};
|
|
|
|
static const unsigned char kUpSortArrow8x8[] = {
|
|
0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff
|
|
};
|
|
|
|
static const unsigned char kDownSortArrow8x8Invert[] = {
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff
|
|
};
|
|
|
|
static const unsigned char kUpSortArrow8x8Invert[] = {
|
|
0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
|
};
|
|
|
|
static const float kTintedLineTint = 0.7 * B_NO_TINT + 0.3 * B_DARKEN_1_TINT;
|
|
|
|
static const float kTitleHeight = 16.0;
|
|
static const float kLatchWidth = 0.0; //5.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
|
|
{190, 190, 190, 255}, // B_COLOR_SELECTION
|
|
{ 0, 0, 0, 255}, // B_COLOR_SELECTION_TEXT
|
|
{200, 200, 200, 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 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 {
|
|
typedef BView _inherited;
|
|
public:
|
|
TitleView(BRect frame, OutlineView* outlineView,
|
|
BList* visibleColumns, BList* sortColumns,
|
|
BColumnListView* masterView,
|
|
uint32 resizingMode);
|
|
virtual ~TitleView();
|
|
|
|
void ColumnAdded(BColumn* column);
|
|
void ColumnResized(BColumn* column, float oldWidth);
|
|
void SetColumnVisible(BColumn* column, bool visible);
|
|
|
|
virtual void Draw(BRect updateRect);
|
|
virtual void ScrollTo(BPoint where);
|
|
virtual void MessageReceived(BMessage* message);
|
|
virtual void MouseDown(BPoint where);
|
|
virtual void MouseMoved(BPoint where, uint32 transit,
|
|
const BMessage* dragMessage);
|
|
virtual void MouseUp(BPoint where);
|
|
virtual void FrameResized(float width, float height);
|
|
|
|
void MoveColumn(BColumn* column, int32 index);
|
|
void SetColumnFlags(column_flags flags);
|
|
|
|
void SetEditMode(bool state)
|
|
{ fEditMode = state; }
|
|
|
|
private:
|
|
void GetTitleRect(BColumn* column, BRect* _rect);
|
|
int32 FindColumn(BPoint where, float* _leftEdge);
|
|
void FixScrollBar(bool scrollToFit);
|
|
void DragSelectedColumn(BPoint where);
|
|
void ResizeSelectedColumn(BPoint where,
|
|
bool preferred = false);
|
|
void ComputeDragBoundries(BColumn* column,
|
|
BPoint where);
|
|
void DrawTitle(BView* view, BRect frame,
|
|
BColumn* column, bool depressed);
|
|
|
|
float _VirtualWidth() const;
|
|
|
|
OutlineView* fOutlineView;
|
|
BList* fColumns;
|
|
BList* fSortColumns;
|
|
// 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;
|
|
|
|
BCursor* fResizeCursor;
|
|
BCursor* fMinResizeCursor;
|
|
BCursor* fMaxResizeCursor;
|
|
BCursor* fColumnMoveCursor;
|
|
};
|
|
|
|
class OutlineView : public BView {
|
|
typedef BView _inherited;
|
|
public:
|
|
OutlineView(BRect, BList* visibleColumns,
|
|
BList* sortColumns,
|
|
BColumnListView* listView);
|
|
virtual ~OutlineView();
|
|
|
|
virtual void Draw(BRect);
|
|
const BRect& VisibleRect() const;
|
|
|
|
void RedrawColumn(BColumn* column, float leftEdge,
|
|
bool isFirstColumn);
|
|
void StartSorting();
|
|
float GetColumnPreferredWidth(BColumn* column);
|
|
|
|
void 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** _parent,
|
|
bool* _isVisible);
|
|
int32 IndexOf(BRow* row);
|
|
void Deselect(BRow*);
|
|
void AddToSelection(BRow*);
|
|
void DeselectAll();
|
|
BRow* FocusRow() const;
|
|
void SetFocusRow(BRow* row, bool select);
|
|
BRow* FindRow(float ypos, int32* _indent,
|
|
float* _top);
|
|
bool FindRect(const BRow* row, BRect* _rect);
|
|
void ScrollTo(const BRow* row);
|
|
|
|
void Clear();
|
|
void SetSelectionMode(list_view_type type);
|
|
list_view_type SelectionMode() const;
|
|
void SetMouseTrackingEnabled(bool);
|
|
void FixScrollBar(bool scrollToFit);
|
|
void SetEditMode(bool state)
|
|
{ fEditMode = state; }
|
|
|
|
virtual void FrameResized(float width, float height);
|
|
virtual void ScrollTo(BPoint where);
|
|
virtual void MouseDown(BPoint where);
|
|
virtual void MouseMoved(BPoint where, uint32 transit,
|
|
const BMessage* dragMessage);
|
|
virtual void MouseUp(BPoint where);
|
|
virtual void MessageReceived(BMessage* message);
|
|
|
|
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* _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;
|
|
};
|
|
|
|
class RecursiveOutlineIterator {
|
|
public:
|
|
RecursiveOutlineIterator(
|
|
BRowContainer* container,
|
|
bool openBranchesOnly = true);
|
|
|
|
BRow* CurrentRow() const;
|
|
int32 CurrentLevel() const;
|
|
void GoToNext();
|
|
|
|
private:
|
|
struct {
|
|
BRowContainer* fRowSet;
|
|
int32 fIndex;
|
|
int32 fDepth;
|
|
} fStack[kMaxDepth];
|
|
|
|
int32 fStackIndex;
|
|
BRowContainer* fCurrentList;
|
|
int32 fCurrentListIndex;
|
|
int32 fCurrentListDepth;
|
|
bool fOpenBranchesOnly;
|
|
};
|
|
|
|
} // 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((int32)0);
|
|
if (field == 0)
|
|
break;
|
|
|
|
delete field;
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
BRow::HasLatch() const
|
|
{
|
|
return fChildList != 0;
|
|
}
|
|
|
|
|
|
int32
|
|
BRow::CountFields() const
|
|
{
|
|
return fFields.CountItems();
|
|
}
|
|
|
|
|
|
BField*
|
|
BRow::GetField(int32 index)
|
|
{
|
|
return (BField*)fFields.ItemAt(index);
|
|
}
|
|
|
|
|
|
const BField*
|
|
BRow::GetField(int32 index) const
|
|
{
|
|
return (const BField*)fFields.ItemAt(index);
|
|
}
|
|
|
|
|
|
void
|
|
BRow::SetField(BField* field, int32 logicalFieldIndex)
|
|
{
|
|
if (fFields.ItemAt(logicalFieldIndex) != 0)
|
|
delete (BField*)fFields.RemoveItem(logicalFieldIndex);
|
|
|
|
if (NULL != fList) {
|
|
ValidateField(field, logicalFieldIndex);
|
|
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 (!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)";
|
|
}
|
|
|
|
|
|
float
|
|
BColumn::GetPreferredWidth(BField* field, BView* parent) const
|
|
{
|
|
return fWidth;
|
|
}
|
|
|
|
|
|
bool
|
|
BColumn::IsVisible() const
|
|
{
|
|
return fVisible;
|
|
}
|
|
|
|
|
|
void
|
|
BColumn::SetVisible(bool visible)
|
|
{
|
|
if (fList && (fVisible != visible))
|
|
fList->SetColumnVisible(this, visible);
|
|
}
|
|
|
|
|
|
bool
|
|
BColumn::ShowHeading() const
|
|
{
|
|
return fShowHeading;
|
|
}
|
|
|
|
|
|
void
|
|
BColumn::SetShowHeading(bool state)
|
|
{
|
|
fShowHeading = state;
|
|
}
|
|
|
|
|
|
alignment
|
|
BColumn::Alignment() const
|
|
{
|
|
return fAlignment;
|
|
}
|
|
|
|
|
|
void
|
|
BColumn::SetAlignment(alignment align)
|
|
{
|
|
fAlignment = align;
|
|
}
|
|
|
|
|
|
bool
|
|
BColumn::WantsEvents() const
|
|
{
|
|
return fWantsEvents;
|
|
}
|
|
|
|
|
|
void
|
|
BColumn::SetWantsEvents(bool state)
|
|
{
|
|
fWantsEvents = state;
|
|
}
|
|
|
|
|
|
int32
|
|
BColumn::LogicalFieldNum() const
|
|
{
|
|
return fFieldID;
|
|
}
|
|
|
|
|
|
bool
|
|
BColumn::AcceptsField(const BField*) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
BColumnListView::BColumnListView(BRect rect, const char* name,
|
|
uint32 resizingMode, uint32 flags, border_style border,
|
|
bool showHorizontalScrollbar)
|
|
:
|
|
BView(rect, name, resizingMode,
|
|
flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
|
|
fStatusView(NULL),
|
|
fSelectionMessage(NULL),
|
|
fSortingEnabled(true),
|
|
fLatchWidth(kLatchWidth),
|
|
fBorderStyle(border)
|
|
{
|
|
_Init(showHorizontalScrollbar);
|
|
}
|
|
|
|
|
|
BColumnListView::BColumnListView(const char* name, uint32 flags,
|
|
border_style border, bool showHorizontalScrollbar)
|
|
:
|
|
BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE),
|
|
fStatusView(NULL),
|
|
fSelectionMessage(NULL),
|
|
fSortingEnabled(true),
|
|
fLatchWidth(kLatchWidth),
|
|
fBorderStyle(border)
|
|
{
|
|
_Init(showHorizontalScrollbar);
|
|
}
|
|
|
|
|
|
BColumnListView::~BColumnListView()
|
|
{
|
|
while (BColumn* column = (BColumn*)fColumns.RemoveItem((int32)0))
|
|
delete column;
|
|
}
|
|
|
|
|
|
bool
|
|
BColumnListView::InitiateDrag(BPoint, bool)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::MessageDropped(BMessage*, BPoint)
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::ExpandOrCollapse(BRow* row, bool Open)
|
|
{
|
|
fOutlineView->ExpandOrCollapse(row, Open);
|
|
}
|
|
|
|
|
|
status_t
|
|
BColumnListView::Invoke(BMessage* message)
|
|
{
|
|
if (message == 0)
|
|
message = Message();
|
|
|
|
return BInvoker::Invoke(message);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::ItemInvoked()
|
|
{
|
|
Invoke();
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetInvocationMessage(BMessage* message)
|
|
{
|
|
SetMessage(message);
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BColumnListView::InvocationMessage() const
|
|
{
|
|
return Message();
|
|
}
|
|
|
|
|
|
uint32
|
|
BColumnListView::InvocationCommand() const
|
|
{
|
|
return Command();
|
|
}
|
|
|
|
|
|
BRow*
|
|
BColumnListView::FocusRow() const
|
|
{
|
|
return fOutlineView->FocusRow();
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetFocusRow(int32 Index, bool Select)
|
|
{
|
|
SetFocusRow(RowAt(Index), Select);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetFocusRow(BRow* row, bool Select)
|
|
{
|
|
fOutlineView->SetFocusRow(row, Select);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetMouseTrackingEnabled(bool Enabled)
|
|
{
|
|
fOutlineView->SetMouseTrackingEnabled(Enabled);
|
|
}
|
|
|
|
|
|
list_view_type
|
|
BColumnListView::SelectionMode() const
|
|
{
|
|
return fOutlineView->SelectionMode();
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::Deselect(BRow* row)
|
|
{
|
|
fOutlineView->Deselect(row);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::AddToSelection(BRow* row)
|
|
{
|
|
fOutlineView->AddToSelection(row);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::DeselectAll()
|
|
{
|
|
fOutlineView->DeselectAll();
|
|
}
|
|
|
|
|
|
BRow*
|
|
BColumnListView::CurrentSelection(BRow* lastSelected) const
|
|
{
|
|
return fOutlineView->CurrentSelection(lastSelected);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SelectionChanged()
|
|
{
|
|
if (fSelectionMessage)
|
|
Invoke(fSelectionMessage);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetSelectionMessage(BMessage* message)
|
|
{
|
|
if (fSelectionMessage == message)
|
|
return;
|
|
|
|
delete fSelectionMessage;
|
|
fSelectionMessage = message;
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BColumnListView::SelectionMessage()
|
|
{
|
|
return fSelectionMessage;
|
|
}
|
|
|
|
|
|
uint32
|
|
BColumnListView::SelectionCommand() const
|
|
{
|
|
if (fSelectionMessage)
|
|
return fSelectionMessage->what;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetSelectionMode(list_view_type mode)
|
|
{
|
|
fOutlineView->SetSelectionMode(mode);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetSortingEnabled(bool enabled)
|
|
{
|
|
fSortingEnabled = enabled;
|
|
fSortColumns.MakeEmpty();
|
|
fTitleView->Invalidate(); // 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 != NULL);
|
|
|
|
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 != NULL);
|
|
fTitleView->MoveColumn(column, index);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::RemoveColumn(BColumn* column)
|
|
{
|
|
if (fColumns.HasItem(column)) {
|
|
SetColumnVisible(column, false);
|
|
if (Window() != NULL)
|
|
Window()->UpdateIfNeeded();
|
|
fColumns.RemoveItem(column);
|
|
}
|
|
}
|
|
|
|
|
|
int32
|
|
BColumnListView::CountColumns() const
|
|
{
|
|
return fColumns.CountItems();
|
|
}
|
|
|
|
|
|
BColumn*
|
|
BColumnListView::ColumnAt(int32 field) const
|
|
{
|
|
return (BColumn*) fColumns.ItemAt(field);
|
|
}
|
|
|
|
|
|
BColumn*
|
|
BColumnListView::ColumnAt(BPoint point) const
|
|
{
|
|
float left = MAX(kLeftMargin, LatchWidth());
|
|
|
|
for (int i = 0; BColumn* column = (BColumn*)fColumns.ItemAt(i); i++) {
|
|
if (!column->IsVisible())
|
|
continue;
|
|
|
|
float right = left + column->Width();
|
|
if (point.x >= left && point.x <= right)
|
|
return column;
|
|
|
|
left = right + 1;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetColumnVisible(BColumn* column, bool visible)
|
|
{
|
|
fTitleView->SetColumnVisible(column, visible);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetColumnVisible(int32 index, bool isVisible)
|
|
{
|
|
BColumn* column = ColumnAt(index);
|
|
if (column)
|
|
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);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::ResizeColumnToPreferred(int32 index)
|
|
{
|
|
BColumn* column = ColumnAt(index);
|
|
if (column == NULL)
|
|
return;
|
|
|
|
// get the preferred column width
|
|
float width = fOutlineView->GetColumnPreferredWidth(column);
|
|
|
|
// set it
|
|
float oldWidth = column->Width();
|
|
column->SetWidth(width);
|
|
|
|
fTitleView->ColumnResized(column, oldWidth);
|
|
fOutlineView->Invalidate();
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::ResizeAllColumnsToPreferred()
|
|
{
|
|
int32 count = CountColumns();
|
|
for (int32 i = 0; i < count; i++)
|
|
ResizeColumnToPreferred(i);
|
|
}
|
|
|
|
|
|
const BRow*
|
|
BColumnListView::RowAt(int32 Index, BRow* parentRow) const
|
|
{
|
|
if (parentRow == 0)
|
|
return fOutlineView->RowList()->ItemAt(Index);
|
|
|
|
return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : NULL;
|
|
}
|
|
|
|
|
|
BRow*
|
|
BColumnListView::RowAt(int32 Index, BRow* parentRow)
|
|
{
|
|
if (parentRow == 0)
|
|
return fOutlineView->RowList()->ItemAt(Index);
|
|
|
|
return parentRow->fChildList ? parentRow->fChildList->ItemAt(Index) : 0;
|
|
}
|
|
|
|
|
|
const BRow*
|
|
BColumnListView::RowAt(BPoint point) const
|
|
{
|
|
float top;
|
|
int32 indent;
|
|
return fOutlineView->FindRow(point.y, &indent, &top);
|
|
}
|
|
|
|
|
|
BRow*
|
|
BColumnListView::RowAt(BPoint point)
|
|
{
|
|
float top;
|
|
int32 indent;
|
|
return fOutlineView->FindRow(point.y, &indent, &top);
|
|
}
|
|
|
|
|
|
bool
|
|
BColumnListView::GetRowRect(const BRow* row, BRect* outRect) const
|
|
{
|
|
return fOutlineView->FindRect(row, outRect);
|
|
}
|
|
|
|
|
|
bool
|
|
BColumnListView::FindParent(BRow* row, BRow** _parent, bool* _isVisible) const
|
|
{
|
|
return fOutlineView->FindParent(row, _parent, _isVisible);
|
|
}
|
|
|
|
|
|
int32
|
|
BColumnListView::IndexOf(BRow* row)
|
|
{
|
|
return fOutlineView->IndexOf(row);
|
|
}
|
|
|
|
|
|
int32
|
|
BColumnListView::CountRows(BRow* parentRow) const
|
|
{
|
|
if (parentRow == 0)
|
|
return fOutlineView->RowList()->CountItems();
|
|
if (parentRow->fChildList)
|
|
return parentRow->fChildList->CountItems();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::AddRow(BRow* row, BRow* parentRow)
|
|
{
|
|
AddRow(row, -1, parentRow);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::AddRow(BRow* row, int32 index, BRow* parentRow)
|
|
{
|
|
row->fChildList = 0;
|
|
row->fList = this;
|
|
row->ValidateFields();
|
|
fOutlineView->AddRow(row, index, parentRow);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::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::ScrollTo(BPoint point)
|
|
{
|
|
fOutlineView->ScrollTo(point);
|
|
}
|
|
|
|
|
|
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 ((itemRect.IntegerWidth() % 2) == 1) {
|
|
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)
|
|
{
|
|
if (fBorderStyle != B_NO_BORDER) {
|
|
// Redraw focus marks around view
|
|
Invalidate();
|
|
fHorizontalScrollBar->SetBorderHighlighted(isFocus);
|
|
fVerticalScrollBar->SetBorderHighlighted(isFocus);
|
|
}
|
|
|
|
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 updateRect)
|
|
{
|
|
BRect rect = Bounds();
|
|
|
|
if (be_control_look != NULL) {
|
|
uint32 flags = 0;
|
|
if (IsFocus() && Window()->IsActive())
|
|
flags |= BControlLook::B_FOCUSED;
|
|
|
|
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
|
|
|
|
BRect verticalScrollBarFrame;
|
|
if (!fVerticalScrollBar->IsHidden())
|
|
verticalScrollBarFrame = fVerticalScrollBar->Frame();
|
|
BRect horizontalScrollBarFrame;
|
|
if (!fHorizontalScrollBar->IsHidden())
|
|
horizontalScrollBarFrame = fHorizontalScrollBar->Frame();
|
|
|
|
if (fBorderStyle == B_NO_BORDER) {
|
|
// We still draw the left/top border, but not focused.
|
|
// The scrollbars cannot be displayed without frame and
|
|
// it looks bad to have no frame only along the left/top
|
|
// side.
|
|
rgb_color borderColor = tint_color(base, B_DARKEN_2_TINT);
|
|
SetHighColor(borderColor);
|
|
StrokeLine(BPoint(rect.left, rect.bottom),
|
|
BPoint(rect.left, rect.top));
|
|
StrokeLine(BPoint(rect.left + 1, rect.top),
|
|
BPoint(rect.right, rect.top));
|
|
}
|
|
|
|
be_control_look->DrawScrollViewFrame(this, rect, updateRect,
|
|
verticalScrollBarFrame, horizontalScrollBarFrame,
|
|
base, fBorderStyle, flags);
|
|
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
// TODO: Need to find focus color programatically
|
|
BView::SetHighColor(0, 0, 190);
|
|
} 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(ui_color(B_PANEL_BACKGROUND_COLOR));
|
|
// fills lower right rect between scroll bars
|
|
FillRect(cornerRect);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SaveState(BMessage* msg)
|
|
{
|
|
msg->MakeEmpty();
|
|
|
|
for (int32 i = 0; BColumn* col = (BColumn*)fColumns.ItemAt(i); i++) {
|
|
msg->AddInt32("ID",col->fFieldID);
|
|
msg->AddFloat("width", col->fWidth);
|
|
msg->AddBool("visible", col->fVisible);
|
|
}
|
|
|
|
msg->AddBool("sortingenabled", fSortingEnabled);
|
|
|
|
if (fSortingEnabled) {
|
|
for (int32 i = 0; BColumn* col = (BColumn*)fSortColumns.ItemAt(i);
|
|
i++) {
|
|
msg->AddInt32("sortID", col->fFieldID);
|
|
msg->AddBool("sortascending", col->fSortAscending);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::LoadState(BMessage* msg)
|
|
{
|
|
int32 id;
|
|
for (int i = 0; msg->FindInt32("ID", i, &id) == B_OK; i++) {
|
|
for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j); j++) {
|
|
if (column->fFieldID == id) {
|
|
// move this column to position 'i' and set its attributes
|
|
MoveColumn(column, i);
|
|
float width;
|
|
if (msg->FindFloat("width", i, &width) == B_OK)
|
|
column->SetWidth(width);
|
|
bool visible;
|
|
if (msg->FindBool("visible", i, &visible) == B_OK)
|
|
column->SetVisible(visible);
|
|
}
|
|
}
|
|
}
|
|
bool b;
|
|
if (msg->FindBool("sortingenabled", &b) == B_OK) {
|
|
SetSortingEnabled(b);
|
|
for (int k = 0; msg->FindInt32("sortID", k, &id) == B_OK; k++) {
|
|
for (int j = 0; BColumn* column = (BColumn*)fColumns.ItemAt(j);
|
|
j++) {
|
|
if (column->fFieldID == id) {
|
|
// add this column to the sort list
|
|
bool value;
|
|
if (msg->FindBool("sortascending", k, &value) == B_OK)
|
|
SetSortColumn(column, true, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::SetEditMode(bool state)
|
|
{
|
|
fOutlineView->SetEditMode(state);
|
|
fTitleView->SetEditMode(state);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::Refresh()
|
|
{
|
|
if (LockLooper()) {
|
|
Invalidate();
|
|
fOutlineView->FixScrollBar (true);
|
|
fOutlineView->Invalidate();
|
|
Window()->UpdateIfNeeded();
|
|
UnlockLooper();
|
|
}
|
|
}
|
|
|
|
|
|
BSize
|
|
BColumnListView::MinSize()
|
|
{
|
|
BSize size;
|
|
size.width = 100;
|
|
size.height = kTitleHeight + 4 * B_H_SCROLL_BAR_HEIGHT;
|
|
if (!fHorizontalScrollBar->IsHidden())
|
|
size.height += fHorizontalScrollBar->Frame().Height() + 1;
|
|
// TODO: Take border size into account
|
|
|
|
return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
|
|
}
|
|
|
|
|
|
BSize
|
|
BColumnListView::PreferredSize()
|
|
{
|
|
BSize size = MinSize();
|
|
size.height += ceilf(be_plain_font->Size()) * 20;
|
|
|
|
int32 count = CountColumns();
|
|
if (count > 0) {
|
|
// return MinSize().width if there are no columns.
|
|
size.width = 40.0f;
|
|
for (int32 i = 0; i < count; i++) {
|
|
BColumn* column = ColumnAt(i);
|
|
if (column != NULL)
|
|
size.width += fOutlineView->GetColumnPreferredWidth(column);
|
|
}
|
|
}
|
|
|
|
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
|
|
}
|
|
|
|
|
|
BSize
|
|
BColumnListView::MaxSize()
|
|
{
|
|
BSize size(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
|
|
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::InvalidateLayout(bool descendants)
|
|
{
|
|
BView::InvalidateLayout(descendants);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::DoLayout()
|
|
{
|
|
if (!(Flags() & B_SUPPORTS_LAYOUT))
|
|
return;
|
|
|
|
BRect titleRect;
|
|
BRect outlineRect;
|
|
BRect vScrollBarRect;
|
|
BRect hScrollBarRect;
|
|
_GetChildViewRects(Bounds(), !fHorizontalScrollBar->IsHidden(),
|
|
titleRect, outlineRect, vScrollBarRect, hScrollBarRect);
|
|
|
|
fTitleView->MoveTo(titleRect.LeftTop());
|
|
fTitleView->ResizeTo(titleRect.Width(), titleRect.Height());
|
|
|
|
fOutlineView->MoveTo(outlineRect.LeftTop());
|
|
fOutlineView->ResizeTo(outlineRect.Width(), outlineRect.Height());
|
|
|
|
fVerticalScrollBar->MoveTo(vScrollBarRect.LeftTop());
|
|
fVerticalScrollBar->ResizeTo(vScrollBarRect.Width(),
|
|
vScrollBarRect.Height());
|
|
|
|
fHorizontalScrollBar->MoveTo(hScrollBarRect.LeftTop());
|
|
fHorizontalScrollBar->ResizeTo(hScrollBarRect.Width(),
|
|
hScrollBarRect.Height());
|
|
|
|
fOutlineView->FixScrollBar(true);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::_Init(bool showHorizontalScrollbar)
|
|
{
|
|
SetViewColor(B_TRANSPARENT_32_BIT);
|
|
|
|
BRect bounds(Bounds());
|
|
if (bounds.Width() <= 0)
|
|
bounds.right = 100;
|
|
if (bounds.Height() <= 0)
|
|
bounds.bottom = 100;
|
|
|
|
for (int i = 0; i < (int)B_COLOR_TOTAL; i++)
|
|
fColorList[i] = kColor[i];
|
|
|
|
BRect titleRect;
|
|
BRect outlineRect;
|
|
BRect vScrollBarRect;
|
|
BRect hScrollBarRect;
|
|
_GetChildViewRects(bounds, showHorizontalScrollbar, titleRect, outlineRect,
|
|
vScrollBarRect, hScrollBarRect);
|
|
|
|
fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this);
|
|
AddChild(fOutlineView);
|
|
|
|
|
|
fTitleView = new TitleView(titleRect, fOutlineView, &fColumns,
|
|
&fSortColumns, this, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
|
|
AddChild(fTitleView);
|
|
|
|
fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar",
|
|
fOutlineView, 0.0, bounds.Height(), B_VERTICAL);
|
|
AddChild(fVerticalScrollBar);
|
|
|
|
fHorizontalScrollBar = new BScrollBar(hScrollBarRect,
|
|
"horizontal_scroll_bar", fTitleView, 0.0, bounds.Width(), B_HORIZONTAL);
|
|
AddChild(fHorizontalScrollBar);
|
|
|
|
if (!showHorizontalScrollbar)
|
|
fHorizontalScrollBar->Hide();
|
|
|
|
fOutlineView->FixScrollBar(true);
|
|
}
|
|
|
|
|
|
void
|
|
BColumnListView::_GetChildViewRects(const BRect& bounds,
|
|
bool showHorizontalScrollbar, BRect& titleRect, BRect& outlineRect,
|
|
BRect& vScrollBarRect, BRect& hScrollBarRect)
|
|
{
|
|
titleRect = bounds;
|
|
titleRect.bottom = titleRect.top + kTitleHeight;
|
|
#if !LOWER_SCROLLBAR
|
|
titleRect.right -= B_V_SCROLL_BAR_WIDTH;
|
|
#endif
|
|
|
|
outlineRect = bounds;
|
|
outlineRect.top = titleRect.bottom + 1.0;
|
|
outlineRect.right -= B_V_SCROLL_BAR_WIDTH;
|
|
if (showHorizontalScrollbar)
|
|
outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT;
|
|
|
|
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;
|
|
|
|
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 || fBorderStyle == B_NO_BORDER) {
|
|
titleRect.InsetBy(1, 0);
|
|
titleRect.OffsetBy(0, 1);
|
|
outlineRect.InsetBy(1, 1);
|
|
} else if (fBorderStyle == B_FANCY_BORDER) {
|
|
titleRect.InsetBy(2, 0);
|
|
titleRect.OffsetBy(0, 2);
|
|
outlineRect.InsetBy(2, 2);
|
|
|
|
vScrollBarRect.OffsetBy(-1, 0);
|
|
#if LOWER_SCROLLBAR
|
|
vScrollBarRect.top += 2;
|
|
vScrollBarRect.bottom -= 1;
|
|
#else
|
|
vScrollBarRect.InsetBy(0, 1);
|
|
#endif
|
|
hScrollBarRect.OffsetBy(0, -1);
|
|
hScrollBarRect.InsetBy(1, 0);
|
|
}
|
|
}
|
|
|
|
|
|
// #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_COLOR);
|
|
|
|
#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_CMAP8);
|
|
fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_CMAP8);
|
|
|
|
fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_CMAP8);
|
|
fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_CMAP8);
|
|
|
|
fResizeCursor = new BCursor(kResizeCursorData);
|
|
fMinResizeCursor = new BCursor(kMinResizeCursorData);
|
|
fMaxResizeCursor = new BCursor(kMaxResizeCursorData);
|
|
fColumnMoveCursor = new BCursor(kColumnMoveCursorData);
|
|
|
|
FixScrollBar(true);
|
|
}
|
|
|
|
|
|
TitleView::~TitleView()
|
|
{
|
|
delete fColumnPop;
|
|
fColumnPop = NULL;
|
|
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
delete fDrawBuffer;
|
|
#endif
|
|
delete fUpSortArrow;
|
|
delete fDownSortArrow;
|
|
|
|
delete fResizeCursor;
|
|
delete fMaxResizeCursor;
|
|
delete fMinResizeCursor;
|
|
delete fColumnMoveCursor;
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::ColumnAdded(BColumn* column)
|
|
{
|
|
// fColumnsWidth += column->Width();
|
|
FixScrollBar(false);
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::ColumnResized(BColumn* column, float oldWidth)
|
|
{
|
|
// fColumnsWidth += column->Width() - oldWidth;
|
|
FixScrollBar(false);
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
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* _rect)
|
|
{
|
|
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
|
|
int32 numColumns = fColumns->CountItems();
|
|
for (int index = 0; index < numColumns; index++) {
|
|
BColumn* column = (BColumn*) fColumns->ItemAt(index);
|
|
if (!column->IsVisible())
|
|
continue;
|
|
|
|
if (column == findColumn) {
|
|
_rect->Set(leftEdge, 0, leftEdge + column->Width(),
|
|
fVisibleRect.bottom);
|
|
return;
|
|
}
|
|
|
|
leftEdge += column->Width() + 1;
|
|
}
|
|
|
|
TRESPASS();
|
|
}
|
|
|
|
|
|
int32
|
|
TitleView::FindColumn(BPoint position, float* _leftEdge)
|
|
{
|
|
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
|
|
int32 numColumns = fColumns->CountItems();
|
|
for (int index = 0; index < numColumns; index++) {
|
|
BColumn* column = (BColumn*) fColumns->ItemAt(index);
|
|
if (!column->IsVisible())
|
|
continue;
|
|
|
|
if (leftEdge > position.x)
|
|
break;
|
|
|
|
if (position.x >= leftEdge
|
|
&& position.x <= leftEdge + column->Width()) {
|
|
*_leftEdge = leftEdge;
|
|
return index;
|
|
}
|
|
|
|
leftEdge += column->Width() + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::FixScrollBar(bool scrollToFit)
|
|
{
|
|
BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
|
|
if (hScrollBar == NULL)
|
|
return;
|
|
|
|
float virtualWidth = _VirtualWidth();
|
|
|
|
if (virtualWidth > fVisibleRect.Width()) {
|
|
hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth);
|
|
|
|
// 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) {
|
|
// disable scroll bar.
|
|
hScrollBar->SetRange(0.0, 0.0);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::DragSelectedColumn(BPoint position)
|
|
{
|
|
float invalidLeft = fSelectedColumnRect.left;
|
|
float invalidRight = fSelectedColumnRect.right;
|
|
|
|
float leftEdge;
|
|
int32 columnIndex = FindColumn(position, &leftEdge);
|
|
fSelectedColumnRect.OffsetTo(leftEdge, 0);
|
|
|
|
MoveColumn(fSelectedColumn, columnIndex);
|
|
|
|
fSelectedColumn->fVisible = true;
|
|
ComputeDragBoundries(fSelectedColumn, position);
|
|
|
|
// 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, bool preferred)
|
|
{
|
|
float minWidth = fSelectedColumn->MinWidth();
|
|
float maxWidth = fSelectedColumn->MaxWidth();
|
|
|
|
float oldWidth = fSelectedColumn->Width();
|
|
float originalEdge = fSelectedColumnRect.left + oldWidth;
|
|
if (preferred) {
|
|
float width = fOutlineView->GetColumnPreferredWidth(fSelectedColumn);
|
|
fSelectedColumn->SetWidth(width);
|
|
} else if (position.x > fSelectedColumnRect.left + maxWidth)
|
|
fSelectedColumn->SetWidth(maxWidth);
|
|
else if (position.x < fSelectedColumnRect.left + minWidth)
|
|
fSelectedColumn->SetWidth(minWidth);
|
|
else
|
|
fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1);
|
|
|
|
float dX = fSelectedColumnRect.left + fSelectedColumn->Width()
|
|
- originalEdge;
|
|
if (dX != 0) {
|
|
float columnHeight = fVisibleRect.Height();
|
|
BRect originalRect(originalEdge, 0, 1000000.0, columnHeight);
|
|
BRect movedRect(originalRect);
|
|
movedRect.OffsetBy(dX, 0);
|
|
|
|
// Update the size of the title column
|
|
BRect sourceRect(0, 0, fSelectedColumn->Width(), columnHeight);
|
|
BRect destRect(sourceRect);
|
|
destRect.OffsetBy(fSelectedColumnRect.left, 0);
|
|
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
fDrawBuffer->Lock();
|
|
DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false);
|
|
fDrawBufferView->Sync();
|
|
fDrawBuffer->Unlock();
|
|
|
|
CopyBits(originalRect, movedRect);
|
|
DrawBitmap(fDrawBuffer, sourceRect, destRect);
|
|
#else
|
|
CopyBits(originalRect, movedRect);
|
|
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 (fSelectedColumn->Width() == minWidth)
|
|
SetViewCursor(fMinResizeCursor, true);
|
|
else if (fSelectedColumn->Width() == maxWidth)
|
|
SetViewCursor(fMaxResizeCursor, true);
|
|
else
|
|
SetViewCursor(fResizeCursor, true);
|
|
|
|
ColumnResized(fSelectedColumn, oldWidth);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::ComputeDragBoundries(BColumn* findColumn, BPoint)
|
|
{
|
|
float previousColumnLeftEdge = -1000000.0;
|
|
float nextColumnRightEdge = 1000000.0;
|
|
|
|
bool foundColumn = false;
|
|
float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
|
|
int32 numColumns = fColumns->CountItems();
|
|
for (int index = 0; index < numColumns; index++) {
|
|
BColumn* column = (BColumn*) fColumns->ItemAt(index);
|
|
if (!column->IsVisible())
|
|
continue;
|
|
|
|
if (column == findColumn) {
|
|
foundColumn = true;
|
|
continue;
|
|
}
|
|
|
|
if (foundColumn) {
|
|
nextColumnRightEdge = leftEdge + column->Width();
|
|
break;
|
|
} else
|
|
previousColumnLeftEdge = leftEdge;
|
|
|
|
leftEdge += column->Width() + 1;
|
|
}
|
|
|
|
float rightEdge = leftEdge + findColumn->Width();
|
|
|
|
fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(),
|
|
leftEdge);
|
|
fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge);
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::DrawTitle(BView* view, BRect rect, BColumn* column, bool depressed)
|
|
{
|
|
BRect drawRect;
|
|
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.
|
|
if (be_control_look == NULL) {
|
|
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--;
|
|
}
|
|
} else {
|
|
drawRect = rect;
|
|
}
|
|
|
|
font_height fh;
|
|
GetFontHeight(&fh);
|
|
|
|
float baseline = floor(drawRect.top + fh.ascent
|
|
+ (drawRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
|
|
|
|
if (be_control_look != NULL) {
|
|
BRect bgRect = rect;
|
|
|
|
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
|
|
view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
|
|
view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());
|
|
|
|
bgRect.bottom--;
|
|
bgRect.right--;
|
|
|
|
if (depressed)
|
|
base = tint_color(base, B_DARKEN_1_TINT);
|
|
|
|
be_control_look->DrawButtonBackground(view, bgRect, rect, base, 0,
|
|
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
|
|
|
|
view->SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
|
|
B_DARKEN_2_TINT));
|
|
view->StrokeLine(rect.RightTop(), rect.RightBottom());
|
|
|
|
} else {
|
|
|
|
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();
|
|
|
|
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(drawRect);
|
|
view->PushState();
|
|
view->ConstrainClippingRegion(&clipRegion);
|
|
#endif
|
|
view->MovePenTo(BPoint(drawRect.left + 8, baseline));
|
|
view->SetDrawingMode(B_OP_OVER);
|
|
view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT));
|
|
column->DrawTitle(drawRect, view);
|
|
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
view->PopState();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
float
|
|
TitleView::_VirtualWidth() const
|
|
{
|
|
float width = 0.0f;
|
|
|
|
int32 count = fColumns->CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
BColumn* column = reinterpret_cast<BColumn*>(fColumns->ItemAt(i));
|
|
width += column->Width();
|
|
}
|
|
|
|
return width + MAX(kLeftMargin,
|
|
fMasterView->LatchWidth()) + kRightMargin * 2;
|
|
}
|
|
|
|
|
|
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 = _VirtualWidth() - fVisibleRect.Width();
|
|
BScrollBar* hScrollBar = ScrollBar(B_HORIZONTAL);
|
|
float min, max;
|
|
hScrollBar->GetRange(&min, &max);
|
|
if (max != maxScrollBarValue && position.x > maxScrollBarValue)
|
|
FixScrollBar(true);
|
|
|
|
_inherited::ScrollTo(position);
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::MessageReceived(BMessage* message)
|
|
{
|
|
if (message->what == kToggleColumn) {
|
|
int32 num;
|
|
if (message->FindInt32("be:field_num", &num) == B_OK) {
|
|
for (int index = 0; index < fColumns->CountItems(); index++) {
|
|
BColumn* column = (BColumn*) fColumns->ItemAt(index);
|
|
if (!column)
|
|
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)) {
|
|
|
|
int32 clicks = 0;
|
|
Window()->CurrentMessage()->FindInt32("clicks", &clicks);
|
|
if (clicks == 2) {
|
|
ResizeSelectedColumn(position, true);
|
|
fCurrentState = INACTIVE;
|
|
break;
|
|
}
|
|
fCurrentState = RESIZING_COLUMN;
|
|
fSelectedColumn = column;
|
|
fSelectedColumnRect.Set(leftEdge, 0, rightEdge,
|
|
fVisibleRect.Height());
|
|
fClickPoint = BPoint(position.x - rightEdge - 1,
|
|
position.y - fSelectedColumnRect.top);
|
|
SetMouseEventMask(B_POINTER_EVENTS,
|
|
B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY);
|
|
break;
|
|
}
|
|
|
|
fResizingFirstColumn = false;
|
|
|
|
// 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* dragMessage)
|
|
{
|
|
if (fEditMode)
|
|
return;
|
|
|
|
// Handle column manipulation
|
|
switch (fCurrentState) {
|
|
case RESIZING_COLUMN:
|
|
ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
|
|
break;
|
|
|
|
case PRESSING_COLUMN: {
|
|
if (abs((int32)(position.x - (fClickPoint.x
|
|
+ fSelectedColumnRect.left))) > kColumnResizeAreaWidth
|
|
|| abs((int32)(position.y - (fClickPoint.y
|
|
+ fSelectedColumnRect.top))) > kColumnResizeAreaWidth) {
|
|
// 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);
|
|
SetViewCursor(fColumnMoveCursor, true);
|
|
#if DRAG_TITLE_OUTLINE
|
|
BRect invalidRect(fSelectedColumnRect);
|
|
invalidRect.OffsetTo(position.x - fClickPoint.x, 0);
|
|
fCurrentDragPosition = position;
|
|
Invalidate(invalidRect);
|
|
#endif
|
|
}
|
|
} else {
|
|
if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) {
|
|
// 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 - BPoint(fClickPoint.x, 0));
|
|
}
|
|
|
|
#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 - BPoint(fClickPoint.x, 0));
|
|
}
|
|
|
|
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 (resizeColumn) {
|
|
if (resizeColumn->Width() == resizeColumn->MinWidth())
|
|
SetViewCursor(fMinResizeCursor, true);
|
|
else if (resizeColumn->Width() == resizeColumn->MaxWidth())
|
|
SetViewCursor(fMaxResizeCursor, true);
|
|
else
|
|
SetViewCursor(fResizeCursor, true);
|
|
} else
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::MouseUp(BPoint position)
|
|
{
|
|
if (fEditMode)
|
|
return;
|
|
|
|
switch (fCurrentState) {
|
|
case RESIZING_COLUMN:
|
|
ResizeSelectedColumn(position - BPoint(fClickPoint.x, 0));
|
|
fCurrentState = INACTIVE;
|
|
FixScrollBar(false);
|
|
break;
|
|
|
|
case PRESSING_COLUMN: {
|
|
if (fMasterView->SortingEnabled()) {
|
|
if (fSortColumns->HasItem(fSelectedColumn)) {
|
|
if ((modifiers() & B_CONTROL_KEY) == 0
|
|
&& fSortColumns->CountItems() > 1) {
|
|
fSortColumns->MakeEmpty();
|
|
fSortColumns->AddItem(fSelectedColumn);
|
|
}
|
|
|
|
fSelectedColumn->fSortAscending
|
|
= !fSelectedColumn->fSortAscending;
|
|
} else {
|
|
if ((modifiers() & B_CONTROL_KEY) == 0)
|
|
fSortColumns->MakeEmpty();
|
|
|
|
fSortColumns->AddItem(fSelectedColumn);
|
|
fSelectedColumn->fSortAscending = true;
|
|
}
|
|
|
|
fOutlineView->StartSorting();
|
|
}
|
|
|
|
fCurrentState = INACTIVE;
|
|
Invalidate();
|
|
break;
|
|
}
|
|
|
|
case DRAG_COLUMN_INSIDE_TITLE:
|
|
fCurrentState = INACTIVE;
|
|
|
|
#if DRAG_TITLE_OUTLINE
|
|
Invalidate(); // xxx Can make this smaller
|
|
#else
|
|
Invalidate(fSelectedColumnRect);
|
|
#endif
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
|
|
break;
|
|
|
|
case DRAG_COLUMN_OUTSIDE_TITLE:
|
|
fCurrentState = INACTIVE;
|
|
EndRectTracking();
|
|
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
|
|
break;
|
|
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TitleView::FrameResized(float width, float height)
|
|
{
|
|
fVisibleRect.right = fVisibleRect.left + width;
|
|
fVisibleRect.bottom = fVisibleRect.top + height;
|
|
FixScrollBar(true);
|
|
}
|
|
|
|
|
|
// #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(0.0),
|
|
fVisibleRect(rect.OffsetToCopy(0, 0)),
|
|
fFocusRow(0),
|
|
fRollOverRow(0),
|
|
fLastSelectedItem(0),
|
|
fFirstSelectedItem(0),
|
|
fSortThread(B_BAD_THREAD_ID),
|
|
fCurrentState(INACTIVE),
|
|
fMasterView(listView),
|
|
fSelectionMode(B_MULTIPLE_SELECTION_LIST),
|
|
fTrackMouse(false),
|
|
fCurrentField(0),
|
|
fCurrentRow(0),
|
|
fCurrentColumn(0),
|
|
fMouseDown(false),
|
|
fCurrentCode(B_OUTSIDE_VIEW),
|
|
fEditMode(false),
|
|
fDragging(false),
|
|
fClickCount(0),
|
|
fDropHighlightY(-1)
|
|
{
|
|
SetViewColor(B_TRANSPARENT_COLOR);
|
|
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
// TODO: 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()
|
|
{
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
delete fDrawBuffer;
|
|
#endif
|
|
|
|
Clear();
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::Clear()
|
|
{
|
|
DeselectAll();
|
|
// Make sure selection list doesn't point to deleted rows!
|
|
RecursiveDeleteRows(&fRows, false);
|
|
Invalidate();
|
|
fItemsHeight = 0.0;
|
|
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 == NULL)
|
|
return;
|
|
|
|
if (row->fNextSelected != 0) {
|
|
row->fNextSelected->fPrevSelected = row->fPrevSelected;
|
|
row->fPrevSelected->fNextSelected = row->fNextSelected;
|
|
row->fNextSelected = 0;
|
|
row->fPrevSelected = 0;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::AddToSelection(BRow* row)
|
|
{
|
|
if (row == NULL)
|
|
return;
|
|
|
|
if (row->fNextSelected == 0) {
|
|
if (fSelectionMode == B_SINGLE_SELECTION_LIST)
|
|
DeselectAll();
|
|
|
|
row->fNextSelected = fSelectionListDummyHead.fNextSelected;
|
|
row->fPrevSelected = &fSelectionListDummyHead;
|
|
row->fNextSelected->fPrevSelected = row;
|
|
row->fPrevSelected->fNextSelected = row;
|
|
|
|
BRect invalidRect;
|
|
if (FindVisibleRect(row, &invalidRect))
|
|
Invalidate(invalidRect);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::RecursiveDeleteRows(BRowContainer* list, bool isOwner)
|
|
{
|
|
if (list == NULL)
|
|
return;
|
|
|
|
while (true) {
|
|
BRow* row = list->RemoveItemAt(0L);
|
|
if (row == 0)
|
|
break;
|
|
|
|
if (row->fChildList)
|
|
RecursiveDeleteRows(row->fChildList, true);
|
|
|
|
delete row;
|
|
}
|
|
|
|
if (isOwner)
|
|
delete list;
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::RedrawColumn(BColumn* column, float leftEdge, bool isFirstColumn)
|
|
{
|
|
// TODO: Remove code duplication (private function which takes a view
|
|
// pointer, pass "this" in non-double buffered mode)!
|
|
// Watch out for sourceRect versus destRect though!
|
|
if (!column)
|
|
return;
|
|
|
|
font_height fh;
|
|
GetFontHeight(&fh);
|
|
float line = 0.0;
|
|
bool tintedLine = true;
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) {
|
|
|
|
BRow* row = iterator.CurrentRow();
|
|
float rowHeight = row->Height();
|
|
if (line > fVisibleRect.bottom)
|
|
break;
|
|
tintedLine = !tintedLine;
|
|
|
|
if (line + rowHeight >= fVisibleRect.top) {
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
BRect sourceRect(0, 0, column->Width(), rowHeight);
|
|
#endif
|
|
BRect destRect(leftEdge, line, leftEdge + column->Width(),
|
|
line + rowHeight);
|
|
|
|
rgb_color highColor;
|
|
rgb_color lowColor;
|
|
if (row->fNextSelected != 0) {
|
|
if (fEditMode) {
|
|
highColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
|
|
lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
|
|
} else {
|
|
highColor = fMasterView->Color(B_COLOR_SELECTION);
|
|
lowColor = fMasterView->Color(B_COLOR_SELECTION);
|
|
}
|
|
} else {
|
|
highColor = fMasterView->Color(B_COLOR_BACKGROUND);
|
|
lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
|
|
}
|
|
if (tintedLine)
|
|
lowColor = tint_color(lowColor, kTintedLineTint);
|
|
|
|
|
|
#if DOUBLE_BUFFERED_COLUMN_RESIZE
|
|
fDrawBuffer->Lock();
|
|
|
|
fDrawBufferView->SetHighColor(highColor);
|
|
fDrawBufferView->SetLowColor(lowColor);
|
|
|
|
BFont font;
|
|
GetFont(&font);
|
|
fDrawBufferView->SetFont(&font);
|
|
fDrawBufferView->FillRect(sourceRect, B_SOLID_LOW);
|
|
|
|
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(fieldRect);
|
|
fDrawBufferView->PushState();
|
|
fDrawBufferView->ConstrainClippingRegion(&clipRegion);
|
|
#endif
|
|
fDrawBufferView->SetHighColor(fMasterView->Color(
|
|
row->fNextSelected ? B_COLOR_SELECTION_TEXT
|
|
: B_COLOR_TEXT));
|
|
float baseline = floor(fieldRect.top + fh.ascent
|
|
+ (fieldRect.Height() + 1 - (fh.ascent+fh.descent)) / 2);
|
|
fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline);
|
|
column->DrawField(field, fieldRect, fDrawBufferView);
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
fDrawBufferView->PopState();
|
|
#endif
|
|
}
|
|
|
|
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
|
|
&& Window()->IsActive()) {
|
|
fDrawBufferView->SetHighColor(fMasterView->Color(
|
|
B_COLOR_ROW_DIVIDER));
|
|
fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top,
|
|
10000.0, sourceRect.bottom));
|
|
}
|
|
|
|
fDrawBufferView->Sync();
|
|
fDrawBuffer->Unlock();
|
|
SetDrawingMode(B_OP_COPY);
|
|
DrawBitmap(fDrawBuffer, sourceRect, destRect);
|
|
|
|
#else
|
|
|
|
SetHighColor(highColor);
|
|
SetLowColor(lowColor);
|
|
FillRect(destRect, B_SOLID_LOW);
|
|
|
|
BField* field = row->GetField(column->fFieldID);
|
|
if (field) {
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
BRegion clipRegion(destRect);
|
|
PushState();
|
|
ConstrainClippingRegion(&clipRegion);
|
|
#endif
|
|
SetHighColor(fMasterView->Color(row->fNextSelected
|
|
? B_COLOR_SELECTION_TEXT : B_COLOR_TEXT));
|
|
float baseline = floor(destRect.top + fh.ascent
|
|
+ (destRect.Height() + 1 - (fh.ascent + fh.descent)) / 2);
|
|
MovePenTo(destRect.left + 8, baseline);
|
|
column->DrawField(field, destRect, this);
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
PopState();
|
|
#endif
|
|
}
|
|
|
|
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
|
|
&& Window()->IsActive()) {
|
|
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
|
|
StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::Draw(BRect invalidBounds)
|
|
{
|
|
#if SMART_REDRAW
|
|
BRegion invalidRegion;
|
|
GetClippingRegion(&invalidRegion);
|
|
#endif
|
|
|
|
font_height fh;
|
|
GetFontHeight(&fh);
|
|
|
|
float line = 0.0;
|
|
bool tintedLine = true;
|
|
int32 numColumns = fColumns->CountItems();
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
iterator.GoToNext()) {
|
|
BRow* row = iterator.CurrentRow();
|
|
if (line > invalidBounds.bottom)
|
|
break;
|
|
|
|
tintedLine = !tintedLine;
|
|
float rowHeight = row->Height();
|
|
|
|
if (line > invalidBounds.top - rowHeight) {
|
|
bool isFirstColumn = true;
|
|
float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth());
|
|
|
|
// setup background color
|
|
rgb_color lowColor;
|
|
if (row->fNextSelected != 0) {
|
|
if (Window()->IsActive()) {
|
|
if (fEditMode)
|
|
lowColor = fMasterView->Color(B_COLOR_EDIT_BACKGROUND);
|
|
else
|
|
lowColor = fMasterView->Color(B_COLOR_SELECTION);
|
|
}
|
|
else
|
|
lowColor = fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION);
|
|
} else
|
|
lowColor = fMasterView->Color(B_COLOR_BACKGROUND);
|
|
if (tintedLine)
|
|
lowColor = tint_color(lowColor, kTintedLineTint);
|
|
|
|
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.
|
|
|
|
SetHighColor(lowColor);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
SetHighColor(fMasterView->HighColor());
|
|
// The master view just holds the high color for us.
|
|
SetLowColor(lowColor);
|
|
|
|
BField* field = row->GetField(column->fFieldID);
|
|
if (field) {
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
BRegion clipRegion(destRect);
|
|
PushState();
|
|
ConstrainClippingRegion(&clipRegion);
|
|
#endif
|
|
SetHighColor(fMasterView->Color(
|
|
row->fNextSelected ? B_COLOR_SELECTION_TEXT
|
|
: B_COLOR_TEXT));
|
|
float baseline = floor(destRect.top + fh.ascent
|
|
+ (destRect.Height() + 1
|
|
- (fh.ascent+fh.descent)) / 2);
|
|
MovePenTo(destRect.left + 8, baseline);
|
|
column->DrawField(field, destRect, this);
|
|
#if CONSTRAIN_CLIPPING_REGION
|
|
PopState();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
isFirstColumn = false;
|
|
fieldLeftEdge += column->Width() + 1;
|
|
}
|
|
|
|
if (fieldLeftEdge <= invalidBounds.right) {
|
|
SetHighColor(lowColor);
|
|
FillRect(BRect(fieldLeftEdge, line, invalidBounds.right,
|
|
line + rowHeight));
|
|
}
|
|
}
|
|
|
|
// indicate the keyboard focus row
|
|
if (fFocusRow == row && !fEditMode && fMasterView->IsFocus()
|
|
&& Window()->IsActive()) {
|
|
SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER));
|
|
StrokeRect(BRect(0, line, 10000.0, line + rowHeight));
|
|
}
|
|
|
|
line += rowHeight + 1;
|
|
}
|
|
|
|
if (line <= invalidBounds.bottom) {
|
|
// fill background below last item
|
|
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* _rowIndent, float* _top)
|
|
{
|
|
if (_rowIndent && _top) {
|
|
float line = 0.0;
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
iterator.GoToNext()) {
|
|
|
|
BRow* row = iterator.CurrentRow();
|
|
if (line > ypos)
|
|
break;
|
|
|
|
float rowHeight = row->Height();
|
|
if (ypos <= line + rowHeight) {
|
|
*_top = line;
|
|
*_rowIndent = iterator.CurrentLevel();
|
|
return row;
|
|
}
|
|
|
|
line += rowHeight + 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void OutlineView::SetMouseTrackingEnabled(bool enabled)
|
|
{
|
|
fTrackMouse = enabled;
|
|
if (!enabled && fDropHighlightY != -1) {
|
|
// Erase the old target line
|
|
InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2,
|
|
1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
|
|
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 != NULL) {
|
|
|
|
// 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)
|
|
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.
|
|
FindVisibleRect(fFocusRow, &fFocusRowRect);
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::MouseMoved(BPoint position, uint32 /*transit*/,
|
|
const BMessage* /*dragMessage*/)
|
|
{
|
|
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)
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::MouseUp(BPoint position)
|
|
{
|
|
if (fCurrentField) {
|
|
fCurrentColumn->MouseUp(fMasterView, fCurrentRow, fCurrentField);
|
|
fMouseDown = false;
|
|
}
|
|
|
|
if (fEditMode)
|
|
return;
|
|
|
|
switch (fCurrentState) {
|
|
case LATCH_CLICKED:
|
|
if (fLatchRect.Contains(position)) {
|
|
fMasterView->ExpandOrCollapse(fTargetRow,
|
|
!fTargetRow->fIsExpanded);
|
|
}
|
|
|
|
Invalidate(fLatchRect);
|
|
fCurrentState = INACTIVE;
|
|
break;
|
|
|
|
case ROW_CLICKED:
|
|
if (fClickCount > 1
|
|
&& abs((int)fClickPoint.x - (int)position.x)
|
|
< kDoubleClickMoveSensitivity
|
|
&& abs((int)fClickPoint.y - (int)position.y)
|
|
< kDoubleClickMoveSensitivity) {
|
|
fMasterView->ItemInvoked();
|
|
}
|
|
fCurrentState = INACTIVE;
|
|
break;
|
|
|
|
case DRAGGING_ROWS:
|
|
fCurrentState = INACTIVE;
|
|
// Falls through
|
|
|
|
default:
|
|
if (fDropHighlightY != -1) {
|
|
InvertRect(BRect(0,
|
|
fDropHighlightY - kDropHighlightLineHeight / 2,
|
|
1000000, fDropHighlightY + kDropHighlightLineHeight / 2));
|
|
// Erase the old target line
|
|
fDropHighlightY = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::ExpandOrCollapse(BRow* parentRow, bool expand)
|
|
{
|
|
// TODO: Could use CopyBits here to speed things up.
|
|
|
|
if (parentRow == NULL)
|
|
return;
|
|
|
|
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 == NULL)
|
|
return;
|
|
|
|
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)
|
|
return;
|
|
|
|
row->fParent = parentRow;
|
|
|
|
if (fMasterView->SortingEnabled()) {
|
|
// Ignore index here.
|
|
if (parentRow) {
|
|
if (parentRow->fChildList == NULL)
|
|
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 - fVisibleRect.Height();
|
|
vScrollBar->SetProportion(fVisibleRect.Height() / fItemsHeight);
|
|
|
|
// 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 - fVisibleRect.Height();
|
|
float min, max;
|
|
vScrollBar->GetRange(&min, &max);
|
|
if (max != maxScrollBarValue && position.y > maxScrollBarValue)
|
|
FixScrollBar(true);
|
|
|
|
_inherited::ScrollTo(position);
|
|
}
|
|
|
|
|
|
const BRect&
|
|
OutlineView::VisibleRect() const
|
|
{
|
|
return fVisibleRect;
|
|
}
|
|
|
|
|
|
bool
|
|
OutlineView::FindVisibleRect(BRow* row, BRect* _rect)
|
|
{
|
|
if (row && _rect) {
|
|
float line = 0.0;
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
iterator.GoToNext()) {
|
|
if (line > fVisibleRect.bottom)
|
|
break;
|
|
|
|
if (iterator.CurrentRow() == row) {
|
|
_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* _rect)
|
|
{
|
|
float line = 0.0;
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
iterator.GoToNext()) {
|
|
if (iterator.CurrentRow() == row) {
|
|
_rect->Set(fVisibleRect.left, line, fVisibleRect.right,
|
|
line + row->Height());
|
|
return true;
|
|
}
|
|
|
|
line += iterator.CurrentRow()->Height() + 1;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::ScrollTo(const BRow* row)
|
|
{
|
|
BRect rect;
|
|
if (FindRect(row, &rect)) {
|
|
BRect bounds = Bounds();
|
|
if (rect.top < bounds.top)
|
|
ScrollTo(BPoint(bounds.left, rect.top));
|
|
else if (rect.bottom > bounds.bottom)
|
|
ScrollBy(0, rect.bottom - bounds.bottom);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
OutlineView::DeselectAll()
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
|
|
float
|
|
OutlineView::GetColumnPreferredWidth(BColumn* column)
|
|
{
|
|
float preferred = 0.0;
|
|
for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow();
|
|
iterator.GoToNext()) {
|
|
BRow* row = iterator.CurrentRow();
|
|
BField* field = row->GetField(column->fFieldID);
|
|
if (field) {
|
|
float width = column->GetPreferredWidth(field, this);
|
|
if (preferred < width)
|
|
preferred = width;
|
|
}
|
|
}
|
|
// Constrain to preferred width. This makes the method do a little
|
|
// more than asked, but it's for convenience.
|
|
if (preferred < column->MinWidth())
|
|
preferred = column->MinWidth();
|
|
else if (preferred > column->MaxWidth())
|
|
preferred = column->MaxWidth();
|
|
|
|
return preferred;
|
|
}
|
|
|
|
|
|
// #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;
|
|
}
|
|
|
|
|