1//-------------------------------------------------------------------- 2// 3// TToolTip.cpp 4// 5// Written by: Robert Polic 6// 7//-------------------------------------------------------------------- 8 9#include <Screen.h> 10#include <stdio.h> 11#include <stdlib.h> 12#include <string.h> 13 14#include <Application.h> 15#include <Roster.h> 16 17#include "TToolTip.h" 18 19#define kHOR_MARGIN 4 // hor. gap between frame and tip 20#define kVER_MARGIN 3 // ver. gap between frame and tip 21#define kTIP_HOR_OFFSET 10 // tip position right of cursor 22#define kTIP_VER_OFFSET 16 // tip position below cursor 23#define kSLOP 4 // mouse slop before tip hides 24 25#define kTOOL_TIP_DELAY_TIME 500000 // default delay time before tip shows (.5 secs.) 26#define kTOOL_TIP_HOLD_TIME 3000000 // default hold time of time (3 secs.) 27 28#define kDRAW_WINDOW_FRAME 29 30const rgb_color kVIEW_COLOR = {255, 203, 0, 255}; // view background color (light yellow) 31const rgb_color kLIGHT_VIEW_COLOR = {255, 255, 80, 255}; // top left frame highlight 32const rgb_color kDARK_VIEW_COLOR = {175, 123, 0, 255}; // bottom right frame highlight 33const rgb_color kTEXT_COLOR = {0, 0, 0, 255}; // text color (black) 34 35 36//==================================================================== 37 38TToolTip::TToolTip(tool_tip_settings *settings) 39 :BWindow(BRect(0, 0, 10, 10), "tool_tip", B_NO_BORDER_WINDOW_LOOK, 40 B_FLOATING_ALL_WINDOW_FEEL, B_AVOID_FRONT) 41{ 42 // setup the tooltip view 43 AddChild(fView = new TToolTipView(settings)); 44 // start the message loop thread 45 Run(); 46} 47 48//-------------------------------------------------------------------- 49 50void TToolTip::MessageReceived(BMessage *msg) 51{ 52 switch (msg->what) { 53 // forward interesting messages to the view 54 case B_SOME_APP_ACTIVATED: 55 case eToolTipStart: 56 case eToolTipStop: 57 PostMessage(msg, fView); 58 break; 59 default: 60 BWindow::MessageReceived(msg); 61 } 62} 63 64//-------------------------------------------------------------------- 65 66void TToolTip::GetSettings(tool_tip_settings *settings) 67{ 68 fView->GetSettings(settings); 69} 70 71//-------------------------------------------------------------------- 72 73void TToolTip::SetSettings(tool_tip_settings *settings) 74{ 75 fView->SetSettings(settings); 76} 77 78 79//==================================================================== 80 81TToolTipView::TToolTipView(tool_tip_settings *settings) 82 :BView(BRect(0, 0, 10, 10), "tool_tip", B_FOLLOW_ALL, B_WILL_DRAW) 83{ 84 // initialize tooltip settings 85 if (settings) 86 // we should probably sanity-check user defined settings (but we won't) 87 fTip.settings = *settings; 88 else { 89 // use defaults if no settings are passed 90 fTip.settings.enabled = true; 91 fTip.settings.one_time_only = false; 92 fTip.settings.delay = kTOOL_TIP_DELAY_TIME; 93 fTip.settings.hold = kTOOL_TIP_HOLD_TIME; 94 fTip.settings.font = be_plain_font; 95 } 96 97 // initialize the tip 98 fString = (char *)malloc(1); 99 fString[0] = 0; 100 101 // initialize the view 102 SetFont(&fTip.settings.font); 103 SetViewColor(kVIEW_COLOR); 104} 105 106//-------------------------------------------------------------------- 107 108TToolTipView::~TToolTipView() 109{ 110 status_t status; 111 112 // kill tool_tip thread 113 fTip.quit = true; 114 wait_for_thread(fThread, &status); 115 116 // free tip 117 free(fString); 118} 119 120//-------------------------------------------------------------------- 121 122void TToolTipView::AllAttached() 123{ 124 // initialize internal settings 125 fTip.app_active = true; 126 fTip.quit = false; 127 fTip.stopped = true; 128 129 fTip.tool_tip_view = this; 130 fTip.tool_tip_window = Window(); 131 132 // start tool_tip thread 133 resume_thread(fThread = spawn_thread((status_t (*)(void *)) ToolTipThread, 134 "tip_thread", B_DISPLAY_PRIORITY, &fTip)); 135} 136 137//-------------------------------------------------------------------- 138 139void TToolTipView::Draw(BRect /* where */) 140{ 141 char *src_strings[1]; 142 char *tmp_string; 143 char *truncated_strings[1]; 144 BFont font; 145 BRect r = Bounds(); 146 font_height finfo; 147 148 // draw border around window 149#ifdef kDRAW_WINDOW_FRAME 150 SetHighColor(0, 0, 0, 255); 151 StrokeRect(r); 152 r.InsetBy(1, 1); 153#endif 154 SetHighColor(kLIGHT_VIEW_COLOR); 155 StrokeLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top)); 156 StrokeLine(BPoint(r.left + 1, r.top), BPoint(r.right - 1, r.top)); 157 SetHighColor(kDARK_VIEW_COLOR); 158 StrokeLine(BPoint(r.right, r.top), BPoint(r.right, r.bottom)); 159 StrokeLine(BPoint(r.right - 1, r.bottom), BPoint(r.left + 1, r.bottom)); 160 161 // set pen position 162 GetFont(&font); 163 font.GetHeight(&finfo); 164 MovePenTo(kHOR_MARGIN + 1, kVER_MARGIN + finfo.ascent); 165 166 // truncate string if needed 167 src_strings[0] = fString; 168 tmp_string = (char *)malloc(strlen(fString) + 16); 169 truncated_strings[0] = tmp_string; 170 font.GetTruncatedStrings((const char **)src_strings, 1, B_TRUNCATE_END, 171 Bounds().Width() - (2 * kHOR_MARGIN) + 1, truncated_strings); 172 173 // draw string 174 SetLowColor(kVIEW_COLOR); 175 SetHighColor(kTEXT_COLOR); 176 DrawString(tmp_string); 177 free(tmp_string); 178} 179 180//-------------------------------------------------------------------- 181 182void TToolTipView::MessageReceived(BMessage *msg) 183{ 184 switch (msg->what) { 185 case B_SOME_APP_ACTIVATED: 186 msg->FindBool("active", &fTip.app_active); 187 break; 188 189 case eToolTipStart: 190 { 191 const char *str; 192 193 // extract parameters 194 msg->FindPoint("start", &fTip.start); 195 msg->FindRect("bounds", &fTip.bounds); 196 msg->FindString("string", &str); 197 free(fString); 198 fString = (char *)malloc(strlen(str) + 1); 199 strcpy(fString, str); 200 201 // force window to fit new parameters 202 AdjustWindow(); 203 204 // flag thread to reset 205 fTip.reset = true; 206 } 207 break; 208 209 case eToolTipStop: 210 // flag thread to stop 211 fTip.stop = true; 212 break; 213 } 214} 215 216//-------------------------------------------------------------------- 217 218void TToolTipView::GetSettings(tool_tip_settings *settings) 219{ 220 // return current settings 221 *settings = fTip.settings; 222} 223 224//-------------------------------------------------------------------- 225 226void TToolTipView::SetSettings(tool_tip_settings *settings) 227{ 228 bool invalidate = fTip.settings.font != settings->font; 229 230 // we should probably sanity-check user defined settings (but we won't) 231 fTip.settings = *settings; 232 233 // if the font changed, adjust window to fit 234 if (invalidate) { 235 Window()->Lock(); 236 SetFont(&fTip.settings.font); 237 AdjustWindow(); 238 Window()->Unlock(); 239 } 240} 241 242//-------------------------------------------------------------------- 243 244void TToolTipView::AdjustWindow() 245{ 246 float width; 247 float height; 248 float x; 249 float y; 250 BScreen s(B_MAIN_SCREEN_ID); 251 BRect screen = s.Frame(); 252 BWindow *wind = Window(); 253 font_height finfo; 254 255 screen.InsetBy(2, 2); // we want a 2-pixel clearance 256 fTip.settings.font.GetHeight(&finfo); 257 width = fTip.settings.font.StringWidth(fString) + (kHOR_MARGIN * 2); // string width 258 height = (finfo.ascent + finfo.descent + finfo.leading) + (kVER_MARGIN * 2); // string height 259 260 // calculate new position and size of window 261 x = fTip.start.x + kTIP_HOR_OFFSET; 262 if ((x + width) > screen.right) 263 x = screen.right - width; 264 y = fTip.start.y + kTIP_VER_OFFSET; 265 if ((y + height) > screen.bottom) { 266 y = screen.bottom - height; 267 if ((fTip.start.y >= (y - kSLOP)) && (fTip.start.y <= (y + height))) 268 y = fTip.start.y - kTIP_VER_OFFSET - height; 269 } 270 if (x < screen.left) { 271 width -= screen.left - x; 272 x = screen.left; 273 } 274 if (y < screen.top) { 275 height -= screen.top - y; 276 y = screen.top; 277 } 278 279 wind->MoveTo((int)x, (int)y); 280 wind->ResizeTo((int)width, (int)height); 281 282 // force an update 283 Invalidate(Bounds()); 284} 285 286//-------------------------------------------------------------------- 287 288status_t TToolTipView::ToolTipThread(tool_tip *tip) 289{ 290 uint32 buttons; 291 BPoint where; 292 BScreen s(B_MAIN_SCREEN_ID); 293 BRect screen = s.Frame(); 294 295 screen.InsetBy(2, 2); 296 while (!tip->quit) { 297 if (tip->tool_tip_window->LockWithTimeout(0) == B_NO_ERROR) { 298 tip->tool_tip_view->GetMouse(&where, &buttons); 299 tip->tool_tip_view->ConvertToScreen(&where); 300 301 tip->stopped = tip->stop; 302 if (tip->reset) { 303 if (tip->showing) 304 tip->tool_tip_window->Hide(); 305 tip->stop = false; 306 tip->stopped = false; 307 tip->reset = false; 308 tip->shown = false; 309 tip->showing = false; 310 tip->start_time = system_time() + tip->settings.delay; 311 } 312 else if (tip->showing) { 313 if ((tip->stop) || 314 (!tip->settings.enabled) || 315 (!tip->app_active) || 316 (!tip->bounds.Contains(where)) || 317 (tip->expire_time < system_time()) || 318 (abs((int)tip->start.x - (int)where.x) > kSLOP) || 319 (abs((int)tip->start.y - (int)where.y) > kSLOP) || 320 (buttons)) { 321 tip->tool_tip_window->Hide(); 322 tip->shown = tip->settings.one_time_only; 323 tip->showing = false; 324 tip->tip_timed_out = (tip->expire_time < system_time()); 325 tip->start_time = system_time() + tip->settings.delay; 326 } 327 } 328 else if ((tip->settings.enabled) && 329 (!tip->stopped) && 330 (tip->app_active) && 331 (!tip->shown) && 332 (!tip->tip_timed_out) && 333 (!buttons) && 334 (tip->bounds.Contains(where)) && 335 (tip->start_time < system_time())) { 336 tip->start = where; 337 tip->tool_tip_view->AdjustWindow(); 338 tip->tool_tip_window->Show(); 339 tip->tool_tip_window->Activate(false); 340 tip->showing = true; 341 tip->expire_time = system_time() + tip->settings.hold; 342 tip->start = where; 343 } 344 else if ((abs((int)tip->start.x - (int)where.x) > kSLOP) || 345 (abs((int)tip->start.y - (int)where.y) > kSLOP)) { 346 tip->start = where; 347 tip->start_time = system_time() + tip->settings.delay; 348 tip->tip_timed_out = false; 349 } 350 if (buttons) 351 tip->start_time = system_time() + tip->settings.delay; 352 tip->tool_tip_window->Unlock(); 353 } 354 snooze(50000); 355 } 356 return B_NO_ERROR; 357}