* Copyright (c) 1999-2000, Eric Moon.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions, and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <algorithm>
#include "TipManager.h"
#include "TipManagerImpl.h"
#include "TipWindow.h"
#include <Autolock.h>
#include <Debug.h>
#include <MessageRunner.h>
#include <Region.h>
#include <Screen.h>
using namespace std;
__USE_CORTEX_NAMESPACE
class entry_target_matches_view { public:
const BView* pView;
entry_target_matches_view(const BView* p) : pView(p) {}
bool operator()(const _ViewEntry* view) const {
return view->target() == pView;
}
};
_ViewEntry::~_ViewEntry() {
for(list<_ViewEntry*>::iterator it = m_childViews.begin();
it != m_childViews.end(); ++it) {
delete *it;
}
for(tip_entry_set::iterator it = m_tips.begin();
it != m_tips.end(); ++it) {
delete *it;
}
}
status_t _ViewEntry::add(BView* pView, const tip_entry& tipEntry) {
list<BView*> parentOrder;
BView* pCurView = pView;
while(pCurView && pCurView != m_target) {
parentOrder.push_front(pCurView);
pCurView = pCurView->Parent();
}
if(pCurView != m_target)
return B_ERROR;
_ViewEntry* viewEntry = this;
tip_entry* newTipEntry = new tip_entry(tipEntry);
for(list<BView*>::iterator itView = parentOrder.begin();
itView != parentOrder.end(); itView++) {
list<_ViewEntry*>::iterator itEntry =
find_if(
viewEntry->m_childViews.begin(),
viewEntry->m_childViews.end(),
entry_target_matches_view(*itView));
if(itEntry == viewEntry->m_childViews.end()) {
viewEntry->m_childViews.push_back(new _ViewEntry(*itView, viewEntry));
viewEntry = viewEntry->m_childViews.back();
} else
viewEntry = *itEntry;
}
if(viewEntry->m_tips.size() &&
!(*viewEntry->m_tips.begin())->rect.IsValid()) {
delete newTipEntry;
return B_ERROR;
}
tip_entry_set::iterator itFound = viewEntry->m_tips.find(newTipEntry);
if(itFound != viewEntry->m_tips.end()) {
delete *itFound;
viewEntry->m_tips.erase(itFound);
}
pair<tip_entry_set::iterator, bool> ret;
ret = viewEntry->m_tips.insert(newTipEntry);
ASSERT(ret.second);
return B_OK;
}
status_t _ViewEntry::remove(
BView* pView, const BRect& rect) {
list<BView*> parentOrder;
BView* pCurView = pView;
while(pCurView && pCurView != m_target) {
parentOrder.push_front(pCurView);
pCurView = pCurView->Parent();
}
if(pCurView != m_target)
return B_ERROR;
_ViewEntry* viewEntry = this;
for(list<BView*>::iterator itView = parentOrder.begin();
itView != parentOrder.end(); itView++) {
list<_ViewEntry*>::iterator itEntry =
find_if(
viewEntry->m_childViews.begin(),
viewEntry->m_childViews.end(),
entry_target_matches_view(*itView));
if(itEntry == viewEntry->m_childViews.end())
return B_ERROR;
viewEntry = *itEntry;
}
if(rect.IsValid()) {
tip_entry matchEntry(rect);
tip_entry_set::iterator it = viewEntry->m_tips.lower_bound(&matchEntry);
tip_entry_set::iterator itEnd = viewEntry->m_tips.upper_bound(&matchEntry);
while(it != itEnd) {
delete *it;
viewEntry->m_tips.erase(it++);
}
}
else {
for(
tip_entry_set::iterator it = viewEntry->m_tips.begin();
it != viewEntry->m_tips.end(); ++it) {
delete *it;
}
viewEntry->m_tips.clear();
for(
list<_ViewEntry*>::iterator itChild = viewEntry->m_childViews.begin();
itChild != viewEntry->m_childViews.end(); ++itChild) {
delete *itChild;
}
viewEntry->m_childViews.clear();
if(viewEntry->m_parent) {
PRINT((
"### - removing view entry from %p\n",
viewEntry->m_parent));
list<_ViewEntry*>::iterator it =
find_if(
viewEntry->m_parent->m_childViews.begin(),
viewEntry->m_parent->m_childViews.end(),
entry_target_matches_view(pView));
ASSERT(it != viewEntry->m_parent->m_childViews.end());
_ViewEntry* parent = viewEntry->m_parent;
delete viewEntry;
parent->m_childViews.erase(it);
}
}
return B_OK;
}
pair<BView*, const tip_entry*> _ViewEntry::match(
BPoint point, BPoint screenPoint) {
BRect f = Frame();
const tip_entry* pFront = fullFrameTip();
if(pFront) {
m_target->ConvertFromParent(&f);
return make_pair(m_target, f.Contains(point) ? pFront : 0);
}
if(m_tips.size()) {
tip_entry matchEntry(BRect(point, point));
tip_entry_set::iterator itCur = m_tips.lower_bound(&matchEntry);
tip_entry_set::iterator itEnd = m_tips.end();
while(itCur != itEnd) {
const tip_entry* entry = *itCur;
if(entry->rect.Contains(point))
return pair<BView*, const tip_entry*>(m_target, entry);
++itCur;
}
}
for(list<_ViewEntry*>::iterator it = m_childViews.begin();
it != m_childViews.end(); it++) {
_ViewEntry* entry = *it;
BPoint childPoint =
entry->target()->ConvertFromParent(point);
pair<BView*, const tip_entry*> ret = entry->match(
childPoint,
screenPoint);
if(ret.second)
return ret;
}
return pair<BView*, const tip_entry*>(0, 0);
}
BRect _ViewEntry::Frame() {
ASSERT(m_target);
BRect f = m_target->Frame();
return f;
}
const tip_entry* _ViewEntry::fullFrameTip() const {
if(m_tips.size()) {
const tip_entry* front = *m_tips.begin();
if(!front->rect.IsValid()) {
return front;
}
}
return 0;
}
size_t _ViewEntry::countTips() const {
size_t tips = m_tips.size();
for(list<_ViewEntry*>::const_iterator it = m_childViews.begin();
it != m_childViews.end(); it++) {
tips += (*it)->countTips();
}
return tips;
}
void _ViewEntry::dump(int indent) {
BString s;
s.SetTo('\t', indent);
PRINT((
"%s_ViewEntry '%s'\n",
s.String(),
m_target->Name()));
for(tip_entry_set::iterator it = m_tips.begin();
it != m_tips.end(); ++it) {
(*it)->dump(indent + 1);
}
for(list<_ViewEntry*>::iterator it = m_childViews.begin();
it != m_childViews.end(); it++) {
(*it)->dump(indent + 1);
}
}
_WindowEntry::~_WindowEntry() {
for(list<_ViewEntry*>::iterator it = m_views.begin();
it != m_views.end(); ++it) {
delete *it;
}
}
status_t _WindowEntry::add(
BView* view,
const tip_entry& entry) {
ASSERT(view);
if(view->Window() != target())
return B_ERROR;
BView* parent = view;
while(parent && parent->Parent())
parent = parent->Parent();
for(list<_ViewEntry*>::iterator it = m_views.begin();
it != m_views.end(); ++it)
if((*it)->target() == parent)
return (*it)->add(view, entry);
_ViewEntry* v = new _ViewEntry(parent, 0);
m_views.push_back(v);
return v->add(view, entry);
}
status_t _WindowEntry::remove(
BView* view,
const BRect& rect) {
ASSERT(view);
if(view->Window() != target())
return B_ERROR;
BView* parent = view;
while(parent && parent->Parent())
parent = parent->Parent();
for(list<_ViewEntry*>::iterator it = m_views.begin();
it != m_views.end(); ++it)
if((*it)->target() == parent) {
status_t ret = (*it)->remove(view, rect);
if(!(*it)->countTips()) {
delete *it;
m_views.erase(it);
}
return ret;
}
PRINT((
"!!! _WindowEntry::remove(): no matching view\n"));
return B_ERROR;
}
pair<BView*, const tip_entry*> _WindowEntry::match(
BPoint screenPoint) {
for(list<_ViewEntry*>::iterator it = m_views.begin();
it != m_views.end(); ++it) {
BView* target = (*it)->target();
if(target->Window() != m_target) {
PRINT((
"!!! _WindowEntry::match(): unexpected window for target view (%p)\n",
target));
return pair<BView*,const tip_entry*>(0,0);
}
pair<BView*,const tip_entry*> ret = (*it)->match(
(*it)->target()->ConvertFromScreen(screenPoint),
screenPoint);
if(ret.second)
return ret;
}
return pair<BView*,const tip_entry*>(0,0);
}
void _WindowEntry::dump(int indent) {
BString s;
s.SetTo('\t', indent);
PRINT((
"%s_WindowEntry '%s'\n",
s.String(),
m_target->Name()));
for(list<_ViewEntry*>::iterator it = m_views.begin();
it != m_views.end(); it++) {
(*it)->dump(indent + 1);
}
}
_TipManagerView::~_TipManagerView() {
for(list<_WindowEntry*>::iterator it = m_windows.begin();
it != m_windows.end(); ++it) {
delete *it;
}
if(m_messageRunner)
delete m_messageRunner;
m_tipWindow->Lock();
m_tipWindow->Quit();
}
_TipManagerView::_TipManagerView(
TipWindow* tipWindow,
TipManager* manager,
bigtime_t updatePeriod,
bigtime_t idlePeriod) :
BView(
BRect(0,0,0,0),
"_TipManagerView",
B_FOLLOW_NONE,
B_PULSE_NEEDED),
m_tipWindow(tipWindow),
m_manager(manager),
m_messageRunner(0),
m_tipWindowState(TIP_WINDOW_HIDDEN),
m_updatePeriod(updatePeriod),
m_idlePeriod(idlePeriod),
m_lastEventTime(0LL),
m_triggered(false),
m_armedTip(0) {
ASSERT(m_tipWindow);
ASSERT(m_manager);
m_tipWindow->Run();
SetEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS);
SetViewColor(B_TRANSPARENT_COLOR);
}
status_t _TipManagerView::armTip(
const BRect& screenRect,
const char* text,
TipManager::offset_mode_t offsetMode,
BPoint offset,
uint32 flags) {
ASSERT(Looper()->IsLocked());
ASSERT(text);
if(!screenRect.IsValid())
return B_BAD_VALUE;
if(m_armedTip) {
ASSERT(m_tipWindowState == TIP_WINDOW_ARMED);
delete m_armedTip;
m_armedTip = 0;
}
if(m_tipWindowState == TIP_WINDOW_VISIBLE &&
m_visibleTipRect == screenRect) {
BAutolock _l(m_tipWindow);
m_tipWindow->setText(text);
return B_OK;
}
m_armedTip = new tip_entry(
screenRect,
text,
offsetMode,
offset,
flags);
m_tipWindowState = TIP_WINDOW_ARMED;
return B_OK;
}
status_t _TipManagerView::hideTip(
const BRect& screenRect) {
ASSERT(Looper()->IsLocked());
if(m_armedTip) {
ASSERT(m_tipWindowState == TIP_WINDOW_ARMED);
if(m_armedTip->rect == screenRect) {
delete m_armedTip;
m_armedTip = 0;
m_tipWindowState = TIP_WINDOW_HIDDEN;
return B_OK;
}
}
if(m_tipWindowState != TIP_WINDOW_VISIBLE)
return B_BAD_VALUE;
if(m_visibleTipRect != screenRect)
return B_BAD_VALUE;
_hideTip();
m_tipWindowState = TIP_WINDOW_HIDDEN;
return B_OK;
}
status_t _TipManagerView::setTip(
const BRect& rect,
const char* text,
BView* view,
TipManager::offset_mode_t offsetMode,
BPoint offset,
uint32 flags) {
ASSERT(text);
ASSERT(view);
ASSERT(Looper()->IsLocked());
BWindow* window = view->Window();
if(!window)
return B_ERROR;
tip_entry e(rect, text, offsetMode, offset, flags);
for(
list<_WindowEntry*>::iterator it = m_windows.begin();
it != m_windows.end(); ++it) {
if((*it)->target() == window)
return (*it)->add(view, e);
}
_WindowEntry* windowEntry = new _WindowEntry(window);
m_windows.push_back(windowEntry);
return windowEntry->add(view, e);
}
status_t _TipManagerView::removeTip(
const BRect& rect,
BView* view) {
ASSERT(view);
ASSERT(Looper()->IsLocked());
BWindow* window = view->Window();
if(!window) {
PRINT((
"!!! _TipManagerView::removeTip(): not attached !!!\n"));
return B_ERROR;
}
for(
list<_WindowEntry*>::iterator it = m_windows.begin();
it != m_windows.end(); ++it) {
if((*it)->target() == window) {
status_t ret = (*it)->remove(view, rect);
if(!(*it)->countViews()) {
delete *it;
m_windows.erase(it);
}
return ret;
}
}
PRINT((
"!!! _TipManagerView::removeTip(): window entry not found!\n\n"));
return B_ERROR;
}
status_t _TipManagerView::removeAll(
BWindow* window) {
ASSERT(window);
ASSERT(Looper()->IsLocked());
for(
list<_WindowEntry*>::iterator it = m_windows.begin();
it != m_windows.end(); ++it) {
if((*it)->target() == window) {
delete *it;
m_windows.erase(it);
return B_OK;
}
}
PRINT((
"!!! _TipManagerView::removeAll(): window entry not found!\n"));
return B_ERROR;
}
void _TipManagerView::AttachedToWindow() {
m_messageRunner = new BMessageRunner(
BMessenger(this),
new BMessage(M_TIME_PASSED),
m_updatePeriod);
}
void _TipManagerView::KeyDown(
const char* bytes,
int32 count) {
if(!Window())
return;
if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
_hideTip();
m_tipWindowState = TIP_WINDOW_HIDDEN;
}
m_lastEventTime = system_time();
}
void _TipManagerView::MouseDown(
BPoint point) {
if(!Window())
return;
if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
_hideTip();
m_tipWindowState = TIP_WINDOW_HIDDEN;
}
m_lastEventTime = system_time();
ConvertToScreen(&point);
m_lastMousePoint = point;
}
void _TipManagerView::MouseMoved(
BPoint point,
uint32 orientation,
const BMessage* dragMessage) {
if(!Window())
return;
ConvertToScreen(&point);
bool moved = (point != m_lastMousePoint);
if(m_tipWindowState == TIP_WINDOW_ARMED) {
ASSERT(m_armedTip);
if(moved && !m_armedTip->rect.Contains(point)) {
m_tipWindowState = TIP_WINDOW_HIDDEN;
delete m_armedTip;
m_armedTip = 0;
}
}
else if(m_tipWindowState == TIP_WINDOW_VISIBLE) {
ASSERT(m_visibleTipRect.IsValid());
if(moved && !m_visibleTipRect.Contains(point)) {
_hideTip();
m_tipWindowState = TIP_WINDOW_HIDDEN;
}
return;
}
if(moved) {
m_lastMousePoint = point;
m_lastEventTime = system_time();
m_triggered = false;
}
}
void _TipManagerView::MessageReceived(
BMessage* message) {
switch(message->what) {
case M_TIME_PASSED:
_timePassed();
break;
default:
_inherited::MessageReceived(message);
}
}
inline void _TipManagerView::_timePassed() {
if(!Window())
return;
if(m_triggered)
return;
bigtime_t now = system_time();
if(now - m_lastEventTime < m_idlePeriod)
return;
m_triggered = true;
if(m_tipWindowState == TIP_WINDOW_ARMED) {
ASSERT(m_armedTip);
m_visibleTipRect = m_armedTip->rect;
_showTip(m_armedTip);
m_tipWindowState = TIP_WINDOW_VISIBLE;
delete m_armedTip;
m_armedTip = 0;
return;
}
for(
list<_WindowEntry*>::iterator it = m_windows.begin();
it != m_windows.end(); ++it) {
BWindow* window = (*it)->target();
ASSERT(window);
window->Lock();
pair<BView*, const tip_entry*> found =
(*it)->match(m_lastMousePoint);
if(!found.second || found.first->Window() != window) {
window->Unlock();
continue;
}
BRegion clipRegion;
found.first->GetClippingRegion(&clipRegion);
if(!clipRegion.Contains(
found.first->ConvertFromScreen(m_lastMousePoint))) {
window->Unlock();
continue;
}
if(found.second->rect.IsValid())
m_visibleTipRect = found.first->ConvertToScreen(
found.second->rect);
else
m_visibleTipRect = found.first->ConvertToScreen(
found.first->Bounds());
_showTip(found.second);
m_tipWindowState = TIP_WINDOW_VISIBLE;
window->Unlock();
break;
}
}
inline void _TipManagerView::_showTip(
const tip_entry* entry) {
ASSERT(m_tipWindow);
ASSERT(m_tipWindowState != TIP_WINDOW_VISIBLE);
ASSERT(entry);
BAutolock _l(m_tipWindow);
m_tipWindow->SetWorkspaces(B_ALL_WORKSPACES);
m_tipWindow->setText(entry->text.String());
BPoint offset = (entry->offset == TipManager::s_useDefaultOffset) ?
TipManager::s_defaultOffset :
entry->offset;
BPoint p;
switch(entry->offsetMode) {
case TipManager::LEFT_OFFSET_FROM_RECT:
p = m_visibleTipRect.RightTop() + offset;
break;
case TipManager::LEFT_OFFSET_FROM_POINTER:
p = m_lastMousePoint + offset;
break;
case TipManager::RIGHT_OFFSET_FROM_RECT:
p = m_visibleTipRect.LeftTop();
p.x -= offset.x;
p.y += offset.y;
p.x -= m_tipWindow->Frame().Width();
break;
case TipManager::RIGHT_OFFSET_FROM_POINTER:
p = m_lastMousePoint;
p.x -= offset.x;
p.y += offset.y;
p.x -= m_tipWindow->Frame().Width();
break;
default:
ASSERT(!"bad offset mode");
}
m_tipWindow->MoveTo(p);
BRect screenR = BScreen(m_tipWindow).Frame();
BRect tipR = m_tipWindow->Frame();
if(tipR.left < screenR.left)
tipR.left = screenR.left;
else if(tipR.right > screenR.right)
tipR.left = screenR.right - tipR.Width();
if(tipR.top < screenR.top)
tipR.top = screenR.top;
else if(tipR.bottom > screenR.bottom)
tipR.top = screenR.bottom - tipR.Height();
if(tipR.LeftTop() != p)
m_tipWindow->MoveTo(tipR.LeftTop());
if(m_tipWindow->IsHidden())
m_tipWindow->Show();
}
inline void _TipManagerView::_hideTip() {
ASSERT(m_tipWindow);
ASSERT(m_tipWindowState == TIP_WINDOW_VISIBLE);
BAutolock _l(m_tipWindow);
if(m_tipWindow->IsHidden())
return;
m_tipWindow->Hide();
}