You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/07/12 14:11:13 UTC
[groovy] branch master updated: GROOVY-10686: Additional refactoring to more cleanly browse other objects (now keeps objects as a stack of cards) or Shift-double-click for new window
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 1eb5ccd32f GROOVY-10686: Additional refactoring to more cleanly browse other objects (now keeps objects as a stack of cards) or Shift-double-click for new window
1eb5ccd32f is described below
commit 1eb5ccd32f9e3d3a09d079d2e9a276381cfed207
Author: Paul King <pa...@asert.com.au>
AuthorDate: Wed Jul 13 00:10:13 2022 +1000
GROOVY-10686: Additional refactoring to more cleanly browse other objects (now keeps objects as a stack of cards) or Shift-double-click for new window
---
.../groovy/groovy/console/ui/ObjectBrowser.groovy | 316 ++++++++++++---------
1 file changed, 176 insertions(+), 140 deletions(-)
diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
index 116ba15de6..1da9fd3310 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/ObjectBrowser.groovy
@@ -18,6 +18,7 @@
*/
package groovy.console.ui
+import groovy.beans.Bindable
import groovy.inspect.Inspector
import groovy.swing.table.TableSorter
import groovy.swing.SwingBuilder
@@ -50,8 +51,10 @@ import static groovy.inspect.Inspector.MEMBER_VALUE_IDX
class ObjectBrowser {
def inspector
- def path
- def swing, frame, fieldTable, methodTable, arrayTable, collectionTable, mapTable
+ String path
+ int pathCount = 0
+ def tracker
+ def swing, frame, fieldTable, methodTable, arrayTable, collectionTable, mapTable, cards, pathMenu, mb
private static final Comparator<Object> comparator = new Inspector.MemberComparatorWithValue()
static void main(args) {
@@ -67,168 +70,196 @@ class ObjectBrowser {
browser.run()
}
+ void inspectAlso(objectUnderInspection, String path = '') {
+ int idx = pathCount++
+ def pathId = 'path' + idx
+ tracker.current = idx
+ cards.add(makeCard(swing, new Inspector(objectUnderInspection), path), pathId)
+ cards.layout.show(cards, pathId)
+ cards.revalidate()
+ pathMenu.add(swing.menuItem { action(name: path, enabled: bind{ tracker.current != idx }, closure: this.&switchCard.curry(idx)) })
+ mb.revalidate()
+ }
+
void run() {
swing = new SwingBuilder()
+ tracker = new CardTracker()
frame = swing.frame(title: 'Groovy Object Browser', location: [200, 200],
size: [800, 600], pack: true, show: true,
iconImage: swing.imageIcon(Console.ICON_PATH).image,
defaultCloseOperation: WindowConstants.DISPOSE_ON_CLOSE) {
- menuBar {
+ mb = menuBar {
+ pathMenu = menu(text: 'Path') {
+ int idx = pathCount++
+ menuItem { action(name: path.toString(), enabled: bind{ tracker.current != idx }, closure: this.&switchCard.curry(idx)) }
+ }
menu(text: 'Help') {
menuItem { action(name: 'About', closure: this.&showAbout) }
}
}
- panel {
- borderLayout()
- panel(name: 'Class Info',
- border: emptyBorder([5, 10, 5, 10]),
- constraints: NORTH) {
- flowLayout(alignment: FlowLayout.LEFT)
- def props = inspector.classProps
- def classLabel = '<html>' + props.join('<br>')
- label(classLabel)
- }
- tabbedPane(constraints: CENTER) {
- if (inspector.object?.class?.array) {
- scrollPane(name: ' Array data ') {
- arrayTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
- tableModel(list: inspector.object.toList().withIndex()) {
- closureColumn(header: 'Index', read: { it[1] })
- closureColumn(header: 'Value', read: { it[0] })
- closureColumn(header: 'Raw Value', read: { it[0] })
- }
- }
- arrayTable.columnModel.with {
- getColumn(2).with {
- minWidth = 0
- maxWidth = 0
- width = 0
- preferredWidth = 0
- }
- getColumn(0).preferredWidth = 50
- getColumn(1).preferredWidth = 400
- }
- arrayTable.addMouseListener(makeClickAdapter(arrayTable, 2) { row ->
- path + "[${arrayTable.model.getValueAt(row, 0)}]"
- })
- }
- } else if (inspector.object instanceof Collection) {
- scrollPane(name: ' Collection data ') {
- collectionTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
- tableModel(list: inspector.object.withIndex()) {
- closureColumn(header: 'Index', read: { it[1] })
- closureColumn(header: 'Value', read: { it[0] })
- closureColumn(header: 'Raw Value', read: { it[0] })
- }
- }
- collectionTable.columnModel.with {
- getColumn(2).with {
- minWidth = 0
- maxWidth = 0
- width = 0
- preferredWidth = 0
- }
- getColumn(0).preferredWidth = 50
- getColumn(1).preferredWidth = 400
+ cards = panel {
+ cardLayout()
+ makeCard(swing, inspector, path)
+ }
+ }
+ // can't seem to get the 'path0' to stick so re-add and remove the original
+ cards.add(makeCard(swing, inspector, path), 'path0')
+ cards.remove(0)
+ cards.revalidate()
+
+ // Add a bit of formatting
+ addSorter(arrayTable)
+ addSorter(collectionTable)
+ addSorter(mapTable)
+ addSorter(fieldTable)
+ addSorter(methodTable)
+
+ frame.toFront()
+ }
+
+ private makeCard(builder, inspector, String path) {
+ builder.panel {
+ borderLayout()
+ panel(name: 'Class Info',
+ border: emptyBorder([5, 10, 5, 10]),
+ constraints: NORTH) {
+ flowLayout(alignment: FlowLayout.LEFT)
+ def props = inspector.classProps
+ def classLabel = '<html>' + props.join('<br>')
+ label(classLabel)
+ }
+ tabbedPane(constraints: CENTER) {
+ if (inspector.object?.class?.array) {
+ scrollPane(name: ' Array data ') {
+ arrayTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
+ tableModel(list: inspector.object.toList().withIndex()) {
+ closureColumn(header: 'Index', read: { it[1] })
+ closureColumn(header: 'Value', read: { it[0] })
+ closureColumn(header: 'Raw Value', read: { it[0] })
}
- collectionTable.addMouseListener(makeClickAdapter(collectionTable, 2) { row ->
- path + "[${collectionTable.model.getValueAt(row, 0)}]"
- })
}
- } else if (inspector.object instanceof Map) {
- scrollPane(name: ' Map data ') {
- mapTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
- tableModel(list: inspector.object.entrySet().withIndex()) {
- closureColumn(header: 'Index', read: { it[1] })
- closureColumn(header: 'Key', read: { it[0].key })
- closureColumn(header: 'Value', read: { it[0].value })
- closureColumn(header: 'Raw Value', read: { it[0].value })
- }
- }
- ToolTipManager.sharedInstance().registerComponent(mapTable)
- mapTable.columnModel.with {
- getColumn(3).with {
- minWidth = 0
- maxWidth = 0
- width = 0
- preferredWidth = 0
- }
- getColumn(0).preferredWidth = 50
- getColumn(1).preferredWidth = 200
- getColumn(2).preferredWidth = 400
+ arrayTable.columnModel.with {
+ getColumn(2).with {
+ minWidth = 0
+ maxWidth = 0
+ width = 0
+ preferredWidth = 0
}
- mapTable.addMouseListener(makeClickAdapter(mapTable, 2) { row ->
- path + "[${mapTable.model.getValueAt(row, 1)}]"
- })
+ getColumn(0).preferredWidth = 50
+ getColumn(1).preferredWidth = 400
}
+
+ arrayTable.addMouseListener(makeClickAdapter(arrayTable, 2) { row ->
+ path + "[${arrayTable.model.getValueAt(row, 0)}]"
+ })
}
- scrollPane(name: ' Properties (includes public fields) ') {
- def data = Inspector.sort(inspector.propertiesWithInfo.toList(), comparator)
- fieldTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
- tableModel(list: data) {
- closureColumn(header: 'Name', read: { it.v2[MEMBER_NAME_IDX] })
- closureColumn(header: 'Value', read: { it.v2[MEMBER_VALUE_IDX] })
- closureColumn(header: 'Type', read: { it.v2[MEMBER_TYPE_IDX] })
- closureColumn(header: 'Origin', read: { it.v2[MEMBER_ORIGIN_IDX] })
- closureColumn(header: 'Modifier', read: { it.v2[MEMBER_MODIFIER_IDX] })
- closureColumn(header: 'Declarer', read: { it.v2[MEMBER_DECLARER_IDX] })
- closureColumn(header: 'Raw Value', read: { it.v1 })
+ } else if (inspector.object instanceof Collection) {
+ scrollPane(name: ' Collection data ') {
+ collectionTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
+ tableModel(list: inspector.object.withIndex()) {
+ closureColumn(header: 'Index', read: { it[1] })
+ closureColumn(header: 'Value', read: { it[0] })
+ closureColumn(header: 'Raw Value', read: { it[0] })
}
}
- fieldTable.columnModel.getColumn(6).with {
- minWidth = 0
- maxWidth = 0
- width = 0
+ collectionTable.columnModel.with {
+ getColumn(2).with {
+ minWidth = 0
+ maxWidth = 0
+ width = 0
+ preferredWidth = 0
+ }
+ getColumn(0).preferredWidth = 50
+ getColumn(1).preferredWidth = 400
}
- fieldTable.addMouseListener(makeClickAdapter(fieldTable, 6) { row ->
- path + (path.size() == 0 ? '' : '.') + "${fieldTable.model.getValueAt(row, 0)}"
+ collectionTable.addMouseListener(makeClickAdapter(collectionTable, 2) { row ->
+ path + "[${collectionTable.model.getValueAt(row, 0)}]"
})
}
- scrollPane(name: ' (Meta) Methods ') {
- methodTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
- def data = Inspector.sort(inspector.methodsWithInfo.toList(), comparator)
- data.addAll(Inspector.sort(inspector.metaMethodsWithInfo.toList(), comparator))
- tableModel(list: data) {
- closureColumn(header: 'Name', read: { it.v2[MEMBER_NAME_IDX] })
- closureColumn(header: 'Params', read: { it.v2[MEMBER_PARAMS_IDX] })
- closureColumn(header: 'Type', read: { it.v2[MEMBER_TYPE_IDX] })
- closureColumn(header: 'Origin', read: { it.v2[MEMBER_ORIGIN_IDX] })
- closureColumn(header: 'Modifier', read: { it.v2[MEMBER_MODIFIER_IDX] })
- closureColumn(header: 'Declarer', read: { it.v2[MEMBER_DECLARER_IDX] })
- closureColumn(header: 'Exceptions', read: { it.v2[MEMBER_EXCEPTIONS_IDX] })
- closureColumn(header: 'Raw Value', read: { it.v1 })
+ } else if (inspector.object instanceof Map) {
+ scrollPane(name: ' Map data ') {
+ mapTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
+ tableModel(list: inspector.object.entrySet().withIndex()) {
+ closureColumn(header: 'Index', read: { it[1] })
+ closureColumn(header: 'Key', read: { it[0].key })
+ closureColumn(header: 'Value', read: { it[0].value })
+ closureColumn(header: 'Raw Value', read: { it[0].value })
}
}
- methodTable.columnModel.getColumn(7).with {
- minWidth = 0
- maxWidth = 0
- width = 0
+ ToolTipManager.sharedInstance().registerComponent(mapTable)
+ mapTable.columnModel.with {
+ getColumn(3).with {
+ minWidth = 0
+ maxWidth = 0
+ width = 0
+ preferredWidth = 0
+ }
+ getColumn(0).preferredWidth = 50
+ getColumn(1).preferredWidth = 200
+ getColumn(2).preferredWidth = 400
}
- methodTable.addMouseListener(makeClickAdapter(methodTable, 7) { row ->
- path + (path.size() == 0 ? '' : ".method['") + "${methodTable.model.getValueAt(row, 0)}']"
+ mapTable.addMouseListener(makeClickAdapter(mapTable, 2) { row ->
+ path + "[${mapTable.model.getValueAt(row, 1)}]"
})
}
}
- panel(name: 'Path',
- border: emptyBorder([5, 10, 5, 10]),
- constraints: SOUTH) {
- boxLayout(axis: 2)
- label('Path: ')
- textField(editable: false, text: path)
+ scrollPane(name: ' Properties (includes public fields) ') {
+ def data = Inspector.sort(inspector.propertiesWithInfo.toList(), comparator)
+ fieldTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
+ tableModel(list: data) {
+ closureColumn(header: 'Name', read: { it.v2[MEMBER_NAME_IDX] })
+ closureColumn(header: 'Value', read: { it.v2[MEMBER_VALUE_IDX] })
+ closureColumn(header: 'Type', read: { it.v2[MEMBER_TYPE_IDX] })
+ closureColumn(header: 'Origin', read: { it.v2[MEMBER_ORIGIN_IDX] })
+ closureColumn(header: 'Modifier', read: { it.v2[MEMBER_MODIFIER_IDX] })
+ closureColumn(header: 'Declarer', read: { it.v2[MEMBER_DECLARER_IDX] })
+ closureColumn(header: 'Raw Value', read: { it.v1 })
+ }
+ }
+ fieldTable.columnModel.getColumn(6).with {
+ minWidth = 0
+ maxWidth = 0
+ width = 0
+ }
+ fieldTable.addMouseListener(makeClickAdapter(fieldTable, 6) { row ->
+ path + (path.size() == 0 ? '' : '.') + "${fieldTable.model.getValueAt(row, 0)}"
+ })
+ }
+ scrollPane(name: ' (Meta) Methods ') {
+ methodTable = table(new CellValueToolTipJTable(), selectionMode: SINGLE_SELECTION) {
+ def data = Inspector.sort(inspector.methodsWithInfo.toList(), comparator)
+ data.addAll(Inspector.sort(inspector.metaMethodsWithInfo.toList(), comparator))
+ tableModel(list: data) {
+ closureColumn(header: 'Name', read: { it.v2[MEMBER_NAME_IDX] })
+ closureColumn(header: 'Params', read: { it.v2[MEMBER_PARAMS_IDX] })
+ closureColumn(header: 'Type', read: { it.v2[MEMBER_TYPE_IDX] })
+ closureColumn(header: 'Origin', read: { it.v2[MEMBER_ORIGIN_IDX] })
+ closureColumn(header: 'Modifier', read: { it.v2[MEMBER_MODIFIER_IDX] })
+ closureColumn(header: 'Declarer', read: { it.v2[MEMBER_DECLARER_IDX] })
+ closureColumn(header: 'Exceptions', read: { it.v2[MEMBER_EXCEPTIONS_IDX] })
+ closureColumn(header: 'Raw Value', read: { it.v1 })
+ }
+ }
+ methodTable.columnModel.getColumn(7).with {
+ minWidth = 0
+ maxWidth = 0
+ width = 0
+ }
+ methodTable.addMouseListener(makeClickAdapter(methodTable, 7) { row ->
+ path + (path.size() == 0 ? '' : ".method['") + "${methodTable.model.getValueAt(row, 0)}']"
+ })
}
}
+ panel(name: 'Path',
+ border: emptyBorder([5, 10, 5, 10]),
+ constraints: SOUTH) {
+ boxLayout(axis: 2)
+ label('Path: ')
+ textField(editable: false, text: path)
+ }
}
-
- // Add a bit of formatting
- addSorter(arrayTable)
- addSorter(collectionTable)
- addSorter(mapTable)
- addSorter(fieldTable)
- addSorter(methodTable)
-
- frame.toFront()
}
def makeClickAdapter(table, int valueCol, Closure pathClosure) {
@@ -239,8 +270,10 @@ class ObjectBrowser {
if (selectedRow != -1) {
def value = table.model.getValueAt(selectedRow, valueCol)
if (value != null) {
- closeFrameIfAltDoubleClick(e)
- ObjectBrowser.inspect(value, pathClosure(selectedRow))
+ if (e.shiftDown)
+ ObjectBrowser.inspect(value, pathClosure(selectedRow))
+ else
+ inspectAlso(value, pathClosure(selectedRow))
}
}
}
@@ -256,11 +289,10 @@ class ObjectBrowser {
}
}
- void closeFrameIfAltDoubleClick(MouseEvent e) {
- if (e.altDown) {
- frame.visible = false;
- frame.dispose();
- }
+ void switchCard(int idx, EventObject evt) {
+ tracker.current = idx
+ cards.layout.show(cards, 'path' + idx)
+ cards.revalidate()
}
void showAbout(EventObject evt) {
@@ -272,3 +304,7 @@ class ObjectBrowser {
dialog.show()
}
}
+
+@Bindable class CardTracker {
+ int current = 0
+}