1 /* 2 * Copyright 2006-2009, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "ShapeListView.h" 10 11 #include <new> 12 #include <stdio.h> 13 14 #include <Application.h> 15 #include <ListItem.h> 16 #include <Menu.h> 17 #include <MenuItem.h> 18 #include <Message.h> 19 #include <Mime.h> 20 #include <Window.h> 21 22 #include "AddPathsCommand.h" 23 #include "AddShapesCommand.h" 24 #include "AddStylesCommand.h" 25 #include "CommandStack.h" 26 #include "FreezeTransformationCommand.h" 27 #include "MoveShapesCommand.h" 28 #include "Observer.h" 29 #include "RemoveShapesCommand.h" 30 #include "ResetTransformationCommand.h" 31 #include "Selection.h" 32 #include "Shape.h" 33 #include "Util.h" 34 35 using std::nothrow; 36 37 class ShapeListItem : public SimpleItem, 38 public Observer { 39 public: 40 ShapeListItem(Shape* s, 41 ShapeListView* listView) 42 : SimpleItem(""), 43 shape(NULL), 44 fListView(listView) 45 { 46 SetClip(s); 47 } 48 49 virtual ~ShapeListItem() 50 { 51 SetClip(NULL); 52 } 53 54 virtual void ObjectChanged(const Observable* object) 55 { 56 UpdateText(); 57 } 58 59 void SetClip(Shape* s) 60 { 61 if (s == shape) 62 return; 63 64 if (shape) { 65 shape->RemoveObserver(this); 66 shape->Release(); 67 } 68 69 shape = s; 70 71 if (shape) { 72 shape->Acquire(); 73 shape->AddObserver(this); 74 UpdateText(); 75 } 76 } 77 void UpdateText() 78 { 79 SetText(shape->Name()); 80 // :-/ 81 if (fListView->LockLooper()) { 82 fListView->InvalidateItem( 83 fListView->IndexOf(this)); 84 fListView->UnlockLooper(); 85 } 86 } 87 88 Shape* shape; 89 private: 90 ShapeListView* fListView; 91 }; 92 93 // #pragma mark - 94 95 enum { 96 MSG_REMOVE = 'rmsh', 97 MSG_DUPLICATE = 'dpsh', 98 MSG_RESET_TRANSFORMATION = 'rstr', 99 MSG_FREEZE_TRANSFORMATION = 'frzt', 100 101 MSG_DRAG_SHAPE = 'drgs', 102 }; 103 104 // constructor 105 ShapeListView::ShapeListView(BRect frame, 106 const char* name, 107 BMessage* message, BHandler* target) 108 : SimpleListView(frame, name, 109 NULL, B_MULTIPLE_SELECTION_LIST), 110 fMessage(message), 111 fShapeContainer(NULL), 112 fCommandStack(NULL) 113 { 114 SetDragCommand(MSG_DRAG_SHAPE); 115 SetTarget(target); 116 } 117 118 // destructor 119 ShapeListView::~ShapeListView() 120 { 121 _MakeEmpty(); 122 delete fMessage; 123 124 if (fShapeContainer) 125 fShapeContainer->RemoveListener(this); 126 } 127 128 // SelectionChanged 129 void 130 ShapeListView::SelectionChanged() 131 { 132 SimpleListView::SelectionChanged(); 133 134 if (!fSyncingToSelection) { 135 ShapeListItem* item 136 = dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(0))); 137 if (fMessage) { 138 BMessage message(*fMessage); 139 message.AddPointer("shape", item ? (void*)item->shape : NULL); 140 Invoke(&message); 141 } 142 } 143 144 _UpdateMenu(); 145 } 146 147 // MessageReceived 148 void 149 ShapeListView::MessageReceived(BMessage* message) 150 { 151 switch (message->what) { 152 case MSG_REMOVE: 153 RemoveSelected(); 154 break; 155 case MSG_DUPLICATE: { 156 int32 count = CountSelectedItems(); 157 int32 index = 0; 158 BList items; 159 for (int32 i = 0; i < count; i++) { 160 index = CurrentSelection(i); 161 BListItem* item = ItemAt(index); 162 if (item) 163 items.AddItem((void*)item); 164 } 165 CopyItems(items, index + 1); 166 break; 167 } 168 case MSG_RESET_TRANSFORMATION: { 169 BList shapes; 170 _GetSelectedShapes(shapes); 171 int32 count = shapes.CountItems(); 172 if (count < 0) 173 break; 174 175 Transformable* transformables[count]; 176 for (int32 i = 0; i < count; i++) { 177 Shape* shape = (Shape*)shapes.ItemAtFast(i); 178 transformables[i] = shape; 179 } 180 181 ResetTransformationCommand* command = 182 new ResetTransformationCommand(transformables, count); 183 184 fCommandStack->Perform(command); 185 break; 186 } 187 case MSG_FREEZE_TRANSFORMATION: { 188 BList shapes; 189 _GetSelectedShapes(shapes); 190 int32 count = shapes.CountItems(); 191 if (count < 0) 192 break; 193 194 FreezeTransformationCommand* command = 195 new FreezeTransformationCommand((Shape**)shapes.Items(), 196 count); 197 198 fCommandStack->Perform(command); 199 break; 200 } 201 default: 202 SimpleListView::MessageReceived(message); 203 break; 204 } 205 } 206 207 // MakeDragMessage 208 void 209 ShapeListView::MakeDragMessage(BMessage* message) const 210 { 211 SimpleListView::MakeDragMessage(message); 212 message->AddPointer("container", fShapeContainer); 213 int32 count = CountSelectedItems(); 214 for (int32 i = 0; i < count; i++) { 215 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 216 ItemAt(CurrentSelection(i))); 217 if (item) 218 message->AddPointer("shape", (void*)item->shape); 219 else 220 break; 221 } 222 223 // message->AddInt32("be:actions", B_COPY_TARGET); 224 // message->AddInt32("be:actions", B_TRASH_TARGET); 225 // 226 // message->AddString("be:types", B_FILE_MIME_TYPE); 227 //// message->AddString("be:filetypes", ""); 228 //// message->AddString("be:type_descriptions", ""); 229 // 230 // message->AddString("be:clip_name", item->shape->Name()); 231 // 232 // message->AddString("be:originator", "Icon-O-Matic"); 233 // message->AddPointer("be:originator_data", (void*)item->shape); 234 } 235 236 // AcceptDragMessage 237 bool 238 ShapeListView::AcceptDragMessage(const BMessage* message) const 239 { 240 return SimpleListView::AcceptDragMessage(message); 241 } 242 243 // SetDropTargetRect 244 void 245 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where) 246 { 247 SimpleListView::SetDropTargetRect(message, where); 248 } 249 250 // #pragma mark - 251 252 // MoveItems 253 void 254 ShapeListView::MoveItems(BList& items, int32 toIndex) 255 { 256 if (!fCommandStack || !fShapeContainer) 257 return; 258 259 int32 count = items.CountItems(); 260 Shape** shapes = new (nothrow) Shape*[count]; 261 if (!shapes) 262 return; 263 264 for (int32 i = 0; i < count; i++) { 265 ShapeListItem* item 266 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 267 shapes[i] = item ? item->shape : NULL; 268 } 269 270 MoveShapesCommand* command 271 = new (nothrow) MoveShapesCommand(fShapeContainer, 272 shapes, count, toIndex); 273 if (!command) { 274 delete[] shapes; 275 return; 276 } 277 278 fCommandStack->Perform(command); 279 } 280 281 // CopyItems 282 void 283 ShapeListView::CopyItems(BList& items, int32 toIndex) 284 { 285 if (!fCommandStack || !fShapeContainer) 286 return; 287 288 int32 count = items.CountItems(); 289 Shape* shapes[count]; 290 291 for (int32 i = 0; i < count; i++) { 292 ShapeListItem* item 293 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 294 shapes[i] = item ? new (nothrow) Shape(*item->shape) : NULL; 295 } 296 297 AddShapesCommand* command 298 = new (nothrow) AddShapesCommand(fShapeContainer, 299 shapes, count, toIndex, 300 fSelection); 301 if (!command) { 302 for (int32 i = 0; i < count; i++) 303 delete shapes[i]; 304 return; 305 } 306 307 fCommandStack->Perform(command); 308 } 309 310 // RemoveItemList 311 void 312 ShapeListView::RemoveItemList(BList& items) 313 { 314 if (!fCommandStack || !fShapeContainer) 315 return; 316 317 int32 count = items.CountItems(); 318 int32 indices[count]; 319 for (int32 i = 0; i < count; i++) 320 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); 321 322 RemoveShapesCommand* command 323 = new (nothrow) RemoveShapesCommand(fShapeContainer, 324 indices, count); 325 fCommandStack->Perform(command); 326 } 327 328 // CloneItem 329 BListItem* 330 ShapeListView::CloneItem(int32 index) const 331 { 332 if (ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index))) { 333 return new ShapeListItem(item->shape, 334 const_cast<ShapeListView*>(this)); 335 } 336 return NULL; 337 } 338 339 // IndexOfSelectable 340 int32 341 ShapeListView::IndexOfSelectable(Selectable* selectable) const 342 { 343 Shape* shape = dynamic_cast<Shape*>(selectable); 344 if (!shape) { 345 Transformer* transformer = dynamic_cast<Transformer*>(selectable); 346 if (!transformer) 347 return -1; 348 for (int32 i = 0; 349 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 350 i++) { 351 if (item->shape->HasTransformer(transformer)) 352 return i; 353 } 354 } else { 355 for (int32 i = 0; 356 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 357 i++) { 358 if (item->shape == shape) 359 return i; 360 } 361 } 362 363 return -1; 364 } 365 366 // SelectableFor 367 Selectable* 368 ShapeListView::SelectableFor(BListItem* item) const 369 { 370 ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item); 371 if (shapeItem) 372 return shapeItem->shape; 373 return NULL; 374 } 375 376 // #pragma mark - 377 378 // ShapeAdded 379 void 380 ShapeListView::ShapeAdded(Shape* shape, int32 index) 381 { 382 // NOTE: we are in the thread that messed with the 383 // ShapeContainer, so no need to lock the 384 // container, when this is changed to asynchronous 385 // notifications, then it would need to be read-locked! 386 if (!LockLooper()) 387 return; 388 389 if (_AddShape(shape, index)) 390 Select(index); 391 392 UnlockLooper(); 393 } 394 395 // ShapeRemoved 396 void 397 ShapeListView::ShapeRemoved(Shape* shape) 398 { 399 // NOTE: we are in the thread that messed with the 400 // ShapeContainer, so no need to lock the 401 // container, when this is changed to asynchronous 402 // notifications, then it would need to be read-locked! 403 if (!LockLooper()) 404 return; 405 406 // NOTE: we're only interested in Shape objects 407 _RemoveShape(shape); 408 409 UnlockLooper(); 410 } 411 412 // #pragma mark - 413 414 // SetMenu 415 void 416 ShapeListView::SetMenu(BMenu* menu) 417 { 418 if (fMenu == menu) 419 return; 420 421 fMenu = menu; 422 if (fMenu == NULL) 423 return; 424 425 BMessage* message = new BMessage(MSG_ADD_SHAPE); 426 fAddEmptyMI = new BMenuItem("Add empty", message); 427 428 message = new BMessage(MSG_ADD_SHAPE); 429 message->AddBool("path", true); 430 fAddWidthPathMI = new BMenuItem("Add with path", message); 431 432 message = new BMessage(MSG_ADD_SHAPE); 433 message->AddBool("style", true); 434 fAddWidthStyleMI = new BMenuItem("Add with style", message); 435 436 message = new BMessage(MSG_ADD_SHAPE); 437 message->AddBool("path", true); 438 message->AddBool("style", true); 439 fAddWidthPathAndStyleMI = new BMenuItem("Add with path & style", message); 440 441 fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE)); 442 fResetTransformationMI = new BMenuItem("Reset transformation", 443 new BMessage(MSG_RESET_TRANSFORMATION)); 444 fFreezeTransformationMI = new BMenuItem("Freeze transformation", 445 new BMessage(MSG_FREEZE_TRANSFORMATION)); 446 447 fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE)); 448 449 450 fMenu->AddItem(fAddEmptyMI); 451 fMenu->AddItem(fAddWidthPathMI); 452 fMenu->AddItem(fAddWidthStyleMI); 453 fMenu->AddItem(fAddWidthPathAndStyleMI); 454 455 fMenu->AddSeparatorItem(); 456 457 fMenu->AddItem(fDuplicateMI); 458 fMenu->AddItem(fResetTransformationMI); 459 fMenu->AddItem(fFreezeTransformationMI); 460 461 fMenu->AddSeparatorItem(); 462 463 fMenu->AddItem(fRemoveMI); 464 465 fDuplicateMI->SetTarget(this); 466 fResetTransformationMI->SetTarget(this); 467 fFreezeTransformationMI->SetTarget(this); 468 fRemoveMI->SetTarget(this); 469 470 _UpdateMenu(); 471 } 472 473 // SetShapeContainer 474 void 475 ShapeListView::SetShapeContainer(ShapeContainer* container) 476 { 477 if (fShapeContainer == container) 478 return; 479 480 // detach from old container 481 if (fShapeContainer) 482 fShapeContainer->RemoveListener(this); 483 484 _MakeEmpty(); 485 486 fShapeContainer = container; 487 488 if (!fShapeContainer) 489 return; 490 491 fShapeContainer->AddListener(this); 492 493 // sync 494 int32 count = fShapeContainer->CountShapes(); 495 for (int32 i = 0; i < count; i++) 496 _AddShape(fShapeContainer->ShapeAtFast(i), i); 497 } 498 499 // SetCommandStack 500 void 501 ShapeListView::SetCommandStack(CommandStack* stack) 502 { 503 fCommandStack = stack; 504 } 505 506 // #pragma mark - 507 508 // _AddShape 509 bool 510 ShapeListView::_AddShape(Shape* shape, int32 index) 511 { 512 if (shape) 513 return AddItem(new ShapeListItem(shape, this), index); 514 return false; 515 } 516 517 // _RemoveShape 518 bool 519 ShapeListView::_RemoveShape(Shape* shape) 520 { 521 ShapeListItem* item = _ItemForShape(shape); 522 if (item && RemoveItem(item)) { 523 delete item; 524 return true; 525 } 526 return false; 527 } 528 529 // _ItemForShape 530 ShapeListItem* 531 ShapeListView::_ItemForShape(Shape* shape) const 532 { 533 for (int32 i = 0; 534 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 535 i++) { 536 if (item->shape == shape) 537 return item; 538 } 539 return NULL; 540 } 541 542 // _UpdateMenu 543 void 544 ShapeListView::_UpdateMenu() 545 { 546 if (!fMenu) 547 return; 548 549 bool gotSelection = CurrentSelection(0) >= 0; 550 551 fDuplicateMI->SetEnabled(gotSelection); 552 fResetTransformationMI->SetEnabled(gotSelection); 553 fFreezeTransformationMI->SetEnabled(gotSelection); 554 fRemoveMI->SetEnabled(gotSelection); 555 } 556 557 // _GetSelectedShapes 558 void 559 ShapeListView::_GetSelectedShapes(BList& shapes) const 560 { 561 int32 count = CountSelectedItems(); 562 for (int32 i = 0; i < count; i++) { 563 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 564 ItemAt(CurrentSelection(i))); 565 if (item && item->shape) { 566 if (!shapes.AddItem((void*)item->shape)) 567 break; 568 } 569 } 570 } 571