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 472 previous->SetValue(current); 473 } 474 } 475 476 // ScrollOffsetChanged 477 void 478 PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) 479 { 480 ScrollBy(newOffset.x - oldOffset.x, 481 newOffset.y - oldOffset.y); 482 } 483 484 // Select 485 void 486 PropertyListView::Select(PropertyItemView* item) 487 { 488 if (item) { 489 if (modifiers() & B_SHIFT_KEY) { 490 item->SetSelected(!item->IsSelected()); 491 } else if (modifiers() & B_OPTION_KEY) { 492 item->SetSelected(true); 493 int32 firstSelected = _CountItems(); 494 int32 lastSelected = -1; 495 for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { 496 if (otherItem->IsSelected()) { 497 if (i < firstSelected) 498 firstSelected = i; 499 if (i > lastSelected) 500 lastSelected = i; 501 } 502 } 503 if (lastSelected > firstSelected) { 504 for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) { 505 if (i > lastSelected) 506 break; 507 otherItem->SetSelected(true); 508 } 509 } 510 } else { 511 for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { 512 otherItem->SetSelected(otherItem == item); 513 } 514 } 515 } 516 _CheckMenuStatus(); 517 } 518 519 // DeselectAll 520 void 521 PropertyListView::DeselectAll() 522 { 523 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 524 item->SetSelected(false); 525 } 526 _CheckMenuStatus(); 527 } 528 529 // Clicked 530 void 531 PropertyListView::Clicked(PropertyItemView* item) 532 { 533 fLastClickedItem = item; 534 } 535 536 // DoubleClicked 537 void 538 PropertyListView::DoubleClicked(PropertyItemView* item) 539 { 540 if (fLastClickedItem == item) { 541 printf("implement PropertyListView::DoubleClicked()\n"); 542 } 543 fLastClickedItem = NULL; 544 } 545 546 // #pragma mark - 547 548 // _UpdateSavedProperties 549 void 550 PropertyListView::_UpdateSavedProperties() 551 { 552 fSavedProperties->DeleteProperties(); 553 554 if (!fPropertyObject) 555 return; 556 557 int32 count = fPropertyObject->CountProperties(); 558 for (int32 i = 0; i < count; i++) { 559 const Property* p = fPropertyObject->PropertyAtFast(i); 560 fSavedProperties->AddProperty(p->Clone()); 561 } 562 } 563 564 // _AddItem 565 bool 566 PropertyListView::_AddItem(PropertyItemView* item) 567 { 568 if (item && BList::AddItem((void*)item)) { 569 // AddChild(item); 570 // NOTE: for now added in _LayoutItems() 571 item->SetListView(this); 572 return true; 573 } 574 return false; 575 } 576 577 // _RemoveItem 578 PropertyItemView* 579 PropertyListView::_RemoveItem(int32 index) 580 { 581 PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index); 582 if (item) { 583 item->SetListView(NULL); 584 if (!RemoveChild(item)) 585 fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n"); 586 } 587 return item; 588 } 589 590 // _ItemAt 591 PropertyItemView* 592 PropertyListView::_ItemAt(int32 index) const 593 { 594 return (PropertyItemView*)BList::ItemAt(index); 595 } 596 597 // _CountItems 598 int32 599 PropertyListView::_CountItems() const 600 { 601 return BList::CountItems(); 602 } 603 604 // _MakeEmpty 605 void 606 PropertyListView::_MakeEmpty() 607 { 608 int32 count = _CountItems(); 609 while (PropertyItemView* item = _RemoveItem(count - 1)) { 610 delete item; 611 count--; 612 } 613 delete fPropertyObject; 614 fPropertyObject = NULL; 615 616 SetScrollOffset(BPoint(0.0, 0.0)); 617 } 618 619 // _ItemsRect 620 BRect 621 PropertyListView::_ItemsRect() const 622 { 623 float width = Bounds().Width(); 624 float height = -1.0; 625 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 626 height += item->PreferredHeight() + 1.0; 627 } 628 if (height < 0.0) 629 height = 0.0; 630 return BRect(0.0, 0.0, width, height); 631 } 632 633 // _LayoutItems 634 void 635 PropertyListView::_LayoutItems() 636 { 637 // figure out maximum label width 638 float labelWidth = Bounds().Width() * 0.5; 639 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 640 if (item->PreferredLabelWidth() > labelWidth) 641 labelWidth = item->PreferredLabelWidth(); 642 } 643 labelWidth = ceilf(labelWidth); 644 // layout items 645 float top = 0.0; 646 float width = Bounds().Width(); 647 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 648 item->MoveTo(BPoint(0.0, top)); 649 float height = item->PreferredHeight(); 650 item->SetLabelWidth(labelWidth); 651 item->ResizeTo(width, height); 652 item->FrameResized(item->Bounds().Width(), 653 item->Bounds().Height()); 654 top += height + 1.0; 655 656 AddChild(item); 657 } 658 } 659 660 // _CheckMenuStatus 661 void 662 PropertyListView::_CheckMenuStatus() 663 { 664 if (!fPropertyM || !fSuspendUpdates) 665 return; 666 667 bool gotSelection = false; 668 for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { 669 if (item->IsSelected()) { 670 gotSelection = true; 671 break; 672 } 673 } 674 fCopyMI->SetEnabled(gotSelection); 675 676 bool clipboardHasData = false; 677 if (fClipboard->Lock()) { 678 if (BMessage* data = fClipboard->Data()) { 679 clipboardHasData = data->HasMessage("property"); 680 } 681 fClipboard->Unlock(); 682 } 683 684 fPasteMI->SetEnabled(clipboardHasData); 685 // LanguageManager* m = LanguageManager::Default(); 686 if (IsEditingMultipleObjects()) 687 // fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste")); 688 fPasteMI->SetLabel("Multi Paste"); 689 else 690 // fPasteMI->SetLabel(m->GetString(PASTE, "Paste")); 691 fPasteMI->SetLabel("Paste"); 692 693 bool enableMenu = fPropertyObject; 694 if (fPropertyM->IsEnabled() != enableMenu) 695 fPropertyM->SetEnabled(enableMenu); 696 697 bool gotItems = _CountItems() > 0; 698 fSelectM->SetEnabled(gotItems); 699 } 700 701 702