Merge remote-tracking branch 'origin/master' into numpy-array-view
authorAlexander Marchuk <Alexander.Marchuk@jetbrains.com>
Thu, 18 Sep 2014 12:12:42 +0000 (16:12 +0400)
committerAlexander Marchuk <Alexander.Marchuk@jetbrains.com>
Thu, 18 Sep 2014 12:12:42 +0000 (16:12 +0400)
15 files changed:
platform/platform-resources-en/src/messages/ActionsBundle.properties
python/helpers/pydev/pydevd_vars.py
python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
python/src/META-INF/python-core.xml
python/src/com/jetbrains/python/actions/view/array/ArrayTableCellEditor.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/ArrayTableCellRenderer.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/ArrayTableComponent.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.form [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/ArrayValueProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/Numpy2DArraySlice.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/NumpyArrayValueProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/PyViewArrayAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/RowNumberTable.java [new file with mode: 0644]
python/src/com/jetbrains/python/actions/view/array/XDebuggerTreeTableListener.java [new file with mode: 0644]

index 3da9a7382d01316aef38132dfbc39cbd2ad2a54e..e9ea6fa4bb8fb630eb74caafeb1ce913f31f89d9 100644 (file)
@@ -971,6 +971,7 @@ action.Debugger.AddToWatch.text=Add to Watches
 action.Debugger.EvaluateInConsole.text=Evaluate in Console
 action.Debugger.UnmuteOnStop.text=Unmute breakpoints on session finish
 action.Debugger.AutoRenderer.text=Auto
+action.PyDebugger.ViewArray.text = View as array
 group.EditorPopupMenu.text=Editor Popup Menu
 group.EditorPopupMenu.description=Editor Popup Menu
 action.Add\ to\ migration\ roots.text=_Add to migration roots
index d85c4c3f2ff973ca8fb63f9f6595840d48c0c30c..a710eb7d2d0f5018b81746b4c907239d7a0f798e 100644 (file)
@@ -2,6 +2,7 @@
     resolution/conversion to XML.
 """
 import pickle
+from django_frame import DjangoTemplateFrame
 from pydevd_constants import * #@UnusedWildImport
 from types import * #@UnusedWildImport
 
index ddec6797ea8dcc6019b066473815ac8fa523efea..26f81dddf56a56a4422ac917f9a9a3faa906d746 100644 (file)
@@ -124,7 +124,10 @@ public class PyDebugValue extends XNamedValue {
 
   private static String removeLeadingZeros(@NotNull String name) {
     //bugs.python.org/issue15254: "0" prefix for octal
-    return name.replaceFirst("^0+(?!$)", "");
+    while (name.length() > 1 && name.startsWith("0")) {
+      name = name.substring(1);
+    }
+    return name;
   }
 
   private static boolean isLen(String name) {
index 5091d43386a112c885a2ddc87a308c639fa28351..f82093b19d0dc7fd3746a89300142931cb9105af 100644 (file)
     <action id="PyInvertBooleanAction" class="com.jetbrains.python.refactoring.invertBoolean.PyInvertBooleanAction" text="Invert Boolean">
       <add-to-group group-id="RefactoringMenu" anchor="last" />
     </action>
+
+    <action id="PyDebugger.ViewArray" class="com.jetbrains.python.actions.view.array.PyViewArrayAction">
+      <add-to-group group-id="XDebugger.ValueGroup" anchor="after" relative-to-action="Debugger.Tree.AddToWatches"/>
+    </action>
+
   </actions>
 
   <extensions defaultExtensionNs="com.intellij.spellchecker">
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayTableCellEditor.java b/python/src/com/jetbrains/python/actions/view/array/ArrayTableCellEditor.java
new file mode 100644 (file)
index 0000000..e190856
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.EditorTextField;
+import com.intellij.xdebugger.XExpression;
+import com.intellij.xdebugger.XSourcePosition;
+import com.intellij.xdebugger.evaluation.EvaluationMode;
+import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
+import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
+import com.intellij.xdebugger.impl.ui.XDebuggerEditorBase;
+import com.jetbrains.python.PythonLanguage;
+import com.jetbrains.python.debugger.PyDebuggerEditorsProvider;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.table.TableCellEditor;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+/**
+ * @author amarch
+ */
+class ArrayTableCellEditor extends AbstractCellEditor implements TableCellEditor {
+  MyTableEditor myEditor;
+  Project myProject;
+  Object lastValue;
+
+  public ArrayTableCellEditor(Project project) {
+    super();
+    myProject = project;
+  }
+
+  public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
+                                               int rowIndex, int vColIndex) {
+
+
+    myEditor = new MyTableEditor(myProject, new PyDebuggerEditorsProvider(), "arrayTableView", null,
+                                 new XExpressionImpl(value.toString(), PythonLanguage.getInstance(), "", EvaluationMode.CODE_FRAGMENT));
+
+    lastValue = value;
+    JComponent editorComponent = myEditor.getComponent();
+
+    editorComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+      .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "strokeEnter");
+    editorComponent.getActionMap().put("strokeEnter", new AbstractAction() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        doOKAction();
+      }
+    });
+    editorComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+      .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escapeStroke");
+    editorComponent.getActionMap().put("escapeStroke", new AbstractAction() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        cancelEditing();
+      }
+    });
+
+    return editorComponent;
+  }
+
+  public Object getCellEditorValue() {
+    if (myEditor.getEditor() != null) {
+      return myEditor.getEditor().getDocument().getText();
+    }
+    else {
+      return null;
+    }
+  }
+
+  public void doOKAction() {
+  }
+
+  public void cancelEditing() {
+    new WriteCommandAction(null) {
+      protected void run(@NotNull Result result) throws Throwable {
+        if (myEditor.getEditor() != null) {
+          myEditor.getEditor().getDocument().setText(lastValue.toString());
+        }
+      }
+    }.execute();
+    myEditor.getComponent().repaint();
+    myEditor.getComponent().requestFocus();
+  }
+
+  public class MyTableEditor extends XDebuggerEditorBase {
+    private final EditorTextField myEditorTextField;
+    private XExpression myExpression;
+
+    public MyTableEditor(Project project,
+                         XDebuggerEditorsProvider debuggerEditorsProvider,
+                         @Nullable @NonNls String historyId,
+                         @Nullable XSourcePosition sourcePosition, @NotNull XExpression text) {
+      super(project, debuggerEditorsProvider, EvaluationMode.CODE_FRAGMENT, historyId, sourcePosition);
+      myExpression = XExpressionImpl.changeMode(text, getMode());
+      myEditorTextField = new EditorTextField(createDocument(myExpression), project, debuggerEditorsProvider.getFileType()) {
+        @Override
+        protected EditorEx createEditor() {
+          final EditorEx editor = super.createEditor();
+          editor.setVerticalScrollbarVisible(false);
+          editor.setOneLineMode(true);
+          return editor;
+        }
+
+        @Override
+        protected boolean isOneLineMode() {
+          return false;
+        }
+      };
+      myEditorTextField.setFontInheritedFromLAF(false);
+    }
+
+    @Override
+    public JComponent getComponent() {
+      return myEditorTextField;
+    }
+
+    @Override
+    protected void doSetText(XExpression text) {
+      myEditorTextField.setText(text.getExpression());
+    }
+
+    @Override
+    public XExpression getExpression() {
+      return getEditorsProvider()
+        .createExpression(getProject(), myEditorTextField.getDocument(), myExpression.getLanguage(), EvaluationMode.CODE_FRAGMENT);
+    }
+
+    @Override
+    @Nullable
+    public JComponent getPreferredFocusedComponent() {
+      final Editor editor = myEditorTextField.getEditor();
+      return editor != null ? editor.getContentComponent() : null;
+    }
+
+    @Nullable
+    @Override
+    public Editor getEditor() {
+      return myEditorTextField.getEditor();
+    }
+
+    @Override
+    public void selectAll() {
+      myEditorTextField.selectAll();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayTableCellRenderer.java b/python/src/com/jetbrains/python/actions/view/array/ArrayTableCellRenderer.java
new file mode 100644 (file)
index 0000000..0a9ae77
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import java.awt.*;
+
+/**
+* @author amarch
+*/
+class ArrayTableCellRenderer extends DefaultTableCellRenderer {
+
+  double min;
+  double max;
+  Color minColor;
+  Color maxColor;
+  boolean colored = true;
+
+  public ArrayTableCellRenderer(double min, double max) {
+    this.min = min;
+    this.max = max;
+    minColor = new Color(100, 0, 0, 200);
+    maxColor = new Color(254, 0, 0, 200);
+  }
+
+  public void setColored(boolean colored) {
+    this.colored = colored;
+  }
+
+  public Component getTableCellRendererComponent(JTable table, Object value,
+                                                 boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {
+    if (isSelected) {
+      // cell (and perhaps other cells) are selected
+    }
+
+    if (hasFocus) {
+      // this cell is the anchor and the table has the focus
+    }
+
+    if (value != null) {
+      setText(value.toString());
+    }
+
+
+    if (max != min) {
+      if (colored) {
+        try {
+          double med = Double.parseDouble(value.toString());
+          int r = (int)(minColor.getRed() + Math.round((maxColor.getRed() - minColor.getRed()) / (max - min) * (med - min)));
+          this.setBackground(new Color(r % 256, 0, 0, 200));
+        }
+        catch (NumberFormatException e) {
+        }
+      }
+      else {
+        this.setBackground(new Color(255, 255, 255));
+      }
+    }
+
+
+    return this;
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayTableComponent.java b/python/src/com/jetbrains/python/actions/view/array/ArrayTableComponent.java
new file mode 100644 (file)
index 0000000..20f983c
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.ui.components.JBScrollPane;
+import com.intellij.ui.table.JBTable;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableModel;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/**
+ * @author amarch
+ */
+class ArrayTableComponent extends JPanel {
+  private JScrollPane myScrollPane;
+  private JTextField mySliceTextField;
+  private JTextField myFormatTextField;
+  private JBTable myTable;
+  private JCheckBox myColoredCheckbox;
+
+  private static final String DATA_LOADING_IN_PROCESS = "Please wait, load array data.";
+
+  private static final String NOT_APPLICABLE = "View not applicable for ";
+
+  public ArrayTableComponent() {
+    super(new GridBagLayout());
+
+    mySliceTextField = new JTextField();
+    mySliceTextField.setToolTipText("Current slice");
+    mySliceTextField.setEditable(false);
+
+    myFormatTextField = new JTextField();
+    myFormatTextField.setToolTipText("Value format");
+    myFormatTextField.setEditable(false);
+
+    myTable = new JBTable() {
+      public boolean getScrollableTracksViewportWidth() {
+        return getPreferredSize().width < getParent().getWidth();
+      }
+    };
+    myTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
+    myTable.setRowSelectionAllowed(false);
+
+    myColoredCheckbox = new JCheckBox();
+    myColoredCheckbox.setText("Colored");
+    myColoredCheckbox.setSelected(true);
+    myColoredCheckbox.addItemListener(new ItemListener() {
+      @Override
+      public void itemStateChanged(ItemEvent e) {
+        if (e.getSource() == myColoredCheckbox) {
+          if (myTable.getColumnCount() > 0 && myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) {
+            ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0);
+            if (myColoredCheckbox.isSelected()) {
+              renderer.setColored(true);
+            }
+            else {
+              renderer.setColored(false);
+            }
+          }
+          myScrollPane.repaint();
+        }
+      }
+    });
+
+    myScrollPane = new JBScrollPane(myTable);
+    myScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+    myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+    JTable rowTable = new RowNumberTable(myTable);
+    myScrollPane.setRowHeaderView(rowTable);
+    myScrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
+                           rowTable.getTableHeader());
+
+    add(myScrollPane,
+        new GridBagConstraints(0, 0, 4, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    add(mySliceTextField,
+        new GridBagConstraints(0, 1, 1, 1, 1, 0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    add(new JLabel("Format:"),
+        new GridBagConstraints(1, 1, 1, 1, 1, 0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    add(myFormatTextField,
+        new GridBagConstraints(2, 1, 1, 1, 1, 0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    add(myColoredCheckbox,
+        new GridBagConstraints(3, 1, 1, 1, 1, 0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+  }
+
+  public JTextField getSliceTextField() {
+    return mySliceTextField;
+  }
+
+  public JTextField getFormatTextField() {
+    return myFormatTextField;
+  }
+
+  public JBTable getTable() {
+    return myTable;
+  }
+
+  public JCheckBox getColored() {
+    return myColoredCheckbox;
+  }
+
+  private void setSpinnerText(String text) {
+    DefaultTableModel model = new DefaultTableModel(1, 1) {
+      @Override
+      public boolean isCellEditable(int row, int column) {
+        return false;
+      }
+    };
+    myTable.setModel(model);
+    myTable.setValueAt(text, 0, 0);
+  }
+
+  public void setDefaultSpinnerText() {
+    setSpinnerText(DATA_LOADING_IN_PROCESS);
+  }
+
+  public void setErrorSpinnerText(Exception e) {
+    setSpinnerText(e.getMessage());
+  }
+
+  public void setErrorSpinnerText(String message) {
+    //todo: Access to realized (ever shown) UI components
+    // should be done only from the AWT event dispatch thread,
+    // revalidate(), invalidate() & repaint() is ok from any thread
+    setSpinnerText(message);
+  }
+
+  public void setNotApplicableSpinner(XValueNodeImpl node) {
+    setSpinnerText(NOT_APPLICABLE + node.getName());
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.form b/python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.form
new file mode 100644 (file)
index 0000000..76452f9
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.actions.view.array.ArrayTableForm">
+  <grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="0" left="0" bottom="0" right="0"/>
+    <constraints>
+      <xy x="20" y="20" width="500" height="375"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <scrollpane id="90774" class="com.intellij.ui.components.JBScrollPane" binding="myScrollPane" custom-create="true">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <horizontalScrollBarPolicy value="30"/>
+        </properties>
+        <border type="none"/>
+        <children>
+          <component id="c7f7c" class="com.intellij.ui.table.JBTable" binding="myTable" custom-create="true">
+            <constraints/>
+            <properties>
+              <autoResizeMode value="0"/>
+              <fillsViewportHeight value="true"/>
+            </properties>
+          </component>
+        </children>
+      </scrollpane>
+      <component id="32570" class="javax.swing.JTextField" binding="mySliceTextField">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+            <preferred-size width="150" height="-1"/>
+          </grid>
+        </constraints>
+        <properties>
+          <toolTipText value="Current slice"/>
+        </properties>
+      </component>
+      <component id="b6d58" class="javax.swing.JCheckBox" binding="myColoredCheckbox">
+        <constraints>
+          <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <enabled value="true"/>
+          <selected value="true"/>
+          <text value="Colored"/>
+        </properties>
+      </component>
+      <grid id="db2ae" binding="myFormatPanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="8f575" class="javax.swing.JLabel" binding="myFormatLabel">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <text value="Format:"/>
+            </properties>
+          </component>
+          <component id="b056a" class="javax.swing.JTextField" binding="myFormatTextField">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+                <preferred-size width="150" height="-1"/>
+              </grid>
+            </constraints>
+            <properties>
+              <toolTipText value="Array value format"/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+    </children>
+  </grid>
+</form>
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.java b/python/src/com/jetbrains/python/actions/view/array/ArrayTableForm.java
new file mode 100644 (file)
index 0000000..2176e27
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.ui.components.JBScrollPane;
+import com.intellij.ui.table.JBTable;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author amarch
+ */
+public class ArrayTableForm {
+  private JTextField mySliceTextField;
+  private JCheckBox myColoredCheckbox;
+  private JTextField myFormatTextField;
+  private JBScrollPane myScrollPane;
+  private JLabel myFormatLabel;
+  private JPanel myFormatPanel;
+  private JPanel myMainPanel;
+  public JBTable myTable;
+  private PyViewArrayAction.MyDialog myParentDialog;
+
+
+  private static final String DATA_LOADING_IN_PROCESS = "Please wait, load array data.";
+
+  private static final String NOT_APPLICABLE = "View not applicable for ";
+
+  public ArrayTableForm(PyViewArrayAction.MyDialog dialog) {
+    myParentDialog = dialog;
+  }
+
+  private void createUIComponents() {
+    myTable = new JBTable() {
+      public boolean getScrollableTracksViewportWidth() {
+        return getPreferredSize().width < getParent().getWidth();
+      }
+    };
+    myTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
+    myTable.setRowSelectionAllowed(false);
+
+    myScrollPane = new JBScrollPane();
+    JTable rowTable = new RowNumberTable(myTable) {
+      @Override
+      protected void paintComponent(@NotNull Graphics g) {
+        getEmptyText().setText("");
+        super.paintComponent(g);
+      }
+    };
+    myScrollPane.setRowHeaderView(rowTable);
+    myScrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
+                           rowTable.getTableHeader());
+  }
+
+  public JTextField getSliceTextField() {
+    return mySliceTextField;
+  }
+
+  public JTextField getFormatTextField() {
+    return myFormatTextField;
+  }
+
+  public JTable getTable() {
+    return myTable;
+  }
+
+  public JCheckBox getColored() {
+    return myColoredCheckbox;
+  }
+
+  public void setDefaultStatus() {
+    myTable.getEmptyText().setText(DATA_LOADING_IN_PROCESS);
+  }
+
+  public void setErrorText(Exception e) {
+    setErrorText(e.getMessage());
+  }
+
+  public void setErrorText(String message) {
+    myParentDialog.setError(message);
+  }
+
+  public void setNotApplicableStatus(XValueNodeImpl node) {
+    myTable.getEmptyText().setText(NOT_APPLICABLE + node.getName());
+  }
+
+  public JComponent getMainPanel() {
+    return myMainPanel;
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/ArrayValueProvider.java b/python/src/com/jetbrains/python/actions/view/array/ArrayValueProvider.java
new file mode 100644 (file)
index 0000000..a32b5d6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.xdebugger.frame.XValueNode;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+
+/**
+* @author amarch
+*/
+abstract class ArrayValueProvider {
+  XValueNode myBaseNode;
+
+  public ArrayValueProvider(XValueNode node){
+    myBaseNode = node;
+  }
+
+  public abstract boolean isNumeric();
+
+  public String getNodeName() {
+    return ((XValueNodeImpl)myBaseNode).getName();
+  }
+
+  public XDebuggerTree getTree() {
+    return ((XValueNodeImpl)myBaseNode).getTree();
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/Numpy2DArraySlice.java b/python/src/com/jetbrains/python/actions/view/array/Numpy2DArraySlice.java
new file mode 100644 (file)
index 0000000..92bb193
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener;
+import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.jetbrains.python.debugger.PyDebugValue;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author amarch
+ */
+class Numpy2DArraySlice {
+  private String myValueName;
+  private List<Pair<Integer, Integer>> myFullSlice;
+  private int[] myShape;
+  private String myDtype;
+  private String mySlicePresentation;
+  private NumpyArrayValueProvider myValueProvider;
+  private DataEvaluator myDataEvaluator;
+  private int myRows = 0;
+  private int myColumns = 0;
+  private int myRowsOffset = 0;
+  private int myColumnsOffset = 0;
+
+  public Numpy2DArraySlice(@NotNull String valueName,
+                           @NotNull List<Pair<Integer, Integer>> fullSlice,
+                           @NotNull NumpyArrayValueProvider valueProvider,
+                           @NotNull int[] shape,
+                           @Nullable String dtype) {
+    myValueName = valueName;
+    myFullSlice = fullSlice;
+    myValueProvider = valueProvider;
+    myShape = shape;
+    myDtype = dtype;
+    myDataEvaluator = new DataEvaluator();
+
+    checkShapeConsistency();
+  }
+
+  public Numpy2DArraySlice getInstance() {
+    return this;
+  }
+
+  private void checkShapeConsistency() throws IllegalStateException {
+    boolean consistent = myFullSlice.size() == myShape.length || myFullSlice.size() == myShape.length - 1;
+    if (consistent) {
+      if (myFullSlice.size() == myShape.length - 1) {
+        myFullSlice.add(new Pair<Integer, Integer>(0, myShape[myShape.length - 1]));
+      }
+      for (int index = 0; index < myFullSlice.size() - 2; index++) {
+        Pair<Integer, Integer> slice = myFullSlice.get(index);
+        consistent = slice.getFirst().equals(slice.getSecond());
+        if (!consistent) break;
+      }
+    }
+
+    if (!consistent) {
+      throw new IllegalStateException("Illegal slice shape.");
+    }
+
+    int size = myFullSlice.size();
+    myRows = myFullSlice.get(size - 2).getSecond() - myFullSlice.get(size - 2).getFirst();
+    myColumns = myFullSlice.get(size - 1).getSecond() - myFullSlice.get(size - 1).getFirst();
+    myRowsOffset = myFullSlice.get(size - 2).getFirst();
+    myColumnsOffset = myFullSlice.get(size - 1).getFirst();
+  }
+
+  public String getPresentation() {
+    if (mySlicePresentation == null) {
+      fillPresentation();
+    }
+    return mySlicePresentation;
+  }
+
+  private void fillPresentation() {
+    mySlicePresentation = myValueName;
+    for (int index = 0; index < myFullSlice.size() - 2; index++) {
+      mySlicePresentation += "[" + myFullSlice.get(index).getFirst() + "]";
+    }
+    mySlicePresentation +=
+      "[" + myRowsOffset + ":" + (myRowsOffset + myRows) + ", " + myColumnsOffset + ":" + (myColumnsOffset + myColumns) + "]";
+  }
+
+  public void startFillData(Runnable callback) {
+    myDataEvaluator.evaluateData(callback);
+  }
+
+  public boolean dataFilled() {
+    return myDataEvaluator.dataFilled();
+  }
+
+  private class DataEvaluator {
+    private Object[][] myData;
+    private int myFilledRows = 0;
+    private int nextRow = 0;
+
+    public Object[][] getData() {
+      return myData;
+    }
+
+    public boolean dataFilled() {
+      return myRows > 0 && myFilledRows == myRows;
+    }
+
+
+    public void evaluateData(final Runnable callback) {
+      final XDebuggerEvaluator.XEvaluationCallback computeChildrenCallback = new XDebuggerEvaluator.XEvaluationCallback() {
+        @Override
+        public void evaluated(@NotNull final XValue result) {
+          final String name = ((PyDebugValue)result).getName();
+          DebuggerUIUtil.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+              XValueNodeImpl node = new XValueNodeImpl(myValueProvider.getTree(), null, name, result);
+              node.startComputingChildren();
+            }
+          });
+        }
+
+        @Override
+        public void errorOccurred(@NotNull String errorMessage) {
+        }
+      };
+
+      XDebuggerTreeListener treeListener = new XDebuggerTreeListener() {
+        @Override
+        public void nodeLoaded(@NotNull RestorableStateNode node, String name) {
+        }
+
+        @Override
+        public void childrenLoaded(@NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) {
+          String fullName = ((XValueNodeImpl)node).getName();
+          if (fullName != null && fullName.contains("[")) {
+            fullName = fullName.substring(0, fullName.lastIndexOf("["));
+          }
+          int row = -1;
+          if (fullName != null && fullName.contains("[")) {
+            row = Integer.parseInt(fullName.substring(fullName.lastIndexOf('[') + 1, fullName.length() - 1));
+          }
+          else if (fullName != null && !fullName.contains("[")) {
+            row = 0;
+          }
+          if (row != -1 && myData[row][0] == null) {
+            for (int i = 0; i < node.getChildCount() - 1; i++) {
+              myData[row][i] = ((XValueNodeImpl)node.getChildAt(i + 1)).getRawValue();
+            }
+            myFilledRows += 1;
+          }
+          if (myFilledRows == myRows) {
+            node.getTree().removeTreeListener(this);
+            callback.run();
+          }
+          else {
+            nextRow += 1;
+            startEvalNextRow(computeChildrenCallback);
+          }
+        }
+      };
+
+      myData =
+        new Object[myRows][myColumns];
+      myRows = myData.length;
+      myValueProvider.getTree().addTreeListener(treeListener);
+      nextRow = myRowsOffset;
+      startEvalNextRow(computeChildrenCallback);
+    }
+
+    private void startEvalNextRow(XDebuggerEvaluator.XEvaluationCallback callback) {
+      String evalRowCommand = "list(" + getUpperSlice(getPresentation());
+      if (!isOneDimensional()) {
+        evalRowCommand += "[" + nextRow + "]";
+      }
+      evalRowCommand +=
+        "[" + myColumnsOffset + ":" + (myColumnsOffset + myColumns) + "])";
+      myValueProvider.getEvaluator().evaluate(evalRowCommand, callback, null);
+    }
+
+    private String getUpperSlice(String presentation) {
+      return presentation.substring(0, presentation.lastIndexOf('['));
+    }
+  }
+
+  private boolean isOneDimensional() {
+    return myShape.length == 2 && myShape[0] == 1;
+  }
+
+
+  public int[] getShape() {
+    return myShape;
+  }
+
+  public void setShape(int[] shape) {
+    myShape = shape;
+  }
+
+  public String getDtype() {
+    return myDtype;
+  }
+
+  public void setDtype(String dtype) {
+    myDtype = dtype;
+  }
+
+  public Object[][] getData() {
+    return myDataEvaluator.getData();
+  }
+}
+
diff --git a/python/src/com/jetbrains/python/actions/view/array/NumpyArrayValueProvider.java b/python/src/com/jetbrains/python/actions/view/array/NumpyArrayValueProvider.java
new file mode 100644 (file)
index 0000000..48fe6c9
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.ui.AppUIUtil;
+import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
+import com.intellij.xdebugger.frame.XValue;
+import com.intellij.xdebugger.frame.XValueNode;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeState;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.jetbrains.python.debugger.PyDebugValue;
+import com.jetbrains.python.debugger.PyDebuggerEvaluator;
+import org.jetbrains.annotations.NotNull;
+
+import javax.management.InvalidAttributeValueException;
+import javax.swing.*;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableCellEditor;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author amarch
+ */
+class NumpyArrayValueProvider extends ArrayValueProvider {
+  private ArrayTableForm myComponent;
+  private JTable myTable;
+  private Project myProject;
+  private PyDebuggerEvaluator myEvaluator;
+  private Numpy2DArraySlice myLastPresentation;
+  private String myDtypeKind;
+  private int[] myShape;
+
+  private final static int COLUMNS_IN_DEFAULT_SLICE = 50;
+  private final static int ROWS_IN_DEFAULT_SLICE = 50;
+
+  public NumpyArrayValueProvider(@NotNull XValueNode node, @NotNull ArrayTableForm component, @NotNull Project project) {
+    super(node);
+    myComponent = component;
+    myProject = project;
+    myTable = component.getTable();
+    myEvaluator = new PyDebuggerEvaluator(project, ((PyDebugValue)((XValueNodeImpl)node).getValueContainer()).getFrameAccessor());
+  }
+
+  public PyDebugValue getValueContainer() {
+    return (PyDebugValue)((XValueNodeImpl)myBaseNode).getValueContainer();
+  }
+
+  public PyDebuggerEvaluator getEvaluator() {
+    return myEvaluator;
+  }
+
+  public void startFillTable() {
+    if (myDtypeKind == null) {
+      fillType();
+      return;
+    }
+
+    if (myShape == null) {
+      fillShape();
+      return;
+    }
+
+    List<Pair<Integer, Integer>> defaultSlice = getDefaultSlice();
+    startFillTable(new Numpy2DArraySlice(getNodeName(), defaultSlice, this, getShape(), getDtype()));
+  }
+
+  private List<Pair<Integer, Integer>> getDefaultSlice() {
+    return getSlice(COLUMNS_IN_DEFAULT_SLICE, ROWS_IN_DEFAULT_SLICE);
+  }
+
+  private List<Pair<Integer, Integer>> getSlice(int columns, int rows) {
+    List<Pair<Integer, Integer>> slices = new ArrayList<Pair<Integer, Integer>>();
+    for (int i = 0; i < myShape.length; i++) {
+      Pair<Integer, Integer> slice = new Pair<Integer, Integer>(0, 0);
+      if (i == myShape.length - 1) {
+        slice = new Pair<Integer, Integer>(0, Math.min(myShape[i], columns));
+      }
+      else if (i == myShape.length - 2) {
+        slice = new Pair<Integer, Integer>(0, Math.min(myShape[i], rows));
+      }
+      slices.add(slice);
+    }
+    return slices;
+  }
+
+  private void fillType() {
+    XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+      @Override
+      public void evaluated(@NotNull XValue result) {
+        setDtype(((PyDebugValue)result).getValue());
+        startFillTable();
+      }
+
+      @Override
+      public void errorOccurred(@NotNull String errorMessage) {
+        showError(errorMessage);
+      }
+    };
+    String evalTypeCommand = getNodeName() + ".dtype.kind";
+    getEvaluator().evaluate(evalTypeCommand, callback, null);
+  }
+
+  private void fillShape() {
+    XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+      @Override
+      public void evaluated(@NotNull XValue result) {
+        try {
+          setShape(parseShape(((PyDebugValue)result).getValue()));
+          startFillTable();
+        }
+        catch (InvalidAttributeValueException e) {
+          errorOccurred(e.getMessage());
+        }
+      }
+
+      @Override
+      public void errorOccurred(@NotNull String errorMessage) {
+        showError(errorMessage);
+      }
+    };
+    String evalShapeCommand = getNodeName() + ".shape";
+    getEvaluator().evaluate(evalShapeCommand, callback, null);
+  }
+
+  private int[] parseShape(String shape) throws InvalidAttributeValueException {
+    String[] dimensions = shape.substring(1, shape.length() - 1).trim().split(",");
+    if (dimensions.length > 1) {
+      int[] result = new int[dimensions.length];
+      for (int i = 0; i < dimensions.length; i++) {
+        result[i] = Integer.parseInt(dimensions[i].trim());
+      }
+      return result;
+    }
+    else if (dimensions.length == 1) {
+      int[] result = new int[2];
+      result[0] = 1;
+      result[1] = Integer.parseInt(dimensions[0].trim());
+      return result;
+    }
+    else {
+      throw new InvalidAttributeValueException("Invalid shape string for " + getNodeName() + ".");
+    }
+  }
+
+  @Override
+  public boolean isNumeric() {
+    if (myDtypeKind != null) {
+      return "biufc".contains(myDtypeKind.substring(0, 1));
+    }
+    return false;
+  }
+
+  private void startFillTable(final Numpy2DArraySlice arraySlice) {
+    if (!arraySlice.dataFilled()) {
+      arraySlice.startFillData(new Runnable() {
+        @Override
+        public void run() {
+          Object[][] data = arraySlice.getData();
+
+          if (myLastPresentation == null || !arraySlice.getPresentation().equals(myLastPresentation.getPresentation())) {
+            myLastPresentation = arraySlice;
+          }
+
+          DefaultTableModel model = new DefaultTableModel(data, range(0, data[0].length - 1));
+          myTable.setModel(model);
+          myTable.setDefaultEditor(myTable.getColumnClass(0), getArrayTableCellEditor());
+
+
+          //enableColor(data);
+          myComponent.getSliceTextField().setText(myLastPresentation.getPresentation());
+          myComponent.getFormatTextField().setText(getFormat());
+        }
+      });
+    }
+  }
+
+  private TableCellEditor getArrayTableCellEditor() {
+    return new ArrayTableCellEditor(myProject) {
+
+      private String getCellSlice() {
+        String expression = myLastPresentation.getPresentation();
+        if (myTable.getRowCount() == 1) {
+          expression += "[" + myTable.getSelectedColumn() + "]";
+        }
+        else {
+          expression += "[" + myTable.getSelectedRow() + "][" + myTable.getSelectedColumn() + "]";
+        }
+        return expression;
+      }
+
+      private String changeValExpression() {
+        return getCellSlice() + " = " + myEditor.getEditor().getDocument().getText();
+      }
+
+      @Override
+      public void doOKAction() {
+
+        if (myEditor.getEditor() == null) {
+          return;
+        }
+
+        myEvaluator.evaluate(changeValExpression(), new XDebuggerEvaluator.XEvaluationCallback() {
+          @Override
+          public void evaluated(@NotNull XValue result) {
+            AppUIUtil.invokeOnEdt(new Runnable() {
+              @Override
+              public void run() {
+                XDebuggerTree tree = ((XValueNodeImpl)myBaseNode).getTree();
+                final XDebuggerTreeState treeState = XDebuggerTreeState.saveState(tree);
+                tree.rebuildAndRestore(treeState);
+              }
+            });
+
+            XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
+              @Override
+              public void evaluated(@NotNull XValue value) {
+
+                //todo: compute presentation and work with
+                String text = ((PyDebugValue)value).getValue();
+                final String corrected;
+                if (!isNumeric()) {
+                  if (!text.startsWith("\\\'") && !text.startsWith("\\\"")) {
+                    corrected = "\'" + text + "\'";
+                  }
+                  else {
+                    corrected = text;
+                  }
+                }
+                else {
+                  corrected = text;
+                }
+
+                new WriteCommandAction(null) {
+                  protected void run(@NotNull Result result) throws Throwable {
+                    if (myEditor.getEditor() != null) {
+                      myEditor.getEditor().getDocument().setText(corrected);
+                    }
+                  }
+                }.execute();
+                lastValue = corrected;
+              }
+
+              @Override
+              public void errorOccurred(@NotNull String errorMessage) {
+              }
+            };
+
+            myEvaluator.evaluate(getCellSlice(), callback, null);
+          }
+
+          @Override
+          public void errorOccurred(@NotNull String errorMessage) {
+            myComponent.setErrorText(errorMessage);
+          }
+        }, null);
+        super.doOKAction();
+      }
+    };
+  }
+
+  private static String[] range(int min, int max) {
+    String[] array = new String[max - min + 1];
+    for (int i = min; i <= max; i++) {
+      array[i] = Integer.toString(i);
+    }
+    return array;
+  }
+
+  public void setDtype(String dtype) {
+    this.myDtypeKind = dtype;
+  }
+
+
+  public String getDtype() {
+    return myDtypeKind;
+  }
+
+  public int[] getShape() {
+    return myShape;
+  }
+
+  public void setShape(int[] shape) {
+    this.myShape = shape;
+  }
+
+  private void showError(String message) {
+    myComponent.setErrorText(message);
+  }
+
+  public String getFormat() {
+    if (isNumeric()) {
+      return "\'%.3f\'";
+    }
+    return "\'%s\'";
+  }
+
+  private void enableColor(Object[][] data) {
+    if (isNumeric()) {
+      double min = Double.MAX_VALUE;
+      double max = Double.MIN_VALUE;
+      if (data.length > 0) {
+        try {
+          for (Object[] aData : data) {
+            for (int j = 0; j < data[0].length; j++) {
+              double d = Double.parseDouble(aData[j].toString());
+              min = min > d ? d : min;
+              max = max < d ? d : max;
+            }
+          }
+        }
+        catch (NumberFormatException e) {
+          min = 0;
+          max = 0;
+        }
+      }
+      else {
+        min = 0;
+        max = 0;
+      }
+
+      myTable.setDefaultRenderer(myTable.getColumnClass(0), new ArrayTableCellRenderer(min, max));
+    }
+    else {
+      myComponent.getColored().setSelected(false);
+      myComponent.getColored().setVisible(false);
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/PyViewArrayAction.java b/python/src/com/jetbrains/python/actions/view/array/PyViewArrayAction.java
new file mode 100644 (file)
index 0000000..f093924
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import com.jetbrains.python.debugger.PyDebugValue;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author amarch
+ */
+
+public class PyViewArrayAction extends XDebuggerTreeActionBase {
+
+  @Override
+  protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) {
+    final MyDialog dialog = new MyDialog(e.getProject());
+    dialog.setTitle("View Array");
+    dialog.setValue(node);
+    dialog.show();
+  }
+
+  protected class MyDialog extends DialogWrapper {
+    public JTable myTable;
+    private Project myProject;
+    private ArrayTableForm myComponent;
+
+    private MyDialog(Project project) {
+      super(project, false);
+      setModal(false);
+      setCancelButtonText("Close");
+      setCrossClosesWindow(true);
+
+      myProject = project;
+
+      myComponent = new ArrayTableForm(this);
+      myTable = myComponent.getTable();
+
+      init();
+    }
+
+    public void setValue(XValueNodeImpl node) {
+
+      if (node.getValueContainer() instanceof PyDebugValue) {
+        PyDebugValue debugValue = (PyDebugValue)node.getValueContainer();
+        if ("ndarray".equals(debugValue.getType())) {
+          myComponent.setDefaultStatus();
+          final NumpyArrayValueProvider valueProvider = new NumpyArrayValueProvider(node, myComponent, myProject);
+          try {
+            valueProvider.startFillTable();
+          }
+          catch (Exception e) {
+            myComponent.setErrorText(e);
+          }
+        }
+        else {
+          myComponent.setNotApplicableStatus(node);
+          //this.close(CLOSE_EXIT_CODE);
+        }
+      }
+    }
+
+    public void setError(String text){
+      //todo: think about this usage
+      setErrorText(text);
+    }
+
+    @Override
+    @NotNull
+    protected Action[] createActions() {
+      return new Action[]{getCancelAction()};
+    }
+
+    @Override
+    protected String getDimensionServiceKey() {
+      return "#com.jetbrains.python.actions.view.array.PyViewArrayAction";
+    }
+
+    @Override
+    protected JComponent createCenterPanel() {
+      return myComponent.getMainPanel();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/RowNumberTable.java b/python/src/com/jetbrains/python/actions/view/array/RowNumberTable.java
new file mode 100644 (file)
index 0000000..c83a5c5
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.ui.table.JBTable;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumn;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/*
+*      Use a JTable as a renderer for row numbers of a given main table.
+*  This table must be added to the row header of the scrollpane that
+*  contains the main table.
+*/
+public class RowNumberTable extends JBTable
+  implements ChangeListener, PropertyChangeListener, TableModelListener {
+  private JTable main;
+
+  public RowNumberTable(JTable table) {
+    main = table;
+    main.addPropertyChangeListener(this);
+    main.getModel().addTableModelListener(this);
+
+    setFocusable(false);
+    setAutoCreateColumnsFromModel(false);
+    setSelectionModel(main.getSelectionModel());
+
+
+    TableColumn column = new TableColumn();
+    column.setHeaderValue(" ");
+    addColumn(column);
+    column.setCellRenderer(new RowNumberRenderer());
+
+    getColumnModel().getColumn(0).setPreferredWidth(50);
+    setPreferredScrollableViewportSize(getPreferredSize());
+  }
+
+  @Override
+  public void addNotify() {
+    super.addNotify();
+
+    Component c = getParent();
+
+    //  Keep scrolling of the row table in sync with the main table.
+
+    if (c instanceof JViewport) {
+      JViewport viewport = (JViewport)c;
+      viewport.addChangeListener(this);
+    }
+  }
+
+  /*
+   *  Delegate method to main table
+   */
+  @Override
+  public int getRowCount() {
+    return main.getRowCount();
+  }
+
+  @Override
+  public int getRowHeight(int row) {
+    int rowHeight = main.getRowHeight(row);
+
+    if (rowHeight != super.getRowHeight(row)) {
+      super.setRowHeight(row, rowHeight);
+    }
+
+    return rowHeight;
+  }
+
+  /*
+   *  No model is being used for this table so just use the row number
+   *  as the value of the cell.
+   */
+  @Override
+  public Object getValueAt(int row, int column) {
+    return Integer.toString(row + 1);
+  }
+
+  /*
+   *  Don't edit data in the main TableModel by mistake
+   */
+  @Override
+  public boolean isCellEditable(int row, int column) {
+    return false;
+  }
+
+  /*
+   *  Do nothing since the table ignores the model
+   */
+  @Override
+  public void setValueAt(Object value, int row, int column) {
+  }
+
+  //
+  //  Implement the ChangeListener
+  //
+  public void stateChanged(ChangeEvent e) {
+    //  Keep the scrolling of the row table in sync with main table
+
+    JViewport viewport = (JViewport)e.getSource();
+    JScrollPane scrollPane = (JScrollPane)viewport.getParent();
+    scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
+  }
+
+  //
+  //  Implement the PropertyChangeListener
+  //
+  public void propertyChange(PropertyChangeEvent e) {
+    //  Keep the row table in sync with the main table
+
+    if ("selectionModel".equals(e.getPropertyName())) {
+      setSelectionModel(main.getSelectionModel());
+    }
+
+    if ("rowHeight".equals(e.getPropertyName())) {
+      repaint();
+    }
+
+    if ("model".equals(e.getPropertyName())) {
+      main.getModel().addTableModelListener(this);
+      revalidate();
+    }
+  }
+
+  //
+  //  Implement the TableModelListener
+  //
+  @Override
+  public void tableChanged(TableModelEvent e) {
+    revalidate();
+  }
+
+  /*
+   *  Attempt to mimic the table header renderer
+   */
+  private class RowNumberRenderer extends DefaultTableCellRenderer {
+    public RowNumberRenderer() {
+      setHorizontalAlignment(JLabel.CENTER);
+    }
+
+    public Component getTableCellRendererComponent(
+      JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+      if (table != null) {
+        JTableHeader header = table.getTableHeader();
+
+        if (header != null) {
+          setForeground(header.getForeground());
+          setBackground(header.getBackground());
+          setFont(header.getFont());
+        }
+      }
+
+      if (isSelected) {
+        setFont(getFont().deriveFont(Font.BOLD));
+      }
+
+      setText((value == null) ? "" : value.toString());
+      setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+
+      return this;
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/actions/view/array/XDebuggerTreeTableListener.java b/python/src/com/jetbrains/python/actions/view/array/XDebuggerTreeTableListener.java
new file mode 100644 (file)
index 0000000..d368158
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.actions.view.array;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.util.containers.HashSet;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener;
+import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableModel;
+import java.util.List;
+
+/**
+* @author amarch
+*/
+public class XDebuggerTreeTableListener implements XDebuggerTreeListener {
+
+  XValueNodeImpl baseNode;
+
+  XValueContainerNode innerNdarray;
+
+  XValueContainerNode innerItems;
+
+  ArrayTableComponent myComponent;
+
+  Project myProject;
+
+  JTable myTable;
+
+  int[] shape;
+
+  int depth = 0;
+
+  int loadedRows = 0;
+
+  HashSet<String> unloadedRowNumbers = new HashSet<String>();
+
+  boolean baseChildrenLoaded = false;
+
+  boolean numeric = false;
+
+  boolean dataLoaded = false;
+
+  Object[][] data;
+
+  public XDebuggerTreeTableListener(XValueNodeImpl node, JTable table, ArrayTableComponent component, Project project) {
+    super();
+    baseNode = baseNode;
+    myTable = table;
+    myComponent = component;
+    myProject= project;
+  }
+
+  @Override
+  public void nodeLoaded(@NotNull RestorableStateNode node, String name) {
+    System.out.printf(name + " node loaded\n");
+
+    if (!baseChildrenLoaded &&
+        (shape == null || name.equals("dtype")) &&
+        ((XValueNodeImpl)node.getParent()).getName().equals(baseNode.getName())) {
+      if (name.equals("shape")) {
+        String rawValue = node.getRawValue();
+        String[] shapes = rawValue.substring(1, rawValue.length() - 1).split(",");
+        shape = new int[shapes.length];
+        for (int i = 0; i < shapes.length; i++) {
+          shape[i] = Integer.parseInt(shapes[i].trim());
+        }
+        depth = Math.max(shape.length - 2, 0);
+      }
+
+      if (name.equals("dtype")) {
+        String rawValue = node.getRawValue();
+        if ("biufc".contains(rawValue.substring(0, 1))) {
+          numeric = true;
+        }
+      }
+    }
+  }
+
+  @Override
+  public void childrenLoaded(@NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) {
+    System.out.printf(children + "children loaded\n");
+
+    if (dataLoaded) {
+      return;
+    }
+
+    //todo: not compute children if they yet computed
+
+    if (!baseChildrenLoaded && node.equals(baseNode)) {
+      baseChildrenLoaded = true;
+      innerNdarray = (XValueContainerNode)node;
+      if (shape != null) {
+        if (shape.length >= 2) {
+          data = new Object[shape[shape.length - 2]][shape[shape.length - 1]];
+        }
+        else {
+          data = new Object[1][shape[0]];
+        }
+      }
+    }
+
+    //go deeper
+    if (depth > 0) {
+      if (innerNdarray != null && innerNdarray.equals(node)) {
+        innerNdarray = null;
+        innerItems = findItems(node);
+        innerItems.startComputingChildren();
+      }
+
+      if (innerItems != null && innerItems.equals(node)) {
+        innerNdarray = (XValueContainerNode)node.getChildAt(1);
+        innerItems = null;
+        innerNdarray.startComputingChildren();
+        depth -= 1;
+      }
+
+      return;
+    }
+
+    //find ndarray slice to display
+    if (depth == 0) {
+      innerItems = findItems(node);
+      innerItems.startComputingChildren();
+      depth -= 1;
+      return;
+    }
+
+    if (depth == -1 && node.equals(innerItems)) {
+      if (shape != null && shape.length == 1) {
+        for (int i = 0; i < node.getChildCount() - 1; i++) {
+          data[0][i] = ((XValueNodeImpl)node.getChildAt(i + 1)).getRawValue();
+        }
+        loadData();
+        loadedRows = 1;
+      }
+      else {
+        for (int i = 0; i < node.getChildCount() - 1; i++) {
+          ((XValueNodeImpl)node.getChildAt(i + 1)).startComputingChildren();
+          unloadedRowNumbers.add(((XValueNodeImpl)node.getChildAt(i + 1)).getName());
+        }
+        depth -= 1;
+      }
+      return;
+    }
+
+
+    if (depth == -2) {
+      String name = ((XValueNodeImpl)node).getName();
+      // ndarrray children not computed yet
+      if (unloadedRowNumbers.contains(name)) {
+        unloadedRowNumbers.remove(name);
+        findItems(node).startComputingChildren();
+        return;
+      }
+
+      if (name.startsWith("[")) {
+        int row = Integer.parseInt((((XValueNodeImpl)node.getParent()).getName()));
+        if (data[row][0] == null) {
+          for (int i = 0; i < node.getChildCount() - 1; i++) {
+            data[row][i] = ((XValueNodeImpl)node.getChildAt(i + 1)).getRawValue();
+          }
+          loadedRows += 1;
+        }
+      }
+    }
+
+    if (loadedRows == shape[shape.length - 2]) {
+      loadData();
+    }
+  }
+
+  XValueContainerNode findItems(@NotNull XDebuggerTreeNode node) {
+    for (int i = 0; i < node.getChildCount(); i++) {
+      if (node.getChildAt(i).toString().startsWith("[")) {
+        return (XValueContainerNode)node.getChildAt(i);
+      }
+    }
+    return null;
+  }
+
+  private String[] range(int min, int max) {
+    String[] array = new String[max - min + 1];
+    for (int i = min; i <= max; i++) {
+      array[i] = Integer.toString(i);
+    }
+    return array;
+  }
+
+  private void loadData() {
+
+    DefaultTableModel model = new DefaultTableModel(data, range(0, data[0].length - 1));
+
+    myTable.setModel(model);
+    myTable.setDefaultEditor(myTable.getColumnClass(0), new ArrayTableCellEditor(myProject));
+
+    if (numeric) {
+      double min = Double.MAX_VALUE;
+      double max = Double.MIN_VALUE;
+      if (data.length > 0) {
+        try {
+          for (int i = 0; i < data.length; i++) {
+            for (int j = 0; j < data[0].length; j++) {
+              double d = Double.parseDouble(data[i][j].toString());
+              min = min > d ? d : min;
+              max = max < d ? d : max;
+            }
+          }
+        }
+        catch (NumberFormatException e) {
+          min = 0;
+          max = 0;
+        }
+      }
+      else {
+        min = 0;
+        max = 0;
+      }
+
+      myTable.setDefaultRenderer(myTable.getColumnClass(0), new ArrayTableCellRenderer(min, max));
+    }
+    else {
+      myComponent.getColored().setSelected(false);
+      myComponent.getColored().setVisible(false);
+    }
+
+    myComponent.getTextField().setText(getDefaultSliceRepresentation());
+    dataLoaded = true;
+    innerItems = null;
+    innerNdarray = null;
+  }
+
+  public String getDefaultSliceRepresentation() {
+    String representation = "";
+
+    if (baseNode != null) {
+      representation += baseNode.getName();
+      if (shape != null && shape.length > 0) {
+        for (int i = 0; i < shape.length - 2; i++) {
+          representation += "[0]";
+        }
+        if (shape.length == 1) {
+          representation += "[0:" + shape[0] + "]";
+        }
+        else {
+          representation += "[0:" + shape[shape.length - 2] + "][0:" + shape[shape.length - 1] + "]";
+        }
+      }
+    }
+
+    return representation;
+  }
+
+}