Repository: spyder-ide/spyder

Issue:
Make Variable Explorer column headers movable
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

The variable explorer currently has the columns as Name -> Type -> Size -> Value, which is kind of annoying as the main thing I usually care about is Value which is way off at the end. There is no way to change this order but a simple one-liner in Qt could make the column headers draggable. In `collectionseditor.py`, in class `BaseTableView`'s `def setup_table(self)`, simply add the one line

```
    def setup_table(self):
        """Setup table"""
        self.horizontalHeader().setStretchLastSection(True)
        self.horizontalHeader().setSectionsMovable(True) # <--- NEW LINE HERE
        self.adjust_columns()
        # Sorting columns
        self.setSortingEnabled(True)
        self.sortByColumn(0, Qt.AscendingOrder)
```

and you're done. Note this is for BaseTableView; you could also do this instead in RemoteCollectionsEditorTableView and CollectionsEditorTableView (not sure which subclass is the best to do this in, or what other things subclass BaseTableView).

FWIW, I'd much prefer it if the default were changed to Name -> Value -> Type -> Size, but that seems more involved and I'd be happy enough with this.

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

Spyder: 5.0.5 | Python 3.9.5 64-bit | Qt 5.9.7 | PyQt5 5.9.2 | Darwin 17.7.0 


Discussion:
Hi @battaglia01,

This is something we need to discuss with the team, so @spyder-ide/core-developers what do you think about this? I'm afraid that if the value of the variable is too long, then the other columns will not be that visible. And I'm not sure if we want to enable those columns to be movable. 
I think there is a restriction in the size that the columns can have at the beginning except for the last one which takes all the remaining available space (unless the user resizes manually the columns). However I think too that changing the default order of the columns could be quite involved (and the current order is probably like that to have more space assigned to the value column by default). Regarding being able to move the columns, I don't have a strong opinion, but maybe it could be handy for some people and also I think the object explorer allows you to move the columns already.
What if we allow to hide certain columns instead of making them draggable?
What exactly is wrong with making the columns movable? I patched it in on
my Spyder and haven't seen any problem.

On Fri, Sep 17, 2021, 10:52 AM Carlos Cordoba ***@***.***>
wrote:

> What if we allow to hide certain columns instead of making them draggable?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/spyder-ide/spyder/issues/16423#issuecomment-921860915>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAHZFMR7UU4P3LJMS6YXFJ3UCNI2NANCNFSM5EBBHOXA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

I think there are several hard-coded things that assume the current column ordering. 
I suggested `setSectionsMovable` because it seems to still internally handle column IDs correctly. e.g. if you drag the "Value" column to position 2, Qt still knows that it was originally the fourth column for when you add new variables into the explorer. But if it's easier to hide columns instead then alright, I'd be happy with that... 
Ok, if that's the case, then please submit a pull request about it.
Alright, although I will wait a little to hear if there are any additional comments from @steff456 and @dalthviz.

BTW: is there some way to get some of these top-level Spyder widgets, from within Spyder, at a running kernel? Something like `get_ipython()` but for the QApplication? I've tried `QtWidgets.QApplication.instance()` but sometimes that returns `NoneType`; also `spyder_kernels.customize.spydercustomize.SpyderQApplication._instance_list` often is empty...
> BTW: is there some way to get some of these top-level Spyder widgets, from within Spyder, at a running kernel?

I'm not sure that I understand your question. Do you mean for debugging purposes?
Right.

On Fri, Sep 17, 2021 at 8:38 PM Ryan Clary ***@***.***> wrote:

> BTW: is there some way to get some of these top-level Spyder widgets, from
> within Spyder, at a running kernel?
>
> I'm not sure that I understand your question. Do you mean for debugging
> purposes?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/spyder-ide/spyder/issues/16423#issuecomment-922147740>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAHZFMQCCPCCL26U5ETYMKLUCPNP7ANCNFSM5EBBHOXA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

The only way that I know of is to access them from inside a namespace in which they reside.
Do you have a specific example of what you want to look at?
My general procedure is this:
1. Insert `import pdb; pdb.set_trace()` somewhere in the source code that I want to probe
2. launch spyder from commandline using bootstrap and some debug level
  ```bash
  (dev) $ python bootstrap.py -- --debug-info=miminal
  ```
3. My terminal will now show a `(pdb)` prompt that I can use for debugging.

Caveat: depending on where you place your `set_trace` there may be a Qt event loop running, in which case it will complain at you in the stdout of your terminal, rendering debugging impossible. To avoid this, you can place the following code before your `set_trace`.
```python
from qtpy.QtCore import pyqtRemoveInputHook; pyqtRemoveInputHook()
``` 

