xref: /haiku/src/kits/tracker/PoseViewScripting.cpp (revision fae7ea18b62865f5e1159e1678c851d3aa1ddce0)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 //	PoseView scripting interface
36 
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 
41 #include <ByteOrder.h>
42 #include <Debug.h>
43 #include <Message.h>
44 #include <PropertyInfo.h>
45 
46 #include "Tracker.h"
47 #include "PoseView.h"
48 
49 #define kPosesSuites "suite/vnd.Be-TrackerPoses"
50 
51 #define kPropertyPath "Path"
52 
53 // notes on PoseView scripting interface:
54 // Indices and entry_refs are used to specify poses; In the case of indices
55 // and previous/next specifiers the current PoseView sort order is used.
56 // If PoseView is not in list view mode, the order in which poses are indexed
57 // is arbitrary.
58 // Both of these specifiers, but indices more so, are likely to be accurate
59 // only untill a next change to the PoseView (a change may be adding,
60 // removing a pose, changing an attribute or stat resulting in a sort ordering
61 // change, changing the sort ordering rule. When getting a selected item,
62 // there is no guarantee that the item will still be selected after the
63 // operation. The client must be able to deal with these inaccuracies.
64 // Specifying an index/entry_ref that no longer exists will be handled well.
65 
66 #if 0
67 doo Tracker get Suites of Poses of Window test
68 doo Tracker get Path of Poses of Window test
69 doo Tracker count Entry of Poses of Window test
70 doo Tracker get Entry of Poses of Window test
71 doo Tracker get Entry 2 of Poses of Window test
72 doo Tracker count Selection of Poses of Window test
73 doo Tracker get Selection of Poses of Window test
74 doo Tracker delete Entry 'test/6L6' of Poses of Window test
75 doo Tracker execute Entry 'test/6L6' of Poses of Window test
76 doo Tracker execute Entry 2 of Poses of Window test
77 doo Tracker set Selection of Poses of Window test to [0,2]
78 doo Tracker set Selection of Poses of Window test to 'test/KT55'
79 doo Tracker create Selection of Poses of Window test to 'test/EL34'
80 doo Tracker delete Selection 'test/EL34' of Poses of Window test
81 #endif
82 
83 // ToDo:
84 //	access list view column state
85 //	access poses
86 //				- pose location
87 //				- pose text widgets
88 
89 
90 const property_info kPosesPropertyList[] = {
91 	{	kPropertyPath,
92 		{ B_GET_PROPERTY },
93 		{ B_DIRECT_SPECIFIER },
94 		"get Path of ... # returns the path of a Tracker window, "
95 			"error if no path associated",
96 		0,
97 		{ B_REF_TYPE },
98 		{},
99 		{}
100 	},
101 	{	kPropertyEntry,
102 		{ B_COUNT_PROPERTIES },
103 		{ B_DIRECT_SPECIFIER },
104 		"count Entry of ... # count entries in a PoseView",
105 		0,
106 		{ B_INT32_TYPE },
107 		{},
108 		{}
109 	},
110 	{	kPropertyEntry,
111 		{ B_DELETE_PROPERTY },
112 		{ B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER },
113 		"delete Entry {path|index} # deletes specified entries in a PoseView",
114 		0,
115 		{},
116 		{},
117 		{}
118 	},
119 	{	kPropertyEntry,
120 		{ B_GET_PROPERTY },
121 		{ B_DIRECT_SPECIFIER, B_INDEX_SPECIFIER, kPreviousSpecifier,
122 			kNextSpecifier },
123 		"get Entry [next|previous|index] # returns specified entries",
124 		0,
125 		{ B_REF_TYPE },
126 		{},
127 		{}
128 	},
129 	{	kPropertyEntry,
130 		{ B_EXECUTE_PROPERTY },
131 		{ B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER },
132 		"execute Entry {path|index}	# opens specified entries",
133 		0,
134 		{ B_REF_TYPE },
135 		{},
136 		{}
137 	},
138 	{	kPropertySelection,
139 		{ B_GET_PROPERTY },
140 		{ B_DIRECT_SPECIFIER, kPreviousSpecifier, kNextSpecifier },
141 		"get Selection [next|previous] # returns the selected entries",
142 		0,
143 		{ B_REF_TYPE },
144 		{},
145 		{}
146 	},
147 	{	kPropertySelection,
148 		{ B_SET_PROPERTY },
149 		{ B_DIRECT_SPECIFIER, kPreviousSpecifier, kNextSpecifier },
150 		"set Selection of ... to {next|previous|entry} # selects specified "
151 		"entries",
152 		0,
153 		{},
154 		{},
155 		{}
156 	},
157 	{	kPropertySelection,
158 		{ B_COUNT_PROPERTIES },
159 		{ B_DIRECT_SPECIFIER },
160 		"count Selection of ... # counts selected items",
161 		0,
162 		{ B_INT32_TYPE },
163 		{},
164 		{}
165 	},
166 	{	kPropertySelection,
167 		{ B_CREATE_PROPERTY },
168 		{ B_DIRECT_SPECIFIER },
169 		"create selection of ... to {entry|index} "
170 		"# adds specified items to a selection in a PoseView",
171 		0,
172 		{},
173 		{},
174 		{}
175 	},
176 	{	kPropertySelection,
177 		{ B_DELETE_PROPERTY },
178 		{ B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER },
179 		"delete selection {path|index} of ... "
180 		"# removes specified items from a selection in a PoseView",
181 		0,
182 		{},
183 		{},
184 		{}
185 	},
186 	{	NULL,
187 		{},
188 		{},
189 		NULL, 0,
190 		{},
191 		{},
192 		{}
193 	}
194 };
195 
196 
197 status_t
198 BPoseView::GetSupportedSuites(BMessage* data)
199 {
200 	data->AddString("suites", kPosesSuites);
201 	BPropertyInfo propertyInfo(
202 		const_cast<property_info*>(kPosesPropertyList));
203 	data->AddFlat("messages", &propertyInfo);
204 
205 	return _inherited::GetSupportedSuites(data);
206 }
207 
208 
209 bool
210 BPoseView::HandleScriptingMessage(BMessage* message)
211 {
212 	if (message->what != B_GET_PROPERTY
213 		&& message->what != B_SET_PROPERTY
214 		&& message->what != B_CREATE_PROPERTY
215 		&& message->what != B_COUNT_PROPERTIES
216 		&& message->what != B_DELETE_PROPERTY
217 		&& message->what != B_EXECUTE_PROPERTY) {
218 		return false;
219 	}
220 
221 	// dispatch scripting messages
222 	BMessage reply(B_REPLY);
223 	const char* property = 0;
224 	bool handled = false;
225 
226 	int32 index = 0;
227 	int32 form = 0;
228 	BMessage specifier;
229 	status_t result = message->GetCurrentSpecifier(&index, &specifier,
230 		&form, &property);
231 
232 	if (result != B_OK || index == -1)
233 		return false;
234 
235 	ASSERT(property != NULL);
236 
237 	switch (message->what) {
238 		case B_CREATE_PROPERTY:
239 			handled = CreateProperty(message, &specifier, form, property,
240 				&reply);
241 			break;
242 
243 		case B_GET_PROPERTY:
244 			handled = GetProperty(&specifier, form, property, &reply);
245 			break;
246 
247 		case B_SET_PROPERTY:
248 			handled = SetProperty(message, &specifier, form, property,
249 				&reply);
250 			break;
251 
252 		case B_COUNT_PROPERTIES:
253 			handled = CountProperty(&specifier, form, property, &reply);
254 			break;
255 
256 		case B_DELETE_PROPERTY:
257 			handled = DeleteProperty(&specifier, form, property, &reply);
258 			break;
259 
260 		case B_EXECUTE_PROPERTY:
261 			handled = ExecuteProperty(&specifier, form, property, &reply);
262 			break;
263 	}
264 
265 	if (handled) {
266 		// done handling message, send a reply
267 		message->SendReply(&reply);
268 	}
269 
270 	return handled;
271 }
272 
273 
274 bool
275 BPoseView::ExecuteProperty(BMessage* specifier, int32 form,
276 	const char* property, BMessage* reply)
277 {
278 	status_t result = B_OK;
279 	bool handled = false;
280 	if (strcmp(property, kPropertyEntry) == 0) {
281 		BMessage launchMessage(B_REFS_RECEIVED);
282 
283 		if (form == (int32)B_ENTRY_SPECIFIER) {
284 			// move all poses specified by entry_ref to Trash
285 			entry_ref ref;
286 			for (int32 index = 0; specifier->FindRef("refs", index, &ref)
287 				== B_OK; index++)
288 				launchMessage.AddRef("refs", &ref);
289 		} else if (form == (int32)B_INDEX_SPECIFIER) {
290 			// move all poses specified by index to Trash
291 			int32 specifyingIndex;
292 			for (int32 index = 0; specifier->FindInt32("index", index,
293 				&specifyingIndex) == B_OK; index++) {
294 				BPose* pose = PoseAtIndex(specifyingIndex);
295 
296 				if (pose == NULL) {
297 					result = B_ENTRY_NOT_FOUND;
298 					break;
299 				}
300 
301 				launchMessage.AddRef("refs", pose->TargetModel()->EntryRef());
302 			}
303 		} else
304 			return false;
305 
306 		if (result == B_OK) {
307 			// add a messenger to the launch message that will be used to
308 			// dispatch scripting calls from apps to the PoseView
309 			launchMessage.AddMessenger("TrackerViewToken",
310 				BMessenger(this, 0, 0));
311 			if (fSelectionHandler)
312 				fSelectionHandler->PostMessage(&launchMessage);
313 		}
314 		handled = true;
315 	}
316 
317 	if (result != B_OK)
318 		reply->AddInt32("error", result);
319 
320 	return handled;
321 }
322 
323 
324 bool
325 BPoseView::CreateProperty(BMessage* specifier, BMessage*, int32 form,
326 	const char* property, BMessage* reply)
327 {
328 	status_t result = B_OK;
329 	bool handled = false;
330 	if (strcmp(property, kPropertySelection) == 0) {
331 		// creating on a selection expands the current selection
332 
333 		if (form != B_DIRECT_SPECIFIER)
334 			// only support direct specifier
335 			return false;
336 
337 		// items to add to a selection may be passed as refs or as indices
338 		if (specifier->HasRef("data")) {
339 			entry_ref ref;
340 			// select poses specified by entries
341 			for (int32 index = 0; specifier->FindRef("data", index, &ref)
342 					== B_OK; index++) {
343 				int32 poseIndex;
344 				BPose* pose = FindPose(&ref, form, &poseIndex);
345 
346 				if (pose == NULL) {
347 					result = B_ENTRY_NOT_FOUND;
348 					handled = true;
349 					break;
350 				}
351 
352 				AddPoseToSelection(pose, poseIndex);
353 			}
354 			handled = true;
355 		} else {
356 			// select poses specified by indices
357 			int32 specifyingIndex;
358 			for (int32 index = 0; specifier->FindInt32("data", index,
359 					&specifyingIndex) == B_OK; index++) {
360 				BPose* pose = PoseAtIndex(specifyingIndex);
361 				if (pose == NULL) {
362 					result = B_BAD_INDEX;
363 					handled = true;
364 					break;
365 				}
366 
367 				AddPoseToSelection(pose, specifyingIndex);
368 			}
369 			handled = true;
370 		}
371 	}
372 
373 	if (result != B_OK)
374 		reply->AddInt32("error", result);
375 
376 	return handled;
377 }
378 
379 
380 bool
381 BPoseView::DeleteProperty(BMessage* specifier, int32 form,
382 	const char* property, BMessage* reply)
383 {
384 	status_t result = B_OK;
385 	bool handled = false;
386 
387 	if (strcmp(property, kPropertySelection) == 0) {
388 		// deleting on a selection is handled as removing a part of the
389 		// selection not to be confused with deleting a selected item
390 
391 		if (form == (int32)B_ENTRY_SPECIFIER) {
392 			entry_ref ref;
393 			// select poses specified by entries
394 			for (int32 index = 0; specifier->FindRef("refs", index, &ref)
395 					== B_OK; index++) {
396 				int32 poseIndex;
397 				BPose* pose = FindPose(&ref, form, &poseIndex);
398 
399 				if (pose == NULL) {
400 					result = B_ENTRY_NOT_FOUND;
401 					break;
402 				}
403 
404 				RemovePoseFromSelection(pose);
405 			}
406 			handled = true;
407 
408 		} else if (form == B_INDEX_SPECIFIER) {
409 			// move all poses specified by index to Trash
410 			int32 specifyingIndex;
411 			for (int32 index = 0; specifier->FindInt32("index", index,
412 					&specifyingIndex) == B_OK; index++) {
413 				BPose* pose = PoseAtIndex(specifyingIndex);
414 
415 				if (pose == NULL) {
416 					result = B_BAD_INDEX;
417 					break;
418 				}
419 
420 				RemovePoseFromSelection(pose);
421 			}
422 			handled = true;
423 		} else
424 			return false;
425 
426 	} else if (strcmp(property, kPropertyEntry) == 0) {
427 		// deleting entries is handled by moving entries to trash
428 
429 		// build a list of entries, specified by the specifier
430 		BObjectList<entry_ref>* entryList = new BObjectList<entry_ref>();
431 			// list will be deleted for us by the trashing thread
432 
433 		if (form == (int32)B_ENTRY_SPECIFIER) {
434 			// move all poses specified by entry_ref to Trash
435 			entry_ref ref;
436 			for (int32 index = 0; specifier->FindRef("refs", index, &ref)
437 					== B_OK; index++) {
438 				entryList->AddItem(new entry_ref(ref));
439 			}
440 		} else if (form == (int32)B_INDEX_SPECIFIER) {
441 			// move all poses specified by index to Trash
442 			int32 specifyingIndex;
443 			for (int32 index = 0; specifier->FindInt32("index", index,
444 					&specifyingIndex) == B_OK; index++) {
445 				BPose* pose = PoseAtIndex(specifyingIndex);
446 
447 				if (pose == NULL) {
448 					result = B_BAD_INDEX;
449 					break;
450 				}
451 
452 				entryList->AddItem(
453 					new entry_ref(*pose->TargetModel()->EntryRef()));
454 			}
455 		} else {
456 			delete entryList;
457 			return false;
458 		}
459 
460 		if (result == B_OK) {
461 			TrackerSettings settings;
462 			if (!settings.DontMoveFilesToTrash()) {
463 				// move the list we build into trash, don't make the
464 				// trashing task select the next item
465 				MoveListToTrash(entryList, false, false);
466 			} else
467 				Delete(entryList, false, settings.AskBeforeDeleteFile());
468 		} else {
469 			for (int i = entryList->CountItems() - 1; i >= 0; i--)
470 				delete entryList->ItemAt(i);
471 			delete entryList;
472 		}
473 
474 		handled = true;
475 	}
476 
477 	if (result != B_OK)
478 		reply->AddInt32("error", result);
479 
480 	return handled;
481 }
482 
483 
484 bool
485 BPoseView::CountProperty(BMessage*, int32, const char* property,
486 	BMessage* reply)
487 {
488 	bool handled = false;
489 	//PRINT(("BPoseView::CountProperty, %s\n", property));
490 
491 	// just return the respecitve counts
492 	if (strcmp(property, kPropertySelection) == 0) {
493 		reply->AddInt32("result", fSelectionList->CountItems());
494 		handled = true;
495 	} else if (strcmp(property, kPropertyEntry) == 0) {
496 		reply->AddInt32("result", fPoseList->CountItems());
497 		handled = true;
498 	}
499 
500 	return handled;
501 }
502 
503 
504 bool
505 BPoseView::GetProperty(BMessage* specifier, int32 form,
506 	const char* property, BMessage* reply)
507 {
508 //	PRINT(("GetProperty %s\n", property));
509 	bool handled = false;
510 	status_t result = B_OK;
511 
512 	if (strcmp(property, kPropertyPath) == 0) {
513 		if (form == B_DIRECT_SPECIFIER) {
514 			handled = true;
515 			if (TargetModel() == NULL)
516 				result = B_NOT_A_DIRECTORY;
517 			else
518 				reply->AddRef("result", TargetModel()->EntryRef());
519 		}
520 	} else if (strcmp(property, kPropertySelection) == 0) {
521 		int32 count = fSelectionList->CountItems();
522 		switch (form) {
523 			case B_DIRECT_SPECIFIER:
524 				// return entries of all poses in selection
525 				for (int32 index = 0; index < count; index++) {
526 					reply->AddRef("result", fSelectionList->ItemAt(index)->
527 						TargetModel()->EntryRef());
528 				}
529 
530 				handled = true;
531 				break;
532 
533 			case kPreviousSpecifier:
534 			case kNextSpecifier:
535 				{
536 					// return entry and index of selected pose before or after
537 					// specified pose
538 					entry_ref ref;
539 					if (specifier->FindRef("data", &ref) != B_OK)
540 						break;
541 
542 					int32 poseIndex;
543 					BPose* pose = FindPose(&ref, &poseIndex);
544 
545 					for (;;) {
546 						if (form == (int32)kPreviousSpecifier)
547 							pose = PoseAtIndex(--poseIndex);
548 						else if (form == (int32)kNextSpecifier)
549 							pose = PoseAtIndex(++poseIndex);
550 
551 						if (pose == NULL) {
552 							result = B_ENTRY_NOT_FOUND;
553 							break;
554 						}
555 
556 						if (pose->IsSelected()) {
557 							reply->AddRef("result",
558 								pose->TargetModel()->EntryRef());
559 							reply->AddInt32("index", IndexOfPose(pose));
560 							break;
561 						}
562 					}
563 
564 					handled = true;
565 					break;
566 				}
567 		}
568 	} else if (strcmp(property, kPropertyEntry) == 0) {
569 		int32 count = fPoseList->CountItems();
570 		switch (form) {
571 			case B_DIRECT_SPECIFIER:
572 			{
573 				// return all entries of all poses in PoseView
574 				for (int32 index = 0; index < count; index++) {
575 					reply->AddRef("result",
576 						PoseAtIndex(index)->TargetModel()->EntryRef());
577 				}
578 
579 				handled = true;
580 				break;
581 			}
582 
583 			case B_INDEX_SPECIFIER:
584 			{
585 				// return entry at index
586 				int32 index;
587 				if (specifier->FindInt32("index", &index) != B_OK)
588 					break;
589 
590 				if (!PoseAtIndex(index)) {
591 					result = B_BAD_INDEX;
592 					handled = true;
593 					break;
594 				}
595 				reply->AddRef("result",
596 					PoseAtIndex(index)->TargetModel()->EntryRef());
597 
598 				handled = true;
599 				break;
600 			}
601 
602 			case kPreviousSpecifier:
603 			case kNextSpecifier:
604 			{
605 				// return entry and index of pose before or after
606 				// specified pose
607 				entry_ref ref;
608 				if (specifier->FindRef("data", &ref) != B_OK)
609 					break;
610 
611 				int32 tmp;
612 				BPose* pose = FindPose(&ref, form, &tmp);
613 
614 				if (pose == NULL) {
615 					result = B_ENTRY_NOT_FOUND;
616 					handled = true;
617 					break;
618 				}
619 
620 				reply->AddRef("result", pose->TargetModel()->EntryRef());
621 				reply->AddInt32("index", IndexOfPose(pose));
622 
623 				handled = true;
624 				break;
625 			}
626 		}
627 	}
628 
629 	if (result != B_OK)
630 		reply->AddInt32("error", result);
631 
632 	return handled;
633 }
634 
635 
636 bool
637 BPoseView::SetProperty(BMessage* message, BMessage*, int32 form,
638 	const char* property, BMessage* reply)
639 {
640 	status_t result = B_OK;
641 	bool handled = false;
642 
643 	if (strcmp(property, kPropertySelection) == 0) {
644 		entry_ref ref;
645 
646 		switch (form) {
647 			case B_DIRECT_SPECIFIER:
648 			{
649 				int32 selStart;
650 				int32 selEnd;
651 				if (message->FindInt32("data", 0, &selStart) == B_OK
652 					&& message->FindInt32("data", 1, &selEnd) == B_OK) {
653 					if (selStart < 0 || selStart >= fPoseList->CountItems()
654 						|| selEnd < 0 || selEnd >= fPoseList->CountItems()) {
655 						result = B_BAD_INDEX;
656 						handled = true;
657 						break;
658 					}
659 
660 					SelectPoses(selStart, selEnd);
661 					handled = true;
662 					break;
663 				}
664 			} // fall thru
665 			case kPreviousSpecifier:
666 			case kNextSpecifier:
667 			{
668 				// PRINT(("SetProperty direct/previous/next %s\n", property));
669 				// select/unselect poses specified by entries
670 				bool clearSelection = true;
671 				for (int32 index = 0; message->FindRef("data", index, &ref)
672 						== B_OK; index++) {
673 					int32 poseIndex;
674 					BPose* pose = FindPose(&ref, form, &poseIndex);
675 
676 					if (pose == NULL) {
677 						result = B_ENTRY_NOT_FOUND;
678 						handled = true;
679 						break;
680 					}
681 
682 					if (clearSelection) {
683 						// first selected item must call SelectPose so the
684 						// selection gets cleared first
685 						SelectPose(pose, poseIndex);
686 						clearSelection = false;
687 					} else
688 						AddPoseToSelection(pose, poseIndex);
689 
690 					handled = true;
691 				}
692 				break;
693 			}
694 		}
695 	}
696 
697 	if (result != B_OK)
698 		reply->AddInt32("error", result);
699 
700 	return handled;
701 }
702 
703 
704 BHandler*
705 BPoseView::ResolveSpecifier(BMessage* message, int32 index,
706 	BMessage* specifier, int32 form, const char* property)
707 {
708 	BPropertyInfo propertyInfo(
709 		const_cast<property_info*>(kPosesPropertyList));
710 
711 	int32 result = propertyInfo.FindMatch(message, index, specifier, form,
712 		property);
713 	if (result < 0) {
714 		//PRINT(("FindMatch result %d \n"));
715 		return _inherited::ResolveSpecifier(message, index, specifier,
716 			form, property);
717 	}
718 
719 	return this;
720 }
721 
722 
723 BPose*
724 BPoseView::FindPose(const entry_ref* ref, int32 specifierForm,
725 	int32* index) const
726 {
727 	// flavor of FindPose, used by previous/next specifiers
728 
729 	BPose* pose = FindPose(ref, index);
730 
731 	if (specifierForm == (int32)kPreviousSpecifier)
732 		return PoseAtIndex(--*index);
733 	else if (specifierForm == (int32)kNextSpecifier)
734 		return PoseAtIndex(++*index);
735 	else
736 		return pose;
737 }
738