/* * 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. */ // TipManagerImpl.cpp // e.moon 13may99 #include #include "TipManager.h" #include "TipManagerImpl.h" #include "TipWindow.h" #include #include #include #include #include //#include "debug_tools.h" using namespace std; __USE_CORTEX_NAMESPACE // -------------------------------------------------------- // // [e.moon 13oct99] now matches entry by pointer 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 impl. // -------------------------------------------------------- // // [e.moon 13oct99] delete tips & children _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; } } // add the given entry for the designated view // (which may be the target view or a child.) // returns B_OK on success, B_ERROR if the given view is // NOT a child . status_t _ViewEntry::add(BView* pView, const tip_entry& tipEntry) { // walk up the view's parent tree, building a child- // hierarchy list and looking for my target view. // The list should be in descending order: the last // entry is pView. // +++++ move to separate method list parentOrder; BView* pCurView = pView; while(pCurView && pCurView != m_target) { parentOrder.push_front(pCurView); pCurView = pCurView->Parent(); } if(pCurView != m_target) return B_ERROR; // +++++ ever so descriptive // walk down the child hierarchy, making ViewEntries as // needed _ViewEntry* viewEntry = this; // [e.moon 13oct99] clone tipEntry tip_entry* newTipEntry = new tip_entry(tipEntry); for(list::iterator itView = parentOrder.begin(); itView != parentOrder.end(); itView++) { // look for this view in children of the current entry list<_ViewEntry*>::iterator itEntry = find_if( viewEntry->m_childViews.begin(), viewEntry->m_childViews.end(), entry_target_matches_view(*itView)); // add new _ViewEntry if necessary if(itEntry == viewEntry->m_childViews.end()) { viewEntry->m_childViews.push_back(new _ViewEntry(*itView, viewEntry)); viewEntry = viewEntry->m_childViews.back(); } else viewEntry = *itEntry; } // found a home; can it hold the tip? if(viewEntry->m_tips.size() && !(*viewEntry->m_tips.begin())->rect.IsValid()) { // [e.moon 13oct99] clean up delete newTipEntry; return B_ERROR; // +++++ error: full-view tip leaves no room } // remove matching tip if any, then add the new one: // [e.moon 13oct99] ref'd by pointer tip_entry_set::iterator itFound = viewEntry->m_tips.find(newTipEntry); if(itFound != viewEntry->m_tips.end()) { delete *itFound; viewEntry->m_tips.erase(itFound); } pair ret; ret = viewEntry->m_tips.insert(newTipEntry); // something's terribly wrong if insert() failed ASSERT(ret.second); return B_OK; } // remove tip matching the given rect's upper-left corner or // all tips if rect is invalid. // returns B_OK on success, B_ERROR on failure status_t _ViewEntry::remove( BView* pView, const BRect& rect) { // walk up the view's parent tree, building a child- // hierarchy list and looking for my target view. // The list should be in descending order: the last // entry is pView. // +++++ move to separate method list parentOrder; BView* pCurView = pView; while(pCurView && pCurView != m_target) { parentOrder.push_front(pCurView); pCurView = pCurView->Parent(); } if(pCurView != m_target) return B_ERROR; // +++++ ever so descriptive // walk down the child tree to the entry for the // target view _ViewEntry* viewEntry = this; for(list::iterator itView = parentOrder.begin(); itView != parentOrder.end(); itView++) { // look for this view in children of the current entry list<_ViewEntry*>::iterator itEntry = find_if( viewEntry->m_childViews.begin(), viewEntry->m_childViews.end(), entry_target_matches_view(*itView)); // it'd better be there! if(itEntry == viewEntry->m_childViews.end()) return B_ERROR; viewEntry = *itEntry; } // remove matching entries: // [13oct99 e.moon] now ref'd by pointer; find and erase all matching tips 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) { // found one; erase it delete *it; viewEntry->m_tips.erase(it++); } } else { // invalid rect == wildcard // PRINT(( // "### _ViewEntry::remove(): WILDCARD MODE\n")); // [13oct99 e.moon] free all tip entries for( tip_entry_set::iterator it = viewEntry->m_tips.begin(); it != viewEntry->m_tips.end(); ++it) { delete *it; } viewEntry->m_tips.clear(); // PRINT(( // "### - freed all tips\n")); // [27oct99 e.moon] remove all child views for( list<_ViewEntry*>::iterator itChild = viewEntry->m_childViews.begin(); itChild != viewEntry->m_childViews.end(); ++itChild) { delete *itChild; } viewEntry->m_childViews.clear(); // PRINT(( // "### - freed all child views\n")); // remove the view entry if possible 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; } // match the given point (in target's view coordinates) // against tips in this view and child views. recurse. pair _ViewEntry::match( BPoint point, BPoint screenPoint) { // fetch this view's current frame rect BRect f = Frame(); // check for a full-frame tip: const tip_entry* pFront = fullFrameTip(); if(pFront) { // match, and stop recursing here; children can't have tips. m_target->ConvertFromParent(&f); return make_pair(m_target, f.Contains(point) ? pFront : 0); } // match against tips for my target view if(m_tips.size()) { tip_entry matchEntry(BRect(point, point)); tip_entry_set::iterator itCur = m_tips.lower_bound(&matchEntry); // tip_entry_set::iterator itCur = m_tips.begin(); tip_entry_set::iterator itEnd = m_tips.end(); while(itCur != itEnd) { // match: const tip_entry* entry = *itCur; if(entry->rect.Contains(point)) return pair(m_target, entry); ++itCur; } } // recurse through children for(list<_ViewEntry*>::iterator it = m_childViews.begin(); it != m_childViews.end(); it++) { _ViewEntry* entry = *it; BPoint childPoint = entry->target()->ConvertFromParent(point); pair ret = entry->match( childPoint, screenPoint); if(ret.second) return ret; } // nothing found return pair(0, 0); } // get frame rect (in parent view's coordinates) BRect _ViewEntry::Frame() { ASSERT(m_target); // ASSERT(m_target->Parent()); // +++++ if caching or some weird proxy mechanism // works out, return a cached BRect here // rather than asking every view every time! BRect f = m_target->Frame(); return f; } // returns pointer to sole entry in the set if it's // a full-frame tip, or 0 if there's no full-frame tip 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 impl // -------------------------------------------------------- // _WindowEntry::~_WindowEntry() { for(list<_ViewEntry*>::iterator it = m_views.begin(); it != m_views.end(); ++it) { delete *it; } } // add the given entry for the designated view (which must // be attached to the target window) // returns B_OK on success, B_ERROR if: // - the given view is NOT attached to the target window, or // - tips can't be added to this view due to it, or one of its // parents, having a full-frame tip. status_t _WindowEntry::add( BView* view, const tip_entry& entry) { ASSERT(view); if(view->Window() != target()) return B_ERROR; // find top-level view BView* parent = view; while(parent && parent->Parent()) parent = parent->Parent(); // look for a _ViewEntry matching the parent & hand off for(list<_ViewEntry*>::iterator it = m_views.begin(); it != m_views.end(); ++it) if((*it)->target() == parent) return (*it)->add(view, entry); // create _ViewEntry for the parent & hand off _ViewEntry* v = new _ViewEntry(parent, 0); m_views.push_back(v); return v->add(view, entry); } // remove tip matching the given rect's upper-left corner or // all tips if rect is invalid. // returns B_ERROR on failure -- if there are no entries for // the given view -- or B_OK otherwise. status_t _WindowEntry::remove( BView* view, const BRect& rect) { ASSERT(view); if(view->Window() != target()) return B_ERROR; // find top-level view BView* parent = view; while(parent && parent->Parent()) parent = parent->Parent(); // look for a matching _ViewEntry & hand off for(list<_ViewEntry*>::iterator it = m_views.begin(); it != m_views.end(); ++it) if((*it)->target() == parent) { // do it status_t ret = (*it)->remove(view, rect); if(!(*it)->countTips()) { // remove empty entry delete *it; m_views.erase(it); } return ret; } // not found PRINT(( "!!! _WindowEntry::remove(): no matching view\n")); return B_ERROR; } // match the given point (in screen coordinates) // against tips in this window's views. pair _WindowEntry::match( BPoint screenPoint) { for(list<_ViewEntry*>::iterator it = m_views.begin(); it != m_views.end(); ++it) { // +++++ failing on invalid views? [e.moon 27oct99] BView* target = (*it)->target(); if(target->Window() != m_target) { PRINT(( "!!! _WindowEntry::match(): unexpected window for target view (%p)\n", target)); // skip it return pair(0,0); } pair ret = (*it)->match( (*it)->target()->ConvertFromScreen(screenPoint), screenPoint); if(ret.second) return ret; } return pair(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 // -------------------------------------------------------- // // -------------------------------------------------------- // // *** ctor/dtor // -------------------------------------------------------- // _TipManagerView::~_TipManagerView() { for(list<_WindowEntry*>::iterator it = m_windows.begin(); it != m_windows.end(); ++it) { delete *it; } if(m_messageRunner) delete m_messageRunner; // clean up the tip-display window 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); // +++++ is this cheating? m_tipWindow->Run(); // request to be sent all mouse & keyboard events SetEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS); // don't draw SetViewColor(B_TRANSPARENT_COLOR); } // -------------------------------------------------------- // // *** operations // -------------------------------------------------------- // // Prepare a 'one-off' tip: this interrupts normal mouse-watching // behavior while the mouse remains in the given screen rectangle. // If it idles long enough, the tip window is displayed. 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; // cancel previous manual tip operation if(m_armedTip) { ASSERT(m_tipWindowState == TIP_WINDOW_ARMED); delete m_armedTip; m_armedTip = 0; } // is a tip with the same screen rect visible? update it: if(m_tipWindowState == TIP_WINDOW_VISIBLE && m_visibleTipRect == screenRect) { BAutolock _l(m_tipWindow); m_tipWindow->setText(text); return B_OK; } // create new entry; enter 'armed' state m_armedTip = new tip_entry( screenRect, text, offsetMode, offset, flags); m_tipWindowState = TIP_WINDOW_ARMED; return B_OK; } // Hide tip corresponding to the given screenRect, if any. // [e.moon 29nov99] Cancel 'one-off' tip for the given rect if any. status_t _TipManagerView::hideTip( const BRect& screenRect) { ASSERT(Looper()->IsLocked()); // check for armed tip if(m_armedTip) { ASSERT(m_tipWindowState == TIP_WINDOW_ARMED); if(m_armedTip->rect == screenRect) { // cancel it delete m_armedTip; m_armedTip = 0; m_tipWindowState = TIP_WINDOW_HIDDEN; return B_OK; } } // check for visible tip 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; // can't add non-attached views // construct & add an entry 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); } // create new window entry _WindowEntry* windowEntry = new _WindowEntry(window); m_windows.push_back(windowEntry); return windowEntry->add(view, e); } // [e.moon 27oct99] // +++++ broken for 'remove all' mode (triggered by invalid rect): // doesn't remove entries. 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; // can't add non-attached views } // hand off to the entry for the containing window for( list<_WindowEntry*>::iterator it = m_windows.begin(); it != m_windows.end(); ++it) { if((*it)->target() == window) { // PRINT(( // "### _TipManagerView::removeTip(%.0f,%.0f - %.0f,%.0f)\n" // " * BEFORE\n\n", // rect.left, rect.top, rect.right, rect.bottom)); // (*it)->dump(1); // remove status_t ret = (*it)->remove(view, rect); if(!(*it)->countViews()) { // emptied window entry; remove it delete *it; m_windows.erase(it); // PRINT(( // " (removed window entry)\n")); } // else { // PRINT(( // " * AFTER\n\n")); // (*it)->dump(1); // } 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()); // PRINT(( // "### _TipManagerView::removeAll()\n")); 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; } // -------------------------------------------------------- // // *** BView // -------------------------------------------------------- // void _TipManagerView::AttachedToWindow() { // PRINT(( // "### _TipManagerView::AttachedToWindow()\n")); // start the updates flowing m_messageRunner = new BMessageRunner( BMessenger(this), new BMessage(M_TIME_PASSED), m_updatePeriod); } void _TipManagerView::KeyDown( const char* bytes, int32 count) { // no longer attached? if(!Window()) return; // hide the tip if(m_tipWindowState == TIP_WINDOW_VISIBLE) { _hideTip(); m_tipWindowState = TIP_WINDOW_HIDDEN; } m_lastEventTime = system_time(); } void _TipManagerView::MouseDown( BPoint point) { // no longer attached? if(!Window()) return; // hide the tip 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) { // PRINT(( // "### _TipManagerView::MouseMoved()\n")); // no longer attached? 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)) { // mouse has moved outside the tip region, // disarming this manually-armed tip. 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)) { // hide the tip _hideTip(); m_tipWindowState = TIP_WINDOW_HIDDEN; } // don't reset timing state until the tip is closed return; } // if the mouse has moved, reset timing state: if(moved) { m_lastMousePoint = point; m_lastEventTime = system_time(); m_triggered = false; } } // -------------------------------------------------------- // // *** BHandler // -------------------------------------------------------- // void _TipManagerView::MessageReceived( BMessage* message) { switch(message->what) { case M_TIME_PASSED: _timePassed(); break; default: _inherited::MessageReceived(message); } } // -------------------------------------------------------- // // implementation // -------------------------------------------------------- // inline void _TipManagerView::_timePassed() { // PRINT(( // "### _TipManagerView::_timePassed()\n")); // no longer attached? if(!Window()) return; // has the mouse already triggered at this point? if(m_triggered) // yup; nothing more to do return; // see if the mouse has idled for long enough to trigger bigtime_t now = system_time(); if(now - m_lastEventTime < m_idlePeriod) // nope return; // trigger! m_triggered = true; if(m_tipWindowState == TIP_WINDOW_ARMED) { // a tip has been manually set ASSERT(m_armedTip); m_visibleTipRect = m_armedTip->rect; _showTip(m_armedTip); m_tipWindowState = TIP_WINDOW_VISIBLE; // clean up delete m_armedTip; m_armedTip = 0; return; } // look for a tip under the current mouse point for( list<_WindowEntry*>::iterator it = m_windows.begin(); it != m_windows.end(); ++it) { // lock the window BWindow* window = (*it)->target(); ASSERT(window); // [e.moon 21oct99] does autolock work in this context? //BAutolock _l(window); window->Lock(); // match pair found = (*it)->match(m_lastMousePoint); // if no tip found, or the view's no longer attached, bail: if(!found.second || found.first->Window() != window) { window->Unlock(); continue; } // found a tip under the mouse; see if it's obscured // by another window BRegion clipRegion; found.first->GetClippingRegion(&clipRegion); if(!clipRegion.Contains( found.first->ConvertFromScreen(m_lastMousePoint))) { // view hidden; don't show tip window->Unlock(); continue; } // show the tip 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) { // PRINT(( // "### _TipManagerView::_showTip()\n")); ASSERT(m_tipWindow); ASSERT(m_tipWindowState != TIP_WINDOW_VISIBLE); ASSERT(entry); BAutolock _l(m_tipWindow); // set text m_tipWindow->SetWorkspaces(B_ALL_WORKSPACES); m_tipWindow->setText(entry->text.String()); // figure position 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"); } // constrain window to be on-screen: 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() { // PRINT(( // "### _TipManagerView::_hideTip()\n")); ASSERT(m_tipWindow); ASSERT(m_tipWindowState == TIP_WINDOW_VISIBLE); BAutolock _l(m_tipWindow); if(m_tipWindow->IsHidden()) return; m_tipWindow->Hide(); } // END -- TipManagerImpl.cpp --