Does anyone have a better way of doing it? If so, I'd like to here how others do it.
No specific example, just sometimes I like to try various things, like in this case `setSectionsMovable`, without having to restart Spyder. It would be nice if there were some way to introspect Spyder itself from within the running kernel - i.e. something like getting the current application from `QtWidgets.QApplication.instance()`, and from there I should theoretically be able to look at `QtWidgets.QApplication.instance().topLevelWidgets()` or whatever, eventually getting to the Variable Explorer widget... but since the kernel is in a different process, it doesn't work.
I see. In that case, I would recommend running the widget by itself so you don't have to run the entire Spyder application. One example of how to do this is in the PYTHONPATH Manager widget: spyder/widgets/pathmanager.py. There you will find a `test` function and `if __name__ == '__main__'` protection block at the end of the module, so that you can run just this file as a script to play around with the widget without having to launch Spyder. There may be other examples throughout the source code, but this is one that I remember seeing recently. If the module that contains the widget you're interested in doesn't already have something like that, then try writing it and pushing it up so it can be used by others in the future!

Hope that helps.
> Alright, although I will wait a little to hear if there are any additional comments from @steff456 and @dalthviz.

I think is ok :+1: I also tried a little bit setting the movable columns in my local and seems like everything is working (as @battaglia01 says even if the columns move seems like their id remains the same)
@battaglia01, if you want to tinker with Spyder internals at runtime, you can do it with the Internal Console, which can be opened by going to the menu `View > Panes`.

It exposes an object called `spy`, which holds a reference to the main window:

