1"======================================================================
2|
3|   Smalltalk GUI generic inspectors
4|
5|
6 ======================================================================"
7
8"======================================================================
9|
10| Copyright 1992,94,95,99,2000,2001,2002,2003 Free Software Foundation, Inc.
11| Written by Brad Diller and Paolo Bonzini.
12|
13| This file is part of GNU Smalltalk.
14|
15| GNU Smalltalk is free software; you can redistribute it and/or modify it
16| under the terms of the GNU General Public License as published by the Free
17| Software Foundation; either version 2, or (at your option) any later version.
18|
19| GNU Smalltalk is distributed in the hope that it will be useful, but WITHOUT
20| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21| FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22| details.
23|
24| You should have received a copy of the GNU General Public License along with
25| GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
26| Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27|
28 ======================================================================
29"
30
31
32
33GuiData subclass: Inspector [
34    | listView textView topView fieldList fieldLists diveList |
35
36    <comment: nil>
37    <category: 'Graphics-Browser'>
38
39    text [
40	"Return string representation of currently selected instance or indexed
41	 variable"
42
43	<category: 'accessing'>
44	fieldList currentField == 0 ifTrue: [^''].
45	^fieldList currentFieldString
46    ]
47
48    object [
49	<category: 'accessing'>
50	^textView object
51    ]
52
53    object: anObject [
54	<category: 'accessing'>
55	textView object: anObject.
56	fieldLists do: [:each | each value: anObject].
57	self changeState: #fieldList.
58	self changeState: #text.
59	Primitive updateViews
60    ]
61
62    fields [
63	"Return list of variable names displayed in the variable list pane"
64
65	<category: 'accessing'>
66	^fieldList fields
67    ]
68
69    currentField: field [
70	<category: 'accessing'>
71	fieldList currentField: field.
72	self changeState: #text.
73	Primitive updateViews
74    ]
75
76    currentField [
77	<category: 'accessing'>
78	^fieldList currentField
79    ]
80
81    fieldLists [
82	<category: 'initializing'>
83	^fieldLists
84    ]
85
86    fieldLists: aCollection [
87	<category: 'initializing'>
88	fieldLists := aCollection.
89	self fieldList: aCollection first value
90    ]
91
92    fieldList: aFieldList [
93	<category: 'initializing'>
94	fieldList := aFieldList.
95	fieldList inspector: self.
96	textView isNil
97	    ifFalse:
98		[textView object: fieldList value.
99		listView menuInit: ((fieldList inspectMenu: listView)
100			    selectors: #(#() #('Dive' #dive) #('Pop' #pop) #('Browse class' #browse) #())
101			    receiver: self
102			    argument: nil).
103		self initFieldListsMenu.
104		self changeState: #fieldList.
105		self changeState: #text.
106		Primitive updateViews]
107    ]
108
109    initFieldListsMenu [
110	<category: 'initializing'>
111	fieldLists do:
112		[:each |
113		listView menu selectors:
114			{
115			{each key.
116			#fieldList:.
117			each value}}
118		    receiver: self]
119    ]
120
121    open [
122	<category: 'initializing'>
123	| pane |
124	topView := BrowserShell
125		    new: 'Inspecting %1%2' %
126				{fieldList value isClass
127				    ifFalse: [fieldList value class article , ' ']
128				    ifTrue: [''].
129				fieldList value class nameIn: Namespace current}.
130	topView data: self.
131	topView blox
132	    x: 20
133	    y: 330
134	    width: 300
135	    height: 100.
136	pane := Form new: 'forms' in: topView.
137	topView addChildView: pane.
138	self openIn: pane menuName: 'Edit'.
139	topView display
140    ]
141
142    openIn: pane [
143	<category: 'initializing'>
144	self openIn: pane menuName: 'Edit'
145    ]
146
147    openIn: pane menuName: label [
148	"Initialize Inspector and open an Inspector window on anObject"
149
150	"Initialize instance variable, fields, which governs display of
151	 variable list pane"
152
153	<category: 'initializing'>
154	"Create a Form manager which will contain the variable and text pane"
155
156	| listWidth container |
157	container := pane blox.
158	listWidth := pane blox width // 3 min: 100.
159
160	"Create a text window and position it in first third of window"
161	pane addChildView: ((listView := PList new: 'InstanceVars' in: pane)
162		    initialize;
163		    data: self;
164		    stateChange: #fieldList;
165		    handleUserChange: #currentField:;
166		    listMsg: #fields;
167		    selectionMsg: #currentField;
168		    yourself).
169	(listView blox)
170	    width: listWidth height: pane blox height;
171	    inset: 2.
172
173	"Create text pane and position it in right 2/3s of window"
174	pane addChildView: ((textView := PText new: pane)
175		    data: self;
176		    stateChange: #text;
177		    handleUserChange: #setArg:from:;
178		    textMsg: #text;
179		    canBeDirty: false;
180		    setEvaluationKeyBindings;
181		    object: fieldList value;
182		    yourself).
183	(textView blox)
184	    width: pane blox width - listWidth height: pane blox height;
185	    inset: 2.
186	textView blox posHoriz: listView blox.
187	"Initialize popup for text pane"
188	textView menuInit: ((PopupMenu new: textView label: label)
189		    selectors: #(#('Cut' #gstCut) #('Copy' #gstCopy) #('Paste' #gstPaste) #() #('Clear' #gstClear) #() #('Line...' #line) #('Find...' #find))
190			receiver: textView
191			argument: nil;
192		    selectors: #(#() #('Do it' #eval: #textView) #('Print it' #evalAndPrintResult: #textView) #('Inspect' #inspectValue: #textView))
193			receiver: self
194			argument: textView;
195		    selectors: #(#() #('Accept' #compileIt) #('Cancel' #revert) #() #('Close' #close))
196			receiver: textView
197			argument: nil;
198		    yourself).
199	self fieldLists: self fieldLists.
200	self changeState: #fieldList.
201	Primitive updateViews
202    ]
203
204    browse [
205	<category: 'list view menu'>
206	ClassBrowser new openOn: self object class asClass
207    ]
208
209    dive [
210	<category: 'list view menu'>
211	diveList isNil ifTrue: [diveList := OrderedCollection new].
212	diveList addLast: fieldLists.
213	self fieldLists: fieldList currentFieldValue inspectorFieldLists
214    ]
215
216    pop [
217	<category: 'list view menu'>
218	diveList isNil ifTrue: [^self].
219	diveList isEmpty ifTrue: [^self].
220	self fieldLists: diveList removeLast
221    ]
222
223    eval: aView [
224	"Invoked from text pane popup.  Evaluate selected expression in text pane"
225
226	<category: 'text view menu'>
227	| pos aStream text |
228	text := aView blox getSelection.
229	(text isNil or: [text size = 0]) ifTrue: [^aView beep].
230	aStream := WriteStream on: (String new: 0).
231	fieldList value class evaluate: text to: fieldList value
232    ]
233
234    evalAndPrintResult: aView [
235	"Print result of evaluation of selected expression to its right"
236
237	<category: 'text view menu'>
238	| pos result text |
239	text := aView blox getSelection.
240	(text isNil or: [text size = 0]) ifTrue: [^aView beep].
241	result := fieldList value class
242		    evaluate: text
243		    to: fieldList value
244		    ifError: [:fname :lineNo :errorString | errorString].
245	aView blox insertTextSelection: result printString
246    ]
247
248    inspectValue: aView [
249	"Open an inspector for evaluated selected expression.  If selected expression
250	 contains parsing error(s), the error description is selected and printed at end
251	 of selection"
252
253	<category: 'text view menu'>
254	| obj text |
255	text := aView blox getSelection.
256	(text isNil or: [text size = 0]) ifTrue: [^aView beep].
257	obj := fieldList value class
258		    evaluate: text
259		    to: fieldList value
260		    ifError:
261			[:fname :lineNo :errorString |
262			aView displayError: errorString.
263			^nil].
264	obj inspect
265    ]
266
267    setArg: aString from: aView [
268	"Store result of evaluation of selected expression in selected instance or
269	 indexed variable"
270
271	<category: 'text view menu'>
272	| obj |
273	(aString isNil or: [aString size = 0]) ifTrue: [^aView beep].
274	fieldList currentField <= 1 ifTrue: [^aView beep].
275
276	"Evaluate selected expression.  If expression contains a parsing error, the
277	 description is output at end of expression and nil is returned"
278	obj := fieldList value class
279		    evaluate: aString
280		    to: fieldList value
281		    ifError:
282			[:fname :lineNo :errorString |
283			aView displayError: errorString at: lineNo.
284			^nil].
285	fieldList currentFieldValue: obj
286    ]
287]
288
289
290
291ValueHolder subclass: InspectorFieldList [
292    | inspector fields currentField |
293
294    <category: 'Graphics-Browser'>
295    <comment: nil>
296
297    evalAndInspectResult: listView [
298	<category: 'field list menu'>
299	currentField == 0 ifTrue: [^listView beep].
300	self currentFieldValue inspect
301    ]
302
303    inspector [
304	<category: 'private'>
305	^inspector
306    ]
307
308    inspector: anInspector [
309	<category: 'private'>
310	inspector := anInspector
311    ]
312
313    inspectMenu: listView [
314	"Initialize menu for variable list pane"
315
316	<category: 'private'>
317	^(PopupMenu new: listView)
318	    selectors: #(#('Inspect' #evalAndInspectResult: #listView))
319	    receiver: self
320	    argument: listView
321    ]
322
323    currentField [
324	<category: 'private'>
325	^currentField
326    ]
327
328    currentField: assoc [
329	"Set variable list index to 'index'."
330
331	<category: 'private'>
332	currentField := assoc key
333    ]
334
335    currentFieldValue: obj [
336	<category: 'private'>
337	self subclassResponsibility
338    ]
339
340    currentFieldValue [
341	<category: 'private'>
342	self subclassResponsibility
343    ]
344
345    currentFieldString [
346	<category: 'private'>
347	^[self currentFieldValue printString] on: Error
348	    do: [:ex | ex return: '[%1 exception raised while printing item]' % {ex class}]
349    ]
350
351    fieldsSortBlock [
352	"nil = use OrderedCollection, else a block to be used as fields'
353	 sort block."
354
355	<category: 'private'>
356	^nil
357    ]
358
359    fields [
360	<category: 'private'>
361	^fields
362    ]
363
364    value: anObject [
365	<category: 'private'>
366	super value: anObject.
367	fields := self fieldsSortBlock ifNil: [OrderedCollection new]
368		    ifNotNil: [:block | SortedCollection sortBlock: block].
369	currentField := 0.
370	self computeFieldList: anObject
371    ]
372
373    computeFieldList: anObject [
374	"Store a string representation of the inspected object, anObject, in fields.
375	 The first string is self.  The subsequent values are the object's complete set
376	 of instance variables names.  If the object is a variable class, append
377	 numerical indices from one to number of indexed variables"
378
379	<category: 'private'>
380	self subclassResponsibility
381    ]
382]
383
384
385
386InspectorFieldList subclass: ObjectInspectorFieldList [
387    | base |
388
389    <category: 'Graphics-Browser'>
390    <comment: nil>
391
392    currentFieldValue: obj [
393	<category: 'accessing'>
394	currentField > base
395	    ifTrue: [self value basicAt: currentField - base put: obj]
396	    ifFalse: [self value instVarAt: currentField - 1 put: obj]
397    ]
398
399    currentFieldValue [
400	<category: 'accessing'>
401	currentField == 0 ifTrue: [^nil].
402	currentField == 1 ifTrue: [^self value].
403	^currentField > base
404	    ifTrue: [self value basicAt: currentField - base]
405	    ifFalse: [self value instVarAt: currentField - 1]
406    ]
407
408    computeFieldList: anObject [
409	"Store a string representation of the inspected object, anObject, in fields.
410	 The first string is self.  The subsequent values are the object's complete
411	 set of instance variables names.  If the object is a variable class,
412	 append numerical indices from one to number of indexed variables"
413
414	<category: 'accessing'>
415	| instVarNames |
416	fields add: 'self'.
417	instVarNames := anObject class allInstVarNames.
418	1 to: instVarNames size
419	    do: [:x | fields add: (instVarNames at: x) asString].
420	base := fields size.
421	anObject class isVariable
422	    ifTrue: [1 to: anObject validSize do: [:x | fields add: x printString]]
423    ]
424]
425
426
427
428ObjectInspectorFieldList subclass: CollectionInspectorFieldList [
429    | array |
430
431    <category: 'Graphics-Browser'>
432    <comment: nil>
433
434    currentFieldValue: obj [
435	<category: 'initializing'>
436	(self value isKindOf: SequenceableCollection) not
437	    | (self value class == SortedCollection)
438		ifTrue:
439		    [(self value)
440			remove: self currentFieldValue ifAbsent: [];
441			add: obj.
442		    array := self value asArray.
443		    ^self].
444	self value at: currentField - 1 put: obj.
445	array == self value ifFalse: [array at: currentField - 1 put: obj]
446    ]
447
448    currentFieldValue [
449	<category: 'initializing'>
450	currentField == 0 ifTrue: [^nil].
451	currentField == 1 ifTrue: [^self value].
452	^array at: currentField - 1
453    ]
454
455    computeFieldList: anObject [
456	"Use this so that the user doesn't see implementation-dependant details"
457
458	<category: 'initializing'>
459	array := (anObject isKindOf: ArrayedCollection)
460		    ifFalse: [anObject asArray]
461		    ifTrue: [anObject].
462	super computeFieldList: array
463    ]
464]
465
466
467
468Object extend [
469
470    inspectorFieldLists [
471	<category: 'debugging'>
472	^{'Basic' -> (BLOX.BLOXBrowser.ObjectInspectorFieldList new value: self)}
473    ]
474
475    basicInspect [
476	"Open an Inspector window on self"
477
478	<category: 'debugging'>
479	^(BLOX.BLOXBrowser.Inspector new)
480	    fieldLists:
481		    {'Basic' -> (BLOX.BLOXBrowser.ObjectInspectorFieldList new value: self)};
482	    open;
483	    yourself
484    ]
485
486    inspect [
487	"Open an inspection window on self -- by default, the same Inspector used
488	 in #basicInspect."
489
490	<category: 'debugging'>
491	^(BLOX.BLOXBrowser.Inspector new)
492	    fieldLists: self inspectorFieldLists;
493	    open;
494	    yourself
495    ]
496
497]
498
499
500
501Collection extend [
502
503    inspectorFieldLists [
504	<category: 'debugging'>
505	^
506	{'Elements'
507	    -> (BLOX.BLOXBrowser.CollectionInspectorFieldList new value: self).
508	'Basic' -> (BLOX.BLOXBrowser.ObjectInspectorFieldList new value: self)}
509    ]
510
511]
512
513