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