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