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