From 5c99440e0fc0dbde09bbd475a010efb2a600b4aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Thu, 16 Sep 2004 20:42:34 +0000 Subject: [PATCH] Added ColorTools and ColumnListView from Vision cvs repository (ColorTools.h : 1.2, ColorTools.cpp : 1.4, ColumnListView.h : 1.13, ColumnListView.cpp : 1.3) Original code from OpenTracker git-svn-id: file:///srv/svn/repos/haiku/trunk/current@8984 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/interface/ColorTools.h | 103 + headers/private/interface/ColumnListView.h | 366 ++ src/kits/interface/ColorTools.cpp | 110 + src/kits/interface/ColumnListView.cpp | 4176 ++++++++++++++++++++ 4 files changed, 4755 insertions(+) create mode 100644 headers/private/interface/ColorTools.h create mode 100644 headers/private/interface/ColumnListView.h create mode 100644 src/kits/interface/ColorTools.cpp create mode 100644 src/kits/interface/ColumnListView.cpp diff --git a/headers/private/interface/ColorTools.h b/headers/private/interface/ColorTools.h new file mode 100644 index 0000000000..7c91767fe3 --- /dev/null +++ b/headers/private/interface/ColorTools.h @@ -0,0 +1,103 @@ +/* +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: ColorTools.h +/ +/ Description: Additional experimental color manipulation functions. +/ +/ Copyright 2000, Be Incorporated, All Rights Reserved +/ +*******************************************************************************/ + + +#ifndef _COLOR_TOOLS_H +#define _COLOR_TOOLS_H + +#include + +#if B_BEOS_VERSION <= B_BEOS_VERSION_MAUI + +namespace BExperimental { + +// Comparison operators. + +inline bool operator==(const rgb_color c1, const rgb_color c2) +{ + return (*((uint32*)&c1)) == (*((uint32*)&c2)); +} + +inline bool operator!=(const rgb_color c1, const rgb_color c2) +{ + return (*((uint32*)&c1)) != (*((uint32*)&c2)); +} + +// Color creation. + +inline rgb_color make_color(uint8 red, uint8 green, uint8 blue, uint8 alpha=255) +{ + rgb_color c; + c.red = red; + c.green = green; + c.blue = blue; + c.alpha = alpha; + return c; +} + +// Mix two colors together, ignoring their relative alpha channels. +// If amount is 0, the result is color1; if 255, the result is color2; +// if another value, it is somewhere in-between. The resulting alpha +// channel is mixed exactly like the other color channels. +rgb_color mix_color(rgb_color color1, rgb_color color2, uint8 amount); + +// Blend two colors together, weighting by their relative alpha channels. +// The resulting color is the same as mix_color(), except that the amount +// used from color1 and color2's color channels is dependent on that color's +// alpha channel. For example, if color1.alpha is 0 and color2.alpha is +// 255, the resulting red, green, and blue values will be the same as those +// in color2, regardless of 'amount'. +rgb_color blend_color(rgb_color color1, rgb_color color2, uint8 amount); + +// Return a color that is the disabled representation of 'color' when drawn +// on a solid color 'background'. +rgb_color disable_color(rgb_color color, rgb_color background); + +} // namespace BExperimental + +using namespace BExperimental; + +#endif + +#endif diff --git a/headers/private/interface/ColumnListView.h b/headers/private/interface/ColumnListView.h new file mode 100644 index 0000000000..56aaf54542 --- /dev/null +++ b/headers/private/interface/ColumnListView.h @@ -0,0 +1,366 @@ +/* +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.h +/ +/ Description: Experimental multi-column list view. +/ +/ Copyright 2000+, Be Incorporated, All Rights Reserved +/ +*******************************************************************************/ + + +#ifndef _COLUMN_LIST_VIEW_H +#define _COLUMN_LIST_VIEW_H + +#include +#include +#include +#include +#include + +class BScrollBar; + +namespace BPrivate { + +class OutlineView; +class TitleView; +class BRowContainer; +class RecursiveOutlineIterator; + +} // ns BPrivate + +class BField; +class BRow; +class BColumn; +class BColumnListView; + +enum LatchType { + B_NO_LATCH, + B_OPEN_LATCH, + B_PRESSED_LATCH, + B_CLOSED_LATCH +}; + +typedef enum { + B_ALLOW_COLUMN_NONE = 0, + B_ALLOW_COLUMN_MOVE = 1, + B_ALLOW_COLUMN_RESIZE = 2, + B_ALLOW_COLUMN_POPUP = 4, + B_ALLOW_COLUMN_REMOVE = 8, +} column_flags; + +enum ColumnListViewColor { + B_COLOR_BACKGROUND = 0, + B_COLOR_TEXT = 1, + B_COLOR_ROW_DIVIDER = 2, + B_COLOR_SELECTION = 3, + B_COLOR_SELECTION_TEXT = 4, + B_COLOR_NON_FOCUS_SELECTION = 5, + B_COLOR_EDIT_BACKGROUND = 6, + B_COLOR_EDIT_TEXT = 7, + B_COLOR_HEADER_BACKGROUND = 8, + B_COLOR_HEADER_TEXT = 9, + B_COLOR_SEPARATOR_LINE = 10, + B_COLOR_SEPARATOR_BORDER = 11, + + B_COLOR_TOTAL = 12 +}; + +enum ColumnListViewFont { + B_FONT_ROW = 0, + B_FONT_HEADER = 1, + + B_FONT_TOTAL = 2 +}; + + +// A single row/column intersection in the list. +class BField { +public: + BField(); + virtual ~BField(); +}; + +// A single line in the list. Each line contains a BField object +// for each column in the list, associated by their "logical field" +// index. Hierarchies are formed by adding other BRow objects as +// a parent of a row, using the AddRow() function in BColumnListView(). +class BRow { +public: + BRow(float height = 16.0); + virtual ~BRow(); + virtual bool HasLatch() const; + + int32 CountFields() const; + BField* GetField(int32 logicalFieldIndex); + const BField* GetField(int32 logicalFieldIndex) const; + void SetField(BField* field, int32 logicalFieldIndex); + + float Height() const; + bool IsExpanded() const; + +private: + // Blows up into the debugger if the validation fails. + void ValidateFields() const; + void ValidateField(const BField *field, int32 logicalFieldIndex) const; +private: + BList fFields; + BPrivate:: + BRowContainer* fChildList; + bool fIsExpanded; + float fHeight; + BRow* fNextSelected; + BRow* fPrevSelected; + BRow* fParent; + BColumnListView* fList; + + + friend class BColumnListView; + friend class BPrivate::RecursiveOutlineIterator; + friend class BPrivate::OutlineView; +}; + +// Information about a single column in the list. A column knows +// how to display the BField objects that occur at its location in +// each of the list's rows. See ColumnTypes.h for particular +// subclasses of BField and BColumn that handle common data types. +class BColumn +{ +public: + BColumn(float width, + float minWidth, + float maxWidth, + alignment align = B_ALIGN_LEFT); + virtual ~BColumn(); + + float Width() const; + void SetWidth(float width); + float MinWidth() const; + float MaxWidth() const; + + virtual void DrawTitle(BRect rect, BView *targetView); + virtual void DrawField(BField *field, BRect rect, BView *targetView); + virtual int CompareFields(BField *field1, BField *field2); + + virtual void MouseMoved(BColumnListView *parent, BRow *row, BField *field, + BRect field_rect, BPoint point, uint32 buttons, int32 code); + virtual void MouseDown(BColumnListView *parent, BRow *row, BField *field, + BRect field_rect, BPoint point, uint32 buttons); + virtual void MouseUp(BColumnListView *parent, BRow *row, BField *field); + + virtual void GetColumnName(BString* into) const; + + bool IsVisible() const; + void SetVisible(bool); + + bool WantsEvents() const; + void SetWantsEvents(bool); + + bool ShowHeading() const; + void SetShowHeading(bool); + + alignment Alignment() const; + void SetAlignment(alignment); + + int32 LogicalFieldNum() const; + + /*! + \param field The BField derivative to validate. + + Implement this function on your BColumn derivatives to validate BField derivatives + that your BColumn will be drawing/manipulating. + + This function will be called when BFields are added to the Column, use dynamic_cast<> to + determine if it is of a kind that your BColumn know how ot handle. return false if it is not. + + \note The debugger will be called if you return false from here with information about + what type of BField and BColumn and the logical field index where it occured. + + \note Do not call the inherited version of this, it just returns true; + */ + virtual bool AcceptsField(const BField* field) const; + +private: + float fWidth; + float fMinWidth; + float fMaxWidth; + bool fVisible; + int32 fFieldID; + BColumnListView *fList; + bool fSortAscending; + bool fWantsEvents; + bool fShowHeading; + alignment fAlignment; + + friend class BPrivate::OutlineView; + friend class BColumnListView; + friend class BPrivate::TitleView; +}; + +// The column list view class. +class BColumnListView : public BView, public BInvoker +{ +public: + BColumnListView(BRect rect, + const char *name, + uint32 resizingMode, + uint32 drawFlags, + border_style = B_NO_BORDER, + bool showHorizontalScrollbar = true); + virtual ~BColumnListView(); + + // Interaction + virtual bool InitiateDrag(BPoint, bool wasSelected); + virtual void MessageDropped(BMessage*, BPoint point); + virtual void ExpandOrCollapse(BRow *row, bool expand); + virtual status_t Invoke(BMessage *message = NULL); + virtual void ItemInvoked(); + virtual void SetInvocationMessage(BMessage*); + BMessage* InvocationMessage() const; + uint32 InvocationCommand() const; + BRow* FocusRow() const; + void SetFocusRow(int32 index, bool select=false); + void SetFocusRow(BRow *row, bool select=false); + void SetMouseTrackingEnabled(bool); + + // Selection + list_view_type SelectionMode() const; + void Deselect(BRow *row); + void AddToSelection(BRow *row); + void DeselectAll(); + BRow* CurrentSelection(BRow *lastSelected = 0) const; + virtual void SelectionChanged(); + virtual void SetSelectionMessage(BMessage *); + BMessage* SelectionMessage(); + uint32 SelectionCommand() const; + void SetSelectionMode(list_view_type); // list_view_type is defined in ListView.h. + + // Sorting + void SetSortingEnabled(bool); + bool SortingEnabled() const; + void SetSortColumn(BColumn *column, bool add, bool ascending); + void ClearSortColumns(); + + // The status view is a little area in the lower left hand corner. + void AddStatusView(BView *view); + BView* RemoveStatusView(); + + // Column Manipulation + void AddColumn(BColumn*, int32 logicalFieldIndex); + void MoveColumn(BColumn*, int32 index); + void RemoveColumn(BColumn*); + int32 CountColumns() const; + BColumn* ColumnAt(int32 index) const; + void SetColumnVisible(BColumn*, bool isVisible); + void SetColumnVisible(int32, bool); + bool IsColumnVisible(int32) const; + void SetColumnFlags(column_flags flags); + + // Row manipulation + const BRow* RowAt(int32 index, BRow *parent = 0) const; + BRow* RowAt(int32 index, BRow *parent = 0); + const BRow* RowAt(BPoint) const; + BRow* RowAt(BPoint); + bool GetRowRect(const BRow *row, BRect *outRect) const; + bool FindParent(BRow *row, BRow **outs_parent, bool *out_isVisible) const; + int32 IndexOf(BRow *row); + int32 CountRows(BRow *parent = 0) const; + void AddRow(BRow*, BRow *parent = 0); + void AddRow(BRow*, int32 index, BRow *parent = 0); + + void ScrollTo(const BRow* Row); + + // Does not delete row or children at this time. + // todo: Make delete row and children + void RemoveRow(BRow*); + + void UpdateRow(BRow*); + void Clear(); + + // Appearance (DEPRECATED) + void GetFont(BFont* font) const {BView::GetFont(font);} + virtual void SetFont(const BFont *font, uint32 mask = B_FONT_ALL); + virtual void SetHighColor(rgb_color); + void SetSelectionColor(rgb_color); + void SetBackgroundColor(rgb_color); + void SetEditColor(rgb_color); + const rgb_color SelectionColor() const; + const rgb_color BackgroundColor() const; + const rgb_color EditColor() const; + + // Appearance (NEW STYLE) + void SetColor(ColumnListViewColor color_num, rgb_color color); + void SetFont(ColumnListViewFont font_num, const BFont* font, uint32 mask = B_FONT_ALL); + rgb_color Color(ColumnListViewColor color_num) const; + void GetFont(ColumnListViewFont font_num, BFont* font) const; + + BPoint SuggestTextPosition(const BRow* row, const BColumn* column=NULL) const; + + void SetLatchWidth(float width); + float LatchWidth() const; + virtual void DrawLatch(BView*, BRect, LatchType, BRow*); + virtual void MakeFocus(bool isfocus = true); + void SaveState(BMessage *msg); + void LoadState(BMessage *msg); + + BView* ScrollView() const { return (BView *)fOutlineView; } + void SetEditMode(bool state); + void Refresh(); + +protected: + virtual void MessageReceived(BMessage *message); + virtual void KeyDown(const char *bytes, int32 numBytes); + virtual void AttachedToWindow(); + virtual void WindowActivated(bool active); + virtual void Draw(BRect); + +private: + rgb_color fColorList[B_COLOR_TOTAL]; + BPrivate::TitleView* fTitleView; + BPrivate::OutlineView* fOutlineView; + BList fColumns; + BScrollBar* fHorizontalScrollBar; + BScrollBar* fVerticalScrollBar; + BList fSortColumns; + BView* fStatusView; + BMessage* fSelectionMessage; + bool fSortingEnabled; + float fLatchWidth; + border_style fBorderStyle; +}; + +#endif diff --git a/src/kits/interface/ColorTools.cpp b/src/kits/interface/ColorTools.cpp new file mode 100644 index 0000000000..e4aea31624 --- /dev/null +++ b/src/kits/interface/ColorTools.cpp @@ -0,0 +1,110 @@ +/* +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: ColorTools.cpp +/ +/ Description: Additional experimental color manipulation functions. +/ +/ Copyright 2000, Be Incorporated, All Rights Reserved +/ +*******************************************************************************/ + +#include "ColorTools.h" + +#if B_BEOS_VERSION <= B_BEOS_VERSION_MAUI + +namespace BExperimental { + +#if DEBUG +#define DB_INLINE +#else +#define DB_INLINE inline +#endif + +static DB_INLINE void mix_color_func(rgb_color* target, const rgb_color other, uint8 amount) +{ + target->red = (uint8)( ((int16(other.red)-int16(target->red))*amount)/255 + + target->red ); + target->green = (uint8)( ((int16(other.green)-int16(target->green))*amount)/255 + + target->green ); + target->blue = (uint8)( ((int16(other.blue)-int16(target->blue))*amount)/255 + + target->blue ); + target->alpha = (uint8)( ((int16(other.alpha)-int16(target->alpha))*amount)/255 + + target->alpha ); +} + +static DB_INLINE void blend_color_func(rgb_color* target, const rgb_color other, uint8 amount) +{ + const uint8 alphaMix = (uint8)( ((int16(other.alpha)-int16(255-target->alpha))*amount)/255 + + (255-target->alpha) ); + target->red = (uint8)( ((int16(other.red)-int16(target->red))*alphaMix)/255 + + target->red ); + target->green = (uint8)( ((int16(other.green)-int16(target->green))*alphaMix)/255 + + target->green ); + target->blue = (uint8)( ((int16(other.blue)-int16(target->blue))*alphaMix)/255 + + target->blue ); + target->alpha = (uint8)( ((int16(other.alpha)-int16(target->alpha))*amount)/255 + + target->alpha ); +} + +static DB_INLINE void disable_color_func(rgb_color* target, const rgb_color background) +{ + blend_color_func(target, background, 255-70); +} + +// -------------------------------------------------------------------------- + +rgb_color mix_color(rgb_color color1, rgb_color color2, uint8 amount) +{ + mix_color_func(&color1, color2, amount); + return color1; +} + +rgb_color blend_color(rgb_color color1, rgb_color color2, uint8 amount) +{ + blend_color_func(&color1, color2, amount); + return color1; +} + +rgb_color disable_color(rgb_color color, rgb_color background) +{ + disable_color_func(&color, background); + return color; +} + +} + +#endif diff --git a/src/kits/interface/ColumnListView.cpp b/src/kits/interface/ColumnListView.cpp new file mode 100644 index 0000000000..1a04fbe35d --- /dev/null +++ b/src/kits/interface/ColumnListView.cpp @@ -0,0 +1,4176 @@ +/* +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 + +#include +#include + +#include +#include +#if _INCLUDES_CLASS_CURSOR +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ObjectList.h" +#include "ColumnListView.h" +#include "ColorTools.h" +/* +#ifndef _ARCHIVE_DEFS_H +#include +#endif +*/ +#define DOUBLE_BUFFERED_COLUMN_RESIZE 1 +#define SMART_REDRAW 1 +#define DRAG_TITLE_OUTLINE 1 +#define CONSTRAIN_CLIPPING_REGION 1 +#define LOWER_SCROLLBAR 1 + +namespace BPrivate { + +static const unsigned char kResizeCursorData[] = { + 16, 1, 8, 8, + 0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 9, 0x90, 0x19, 0x98, 0x39, 0x09c, 0x79, 0x9e, + 0x79, 0x9e, 0x39, 0x9c, 0x19, 0x98, 0x9, 0x90, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0, + 3, 0xc0, 3, 0xc0, 3, 0xc0, 0xf, 0xf0, 0x1f, 0xf8, 0x3f, 0xfa, 0x7f, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfa, 0x1f, 0xf8, 0xf, 0xf0, 3, 0xc0, 3, 0xc0, 3, 0xc0 +}; + +static const unsigned char kMaxResizeCursorData[] = { + 16, 1, 8, 8, + 0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 9, 0x80, 0x19, 0x80, 0x39, 0x80, 0x79, 0x80, + 0x79, 0x80, 0x39, 0x80, 0x19, 0x80, 0x9, 0x80, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0, + 3, 0xc0, 3, 0xc0, 3, 0xc0, 0xf, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0, 0x7f, 0xc0, 0xff, 0xc0, + 0xff, 0xc0, 0x7f, 0xc0, 0x3f, 0xc0, 0x1f, 0xc0, 0xf, 0xc0, 3, 0xc0, 3, 0xc0, 3, 0xc0 +}; + +static const unsigned char kMinResizeCursorData[] = { + 16, 1, 8, 8, + 0, 0, 1, 0x80, 1, 0x80, 1, 0x80, 1, 0x90, 1, 0x98, 1, 0x09c, 1, 0x9e, + 1, 0x9e, 1, 0x9c, 1, 0x98, 1, 0x90, 1, 0x80, 1, 0x80, 1, 0x80, 0, 0, + 3, 0xc0, 3, 0xc0, 3, 0xc0, 3, 0xf0, 3, 0xf8, 3, 0xfa, 3, 0xfe, 3, 0xff, + 3, 0xff, 3, 0xfe, 3, 0xfa, 3, 0xf8, 3, 0xf0, 3, 0xc0, 3, 0xc0, 3, 0xc0 +}; + +static const unsigned char kColumnMoveCursorData[] = { + 16, 1, 8, 8, + 1, 0x80, 3, 0xc0, 7, 0xe0, 0, 0, 0, 0, 0x20, 4, 0x61, 0x86, 0xe3, 0xc7, + 0xe3, 0xc7, 0x61, 0x86, 0x20, 4, 0, 0, 0, 0, 7, 0xe0, 3, 0xc0, 1, 0x80, + 1, 0x80, 3, 0xc0, 7, 0xe0, 0, 0, 0, 0, 0x20, 4, 0x61, 0x86, 0xe3, 0xc7, + 0xe3, 0xc7, 0x61, 0x86, 0x20, 4, 0, 0, 0, 0, 7, 0xe0, 3, 0xc0, 1, 0x80 +}; + +static const unsigned char kUpSortArrow8x8[] = { + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff +}; + +static const unsigned char kDownSortArrow8x8[] = { + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff +}; + +static const unsigned char kUpSortArrow8x8Invert[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff +}; + +static const unsigned char kDownSortArrow8x8Invert[] = { + 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x1f, 0x1f, 0x1f, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* +static const rgb_color kTitleColor = {215, 215, 215, 255}; +static const rgb_color kTitleTextColor = { 0, 0, 0, 255 }; +static const rgb_color kDefaultBackgroundColor = {236, 236, 236, 255}; +static const rgb_color kRowDividerColor = {148, 148, 148, 255}; +static const rgb_color kDefaultSelectionColor = {255, 255, 255, 255}; +static const rgb_color kDefaultEditColor = {180, 180, 180, 180}; +static const rgb_color kNonFocusSelectionColor = {220, 220, 220, 255}; +*/ + +static const float kTitleHeight = 17.0; +static const float kLatchWidth = 15.0; + + +static const rgb_color kColor[B_COLOR_TOTAL] = +{ + {236, 236, 236, 255}, // B_COLOR_BACKGROUND + { 0, 0, 0, 255}, // B_COLOR_TEXT + {148, 148, 148, 255}, // B_COLOR_ROW_DIVIDER + {255, 255, 255, 255}, // B_COLOR_SELECTION + { 0, 0, 0, 255}, // B_COLOR_SELECTION_TEXT + {220, 220, 220, 255}, // B_COLOR_NON_FOCUS_SELECTION + {180, 180, 180, 180}, // B_COLOR_EDIT_BACKGROUND + { 0, 0, 0, 255}, // B_COLOR_EDIT_TEXT + {215, 215, 215, 255}, // B_COLOR_HEADER_BACKGROUND + { 0, 0, 0, 255}, // B_COLOR_HEADER_TEXT + { 0, 0, 0, 255}, // B_COLOR_SEPARATOR_LINE + { 0, 0, 0, 255}, // B_COLOR_SEPARATOR_BORDER +}; + +static const int32 kMaxDepth = 1024; +static const float kLeftMargin = kLatchWidth; +static const float kRightMargin = kLatchWidth; +static const float kBottomMargin = kLatchWidth; +static const float kOutlineLevelIndent = kLatchWidth; +static const float kColumnResizeAreaWidth = 10.0; +static const float kRowDragSensitivity = 5.0; +static const float kDoubleClickMoveSensitivity = 4.0; +static const float kSortIndicatorWidth = 9.0; +static const float kDropHighlightLineHeight = 2.0; + +static const uint32 kToggleColumn = 'BTCL'; + +class BRowContainer : public BObjectList +{ +}; + +class TitleView : public BView { +public: + TitleView(BRect, OutlineView*, BList *visibleColumns, BList *sortColumns, + BColumnListView *masterView, uint32 resizingMode); + ~TitleView(); + void ColumnAdded(BColumn*); + void SetColumnVisible(BColumn*, bool); + + virtual void Draw(BRect rect); + virtual void ScrollTo(BPoint); + virtual void MessageReceived(BMessage*); + virtual void MouseDown(BPoint); + virtual void MouseMoved(BPoint, uint32, const BMessage*); + virtual void MouseUp(BPoint position); + virtual void FrameResized(float width, float height); + + void MoveColumn(BColumn *column, int32 index); + void SetColumnFlags(column_flags flags); + + void SetEditMode(bool state) { fEditMode = state; } + +private: + void GetTitleRect(BColumn*, BRect *out_rect); + int32 FindColumn(BPoint, float *out_leftEdge); + void FixScrollBar(bool scrollToFit); + void DragSelectedColumn(BPoint); + void ResizeSelectedColumn(BPoint); + void ComputeDragBoundries(BColumn*, BPoint); + void DrawTitle(BView*, BRect, BColumn*, bool depressed); + + OutlineView *fOutlineView; + BList *fColumns; + BList *fSortColumns; + float fColumnsWidth; + BRect fVisibleRect; + +#if DOUBLE_BUFFERED_COLUMN_RESIZE + BBitmap *fDrawBuffer; + BView *fDrawBufferView; +#endif + + enum { + INACTIVE, + RESIZING_COLUMN, + PRESSING_COLUMN, + DRAG_COLUMN_INSIDE_TITLE, + DRAG_COLUMN_OUTSIDE_TITLE + } fCurrentState; + + BPopUpMenu *fColumnPop; + BColumnListView *fMasterView; + bool fEditMode; + int32 fColumnFlags; + + // State information for resizing/dragging + BColumn *fSelectedColumn; + BRect fSelectedColumnRect; + bool fResizingFirstColumn; + BPoint fClickPoint; // offset within cell + float fLeftDragBoundry; + float fRightDragBoundry; + BPoint fCurrentDragPosition; + + + BBitmap *fUpSortArrow; + BBitmap *fDownSortArrow; +#if _INCLUDES_CLASS_CURSOR + BCursor *fResizeCursor; + BCursor *fMinResizeCursor; + BCursor *fMaxResizeCursor; + BCursor *fColumnMoveCursor; +#endif + + + typedef BView _inherited; +}; + +class OutlineView : public BView { +public: + OutlineView(BRect, BList *visibleColumns, BList *sortColumns, BColumnListView *listView); + ~OutlineView(); + + virtual void Draw(BRect); + const BRect& VisibleRect() const; + + void RedrawColumn(BColumn *column, float leftEdge, bool isFirstColumn); + void StartSorting(); + + void AddRow(BRow*, int32 index, BRow *TheRow); + BRow* CurrentSelection(BRow *lastSelected) const; + void ToggleFocusRowSelection(bool selectRange); + void ToggleFocusRowOpen(); + void ChangeFocusRow(bool up, bool updateSelection, bool addToCurrentSelection); + void MoveFocusToVisibleRect(); + void ExpandOrCollapse(BRow *parent, bool expand); + void RemoveRow(BRow*); + BRowContainer* RowList(); + void UpdateRow(BRow*); + bool FindParent(BRow *row, BRow **out_parent, bool *out_isVisible); + int32 IndexOf(BRow *row); + void Deselect(BRow*); + void AddToSelection(BRow*); + void DeselectAll(); + BRow* FocusRow() const; + void SetFocusRow(BRow *row, bool select); + BRow* FindRow(float ypos, int32 *out_indent, float *out_top); + bool FindRect(const BRow *row, BRect *out_rect); + void ScrollTo(const BRow* Row); + + void Clear(); + void SetSelectionMode(list_view_type); + list_view_type SelectionMode() const; + void SetMouseTrackingEnabled(bool); + void FixScrollBar(bool scrollToFit); + void SetEditMode(bool state) { fEditMode = state; } + + virtual void FrameResized(float width, float height); + virtual void ScrollTo(BPoint pt); + virtual void MouseDown(BPoint); + virtual void MouseMoved(BPoint, uint32, const BMessage*); + virtual void MouseUp(BPoint); + virtual void MessageReceived(BMessage*); + +private: + bool SortList(BRowContainer *list, bool isVisible); + static int32 DeepSortThreadEntry(void *outlineView); + void DeepSort(); + void SelectRange(BRow *start, BRow *end); + int32 CompareRows(BRow *row1, BRow *row2); + void AddSorted(BRowContainer *list, BRow *row); + void RecursiveDeleteRows(BRowContainer *list, bool owner); + void InvalidateCachedPositions(); + bool FindVisibleRect(BRow *row, BRect *out_rect); + + BList* fColumns; + BList* fSortColumns; + float fItemsHeight; + BRowContainer fRows; + BRect fVisibleRect; + +#if DOUBLE_BUFFERED_COLUMN_RESIZE + BBitmap* fDrawBuffer; + BView* fDrawBufferView; +#endif + + BRow* fFocusRow; + BRect fFocusRowRect; + BRow* fRollOverRow; + + BRow fSelectionListDummyHead; + BRow* fLastSelectedItem; + BRow* fFirstSelectedItem; + + thread_id fSortThread; + int32 fNumSorted; + bool fSortCancelled; + + enum CurrentState + { + INACTIVE, + LATCH_CLICKED, + ROW_CLICKED, + DRAGGING_ROWS + }; + + CurrentState fCurrentState; + + + BColumnListView* fMasterView; + list_view_type fSelectionMode; + bool fTrackMouse; + BField* fCurrentField; + BRow* fCurrentRow; + BColumn* fCurrentColumn; + bool fMouseDown; + BRect fFieldRect; + int32 fCurrentCode; + bool fEditMode; + + // State information for mouse/keyboard interaction + BPoint fClickPoint; + bool fDragging; + int32 fClickCount; + BRow *fTargetRow; + float fTargetRowTop; + BRect fLatchRect; + float fDropHighlightY; + + friend class RecursiveOutlineIterator; + typedef BView _inherited; +}; + +class RecursiveOutlineIterator { +public: + RecursiveOutlineIterator(BRowContainer*, bool openBranchesOnly = true); + BRow *CurrentRow() const; + int32 CurrentLevel() const; + void GoToNext(); + +private: + struct { + BRowContainer *fRowSet; + int32 fIndex; + int32 fDepth; + } fStack[kMaxDepth]; + + int32 fStackIndex; + BRowContainer *fCurrentList; + int32 fCurrentListIndex; + int32 fCurrentListDepth; + bool fOpenBranchesOnly; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +BField::BField() +{ +} + +BField::~BField() +{ +} + +// #pragma mark - + +void BColumn::MouseMoved(BColumnListView */*parent*/, BRow */*row*/, BField */*field*/, + BRect /*field_rect*/, BPoint/*point*/, uint32 /*buttons*/, int32 /*code*/) +{ +} + +void BColumn::MouseDown( BColumnListView */*parent*/, BRow */*row*/, BField */*field*/, + BRect /*field_rect*/, BPoint /*point*/, uint32 /*buttons*/) +{ +} + +void BColumn::MouseUp(BColumnListView */*parent*/, BRow */*row*/, BField */*field*/) +{ +} + +// #pragma mark - + +BRow::BRow(float height) + : fChildList(NULL), + fIsExpanded(false), + fHeight(height), + fNextSelected(NULL), + fPrevSelected(NULL), + fParent(NULL), + fList(NULL) +{ +} + +BRow::~BRow() +{ + while (true) { + BField *field = (BField*) fFields.RemoveItem(0L); + if (field == 0) + break; + + delete field; + } +} + +bool BRow::HasLatch() const +{ + return fChildList != 0; +} + +int32 BRow::CountFields() const +{ + return fFields.CountItems(); +} + +BField* BRow::GetField(int32 index) +{ + return (BField*) fFields.ItemAt(index); +} + +const BField* BRow::GetField(int32 index) const +{ + return (const BField*) fFields.ItemAt(index); +} + +void BRow::SetField(BField *field, int32 logicalFieldIndex) +{ + if (fFields.ItemAt(logicalFieldIndex) != 0) + delete (BField*) fFields.RemoveItem(logicalFieldIndex); + + if( NULL != fList ) { + ValidateField(field, logicalFieldIndex); + BRect inv; + fList->GetRowRect(this, &inv); + fList->Invalidate(inv); + } + + fFields.AddItem(field, logicalFieldIndex); +} + +float BRow::Height() const +{ + return fHeight; +} + +bool BRow::IsExpanded() const +{ + return fIsExpanded; +} + +void +BRow::ValidateFields() const +{ + for( int32 i = 0; i < CountFields(); i++ ) + { + ValidateField(GetField(i), i); + } +} + +void +BRow::ValidateField(const BField *field, int32 logicalFieldIndex) const +{ + // The Fields may be moved by the user, but the logicalFieldIndexes + // do not change, so we need to map them over when checking the + // Field types. + BColumn* col = NULL; + int32 items = fList->CountColumns(); + for( int32 i = 0 ; i < items; ++i ) + { + col = fList->ColumnAt(i); + if( col->LogicalFieldNum() == logicalFieldIndex ) + break; + } + + if( NULL == col ) + { + BString dbmessage("\n\n\tThe parent BColumnListView does not have " + "\n\ta BColumn at the logical field index "); + dbmessage << logicalFieldIndex << ".\n\n"; + printf(dbmessage.String()); + } + else + { + if( false == col->AcceptsField(field) ) + { + BString dbmessage("\n\n\tThe BColumn of type "); + dbmessage << typeid(*col).name() << "\n\tat logical field index " + << logicalFieldIndex << "\n\tdoes not support the field type " + << typeid(*field).name() << ".\n\n"; + debugger(dbmessage.String()); + } + } +} + + +// #pragma mark - + +BColumn::BColumn(float width, float minWidth, float maxWidth, alignment align) + : fWidth(width), + fMinWidth(minWidth), + fMaxWidth(maxWidth), + fVisible(true), + fList(0), + fShowHeading(true), + fAlignment(align) +{ +} + +BColumn::~BColumn() +{ +} + +float BColumn::Width() const +{ + return fWidth; +} + +void BColumn::SetWidth(float width) +{ + fWidth = width; +} + +float BColumn::MinWidth() const +{ + return fMinWidth; +} + +float BColumn::MaxWidth() const +{ + return fMaxWidth; +} + +void BColumn::DrawTitle(BRect, BView*) +{ +} + +void BColumn::DrawField(BField*, BRect, BView*) +{ +} + +int BColumn::CompareFields(BField *, BField *) +{ + return 0; +} + +void BColumn::GetColumnName(BString* into) const +{ + *into = "(Unnamed)"; +} + +bool BColumn::IsVisible() const +{ + return fVisible; +} + +void BColumn::SetVisible(bool visible) +{ + if (fList && (fVisible != visible)) + fList->SetColumnVisible(this, visible); +} + +bool BColumn::ShowHeading() const +{ + return fShowHeading; +} + +void BColumn::SetShowHeading(bool state) +{ + fShowHeading = state; +} + +alignment BColumn::Alignment() const +{ + return fAlignment; +} + +void BColumn::SetAlignment(alignment align) +{ + fAlignment = align; +} + +bool BColumn::WantsEvents() const +{ + return fWantsEvents; +} + +void BColumn::SetWantsEvents(bool state) +{ + fWantsEvents = state; +} + +int32 BColumn::LogicalFieldNum() const +{ + return fFieldID; +} + +bool +BColumn::AcceptsField(const BField*) const +{ + return true; +} + + +// #pragma mark - + +BColumnListView::BColumnListView(BRect rect, const char *name, uint32 resizingMode, + uint32 drawFlags, border_style border, bool showHorizontalScrollbar) + : BView(rect, name, resizingMode, drawFlags | B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), + fStatusView(0), + fSelectionMessage(0), + fSortingEnabled(true), + fLatchWidth(kLatchWidth), + fBorderStyle(border) +{ + SetViewColor(B_TRANSPARENT_32_BIT); + + BRect bounds(rect); + bounds.OffsetTo(0, 0); + + for (int i = 0; i < (int)B_COLOR_TOTAL; i++) + fColorList[i] = kColor[i]; + + BRect titleRect(bounds); + titleRect.bottom = titleRect.top + kTitleHeight; +#if !LOWER_SCROLLBAR + titleRect.right -= B_V_SCROLL_BAR_WIDTH + 1; +#endif + + BRect outlineRect(bounds); + outlineRect.top = titleRect.bottom + 1.0; + outlineRect.right -= B_V_SCROLL_BAR_WIDTH + 1; + if(showHorizontalScrollbar) + outlineRect.bottom -= B_H_SCROLL_BAR_HEIGHT + 1; + + BRect vScrollBarRect(bounds); +#if LOWER_SCROLLBAR + vScrollBarRect.top += kTitleHeight; +#endif + + vScrollBarRect.left = vScrollBarRect.right - B_V_SCROLL_BAR_WIDTH; + if(showHorizontalScrollbar) + vScrollBarRect.bottom -= B_H_SCROLL_BAR_HEIGHT; + + BRect hScrollBarRect(bounds); + hScrollBarRect.top = hScrollBarRect.bottom - B_H_SCROLL_BAR_HEIGHT; + hScrollBarRect.right -= B_V_SCROLL_BAR_WIDTH; + + // Adjust stuff so the border will fit. + if (fBorderStyle == B_PLAIN_BORDER) { + titleRect.InsetBy(1, 0); + titleRect.top++; + outlineRect.InsetBy(1, 0); + outlineRect.bottom--; + + vScrollBarRect.OffsetBy(-1, 0); + vScrollBarRect.InsetBy(0, 1); + hScrollBarRect.OffsetBy(0, -1); + hScrollBarRect.InsetBy(1, 0); + } else if (fBorderStyle == B_FANCY_BORDER) { + titleRect.InsetBy(2, 0); + titleRect.top += 2; + outlineRect.InsetBy(2, 0); + outlineRect.bottom -= 2; + + vScrollBarRect.OffsetBy(-2, 0); + vScrollBarRect.InsetBy(0, 2); + hScrollBarRect.OffsetBy(0, -2); + hScrollBarRect.InsetBy(2, 0); + } + + fOutlineView = new OutlineView(outlineRect, &fColumns, &fSortColumns, this); + AddChild(fOutlineView); + + + // Adapt to correct resizing mode + uint32 fParentFlags = resizingMode; + // Always follow LEFT_RIGHT + uint32 fTitleFlags = B_FOLLOW_LEFT_RIGHT; + + if ((fParentFlags & B_FOLLOW_TOP) && (fParentFlags & ~B_FOLLOW_BOTTOM)) { + fTitleFlags |= B_FOLLOW_TOP; + } + else if ((fParentFlags & B_FOLLOW_BOTTOM) && (fParentFlags & ~B_FOLLOW_TOP)) { + fTitleFlags |= B_FOLLOW_BOTTOM; + } + + fTitleView = new TitleView(titleRect, fOutlineView, &fColumns, &fSortColumns, this, fTitleFlags); + + + AddChild(fTitleView); + fVerticalScrollBar = new BScrollBar(vScrollBarRect, "vertical_scroll_bar", + fOutlineView, 0.0, bounds.Height(), B_VERTICAL); + AddChild(fVerticalScrollBar); + fHorizontalScrollBar = new BScrollBar(hScrollBarRect, "horizontal_scroll_bar", + fTitleView, 0.0, bounds.Width(), B_HORIZONTAL); + AddChild(fHorizontalScrollBar); + if(!showHorizontalScrollbar) + fHorizontalScrollBar->Hide(); + fOutlineView->FixScrollBar(true); +} + +BColumnListView::~BColumnListView() +{ + while (true) { + BColumn *column = (BColumn*) fColumns.RemoveItem(0L); + if (column == 0) + break; + + delete column; + } +} + +bool BColumnListView::InitiateDrag(BPoint, bool) +{ + return false; +} + +void BColumnListView::MessageDropped(BMessage*, BPoint) +{ +} + +void BColumnListView::ExpandOrCollapse(BRow* Row, bool Open) +{ + fOutlineView->ExpandOrCollapse(Row, Open); +} + +status_t BColumnListView::Invoke(BMessage *message) +{ + if (message == 0) + message = Message(); + + return BInvoker::Invoke(message); +} + +void BColumnListView::ItemInvoked() +{ + Invoke(); +} + +void BColumnListView::SetInvocationMessage(BMessage *message) +{ + SetMessage(message); +} + +BMessage* BColumnListView::InvocationMessage() const +{ + return Message(); +} + +uint32 BColumnListView::InvocationCommand() const +{ + return Command(); +} + +BRow* BColumnListView::FocusRow() const +{ + return fOutlineView->FocusRow(); +} + +void BColumnListView::SetFocusRow(int32 Index, bool Select) +{ + SetFocusRow(RowAt(Index), Select); +} + +void BColumnListView::SetFocusRow(BRow* Row, bool Select) +{ + fOutlineView->SetFocusRow(Row, Select); +} + +void BColumnListView::SetMouseTrackingEnabled(bool Enabled) +{ + fOutlineView->SetMouseTrackingEnabled(Enabled); +} + +list_view_type BColumnListView::SelectionMode() const +{ + return fOutlineView->SelectionMode(); +} + +void BColumnListView::Deselect(BRow *row) +{ + fOutlineView->Deselect(row); +} + +void BColumnListView::AddToSelection(BRow *row) +{ + fOutlineView->AddToSelection(row); +} + +void BColumnListView::DeselectAll() +{ + fOutlineView->DeselectAll(); +} + +BRow* BColumnListView::CurrentSelection(BRow *lastSelected) const +{ + return fOutlineView->CurrentSelection(lastSelected); +} + +void BColumnListView::SelectionChanged() +{ + if (fSelectionMessage) + Invoke(fSelectionMessage); +} + +void BColumnListView::SetSelectionMessage(BMessage *message) +{ + if (fSelectionMessage == message) + return; + + delete fSelectionMessage; + fSelectionMessage = message; +} + +BMessage* BColumnListView::SelectionMessage() +{ + return fSelectionMessage; +} + +uint32 BColumnListView::SelectionCommand() const +{ + if (fSelectionMessage) + return fSelectionMessage->what; + + return 0; +} + +void BColumnListView::SetSelectionMode(list_view_type mode) +{ + fOutlineView->SetSelectionMode(mode); +} + +void BColumnListView::SetSortingEnabled(bool enabled) +{ + fSortingEnabled = enabled; + fSortColumns.MakeEmpty(); + fTitleView->Invalidate(); // Erase sort indicators +} + +bool BColumnListView::SortingEnabled() const +{ + return fSortingEnabled; +} + +void BColumnListView::SetSortColumn(BColumn *column, bool add, bool ascending) +{ + if (!SortingEnabled()) + return; + + if (!add) + fSortColumns.MakeEmpty(); + + if (!fSortColumns.HasItem(column)) + fSortColumns.AddItem(column); + + column->fSortAscending = ascending; + fTitleView->Invalidate(); + fOutlineView->StartSorting(); +} + +void BColumnListView::ClearSortColumns() +{ + fSortColumns.MakeEmpty(); + fTitleView->Invalidate(); // Erase sort indicators +} + +void BColumnListView::AddStatusView(BView *view) +{ + BRect bounds = Bounds(); + float width = view->Bounds().Width(); + if (width > bounds.Width() / 2) + width = bounds.Width() / 2; + + fStatusView = view; + + Window()->BeginViewTransaction(); + fHorizontalScrollBar->ResizeBy(-(width + 1), 0); + fHorizontalScrollBar->MoveBy((width + 1), 0); + AddChild(view); + + BRect viewRect(bounds); + viewRect.right = width; + viewRect.top = viewRect.bottom - B_H_SCROLL_BAR_HEIGHT; + if (fBorderStyle == B_PLAIN_BORDER) + viewRect.OffsetBy(1, -1); + else if (fBorderStyle == B_FANCY_BORDER) + viewRect.OffsetBy(2, -2); + + view->SetResizingMode(B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); + view->ResizeTo(viewRect.Width(), viewRect.Height()); + view->MoveTo(viewRect.left, viewRect.top); + Window()->EndViewTransaction(); +} + +BView* BColumnListView::RemoveStatusView() +{ + if (fStatusView) { + float width = fStatusView->Bounds().Width(); + Window()->BeginViewTransaction(); + fStatusView->RemoveSelf(); + fHorizontalScrollBar->MoveBy(-width, 0); + fHorizontalScrollBar->ResizeBy(width, 0); + Window()->EndViewTransaction(); + } + + BView *view = fStatusView; + fStatusView = 0; + return view; +} +void BColumnListView::AddColumn(BColumn *column, int32 logicalFieldIndex) +{ + ASSERT(column != 0); + + column->fList = this; + column->fFieldID = logicalFieldIndex; + + // sanity check. If there is already a field with this ID, remove it. + for (int32 index = 0; index < fColumns.CountItems(); index++) { + BColumn *existingColumn = (BColumn*) fColumns.ItemAt(index); + if (existingColumn && existingColumn->fFieldID == logicalFieldIndex) { + RemoveColumn(existingColumn); + break; + } + } + + if (column->Width() < column->MinWidth()) + column->SetWidth(column->MinWidth()); + else if (column->Width() > column->MaxWidth()) + column->SetWidth(column->MaxWidth()); + + fColumns.AddItem((void*) column); + fTitleView->ColumnAdded(column); +} + +void BColumnListView::MoveColumn(BColumn *column, int32 index) +{ + ASSERT(column != 0); + fTitleView->MoveColumn(column, index); +} + +void BColumnListView::RemoveColumn(BColumn *column) +{ + if (fColumns.HasItem(column)) { + SetColumnVisible(column, false); + Window()->UpdateIfNeeded(); + fColumns.RemoveItem(column); + } +} + +int32 BColumnListView::CountColumns() const +{ + return fColumns.CountItems(); +} + +BColumn* BColumnListView::ColumnAt(int32 field) const +{ + return (BColumn*) fColumns.ItemAt(field); +} + +void BColumnListView::SetColumnVisible(BColumn *column, bool visible) +{ + fTitleView->SetColumnVisible(column, visible); +} + +void BColumnListView::SetColumnVisible(int32 index, bool isVisible) +{ + BColumn *column = ColumnAt(index); + if (column) + column->SetVisible(isVisible); +} + +bool BColumnListView::IsColumnVisible(int32 index) const +{ + BColumn *column = ColumnAt(index); + if (column) + return column->IsVisible(); + + return false; +} + +void BColumnListView::SetColumnFlags(column_flags flags) +{ + fTitleView->SetColumnFlags(flags); +} + +const BRow* BColumnListView::RowAt(int32 Index, BRow* ParentRow) const +{ + if (ParentRow == 0) + return fOutlineView->RowList()->ItemAt(Index); + + return ParentRow->fChildList ? ParentRow->fChildList->ItemAt(Index) : NULL; +} + +BRow* BColumnListView::RowAt(int32 Index, BRow* ParentRow) +{ + if (ParentRow == 0) + return fOutlineView->RowList()->ItemAt(Index); + + return ParentRow->fChildList ? ParentRow->fChildList->ItemAt(Index) : 0; +} + +const BRow* BColumnListView::RowAt(BPoint point) const +{ + float top; + int32 indent; + return fOutlineView->FindRow(point.y, &indent, &top); +} + +BRow* BColumnListView::RowAt(BPoint point) +{ + float top; + int32 indent; + return fOutlineView->FindRow(point.y, &indent, &top); +} + +bool BColumnListView::GetRowRect(const BRow *row, BRect *outRect) const +{ + return fOutlineView->FindRect(row, outRect); +} + +bool BColumnListView::FindParent(BRow *row, BRow **out_parent, bool *out_isVisible) const +{ + return fOutlineView->FindParent(row, out_parent, out_isVisible); +} + +int32 BColumnListView::IndexOf(BRow *row) +{ + return fOutlineView->IndexOf(row); +} + +int32 BColumnListView::CountRows(BRow* ParentRow) const +{ + if (ParentRow == 0) + return fOutlineView->RowList()->CountItems(); + if (ParentRow->fChildList) + return ParentRow->fChildList->CountItems(); + else + return 0; +} + +void BColumnListView::AddRow(BRow *row, BRow* ParentRow) +{ + AddRow(row, -1, ParentRow); +} + +void BColumnListView::AddRow(BRow *row, int32 index, BRow* ParentRow) +{ + row->fChildList = 0; + row->fList = this; + row->ValidateFields(); + fOutlineView->AddRow(row, index, ParentRow); +} + +void BColumnListView::RemoveRow(BRow *row) +{ + fOutlineView->RemoveRow(row); + row->fList = NULL; +} + +void BColumnListView::UpdateRow(BRow *row) +{ + fOutlineView->UpdateRow(row); +} + +void BColumnListView::ScrollTo(const BRow* Row) +{ + fOutlineView->ScrollTo(Row); +} + +void BColumnListView::Clear() +{ + fOutlineView->Clear(); +} + +void BColumnListView::SetFont(const BFont *font, uint32 mask) +{ + // This method is deprecated. + fOutlineView->SetFont(font, mask); + fTitleView->SetFont(font, mask); +} + +void BColumnListView::SetFont(ColumnListViewFont font_num, const BFont* font, uint32 mask) +{ + switch (font_num) { + case B_FONT_ROW: + fOutlineView->SetFont(font, mask); + break; + + case B_FONT_HEADER: + fTitleView->SetFont(font, mask); + break; + + default: + ASSERT(false); + break; + }; +} + +void BColumnListView::GetFont(ColumnListViewFont font_num, BFont* font) const +{ + switch (font_num) { + case B_FONT_ROW: + fOutlineView->GetFont(font); + break; + + case B_FONT_HEADER: + fTitleView->GetFont(font); + break; + + default: + ASSERT(false); + break; + }; +} + +void BColumnListView::SetColor(ColumnListViewColor color_num, const rgb_color color) +{ + if ((int)color_num < 0) + { + ASSERT(false); + color_num = (ColumnListViewColor) 0; + } + + if ((int)color_num >= (int)B_COLOR_TOTAL) + { + ASSERT(false); + color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1); + } + + fColorList[color_num] = color; +} + +rgb_color BColumnListView::Color(ColumnListViewColor color_num) const +{ + if ((int)color_num < 0) + { + ASSERT(false); + color_num = (ColumnListViewColor) 0; + } + + if ((int)color_num >= (int)B_COLOR_TOTAL) + { + ASSERT(false); + color_num = (ColumnListViewColor) (B_COLOR_TOTAL - 1); + } + + return fColorList[color_num]; +} + +void BColumnListView::SetHighColor(rgb_color color) +{ + BView::SetHighColor(color); +// fOutlineView->Invalidate(); // Redraw things with the new color + // Note that this will currently cause + // an infinite loop, refreshing over and over. + // A better solution is needed. +} + +void BColumnListView::SetSelectionColor(rgb_color color) +{ + fColorList[B_COLOR_SELECTION] = color; +} + +void BColumnListView::SetBackgroundColor(rgb_color color) +{ + fColorList[B_COLOR_BACKGROUND] = color; + fOutlineView->Invalidate(); // Repaint with new color +} + +void BColumnListView::SetEditColor(rgb_color color) +{ + fColorList[B_COLOR_EDIT_BACKGROUND] = color; +} + +const rgb_color BColumnListView::SelectionColor() const +{ + return fColorList[B_COLOR_SELECTION]; +} + +const rgb_color BColumnListView::BackgroundColor() const +{ + return fColorList[B_COLOR_BACKGROUND]; +} + +const rgb_color BColumnListView::EditColor() const +{ + return fColorList[B_COLOR_EDIT_BACKGROUND]; +} + +BPoint BColumnListView::SuggestTextPosition(const BRow* row, const BColumn* inColumn) const +{ + BRect rect; + GetRowRect(row, &rect); + if (inColumn) { + float leftEdge = MAX(kLeftMargin, LatchWidth()); + for (int index = 0; index < fColumns.CountItems(); index++) { + BColumn *column = (BColumn*) fColumns.ItemAt(index); + if (!column->IsVisible()) + continue; + + if (column == inColumn) { + rect.left = leftEdge; + rect.right = rect.left + column->Width(); + break; + } + + leftEdge += column->Width() + 1; + } + } + + font_height fh; + fOutlineView->GetFontHeight(&fh); + float baseline = floor(rect.top + fh.ascent + + (rect.Height()+1-(fh.ascent+fh.descent))/2); + return BPoint(rect.left + 8, baseline); +} + +void BColumnListView::SetLatchWidth(float width) +{ + fLatchWidth = width; + Invalidate(); +} + +float BColumnListView::LatchWidth() const +{ + return fLatchWidth; +} + +void BColumnListView::DrawLatch(BView *view, BRect rect, LatchType position, BRow *) +{ + const int32 rectInset = 4; + + view->SetHighColor(0, 0, 0); + + // Make Square + int32 sideLen = rect.IntegerWidth(); + if( sideLen > rect.IntegerHeight() ) + { + sideLen = rect.IntegerHeight(); + } + + // Make Center + int32 halfWidth = rect.IntegerWidth() / 2; + int32 halfHeight = rect.IntegerHeight() / 2; + int32 halfSide = sideLen / 2; + + float left = rect.left + halfWidth - halfSide; + float top = rect.top + halfHeight - halfSide; + + BRect itemRect(left, top, left + sideLen, top + sideLen); + + // Why it is a pixel high? I don't know. + itemRect.OffsetBy(0, -1); + + itemRect.InsetBy(rectInset, rectInset); + + // Make it an odd number of pixels wide, the latch looks better this way + if (1 == (itemRect.IntegerWidth() % 2)) + { + itemRect.right += 1; + itemRect.bottom += 1; + } + + switch (position) { + case B_OPEN_LATCH: + view->StrokeRect(itemRect); + view->StrokeLine(BPoint(itemRect.left + 2, (itemRect.top + itemRect.bottom) / 2), + BPoint(itemRect.right - 2, (itemRect.top + itemRect.bottom) / 2)); + break; + + case B_PRESSED_LATCH: + view->StrokeRect(itemRect); + view->StrokeLine(BPoint(itemRect.left + 2, (itemRect.top + itemRect.bottom) / 2), + BPoint(itemRect.right - 2, (itemRect.top + itemRect.bottom) / 2)); + view->StrokeLine(BPoint((itemRect.left + itemRect.right) / 2, itemRect.top + 2), + BPoint((itemRect.left + itemRect.right) / 2, itemRect.bottom - 2)); + view->InvertRect(itemRect); + break; + + case B_CLOSED_LATCH: + view->StrokeRect(itemRect); + view->StrokeLine(BPoint(itemRect.left + 2, (itemRect.top + itemRect.bottom) / 2), + BPoint(itemRect.right - 2, (itemRect.top + itemRect.bottom) / 2)); + view->StrokeLine(BPoint((itemRect.left + itemRect.right) / 2, itemRect.top + 2), + BPoint((itemRect.left + itemRect.right) / 2, itemRect.bottom - 2)); + break; + + case B_NO_LATCH: + // No drawing + break; + } +} + +void BColumnListView::MakeFocus(bool isFocus) +{ + Invalidate(); // Redraw focus marks around view + BView::MakeFocus(isFocus); +} + +void BColumnListView::MessageReceived(BMessage *message) +{ + // Propagate mouse wheel messages down to child, so that it can + // scroll. Note we have done so, so we don't go into infinite + // recursion if this comes back up here. + if (message->what == B_MOUSE_WHEEL_CHANGED) { + bool handled; + if (message->FindBool("be:clvhandled", &handled) != B_OK) { + message->AddBool("be:clvhandled", true); + fOutlineView->MessageReceived(message); + return; + } + } + BView::MessageReceived(message); +} + +void BColumnListView::KeyDown(const char *bytes, int32 numBytes) +{ + char c = bytes[0]; + switch (c) { + case B_RIGHT_ARROW: + case B_LEFT_ARROW: { + float minVal, maxVal; + fHorizontalScrollBar->GetRange(&minVal, &maxVal); + float smallStep, largeStep; + fHorizontalScrollBar->GetSteps(&smallStep, &largeStep); + float oldVal = fHorizontalScrollBar->Value(); + float newVal = oldVal; + + if (c == B_LEFT_ARROW) + newVal -= smallStep; + else if (c == B_RIGHT_ARROW) + newVal += smallStep; + + if (newVal < minVal) + newVal = minVal; + else if (newVal > maxVal) + newVal = maxVal; + + fHorizontalScrollBar->SetValue(newVal); + break; + } + + case B_DOWN_ARROW: + fOutlineView->ChangeFocusRow(false, (modifiers() & B_CONTROL_KEY) == 0, + (modifiers() & B_SHIFT_KEY) != 0); + break; + + case B_UP_ARROW: + fOutlineView->ChangeFocusRow(true, (modifiers() & B_CONTROL_KEY) == 0, + (modifiers() & B_SHIFT_KEY) != 0); + break; + + case B_PAGE_UP: + case B_PAGE_DOWN: { + float minValue, maxValue; + fVerticalScrollBar->GetRange(&minValue, &maxValue); + float smallStep, largeStep; + fVerticalScrollBar->GetSteps(&smallStep, &largeStep); + float currentValue = fVerticalScrollBar->Value(); + float newValue = currentValue; + + if (c == B_PAGE_UP) + newValue -= largeStep; + else + newValue += largeStep; + + if (newValue > maxValue) + newValue = maxValue; + else if (newValue < minValue) + newValue = minValue; + + fVerticalScrollBar->SetValue(newValue); + + // Option + pgup or pgdn scrolls and changes the selection. + if (modifiers() & B_OPTION_KEY) + fOutlineView->MoveFocusToVisibleRect(); + + break; + } + + case B_ENTER: + Invoke(); + break; + + case B_SPACE: + fOutlineView->ToggleFocusRowSelection((modifiers() & B_SHIFT_KEY) != 0); + break; + + case '+': + fOutlineView->ToggleFocusRowOpen(); + break; + + default: + BView::KeyDown(bytes, numBytes); + } +} + +void BColumnListView::AttachedToWindow() +{ + if (!Messenger().IsValid()) + SetTarget(Window()); + + if (SortingEnabled()) fOutlineView->StartSorting(); +} + +void BColumnListView::WindowActivated(bool active) +{ + fOutlineView->Invalidate(); + // Focus and selection appearance changes with focus + + Invalidate(); // Redraw focus marks around view + BView::WindowActivated(active); +} + +void BColumnListView::Draw(BRect) +{ + BRect rect = Bounds(); + PushState(); + + BRect cornerRect(rect.right - B_V_SCROLL_BAR_WIDTH, rect.bottom - B_H_SCROLL_BAR_HEIGHT, + rect.right, rect.bottom); + if (fBorderStyle == B_PLAIN_BORDER) { + BView::SetHighColor(0, 0, 0); + StrokeRect(rect); + cornerRect.OffsetBy(-1, -1); + } else if (fBorderStyle == B_FANCY_BORDER) { + bool isFocus = IsFocus() && Window()->IsActive(); + + if (isFocus) + BView::SetHighColor(0, 0, 190); // Need to find focus color programatically + else + BView::SetHighColor(255, 255, 255); + + StrokeRect(rect); + if (!isFocus) + BView::SetHighColor(184, 184, 184); + else + BView::SetHighColor(152, 152, 152); + + rect.InsetBy(1,1); + StrokeRect(rect); + cornerRect.OffsetBy(-2, -2); + } + + BView::SetHighColor(215, 215, 215); // fills lower right rect between scroll bars + FillRect(cornerRect); + PopState(); +} + +void BColumnListView::SaveState(BMessage *msg) +{ + msg->MakeEmpty(); + + // Damn compiler issuing l43m incorrect warnings. + int i; + for (i = 0; ;i++) + { + BColumn *col = (BColumn*) fColumns.ItemAt(i); + if(!col) + break; + msg->AddInt32("ID",col->fFieldID); + msg->AddFloat("width",col->fWidth); + msg->AddBool("visible",col->fVisible); + } + + msg->AddBool("sortingenabled",fSortingEnabled); + + if(fSortingEnabled) + { + for (i = 0; ;i++) + { + BColumn *col = (BColumn*) fSortColumns.ItemAt(i); + if(!col) + break; + msg->AddInt32("sortID",col->fFieldID); + msg->AddBool("sortascending",col->fSortAscending); + } + } +} + +void BColumnListView::LoadState(BMessage *msg) +{ + for(int i=0;;i++) + { + int32 ID; + if(B_OK!=msg->FindInt32("ID",i,&ID)) + break; + for(int j=0;;j++) + { + BColumn *col = (BColumn*) fColumns.ItemAt(j); + if(!col) + break; + if(col->fFieldID==ID) + { + // move this column to position 'i' and set its attributes + MoveColumn(col,i); + float f; + if(B_OK==msg->FindFloat("width",i,&f)) + col->SetWidth(f); + bool b; + if(B_OK==msg->FindBool("visible",i,&b)) + col->SetVisible(b); + } + } + } + bool b; + if(B_OK==msg->FindBool("sortingenabled",&b)) + { + SetSortingEnabled(b); + // Damn compiler issuing l43m incorrect warnings. + for(int k=0;;k++) + { + int32 ID; + if(B_OK!=msg->FindInt32("sortID", k, &ID)) + break; + for(int j=0;;j++) + { + BColumn *col = (BColumn*) fColumns.ItemAt(j); + if(!col) + break; + if(col->fFieldID==ID) + { + // add this column to the sort list + bool val; + if(B_OK==msg->FindBool("sortascending", k, &val)) + SetSortColumn(col, true, val); + } + } + } + } +} + +void BColumnListView::SetEditMode(bool state) +{ + fOutlineView->SetEditMode(state); + fTitleView->SetEditMode(state); +} + +void BColumnListView::Refresh() +{ + if(LockLooper()) + { + Invalidate(); + fOutlineView->FixScrollBar (true); + fOutlineView->Invalidate(); + Window()->UpdateIfNeeded(); + UnlockLooper(); + } +} + +// #pragma mark - + + +TitleView::TitleView(BRect rect, OutlineView *horizontalSlave, BList *visibleColumns, + BList *sortColumns, BColumnListView *listView, uint32 resizingMode) + : BView(rect, "title_view", resizingMode, B_WILL_DRAW | B_FRAME_EVENTS), + fOutlineView(horizontalSlave), + fColumns(visibleColumns), + fSortColumns(sortColumns), + fColumnsWidth(0), + fVisibleRect(rect.OffsetToCopy(0, 0)), + fCurrentState(INACTIVE), + fColumnPop(NULL), + fMasterView(listView), + fEditMode(false), + fColumnFlags(B_ALLOW_COLUMN_MOVE|B_ALLOW_COLUMN_RESIZE|B_ALLOW_COLUMN_POPUP|B_ALLOW_COLUMN_REMOVE) +{ + SetViewColor(B_TRANSPARENT_32_BIT); + +#if DOUBLE_BUFFERED_COLUMN_RESIZE + // xxx this needs to be smart about the size of the backbuffer. + BRect doubleBufferRect(0, 0, 600, 35); + fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true); + fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view", + B_FOLLOW_ALL_SIDES, 0); + fDrawBuffer->Lock(); + fDrawBuffer->AddChild(fDrawBufferView); + fDrawBuffer->Unlock(); +#endif + + fUpSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_COLOR_8_BIT); + fDownSortArrow = new BBitmap(BRect(0, 0, 7, 7), B_COLOR_8_BIT); + + fUpSortArrow->SetBits((const void*) kUpSortArrow8x8, 64, 0, B_COLOR_8_BIT); + fDownSortArrow->SetBits((const void*) kDownSortArrow8x8, 64, 0, B_COLOR_8_BIT); + +#if _INCLUDES_CLASS_CURSOR + fResizeCursor = new BCursor(kResizeCursorData); + fMinResizeCursor = new BCursor(kMinResizeCursorData); + fMaxResizeCursor = new BCursor(kMaxResizeCursorData); + fColumnMoveCursor = new BCursor(kColumnMoveCursorData); +#endif + FixScrollBar(true); +} + +TitleView::~TitleView() +{ + delete fColumnPop; + fColumnPop = NULL; + + fDrawBuffer->Lock(); + fDrawBufferView->RemoveSelf(); + fDrawBuffer->Unlock(); + delete fDrawBufferView; + delete fDrawBuffer; + delete fUpSortArrow; + delete fDownSortArrow; + +#if _INCLUDES_CLASS_CURSOR + delete fResizeCursor; + delete fMaxResizeCursor; + delete fMinResizeCursor; + delete fColumnMoveCursor; +#endif +} + +void TitleView::ColumnAdded(BColumn *column) +{ + fColumnsWidth += column->Width() + 1; + FixScrollBar(false); + Invalidate(); +} +// Could use a CopyBits here. +void TitleView::SetColumnVisible(BColumn *column, bool visible) +{ + if (column->fVisible == visible) + return; + + // If setting it visible, do this first so we can find its position + // to invalidate. If hiding it, do it last. + if (visible) + column->fVisible = visible; + + BRect titleInvalid; + GetTitleRect(column, &titleInvalid); + + // Now really set the visibility + column->fVisible = visible; + + if (visible) + fColumnsWidth += column->Width(); + else + fColumnsWidth -= column->Width(); + + BRect outlineInvalid(fOutlineView->VisibleRect()); + outlineInvalid.left = titleInvalid.left; + titleInvalid.right = outlineInvalid.right; + + Invalidate(titleInvalid); + fOutlineView->Invalidate(outlineInvalid); +} + +void TitleView::GetTitleRect(BColumn *findColumn, BRect *out_rect) +{ + float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + int32 numColumns = fColumns->CountItems(); + for (int index = 0; index < numColumns; index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column->IsVisible()) + continue; + + if (column == findColumn) { + out_rect->Set(leftEdge, 0, leftEdge + column->Width(), fVisibleRect.bottom); + return; + } + + leftEdge += column->Width() + 1; + } + + TRESPASS(); +} + +int32 TitleView::FindColumn(BPoint position, float *out_leftEdge) +{ + float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + int32 numColumns = fColumns->CountItems(); + for (int index = 0; index < numColumns; index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column->IsVisible()) + continue; + + if (leftEdge > position.x) + break; + + if (position.x >= leftEdge && position.x <= leftEdge + column->Width()) { + *out_leftEdge = leftEdge; + return index; + } + + leftEdge += column->Width() + 1; + } + + return 0; +} + +void TitleView::FixScrollBar(bool scrollToFit) +{ + BScrollBar *hScrollBar = ScrollBar(B_HORIZONTAL); + if (hScrollBar) { + float virtualWidth = fColumnsWidth + MAX(kLeftMargin, fMasterView->LatchWidth()) + + kRightMargin * 2; + + if (virtualWidth > fVisibleRect.Width()) { + hScrollBar->SetProportion(fVisibleRect.Width() / virtualWidth); + + // Perform the little trick if the user is scrolled over too far. + // See OutlineView::FixScrollBar for a more in depth explanation + float maxScrollBarValue = virtualWidth - fVisibleRect.Width(); + if (scrollToFit || hScrollBar->Value() <= maxScrollBarValue) { + hScrollBar->SetRange(0.0, maxScrollBarValue); + hScrollBar->SetSteps(50, fVisibleRect.Width()); + } + } else if (hScrollBar->Value() == 0.0) + hScrollBar->SetRange(0.0, 0.0); // disable scroll bar. + } +} + +void TitleView::DragSelectedColumn(BPoint position) +{ + float invalidLeft = fSelectedColumnRect.left; + float invalidRight = fSelectedColumnRect.right; + + float leftEdge; + int32 columnIndex = FindColumn(position, &leftEdge); + fSelectedColumnRect.OffsetTo(leftEdge, 0); + + MoveColumn(fSelectedColumn, columnIndex); + + fSelectedColumn->fVisible = true; + ComputeDragBoundries(fSelectedColumn, position); + + // Redraw the new column position + GetTitleRect(fSelectedColumn, &fSelectedColumnRect); + invalidLeft = MIN(fSelectedColumnRect.left, invalidLeft); + invalidRight = MAX(fSelectedColumnRect.right, invalidRight); + + Invalidate(BRect(invalidLeft, 0, invalidRight, fVisibleRect.bottom)); + fOutlineView->Invalidate(BRect(invalidLeft, 0, invalidRight, + fOutlineView->VisibleRect().bottom)); + + DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true); +} + +void TitleView::MoveColumn(BColumn *column, int32 index) +{ + fColumns->RemoveItem((void*) column); + + if (-1 == index) + { + // Re-add the column at the end of the list. + fColumns->AddItem((void*) column); + } + else + { + fColumns->AddItem((void*) column, index); + } +} + +void TitleView::SetColumnFlags(column_flags flags) +{ + fColumnFlags = flags; +} + + +void TitleView::ResizeSelectedColumn(BPoint position) +{ + float minWidth = fSelectedColumn->MinWidth(); + float maxWidth = fSelectedColumn->MaxWidth(); + + float originalEdge = fSelectedColumnRect.left + fSelectedColumn->Width(); + if (position.x > fSelectedColumnRect.left + maxWidth) + fSelectedColumn->SetWidth(maxWidth); + else if (position.x < fSelectedColumnRect.left + minWidth) + fSelectedColumn->SetWidth(minWidth); + else + fSelectedColumn->SetWidth(position.x - fSelectedColumnRect.left - 1); + + float dX = fSelectedColumnRect.left + fSelectedColumn->Width() - originalEdge; + if (dX != 0) { + BRect originalRect(originalEdge, 0, 1000000.0, fVisibleRect.Height()); + BRect movedRect(originalRect); + movedRect.OffsetBy(dX, 0); + + // Update the size of the title column + BRect sourceRect(0, 0, fSelectedColumn->Width(), fVisibleRect.Height()); + BRect destRect(sourceRect); + destRect.OffsetBy(fSelectedColumnRect.left, 0); + +#if DOUBLE_BUFFERED_COLUMN_RESIZE + fDrawBuffer->Lock(); + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_HEADER_BACKGROUND)); + fDrawBufferView->FillRect(sourceRect); + DrawTitle(fDrawBufferView, sourceRect, fSelectedColumn, false); + fDrawBufferView->Sync(); + fDrawBuffer->Unlock(); + + CopyBits(originalRect, movedRect); + DrawBitmap(fDrawBuffer, sourceRect, destRect); +#else + CopyBits(originalRect, movedRect); + SetHighColor(fMasterView->Color(B_COLOR_HEADER_BACKGROUND)); + FillRect(destRect); + DrawTitle(this, destRect, fSelectedColumn, false); +#endif + + // Update the body view + BRect slaveSize = fOutlineView->VisibleRect(); + BRect slaveSource(originalRect); + slaveSource.bottom = slaveSize.bottom; + BRect slaveDest(movedRect); + slaveDest.bottom = slaveSize.bottom; + fOutlineView->CopyBits(slaveSource, slaveDest); + fOutlineView->RedrawColumn(fSelectedColumn, fSelectedColumnRect.left, + fResizingFirstColumn); + + fColumnsWidth += dX; + + // Update the cursor +#if _INCLUDES_CLASS_CURSOR + if (fSelectedColumn->Width() == minWidth) + SetViewCursor(fMinResizeCursor, false); + else if (fSelectedColumn->Width() == maxWidth) + SetViewCursor(fMaxResizeCursor, false); + else + SetViewCursor(fResizeCursor, false); +#endif + } +} + +void TitleView::ComputeDragBoundries(BColumn *findColumn, BPoint ) +{ + float previousColumnLeftEdge = -1000000.0; + float nextColumnRightEdge = 1000000.0; + + bool foundColumn = false; + float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + int32 numColumns = fColumns->CountItems(); + for (int index = 0; index < numColumns; index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column->IsVisible()) + continue; + + if (column == findColumn) { + foundColumn = true; + continue; + } + + if (foundColumn) { + nextColumnRightEdge = leftEdge + column->Width(); + break; + } else + previousColumnLeftEdge = leftEdge; + + leftEdge += column->Width() + 1; + } + + float rightEdge = leftEdge + findColumn->Width(); + + fLeftDragBoundry = MIN(previousColumnLeftEdge + findColumn->Width(), leftEdge); + fRightDragBoundry = MAX(nextColumnRightEdge, rightEdge); +} + +void TitleView::DrawTitle(BView *view, BRect rect, BColumn *column, bool depressed) +{ + BRect drawRect; + rgb_color borderColor = mix_color(fMasterView->Color(B_COLOR_HEADER_BACKGROUND), make_color(0, 0, 0), 128); + rgb_color backgroundColor; + + rgb_color bevelHigh; + rgb_color bevelLow; + // Want exterior borders to overlap. + rect.right += 1; + drawRect = rect; + drawRect.InsetBy(2, 2); + if (depressed) { + backgroundColor = mix_color(fMasterView->Color(B_COLOR_HEADER_BACKGROUND), make_color(0, 0, 0), 64); + bevelHigh = mix_color(backgroundColor, make_color(0, 0, 0), 64); + bevelLow = mix_color(backgroundColor, make_color(255, 255, 255), 128); + drawRect.left++; + drawRect.top++; + } else { + backgroundColor = fMasterView->Color(B_COLOR_HEADER_BACKGROUND); + bevelHigh = mix_color(backgroundColor, make_color(255, 255, 255), 192); + bevelLow = mix_color(backgroundColor, make_color(0, 0, 0), 64); + drawRect.bottom--; + drawRect.right--; + } + + view->SetHighColor(borderColor); + view->StrokeRect(rect); + view->BeginLineArray(4); + view->AddLine(BPoint(rect.left+1, rect.top+1), BPoint(rect.right-1, rect.top+1), bevelHigh); + view->AddLine(BPoint(rect.left+1, rect.top+1), BPoint(rect.left+1, rect.bottom-1), bevelHigh); + view->AddLine(BPoint(rect.right-1, rect.top+1), BPoint(rect.right-1, rect.bottom-1), bevelLow); + view->AddLine(BPoint(rect.left+2, rect.bottom-1), BPoint(rect.right-1, rect.bottom-1), bevelLow); + view->EndLineArray(); + + font_height fh; + GetFontHeight(&fh); + + float baseline = floor(drawRect.top + fh.ascent + + (drawRect.Height()+1-(fh.ascent+fh.descent))/2); + + view->SetHighColor(backgroundColor); + view->SetLowColor(backgroundColor); + + view->FillRect(rect.InsetByCopy(2, 2)); + + + // If no column given, nothing else to draw. + if (!column) + return; + + view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT)); + + BFont font; + GetFont(&font); + view->SetFont(&font); + + int sortIndex = fSortColumns->IndexOf(column); + if (sortIndex >= 0) { + // Draw sort notation. + BPoint upperLeft(drawRect.right-kSortIndicatorWidth, baseline); + + if (fSortColumns->CountItems() > 1) { + char str[256]; + sprintf(str, "%d", sortIndex + 1); + const float w = view->StringWidth(str); + upperLeft.x -= w; + + view->SetDrawingMode(B_OP_COPY); + view->MovePenTo(BPoint(upperLeft.x + kSortIndicatorWidth, baseline)); + view->DrawString(str); + } + + float bmh = fDownSortArrow->Bounds().Height()+1; + + view->SetDrawingMode(B_OP_OVER); + + if (column->fSortAscending) { + BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()-fDownSortArrow->Bounds().IntegerHeight())/2); + view->DrawBitmapAsync(fDownSortArrow, leftTop); + } else { + BPoint leftTop(upperLeft.x, drawRect.top + (drawRect.IntegerHeight()-fUpSortArrow->Bounds().IntegerHeight())/2); + view->DrawBitmapAsync(fUpSortArrow, leftTop); + } + + upperLeft.y = baseline-bmh+floor((fh.ascent+fh.descent-bmh)/2); + if (upperLeft.y < drawRect.top) upperLeft.y = drawRect.top; + + // Adjust title stuff for sort indicator + drawRect.right = upperLeft.x - 2; + } + + if (drawRect.right > drawRect.left) { +#if CONSTRAIN_CLIPPING_REGION + BRegion clipRegion; + clipRegion.Set(drawRect); + view->ConstrainClippingRegion(&clipRegion); + view->PushState(); +#endif + view->MovePenTo(BPoint(drawRect.left+8, baseline)); + view->SetDrawingMode(B_OP_COPY); + view->SetHighColor(fMasterView->Color(B_COLOR_HEADER_TEXT)); + column->DrawTitle(drawRect, view); + +#if CONSTRAIN_CLIPPING_REGION + view->PopState(); + view->ConstrainClippingRegion(NULL); +#endif + } +} + +void TitleView::Draw(BRect invalidRect) +{ + float columnLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + for (int32 columnIndex = 0; columnIndex < fColumns->CountItems(); columnIndex++) { + BColumn *column = (BColumn*) fColumns->ItemAt(columnIndex); + if (!column->IsVisible()) + continue; + + if (columnLeftEdge > invalidRect.right) + break; + + if (columnLeftEdge + column->Width() >= invalidRect.left) { + BRect titleRect(columnLeftEdge, 0, + columnLeftEdge + column->Width(), fVisibleRect.Height()); + DrawTitle(this, titleRect, column, + (fCurrentState == DRAG_COLUMN_INSIDE_TITLE && fSelectedColumn == column)); + } + + columnLeftEdge += column->Width() + 1; + } + + + // Bevels for right title margin + if (columnLeftEdge <= invalidRect.right) { + BRect titleRect(columnLeftEdge, 0, Bounds().right+2, fVisibleRect.Height()); + DrawTitle(this, titleRect, NULL, false); + } + + // Bevels for left title margin + if (invalidRect.left < MAX(kLeftMargin, fMasterView->LatchWidth())) { + BRect titleRect(0, 0, MAX(kLeftMargin, fMasterView->LatchWidth()) - 1, fVisibleRect.Height()); + DrawTitle(this, titleRect, NULL, false); + } + +#if DRAG_TITLE_OUTLINE + // (Internal) Column Drag Indicator + if (fCurrentState == DRAG_COLUMN_INSIDE_TITLE) { + BRect dragRect(fSelectedColumnRect); + dragRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0); + if (dragRect.Intersects(invalidRect)) { + SetHighColor(0, 0, 255); + StrokeRect(dragRect); + } + } +#endif +} + +void TitleView::ScrollTo(BPoint position) +{ + fOutlineView->ScrollBy(position.x - fVisibleRect.left, 0); + fVisibleRect.OffsetTo(position.x, position.y); + + // Perform the little trick if the user is scrolled over too far. + // See OutlineView::ScrollTo for a more in depth explanation + float maxScrollBarValue = fColumnsWidth + MAX(kLeftMargin, fMasterView->LatchWidth()) + + kRightMargin * 2 - fVisibleRect.Width(); + BScrollBar *hScrollBar = ScrollBar(B_HORIZONTAL); + float min, max; + hScrollBar->GetRange(&min, &max); + if (max != maxScrollBarValue && position.x > maxScrollBarValue) + FixScrollBar(true); + + _inherited::ScrollTo(position); +} + +void TitleView::MessageReceived(BMessage *message) +{ + if (message->what == kToggleColumn) { + int32 num; + if (message->FindInt32("be:field_num", &num) == B_OK) { + for (int index = 0; index < fColumns->CountItems(); index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column) continue; + if (column->LogicalFieldNum() == num) { + column->SetVisible(!column->IsVisible()); + } + } + } + return; + } else { + BView::MessageReceived(message); + } +} + +void TitleView::MouseDown(BPoint position) +{ + if(fEditMode) + return; + + int32 buttons=1; + Window()->CurrentMessage()->FindInt32("buttons", &buttons); + if (buttons == B_SECONDARY_MOUSE_BUTTON && (fColumnFlags & B_ALLOW_COLUMN_POPUP)) { + // Right mouse button -- bring up menu to show/hide columns. + if (!fColumnPop) fColumnPop = new BPopUpMenu("Columns", false, false); + fColumnPop->RemoveItems(0, fColumnPop->CountItems(), true); + BMessenger me(this); + for (int index = 0; index < fColumns->CountItems(); index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column) continue; + BString name; + column->GetColumnName(&name); + BMessage* msg = new BMessage(kToggleColumn); + msg->AddInt32("be:field_num", column->LogicalFieldNum()); + BMenuItem* it = new BMenuItem(name.String(), msg); + it->SetMarked(column->IsVisible()); + it->SetTarget(me); + fColumnPop->AddItem(it); + } + BPoint screenPosition = ConvertToScreen(position); + BRect sticky(screenPosition, screenPosition); + sticky.InsetBy(-5, -5); + fColumnPop->Go(ConvertToScreen(position), true, false, sticky, true); + return; + } + + fResizingFirstColumn = true; + float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + for (int index = 0; index < fColumns->CountItems(); index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column->IsVisible()) + continue; + + if (leftEdge > position.x + kColumnResizeAreaWidth / 2) + break; + + // Check for resizing a column + float rightEdge = leftEdge + column->Width(); + + if(column->ShowHeading()) { + if (position.x > rightEdge - kColumnResizeAreaWidth / 2 + && position.x < rightEdge + kColumnResizeAreaWidth / 2 + && column->MaxWidth() > column->MinWidth() + && (fColumnFlags & B_ALLOW_COLUMN_RESIZE)) { + fCurrentState = RESIZING_COLUMN; + fSelectedColumn = column; + fSelectedColumnRect.Set(leftEdge, 0, rightEdge, fVisibleRect.Height()); + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | + B_NO_POINTER_HISTORY); + break; + } + + fResizingFirstColumn = false; + + // Check for clicking on a column. + if (position.x > leftEdge && position.x < rightEdge) { + fCurrentState = PRESSING_COLUMN; + fSelectedColumn = column; + fSelectedColumnRect.Set(leftEdge, 0, rightEdge, fVisibleRect.Height()); + DrawTitle(this, fSelectedColumnRect, fSelectedColumn, true); + fClickPoint = BPoint(position.x - fSelectedColumnRect.left, position.y + - fSelectedColumnRect.top); + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | + B_NO_POINTER_HISTORY); + break; + } + } + leftEdge = rightEdge + 1; + } +} + +void TitleView::MouseMoved(BPoint position, uint32 transit, const BMessage *) +{ + + + if(fEditMode) + return; + + + // Handle column manipulation + switch (fCurrentState) { + case RESIZING_COLUMN: + ResizeSelectedColumn(position); + break; + + case PRESSING_COLUMN: { + if (abs((int32)(position.x - (fClickPoint.x + fSelectedColumnRect.left))) > kColumnResizeAreaWidth + || abs((int32)(position.y - (fClickPoint.y + fSelectedColumnRect.top))) > kColumnResizeAreaWidth) { + // User has moved the mouse more than the tolerable amount, + // initiate a drag. + if (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) { + if(fColumnFlags & B_ALLOW_COLUMN_MOVE) { + fCurrentState = DRAG_COLUMN_INSIDE_TITLE; + ComputeDragBoundries(fSelectedColumn, position); +#if _INCLUDES_CLASS_CURSOR + SetViewCursor(fColumnMoveCursor, false); +#endif +#if DRAG_TITLE_OUTLINE + BRect invalidRect(fSelectedColumnRect); + invalidRect.OffsetTo(position.x - fClickPoint.x, 0); + fCurrentDragPosition = position; + Invalidate(invalidRect); +#endif + } + } else { + if(fColumnFlags & B_ALLOW_COLUMN_REMOVE) { + // Dragged outside view + fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE; + fSelectedColumn->SetVisible(false); + BRect dragRect(fSelectedColumnRect); + + // There is a race condition where the mouse may have moved by the + // time we get to handle this message. If the user drags a column very + // quickly, this results in the annoying bug where the cursor is outside + // of the rectangle that is being dragged around. + // Call GetMouse with the checkQueue flag set to false so we + // can get the most recent position of the mouse. This minimizes + // this problem (although it is currently not possible to completely + // eliminate it). + uint32 buttons; + GetMouse(&position, &buttons, false); + dragRect.OffsetTo(position.x - fClickPoint.x, position.y - dragRect.Height() / 2); + BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT); + } + } + } + + break; + } + + case DRAG_COLUMN_INSIDE_TITLE: { + if (transit == B_EXITED_VIEW && (fColumnFlags & B_ALLOW_COLUMN_REMOVE)) { + // Dragged outside view + fCurrentState = DRAG_COLUMN_OUTSIDE_TITLE; + fSelectedColumn->SetVisible(false); + BRect dragRect(fSelectedColumnRect); + + // See explanation above. + uint32 buttons; + GetMouse(&position, &buttons, false); + + dragRect.OffsetTo(position.x - fClickPoint.x, position.y - fClickPoint.y); + BeginRectTracking(dragRect, B_TRACK_WHOLE_RECT); + } else if (position.x < fLeftDragBoundry || position.x > fRightDragBoundry) + DragSelectedColumn(position); + +#if DRAG_TITLE_OUTLINE + // Set up the invalid rect to include the rect for the previous position + // of the drag rect, as well as the new one. + BRect invalidRect(fSelectedColumnRect); + invalidRect.OffsetTo(fCurrentDragPosition.x - fClickPoint.x, 0); + if (position.x < fCurrentDragPosition.x) + invalidRect.left -= fCurrentDragPosition.x - position.x; + else + invalidRect.right += position.x - fCurrentDragPosition.x; + + fCurrentDragPosition = position; + Invalidate(invalidRect); +#endif + break; + } + + case DRAG_COLUMN_OUTSIDE_TITLE: + if (transit == B_ENTERED_VIEW) { + // Drag back into view + EndRectTracking(); + fCurrentState = DRAG_COLUMN_INSIDE_TITLE; + fSelectedColumn->SetVisible(true); + DragSelectedColumn(position); + } + + break; + + case INACTIVE: + // Check for cursor changes if we are over the resize area for + // a column. + BColumn *resizeColumn = 0; + float leftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + for (int index = 0; index < fColumns->CountItems(); index++) { + BColumn *column = (BColumn*) fColumns->ItemAt(index); + if (!column->IsVisible()) + continue; + + if (leftEdge > position.x + kColumnResizeAreaWidth / 2) + break; + + float rightEdge = leftEdge + column->Width(); + if (position.x > rightEdge - kColumnResizeAreaWidth / 2 + && position.x < rightEdge + kColumnResizeAreaWidth / 2 + && column->MaxWidth() > column->MinWidth()) { + resizeColumn = column; + break; + } + + leftEdge = rightEdge + 1; + } + + // Update the cursor +#if _INCLUDES_CLASS_CURSOR + if (resizeColumn) { + if (resizeColumn->Width() == resizeColumn->MinWidth()) + SetViewCursor(fMinResizeCursor, false); + else if (resizeColumn->Width() == resizeColumn->MaxWidth()) + SetViewCursor(fMaxResizeCursor, false); + else + SetViewCursor(fResizeCursor, false); + } else + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false); +#endif + break; + } +} + +void TitleView::MouseUp(BPoint position) +{ + if(fEditMode) + return; + + switch (fCurrentState) { + case RESIZING_COLUMN: + ResizeSelectedColumn(position); + fCurrentState = INACTIVE; + FixScrollBar(false); +#if _INCLUDES_CLASS_CURSOR + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false); +#endif + break; + + case PRESSING_COLUMN: { + if (fMasterView->SortingEnabled()) { + if (fSortColumns->HasItem(fSelectedColumn)) { + if ((modifiers() & B_CONTROL_KEY) == 0 && fSortColumns->CountItems() > 1) { + fSortColumns->MakeEmpty(); + fSortColumns->AddItem(fSelectedColumn); + } + + fSelectedColumn->fSortAscending = !fSelectedColumn->fSortAscending; + } else { + if ((modifiers() & B_CONTROL_KEY) == 0) + fSortColumns->MakeEmpty(); + + fSortColumns->AddItem(fSelectedColumn); + fSelectedColumn->fSortAscending = true; + } + + fOutlineView->StartSorting(); + } + + fCurrentState = INACTIVE; + Invalidate(); + break; + } + + case DRAG_COLUMN_INSIDE_TITLE: + fCurrentState = INACTIVE; + +#if DRAG_TITLE_OUTLINE + Invalidate(); // xxx Can make this smaller +#else + Invalidate(fSelectedColumnRect); +#endif +#if _INCLUDES_CLASS_CURSOR + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false); +#endif + break; + + case DRAG_COLUMN_OUTSIDE_TITLE: + fCurrentState = INACTIVE; + EndRectTracking(); +#if _INCLUDES_CLASS_CURSOR + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, false); +#endif + break; + + default: + ; + } +} + +void TitleView::FrameResized(float width, float height) +{ + fVisibleRect.right = fVisibleRect.left + width; + fVisibleRect.bottom = fVisibleRect.top + height; + FixScrollBar(true); +} + +// #pragma mark - + +OutlineView::OutlineView(BRect rect, BList *visibleColumns, BList *sortColumns, + BColumnListView *listView) + : BView(rect, "outline_view", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS), + fColumns(visibleColumns), + fSortColumns(sortColumns), + fItemsHeight(24.), + fVisibleRect(rect.OffsetToCopy(0, 0)), + fFocusRow(0), + fRollOverRow(0), + fLastSelectedItem(0), + fFirstSelectedItem(0), + fSortThread(B_BAD_THREAD_ID), + fCurrentState(INACTIVE), + fMasterView(listView), + fSelectionMode(B_MULTIPLE_SELECTION_LIST), + fTrackMouse(false), + fCurrentField(0), + fCurrentRow(0), + fCurrentColumn(0), + fMouseDown(false), + fCurrentCode(B_OUTSIDE_VIEW), + fEditMode(false), + fDragging(false), + fClickCount(0), + fDropHighlightY(-1) +{ + SetViewColor(B_TRANSPARENT_32_BIT); + +#if DOUBLE_BUFFERED_COLUMN_RESIZE + // xxx this needs to be smart about the size of the buffer. Also, the buffer can + // be shared with the title's buffer. + BRect doubleBufferRect(0, 0, 600, 35); + fDrawBuffer = new BBitmap(doubleBufferRect, B_RGB32, true); + fDrawBufferView = new BView(doubleBufferRect, "double_buffer_view", + B_FOLLOW_ALL_SIDES, 0); + fDrawBuffer->Lock(); + fDrawBuffer->AddChild(fDrawBufferView); + fDrawBuffer->Unlock(); +#endif + + FixScrollBar(true); + fSelectionListDummyHead.fNextSelected = &fSelectionListDummyHead; + fSelectionListDummyHead.fPrevSelected = &fSelectionListDummyHead; +} + +OutlineView::~OutlineView() +{ + fDrawBuffer->Lock(); + fDrawBufferView->RemoveSelf(); + fDrawBuffer->Unlock(); + delete fDrawBufferView; + delete fDrawBuffer; + + Clear(); +} + +void OutlineView::Clear() +{ + DeselectAll(); // Make sure selection list doesn't point to deleted rows! + RecursiveDeleteRows(&fRows, false); + Invalidate(); + fItemsHeight = 24.; + FixScrollBar(true); +} + +void OutlineView::SetSelectionMode(list_view_type mode) +{ + DeselectAll(); + fSelectionMode = mode; +} + +list_view_type OutlineView::SelectionMode() const +{ + return fSelectionMode; +} + +void OutlineView::Deselect(BRow *row) +{ + if (row) { + if (row->fNextSelected != 0) { + row->fNextSelected->fPrevSelected = row->fPrevSelected; + row->fPrevSelected->fNextSelected = row->fNextSelected; + row->fNextSelected = 0; + row->fPrevSelected = 0; + Invalidate(); + } + } +} + +void OutlineView::AddToSelection(BRow *row) +{ + if (row) { + if (row->fNextSelected == 0) { + if (fSelectionMode == B_SINGLE_SELECTION_LIST) + DeselectAll(); + + row->fNextSelected = fSelectionListDummyHead.fNextSelected; + row->fPrevSelected = &fSelectionListDummyHead; + row->fNextSelected->fPrevSelected = row; + row->fPrevSelected->fNextSelected = row; + + BRect invalidRect; + if (FindVisibleRect(row, &invalidRect)) + Invalidate(invalidRect); + } + } +} + +void OutlineView::RecursiveDeleteRows(BRowContainer* List, bool IsOwner) +{ + if (List) { + while (true) { + BRow *row = List->RemoveItemAt(0L); + if (row == 0) + break; + + if (row->fChildList) + RecursiveDeleteRows(row->fChildList, true); + + delete row; + } + + if (IsOwner) + delete List; + } +} + + +void OutlineView::RedrawColumn(BColumn *column, float leftEdge, bool isFirstColumn) +{ + if (column) { + font_height fh; + GetFontHeight(&fh); + float line = 0.0; + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + line += iterator.CurrentRow()->Height() + 1, iterator.GoToNext()) { + BRow *row = iterator.CurrentRow(); + float rowHeight = row->Height(); + if (line > fVisibleRect.bottom) + break; + + if (line + rowHeight >= fVisibleRect.top) { + BRect sourceRect(0, 0, column->Width(), rowHeight); + BRect destRect(leftEdge, line, leftEdge + column->Width(), line + rowHeight); + + #if DOUBLE_BUFFERED_COLUMN_RESIZE + fDrawBuffer->Lock(); + if (row->fNextSelected != 0) { + if(fEditMode) { + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + } else { + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_SELECTION)); + } + } else { + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + fDrawBufferView->SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND)); + } + BFont font; + GetFont(&font); + fDrawBufferView->SetFont(&font); + fDrawBufferView->FillRect(sourceRect); + + if (isFirstColumn) { + // If this is the first column, double buffer drawing the latch too. + destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent + - fMasterView->LatchWidth(); + sourceRect.left += iterator.CurrentLevel() * kOutlineLevelIndent + - fMasterView->LatchWidth(); + + LatchType pos = B_NO_LATCH; + if (row->HasLatch()) + pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH; + + BRect latchRect(sourceRect); + latchRect.right = latchRect.left + fMasterView->LatchWidth(); + fMasterView->DrawLatch(fDrawBufferView, latchRect, pos, row); + } + + BField *field = row->GetField(column->fFieldID); + if (field) { + BRect fieldRect(sourceRect); + if (isFirstColumn) + fieldRect.left += fMasterView->LatchWidth(); + + #if CONSTRAIN_CLIPPING_REGION + BRegion clipRegion; + clipRegion.Set(fieldRect); + fDrawBufferView->ConstrainClippingRegion(&clipRegion); + fDrawBufferView->PushState(); + #endif + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_TEXT)); + float baseline = floor(fieldRect.top + fh.ascent + + (fieldRect.Height()+1-(fh.ascent+fh.descent))/2); + fDrawBufferView->MovePenTo(fieldRect.left + 8, baseline); + column->DrawField(field, fieldRect, fDrawBufferView); + #if CONSTRAIN_CLIPPING_REGION + fDrawBufferView->PopState(); + fDrawBufferView->ConstrainClippingRegion(NULL); + #endif + } + + if (fFocusRow == row) { + if(!fEditMode) { + fDrawBufferView->SetHighColor(0, 0, 0); + fDrawBufferView->StrokeRect(BRect(-1, sourceRect.top, 10000.0, sourceRect.bottom - 1)); + } + } + + fDrawBufferView->SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER)); + // StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2)); + // StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1)); + fDrawBufferView->StrokeLine(BPoint(0, rowHeight), BPoint(Bounds().Width(), rowHeight)); + + fDrawBufferView->Sync(); + fDrawBuffer->Unlock(); + SetDrawingMode(B_OP_OVER); + DrawBitmap(fDrawBuffer, sourceRect, destRect); + + #else + + if (row->fNextSelected != 0) { + if(fEditMode) { + SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + } + else { + SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + SetLowColor(fMasterView->Color(B_COLOR_SELECTION)); + } + } else { + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND)); + } + + FillRect(destRect); + + BField *field = row->GetField(column->fFieldID); + if (field) { + #if CONSTRAIN_CLIPPING_REGION + BRegion clipRegion; + clipRegion.Set(destRect); + ConstrainClippingRegion(&clipRegion); + PushState(); + #endif + SetHighColor(fColorList[B_TEXT_COLOR]); + float baseline = floor(destRect.top + fh.ascent + + (destRect.Height()+1-(fh.ascent+fh.descent))/2); + MovePenTo(destRect.left + 8, baseline); + column->DrawField(field, destRect, this); + #if CONSTRAIN_CLIPPING_REGION + PopState(); + ConstrainClippingRegion(NULL); + #endif + } + + if (fFocusRow == row) { + if(!fEditMode) { + SetHighColor(0, 0, 0); + StrokeRect(BRect(0, destRect.top, 10000.0, destRect.bottom - 1)); + } + } + + rgb_color color = HighColor(); + SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER)); + // StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2)); + // StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1)); + StrokeLine(BPoint(0, line + rowHeight), BPoint(Bounds().Width(), line + rowHeight)); + SetHighColor(color); + #endif + } + } + } +} + +void OutlineView::Draw(BRect invalidBounds) +{ +#if SMART_REDRAW + BRegion invalidRegion; + GetClippingRegion(&invalidRegion); +#endif + + font_height fh; + GetFontHeight(&fh); + + float line = 0.0; + int32 numColumns = fColumns->CountItems(); + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + iterator.GoToNext()) { + BRow *row = iterator.CurrentRow(); + if (line > invalidBounds.bottom) + break; + + float rowHeight = row->Height(); + + if (line > invalidBounds.top - rowHeight) { + bool isFirstColumn = true; + float fieldLeftEdge = MAX(kLeftMargin, fMasterView->LatchWidth()); + for (int columnIndex = 0; columnIndex < numColumns; columnIndex++) { + BColumn *column = (BColumn*) fColumns->ItemAt(columnIndex); + if (!column->IsVisible()) + continue; + + if (!isFirstColumn && fieldLeftEdge > invalidBounds.right) + break; + + if (fieldLeftEdge + column->Width() >= invalidBounds.left) { + BRect fullRect(fieldLeftEdge, line, + fieldLeftEdge + column->Width(), line + rowHeight); + + bool clippedFirstColumn = false; + // This happens when a column is indented past the + // beginning of the next column. + + if (row->fNextSelected != 0) { + if (Window()->IsActive()) { + if(fEditMode) + SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + else + SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + } + else + SetHighColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION)); + } else + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + + BRect destRect(fullRect); + if (isFirstColumn) { + fullRect.left -= fMasterView->LatchWidth(); + destRect.left += iterator.CurrentLevel() * kOutlineLevelIndent; + if (destRect.left >= destRect.right) { + // clipped + FillRect(BRect(0, line, fieldLeftEdge + column->Width(), + line + rowHeight)); + clippedFirstColumn = true; + } + + + FillRect(BRect(0, line, MAX(kLeftMargin, fMasterView->LatchWidth()), line + row->Height())); + } + + +#if SMART_REDRAW + if (!clippedFirstColumn && invalidRegion.Intersects(fullRect)) +#else + if (!clippedFirstColumn) +#endif + { + FillRect(fullRect); // Using color set above + + // Draw the latch widget if it has one. + if (isFirstColumn) { + if (row == fTargetRow && fCurrentState == LATCH_CLICKED) { + // Note that this only occurs if the user is holding + // down a latch while items are added in the background. + BPoint pos; + uint32 buttons; + GetMouse(&pos, &buttons); + if (fLatchRect.Contains(pos)) + fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH, + fTargetRow); + else + fMasterView->DrawLatch(this, fLatchRect, + row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH, + fTargetRow); + } else { + LatchType pos = B_NO_LATCH; + if (row->HasLatch()) + pos = row->fIsExpanded ? B_OPEN_LATCH : B_CLOSED_LATCH; + + fMasterView->DrawLatch(this, BRect(destRect.left - + fMasterView->LatchWidth(), + destRect.top, destRect.left, destRect.bottom), pos, + row); + } + } + + if (row->fNextSelected != 0) { + if (Window()->IsActive()) { + if(fEditMode) + SetLowColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + else + SetLowColor(fMasterView->Color(B_COLOR_SELECTION)); + } + else + SetLowColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION)); + } else + SetLowColor(fMasterView->Color(B_COLOR_BACKGROUND)); + + SetHighColor(fMasterView->HighColor()); + // The master view just holds the high color for us. + + BField *field = row->GetField(column->fFieldID); + if (field) { +#if CONSTRAIN_CLIPPING_REGION + BRegion clipRegion; + clipRegion.Set(destRect); + ConstrainClippingRegion(&clipRegion); + PushState(); +#endif + SetHighColor(fMasterView->Color(B_COLOR_TEXT)); + float baseline = floor(destRect.top + fh.ascent + + (destRect.Height()+1-(fh.ascent+fh.descent))/2); + MovePenTo(destRect.left + 8, baseline); + column->DrawField(field, destRect, this); +#if CONSTRAIN_CLIPPING_REGION + PopState(); + ConstrainClippingRegion(NULL); +#endif + } + } + } + + isFirstColumn = false; + fieldLeftEdge += column->Width() + 1; + } + + if (fieldLeftEdge <= invalidBounds.right) { + if (row->fNextSelected != 0) { + if (Window()->IsActive()) { + if(fEditMode) + SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + else + SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + } + else + SetHighColor(fMasterView->Color(B_COLOR_NON_FOCUS_SELECTION)); + } else + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + + FillRect(BRect(fieldLeftEdge, line, invalidBounds.right, + line + rowHeight)); + } + } + + if (fFocusRow == row && fMasterView->IsFocus() && Window()->IsActive()) { + if(!fEditMode) { + SetHighColor(0, 0, 0); + StrokeRect(BRect(0, line, 10000.0, line + rowHeight - 1)); + } + } + rgb_color color = HighColor(); + SetHighColor(fMasterView->Color(B_COLOR_ROW_DIVIDER)); +// StrokeLine(BPoint(0, line + rowHeight - 2), BPoint(Bounds().Width(), line + rowHeight - 2)); +// StrokeLine(BPoint(0, line + rowHeight - 1), BPoint(Bounds().Width(), line + rowHeight - 1)); + StrokeLine(BPoint(0, line + rowHeight), BPoint(Bounds().Width(), line + rowHeight)); + SetHighColor(color); + line += rowHeight + 1; + } + + if (line <= invalidBounds.bottom) { + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + FillRect(BRect(invalidBounds.left, line, invalidBounds.right, invalidBounds.bottom)); + } + + // Draw the drop target line + if (fDropHighlightY != -1) + InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000, + fDropHighlightY + kDropHighlightLineHeight / 2)); +} + +BRow* OutlineView::FindRow(float ypos, int32 *out_rowIndent, float *out_top) +{ + if (out_rowIndent && out_top) { + float line = 0.0; + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + iterator.GoToNext()) { + BRow *row = iterator.CurrentRow(); + if (line > ypos) + break; + + float rowHeight = row->Height(); + if (ypos <= line + rowHeight) { + *out_top = line; + *out_rowIndent = iterator.CurrentLevel(); + return row; + } + + line += rowHeight + 1; + } + } + return 0; +} + +void OutlineView::SetMouseTrackingEnabled(bool enabled) +{ + fTrackMouse = enabled; + if (!enabled && fDropHighlightY != -1) { + InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000, + fDropHighlightY + kDropHighlightLineHeight / 2)); // Erase the old target line + fDropHighlightY = -1; + } +} + + +// +// Note that this interaction is not totally safe. If items are added to +// the list in the background, the widget rect will be incorrect, possibly +// resulting in drawing glitches. The code that adds items needs to be a little smarter +// about invalidating state. +// +void OutlineView::MouseDown(BPoint position) +{ + if(!fEditMode) + fMasterView->MakeFocus(true); + + // Check to see if the user is clicking on a widget to open a section + // of the list. + bool reset_click_count = false; + int32 indent; + float rowTop; + BRow *row = FindRow(position.y, &indent, &rowTop); + if (row) { + + // Update fCurrentField + bool handle_field = false; + BField *new_field = 0; + BRow *new_row = 0; + BColumn *new_column = 0; + BRect new_rect; + + if(position.y >=0 ) { + if(position.x >=0 ) { + float x=0; + for(int32 c=0;cCountColumns();c++) { + new_column = fMasterView->ColumnAt(c); + if (!new_column->IsVisible()) + continue; + if((MAX(kLeftMargin, fMasterView->LatchWidth())+x)+new_column->Width() >= position.x) { + if(new_column->WantsEvents()) { + new_field = row->GetField(c); + new_row = row; + FindRect(new_row,&new_rect); + new_rect.left = MAX(kLeftMargin, fMasterView->LatchWidth()) + x; + new_rect.right = new_rect.left + new_column->Width() - 1; + handle_field = true; + } + break; + } + x += new_column->Width(); + } + } + } + + // Handle mouse down + if(handle_field) { + fMouseDown = true; + fFieldRect = new_rect; + fCurrentColumn = new_column; + fCurrentRow = new_row; + fCurrentField = new_field; + fCurrentCode = B_INSIDE_VIEW; + fCurrentColumn->MouseDown(fMasterView,fCurrentRow,fCurrentField,fFieldRect,position,1); + } + + if(!fEditMode) { + + fTargetRow = row; + fTargetRowTop = rowTop; + FindVisibleRect(fFocusRow, &fFocusRowRect); + + float leftWidgetBoundry = indent * kOutlineLevelIndent + MAX(kLeftMargin, fMasterView->LatchWidth()) - + fMasterView->LatchWidth(); + fLatchRect.Set(leftWidgetBoundry, rowTop, leftWidgetBoundry + + fMasterView->LatchWidth(), rowTop + row->Height()); + if (fLatchRect.Contains(position) && row->HasLatch()) { + fCurrentState = LATCH_CLICKED; + if (fTargetRow->fNextSelected != 0) { + if(fEditMode) + SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + else + SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + } + else + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + + FillRect(fLatchRect); + if (fLatchRect.Contains(position)) + fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH, row); + else + fMasterView->DrawLatch(this, fLatchRect, fTargetRow->fIsExpanded + ? B_OPEN_LATCH : B_CLOSED_LATCH, row); + + } else { + Invalidate(fFocusRowRect); + fFocusRow = fTargetRow; + FindVisibleRect(fFocusRow, &fFocusRowRect); + + ASSERT(fTargetRow != 0); + + if ((modifiers() & B_CONTROL_KEY) == 0) + DeselectAll(); + + if ((modifiers() & B_SHIFT_KEY) != 0 && fFirstSelectedItem != 0 + && fSelectionMode == B_MULTIPLE_SELECTION_LIST) { + SelectRange(fFirstSelectedItem, fTargetRow); + } + else { + if (fTargetRow->fNextSelected != 0) { + // Unselect row + fTargetRow->fNextSelected->fPrevSelected = fTargetRow->fPrevSelected; + fTargetRow->fPrevSelected->fNextSelected = fTargetRow->fNextSelected; + fTargetRow->fPrevSelected = 0; + fTargetRow->fNextSelected = 0; + fFirstSelectedItem = NULL; + } else { + // Select row + if (fSelectionMode == B_SINGLE_SELECTION_LIST) + DeselectAll(); + + fTargetRow->fNextSelected = fSelectionListDummyHead.fNextSelected; + fTargetRow->fPrevSelected = &fSelectionListDummyHead; + fTargetRow->fNextSelected->fPrevSelected = fTargetRow; + fTargetRow->fPrevSelected->fNextSelected = fTargetRow; + fFirstSelectedItem = fTargetRow; + } + + Invalidate(BRect(fVisibleRect.left, fTargetRowTop, fVisibleRect.right, + fTargetRowTop + fTargetRow->Height())); + } + + fCurrentState = ROW_CLICKED; + if (fLastSelectedItem != fTargetRow) + reset_click_count = true; + fLastSelectedItem = fTargetRow; + fMasterView->SelectionChanged(); + + } + } + + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | + B_NO_POINTER_HISTORY); + + } else if (fFocusRow != 0) { + // User clicked in open space, unhighlight focus row. + fFocusRow = 0; + Invalidate(fFocusRowRect); + } + + // We stash the click counts here because the 'clicks' field + // is not in the CurrentMessage() when MouseUp is called... ;( + if (reset_click_count) + fClickCount = 1; + else + Window()->CurrentMessage()->FindInt32("clicks", &fClickCount); + fClickPoint = position; + +} // end of MouseDown() + +void OutlineView::MouseMoved(BPoint position, uint32 /*transit*/, const BMessage */*message*/) +{ + if(!fMouseDown) { + // Update fCurrentField + bool handle_field = false; + BField *new_field = 0; + BRow *new_row = 0; + BColumn *new_column = 0; + BRect new_rect(0,0,0,0); + if(position.y >=0 ) { + float top; + int32 indent; + BRow *row = FindRow(position.y, &indent, &top); + if(row && position.x >=0 ) { + float x=0; + for(int32 c=0;cCountColumns();c++) { + new_column = fMasterView->ColumnAt(c); + if (!new_column->IsVisible()) + continue; + if((MAX(kLeftMargin, fMasterView->LatchWidth())+x)+new_column->Width() > position.x) { + if(new_column->WantsEvents()) { + new_field = row->GetField(c); + new_row = row; + FindRect(new_row,&new_rect); + new_rect.left = MAX(kLeftMargin, fMasterView->LatchWidth()) + x; + new_rect.right = new_rect.left + new_column->Width() - 1; + handle_field = true; + } + break; + } + x += new_column->Width(); + } + } + } + + // Handle mouse moved + if(handle_field) { + if(new_field != fCurrentField) { + if(fCurrentField) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 0, fCurrentCode = B_EXITED_VIEW); + } + fCurrentColumn = new_column; + fCurrentRow = new_row; + fCurrentField = new_field; + fFieldRect = new_rect; + if(fCurrentField) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 0, fCurrentCode = B_ENTERED_VIEW); + } + } else { + if(fCurrentField) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 0, fCurrentCode = B_INSIDE_VIEW); + } + } + } else { + if(fCurrentField) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 0, fCurrentCode = B_EXITED_VIEW); + fCurrentField = 0; + fCurrentColumn = 0; + fCurrentRow = 0; + } + } + } else { + if(fCurrentField) { + if(fFieldRect.Contains(position)) { + if ( fCurrentCode == B_OUTSIDE_VIEW + || fCurrentCode == B_EXITED_VIEW + ) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 1, fCurrentCode = B_ENTERED_VIEW); + } else { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 1, fCurrentCode = B_INSIDE_VIEW); + } + } else { + if ( fCurrentCode == B_INSIDE_VIEW + || fCurrentCode == B_ENTERED_VIEW + ) { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 1, fCurrentCode = B_EXITED_VIEW); + } else { + fCurrentColumn->MouseMoved(fMasterView, fCurrentRow, + fCurrentField, fFieldRect, position, 1, fCurrentCode = B_OUTSIDE_VIEW); + } + } + } + } + + if(!fEditMode) { + + switch (fCurrentState) { + case LATCH_CLICKED: + if (fTargetRow->fNextSelected != 0) { + if(fEditMode) + SetHighColor(fMasterView->Color(B_COLOR_EDIT_BACKGROUND)); + else + SetHighColor(fMasterView->Color(B_COLOR_SELECTION)); + } + else + SetHighColor(fMasterView->Color(B_COLOR_BACKGROUND)); + + FillRect(fLatchRect); + if (fLatchRect.Contains(position)) + fMasterView->DrawLatch(this, fLatchRect, B_PRESSED_LATCH, fTargetRow); + else + fMasterView->DrawLatch(this, fLatchRect, fTargetRow->fIsExpanded + ? B_OPEN_LATCH : B_CLOSED_LATCH, fTargetRow); + + break; + + + case ROW_CLICKED: + if (abs((int)(position.x - fClickPoint.x)) > kRowDragSensitivity + || abs((int)(position.y - fClickPoint.y)) > kRowDragSensitivity) { + fCurrentState = DRAGGING_ROWS; + fMasterView->InitiateDrag(fClickPoint, fTargetRow->fNextSelected != 0); + } + break; + + case DRAGGING_ROWS: +#if 0 + // falls through... +#else + if (fTrackMouse /*&& message*/) { + if (fVisibleRect.Contains(position)) { + float top; + int32 indent; + BRow *target = FindRow(position.y, &indent, &top); + if(target) + SetFocusRow(target,true); + } + } + break; +#endif + + default: { + + if (fTrackMouse /*&& message*/) { + // Draw a highlight line... + if (fVisibleRect.Contains(position)) { + float top; + int32 indent; + BRow *target = FindRow(position.y, &indent, &top); + if(target==fRollOverRow) + break; + if(fRollOverRow) + { + BRect rect; + FindRect(fRollOverRow, &rect); + Invalidate(rect); + } + fRollOverRow=target; +#if 0 + SetFocusRow(fRollOverRow,false); +#else + PushState(); + SetDrawingMode(B_OP_BLEND); + SetHighColor(255,255,255,255); + BRect rect; + FindRect(fRollOverRow, &rect); + rect.bottom -= 1.0; + FillRect(rect); + PopState(); +#endif + } else { + if(fRollOverRow) + { + BRect rect; + FindRect(fRollOverRow, &rect); + Invalidate(rect); + fRollOverRow = NULL; + } + } + } + } + } + } +} // end of MouseMoved() + +void OutlineView::MouseUp(BPoint position) +{ + if(fCurrentField) { + fCurrentColumn->MouseUp(fMasterView,fCurrentRow,fCurrentField); + fMouseDown = false; + } + + if(!fEditMode) { + switch (fCurrentState) { + case LATCH_CLICKED: + if (fLatchRect.Contains(position)) + fMasterView->ExpandOrCollapse(fTargetRow, !fTargetRow->fIsExpanded); + + Invalidate(fLatchRect); + fCurrentState = INACTIVE; + break; + + case ROW_CLICKED: + if (fClickCount > 1 + && abs((int)fClickPoint.x - (int)position.x) < kDoubleClickMoveSensitivity + && abs((int)fClickPoint.y - (int)position.y) < kDoubleClickMoveSensitivity) { + fMasterView->ItemInvoked(); + } + fCurrentState = INACTIVE; + break; + + case DRAGGING_ROWS: + fCurrentState = INACTIVE; + // Falls through + + default: + if (fDropHighlightY != -1) { + InvertRect(BRect(0, fDropHighlightY - kDropHighlightLineHeight / 2, 1000000, + fDropHighlightY + kDropHighlightLineHeight / 2)); // Erase the old target line + fDropHighlightY = -1; + } + } + } +} // end of MouseUp() + +void OutlineView::MessageReceived(BMessage *message) +{ + if (message->WasDropped()) { + fMasterView->MessageDropped(message, ConvertFromScreen(message->DropPoint())); + } else { + BView::MessageReceived(message); + } +} + +void OutlineView::ChangeFocusRow(bool up, bool updateSelection, bool addToCurrentSelection) +{ + int32 indent; + float top; + float newRowPos = 0; + float verticalScroll = 0; + + if (fFocusRow) { + // A row currently has the focus, get information about it + newRowPos = fFocusRowRect.top + (up ? -4 : fFocusRow->Height() + 4); + if (newRowPos < fVisibleRect.top + 20) + verticalScroll = newRowPos - 20; + else if (newRowPos > fVisibleRect.bottom - 20) + verticalScroll = newRowPos - fVisibleRect.Height() + 20; + } else + newRowPos = fVisibleRect.top + 2; + // no row is currently focused, set this to the top of the window + // so we will select the first visible item in the list. + + BRow *newRow = FindRow(newRowPos, &indent, &top); + if (newRow) { + if (fFocusRow) { + fFocusRowRect.right = 10000; + Invalidate(fFocusRowRect); + } + fFocusRow = newRow; + fFocusRowRect.top = top; + fFocusRowRect.left = 0; + fFocusRowRect.right = 10000; + fFocusRowRect.bottom = fFocusRowRect.top + fFocusRow->Height(); + Invalidate(fFocusRowRect); + + if (updateSelection) { + if (!addToCurrentSelection || fSelectionMode == B_SINGLE_SELECTION_LIST) + DeselectAll(); + + if (fFocusRow->fNextSelected == 0) { + fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected; + fFocusRow->fPrevSelected = &fSelectionListDummyHead; + fFocusRow->fNextSelected->fPrevSelected = fFocusRow; + fFocusRow->fPrevSelected->fNextSelected = fFocusRow; + } + + fLastSelectedItem = fFocusRow; + } + } else + Invalidate(fFocusRowRect); + + if (verticalScroll != 0) { + BScrollBar *vScrollBar = ScrollBar(B_VERTICAL); + float min, max; + vScrollBar->GetRange(&min, &max); + if (verticalScroll < min) + verticalScroll = min; + else if (verticalScroll > max) + verticalScroll = max; + + vScrollBar->SetValue(verticalScroll); + } + + if (newRow && updateSelection) + fMasterView->SelectionChanged(); +} + +void OutlineView::MoveFocusToVisibleRect() +{ + fFocusRow = 0; + ChangeFocusRow(true, true, false); +} + +BRow* OutlineView::CurrentSelection(BRow *lastSelected) const +{ + BRow *row; + if (lastSelected == 0) + row = fSelectionListDummyHead.fNextSelected; + else + row = lastSelected->fNextSelected; + + + if (row == &fSelectionListDummyHead) + row = 0; + + return row; +} + +void OutlineView::ToggleFocusRowSelection(bool selectRange) +{ + if (fFocusRow == 0) + return; + + if (selectRange && fSelectionMode == B_MULTIPLE_SELECTION_LIST) + SelectRange(fLastSelectedItem, fFocusRow); + else { + if (fFocusRow->fNextSelected != 0) { + // Unselect row + fFocusRow->fNextSelected->fPrevSelected = fFocusRow->fPrevSelected; + fFocusRow->fPrevSelected->fNextSelected = fFocusRow->fNextSelected; + fFocusRow->fPrevSelected = 0; + fFocusRow->fNextSelected = 0; + } else { + // Select row + if (fSelectionMode == B_SINGLE_SELECTION_LIST) + DeselectAll(); + + fFocusRow->fNextSelected = fSelectionListDummyHead.fNextSelected; + fFocusRow->fPrevSelected = &fSelectionListDummyHead; + fFocusRow->fNextSelected->fPrevSelected = fFocusRow; + fFocusRow->fPrevSelected->fNextSelected = fFocusRow; + } + } + + fLastSelectedItem = fFocusRow; + fMasterView->SelectionChanged(); + Invalidate(fFocusRowRect); +} + +void OutlineView::ToggleFocusRowOpen() +{ + if (fFocusRow) + fMasterView->ExpandOrCollapse(fFocusRow, !fFocusRow->fIsExpanded); +} + + +// xxx Could use CopyBits here to speed things up. +void OutlineView::ExpandOrCollapse(BRow* ParentRow, bool Expand) +{ + if (ParentRow) { + + if (ParentRow->fIsExpanded == Expand) + return; + + ParentRow->fIsExpanded = Expand; + + BRect parentRect; + if (FindRect(ParentRow, &parentRect)) { + // Determine my new height + float subTreeHeight = 0.0; + if (ParentRow->fIsExpanded) + for (RecursiveOutlineIterator iterator(ParentRow->fChildList); + iterator.CurrentRow(); + iterator.GoToNext() + ) + { + subTreeHeight += iterator.CurrentRow()->Height()+1; + } + else + for (RecursiveOutlineIterator iterator(ParentRow->fChildList); + iterator.CurrentRow(); + iterator.GoToNext() + ) + { + subTreeHeight -= iterator.CurrentRow()->Height()+1; + } + fItemsHeight += subTreeHeight; + + // Adjust focus row if necessary. + if (FindRect(fFocusRow, &fFocusRowRect) == false) { + // focus row is in a subtree that has collapsed, move it up to the parent. + fFocusRow = ParentRow; + FindRect(fFocusRow, &fFocusRowRect); + } + + Invalidate(BRect(0, parentRect.top, fVisibleRect.right, fVisibleRect.bottom)); + FixScrollBar(false); + } + } + +} + +void OutlineView::RemoveRow(BRow *row) +{ + if (row) { + BRow *parentRow; + bool parentIsVisible; + float subTreeHeight = row->Height(); + if (FindParent(row, &parentRow, &parentIsVisible)) { + // adjust height + if (parentIsVisible && (parentRow == 0 || parentRow->fIsExpanded)) { + if (row->fIsExpanded) { + for (RecursiveOutlineIterator iterator(row->fChildList); + iterator.CurrentRow(); iterator.GoToNext()) + subTreeHeight += iterator.CurrentRow()->Height(); + } + } + } + if (parentRow) { + if (parentRow->fIsExpanded) + fItemsHeight -= subTreeHeight + 1; + } + else { + fItemsHeight -= subTreeHeight + 1; + } + FixScrollBar(false); + if (parentRow) + parentRow->fChildList->RemoveItem(row); + else + fRows.RemoveItem(row); + + if (parentRow != 0 && parentRow->fChildList->CountItems() == 0) { + delete parentRow->fChildList; + parentRow->fChildList = 0; + if (parentIsVisible) + Invalidate(); // xxx crude way of redrawing latch + } + + if (parentIsVisible && (parentRow == 0 || parentRow->fIsExpanded)) + Invalidate(); // xxx make me smarter. + + + // Adjust focus row if necessary. + if (fFocusRow && FindRect(fFocusRow, &fFocusRowRect) == false) { + // focus row is in a subtree that is gone, move it up to the parent. + fFocusRow = parentRow; + if (fFocusRow) + FindRect(fFocusRow, &fFocusRowRect); + } + + // Remove this from the selection if necessary + if (row->fNextSelected != 0) { + row->fNextSelected->fPrevSelected = row->fPrevSelected; + row->fPrevSelected->fNextSelected = row->fNextSelected; + row->fPrevSelected = 0; + row->fNextSelected = 0; + fMasterView->SelectionChanged(); + } + + fCurrentColumn = 0; + fCurrentRow = 0; + fCurrentField = 0; + } +} + +BRowContainer* OutlineView::RowList() +{ + return &fRows; +} + +void OutlineView::UpdateRow(BRow *row) +{ + if (row) { + // Determine if this row has changed its sort order + BRow *parentRow = NULL; + bool parentIsVisible = false; + FindParent(row, &parentRow, &parentIsVisible); + + BRowContainer* list = (parentRow == NULL) ? &fRows : parentRow->fChildList; + + if(list) { + int32 rowIndex = list->IndexOf(row); + ASSERT(rowIndex >= 0); + ASSERT(list->ItemAt(rowIndex) == row); + + bool rowMoved = false; + if (rowIndex > 0 && CompareRows(list->ItemAt(rowIndex - 1), row) > 0) + rowMoved = true; + + if (rowIndex < list->CountItems() - 1 && CompareRows(list->ItemAt(rowIndex + 1), + row) < 0) + rowMoved = true; + + if (rowMoved) { + // Sort location of this row has changed. + // Remove and re-add in the right spot + SortList(list, parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)); + } else if (parentIsVisible && (parentRow == NULL || parentRow->fIsExpanded)) { + BRect invalidRect; + if (FindVisibleRect(row, &invalidRect)) + Invalidate(invalidRect); + } + } + } +} + +void OutlineView::AddRow(BRow* Row, int32 Index, BRow* ParentRow) +{ + if(Row) { + Row->fParent = ParentRow; + + if (fMasterView->SortingEnabled()) { + // Ignore index here. + if (ParentRow) { + if (ParentRow->fChildList == 0) + ParentRow->fChildList = new BRowContainer; + + AddSorted(ParentRow->fChildList, Row); + } else + AddSorted(&fRows, Row); + } else { + // Note, a -1 index implies add to end if sorting is not enabled + if (ParentRow) { + if (ParentRow->fChildList == 0) + ParentRow->fChildList = new BRowContainer; + + if (Index < 0 || Index > ParentRow->fChildList->CountItems()) + ParentRow->fChildList->AddItem(Row); + else + ParentRow->fChildList->AddItem(Row, Index); + } else { + if (Index < 0 || Index >= fRows.CountItems()) + fRows.AddItem(Row); + else + fRows.AddItem(Row, Index); + } + } + + if (ParentRow == 0 || ParentRow->fIsExpanded) + fItemsHeight += Row->Height() + 1; + + FixScrollBar(false); + + BRect newRowRect; + bool newRowIsInOpenBranch = FindRect(Row, &newRowRect); + + if (fFocusRow && fFocusRowRect.top > newRowRect.bottom) { + // The focus row has moved. + Invalidate(fFocusRowRect); + FindRect(fFocusRow, &fFocusRowRect); + Invalidate(fFocusRowRect); + } + + if (newRowIsInOpenBranch) { + if (fCurrentState == INACTIVE) { + if (newRowRect.bottom < fVisibleRect.top) { + // The new row is totally above the current viewport, move + // everything down and redraw the first line. + BRect source(fVisibleRect); + BRect dest(fVisibleRect); + source.bottom -= Row->Height() + 1; + dest.top += Row->Height() + 1; + CopyBits(source, dest); + Invalidate(BRect(fVisibleRect.left, fVisibleRect.top, fVisibleRect.right, + fVisibleRect.top + newRowRect.Height())); + } else if (newRowRect.top < fVisibleRect.bottom) { + // New item is somewhere in the current region. Scroll everything + // beneath it down and invalidate just the new row rect. + BRect source(fVisibleRect.left, newRowRect.top, fVisibleRect.right, + fVisibleRect.bottom - newRowRect.Height()); + BRect dest(source); + dest.OffsetBy(0, newRowRect.Height() + 1); + CopyBits(source, dest); + Invalidate(newRowRect); + } // otherwise, this is below the currently visible region + } else { + // Adding the item may have caused the item that the user is currently + // selected to move. This would cause annoying drawing and interaction + // bugs, as the position of that item is cached. If this happens, resize + // the scroll bar, then scroll back so the selected item is in view. + BRect targetRect; + if (FindRect(fTargetRow, &targetRect)) { + float delta = targetRect.top - fTargetRowTop; + if (delta != 0) { + // This causes a jump because ScrollBy will copy a chunk of the view. + // Since the actual contents of the view have been offset, we don't + // want this, we just want to change the virtual origin of the window. + // Constrain the clipping region so everything is clipped out so no + // copy occurs. + // + // xxx this currently doesn't work if the scroll bars aren't enabled. + // everything will still move anyway. A minor annoyance. + BRegion emptyRegion; + ConstrainClippingRegion(&emptyRegion); + PushState(); + ScrollBy(0, delta); + PopState(); + ConstrainClippingRegion(NULL); + + fTargetRowTop += delta; + fClickPoint.y += delta; + fLatchRect.OffsetBy(0, delta); + } + } + } + } + + // If the parent was previously childless, it will need to have a latch drawn. + BRect parentRect; + if (ParentRow && ParentRow->fChildList->CountItems() == 1 + && FindVisibleRect(ParentRow, &parentRect)) + Invalidate(parentRect); + } +} + + +void OutlineView::FixScrollBar(bool scrollToFit) +{ + BScrollBar *vScrollBar = ScrollBar(B_VERTICAL); + if (vScrollBar) { + if (fItemsHeight > fVisibleRect.Height()) { + float maxScrollBarValue = (fItemsHeight + kBottomMargin) - fVisibleRect.Height(); + vScrollBar->SetProportion(fVisibleRect.Height() / (fItemsHeight + kBottomMargin)); + + // If the user is scrolled down too far when makes the range smaller, the list + // will jump suddenly, which is undesirable. In this case, don't fix the scroll + // bar here. In ScrollTo, it checks to see if this has occured, and will + // fix the scroll bars sneakily if the user has scrolled up far enough. + if (scrollToFit || vScrollBar->Value() <= maxScrollBarValue) { + vScrollBar->SetRange(0.0, maxScrollBarValue); + vScrollBar->SetSteps(20.0, fVisibleRect.Height()); + } + } else if (vScrollBar->Value() == 0.0) + vScrollBar->SetRange(0.0, 0.0); // disable scroll bar. + } +} + +void OutlineView::AddSorted(BRowContainer *list, BRow *row) +{ + if (list && row) { + // Find general vicinity with binary search. + int32 lower = 0; + int32 upper = list->CountItems()-1; + while( lower < upper ) { + int32 middle = lower + (upper-lower+1)/2; + int32 cmp = CompareRows(row, list->ItemAt(middle)); + if( cmp < 0 ) upper = middle-1; + else if( cmp > 0 ) lower = middle+1; + else lower = upper = middle; + } + + // At this point, 'upper' and 'lower' at the last found item. + // Arbitrarily use 'upper' and determine the final insertion + // point -- either before or after this item. + if( upper < 0 ) upper = 0; + else if( upper < list->CountItems() ) { + if( CompareRows(row, list->ItemAt(upper)) > 0 ) upper++; + } + + if (upper >= list->CountItems()) + list->AddItem(row); // Adding to end. + else + list->AddItem(row, upper); // Insert + } +} + +int32 OutlineView::CompareRows(BRow *row1, BRow *row2) +{ + int32 itemCount (fSortColumns->CountItems()); + if (row1 && row2) { + for (int32 index = 0; index < itemCount; index++) { + BColumn *column = (BColumn*) fSortColumns->ItemAt(index); + int comp = 0; + BField *field1 = (BField*) row1->GetField(column->fFieldID); + BField *field2 = (BField*) row2->GetField(column->fFieldID); + if (field1 && field2) + comp = column->CompareFields(field1, field2); + + if (!column->fSortAscending) + comp = -comp; + + if (comp != 0) + return comp; + } + } + return 0; +} + +void OutlineView::FrameResized(float width, float height) +{ + fVisibleRect.right = fVisibleRect.left + width; + fVisibleRect.bottom = fVisibleRect.top + height; + FixScrollBar(true); + _inherited::FrameResized(width, height); +} + +void OutlineView::ScrollTo(BPoint position) +{ + fVisibleRect.OffsetTo(position.x, position.y); + + // In FixScrollBar, we might not have been able to change the size of + // the scroll bar because the user was scrolled down too far. Take + // this opportunity to sneak it in if we can. + BScrollBar *vScrollBar = ScrollBar(B_VERTICAL); + float maxScrollBarValue = (fItemsHeight + kBottomMargin) - fVisibleRect.Height(); + float min, max; + vScrollBar->GetRange(&min, &max); + if (max != maxScrollBarValue && position.y > maxScrollBarValue) + FixScrollBar(true); + + _inherited::ScrollTo(position); +} + +const BRect& OutlineView::VisibleRect() const +{ + return fVisibleRect; +} + + +bool OutlineView::FindVisibleRect(BRow *row, BRect *out_rect) +{ + if (row && out_rect) { + float line = 0.0; + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + iterator.GoToNext()) { + if (line > fVisibleRect.bottom) + break; + + if (iterator.CurrentRow() == row) { + out_rect->Set(fVisibleRect.left, line, fVisibleRect.right, line + row->Height()); + return true; + } + + line += iterator.CurrentRow()->Height() + 1; + } + } + return false; +} + +bool OutlineView::FindRect(const BRow *row, BRect *out_rect) +{ + float line = 0.0; + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + iterator.GoToNext()) { + if (iterator.CurrentRow() == row) { + out_rect->Set(fVisibleRect.left, line, fVisibleRect.right, line + row->Height()); + return true; + } + + line += iterator.CurrentRow()->Height() + 1; + } + + return false; +} + + +void OutlineView::ScrollTo(const BRow* Row) +{ + BRect rect; + if( true == FindRect(Row, &rect) ) + { + ScrollTo(BPoint(rect.left, rect.top)); + } +} + + +void OutlineView::DeselectAll() +{ + // Invalidate all selected rows + float line = 0.0; + for (RecursiveOutlineIterator iterator(&fRows); iterator.CurrentRow(); + iterator.GoToNext()) { + if (line > fVisibleRect.bottom) + break; + + BRow *row = iterator.CurrentRow(); + if (line + row->Height() > fVisibleRect.top) { + if (row->fNextSelected != 0) + Invalidate(BRect(fVisibleRect.left, line, fVisibleRect.right, + line + row->Height())); + } + + line += row->Height() + 1; + } + + // Set items not selected + while (fSelectionListDummyHead.fNextSelected != &fSelectionListDummyHead) { + BRow *row = fSelectionListDummyHead.fNextSelected; + row->fNextSelected->fPrevSelected = row->fPrevSelected; + row->fPrevSelected->fNextSelected = row->fNextSelected; + row->fNextSelected = 0; + row->fPrevSelected = 0; + } +} + +BRow* OutlineView::FocusRow() const +{ + return fFocusRow; +} + +void OutlineView::SetFocusRow(BRow* Row, bool Select) +{ + if (Row) + { + if(Select) + AddToSelection(Row); + + if(fFocusRow == Row) + return; + + Invalidate(fFocusRowRect); // invalidate previous + + fTargetRow = fFocusRow = Row; + + FindVisibleRect(fFocusRow, &fFocusRowRect); + Invalidate(fFocusRowRect); // invalidate current + + fFocusRowRect.right = 10000; + fMasterView->SelectionChanged(); + } +} + +bool OutlineView::SortList(BRowContainer *list, bool isVisible) +{ + if (list) { + // Shellsort + BRow **items = (BRow**) list->AsBList()->Items(); + int32 numItems = list->CountItems(); + int h; + for (h = 1; h < numItems / 9; h = 3 * h + 1) + ; + + for (;h > 0; h /= 3) { + for (int step = h; step < numItems; step++) { + BRow *temp = items[step]; + int i; + for (i = step - h; i >= 0; i -= h) { + if (CompareRows(temp, items[i]) < 0) + items[i + h] = items[i]; + else + break; + } + + items[i + h] = temp; + } + } + + if (isVisible) { + Invalidate(); + + InvalidateCachedPositions(); + int lockCount = Window()->CountLocks(); + for (int i = 0; i < lockCount; i++) + Window()->Unlock(); + + while (lockCount--) + if (!Window()->Lock()) + return false; // Window is gone... + } + } + return true; +} + +int32 OutlineView::DeepSortThreadEntry(void *_outlineView) +{ + ((OutlineView*) _outlineView)->DeepSort(); + return 0; +} + +void OutlineView::DeepSort() +{ + struct stack_entry { + bool isVisible; + BRowContainer *list; + int32 listIndex; + } stack[kMaxDepth]; + int32 stackTop = 0; + + stack[stackTop].list = &fRows; + stack[stackTop].isVisible = true; + stack[stackTop].listIndex = 0; + fNumSorted = 0; + + if (Window()->Lock() == false) + return; + + bool doneSorting = false; + while (!doneSorting && !fSortCancelled) { + + stack_entry *currentEntry = &stack[stackTop]; + + // xxx Can make the invalidate area smaller by finding the rect for the + // parent item and using that as the top of the invalid rect. + + bool haveLock = SortList(currentEntry->list, currentEntry->isVisible); + if (!haveLock) + return ; // window is gone. + + // Fix focus rect. + InvalidateCachedPositions(); + if (fCurrentState != INACTIVE) + fCurrentState = INACTIVE; // sorry... + + // next list. + bool foundNextList = false; + while (!foundNextList && !fSortCancelled) { + for (int32 index = currentEntry->listIndex; index < currentEntry->list->CountItems(); + index++) { + BRow *parentRow = currentEntry->list->ItemAt(index); + BRowContainer *childList = parentRow->fChildList; + if (childList != 0) { + currentEntry->listIndex = index + 1; + stackTop++; + ASSERT(stackTop < kMaxDepth); + stack[stackTop].listIndex = 0; + stack[stackTop].list = childList; + stack[stackTop].isVisible = (currentEntry->isVisible && parentRow->fIsExpanded); + foundNextList = true; + break; + } + } + + if (!foundNextList) { + // back up + if (--stackTop < 0) { + doneSorting = true; + break; + } + + currentEntry = &stack[stackTop]; + } + } + } + + Window()->Unlock(); +} + +void OutlineView::StartSorting() +{ + // If this view is not yet attached to a window, don't start a sort thread! + if (Window() == NULL) + return; + + if (fSortThread != B_BAD_THREAD_ID) { + thread_info tinfo; + if (get_thread_info(fSortThread, &tinfo) == B_OK) { + // Unlock window so this won't deadlock (sort thread is probably + // waiting to lock window). + + int lockCount = Window()->CountLocks(); + for (int i = 0; i < lockCount; i++) + Window()->Unlock(); + + fSortCancelled = true; + int32 status; + wait_for_thread(fSortThread, &status); + + while (lockCount--) + if (!Window()->Lock()) + return ; // Window is gone... + } + } + + fSortCancelled = false; + fSortThread = spawn_thread(DeepSortThreadEntry, "sort_thread", B_NORMAL_PRIORITY, this); + resume_thread(fSortThread); +} + +void OutlineView::SelectRange(BRow *start, BRow *end) +{ + if (!start || !end) + return; + + if (start == end) // start is always selected when this is called + return; + + RecursiveOutlineIterator iterator(&fRows, false); + while (iterator.CurrentRow() != 0) { + if (iterator.CurrentRow() == end) { + // reverse selection, swap to fix special case + BRow *temp = start; + start = end; + end = temp; + break; + } else if (iterator.CurrentRow() == start) + break; + + iterator.GoToNext(); + } + + while (true) { + BRow *row = iterator.CurrentRow(); + if(row) { + if (row->fNextSelected == 0) { + row->fNextSelected = fSelectionListDummyHead.fNextSelected; + row->fPrevSelected = &fSelectionListDummyHead; + row->fNextSelected->fPrevSelected = row; + row->fPrevSelected->fNextSelected = row; + } + } else + break; + + if (row == end) + break; + + iterator.GoToNext(); + } + + Invalidate(); // xxx make invalidation smaller +} + +bool OutlineView::FindParent(BRow *row, BRow **outParent, bool *out_parentIsVisible) +{ + bool result = false; + if (row && outParent) { + *outParent = row->fParent; + + // Walk up the parent chain to determine if this row is visible + bool isVisible = true; + for (BRow *currentRow = row->fParent; currentRow; currentRow = currentRow->fParent) { + if (!currentRow->fIsExpanded) { + isVisible = false; + break; + } + } + + if (out_parentIsVisible) *out_parentIsVisible = isVisible; + result = (NULL != *outParent); + } + + return result; +} + +int32 OutlineView::IndexOf(BRow *row) +{ + if (row) { + if (row->fParent == 0) + return fRows.IndexOf(row); + + ASSERT(row->fParent->fChildList); + return row->fParent->fChildList->IndexOf(row); + } + + return B_ERROR; +} + +void OutlineView::InvalidateCachedPositions() +{ + if (fFocusRow) + FindRect(fFocusRow, &fFocusRowRect); +} + +// #pragma mark - + + +RecursiveOutlineIterator::RecursiveOutlineIterator(BRowContainer *list, bool openBranchesOnly) + : fStackIndex(0), + fCurrentListIndex(0), + fCurrentListDepth(0), + fOpenBranchesOnly(openBranchesOnly) +{ + if (list == 0 || list->CountItems() == 0) + fCurrentList = 0; + else + fCurrentList = list; +} + +BRow* RecursiveOutlineIterator::CurrentRow() const +{ + if (fCurrentList == 0) + return 0; + + return fCurrentList->ItemAt(fCurrentListIndex); +} + +void RecursiveOutlineIterator::GoToNext() +{ + if (fCurrentList == 0) + return; + if (fCurrentListIndex < 0 || fCurrentListIndex >= fCurrentList->CountItems()) { + fCurrentList = 0; + return; + } + + BRow *currentRow = fCurrentList->ItemAt(fCurrentListIndex); + if(currentRow) { + if (currentRow->fChildList && (currentRow->fIsExpanded || !fOpenBranchesOnly) + && currentRow->fChildList->CountItems() > 0) { + // Visit child. + // Put current list on the stack if it needs to be revisited. + if (fCurrentListIndex < fCurrentList->CountItems() - 1) { + fStack[fStackIndex].fRowSet = fCurrentList; + fStack[fStackIndex].fIndex = fCurrentListIndex + 1; + fStack[fStackIndex].fDepth = fCurrentListDepth; + fStackIndex++; + } + + fCurrentList = currentRow->fChildList; + fCurrentListIndex = 0; + fCurrentListDepth++; + } else if (fCurrentListIndex < fCurrentList->CountItems() - 1) + fCurrentListIndex++; // next item in current list + else if (--fStackIndex >= 0) { + fCurrentList = fStack[fStackIndex].fRowSet; + fCurrentListIndex = fStack[fStackIndex].fIndex; + fCurrentListDepth = fStack[fStackIndex].fDepth; + } else + fCurrentList = 0; + } +} + +int32 RecursiveOutlineIterator::CurrentLevel() const +{ + return fCurrentListDepth; +}