1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "TimeView.h" 37 38 #ifdef _SHOW_CALENDAR_MENU_ITEM 39 # include "CalendarMenuItem.h" 40 #endif 41 42 #ifdef _SHOW_CALENDAR_MENU_WINDOW 43 # include "CalendarMenuWindow.h" 44 #endif 45 46 #include <Debug.h> 47 #include <MenuItem.h> 48 #include <MessageRunner.h> 49 #include <PopUpMenu.h> 50 #include <Roster.h> 51 #include <Window.h> 52 #include <Screen.h> 53 54 #include <string.h> 55 56 57 const char *kShortDateFormat = "%m/%d/%y"; 58 const char *kShortEuroDateFormat = "%d/%m/%y"; 59 const char *kLongDateFormat = "%a, %B %d, %Y"; 60 const char *kLongEuroDateFormat = "%a, %d %B, %Y"; 61 62 static const char * const kMinString = "99:99 AM"; 63 static const float kHMargin = 2.0; 64 65 66 enum { 67 kMsgShowClock, 68 kMsgChangeClock, 69 kMsgHide, 70 kMsgLongClick, 71 kMsgShowCalendar 72 }; 73 74 75 TTimeView::TTimeView(float maxWidth, float height, bool showSeconds, bool milTime, bool fullDate, bool euroDate, bool) 76 : BView(BRect(-100,-100,-90,-90), "_deskbar_tv_", 77 B_FOLLOW_RIGHT | B_FOLLOW_TOP, 78 B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS), 79 fParent(NULL), 80 fShowInterval(true), // ToDo: defaulting this to true until UI is in place 81 fShowSeconds(showSeconds), 82 fMilTime(milTime), 83 fFullDate(fullDate), 84 fCanShowFullDate(false), 85 fEuroDate(euroDate), 86 fMaxWidth(maxWidth), 87 fHeight(height), 88 fOrientation(true), 89 fLongClickMessageRunner(NULL) 90 { 91 fShowingDate = false; 92 fTime = fLastTime = time(NULL); 93 fSeconds = fMinute = fHour = 0; 94 fLastTimeStr[0] = 0; 95 fLastDateStr[0] = 0; 96 fNeedToUpdate = true; 97 } 98 99 100 #ifdef AS_REPLICANT 101 TTimeView::TTimeView(BMessage *data) 102 : BView(data) 103 { 104 fTime = fLastTime = time(NULL); 105 data->FindBool("seconds", &fShowSeconds); 106 data->FindBool("miltime", &fMilTime); 107 data->FindBool("fulldate", &fFullDate); 108 data->FindBool("eurodate", &fEuroDate); 109 data->FindBool("interval", &fInterval); 110 fShowingDate = false; 111 } 112 #endif 113 114 115 TTimeView::~TTimeView() 116 { 117 StopLongClickNotifier(); 118 } 119 120 121 #ifdef AS_REPLICANT 122 BArchivable* 123 TTimeView::Instantiate(BMessage *data) 124 { 125 if (!validate_instantiation(data, "TTimeView")) 126 return NULL; 127 128 return new TTimeView(data); 129 } 130 131 132 status_t 133 TTimeView::Archive(BMessage *data, bool deep) const 134 { 135 BView::Archive(data, deep); 136 data->AddBool("seconds", fShowSeconds); 137 data->AddBool("miltime", fMilTime); 138 data->AddBool("fulldate", fFullDate); 139 data->AddBool("eurodate", fEuroDate); 140 data->AddBool("interval", fInterval); 141 data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT); 142 143 return B_OK; 144 } 145 #endif 146 147 148 void 149 TTimeView::AttachedToWindow() 150 { 151 fTime = time(NULL); 152 153 SetFont(be_plain_font); 154 if (Parent()) { 155 fParent = Parent(); 156 SetViewColor(Parent()->ViewColor()); 157 } else 158 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 159 160 ResizeToPreferred(); 161 CalculateTextPlacement(); 162 } 163 164 165 void 166 TTimeView::GetPreferredSize(float *width, float *height) 167 { 168 *height = fHeight; 169 170 GetCurrentTime(); 171 GetCurrentDate(); 172 173 // TODO: SetOrientation never gets called, fix that 174 // When in vertical mode, we want to limit the width so that it can't 175 // overlap the bevels in the parent view. 176 if (ShowingDate()) 177 *width = fOrientation ? 178 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fDateStr)) 179 : kHMargin + StringWidth(fDateStr); 180 else { 181 *width = fOrientation ? 182 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fTimeStr)) 183 : kHMargin + StringWidth(fTimeStr); 184 } 185 } 186 187 188 void 189 TTimeView::ResizeToPreferred() 190 { 191 float width, height; 192 float oldWidth = Bounds().Width(), oldHeight = Bounds().Height(); 193 194 GetPreferredSize(&width, &height); 195 if (height != oldHeight || width != oldWidth) { 196 ResizeTo(width, height); 197 MoveBy(oldWidth - width, 0); 198 fNeedToUpdate = true; 199 } 200 } 201 202 203 void 204 TTimeView::FrameMoved(BPoint) 205 { 206 Update(); 207 } 208 209 210 void 211 TTimeView::MessageReceived(BMessage* message) 212 { 213 switch (message->what) { 214 case kMsgFullDate: 215 ShowFullDate(!ShowingFullDate()); 216 break; 217 218 case kMsgShowSeconds: 219 ShowSeconds(!ShowingSeconds()); 220 break; 221 222 case kMsgMilTime: 223 ShowMilTime(!ShowingMilTime()); 224 break; 225 226 case kMsgEuroDate: 227 ShowEuroDate(!ShowingEuroDate()); 228 break; 229 230 case kMsgChangeClock: 231 // launch the time prefs app 232 be_roster->Launch("application/x-vnd.Haiku-Time"); 233 break; 234 235 case 'time': 236 Window()->PostMessage(message, Parent()); 237 break; 238 239 case kMsgLongClick: 240 { 241 StopLongClickNotifier(); 242 BPoint where; 243 message->FindPoint("where", &where); 244 ShowCalendar(where); 245 break; 246 } 247 248 case kMsgShowCalendar: 249 { 250 BRect bounds(Bounds()); 251 BPoint center(bounds.LeftTop()); 252 center += BPoint(bounds.Width() / 2, bounds.Height() / 2); 253 ShowCalendar(center); 254 break; 255 } 256 257 default: 258 BView::MessageReceived(message); 259 } 260 } 261 262 263 void 264 TTimeView::ShowCalendar(BPoint where) 265 { 266 //TODO: do nothing if the calendar is already shown 267 268 #ifdef _SHOW_CALENDAR_MENU_ITEM 269 270 BPopUpMenu *menu = new BPopUpMenu("", false, false); 271 menu->SetFont(be_plain_font); 272 273 menu->AddItem(new CalendarMenuItem()); 274 menu->ResizeToPreferred(); 275 276 BPoint point = where; 277 BScreen screen; 278 where.y = Bounds().bottom + 4; 279 280 // make sure the menu is visible and doesn't hide the date 281 ConvertToScreen(&where); 282 if (where.y + menu->Bounds().Height() > screen.Frame().bottom) 283 where.y -= menu->Bounds().Height() + 2 * Bounds().Height(); 284 285 ConvertToScreen(&point); 286 menu->Go(where, true, true, BRect(point.x - 4, point.y - 4, 287 point.x + 4, point.y + 4), true); 288 289 #elif _SHOW_CALENDAR_MENU_WINDOW 290 291 where.y = Bounds().bottom + 4.0; 292 ConvertToScreen(&where); 293 294 if (where.y >= BScreen().Frame().bottom) 295 where.y -= (Bounds().Height() + 4.0); 296 297 CalendarMenuWindow* window = new CalendarMenuWindow(where, fEuroDate); 298 window->Show(); 299 #endif 300 } 301 302 303 void 304 TTimeView::StartLongClickNotifier(BPoint where) 305 { 306 StopLongClickNotifier(); 307 308 BMessage longClickMessage(kMsgLongClick); 309 longClickMessage.AddPoint("where", where); 310 311 bigtime_t longClickThreshold; 312 get_click_speed(&longClickThreshold); 313 // use the doubleClickSpeed as a threshold 314 315 fLongClickMessageRunner = new BMessageRunner(BMessenger(this), 316 &longClickMessage, longClickThreshold, 1); 317 } 318 319 320 void 321 TTimeView::StopLongClickNotifier() 322 { 323 delete fLongClickMessageRunner; 324 fLongClickMessageRunner = NULL; 325 } 326 327 328 void 329 TTimeView::GetCurrentTime() 330 { 331 char tmp[64]; 332 tm time = *localtime(&fTime); 333 334 if (fMilTime) { 335 strftime(tmp, 64, fShowSeconds ? "%H:%M:%S" : "%H:%M", &time); 336 } else { 337 if (fShowInterval) 338 strftime(tmp, 64, fShowSeconds ? "%I:%M:%S %p" : "%I:%M %p", &time); 339 else 340 strftime(tmp, 64, fShowSeconds ? "%I:%M:%S" : "%I:%M", &time); 341 } 342 343 // remove leading 0 from time when hour is less than 10 344 const char *str = tmp; 345 if (str[0] == '0') 346 str++; 347 348 strcpy(fTimeStr, str); 349 350 fSeconds = time.tm_sec; 351 fMinute = time.tm_min; 352 fHour = time.tm_hour; 353 } 354 355 356 void 357 TTimeView::GetCurrentDate() 358 { 359 char tmp[64]; 360 tm time = *localtime(&fTime); 361 362 if (fFullDate && CanShowFullDate()) 363 strftime(tmp, 64, fEuroDate ? kLongEuroDateFormat : kLongDateFormat, &time); 364 else 365 strftime(tmp, 64, fEuroDate ? kShortEuroDateFormat : kShortDateFormat, &time); 366 367 // remove leading 0 from date when month is less than 10 (MM/DD/YY) 368 // or remove leading 0 from date when day is less than 10 (DD/MM/YY) 369 const char* str = tmp; 370 if (str[0] == '0') 371 str++; 372 373 strcpy(fDateStr, str); 374 } 375 376 377 void 378 TTimeView::Draw(BRect /*updateRect*/) 379 { 380 PushState(); 381 382 SetHighColor(ViewColor()); 383 SetLowColor(ViewColor()); 384 FillRect(Bounds()); 385 SetHighColor(0, 0, 0, 255); 386 387 if (fShowingDate) 388 DrawString(fDateStr, fDateLocation); 389 else 390 DrawString(fTimeStr, fTimeLocation); 391 392 PopState(); 393 } 394 395 396 void 397 TTimeView::MouseDown(BPoint point) 398 { 399 uint32 buttons; 400 401 Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 402 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 403 ShowClockOptions(ConvertToScreen(point)); 404 return; 405 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 406 StartLongClickNotifier(point); 407 } 408 409 // flip to/from showing date or time 410 fShowingDate = !fShowingDate; 411 if (fShowingDate) 412 fLastTime = time(NULL); 413 414 // invalidate last time/date strings and call the pulse 415 // method directly to change the display instantly 416 fLastDateStr[0] = '\0'; 417 fLastTimeStr[0] = '\0'; 418 Pulse(); 419 } 420 421 422 void 423 TTimeView::MouseUp(BPoint point) 424 { 425 StopLongClickNotifier(); 426 } 427 428 429 void 430 TTimeView::Pulse() 431 { 432 time_t curTime = time(NULL); 433 tm ct = *localtime(&curTime); 434 fTime = curTime; 435 436 GetCurrentTime(); 437 GetCurrentDate(); 438 if ((!fShowingDate && strcmp(fTimeStr, fLastTimeStr) != 0) 439 || (fShowingDate && strcmp(fDateStr, fLastDateStr) != 0)) { 440 // Update bounds when the size of the strings has changed 441 // For dates, Update() could be called two times in a row, 442 // but that should only happen very rarely 443 if ((!fShowingDate && fLastTimeStr[1] != fTimeStr[1] 444 && (fLastTimeStr[1] == ':' || fTimeStr[1] == ':')) 445 || (fShowingDate && strlen(fDateStr) != strlen(fLastDateStr)) 446 || !fLastTimeStr[0]) 447 Update(); 448 449 strcpy(fLastTimeStr, fTimeStr); 450 strcpy(fLastDateStr, fDateStr); 451 fNeedToUpdate = true; 452 } 453 454 if (fShowingDate && (fLastTime + 5 <= time(NULL))) { 455 fShowingDate = false; 456 Update(); // Needs to happen since size can change here 457 } 458 459 if (fNeedToUpdate) { 460 fSeconds = ct.tm_sec; 461 fMinute = ct.tm_min; 462 fHour = ct.tm_hour; 463 fInterval = ct.tm_hour >= 12; 464 465 Draw(Bounds()); 466 fNeedToUpdate = false; 467 } 468 } 469 470 471 void 472 TTimeView::ShowSeconds(bool on) 473 { 474 fShowSeconds = on; 475 Update(); 476 } 477 478 479 void 480 TTimeView::ShowMilTime(bool on) 481 { 482 fMilTime = on; 483 Update(); 484 } 485 486 487 void 488 TTimeView::ShowDate(bool on) 489 { 490 fShowingDate = on; 491 Update(); 492 } 493 494 495 void 496 TTimeView::ShowFullDate(bool on) 497 { 498 fFullDate = on; 499 Update(); 500 } 501 502 503 void 504 TTimeView::ShowEuroDate(bool on) 505 { 506 fEuroDate = on; 507 Update(); 508 } 509 510 511 void 512 TTimeView::AllowFullDate(bool allow) 513 { 514 fCanShowFullDate = allow; 515 516 if (allow != ShowingFullDate()) 517 Update(); 518 } 519 520 521 void 522 TTimeView::Update() 523 { 524 GetCurrentTime(); 525 GetCurrentDate(); 526 527 ResizeToPreferred(); 528 CalculateTextPlacement(); 529 530 if (fParent) { 531 BMessage reformat('Trfm'); 532 fParent->MessageReceived(&reformat); 533 // time string format realign 534 fParent->Invalidate(); 535 } 536 } 537 538 539 void 540 TTimeView::SetOrientation(bool o) 541 { 542 fOrientation = o; 543 CalculateTextPlacement(); 544 Invalidate(); 545 } 546 547 548 void 549 TTimeView::CalculateTextPlacement() 550 { 551 BRect bounds(Bounds()); 552 553 fDateLocation.x = 0.0; 554 fTimeLocation.x = 0.0; 555 556 BFont font; 557 GetFont(&font); 558 const char* stringArray[1]; 559 stringArray[0] = fTimeStr; 560 BRect rectArray[1]; 561 escapement_delta delta = { 0.0, 0.0 }; 562 font.GetBoundingBoxesForStrings(stringArray, 1, B_SCREEN_METRIC, &delta, 563 rectArray); 564 565 fTimeLocation.y = fDateLocation.y = ceilf((bounds.Height() - 566 rectArray[0].Height() + 1.0) / 2.0 - rectArray[0].top); 567 } 568 569 570 void 571 TTimeView::ShowClockOptions(BPoint point) 572 { 573 BPopUpMenu *menu = new BPopUpMenu("", false, false); 574 menu->SetFont(be_plain_font); 575 BMenuItem *item; 576 577 item = new BMenuItem("Change Time" B_UTF8_ELLIPSIS, new BMessage(kMsgChangeClock)); 578 menu->AddItem(item); 579 580 item = new BMenuItem("Hide Time", new BMessage('time')); 581 menu->AddItem(item); 582 583 #if defined(_SHOW_CALENDAR_MENU_ITEM) || defined(_SHOW_CALENDAR_MENU_WINDOW) 584 item = new BMenuItem("Show Calendar" B_UTF8_ELLIPSIS, new BMessage(kMsgShowCalendar)); 585 menu->AddItem(item); 586 #endif 587 588 menu->SetTargetForItems(this); 589 // Changed to accept screen coord system point; 590 // not constrained to this view now 591 menu->Go(point, true, true, BRect(point.x - 4, point.y - 4, 592 point.x + 4, point.y +4), true); 593 } 594