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 "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 133 SimpleListView::SelectionChanged(); 134 135 if (!fSyncingToSelection) { 136 ShapeListItem* item 137 = dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(0))); 138 if (fMessage) { 139 BMessage message(*fMessage); 140 message.AddPointer("shape", item ? (void*)item->shape : NULL); 141 Invoke(&message); 142 } 143 } 144 145 _UpdateMenu(); 146 } 147 148 // MessageReceived 149 void 150 ShapeListView::MessageReceived(BMessage* message) 151 { 152 switch (message->what) { 153 case MSG_REMOVE: 154 RemoveSelected(); 155 break; 156 case MSG_DUPLICATE: { 157 int32 count = CountSelectedItems(); 158 int32 index = 0; 159 BList items; 160 for (int32 i = 0; i < count; i++) { 161 index = CurrentSelection(i); 162 BListItem* item = ItemAt(index); 163 if (item) 164 items.AddItem((void*)item); 165 } 166 CopyItems(items, index + 1); 167 break; 168 } 169 case MSG_RESET_TRANSFORMATION: { 170 BList shapes; 171 _GetSelectedShapes(shapes); 172 int32 count = shapes.CountItems(); 173 if (count < 0) 174 break; 175 176 Transformable* transformables[count]; 177 for (int32 i = 0; i < count; i++) { 178 Shape* shape = (Shape*)shapes.ItemAtFast(i); 179 transformables[i] = shape; 180 } 181 182 ResetTransformationCommand* command = 183 new ResetTransformationCommand(transformables, count); 184 185 fCommandStack->Perform(command); 186 break; 187 } 188 case MSG_FREEZE_TRANSFORMATION: { 189 BList shapes; 190 _GetSelectedShapes(shapes); 191 int32 count = shapes.CountItems(); 192 if (count < 0) 193 break; 194 195 FreezeTransformationCommand* command = 196 new FreezeTransformationCommand((Shape**)shapes.Items(), 197 count); 198 199 fCommandStack->Perform(command); 200 break; 201 } 202 default: 203 SimpleListView::MessageReceived(message); 204 break; 205 } 206 } 207 208 // MakeDragMessage 209 void 210 ShapeListView::MakeDragMessage(BMessage* message) const 211 { 212 SimpleListView::MakeDragMessage(message); 213 message->AddPointer("container", fShapeContainer); 214 int32 count = CountSelectedItems(); 215 for (int32 i = 0; i < count; i++) { 216 ShapeListItem* item = dynamic_cast<ShapeListItem*>( 217 ItemAt(CurrentSelection(i))); 218 if (item) 219 message->AddPointer("shape", (void*)item->shape); 220 else 221 break; 222 } 223 224 // message->AddInt32("be:actions", B_COPY_TARGET); 225 // message->AddInt32("be:actions", B_TRASH_TARGET); 226 // 227 // message->AddString("be:types", B_FILE_MIME_TYPE); 228 //// message->AddString("be:filetypes", ""); 229 //// message->AddString("be:type_descriptions", ""); 230 // 231 // message->AddString("be:clip_name", item->shape->Name()); 232 // 233 // message->AddString("be:originator", "Icon-O-Matic"); 234 // message->AddPointer("be:originator_data", (void*)item->shape); 235 } 236 237 // AcceptDragMessage 238 bool 239 ShapeListView::AcceptDragMessage(const BMessage* message) const 240 { 241 return SimpleListView::AcceptDragMessage(message); 242 } 243 244 // SetDropTargetRect 245 void 246 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where) 247 { 248 SimpleListView::SetDropTargetRect(message, where); 249 } 250 251 // #pragma mark - 252 253 // MoveItems 254 void 255 ShapeListView::MoveItems(BList& items, int32 toIndex) 256 { 257 if (!fCommandStack || !fShapeContainer) 258 return; 259 260 int32 count = items.CountItems(); 261 Shape** shapes = new (nothrow) Shape*[count]; 262 if (!shapes) 263 return; 264 265 for (int32 i = 0; i < count; i++) { 266 ShapeListItem* item 267 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 268 shapes[i] = item ? item->shape : NULL; 269 } 270 271 MoveShapesCommand* command 272 = new (nothrow) MoveShapesCommand(fShapeContainer, 273 shapes, count, toIndex); 274 if (!command) { 275 delete[] shapes; 276 return; 277 } 278 279 fCommandStack->Perform(command); 280 } 281 282 // CopyItems 283 void 284 ShapeListView::CopyItems(BList& items, int32 toIndex) 285 { 286 if (!fCommandStack || !fShapeContainer) 287 return; 288 289 int32 count = items.CountItems(); 290 Shape* shapes[count]; 291 292 for (int32 i = 0; i < count; i++) { 293 ShapeListItem* item 294 = dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i)); 295 shapes[i] = item ? new (nothrow) Shape(*item->shape) : NULL; 296 } 297 298 AddShapesCommand* command 299 = new (nothrow) AddShapesCommand(fShapeContainer, 300 shapes, count, toIndex, 301 fSelection); 302 if (!command) { 303 for (int32 i = 0; i < count; i++) 304 delete shapes[i]; 305 return; 306 } 307 308 fCommandStack->Perform(command); 309 } 310 311 // RemoveItemList 312 void 313 ShapeListView::RemoveItemList(BList& items) 314 { 315 if (!fCommandStack || !fShapeContainer) 316 return; 317 318 int32 count = items.CountItems(); 319 int32 indices[count]; 320 for (int32 i = 0; i < count; i++) 321 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); 322 323 RemoveShapesCommand* command 324 = new (nothrow) RemoveShapesCommand(fShapeContainer, 325 indices, count); 326 fCommandStack->Perform(command); 327 } 328 329 // CloneItem 330 BListItem* 331 ShapeListView::CloneItem(int32 index) const 332 { 333 if (ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index))) { 334 return new ShapeListItem(item->shape, 335 const_cast<ShapeListView*>(this)); 336 } 337 return NULL; 338 } 339 340 // IndexOfSelectable 341 int32 342 ShapeListView::IndexOfSelectable(Selectable* selectable) const 343 { 344 Shape* shape = dynamic_cast<Shape*>(selectable); 345 if (!shape) { 346 Transformer* transformer = dynamic_cast<Transformer*>(selectable); 347 if (!transformer) 348 return -1; 349 for (int32 i = 0; 350 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 351 i++) { 352 if (item->shape->HasTransformer(transformer)) 353 return i; 354 } 355 } else { 356 for (int32 i = 0; 357 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i)); 358 i++) { 359 if (item->shape == shape) 360 return i; 361 } 362 } 363 364 return -1; 365 } 366 367 // SelectableFor 368 Selectable* 369 ShapeListView::SelectableFor(BListItem* item) const 370 { 371 ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item); 372 if (shapeItem) 373 return shapeItem->shape; 374 return NULL; 375 } 376 377 // #pragma mark - 378 379 // ShapeAdded 380 void 381 ShapeListView::ShapeAdded(Shape* shape, int32 index) 382 { 383 // NOTE: we are in the thread that messed with the 384 // ShapeContainer, so no need to lock the 385 // container, when this is changed to asynchronous 386 // notifications, then it would need to be read-locked! 387 if (!LockLooper()) 388 return; 389 390 _AddShape(shape, 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