1 /* 2 * Copyright 2006, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "PropertyListView.h" 10 11 #include <stdio.h> 12 #include <string.h> 13 14 #include <ClassInfo.h> 15 #include <Clipboard.h> 16 #include <Menu.h> 17 #include <MenuItem.h> 18 #include <Message.h> 19 #include <Window.h> 20 21 #include "CommonPropertyIDs.h" 22 //#include "LanguageManager.h" 23 #include "Property.h" 24 #include "PropertyItemView.h" 25 #include "PropertyObject.h" 26 #include "Scrollable.h" 27 #include "Scroller.h" 28 #include "ScrollView.h" 29 30 enum { 31 MSG_COPY_PROPERTIES = 'cppr', 32 MSG_PASTE_PROPERTIES = 'pspr', 33 34 MSG_ADD_KEYFRAME = 'adkf', 35 36 MSG_SELECT_ALL = B_SELECT_ALL, 37 MSG_SELECT_NONE = 'slnn', 38 MSG_INVERT_SELECTION = 'invs', 39 }; 40 41 // TabFilter class 42 43 class TabFilter : public BMessageFilter { 44 public: 45 TabFilter(PropertyListView* target) 46 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 47 fTarget(target) 48 { 49 } 50 virtual ~TabFilter() 51 { 52 } 53 virtual filter_result Filter(BMessage* message, BHandler** target) 54 { 55 filter_result result = B_DISPATCH_MESSAGE; 56 switch (message->what) { 57 case B_UNMAPPED_KEY_DOWN: 58 case B_KEY_DOWN: { 59 uint32 key; 60 uint32 modifiers; 61 if (message->FindInt32("raw_char", (int32*)&key) >= B_OK 62 && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK) 63 if (key == B_TAB && fTarget->TabFocus(modifiers & B_SHIFT_KEY)) 64 result = B_SKIP_MESSAGE; 65 break; 66 } 67 default: 68 break; 69 } 70 return result; 71 } 72 private: 73 PropertyListView* fTarget; 74 }; 75 76 77 // constructor 78 PropertyListView::PropertyListView() 79 : BView(BRect(0.0, 0.0, 100.0, 100.0), NULL, B_FOLLOW_NONE, 80 B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE), 81 Scrollable(), 82 BList(20), 83 fClipboard(new BClipboard("icon-o-matic properties")), 84 85 fPropertyM(NULL), 86 87 fPropertyObject(NULL), 88 fSavedProperties(new PropertyObject()), 89 90 fLastClickedItem(NULL), 91 fSuspendUpdates(false), 92 93 fMouseWheelFilter(new MouseWheelFilter(this)), 94 fTabFilter(new TabFilter(this)) 95 { 96 SetLowColor(255, 255, 255, 255); 97 SetHighColor(0, 0, 0, 255); 98 SetViewColor(B_TRANSPARENT_32_BIT); 99 } 100 101 // destructor 102 PropertyListView::~PropertyListView() 103 { 104 delete fClipboard; 105 106 delete fPropertyObject; 107 delete fSavedProperties; 108 109 delete fMouseWheelFilter; 110 delete fTabFilter; 111 } 112 113 // AttachedToWindow 114 void 115 PropertyListView::AttachedToWindow() 116 { 117 Window()->AddCommonFilter(fMouseWheelFilter); 118 Window()->AddCommonFilter(fTabFilter); 119 } 120 121 // DetachedFromWindow 122 void 123 PropertyListView::DetachedFromWindow() 124 { 125 Window()->RemoveCommonFilter(fTabFilter); 126 Window()->RemoveCommonFilter(fMouseWheelFilter); 127 } 128 129 // FrameResized 130 void 131 PropertyListView::FrameResized(float width, float height) 132 { 133 SetVisibleSize(width, height); 134 Invalidate(); 135 } 136 137 // Draw 138 void 139 PropertyListView::Draw(BRect updateRect) 140 { 141 if (!fSuspendUpdates) 142 FillRect(updateRect, B_SOLID_LOW); 143 } 144 145 // MakeFocus 146 void 147 PropertyListView::MakeFocus(bool focus) 148 { 149 if (focus == IsFocus()) 150 return; 151 152 BView::MakeFocus(focus); 153 if (::ScrollView* scrollView = dynamic_cast< ::ScrollView*>(Parent())) 154 scrollView->ChildFocusChanged(focus); 155 } 156 157 // MouseDown 158 void 159 PropertyListView::MouseDown(BPoint where) 160 { 161 if (!(modifiers() & B_SHIFT_KEY)) { 162 DeselectAll(); 163 } 164 MakeFocus(true); 165 } 166 167 // MessageReceived 168 void 169 PropertyListView::MessageReceived(BMessage* message) 170 { 171 switch (message->what) { 172 case MSG_PASTE_PROPERTIES: { 173 if (!fPropertyObject || !fClipboard->Lock()) 174 break; 175 176 BMessage* data = fClipboard->Data(); 177 if (!data) { 178 fClipboard->Unlock(); 179 break; 180 } 181 182 PropertyObject propertyObject; 183 BMessage archive; 184 for (int32 i = 0; 185 data->FindMessage("property", i, &archive) >= B_OK; 186 i++) { 187 BArchivable* archivable = instantiate_object(&archive); 188 if (!archivable) 189 continue; 190 // see if this is actually a property 191 Property* property = cast_as(archivable, Property); 192 if (!property || !propertyObject.AddProperty(property)) 193 delete archivable; 194 } 195 if (propertyObject.CountProperties() > 0) 196 PasteProperties(&propertyObject); 197 fClipboard->Unlock(); 198 break; 199 } 200 case MSG_COPY_PROPERTIES: { 201 if (!fPropertyObject || !fClipboard->Lock()) 202 break; 203 204 BMessage* data = fClipboard->Data(); 205 if (!data) { 206 fClipboard->Unlock(); 207 break; 208 } 209 210 fClipboard->Clear(); 211 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 212 if (!item->IsSelected()) 213 continue; 214 const Property* property = item->GetProperty(); 215 if (property) { 216 BMessage archive; 217 if (property->Archive(&archive) >= B_OK) { 218 data->AddMessage("property", &archive); 219 } 220 } 221 } 222 fClipboard->Commit(); 223 fClipboard->Unlock(); 224 _CheckMenuStatus(); 225 break; 226 } 227 228 // property selection 229 case MSG_SELECT_ALL: 230 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 231 item->SetSelected(true); 232 } 233 _CheckMenuStatus(); 234 break; 235 case MSG_SELECT_NONE: 236 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 237 item->SetSelected(false); 238 } 239 _CheckMenuStatus(); 240 break; 241 case MSG_INVERT_SELECTION: 242 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 243 item->SetSelected(!item->IsSelected()); 244 } 245 _CheckMenuStatus(); 246 break; 247 248 default: 249 BView::MessageReceived(message); 250 } 251 } 252 253 // #pragma mark - 254 255 // TabFocus 256 bool 257 PropertyListView::TabFocus(bool shift) 258 { 259 bool result = false; 260 PropertyItemView* item = NULL; 261 if (IsFocus() && !shift) { 262 item = _ItemAt(0); 263 } else { 264 int32 focussedIndex = -1; 265 for (int32 i = 0; PropertyItemView* oldItem = _ItemAt(i); i++) { 266 if (oldItem->IsFocused()) { 267 focussedIndex = shift ? i - 1 : i + 1; 268 break; 269 } 270 } 271 item = _ItemAt(focussedIndex); 272 } 273 if (item) { 274 item->MakeFocus(true); 275 result = true; 276 } 277 return result; 278 } 279 280 // SetMenu 281 void 282 PropertyListView::SetMenu(BMenu* menu) 283 { 284 fPropertyM = menu; 285 if (!fPropertyM) 286 return; 287 288 fSelectM = new BMenu("Select"); 289 fSelectAllMI = new BMenuItem("All", new BMessage(MSG_SELECT_ALL)); 290 fSelectM->AddItem(fSelectAllMI); 291 fSelectNoneMI = new BMenuItem("None", new BMessage(MSG_SELECT_NONE)); 292 fSelectM->AddItem(fSelectNoneMI); 293 fInvertSelectionMI = new BMenuItem("Invert Selection", new BMessage(MSG_INVERT_SELECTION)); 294 fSelectM->AddItem(fInvertSelectionMI); 295 fSelectM->SetTargetForItems(this); 296 297 fPropertyM->AddItem(fSelectM); 298 299 fPropertyM->AddSeparatorItem(); 300 301 fCopyMI = new BMenuItem("Copy", new BMessage(MSG_COPY_PROPERTIES)); 302 fPropertyM->AddItem(fCopyMI); 303 fPasteMI = new BMenuItem("Paste", new BMessage(MSG_PASTE_PROPERTIES)); 304 fPropertyM->AddItem(fPasteMI); 305 306 fPropertyM->SetTargetForItems(this); 307 308 // disable menus 309 _CheckMenuStatus(); 310 } 311 312 // UpdateStrings 313 void 314 PropertyListView::UpdateStrings() 315 { 316 // if (fSelectM) { 317 // LanguageManager* m = LanguageManager::Default(); 318 // 319 // fSelectM->Superitem()->SetLabel(m->GetString(PROPERTY_SELECTION, "Select")); 320 // fSelectAllMI->SetLabel(m->GetString(SELECT_ALL_PROPERTIES, "All")); 321 // fSelectNoneMI->SetLabel(m->GetString(SELECT_NO_PROPERTIES, "None")); 322 // fInvertSelectionMI->SetLabel(m->GetString(INVERT_SELECTION, "Invert Selection")); 323 // 324 // fPropertyM->Superitem()->SetLabel(m->GetString(PROPERTY, "Property")); 325 // fCopyMI->SetLabel(m->GetString(COPY, "Copy")); 326 // if (IsEditingMultipleObjects()) 327 // fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste")); 328 // else 329 // fPasteMI->SetLabel(m->GetString(PASTE, "Paste")); 330 // } 331 } 332 333 // ScrollView 334 ::ScrollView* 335 PropertyListView::ScrollView() const 336 { 337 return dynamic_cast< ::ScrollView*>(ScrollSource()); 338 } 339 340 // #pragma mark - 341 342 // SetTo 343 void 344 PropertyListView::SetTo(PropertyObject* object) 345 { 346 // try to do without rebuilding the list 347 // it should in fact be pretty unlikely that this does not 348 // work, but we keep being defensive 349 if (fPropertyObject && object && 350 fPropertyObject->ContainsSameProperties(*object)) { 351 // iterate over view items and update their value views 352 bool error = false; 353 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 354 Property* property = object->PropertyAt(i); 355 if (!item->AdoptProperty(property)) { 356 // the reason for this can be that the property is 357 // unkown to the PropertyEditorFactory and therefor 358 // there is no editor view at this item 359 fprintf(stderr, "PropertyListView::_SetTo() - " 360 "property mismatch at %ld\n", i); 361 error = true; 362 break; 363 } 364 if (property) 365 item->SetEnabled(property->IsEditable()); 366 } 367 // we didn't need to make empty, but transfer ownership 368 // of the object 369 if (!error) { 370 // if the "adopt" process went only halfway, 371 // some properties of the original object 372 // are still referenced, so we can only 373 // delete the original object if the process 374 // was successful and leak Properties otherwise, 375 // but this case is only theoretical anyways... 376 delete fPropertyObject; 377 } 378 fPropertyObject = object; 379 } else { 380 // remember scroll pos, selection and focused item 381 BPoint scrollOffset = ScrollOffset(); 382 BList selection(20); 383 int32 focused = -1; 384 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 385 if (item->IsSelected()) 386 selection.AddItem((void*)i); 387 if (item->IsFocused()) 388 focused = i; 389 } 390 if (Window()) 391 Window()->BeginViewTransaction(); 392 fSuspendUpdates = true; 393 394 // rebuild list 395 _MakeEmpty(); 396 fPropertyObject = object; 397 398 if (fPropertyObject) { 399 // fill with content 400 for (int32 i = 0; Property* property = fPropertyObject->PropertyAt(i); i++) { 401 PropertyItemView* item = new PropertyItemView(property); 402 item->SetEnabled(property->IsEditable()); 403 _AddItem(item); 404 } 405 _LayoutItems(); 406 407 // restore scroll pos, selection and focus 408 SetScrollOffset(scrollOffset); 409 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 410 if (selection.HasItem((void*)i)) 411 item->SetSelected(true); 412 if (i == focused) 413 item->MakeFocus(true); 414 } 415 } 416 417 if (Window()) 418 Window()->EndViewTransaction(); 419 fSuspendUpdates = false; 420 421 SetDataRect(_ItemsRect()); 422 } 423 424 _UpdateSavedProperties(); 425 _CheckMenuStatus(); 426 Invalidate(); 427 } 428 429 // PropertyChanged 430 void 431 PropertyListView::PropertyChanged(const Property* previous, 432 const Property* current) 433 { 434 printf("PropertyListView::PropertyChanged(%s)\n", 435 name_for_id(current->Identifier())); 436 } 437 438 // PasteProperties 439 void 440 PropertyListView::PasteProperties(const PropertyObject* object) 441 { 442 if (!fPropertyObject) 443 return; 444 445 // default implementation is to adopt the pasted properties 446 int32 count = object->CountProperties(); 447 for (int32 i = 0; i < count; i++) { 448 Property* p = object->PropertyAtFast(i); 449 Property* local = fPropertyObject->FindProperty(p->Identifier()); 450 if (local) 451 local->SetValue(p); 452 } 453 } 454 455 // IsEditingMultipleObjects 456 bool 457 PropertyListView::IsEditingMultipleObjects() 458 { 459 return false; 460 } 461 462 // #pragma mark - 463 464 // UpdateObject 465 void 466 PropertyListView::UpdateObject(uint32 propertyID) 467 { 468 Property* previous = fSavedProperties->FindProperty(propertyID); 469 Property* current = fPropertyObject->FindProperty(propertyID); 470 if (previous && current) { 471 // call hook function 472 PropertyChanged(previous, current); 473 // update saved property if it is still contained 474 // in the saved properties (if not, the notification 475 // mechanism has caused to update the properties 476 // and "previous" and "current" are toast) 477 if (fSavedProperties->HasProperty(previous) 478 && fPropertyObject->HasProperty(current)) 479 previous->SetValue(current); 480 } 481 } 482 483 // ScrollOffsetChanged 484 void 485 PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) 486 { 487 ScrollBy(newOffset.x - oldOffset.x, 488 newOffset.y - oldOffset.y); 489 } 490 491 // Select 492 void 493 PropertyListView::Select(PropertyItemView* item) 494 { 495 if (item) { 496 if (modifiers() & B_SHIFT_KEY) { 497 item->SetSelected(!item->IsSelected()); 498 } else if (modifiers() & B_OPTION_KEY) { 499 item->SetSelected(true); 500 int32 firstSelected = _CountItems(); 501 int32 lastSelected = -1; 502 for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { 503 if (otherItem->IsSelected()) { 504 if (i < firstSelected) 505 firstSelected = i; 506 if (i > lastSelected) 507 lastSelected = i; 508 } 509 } 510 if (lastSelected > firstSelected) { 511 for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) { 512 if (i > lastSelected) 513 break; 514 otherItem->SetSelected(true); 515 } 516 } 517 } else { 518 for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { 519 otherItem->SetSelected(otherItem == item); 520 } 521 } 522 } 523 _CheckMenuStatus(); 524 } 525 526 // DeselectAll 527 void 528 PropertyListView::DeselectAll() 529 { 530 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 531 item->SetSelected(false); 532 } 533 _CheckMenuStatus(); 534 } 535 536 // Clicked 537 void 538 PropertyListView::Clicked(PropertyItemView* item) 539 { 540 fLastClickedItem = item; 541 } 542 543 // DoubleClicked 544 void 545 PropertyListView::DoubleClicked(PropertyItemView* item) 546 { 547 if (fLastClickedItem == item) { 548 printf("implement PropertyListView::DoubleClicked()\n"); 549 } 550 fLastClickedItem = NULL; 551 } 552 553 // #pragma mark - 554 555 // _UpdateSavedProperties 556 void 557 PropertyListView::_UpdateSavedProperties() 558 { 559 fSavedProperties->DeleteProperties(); 560 561 if (!fPropertyObject) 562 return; 563 564 int32 count = fPropertyObject->CountProperties(); 565 for (int32 i = 0; i < count; i++) { 566 const Property* p = fPropertyObject->PropertyAtFast(i); 567 fSavedProperties->AddProperty(p->Clone()); 568 } 569 } 570 571 // _AddItem 572 bool 573 PropertyListView::_AddItem(PropertyItemView* item) 574 { 575 if (item && BList::AddItem((void*)item)) { 576 // AddChild(item); 577 // NOTE: for now added in _LayoutItems() 578 item->SetListView(this); 579 return true; 580 } 581 return false; 582 } 583 584 // _RemoveItem 585 PropertyItemView* 586 PropertyListView::_RemoveItem(int32 index) 587 { 588 PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index); 589 if (item) { 590 item->SetListView(NULL); 591 if (!RemoveChild(item)) 592 fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n"); 593 } 594 return item; 595 } 596 597 // _ItemAt 598 PropertyItemView* 599 PropertyListView::_ItemAt(int32 index) const 600 { 601 return (PropertyItemView*)BList::ItemAt(index); 602 } 603 604 // _CountItems 605 int32 606 PropertyListView::_CountItems() const 607 { 608 return BList::CountItems(); 609 } 610 611 // _MakeEmpty 612 void 613 PropertyListView::_MakeEmpty() 614 { 615 int32 count = _CountItems(); 616 while (PropertyItemView* item = _RemoveItem(count - 1)) { 617 delete item; 618 count--; 619 } 620 delete fPropertyObject; 621 fPropertyObject = NULL; 622 623 SetScrollOffset(BPoint(0.0, 0.0)); 624 } 625 626 // _ItemsRect 627 BRect 628 PropertyListView::_ItemsRect() const 629 { 630 float width = Bounds().Width(); 631 float height = -1.0; 632 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 633 height += item->PreferredHeight() + 1.0; 634 } 635 if (height < 0.0) 636 height = 0.0; 637 return BRect(0.0, 0.0, width, height); 638 } 639 640 // _LayoutItems 641 void 642 PropertyListView::_LayoutItems() 643 { 644 // figure out maximum label width 645 float labelWidth = 0.0; 646 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 647 if (item->PreferredLabelWidth() > labelWidth) 648 labelWidth = item->PreferredLabelWidth(); 649 } 650 labelWidth = ceilf(labelWidth); 651 // layout items 652 float top = 0.0; 653 float width = Bounds().Width(); 654 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 655 item->MoveTo(BPoint(0.0, top)); 656 float height = item->PreferredHeight(); 657 item->SetLabelWidth(labelWidth); 658 item->ResizeTo(width, height); 659 item->FrameResized(item->Bounds().Width(), 660 item->Bounds().Height()); 661 top += height + 1.0; 662 663 AddChild(item); 664 } 665 } 666 667 // _CheckMenuStatus 668 void 669 PropertyListView::_CheckMenuStatus() 670 { 671 if (!fPropertyM || fSuspendUpdates) 672 return; 673 674 if (!fPropertyObject) { 675 fPropertyM->SetEnabled(false); 676 return; 677 } else 678 fPropertyM->SetEnabled(false); 679 680 bool gotSelection = false; 681 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 682 if (item->IsSelected()) { 683 gotSelection = true; 684 break; 685 } 686 } 687 fCopyMI->SetEnabled(gotSelection); 688 689 bool clipboardHasData = false; 690 if (fClipboard->Lock()) { 691 if (BMessage* data = fClipboard->Data()) { 692 clipboardHasData = data->HasMessage("property"); 693 } 694 fClipboard->Unlock(); 695 } 696 697 fPasteMI->SetEnabled(clipboardHasData); 698 // LanguageManager* m = LanguageManager::Default(); 699 if (IsEditingMultipleObjects()) 700 // fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste")); 701 fPasteMI->SetLabel("Multi Paste"); 702 else 703 // fPasteMI->SetLabel(m->GetString(PASTE, "Paste")); 704 fPasteMI->SetLabel("Paste"); 705 706 bool enableMenu = fPropertyObject; 707 if (fPropertyM->IsEnabled() != enableMenu) 708 fPropertyM->SetEnabled(enableMenu); 709 710 bool gotItems = _CountItems() > 0; 711 fSelectM->SetEnabled(gotItems); 712 } 713 714 715