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.
*/
#include "TitleView.h"
#include <Alert.h>
#include <Application.h>
#include <ControlLook.h>
#include <Debug.h>
#include <PopUpMenu.h>
#include <Window.h>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include "Commands.h"
#include "ContainerWindow.h"
#include "PoseView.h"
#include "Utilities.h"
#define APP_SERVER_CLEARS_BACKGROUND 1
static const float kMinFontSize = 8.0f;
static const float kMinTitleHeight = 13.0f;
static const float kTitleSpacing = 1.4f;
static void
_DrawLine(BPoseView* view, BPoint from, BPoint to)
{
float tint = B_NO_TINT;
color_which highColor = view->HighUIColor(&tint);
view->SetHighUIColor(view->LowUIColor(), B_DARKEN_1_TINT);
view->StrokeLine(from, to);
view->SetHighUIColor(highColor, tint);
}
static void
_UndrawLine(BPoseView* view, BPoint from, BPoint to)
{
if (!view->TargetVolumeIsReadOnly())
view->SetLowUIColor(view->LowUIColor());
else
view->SetLowUIColor(view->LowUIColor(), ReadOnlyTint(view->LowUIColor()));
view->StrokeLine(from, to, B_SOLID_LOW);
}
static void
_DrawOutline(BView* view, BRect where)
{
where.right++;
where.bottom--;
float tint = B_NO_TINT;
color_which highColor = view->HighUIColor(&tint);
view->SetHighUIColor(B_CONTROL_HIGHLIGHT_COLOR);
view->StrokeRect(where);
view->SetHighUIColor(highColor, tint);
}
BTitleView::BTitleView(BPoseView* view)
:
BView("TitleView", B_WILL_DRAW),
fPoseView(view),
fTitleList(10),
fHorizontalResizeCursor(B_CURSOR_ID_RESIZE_EAST_WEST),
fPreviouslyClickedColumnTitle(0),
fPreviousLeftClickTime(0),
fTrackingState(NULL)
{
SetHighUIColor(B_PANEL_BACKGROUND_COLOR);
SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
#if APP_SERVER_CLEARS_BACKGROUND
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
#else
SetViewColor(B_TRANSPARENT_COLOR);
#endif
float fontSize = std::max(kMinFontSize,
floorf(be_plain_font->Size() * 0.75f));
BFont font(be_plain_font);
font.SetSize(fontSize);
SetFont(&font);
fPreferredHeight = std::max(kMinTitleHeight,
ceilf(fontSize * kTitleSpacing));
Reset();
}
BTitleView::~BTitleView()
{
delete fTrackingState;
}
void
BTitleView::Reset()
{
fTitleList.MakeEmpty();
for (int32 index = 0; ; index++) {
BColumn* column = fPoseView->ColumnAt(index);
if (!column)
break;
fTitleList.AddItem(new BColumnTitle(this, column));
}
Invalidate();
}
void
BTitleView::AddTitle(BColumn* column, const BColumn* after)
{
int32 count = fTitleList.CountItems();
int32 index;
if (after) {
for (index = 0; index < count; index++) {
BColumn* titleColumn = fTitleList.ItemAt(index)->Column();
if (after == titleColumn) {
index++;
break;
}
}
} else
index = count;
fTitleList.AddItem(new BColumnTitle(this, column), index);
Invalidate();
}
void
BTitleView::RemoveTitle(BColumn* column)
{
int32 count = fTitleList.CountItems();
for (int32 index = 0; index < count; index++) {
BColumnTitle* title = fTitleList.ItemAt(index);
if (title->Column() == column) {
fTitleList.RemoveItem(title);
break;
}
}
Invalidate();
}
BSize
BTitleView::MinSize()
{
return BSize(16, fPreferredHeight);
}
BSize
BTitleView::MaxSize()
{
return BSize(B_SIZE_UNLIMITED, fPreferredHeight);
}
void
BTitleView::Draw(BRect rect)
{
Draw(rect, false);
}
void
BTitleView::Draw(BRect , bool useOffscreen, bool updateOnly,
const BColumnTitle* pressedColumn,
void (*trackRectBlitter)(BView*, BRect), BRect passThru)
{
BRect bounds(Bounds());
BView* view;
if (useOffscreen) {
ASSERT(sOffscreen);
BRect frame(bounds);
frame.right += frame.left;
view = sOffscreen->BeginUsing(frame);
view->SetOrigin(-bounds.left, 0);
view->SetLowColor(LowColor());
view->SetHighColor(HighColor());
BFont font;
GetFont(&font);
view->SetFont(&font);
} else
view = this;
view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_2_TINT);
view->StrokeLine(bounds.LeftBottom(), bounds.RightBottom());
bounds.bottom--;
rgb_color baseColor = ui_color(B_CONTROL_BACKGROUND_COLOR);
be_control_look->DrawButtonBackground(view, bounds, bounds, baseColor, 0,
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
int32 count = fTitleList.CountItems();
float minx = bounds.right;
float maxx = bounds.left;
for (int32 index = 0; index < count; index++) {
BColumnTitle* title = fTitleList.ItemAt(index);
title->Draw(view, title == pressedColumn);
BRect titleBounds(title->Bounds());
if (titleBounds.left < minx)
minx = titleBounds.left;
if (titleBounds.right > maxx)
maxx = titleBounds.right;
}
bounds = Bounds();
minx--;
view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
view->StrokeLine(BPoint(minx, bounds.top),
BPoint(minx, bounds.bottom - 1));
#if !(APP_SERVER_CLEARS_BACKGROUND)
FillRect(BRect(bounds.left, bounds.top + 1, minx - 1, bounds.bottom - 1),
B_SOLID_LOW);
FillRect(BRect(maxx + 1, bounds.top + 1, bounds.right, bounds.bottom - 1),
B_SOLID_LOW);
#endif
if (useOffscreen) {
if (trackRectBlitter)
(trackRectBlitter)(view, passThru);
view->Sync();
DrawBitmap(sOffscreen->Bitmap());
sOffscreen->DoneUsing();
} else if (trackRectBlitter)
(trackRectBlitter)(view, passThru);
}
void
BTitleView::MouseDown(BPoint where)
{
BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
if (window == NULL)
return;
if (!window->IsActive()) {
window->Activate();
return;
}
fPoseView->CommitActivePose();
BColumnTitle* title = FindColumnTitle(where);
BColumnTitle* resizedTitle = InColumnResizeArea(where);
uint32 buttons;
GetMouse(&where, &buttons);
if (SecondaryMouseButtonDown(modifiers(), buttons)) {
BPopUpMenu* menu = new BPopUpMenu("Attributes", false, false);
window->NewAttributesMenu(menu);
window->AddMimeTypesToMenu(menu);
window->MarkAttributesMenu(menu);
menu->SetTargetForItems(window->PoseView());
menu->Go(ConvertToScreen(where), true, false);
return;
}
bigtime_t doubleClickSpeed;
get_click_speed(&doubleClickSpeed);
if (resizedTitle) {
bool force = static_cast<bool>(buttons & B_TERTIARY_MOUSE_BUTTON);
if (force || (buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
if (force || fPreviouslyClickedColumnTitle != 0) {
if (force || system_time() - fPreviousLeftClickTime < doubleClickSpeed) {
if (fPoseView->ResizeColumnToWidest(resizedTitle->Column())) {
Invalidate();
return;
}
}
}
fPreviousLeftClickTime = system_time();
fPreviouslyClickedColumnTitle = resizedTitle;
}
} else if (!title)
return;
SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
if (resizedTitle) {
fTrackingState = new ColumnResizeState(this, resizedTitle, where,
system_time() + doubleClickSpeed);
} else {
fTrackingState = new ColumnDragState(this, title, where,
system_time() + doubleClickSpeed);
}
}
void
BTitleView::MouseUp(BPoint where)
{
if (fTrackingState == NULL)
return;
fTrackingState->MouseUp(where);
delete fTrackingState;
fTrackingState = NULL;
}
void
BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
{
BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
if (window == NULL)
return;
if (fTrackingState != NULL) {
int32 buttons = 0;
if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
fTrackingState->MouseMoved(where, buttons);
return;
}
switch (code) {
default:
if (InColumnResizeArea(where) && window->IsActive())
SetViewCursor(&fHorizontalResizeCursor);
else
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
break;
case B_EXITED_VIEW:
SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
break;
}
_inherited::MouseMoved(where, code, dragMessage);
}
BColumnTitle*
BTitleView::InColumnResizeArea(BPoint where) const
{
int32 count = fTitleList.CountItems();
for (int32 index = 0; index < count; index++) {
BColumnTitle* title = fTitleList.ItemAt(index);
if (title->InColumnResizeArea(where))
return title;
}
return NULL;
}
BColumnTitle*
BTitleView::FindColumnTitle(BPoint where) const
{
int32 count = fTitleList.CountItems();
for (int32 index = 0; index < count; index++) {
BColumnTitle* title = fTitleList.ItemAt(index);
if (title->Bounds().Contains(where))
return title;
}
return NULL;
}
BColumnTitle*
BTitleView::FindColumnTitle(const BColumn* column) const
{
int32 count = fTitleList.CountItems();
for (int32 index = 0; index < count; index++) {
BColumnTitle* title = fTitleList.ItemAt(index);
if (title->Column() == column)
return title;
}
return NULL;
}
BColumnTitle::BColumnTitle(BTitleView* view, BColumn* column)
:
fColumn(column),
fParent(view)
{
}
bool
BColumnTitle::InColumnResizeArea(BPoint where) const
{
BRect edge(Bounds());
edge.left = edge.right - kEdgeSize;
edge.right += kEdgeSize;
return edge.Contains(where);
}
BRect
BColumnTitle::Bounds() const
{
BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0,
fParent->Bounds().Height());
bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin;
return bounds;
}
void
BColumnTitle::Draw(BView* view, bool pressed)
{
BRect bounds(Bounds());
font_height fontHeight;
view->GetFontHeight(&fontHeight);
float baseline = floor(bounds.top + fontHeight.ascent
+ (bounds.Height() + 1 - (fontHeight.ascent + fontHeight.descent)) / 2);
BPoint titleLocation(0, baseline);
rgb_color baseColor = ui_color(B_PANEL_BACKGROUND_COLOR);
if (pressed) {
bounds.bottom--;
BRect rect(bounds);
rect.right--;
baseColor = tint_color(baseColor, B_DARKEN_1_TINT);
be_control_look->DrawButtonBackground(view, rect, rect, baseColor, 0,
BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
}
BString titleString(fColumn->Title());
view->TruncateString(&titleString, B_TRUNCATE_END,
bounds.Width() - kTitleColumnExtraMargin);
float resultingWidth = view->StringWidth(titleString.String());
switch (fColumn->Alignment()) {
case B_ALIGN_LEFT:
default:
titleLocation.x = bounds.left + 1 + kTitleColumnLeftExtraMargin;
break;
case B_ALIGN_CENTER:
titleLocation.x = bounds.left + (bounds.Width() / 2)
- (resultingWidth / 2);
break;
case B_ALIGN_RIGHT:
titleLocation.x = bounds.right - resultingWidth
- kTitleColumnRightExtraMargin;
break;
}
view->SetHighUIColor(B_PANEL_TEXT_COLOR, pressed ? B_DARKEN_1_TINT : 1.0f);
view->SetLowColor(baseColor);
view->DrawString(titleString.String(), titleLocation);
bool secondary
= (fColumn->AttrHash() == fParent->PoseView()->SecondarySort());
if (secondary
|| (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) {
BPoint center(titleLocation.x - 6,
roundf((bounds.top + bounds.bottom) / 2.0));
BPoint triangle[3];
if (fParent->PoseView()->ReverseSort()) {
triangle[0] = center + BPoint(-3.5, 1.5);
triangle[1] = center + BPoint(3.5, 1.5);
triangle[2] = center + BPoint(0.0, -2.0);
} else {
triangle[0] = center + BPoint(-3.5, -1.5);
triangle[1] = center + BPoint(3.5, -1.5);
triangle[2] = center + BPoint(0.0, 2.0);
}
uint32 flags = view->Flags();
view->SetFlags(flags | B_SUBPIXEL_PRECISE);
if (secondary) {
view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.3);
view->FillTriangle(triangle[0], triangle[1], triangle[2]);
} else {
view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, 1.6);
view->FillTriangle(triangle[0], triangle[1], triangle[2]);
}
view->SetFlags(flags);
}
view->SetHighUIColor(B_PANEL_BACKGROUND_COLOR, B_DARKEN_1_TINT);
view->StrokeLine(bounds.RightTop(), bounds.RightBottom());
}
ColumnTrackState::ColumnTrackState(BTitleView* view, BColumnTitle* title,
BPoint where, bigtime_t pastClickTime)
:
fTitleView(view),
fTitle(title),
fFirstClickPoint(where),
fPastClickTime(pastClickTime),
fHasMoved(false)
{
}
void
ColumnTrackState::MouseUp(BPoint where)
{
if (system_time() <= fPastClickTime && !fHasMoved)
Clicked(where);
else
Done(where);
}
void
ColumnTrackState::MouseMoved(BPoint where, uint32 buttons)
{
if (!fHasMoved && system_time() < fPastClickTime) {
BRect moveMargin(fFirstClickPoint, fFirstClickPoint);
moveMargin.InsetBy(-1, -1);
if (moveMargin.Contains(where))
return;
}
Moved(where, buttons);
fHasMoved = true;
}
ColumnResizeState::ColumnResizeState(BTitleView* view, BColumnTitle* title,
BPoint where, bigtime_t pastClickTime)
:
ColumnTrackState(view, title, where, pastClickTime),
fLastLineDrawPos(-1),
fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width())
- where.x)
{
DrawLine();
}
bool
ColumnResizeState::ValueChanged(BPoint where)
{
float newWidth = where.x + fInitialTrackOffset
- fTitle->fColumn->Offset();
if (newWidth < kMinColumnWidth)
newWidth = kMinColumnWidth;
return newWidth != fTitle->fColumn->Width();
}
void
ColumnResizeState::Moved(BPoint where, uint32)
{
float newWidth = where.x + fInitialTrackOffset
- fTitle->fColumn->Offset();
if (newWidth < kMinColumnWidth)
newWidth = kMinColumnWidth;
BPoseView* poseView = fTitleView->PoseView();
poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos, _DrawLine, _UndrawLine);
BRect bounds(fTitleView->Bounds());
bounds.left = fTitle->fColumn->Offset();
fTitleView->Draw(bounds, true, false);
}
void
ColumnResizeState::Done(BPoint )
{
UndrawLine();
}
void
ColumnResizeState::Clicked(BPoint )
{
UndrawLine();
}
void
ColumnResizeState::DrawLine()
{
BPoseView* poseView = fTitleView->PoseView();
ASSERT(!poseView->IsDesktopView());
BRect poseViewBounds(poseView->Bounds());
poseViewBounds.left = fTitle->Bounds().right;
fLastLineDrawPos = poseViewBounds.left;
_DrawLine(poseView, poseViewBounds.LeftTop(), poseViewBounds.LeftBottom());
}
void
ColumnResizeState::UndrawLine()
{
if (fLastLineDrawPos < 0)
return;
BRect poseViewBounds(fTitleView->PoseView()->Bounds());
poseViewBounds.left = fLastLineDrawPos;
_UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(), poseViewBounds.LeftBottom());
}
ColumnDragState::ColumnDragState(BTitleView* view, BColumnTitle* columnTitle,
BPoint where, bigtime_t pastClickTime)
:
ColumnTrackState(view, columnTitle, where, pastClickTime),
fInitialMouseTrackOffset(where.x),
fTrackingRemovedColumn(false)
{
ASSERT(columnTitle);
ASSERT(fTitle);
ASSERT(fTitle->Column());
DrawPressNoOutline();
}
void
ColumnDragState::Moved(BPoint where, uint32)
{
BRect titleBounds(fTitleView->Bounds());
bool overTitleView = titleBounds.Contains(where);
BColumnTitle* overTitle = overTitleView ? fTitleView->FindColumnTitle(where) : 0;
BRect titleBoundsWithMargin(titleBounds);
titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin);
bool inMarginRect = overTitleView || titleBoundsWithMargin.Contains(where);
bool drawOutline = false;
bool undrawOutline = false;
if (fTrackingRemovedColumn) {
if (overTitleView) {
fTitleView->EndRectTracking();
fColumnArchive.Seek(0, SEEK_SET);
BColumn* column = BColumn::InstantiateFromStream(&fColumnArchive);
ASSERT(column);
const BColumn* after = NULL;
if (overTitle)
after = overTitle->Column();
fTitleView->PoseView()->AddColumn(column, after);
fTrackingRemovedColumn = false;
fTitle = fTitleView->FindColumnTitle(column);
fInitialMouseTrackOffset += fTitle->Bounds().left;
drawOutline = true;
}
} else {
if (!inMarginRect) {
BRect rect(fTitle->Bounds());
rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5);
fColumnArchive.Seek(0, SEEK_SET);
fTitle->Column()->ArchiveToStream(&fColumnArchive);
fInitialMouseTrackOffset -= fTitle->Bounds().left;
if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(), false)) {
fTitle = 0;
fTitleView->BeginRectTracking(rect);
fTrackingRemovedColumn = true;
undrawOutline = true;
}
} else if (overTitle && overTitle != fTitle
&& (overTitle->Bounds().left >= fTitle->Bounds().right
|| where.x < overTitle->Bounds().left + fTitle->Bounds().Width())) {
BColumn* column = fTitle->Column();
fInitialMouseTrackOffset -= fTitle->Bounds().left;
fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column());
fTitle = fTitleView->FindColumnTitle(column);
fInitialMouseTrackOffset += fTitle->Bounds().left;
drawOutline = true;
} else
drawOutline = true;
}
if (drawOutline)
DrawOutline(where.x - fInitialMouseTrackOffset);
else if (undrawOutline)
UndrawOutline();
}
void
ColumnDragState::Done(BPoint )
{
if (fTrackingRemovedColumn)
fTitleView->EndRectTracking();
UndrawOutline();
}
void
ColumnDragState::Clicked(BPoint )
{
BPoseView* poseView = fTitleView->PoseView();
uint32 hash = fTitle->Column()->AttrHash();
uint32 primarySort = poseView->PrimarySort();
uint32 secondarySort = poseView->SecondarySort();
bool shift = (modifiers() & B_SHIFT_KEY) != 0;
if (hash == primarySort) {
if (shift && secondarySort) {
poseView->SetPrimarySort(secondarySort);
poseView->SetSecondarySort(primarySort);
} else
poseView->SetReverseSort(!poseView->ReverseSort());
} else if (shift) {
if (hash == secondarySort)
poseView->SetSecondarySort(0);
else
poseView->SetSecondarySort(hash);
} else {
poseView->SetPrimarySort(hash);
poseView->SetReverseSort(false);
}
if (poseView->PrimarySort() == poseView->SecondarySort())
poseView->SetSecondarySort(0);
UndrawOutline();
poseView->SortPoses();
poseView->Invalidate();
}
bool
ColumnDragState::ValueChanged(BPoint)
{
return true;
}
void
ColumnDragState::DrawPressNoOutline()
{
fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle);
}
void
ColumnDragState::DrawOutline(float pos)
{
BRect outline(fTitle->Bounds());
outline.OffsetBy(pos, 0);
fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline,
outline);
}
void
ColumnDragState::UndrawOutline()
{
fTitleView->Draw(fTitleView->Bounds(), true, false);
}
OffscreenBitmap* BTitleView::sOffscreen = new OffscreenBitmap;