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
+}