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 30 trademarks of Be Incorporated in the United States and other countries. Other 31 brand product names are registered trademarks or trademarks of their respective 32 holders. 33 All rights reserved. 34 */ 35 36 #include <Debug.h> 37 38 #include <errno.h> 39 #include <stdio.h> 40 #include <string.h> 41 #include <time.h> 42 #include <unistd.h> 43 #include <algorithm> 44 45 #include <fs_index.h> 46 #include <fs_info.h> 47 48 #include <Application.h> 49 #include <Beep.h> 50 #include <Bitmap.h> 51 #include <Catalog.h> 52 #include <ControlLook.h> 53 #include <Directory.h> 54 #include <FindDirectory.h> 55 #include <Locale.h> 56 #include <MenuItem.h> 57 #include <NodeInfo.h> 58 #include <NodeMonitor.h> 59 #include <Path.h> 60 #include <PopUpMenu.h> 61 #include <Roster.h> 62 #include <Screen.h> 63 #include <Volume.h> 64 #include <VolumeRoster.h> 65 #include <Window.h> 66 67 #include "icons_logo.h" 68 #include "BarApp.h" 69 #include "DeskbarUtils.h" 70 #include "ResourceSet.h" 71 #include "StatusView.h" 72 #include "StatusViewShelf.h" 73 #include "TimeView.h" 74 75 using std::max; 76 77 #ifdef DB_ADDONS 78 // Add-on support 79 // 80 // Item - internal item list (node, eref, etc) 81 // Icon - physical replicant handed to the DeskbarClass class 82 // AddOn - attribute based add-on 83 84 const char* const kInstantiateItemCFunctionName = "instantiate_deskbar_item"; 85 const char* const kInstantiateEntryCFunctionName = "instantiate_deskbar_entry"; 86 const char* const kReplicantSettingsFile = "Deskbar_replicants"; 87 const char* const kReplicantPathField = "replicant_path"; 88 89 float sMinimumWindowWidth = kGutter + kMinimumTrayWidth + kDragRegionWidth; 90 91 92 static void 93 DumpItem(DeskbarItemInfo* item) 94 { 95 printf("is addon: %i, id: %li\n", item->isAddOn, item->id); 96 printf("entry_ref: %ld, %Ld, %s\n", item->entryRef.device, 97 item->entryRef.directory, item->entryRef.name); 98 printf("node_ref: %ld, %Ld\n", item->nodeRef.device, item->nodeRef.node); 99 } 100 101 102 static void 103 DumpList(BList* itemlist) 104 { 105 int32 count = itemlist->CountItems() - 1; 106 if (count < 0) { 107 printf("no items in list\n"); 108 return; 109 } 110 for (int32 i = count ; i >= 0 ; i--) { 111 DeskbarItemInfo* item = (DeskbarItemInfo*)itemlist->ItemAt(i); 112 if (!item) 113 continue; 114 115 DumpItem(item); 116 } 117 } 118 #endif /* DB_ADDONS */ 119 120 121 #undef B_TRANSLATE_CONTEXT 122 #define B_TRANSLATE_CONTEXT "Tray" 123 124 // don't change the name of this view to anything other than "Status"! 125 126 TReplicantTray::TReplicantTray(TBarView* parent, bool vertical) 127 : BView(BRect(0, 0, 1, 1), "Status", B_FOLLOW_LEFT | B_FOLLOW_TOP, 128 B_WILL_DRAW | B_FRAME_EVENTS), 129 fClock(NULL), 130 fBarView(parent), 131 fShelf(new TReplicantShelf(this)), 132 fMultiRowMode(vertical), 133 fMinimumTrayWidth(kMinimumTrayWidth), 134 fAlignmentSupport(false) 135 { 136 // init the minimum window width according to the logo. 137 const BBitmap* logoBitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE, 138 R_LeafLogoBitmap); 139 if (logoBitmap != NULL) { 140 sMinimumWindowWidth = max_c(sMinimumWindowWidth, 141 2 * (logoBitmap->Bounds().Width() + 8)); 142 fMinimumTrayWidth = sMinimumWindowWidth - kGutter - kDragRegionWidth; 143 } 144 } 145 146 147 TReplicantTray::~TReplicantTray() 148 { 149 delete fShelf; 150 } 151 152 153 void 154 TReplicantTray::AttachedToWindow() 155 { 156 BView::AttachedToWindow(); 157 158 if (be_control_look != NULL) { 159 SetViewColor(Parent()->ViewColor()); 160 } else { 161 SetViewColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 162 B_DARKEN_1_TINT)); 163 } 164 SetDrawingMode(B_OP_COPY); 165 166 Window()->SetPulseRate(1000000); 167 DealWithClock(fBarView->ShowingClock()); 168 169 170 #ifdef DB_ADDONS 171 // load addons and rehydrate archives 172 #if !defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST) 173 InitAddOnSupport(); 174 #endif 175 #endif 176 ResizeToPreferred(); 177 } 178 179 180 void 181 TReplicantTray::DetachedFromWindow() 182 { 183 #ifdef DB_ADDONS 184 // clean up add-on support 185 #if !defined(HAIKU_TARGET_PLATFORM_LIBBE_TEST) 186 DeleteAddOnSupport(); 187 #endif 188 #endif 189 BView::DetachedFromWindow(); 190 } 191 192 193 void 194 TReplicantTray::RememberClockSettings() 195 { 196 if (fClock) { 197 desk_settings* settings = ((TBarApp*)be_app)->Settings(); 198 199 settings->timeShowSeconds = fClock->ShowingSeconds(); 200 } 201 } 202 203 204 bool 205 TReplicantTray::ShowingSeconds() 206 { 207 if (fClock) 208 return fClock->ShowingSeconds(); 209 return false; 210 } 211 212 213 void 214 TReplicantTray::DealWithClock(bool showClock) 215 { 216 fBarView->ShowClock(showClock); 217 218 if (showClock) { 219 if (!fClock) { 220 desk_settings* settings = ((TBarApp*)be_app)->Settings(); 221 222 fClock = new TTimeView(fMinimumTrayWidth, kMaxReplicantHeight - 1.0, 223 settings->timeShowSeconds, 224 false); 225 AddChild(fClock); 226 227 fClock->MoveTo(Bounds().right - fClock->Bounds().Width() - 1, 2); 228 } 229 } else { 230 if (fClock) { 231 RememberClockSettings(); 232 233 fClock->RemoveSelf(); 234 delete fClock; 235 fClock = NULL; 236 } 237 } 238 } 239 240 241 /*! Width is set to a minimum of kMinimumReplicantCount by kMaxReplicantWidth 242 if not in multirowmode and greater than kMinimumReplicantCount 243 the width should be calculated based on the actual 244 replicant widths 245 */ 246 void 247 TReplicantTray::GetPreferredSize(float* preferredWidth, float* preferredHeight) 248 { 249 float width = 0, height = kMinimumTrayHeight; 250 251 if (fMultiRowMode) { 252 if (fShelf->CountReplicants() > 0) 253 height = fRightBottomReplicant.bottom; 254 255 // the height will be uniform for the number of rows necessary to show 256 // all the reps + any gutters necessary for spacing 257 int32 rowCount = (int32)(height / kMaxReplicantHeight); 258 height = kGutter + (rowCount * kMaxReplicantHeight) 259 + ((rowCount - 1) * kIconGap) + kGutter; 260 height = max(kMinimumTrayHeight, height); 261 width = fMinimumTrayWidth; 262 } else { 263 // if last replicant overruns clock then resize to accomodate 264 if (fShelf->CountReplicants() > 0) { 265 if (fBarView->ShowingClock() 266 && fRightBottomReplicant.right + 6 >= fClock->Frame().left) { 267 width = fRightBottomReplicant.right + 6 268 + fClock->Frame().Width(); 269 } else 270 width = fRightBottomReplicant.right + 3; 271 } 272 273 // this view has a fixed minimum width 274 width = max(fMinimumTrayWidth, width); 275 } 276 277 *preferredWidth = width; 278 // add 1 for the border 279 *preferredHeight = height + 1; 280 } 281 282 283 void 284 TReplicantTray::AdjustPlacement() 285 { 286 // called when an add-on has been added or removed 287 // need to resize the parent of this accordingly 288 289 BRect bounds = Bounds(); 290 float width, height; 291 GetPreferredSize(&width, &height); 292 293 if (width == bounds.Width() && height == bounds.Height()) { 294 // no need to change anything 295 return; 296 } 297 298 Parent()->ResizeToPreferred(); 299 fBarView->UpdatePlacement(); 300 Parent()->Invalidate(); 301 Invalidate(); 302 } 303 304 305 void 306 TReplicantTray::MessageReceived(BMessage* message) 307 { 308 switch (message->what) { 309 case 'time': 310 // from context menu in clock and in this view 311 DealWithClock(!fBarView->ShowingClock()); 312 RealignReplicants(); 313 AdjustPlacement(); 314 break; 315 316 case 'Trfm': 317 // time string reformat -> realign 318 DealWithClock(fBarView->ShowingClock()); 319 RealignReplicants(); 320 AdjustPlacement(); 321 break; 322 323 case kShowSeconds: 324 case kFullDate: 325 if (fClock != NULL) 326 Window()->PostMessage(message, fClock); 327 break; 328 329 #ifdef DB_ADDONS 330 case B_NODE_MONITOR: 331 HandleEntryUpdate(message); 332 break; 333 #endif 334 335 default: 336 BView::MessageReceived(message); 337 break; 338 } 339 } 340 341 342 void 343 TReplicantTray::ShowReplicantMenu(BPoint point) 344 { 345 BPopUpMenu* menu = new BPopUpMenu("", false, false); 346 menu->SetFont(be_plain_font); 347 348 // If clock is visible show the extended menu, otherwise show "Show Time" 349 350 if (fBarView->ShowingClock()) 351 fClock->ShowClockOptions(ConvertToScreen(point)); 352 else { 353 BMenuItem* item = new BMenuItem(B_TRANSLATE("Show Time"), 354 new BMessage('time')); 355 menu->AddItem(item); 356 menu->SetTargetForItems(this); 357 BPoint where = ConvertToScreen(point); 358 menu->Go(where, true, true, BRect(where - BPoint(4, 4), 359 where + BPoint(4, 4)), true); 360 } 361 } 362 363 364 void 365 TReplicantTray::MouseDown(BPoint where) 366 { 367 #ifdef DB_ADDONS 368 if (modifiers() & B_CONTROL_KEY) 369 DumpList(fItemList); 370 #endif 371 372 uint32 buttons; 373 374 Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 375 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 376 ShowReplicantMenu(where); 377 } else { 378 BPoint save = where; 379 bigtime_t doubleClickSpeed; 380 bigtime_t start = system_time(); 381 uint32 buttons; 382 383 get_click_speed(&doubleClickSpeed); 384 385 do { 386 if (fabs(where.x - save.x) > 4 || fabs(where.y - save.y) > 4) 387 // user moved out of bounds of click area 388 break; 389 390 if ((system_time() - start) > (2 * doubleClickSpeed)) { 391 ShowReplicantMenu(where); 392 break; 393 } 394 395 snooze(50000); 396 GetMouse(&where, &buttons); 397 } while (buttons); 398 } 399 BView::MouseDown(where); 400 } 401 402 #ifdef DB_ADDONS 403 404 void 405 TReplicantTray::InitAddOnSupport() 406 { 407 // list to maintain refs to each rep added/deleted 408 fItemList = new BList(); 409 BPath path; 410 411 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) == B_OK) { 412 path.Append(kReplicantSettingsFile); 413 414 BFile file(path.Path(), B_READ_ONLY); 415 if (file.InitCheck() == B_OK) { 416 status_t result; 417 BEntry entry; 418 int32 id; 419 BString path; 420 if (fAddOnSettings.Unflatten(&file) == B_OK) { 421 for (int32 i = 0; fAddOnSettings.FindString(kReplicantPathField, 422 i, &path) == B_OK; i++) { 423 if (entry.SetTo(path.String()) == B_OK && entry.Exists()) { 424 result = LoadAddOn(&entry, &id, false); 425 } else 426 result = B_ENTRY_NOT_FOUND; 427 428 if (result != B_OK) { 429 fAddOnSettings.RemoveData(kReplicantPathField, i); 430 --i; 431 } 432 } 433 } 434 } 435 } 436 } 437 438 439 void 440 TReplicantTray::DeleteAddOnSupport() 441 { 442 _SaveSettings(); 443 444 for (int32 i = fItemList->CountItems(); i-- > 0 ;) { 445 DeskbarItemInfo* item = (DeskbarItemInfo*)fItemList->RemoveItem(i); 446 if (item) { 447 if (item->isAddOn) 448 watch_node(&(item->nodeRef), B_STOP_WATCHING, this, Window()); 449 450 delete item; 451 } 452 } 453 delete fItemList; 454 455 // stop the volume mount/unmount watch 456 stop_watching(this, Window()); 457 } 458 459 460 DeskbarItemInfo* 461 TReplicantTray::DeskbarItemFor(node_ref& nodeRef) 462 { 463 for (int32 i = fItemList->CountItems(); i-- > 0 ;) { 464 DeskbarItemInfo* item = (DeskbarItemInfo*)fItemList->ItemAt(i); 465 if (item == NULL) 466 continue; 467 468 if (item->nodeRef == nodeRef) 469 return item; 470 } 471 472 return NULL; 473 } 474 475 476 DeskbarItemInfo* 477 TReplicantTray::DeskbarItemFor(int32 id) 478 { 479 for (int32 i = fItemList->CountItems(); i-- > 0 ;) { 480 DeskbarItemInfo* item = (DeskbarItemInfo*)fItemList->ItemAt(i); 481 if (item == NULL) 482 continue; 483 484 if (item->id == id) 485 return item; 486 } 487 488 return NULL; 489 } 490 491 492 bool 493 TReplicantTray::NodeExists(node_ref& nodeRef) 494 { 495 return DeskbarItemFor(nodeRef) != NULL; 496 } 497 498 499 /*! This handles B_NODE_MONITOR & B_QUERY_UPDATE messages received 500 for the registered add-ons. 501 */ 502 void 503 TReplicantTray::HandleEntryUpdate(BMessage* message) 504 { 505 int32 opcode; 506 if (message->FindInt32("opcode", &opcode) != B_OK) 507 return; 508 509 BPath path; 510 switch (opcode) { 511 case B_ENTRY_MOVED: 512 { 513 entry_ref ref; 514 ino_t todirectory; 515 ino_t node; 516 const char* name; 517 if (message->FindString("name", &name) == B_OK 518 && message->FindInt64("from directory", &(ref.directory)) 519 == B_OK 520 && message->FindInt64("to directory", &todirectory) == B_OK 521 && message->FindInt32("device", &(ref.device)) == B_OK 522 && message->FindInt64("node", &node) == B_OK ) { 523 524 if (!name) 525 break; 526 527 ref.set_name(name); 528 // change the directory reference to 529 // the new directory 530 MoveItem(&ref, todirectory); 531 } 532 break; 533 } 534 535 case B_ENTRY_REMOVED: 536 { 537 // entry was rm'd from the device 538 node_ref nodeRef; 539 if (message->FindInt32("device", &(nodeRef.device)) == B_OK 540 && message->FindInt64("node", &(nodeRef.node)) == B_OK) { 541 DeskbarItemInfo* item = DeskbarItemFor(nodeRef); 542 if (item == NULL) 543 break; 544 545 // If there is a team running where the add-on comes from, 546 // we don't want to remove the icon yet. 547 if (be_roster->IsRunning(&item->entryRef)) 548 break; 549 550 UnloadAddOn(&nodeRef, NULL, true, false); 551 } 552 break; 553 } 554 } 555 } 556 557 558 /*! The add-ons must support the exported C function API 559 if they do, they will be loaded and added to deskbar 560 primary function is the Instantiate function 561 */ 562 status_t 563 TReplicantTray::LoadAddOn(BEntry* entry, int32* id, bool addToSettings) 564 { 565 if (!entry) 566 return B_ERROR; 567 568 node_ref nodeRef; 569 entry->GetNodeRef(&nodeRef); 570 // no duplicates 571 if (NodeExists(nodeRef)) 572 return B_ERROR; 573 574 BNode node(entry); 575 BPath path; 576 status_t status = entry->GetPath(&path); 577 if (status < B_OK) 578 return status; 579 580 // load the add-on 581 image_id image = load_add_on(path.Path()); 582 if (image < B_OK) 583 return image; 584 585 // get the view loading function symbol 586 // we first look for a symbol that takes an image_id 587 // and entry_ref pointer, if not found, go with normal 588 // instantiate function 589 BView* (*entryFunction)(image_id, const entry_ref*); 590 BView* (*itemFunction)(void); 591 BView* view = NULL; 592 593 entry_ref ref; 594 entry->GetRef(&ref); 595 596 if (get_image_symbol(image, kInstantiateEntryCFunctionName, 597 B_SYMBOL_TYPE_TEXT, (void**)&entryFunction) >= B_OK) { 598 view = (*entryFunction)(image, &ref); 599 } else if (get_image_symbol(image, kInstantiateItemCFunctionName, 600 B_SYMBOL_TYPE_TEXT, (void**)&itemFunction) >= B_OK) { 601 view = (*itemFunction)(); 602 } else { 603 unload_add_on(image); 604 return B_ERROR; 605 } 606 607 if (view == NULL || IconExists(view->Name())) { 608 delete view; 609 unload_add_on(image); 610 return B_ERROR; 611 } 612 613 BMessage* data = new BMessage; 614 view->Archive(data); 615 delete view; 616 617 AddIcon(data, id, &ref); 618 // add the rep; adds info to list 619 620 if (addToSettings) { 621 fAddOnSettings.AddString(kReplicantPathField, path.Path()); 622 _SaveSettings(); 623 } 624 625 return B_OK; 626 } 627 628 629 status_t 630 TReplicantTray::AddItem(int32 id, node_ref nodeRef, BEntry& entry, bool isAddOn) 631 { 632 DeskbarItemInfo* item = new DeskbarItemInfo; 633 if (item == NULL) 634 return B_NO_MEMORY; 635 636 item->id = id; 637 item->isAddOn = isAddOn; 638 639 if (entry.GetRef(&item->entryRef) < B_OK) { 640 item->entryRef.device = -1; 641 item->entryRef.directory = -1; 642 item->entryRef.name = NULL; 643 } 644 item->nodeRef = nodeRef; 645 646 fItemList->AddItem(item); 647 648 if (isAddOn) 649 watch_node(&nodeRef, B_WATCH_NAME | B_WATCH_ATTR, this, Window()); 650 651 return B_OK; 652 } 653 654 655 /** from entry_removed message, when attribute removed 656 * or when a device is unmounted (use removeall, by device) 657 */ 658 659 void 660 TReplicantTray::UnloadAddOn(node_ref* nodeRef, dev_t* device, 661 bool which, bool removeAll) 662 { 663 for (int32 i = fItemList->CountItems(); i-- > 0 ;) { 664 DeskbarItemInfo* item = (DeskbarItemInfo*)fItemList->ItemAt(i); 665 if (!item) 666 continue; 667 668 if ((which && nodeRef && item->nodeRef == *nodeRef) 669 || (device && item->nodeRef.device == *device)) { 670 671 if (device && be_roster->IsRunning(&item->entryRef)) 672 continue; 673 674 RemoveIcon(item->id); 675 676 if (!removeAll) 677 break; 678 } 679 } 680 } 681 682 683 void 684 TReplicantTray::RemoveItem(int32 id) 685 { 686 DeskbarItemInfo* item = DeskbarItemFor(id); 687 if (item == NULL) 688 return; 689 690 // attribute was added via Deskbar API (AddItem(entry_ref*, int32*) 691 if (item->isAddOn) { 692 BPath path(&item->entryRef); 693 BString storedPath; 694 for (int32 i = 0; 695 fAddOnSettings.FindString(kReplicantPathField, i, &storedPath) 696 == B_OK; i++) { 697 if (storedPath == path.Path()) { 698 fAddOnSettings.RemoveData(kReplicantPathField, i); 699 break; 700 } 701 } 702 _SaveSettings(); 703 704 BNode node(&item->entryRef); 705 watch_node(&item->nodeRef, B_STOP_WATCHING, this, Window()); 706 } 707 708 fItemList->RemoveItem(item); 709 delete item; 710 } 711 712 713 /** ENTRY_MOVED message, moving only occurs on a device 714 * copying will occur (ENTRY_CREATED) between devices 715 */ 716 717 void 718 TReplicantTray::MoveItem(entry_ref* ref, ino_t toDirectory) 719 { 720 if (!ref) 721 return; 722 723 // scan for a matching entry_ref and update it 724 // 725 // don't need to change node info as it does not change 726 727 for (int32 i = fItemList->CountItems(); i-- > 0 ;) { 728 DeskbarItemInfo* item = (DeskbarItemInfo*)fItemList->ItemAt(i); 729 if (!item) 730 continue; 731 732 if (!strcmp(item->entryRef.name, ref->name) 733 && item->entryRef.device == ref->device 734 && item->entryRef.directory == ref->directory) { 735 item->entryRef.directory = toDirectory; 736 break; 737 } 738 } 739 } 740 741 #endif // add-on support 742 743 // external add-on API routines 744 // called using the new BDeskbar class 745 746 // existence of icon/replicant by name or ID 747 // returns opposite 748 // note: name and id are semi-private limiting 749 // the ability of non-host apps to remove 750 // icons without a little bit of work 751 752 /** for a specific id 753 * return the name of the replicant (name of view) 754 */ 755 756 status_t 757 TReplicantTray::ItemInfo(int32 id, const char** name) 758 { 759 if (id < 0) 760 return B_ERROR; 761 762 int32 index, temp; 763 BView* view = ViewAt(&index, &temp, id, false); 764 if (view) { 765 *name = view->Name(); 766 return B_OK; 767 } 768 769 return B_ERROR; 770 } 771 772 773 /** for a specific name 774 * return the id (internal to Deskbar) 775 */ 776 777 status_t 778 TReplicantTray::ItemInfo(const char* name, int32* id) 779 { 780 if (!name || strlen(name) <= 0) 781 return B_ERROR; 782 783 int32 index; 784 BView* view = ViewAt(&index, id, name); 785 if (view) 786 return B_OK; 787 788 return B_ERROR; 789 } 790 791 792 /** at a specific index 793 * return both the name and the id of the replicant 794 */ 795 796 status_t 797 TReplicantTray::ItemInfo(int32 index, const char** name, int32* id) 798 { 799 if (index < 0) 800 return B_ERROR; 801 802 BView* view; 803 fShelf->ReplicantAt(index, &view, (uint32*)id, NULL); 804 if (view) { 805 *name = view->Name(); 806 return B_OK; 807 } 808 809 return B_ERROR; 810 } 811 812 813 /** replicant exists, by id/index */ 814 815 bool 816 TReplicantTray::IconExists(int32 target, bool byIndex) 817 { 818 int32 index, id; 819 BView* view = ViewAt(&index, &id, target, byIndex); 820 821 return view && index >= 0; 822 } 823 824 825 /** replicant exists, by name */ 826 827 bool 828 TReplicantTray::IconExists(const char* name) 829 { 830 if (!name || strlen(name) == 0) 831 return false; 832 833 int32 index, id; 834 BView* view = ViewAt(&index, &id, name); 835 836 return view && index >= 0; 837 } 838 839 840 int32 841 TReplicantTray::IconCount() const 842 { 843 return fShelf->CountReplicants(); 844 } 845 846 847 /*! Message must contain an archivable view for later rehydration. 848 This function takes over ownership of the provided message on success 849 only. 850 Returns the current replicant ID. 851 */ 852 status_t 853 TReplicantTray::AddIcon(BMessage* archive, int32* id, const entry_ref* addOn) 854 { 855 if (archive == NULL || id == NULL) 856 return B_ERROR; 857 858 // find entry_ref 859 860 entry_ref ref; 861 if (addOn) { 862 // Use it if we got it 863 ref = *addOn; 864 } else { 865 const char* signature; 866 867 status_t status = archive->FindString("add_on", &signature); 868 if (status == B_OK) { 869 BRoster roster; 870 status = roster.FindApp(signature, &ref); 871 } 872 if (status < B_OK) 873 return status; 874 } 875 876 BFile file; 877 status_t status = file.SetTo(&ref, B_READ_ONLY); 878 if (status < B_OK) 879 return status; 880 881 node_ref nodeRef; 882 status = file.GetNodeRef(&nodeRef); 883 if (status < B_OK) 884 return status; 885 886 BEntry entry(&ref, true); 887 // TODO: this resolves an eventual link for the item being added - this 888 // is okay for now, but in multi-user environments, one might want to 889 // have links that carry the be:deskbar_item_status attribute 890 status = entry.InitCheck(); 891 if (status != B_OK) 892 return status; 893 894 *id = 999; 895 if (archive->what == B_ARCHIVED_OBJECT) 896 archive->what = 0; 897 898 BRect originalBounds = archive->FindRect("_frame"); 899 // this is a work-around for buggy replicants that change their size in 900 // AttachedToWindow() (such as "SVM") 901 902 // TODO: check for name collisions? 903 status = fShelf->AddReplicant(archive, BPoint(1, 1)); 904 if (status != B_OK) 905 return status; 906 907 int32 count = fShelf->CountReplicants(); 908 BView* view; 909 fShelf->ReplicantAt(count - 1, &view, (uint32*)id, NULL); 910 911 if (originalBounds != view->Bounds()) { 912 // The replicant changed its size when added to the window, so we need 913 // to recompute all over again (it's already done once via 914 // BShelf::AddReplicant() and TReplicantShelf::CanAcceptReplicantView()) 915 RealignReplicants(); 916 } 917 918 float oldWidth = Bounds().Width(); 919 float oldHeight = Bounds().Height(); 920 float width, height; 921 GetPreferredSize(&width, &height); 922 if (oldWidth != width || oldHeight != height) 923 AdjustPlacement(); 924 925 // add the item to the add-on list 926 927 AddItem(*id, nodeRef, entry, addOn != NULL); 928 return B_OK; 929 } 930 931 932 void 933 TReplicantTray::RemoveIcon(int32 target, bool byIndex) 934 { 935 if (target < 0) 936 return; 937 938 int32 index, id; 939 BView* view = ViewAt(&index, &id, target, byIndex); 940 if (view && index >= 0) { 941 // remove the reference from the item list & the shelf 942 RemoveItem(id); 943 fShelf->DeleteReplicant(index); 944 945 // force a placement update, !! need to fix BShelf 946 RealReplicantAdjustment(index); 947 } 948 } 949 950 951 void 952 TReplicantTray::RemoveIcon(const char* name) 953 { 954 if (!name || strlen(name) <= 0) 955 return; 956 957 int32 id, index; 958 BView* view = ViewAt(&index, &id, name); 959 if (view && index >= 0) { 960 // remove the reference from the item list & shelf 961 RemoveItem(id); 962 fShelf->DeleteReplicant(index); 963 964 // force a placement update, !! need to fix BShelf 965 RealReplicantAdjustment(index); 966 } 967 } 968 969 970 void 971 TReplicantTray::RealReplicantAdjustment(int32 startIndex) 972 { 973 if (startIndex < 0) 974 return; 975 976 if (startIndex == fLastReplicant) 977 startIndex = 0; 978 979 // reset the locations of all replicants after the one deleted 980 RealignReplicants(startIndex); 981 982 float oldWidth = Bounds().Width(); 983 float oldHeight = Bounds().Height(); 984 float width, height; 985 GetPreferredSize(&width, &height); 986 if (oldWidth != width || oldHeight != height) { 987 // resize view to accomodate the replicants, redraw as necessary 988 AdjustPlacement(); 989 } 990 } 991 992 993 /** looking for a replicant by id/index 994 * return the view and index 995 */ 996 997 BView* 998 TReplicantTray::ViewAt(int32* index, int32* id, int32 target, bool byIndex) 999 { 1000 *index = -1; 1001 1002 BView* view; 1003 if (byIndex) { 1004 if (fShelf->ReplicantAt(target, &view, (uint32*)id)) { 1005 if (view) { 1006 *index = target; 1007 return view; 1008 } 1009 } 1010 } else { 1011 int32 count = fShelf->CountReplicants() - 1; 1012 int32 localid; 1013 for (int32 repIndex = count ; repIndex >= 0 ; repIndex--) { 1014 fShelf->ReplicantAt(repIndex, &view, (uint32*)&localid); 1015 if (localid == target && view) { 1016 *index = repIndex; 1017 *id = localid; 1018 return view; 1019 } 1020 } 1021 } 1022 return NULL; 1023 } 1024 1025 1026 /** looking for a replicant with a view by name 1027 * return the view, index and the id of the replicant 1028 */ 1029 1030 BView* 1031 TReplicantTray::ViewAt(int32* index, int32* id, const char* name) 1032 { 1033 *index = -1; 1034 *id = -1; 1035 1036 BView* view; 1037 int32 count = fShelf->CountReplicants()-1; 1038 for (int32 repIndex = count ; repIndex >= 0 ; repIndex--) { 1039 fShelf->ReplicantAt(repIndex, &view, (uint32*)id); 1040 if (view && view->Name() && strcmp(name, view->Name()) == 0) { 1041 *index = repIndex; 1042 return view; 1043 } 1044 } 1045 return NULL; 1046 } 1047 1048 1049 /** Shelf will call to determine where and if 1050 * the replicant is to be added 1051 */ 1052 1053 bool 1054 TReplicantTray::AcceptAddon(BRect replicantFrame, BMessage* message) 1055 { 1056 if (!message) 1057 return false; 1058 1059 if (replicantFrame.Height() > kMaxReplicantHeight) 1060 return false; 1061 1062 alignment align = B_ALIGN_LEFT; 1063 if (fAlignmentSupport && message->HasBool("deskbar:dynamic_align")) { 1064 if (!fBarView->Vertical()) 1065 align = B_ALIGN_RIGHT; 1066 else 1067 align = fBarView->Left() ? B_ALIGN_LEFT : B_ALIGN_RIGHT; 1068 } else if (message->HasInt32("deskbar:align")) 1069 message->FindInt32("deskbar:align", (int32*)&align); 1070 1071 if (message->HasInt32("deskbar:private_align")) 1072 message->FindInt32("deskbar:private_align", (int32*)&align); 1073 else 1074 align = B_ALIGN_LEFT; 1075 1076 BPoint loc = LocationForReplicant(fShelf->CountReplicants(), 1077 replicantFrame.Width()); 1078 1079 message->AddPoint("_pjp_loc", loc); 1080 return true; 1081 } 1082 1083 1084 /** based on the previous (index - 1) replicant in the list 1085 * calculate where the left point should be for this 1086 * replicant. replicant will flow to the right on its own 1087 */ 1088 1089 BPoint 1090 TReplicantTray::LocationForReplicant(int32 index, float width) 1091 { 1092 BPoint loc(kIconGap + 1, kGutter + 1); 1093 1094 if (fMultiRowMode) { 1095 // try to find free space in every row 1096 for (int32 row = 0; ; loc.y += kMaxReplicantHeight + kIconGap, row++) { 1097 // determine free space in this row 1098 BRect rect(loc.x, loc.y, loc.x + fMinimumTrayWidth - kIconGap - 2.0, 1099 loc.y + kMaxReplicantHeight); 1100 if (row == 0 && fBarView->ShowingClock()) 1101 rect.right -= fClock->Frame().Width() + kIconGap; 1102 1103 for (int32 i = 0; i < index; i++) { 1104 BView* view = NULL; 1105 fShelf->ReplicantAt(i, &view); 1106 if (view == NULL || view->Frame().top != rect.top) 1107 continue; 1108 1109 rect.left = view->Frame().right + kIconGap + 1; 1110 } 1111 1112 if (rect.Width() >= width) { 1113 // the icon fits in this row 1114 loc = rect.LeftTop(); 1115 break; 1116 } 1117 } 1118 } else { 1119 if (index > 0) { 1120 // get the last replicant added for placement reference 1121 BView* view = NULL; 1122 fShelf->ReplicantAt(index - 1, &view); 1123 if (view) { 1124 // push this rep placement past the last one 1125 loc.x = view->Frame().right + kIconGap + 1; 1126 loc.y = view->Frame().top; 1127 } 1128 } 1129 } 1130 1131 if ((loc.y == fRightBottomReplicant.top && loc.x 1132 > fRightBottomReplicant.left) || loc.y > fRightBottomReplicant.top) { 1133 fRightBottomReplicant.Set(loc.x, loc.y, loc.x + width, loc.y 1134 + kMaxReplicantHeight); 1135 fLastReplicant = index; 1136 } 1137 1138 return loc; 1139 } 1140 1141 1142 BRect 1143 TReplicantTray::IconFrame(int32 target, bool byIndex) 1144 { 1145 int32 index, id; 1146 BView* view = ViewAt(&index, &id, target, byIndex); 1147 if (view) 1148 return view->Frame(); 1149 1150 return BRect(0, 0, 0, 0); 1151 } 1152 1153 1154 BRect 1155 TReplicantTray::IconFrame(const char* name) 1156 { 1157 if (!name) 1158 return BRect(0, 0, 0, 0); 1159 1160 int32 id, index; 1161 BView* view = ViewAt(&index, &id, name); 1162 if (view) 1163 return view->Frame(); 1164 1165 return BRect(0, 0, 0, 0); 1166 } 1167 1168 1169 /** Scan from the startIndex and reset the location 1170 * as defined in LocationForReplicant() 1171 */ 1172 1173 void 1174 TReplicantTray::RealignReplicants(int32 startIndex) 1175 { 1176 if (startIndex < 0) 1177 startIndex = 0; 1178 1179 int32 count = fShelf->CountReplicants(); 1180 if (count <= 0) 1181 return; 1182 1183 if (startIndex == 0) 1184 fRightBottomReplicant.Set(0, 0, 0, 0); 1185 1186 BView* view = NULL; 1187 for (int32 i = startIndex ; i < count ; i++) { 1188 fShelf->ReplicantAt(i, &view); 1189 if (view != NULL) { 1190 BPoint loc = LocationForReplicant(i, view->Frame().Width()); 1191 if (view->Frame().LeftTop() != loc) 1192 view->MoveTo(loc); 1193 } 1194 } 1195 } 1196 1197 1198 void 1199 TReplicantTray::SetMultiRow(bool state) 1200 { 1201 fMultiRowMode = state; 1202 } 1203 1204 1205 status_t 1206 TReplicantTray::_SaveSettings() 1207 { 1208 status_t result; 1209 BPath path; 1210 if ((result = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true)) 1211 == B_OK) { 1212 path.Append(kReplicantSettingsFile); 1213 1214 BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 1215 if ((result = file.InitCheck()) == B_OK) 1216 result = fAddOnSettings.Flatten(&file); 1217 } 1218 1219 return result; 1220 } 1221 1222 1223 // #pragma mark - 1224 1225 1226 /*! Draggable region that is asynchronous so that dragging does not block 1227 other activities. 1228 */ 1229 TDragRegion::TDragRegion(TBarView* parent, BView* child) 1230 : 1231 BControl(BRect(0, 0, 0, 0), "", "", NULL, B_FOLLOW_NONE, 1232 B_WILL_DRAW | B_FRAME_EVENTS), 1233 fBarView(parent), 1234 fChild(child), 1235 fDragLocation(kAutoPlaceDragRegion) 1236 { 1237 } 1238 1239 1240 void 1241 TDragRegion::AttachedToWindow() 1242 { 1243 BView::AttachedToWindow(); 1244 if (be_control_look != NULL) 1245 SetViewColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.1)); 1246 else 1247 SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); 1248 ResizeToPreferred(); 1249 } 1250 1251 1252 void 1253 TDragRegion::GetPreferredSize(float* width, float* height) 1254 { 1255 fChild->ResizeToPreferred(); 1256 *width = fChild->Bounds().Width(); 1257 *height = fChild->Bounds().Height(); 1258 1259 if (fDragLocation != kNoDragRegion) 1260 *width += 7; 1261 else 1262 *width += 6; 1263 1264 *height += 3; 1265 } 1266 1267 1268 void 1269 TDragRegion::FrameMoved(BPoint) 1270 { 1271 if (fBarView->Left() && fBarView->Vertical() 1272 && fDragLocation != kNoDragRegion) 1273 fChild->MoveTo(5, 2); 1274 else 1275 fChild->MoveTo(2, 2); 1276 } 1277 1278 1279 void 1280 TDragRegion::Draw(BRect) 1281 { 1282 rgb_color menuColor = ViewColor(); 1283 rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT); 1284 rgb_color ldark = tint_color(menuColor, 1.02); 1285 rgb_color dark = tint_color(menuColor, B_DARKEN_2_TINT); 1286 rgb_color vvdark = tint_color(menuColor, B_DARKEN_4_TINT); 1287 rgb_color light = tint_color(menuColor, B_LIGHTEN_2_TINT); 1288 1289 BRect frame(Bounds()); 1290 BeginLineArray(4); 1291 1292 if (be_control_look != NULL) { 1293 if (fBarView->Vertical()) { 1294 AddLine(frame.LeftTop(), frame.RightTop(), dark); 1295 AddLine(BPoint(frame.left, frame.top + 1), 1296 BPoint(frame.right, frame.top + 1), ldark); 1297 AddLine(frame.LeftBottom(), frame.RightBottom(), hilite); 1298 } else if (fBarView->AcrossTop() || fBarView->AcrossBottom()) { 1299 AddLine(frame.LeftTop(), 1300 BPoint(frame.left, frame.bottom), dark); 1301 AddLine(BPoint(frame.left + 1, frame.top + 1), 1302 BPoint(frame.right - 1, frame.top + 1), light); 1303 AddLine(BPoint(frame.right, frame.top + 2), 1304 BPoint(frame.right, frame.bottom), hilite); 1305 AddLine(BPoint(frame.left + 1, frame.bottom), 1306 BPoint(frame.right - 1, frame.bottom), hilite); 1307 } 1308 } else { 1309 if (fBarView->Vertical()) { 1310 AddLine(frame.LeftTop(), frame.RightTop(), light); 1311 AddLine(frame.LeftTop(), frame.LeftBottom(), light); 1312 AddLine(frame.RightBottom(), frame.RightTop(), hilite); 1313 } else if (fBarView->AcrossTop()) { 1314 AddLine(BPoint(frame.left, frame.top + 1), 1315 BPoint(frame.right - 1, frame.top + 1), light); 1316 AddLine(frame.RightTop(), frame.RightBottom(), vvdark); 1317 AddLine(BPoint(frame.right - 1, frame.top + 2), 1318 BPoint(frame.right - 1, frame.bottom - 1), hilite); 1319 AddLine(frame.LeftBottom(), 1320 BPoint(frame.right - 1, frame.bottom), hilite); 1321 } else if (fBarView->AcrossBottom()) { 1322 AddLine(BPoint(frame.left, frame.top + 1), 1323 BPoint(frame.right - 1, frame.top + 1), light); 1324 AddLine(frame.LeftBottom(), frame.RightBottom(), hilite); 1325 AddLine(frame.RightTop(), frame.RightBottom(), vvdark); 1326 AddLine(BPoint(frame.right - 1, frame.top + 1), 1327 BPoint(frame.right - 1, frame.bottom - 1), hilite); 1328 } 1329 } 1330 1331 EndLineArray(); 1332 1333 if (fDragLocation != kDontDrawDragRegion || fDragLocation != kNoDragRegion) 1334 DrawDragRegion(); 1335 } 1336 1337 1338 void 1339 TDragRegion::DrawDragRegion() 1340 { 1341 BRect dragRegion(DragRegion()); 1342 1343 rgb_color menuColor = ViewColor(); 1344 rgb_color menuHilite = menuColor; 1345 if (IsTracking()) { 1346 // Draw drag region highlighted if tracking mouse 1347 menuHilite = tint_color(menuColor, B_HIGHLIGHT_BACKGROUND_TINT); 1348 SetHighColor(menuHilite); 1349 FillRect(dragRegion); 1350 } 1351 rgb_color vdark = tint_color(menuHilite, B_DARKEN_3_TINT); 1352 rgb_color light = tint_color(menuHilite, B_LIGHTEN_2_TINT); 1353 1354 BeginLineArray(dragRegion.IntegerHeight()); 1355 BPoint pt; 1356 pt.x = floorf((dragRegion.left + dragRegion.right) / 2 + 0.5) - 1; 1357 pt.y = dragRegion.top + 2; 1358 1359 while (pt.y + 1 <= dragRegion.bottom) { 1360 AddLine(pt, pt, vdark); 1361 AddLine(pt + BPoint(1, 1), pt + BPoint(1, 1), light); 1362 1363 pt.y += 3; 1364 } 1365 EndLineArray(); 1366 } 1367 1368 1369 BRect 1370 TDragRegion::DragRegion() const 1371 { 1372 float kTopBottomInset = 2; 1373 float kLeftRightInset = 1; 1374 float kDragWidth = 3; 1375 if (be_control_look != NULL) { 1376 kTopBottomInset = 1; 1377 kLeftRightInset = 0; 1378 kDragWidth = 4; 1379 } 1380 1381 BRect dragRegion(Bounds()); 1382 dragRegion.top += kTopBottomInset; 1383 dragRegion.bottom -= kTopBottomInset; 1384 1385 bool placeOnLeft = false; 1386 if (fDragLocation == kAutoPlaceDragRegion) { 1387 if (fBarView->Vertical() && fBarView->Left()) 1388 placeOnLeft = true; 1389 else 1390 placeOnLeft = false; 1391 } else if (fDragLocation == kDragRegionLeft) 1392 placeOnLeft = true; 1393 else if (fDragLocation == kDragRegionRight) 1394 placeOnLeft = false; 1395 1396 if (placeOnLeft) { 1397 dragRegion.left += kLeftRightInset; 1398 dragRegion.right = dragRegion.left + kDragWidth; 1399 } else { 1400 dragRegion.right -= kLeftRightInset; 1401 dragRegion.left = dragRegion.right - kDragWidth; 1402 } 1403 1404 return dragRegion; 1405 } 1406 1407 1408 void 1409 TDragRegion::MouseDown(BPoint thePoint) 1410 { 1411 ulong buttons; 1412 BPoint where; 1413 BRect dragRegion(DragRegion()); 1414 1415 dragRegion.InsetBy(-2.0f, -2.0f); 1416 // DragRegion() is designed for drawing, not clicking 1417 1418 if (!dragRegion.Contains(thePoint)) 1419 return; 1420 1421 while (true) { 1422 GetMouse(&where, &buttons); 1423 if (!buttons) 1424 break; 1425 1426 if ((Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) != 0) { 1427 fPreviousPosition = thePoint; 1428 SetTracking(true); 1429 SetMouseEventMask(B_POINTER_EVENTS, 1430 B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); 1431 Invalidate(DragRegion()); 1432 break; 1433 } 1434 1435 snooze(25000); 1436 } 1437 } 1438 1439 1440 void 1441 TDragRegion::MouseUp(BPoint pt) 1442 { 1443 if (IsTracking()) { 1444 SetTracking(false); 1445 Invalidate(DragRegion()); 1446 } else 1447 BControl::MouseUp(pt); 1448 } 1449 1450 1451 bool 1452 TDragRegion::SwitchModeForRect(BPoint mouse, BRect rect, 1453 bool newVertical, bool newLeft, bool newTop, int32 newState) 1454 { 1455 if (!rect.Contains(mouse)) { 1456 // not our rect 1457 return false; 1458 } 1459 1460 if (newVertical == fBarView->Vertical() && newLeft == fBarView->Left() 1461 && newTop == fBarView->Top() && newState == fBarView->State()) { 1462 // already in the correct mode 1463 return true; 1464 } 1465 1466 fBarView->ChangeState(newState, newVertical, newLeft, newTop); 1467 return true; 1468 } 1469 1470 1471 void 1472 TDragRegion::MouseMoved(BPoint where, uint32 code, const BMessage* message) 1473 { 1474 if (IsTracking()) { 1475 BScreen screen; 1476 BRect frame = screen.Frame(); 1477 1478 float hDivider = frame.Width() / 6; 1479 hDivider = (hDivider < sMinimumWindowWidth + 10.0f) 1480 ? sMinimumWindowWidth + 10.0f : hDivider; 1481 float miniDivider = frame.top + kMiniHeight + 10.0f; 1482 float vDivider = frame.Height() / 2; 1483 #ifdef FULL_MODE 1484 float thirdScreen = frame.Height() / 3; 1485 #endif 1486 BRect topLeft(frame.left, frame.top, frame.left + hDivider, 1487 miniDivider); 1488 BRect topMiddle(frame.left + hDivider, frame.top, frame.right 1489 - hDivider, vDivider); 1490 BRect topRight(frame.right - hDivider, frame.top, frame.right, 1491 miniDivider); 1492 1493 #ifdef FULL_MODE 1494 vDivider = miniDivider + thirdScreen; 1495 #endif 1496 BRect middleLeft(frame.left, miniDivider, frame.left + hDivider, 1497 vDivider); 1498 BRect middleRight(frame.right - hDivider, miniDivider, frame.right, 1499 vDivider); 1500 1501 #ifdef FULL_MODE 1502 BRect leftSide(frame.left, vDivider, frame.left + hDivider, 1503 frame.bottom - thirdScreen); 1504 BRect rightSide(frame.right - hDivider, vDivider, frame.right, 1505 frame.bottom - thirdScreen); 1506 1507 vDivider = frame.bottom - thirdScreen; 1508 #endif 1509 BRect bottomLeft(frame.left, vDivider, frame.left + hDivider, 1510 frame.bottom); 1511 BRect bottomMiddle(frame.left + hDivider, vDivider, frame.right 1512 - hDivider, frame.bottom); 1513 BRect bottomRight(frame.right - hDivider, vDivider, frame.right, 1514 frame.bottom); 1515 1516 if (where != fPreviousPosition) { 1517 fPreviousPosition = where; 1518 ConvertToScreen(&where); 1519 1520 // use short circuit evaluation for convenience 1521 if (SwitchModeForRect(where, topLeft, true, true, true, kMiniState) 1522 || SwitchModeForRect(where, topMiddle, false, true, true, 1523 kExpandoState) 1524 || SwitchModeForRect(where, topRight, true, false, true, 1525 kMiniState) 1526 || SwitchModeForRect(where, middleLeft, true, true, true, 1527 kExpandoState) 1528 || SwitchModeForRect(where, middleRight, true, false, true, 1529 kExpandoState) 1530 1531 #ifdef FULL_MODE 1532 || SwitchModeForRect(where, leftSide, true, true, true, 1533 kFullState) 1534 || SwitchModeForRect(where, rightSide, true, false, true, 1535 kFullState) 1536 #endif 1537 || SwitchModeForRect(where, bottomLeft, true, true, false, 1538 kMiniState) 1539 || SwitchModeForRect(where, bottomMiddle, false, true, false, 1540 kExpandoState) 1541 || SwitchModeForRect(where, bottomRight, true, false, false, 1542 kMiniState)) 1543 ; 1544 } 1545 } else 1546 BControl::MouseMoved(where, code, message); 1547 } 1548 1549 1550 int32 1551 TDragRegion::DragRegionLocation() const 1552 { 1553 return fDragLocation; 1554 } 1555 1556 1557 void 1558 TDragRegion::SetDragRegionLocation(int32 location) 1559 { 1560 if (location == fDragLocation) 1561 return; 1562 1563 fDragLocation = location; 1564 Invalidate(); 1565 } 1566 1567