ФЭНДОМ


Что такое Trippy и зачем о нем знатьПравить

В некоторый момент в VisualWorks место традиционного инспектора объектов занял Trippy -- framework, позволяющий создавать специализированные инспекторы для различных классов. Вот несколько примеров таких инспекторов:

  • SequenceInspector (для упорядоченных коллекций)
  • SetInspector и DictionaryInspector
  • TextEditorInspector -- для String и Text
  • BehaviorInspector -- браузер, встроенный в инспектор (вкладка Methods инспектора)

В Cincom сейчас идет работа над превращением в аналогичный framework браузера, что позволит на его базе создавать браузеры для любых доменов (а не только для классов Смолтока). Аналогичная работа ведется и для Squeak -- в рамках проекта OmniBrowser.

Знать и уметь Trippy полезно потому, что с его помощью можно за минимальное время (минуты) создавать дополнительные удобства, облегчающие процесс разработки. Это интересная и поучительная альтернатива созданию "настоящего" графического интерфейса (деятельность, заслуженно считающаяся "могилой человеко-часов").

В этом тьюториале показывается, как "заточить" стандартный инспектор Trippy под нужды игрушечного домена.

Стоит также прочитать описание Trippy (по-английски): http://www.cincomsmalltalk.com/CincomSmalltalkWiki/Trippy+(new+inspector)+Walkthrough

Где берется обсуждаемый здесь код?Править

Выложен в Cincom Public Repository, пакет называется TaskTrippyTutorial.

Было бы неплохо, если бы кто-нибудь захостил парсель, чтобы его могли забрать те, у кого нет доступа к репозиторию, а есть только HTTP

Наш игрушечный доменПравить

Класс Task -- упрощенная версия того, что могло бы появиться в программе-органайзере или в системе учета проблем.

  • <text> - описание задачи; первая (или единственная) строка будет использоваться при отображении задачи в списках (см. метод Task >> title)
  • <complete> - булевский признак того, что задача завершена
  • <dueDate> - срок, к которому должна быть завершена задача (в виде Date), либо nil
  • <relations> - отношения, в которые входит данная задача

В нашем примере эти отношения сведены к единственному бинарному отношению BasicTaskLink. Каждая из двух задач, входящих в это отношение, имеет одну из двух ролей, незамысловато названых first и second. Имея на руках задачу, можно попросить у связи имя роли для другого конца связи (метод otherRoleNameFor:) или объект на другом конце связи (метод otherFor:).

Строковые представления объектовПравить

Для начала стоит разобраться с представлением объектов в виде строк. Обычно объекты пользовательских типов в отладчике и при печати на Workspace выглядят как нечто вроде "a Tools.Trippy.TaskTutorial.Task". Не очень информативно, но совсем нетрудно сделать так, чтобы в печатное представление попадало название задачи.

 Task >> printOn: aStream 
 	aStream
 		nextPutAll: 'Task(';
 		nextPutAll: self title;
 		nextPut: $)

Теперь значение выражения вроде Task text: 'Вымыть окна' выглядело при печати на Workspace как Task('вымыть окна').

Иногда удобнее переопределять метод printString, который по умолчанию вызывает printOn:

 Task >> printString
     ^'Task(' , self title, ')'

Далее, существует специальный метод для получения строкового представления, которое будет использоваться в GUI (прежде всего в SequenceView). По умолчанию этот метод используется printString

 Task >> displayString
 	^self title

Чтобы увидеть, как выглядит новоиспеченное строковое представление для GUI, можно сделать нечто вроде

 SequenceView openOn: (List with: (Task text: 'clean up windows') with: (Task text: 'repair the iron'))

Малоизвестный, но полезный факт: displayString может вернуть не только строку, но и Text (восходящий к седой древности класс для представления текста с форматированием).

 Task >> displayString
 	| textRepresentation |
 	textRepresentation := self text asText.
 	self complete ifFalse: [textRepresentation allBold].
 	^textRepresentation

Task sequence view.gif