![imagen](https://user-images.githubusercontent.com/365293/134035230-91eee6e9-1045-487f-a66b-01fefa751048.png)

(just ignore the warning shown there).

Through it you can access any plugin and then alter its attributes or run its methods:

![imagen](https://user-images.githubusercontent.com/365293/134036084-900eca2d-7db8-4762-b2df-34369b080f84.png)

![imagen](https://user-images.githubusercontent.com/365293/134035793-33d6b203-6910-4543-a4e5-9df82d88cbbd.png)

> Alright, although I will wait a little to hear if there are any additional comments from @steff456 and @dalthviz.

It sounds ok! Please submit the PR :)
Thank you all! I will submit when I get the chance.
OK, implemented in #17022.

Resolving patch:
diff --git a/spyder/widgets/collectionseditor.py b/spyder/widgets/collectionseditor.py
--- a/spyder/widgets/collectionseditor.py
+++ b/spyder/widgets/collectionseditor.py
@@ -614,6 +614,7 @@ def __init__(self, parent):
     def setup_table(self):
         """Setup table"""
         self.horizontalHeader().setStretchLastSection(True)
+        self.horizontalHeader().setSectionsMovable(True)
         self.adjust_columns()
         # Sorting columns
         self.setSortingEnabled(True)


Test patch:
diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py
--- a/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py
+++ b/spyder/plugins/variableexplorer/widgets/tests/test_namespacebrowser.py
@@ -11,7 +11,6 @@
 
 # Standard library imports
 import string
-import sys
 from unittest.mock import Mock
 
 # Third party imports
@@ -22,20 +21,30 @@
 # Local imports
 from spyder.plugins.variableexplorer.widgets.namespacebrowser import (
     NamespaceBrowser, NamespacesBrowserFinder, VALID_VARIABLE_CHARS)
-from spyder.py3compat import PY2
 from spyder.widgets.collectionseditor import ROWS_TO_LOAD
 from spyder.widgets.tests.test_collectioneditor import data, data_table
 
 
-@flaky(max_runs=5)
-@pytest.mark.skipif(
-    sys.platform.startswith('linux') and PY2,
-    reason="Sometimes fails on Linux and Python 2"
-)
-def test_automatic_column_width(qtbot):
+# =============================================================================
+# ---- Fixtures
+# =============================================================================
+@pytest.fixture
+def namespacebrowser(qtbot):
     browser = NamespaceBrowser(None)
     browser.set_shellwidget(Mock())
     browser.setup()
+    browser.resize(640, 480)
+    browser.show()
+    qtbot.addWidget(browser)
+    return browser
+
+
+# =============================================================================
+# ---- Tests
+# =============================================================================
+@flaky(max_runs=5)
+def test_automatic_column_width(namespacebrowser):
+    browser = namespacebrowser
 
     col_width = [browser.editor.columnWidth(i) for i in range(4)]
     browser.set_data({'a_variable':
@@ -53,15 +62,12 @@ def test_automatic_column_width(qtbot):
     assert browser.editor.columnWidth(0) == 100  # Automatic col width is off
 
 
-def test_sort_by_column(qtbot):
+def test_sort_by_column(namespacebrowser, qtbot):
     """
     Test that clicking the header view the namespacebrowser is sorted.
     Regression test for spyder-ide/spyder#9835 .
     """
-    browser = NamespaceBrowser(None)
-    qtbot.addWidget(browser)
-    browser.set_shellwidget(Mock())
-    browser.setup()
+    browser = namespacebrowser
 
     browser.set_data(
         {'a_variable':
@@ -98,17 +104,14 @@ def test_sort_by_column(qtbot):
                                        ['2', '1']]
 
 
-def test_keys_sorted_and_sort_with_large_rows(qtbot):
+def test_keys_sorted_and_sort_with_large_rows(namespacebrowser, qtbot):
     """
     Test that keys are sorted and sorting works as expected when
     there's a large number of rows.
 
     This is a regression test for issue spyder-ide/spyder#10702
     """
-    browser = NamespaceBrowser(None)
-    qtbot.addWidget(browser)
-    browser.set_shellwidget(Mock())
-    browser.setup()
+    browser = namespacebrowser
 
     # Create variables.
     variables = {}
@@ -148,14 +151,12 @@ def test_keys_sorted_and_sort_with_large_rows(qtbot):
     assert data(model, 0, 0) == 'i'
 
 
-def test_filtering_with_large_rows(qtbot):
+def test_filtering_with_large_rows(namespacebrowser, qtbot):
     """
     Test that filtering works when there's a large number of rows.
     """
-    browser = NamespaceBrowser(None)
-    qtbot.addWidget(browser)
-    browser.set_shellwidget(Mock())
-    browser.setup()
+    browser = namespacebrowser
+
     text_finder = NamespacesBrowserFinder(
         browser.editor,
         callback=browser .editor.set_regex,
diff --git a/spyder/widgets/tests/test_collectioneditor.py b/spyder/widgets/tests/test_collectioneditor.py
--- a/spyder/widgets/tests/test_collectioneditor.py
+++ b/spyder/widgets/tests/test_collectioneditor.py
@@ -228,8 +228,8 @@ def test_filter_rows(qtbot):
              'view': 'Column names: 0'}}
     )
     editor = RemoteCollectionsEditorTableView(None, data)
-    editor.finder = NamespacesBrowserFinder(editor,
-                                            editor.set_regex)
+    editor.finder = NamespacesBrowserFinder(
+        editor, editor.set_regex)
     qtbot.addWidget(editor)
 
     # Initially two rows
@@ -252,7 +252,7 @@ def test_filter_rows(qtbot):
     assert editor.model.rowCount() == 0
 
 
-def test_create_dataframeeditor_with_correct_format(qtbot, monkeypatch):
+def test_create_dataframeeditor_with_correct_format(qtbot):
     df = pandas.DataFrame(['foo', 'bar'])
     editor = CollectionsEditorTableView(None, {'df': df})
     qtbot.addWidget(editor)
@@ -262,6 +262,7 @@ def test_create_dataframeeditor_with_correct_format(qtbot, monkeypatch):
     qtbot.addWidget(dataframe_editor)
     dataframe_editor.dataModel._format == '%10d'
 
+
 def test_collectionsmodel_with_two_ints():
     coll = {'x': 1, 'y': 2}
     cm = CollectionsModel(MockParent(), coll)
@@ -285,6 +286,7 @@ def test_collectionsmodel_with_two_ints():
     assert data(cm, row_with_y, 2) == 1
     assert data(cm, row_with_y, 3) == '2'
 
+
 def test_collectionsmodel_with_index():
     # Regression test for spyder-ide/spyder#3380,
     # modified for spyder-ide/spyder#3758.
@@ -300,8 +302,8 @@ def test_collectionsmodel_with_index():
         assert data(cm, 0, 3) == rng.summary()
 
 
-def test_shows_dataframeeditor_when_editing_index(qtbot, monkeypatch):
-    for rng_name, rng in generate_pandas_indexes().items():
+def test_shows_dataframeeditor_when_editing_index(monkeypatch):
+    for __, rng in generate_pandas_indexes().items():
         MockDataFrameEditor = Mock()
         mockDataFrameEditor_instance = MockDataFrameEditor()
         attr_to_patch_dfedit = ('spyder.plugins.variableexplorer.widgets.' +
@@ -594,6 +596,7 @@ def test_view_module_in_coledit():
     editor.setup(os, "module_test", readonly=False)
     assert editor.widget.editor.readonly
 
+
 def test_notimplementederror_multiindex():
     """
     Test that the NotImplementedError when scrolling a MultiIndex is handled.
@@ -807,6 +810,7 @@ def test_collectionseditor_when_clicking_on_header_and_large_rows(qtbot):
     li = [1] * 10000
     editor = CollectionsEditor()
     editor.setup(li)
+    editor.show()
 
     # Perform the sorting. It should be done quite quickly because
     # there's a very small number of rows in display.
@@ -818,6 +822,8 @@ def test_collectionseditor_when_clicking_on_header_and_large_rows(qtbot):
     # Assert data was sorted correctly.
     assert data(view.model, 0, 0) == 9999
 
+    editor.accept()
+
 
 def test_dicts_with_mixed_types_as_key(qtbot):
     """