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 #include <string.h> 39 40 #include <Debug.h> 41 #include <MenuItem.h> 42 #include <MessageRunner.h> 43 #include <PopUpMenu.h> 44 #include <Roster.h> 45 #include <Screen.h> 46 #include <Window.h> 47 48 #include "CalendarMenuWindow.h" 49 50 51 const char* kShortDateFormat = "%m/%d/%y"; 52 const char* kShortEuroDateFormat = "%d/%m/%y"; 53 const char* kLongDateFormat = "%a, %B %d, %Y"; 54 const char* kLongEuroDateFormat = "%a, %d %B, %Y"; 55 56 static const char* const kMinString = "99:99 AM"; 57 static const float kHMargin = 2.0; 58 59 60 enum { 61 kShowClock, 62 kChangeClock, 63 kHide, 64 kLongClick, 65 kShowCalendar 66 }; 67 68 69 TTimeView::TTimeView(float maxWidth, float height, bool showSeconds, 70 bool milTime, bool fullDate, bool euroDate, bool) 71 : 72 BView(BRect(-100,-100,-90,-90), "_deskbar_tv_", 73 B_FOLLOW_RIGHT | B_FOLLOW_TOP, 74 B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS), 75 fParent(NULL), 76 fShowInterval(true), // ToDo: defaulting this to true until UI is in place 77 fShowSeconds(showSeconds), 78 fMilTime(milTime), 79 fFullDate(fullDate), 80 fCanShowFullDate(false), 81 fEuroDate(euroDate), 82 fMaxWidth(maxWidth), 83 fHeight(height), 84 fOrientation(true), 85 fLongClickMessageRunner(NULL) 86 { 87 fShowingDate = false; 88 fTime = fLastTime = time(NULL); 89 fSeconds = fMinute = fHour = 0; 90 fLastTimeStr[0] = 0; 91 fLastDateStr[0] = 0; 92 fNeedToUpdate = true; 93 } 94 95 96 #ifdef AS_REPLICANT 97 TTimeView::TTimeView(BMessage* data) 98 : BView(data) 99 { 100 fTime = fLastTime = time(NULL); 101 data->FindBool("seconds", &fShowSeconds); 102 data->FindBool("miltime", &fMilTime); 103 data->FindBool("fulldate", &fFullDate); 104 data->FindBool("eurodate", &fEuroDate); 105 data->FindBool("interval", &fInterval); 106 fShowingDate = false; 107 } 108 #endif 109 110 111 TTimeView::~TTimeView() 112 { 113 StopLongClickNotifier(); 114 } 115 116 117 #ifdef AS_REPLICANT 118 BArchivable* 119 TTimeView::Instantiate(BMessage* data) 120 { 121 if (!validate_instantiation(data, "TTimeView")) 122 return NULL; 123 124 return new TTimeView(data); 125 } 126 127 128 status_t 129 TTimeView::Archive(BMessage* data, bool deep) const 130 { 131 BView::Archive(data, deep); 132 data->AddBool("seconds", fShowSeconds); 133 data->AddBool("miltime", fMilTime); 134 data->AddBool("fulldate", fFullDate); 135 data->AddBool("eurodate", fEuroDate); 136 data->AddBool("interval", fInterval); 137 data->AddInt32("deskbar:private_align", B_ALIGN_RIGHT); 138 139 return B_OK; 140 } 141 #endif 142 143 144 void 145 TTimeView::AttachedToWindow() 146 { 147 fTime = time(NULL); 148 149 SetFont(be_plain_font); 150 if (Parent()) { 151 fParent = Parent(); 152 SetViewColor(Parent()->ViewColor()); 153 } else 154 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 155 156 ResizeToPreferred(); 157 CalculateTextPlacement(); 158 } 159 160 161 void 162 TTimeView::GetPreferredSize(float* width, float* height) 163 { 164 *height = fHeight; 165 166 GetCurrentTime(); 167 GetCurrentDate(); 168 169 // TODO: SetOrientation never gets called, fix that 170 // When in vertical mode, we want to limit the width so that it can't 171 // overlap the bevels in the parent view. 172 if (ShowingDate()) 173 *width = fOrientation ? 174 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fDateStr)) 175 : kHMargin + StringWidth(fDateStr); 176 else { 177 *width = fOrientation ? 178 min_c(fMaxWidth - kHMargin, kHMargin + StringWidth(fTimeStr)) 179 : kHMargin + StringWidth(fTimeStr); 180 } 181 } 182 183 184 void 185 TTimeView::ResizeToPreferred() 186 { 187 float width, height; 188 float oldWidth = Bounds().Width(), oldHeight = Bounds().Height(); 189 190 GetPreferredSize(&width, &height); 191 if (height != oldHeight || width != oldWidth) { 192 ResizeTo(width, height); 193 MoveBy(oldWidth - width, 0); 194 fNeedToUpdate = true; 195 } 196 } 197 198 199 void 200 TTimeView::FrameMoved(BPoint) 201 { 202 Update(); 203 } 204 205 206 void 207 TTimeView::MessageReceived(BMessage* message) 208 { 209 switch (message->what) { 210 case kFullDate: 211 ShowFullDate(!ShowingFullDate()); 212 break; 213 214 case kShowSeconds: 215 ShowSeconds(!ShowingSeconds()); 216 break; 217 218 case kMilTime: 219 ShowMilTime(!ShowingMilTime()); 220 break; 221 222 case kEuroDate: 223 ShowEuroDate(!ShowingEuroDate()); 224 break; 225 226 case kChangeClock: 227 // launch the time prefs app 228 be_roster->Launch("application/x-vnd.Haiku-Time"); 229 break; 230 231 case 'time': 232 Window()->PostMessage(message, Parent()); 233 break; 234 235 case kLongClick: 236 { 237 StopLongClickNotifier(); 238 BPoint where; 239 message->FindPoint("where", &where); 240 ShowCalendar(where); 241 break; 242 } 243 244 case kShowCalendar: 245 { 246 BRect bounds(Bounds()); 247 BPoint center(bounds.LeftTop()); 248 center += BPoint(bounds.Width() / 2, bounds.Height() / 2); 249 ShowCalendar(center); 250 break; 251 } 252 253 default: 254 BView::MessageReceived(message); 255 } 256 } 257 258 259 void 260 TTimeView::ShowCalendar(BPoint where) 261 { 262 if (fCalendarWindow.IsValid()) { 263 // If the calendar is already shown, just activate it 264 BMessage activate(B_SET_PROPERTY); 265 activate.AddSpecifier("Active"); 266 activate.AddBool("data", true); 267 268 if (fCalendarWindow.SendMessage(&activate) == B_OK) 269 return; 270 } 271 272 where.y = Bounds().bottom + 4.0; 273 ConvertToScreen(&where); 274 275 if (where.y >= BScreen().Frame().bottom) 276 where.y -= (Bounds().Height() + 4.0); 277 278 CalendarMenuWindow* window = new CalendarMenuWindow(where, fEuroDate); 279 fCalendarWindow = BMessenger(window); 280 281 window->Show(); 282 } 283 284 285 void 286 TTimeView::StartLongClickNotifier(BPoint where) 287 { 288 StopLongClickNotifier(); 289 290 BMessage longClickMessage(kLongClick); 291 longClickMessage.AddPoint("where", where); 292 293 bigtime_t longClickThreshold; 294 get_click_speed(&longClickThreshold); 295 // use the doubleClickSpeed as a threshold 296 297 fLongClickMessageRunner = new BMessageRunner(BMessenger(this), 298 &longClickMessage, longClickThreshold, 1); 299 } 300 301 302 void 303 TTimeView::StopLongClickNotifier() 304 { 305 delete fLongClickMessageRunner; 306 fLongClickMessageRunner = NULL; 307 } 308 309 310 void 311 TTimeView::GetCurrentTime() 312 { 313 char tmp[64]; 314 tm time = *localtime(&fTime); 315 316 if (fMilTime) { 317 strftime(tmp, 64, fShowSeconds ? "%H:%M:%S" : "%H:%M", &time); 318 } else { 319 if (fShowInterval) 320 strftime(tmp, 64, fShowSeconds ? "%I:%M:%S %p" : "%I:%M %p", &time); 321 else 322 strftime(tmp, 64, fShowSeconds ? "%I:%M:%S" : "%I:%M", &time); 323 } 324 325 // remove leading 0 from time when hour is less than 10 326 const char *str = tmp; 327 if (str[0] == '0') 328 str++; 329 330 strcpy(fTimeStr, str); 331 332 fSeconds = time.tm_sec; 333 fMinute = time.tm_min; 334 fHour = time.tm_hour; 335 } 336 337 338 void 339 TTimeView::GetCurrentDate() 340 { 341 char tmp[64]; 342 tm time = *localtime(&fTime); 343 344 if (fFullDate && CanShowFullDate()) 345 strftime(tmp, 64, fEuroDate ? kLongEuroDateFormat : kLongDateFormat, 346 &time); 347 else 348 strftime(tmp, 64, fEuroDate ? kShortEuroDateFormat : kShortDateFormat, 349 &time); 350 351 // remove leading 0 from date when month is less than 10 (MM/DD/YY) 352 // or remove leading 0 from date when day is less than 10 (DD/MM/YY) 353 const char* str = tmp; 354 if (str[0] == '0') 355 str++; 356 357 strcpy(fDateStr, str); 358 } 359 360 361 void 362 TTimeView::Draw(BRect /*updateRect*/) 363 { 364 PushState(); 365 366 SetHighColor(ViewColor()); 367 SetLowColor(ViewColor()); 368 FillRect(Bounds()); 369 SetHighColor(0, 0, 0, 255); 370 371 if (fShowingDate) 372 DrawString(fDateStr, fDateLocation); 373 else 374 DrawString(fTimeStr, fTimeLocation); 375 376 PopState(); 377 } 378 379 380 void 381 TTimeView::MouseDown(BPoint point) 382 { 383 uint32 buttons; 384 385 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 386 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 387 ShowClockOptions(ConvertToScreen(point)); 388 return; 389 } else if (buttons == B_PRIMARY_MOUSE_BUTTON) { 390 StartLongClickNotifier(point); 391 } 392 393 // flip to/from showing date or time 394 fShowingDate = !fShowingDate; 395 if (fShowingDate) 396 fLastTime = time(NULL); 397 398 // invalidate last time/date strings and call the pulse 399 // method directly to change the display instantly 400 fLastDateStr[0] = '\0'; 401 fLastTimeStr[0] = '\0'; 402 Pulse(); 403 } 404 405 406 void 407 TTimeView::MouseUp(BPoint point) 408 { 409 StopLongClickNotifier(); 410 } 411 412 413 void 414 TTimeView::Pulse() 415 { 416 time_t curTime = time(NULL); 417 tm ct = *localtime(&curTime); 418 fTime = curTime; 419 420 GetCurrentTime(); 421 GetCurrentDate(); 422 if ((!fShowingDate && strcmp(fTimeStr, fLastTimeStr) != 0) 423 || (fShowingDate && strcmp(fDateStr, fLastDateStr) != 0)) { 424 // Update bounds when the size of the strings has changed 425 // For dates, Update() could be called two times in a row, 426 // but that should only happen very rarely 427 if ((!fShowingDate && fLastTimeStr[1] != fTimeStr[1] 428 && (fLastTimeStr[1] == ':' || fTimeStr[1] == ':')) 429 || (fShowingDate && strlen(fDateStr) != strlen(fLastDateStr)) 430 || !fLastTimeStr[0]) 431 Update(); 432 433 strcpy(fLastTimeStr, fTimeStr); 434 strcpy(fLastDateStr, fDateStr); 435 fNeedToUpdate = true; 436 } 437 438 if (fShowingDate && (fLastTime + 5 <= time(NULL))) { 439 fShowingDate = false; 440 Update(); // Needs to happen since size can change here 441 } 442 443 if (fNeedToUpdate) { 444 fSeconds = ct.tm_sec; 445 fMinute = ct.tm_min; 446 fHour = ct.tm_hour; 447 fInterval = ct.tm_hour >= 12; 448 449 Draw(Bounds()); 450 fNeedToUpdate = false; 451 } 452 } 453 454 455 void 456 TTimeView::ShowSeconds(bool on) 457 { 458 fShowSeconds = on; 459 Update(); 460 } 461 462 463 void 464 TTimeView::ShowMilTime(bool on) 465 { 466 fMilTime = on; 467 Update(); 468 } 469 470 471 void 472 TTimeView::ShowDate(bool on) 473 { 474 fShowingDate = on; 475 Update(); 476 } 477 478 479 void 480 TTimeView::ShowFullDate(bool on) 481 { 482 fFullDate = on; 483 Update(); 484 } 485 486 487 void 488 TTimeView::ShowEuroDate(bool on) 489 { 490 fEuroDate = on; 491 Update(); 492 } 493 494 495 void 496 TTimeView::AllowFullDate(bool allow) 497 { 498 fCanShowFullDate = allow; 499 500 if (allow != ShowingFullDate()) 501 Update(); 502 } 503 504 505 void 506 TTimeView::Update() 507 { 508 GetCurrentTime(); 509 GetCurrentDate(); 510 511 ResizeToPreferred(); 512 CalculateTextPlacement(); 513 514 if (fParent) { 515 BMessage reformat('Trfm'); 516 fParent->MessageReceived(&reformat); 517 // time string format realign 518 fParent->Invalidate(); 519 } 520 } 521 522 523 void 524 TTimeView::SetOrientation(bool o) 525 { 526 fOrientation = o; 527 CalculateTextPlacement(); 528 Invalidate(); 529 } 530 531 532 void 533 TTimeView::CalculateTextPlacement() 534 { 535 BRect bounds(Bounds()); 536 537 fDateLocation.x = 0.0; 538 fTimeLocation.x = 0.0; 539 540 BFont font; 541 GetFont(&font); 542 const char* stringArray[1]; 543 stringArray[0] = fTimeStr; 544 BRect rectArray[1]; 545 escapement_delta delta = { 0.0, 0.0 }; 546 font.GetBoundingBoxesForStrings(stringArray, 1, B_SCREEN_METRIC, &delta, 547 rectArray); 548 549 fTimeLocation.y = fDateLocation.y = ceilf((bounds.Height() - 550 rectArray[0].Height() + 1.0) / 2.0 - rectArray[0].top); 551 } 552 553 554 void 555 TTimeView::ShowClockOptions(BPoint point) 556 { 557 BPopUpMenu* menu = new BPopUpMenu("", false, false); 558 menu->SetFont(be_plain_font); 559 BMenuItem* item; 560 561 item = new BMenuItem("Change time" B_UTF8_ELLIPSIS, 562 new BMessage(kChangeClock)); 563 menu->AddItem(item); 564 565 item = new BMenuItem("Hide time", new BMessage('time')); 566 menu->AddItem(item); 567 568 item = new BMenuItem("Show calendar" B_UTF8_ELLIPSIS, 569 new BMessage(kShowCalendar)); 570 menu->AddItem(item); 571 572 menu->SetTargetForItems(this); 573 // Changed to accept screen coord system point; 574 // not constrained to this view now 575 menu->Go(point, true, true, BRect(point.x - 4, point.y - 4, 576 point.x + 4, point.y +4), true); 577 } 578 579