Украшательство можно продолжить, например, зачеркнуть выполненные задачи:

 Task >> displayString
 	^self text asText 
 		emphasizeAllWith: (self complete ifTrue: [#strikeout] ifFalse: [#bold])

На "украшенную" версию можно полюбоваться с помощью

 SequenceView openOn: (List with: ((Task text: 'clean up windows') complete: true) with: (Task text: 'repair the iron'))

Простейшие способы расширения TrippyПравить

Начнем с милого пустяка:

 Object >> inspectorExtraAttributes
 	^self class comment isEmpty 
 		ifFalse: 
 			[Array with: (Tools.Trippy.TextAttribute label: 'class comment'
 						text: self class comment)]
 		ifTrue: [#()]

Суть изменения ясна из картинки:

Trippy class comment.gif

Любопытно, что почти тот же код имеется в методе inspectorExtraAttributes у стандартного ClassDescription (но мне нравится возможность посмотреть комментарии, не отрываясь от инстанса):

 ClassDescription >> inspectorExtraAttributes
 	^Array with:
 		(Tools.Trippy.TextAttribute
 			label: (#comment << #dialogs >> 'comment')
 			textBlock: [self comment])

Обратите внимание, что используется конструктор label:textBlock:, а не label:text:. Использование textBlock позволяет пересчитывать текст при каждом обновлении окна инспектора.

Попробуем, вооружившись приобретенными знаниями, изготовить дополнительный атрибут. Пусть это будет число дней, оставшихся до намеченного срока завершения:

 Task >> inspectorExtraAttributes
 	^super inspectorExtraAttributes 
 		copyWith: (Tools.Trippy.TextAttribute label: 'days due'
 				textBlock: 
 					[| daysDue |
 					dueDate ifNil: ['n/a']
 						ifNotNil: 
 							[daysDue := dueDate subtractDate: Date today.
 							daysDue printString]])

Trippy due date nil.gif

Приятная фича Trippy: чтобы поменять значение переменной, достаточно выделить её, ввести на правой панели новое значение и нажать Accept (Control-S). Например, если ввести Date today, значение вычисленного атрибута days due сменится на 0. Доступ к переменной происходит, естественно, минуя методы (если таковые имеются) через мета-протокол

Вот еще пример того, для чего пригождаются дополнительные атрибуты. Есть такая хорошая штука -- кватернионы. Отличная вещь для представления поворотов в трехмерном пространстве и для интерполяции при анимации, но вот беда: четыре параметра кватерниона почти ничего не говорят человеку. Зато человек неплохо понимает повороты в виде Эйлеровых углов. Ничто не мешает добавить кватернионам дополнительный атрибут, показывающий соответствующие ему углы Эйлера. Например, для кватерниона <3.1415 0.7071 0.7071 0> это будут (в градусах) крен -180, тангаж 0, рысканье -90.

Продолжим нащи игры.

 Task >> inspectorActions
 	^Array with: (Action label: 'Mark as done' block: [self complete: true])

Дополнительные действия над объектом попадают в меню Object

Trippy custom action in menu.gif

Ещё одна полезная штука: меняя метод inspectorCollaborators мы можем определять объекты, тесно сотрудничающие с данным и добавлять команды для перехода к ним в меню Go.

В нашем крошечном домене пример будет довольно наиграным. Хорошим примером использования этого механизма Trippy является класс ApplicationWindow. Мы же в качестве "сотрудника" будем рассматривать коллекцию задач, связанных с данной.

 Task >> inspectorCollaborators
 	^Array with: (Tools.Trippy.Collaborator label: 'Related Tasks'
 				block: [self relations collect: [:each | each otherFor: self]])

попробуем создать несколько связанных между собой задач:

 t1 := Task text: 'clean up the windows'.
 t2 := Task text: 'buy glass cleaner'.
 t3 := Task text: 'fix the ladder'.
 l1 := BasicTaskLink first: t1 second: t2.
 l2 := BasicTaskLink first: t1 second: t3.
 t1 inspect

вот как выглядит теперь меню Go:

Trippy custom collaborators in menu.gif

Панели инспекторовПравить

У большинства объектов окно инспектора содержит две закладки: Basic и Methods. У коллекций добавляется третья -- Elements. Мы можем изготовить свою собственную закладку -- например, у задачи может быть закладка Related Tasks.

Каждой закладке соответствует класс инспектора. Перечень классов задается методов inspectorClasses:

 Task >> inspectorClasses
 	^super inspectorClasses copyWith: RelatedTasksInspector

Класс BehaviorInspector (закладка Methods) добавляется в конец всегда.

RelatedTasksInspector -- это очень простой инспектор, в нем переопределяются только два метода, унаследованных от предка, BasicInspector. Эти два метода образуют протокол deсomposition, и описывают разделение исследуемого объекта на абстрактные части (parts). Части -- это очень фундаментальное понятие в Trippy. Частями являются переменные экземпляра, элементы коллекций, дополнительные атрибуты. Все части относятся к классам, производным от Part. В RelatedTasksInspector мы используем стандартную часть для вычислимых атрибутов -- DerivedAttribute.

 RelatedTasksInspector >> partCount
 	^self relations size
 RelatedTasksInspector >> partAt: anInteger 
 	| r |
 	r := self relations at: anInteger.
 	^Tools.Trippy.DerivedAttribute label: (r otherRoleNameFor: object)
 		value: (r otherFor: object)

Используемый в этих двух методах self relations просто кеширует коллецию отношений, возвращаемую объектом Task:

 RelatedTasksInspector >> relations
 	^relations ifNil: [relations := object relations]

Кроме того, на стороне класса у RelatedTaskInspector переопределен метод tabLabel. Вот и всё!

Вот что получается в результате. Обратите внимание, что реализованное нами ранее дополнительное действие Mark as done присутствует также и во всплывающем меню:

Trippy related tasks pane.gif

Инспектор можно представлять себе как средство навигации по иерархии частей, из которых состоят объекты. Понимание этого дает ключ к нескольким очень полезным механизмам, ускоряющим перемещение по иерархии: Explore parts, Explore siblings, Explore visited. Вот, например, что делает Explore parts с нашим RelatedTasksInspector

Trippy related tasks pane explore parts.gif

Создание собственных PartsПравить

Попробуем теперь создать собственную часть, которая будет "знать" про то, как устроены связи между задачами. Для этого мы расширим наш RelatedTasksInspector до AdvancedRelatedTasksInspector

 Task >> inspectorClasses
 	^super inspectorClasses copyWith: AdvancedRelatedTasksInspector

Протокол decomposition выглядит несколько иначе:

 AdvancedRelatedTasksInspector >> partAt: anInteger 
 	| r |
 	r := self relations at: anInteger.
 	^TaskLinkPart forLink: r task: object

Вновь создаваемая TaskLinkPart в качестве значения будет возвращать противоположный конец связи

 TaskLinkPart >> value
 	^self link otherFor: self task

При этом в списке частей TaskLinkPart будет показывать роль, соответствующую противоположному концу связи

 TaskLinkPart >> displayString
 	^self link otherRoleNameFor: self task

Расширение менюПравить

Иногда действия связаны не с конкретным объектом, а с группой объектов. Например, мы хотим посмотреть все задачи, связанные с выделенными в данный момент. Для этого можно создать всплывающее меню для списка частей:

 AdvancedRelatedTasksInspector >> buildFieldListMenu
 	^(super buildFieldListMenu)
 		addLine;
 		addItem: ((MenuItem labeled: 'Inspect linked to selected')
 					nameKey: #inspectLinkedToSelected;
 					value: [self inspectLinkedToSelected];
 					enabled: [self selections size >= 1]);
 		yourself

Кроме того, метод augmentMenuBar: дает зацепку для добавления собственных пунктов в меню окна инспектора.

ПрочееПравить

Возможно, вы этого не знали, но Trippy поддерживает Drag and Drop. В простых случаях от инспектора достаточно обработать сообщения drop:at: и drop:before:, а также, возможно, переопределить dragControllerClass. Посмотрите, как это сделано в AdvancedRelatedTasksInspector

Кстати, части из инспектора можно таскать на Workspace для дальнейшего исследования.

У инспектора, кстати, есть свой собственный маленький Workspace -- Evaluation pane (которая по умолчаию спрятана). Содержимое этого Workspace сохраняется и при этом даже обновляется в других инспекторах.

СсылкиПравить

Добавляем собственные установки

Расширение "Инспектора"

Обнаружено использование расширения AdBlock.


Викия — это свободный ресурс, который существует и развивается за счёт рекламы. Для блокирующих рекламу пользователей мы предоставляем модифицированную версию сайта.

Викия не будет доступна для последующих модификаций. Если вы желаете продолжать работать со страницей, то, пожалуйста, отключите расширение для блокировки рекламы.

Также на ФЭНДОМЕ

Случайная вики