Repository: spyder-ide/spyder

Issue:
Code Analysis customize history not working and actions unavailable when pane is undocked
<!--- **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! --->

### Issue Report Checklist

* [ ] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [ ] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
1. Changing the number maximum entries in the history option in the Code Analysis options menu doesn't do anything. Number of maximum files shown when dropping down the arrow in the directory path stays the same.

![Screen Shot 2020-05-14 at 1 09 55 AM](https://user-images.githubusercontent.com/18587879/81899221-f01c6780-957f-11ea-932c-cfd263b46dbe.png)
![Screen Shot 2020-05-14 at 1 11 59 AM](https://user-images.githubusercontent.com/18587879/81899229-f27ec180-957f-11ea-971c-76daf0392462.png)
(There are more than 10 entries)

2. Actions from options menu are unavailable when Code Analysis pane is undocked.

![Screen Shot 2020-05-14 at 12 54 37 AM](https://user-images.githubusercontent.com/18587879/81898039-7daa8800-957d-11ea-84e2-4615024496d4.png)


### What steps reproduce the problem?

1. Open Code Analysis
2. Go to options menu
3. Select history
4. Change number of entries

### What is the expected output? What do you see instead?



### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

PASTE TRACEBACK HERE

```

## 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 version:  4.2.0.dev0 and 4.1.3
* Python version: 
* Qt version: 
* PyQt version: 
* Operating System name/version: Mac OS

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
PASTE DEPENDENCIES HERE
```



Discussion:
These appear to be two fully separate issues, but I can reproduce both of them on WIndows and Spyder as far back as 4.0.0 final, and I can also confirm the history length persists even after a full restart, and running additional code analyses in the pane.

Resolving patch:
diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py
--- a/spyder/plugins/pylint/plugin.py
+++ b/spyder/plugins/pylint/plugin.py
@@ -44,6 +44,7 @@ class Pylint(SpyderPluginWidget):
     CONF_SECTION = 'pylint'
     CONFIGWIDGET_CLASS = PylintConfigPage
     CONF_FILE = False
+    DISABLE_ACTIONS_WHEN_HIDDEN = False
 
     def __init__(self, parent=None):
         SpyderPluginWidget.__init__(self, parent)
diff --git a/spyder/plugins/pylint/widgets/pylintgui.py b/spyder/plugins/pylint/widgets/pylintgui.py
--- a/spyder/plugins/pylint/widgets/pylintgui.py
+++ b/spyder/plugins/pylint/widgets/pylintgui.py
@@ -165,6 +165,7 @@ def __init__(self, parent, max_entries=100, options_button=None,
 
         self.setWindowTitle("Pylint")
 
+        self.parent = parent
         self.output = None
         self.error_output = None
         self.filename = None
@@ -455,7 +456,10 @@ def finished(self, exit_code, exit_status):
             else:
                 pylint_item = (module, items["line_nb"], items["message"],
                                items["msg_id"], items["message_name"])
-                results[line[0] + ':'].append(pylint_item)
+                act_result = results[line[0] + ':']
+                if (self.parent is not None and
+                    len(act_result) < self.parent.get_option('max_entries')):
+                    results[line[0] + ':'].append(pylint_item)
 
         # Rate
         rate = None


Test patch:
diff --git a/spyder/plugins/pylint/tests/test_pylint.py b/spyder/plugins/pylint/tests/test_pylint.py
--- a/spyder/plugins/pylint/tests/test_pylint.py
+++ b/spyder/plugins/pylint/tests/test_pylint.py
@@ -17,8 +17,14 @@
 
 # Third party imports
 import pytest
+from qtpy.QtCore import Signal, QObject
+try:
+    from unittest.mock import Mock, MagicMock
+except ImportError:
+    from mock import Mock, MagicMock  # Python 2
 
 # Local imports
+from spyder.plugins.pylint.plugin import Pylint
 from spyder.plugins.pylint.widgets.pylintgui import PylintWidget
 from spyder.plugins.pylint.utils import get_pylintrc_path
 from spyder.py3compat import PY2
@@ -40,7 +46,7 @@
 DIR_LIST = [SCRIPT_DIR, WORKING_DIR, PROJECT_DIR, HOME_DIR]
 DIR_LIST_ALL = [NO_DIR] + DIR_LIST + [ALL_DIR]
 
-PYLINT_TEST_SCRIPT = "\n".join(
+PYLINT_TEST_SCRIPT = "import math\nimport os\nimport sys\n" + "\n".join(
     [dir_name + " = " + str(idx) for idx, dir_name in enumerate(DIR_LIST_ALL)])
 PYLINT_TEST_SCRIPT = "\"\"\"Docstring.\"\"\"\n" + PYLINT_TEST_SCRIPT + "\n"
 
@@ -54,6 +60,16 @@
 """
 
 
+class MainWindowMock(QObject):
+    sig_editor_focus_changed = Signal(str)
+
+    def __init__(self):
+        super(MainWindowMock, self).__init__(None)
+        self.editor = Mock()
+        self.editor.sig_editor_focus_changed = self.sig_editor_focus_changed
+        self.projects = MagicMock()
+
+
 @pytest.fixture
 def pylintrc_search_paths(tmp_path_factory):
     """Construct temporary .pylintrc search paths."""
@@ -120,9 +136,13 @@ def test_get_pylintrc_path(pylintrc_files, mocker):
     assert actual_path == expected_path
 
 
-def test_pylint_widget_noproject(pylint_test_script, qtbot):
+def test_pylint_widget_noproject(pylint_test_script, mocker, qtbot):
     """Test that pylint works without errors with no project open."""
-    pylint_widget = PylintWidget(parent=None)
+    main_window = MainWindowMock()
+    main_window.projects.get_active_project_path = mocker.MagicMock(
+        return_value=None)
+    pylint_sw = Pylint(parent=main_window)
+    pylint_widget = PylintWidget(parent=pylint_sw)
     pylint_widget.analyze(filename=pylint_test_script)
     qtbot.waitUntil(
         lambda: pylint_widget.get_data(pylint_test_script)[1] is not None,
@@ -143,16 +163,14 @@ def test_pylint_widget_pylintrc(
                  return_value=search_paths[HOME_DIR])
     mocker.patch("spyder.plugins.pylint.widgets.pylintgui.getcwd_or_home",
                  return_value=search_paths[WORKING_DIR])
-    mock_parent = mocker.Mock()
-    mock_parent.main.projects.get_active_project_path = mocker.MagicMock(
-        return_value=search_paths[PROJECT_DIR])
-    mocker.patch(
-        "spyder.plugins.pylint.widgets.pylintgui.PylintWidget.parentWidget",
-        return_value=mock_parent)
     mocker.patch("spyder.plugins.pylint.widgets.pylintgui.osp.expanduser",
                  return_value=search_paths[HOME_DIR])
+    main_window = MainWindowMock()
+    main_window.projects.get_active_project_path = mocker.MagicMock(
+        return_value=search_paths[PROJECT_DIR])
+    pylint_sw = Pylint(parent=main_window)
 
-    pylint_widget = PylintWidget(parent=None)
+    pylint_widget = PylintWidget(parent=pylint_sw)
     pylint_widget.analyze(filename=pylint_test_script)
     qtbot.waitUntil(
         lambda: pylint_widget.get_data(pylint_test_script)[1] is not None,
@@ -167,5 +185,34 @@ def test_pylint_widget_pylintrc(
                 for bad_name in bad_names])
 
 
+def test_pylint_max_history_conf(pylint_test_script, mocker, qtbot):
+    """Regression test for checking max_entries configuration.
+
+    For further information see spyder-ide/spyder#12874
+    """
+    # Create the pylint widget for code analysis
+    main_window = MainWindowMock()
+    main_window.projects.get_active_project_path = mocker.MagicMock(
+        return_value=None)
+    pylint_sw = Pylint(parent=main_window)
+    pylint_widget = PylintWidget(parent=pylint_sw)
+
+    # Change the max_entry to 2
+    pylint_widget.parent.set_option('max_entries', 2)
+    assert pylint_widget.parent.get_option('max_entries') == 2
+
+    # Analyze the test script with 1 as max_entry
+    pylint_widget.analyze(filename=pylint_test_script)
+    qtbot.waitUntil(
+        lambda: pylint_widget.get_data(pylint_test_script)[1] is not None,
+        timeout=5000)
+    pylint_data = pylint_widget.get_data(filename=pylint_test_script)
+    results = pylint_data[1][-1]
+
+    max_entries = pylint_widget.parent.get_option('max_entries')
+    for key in results:
+        assert len(results[key]) <= max_entries
+
+
 if __name__ == "__main__":
     pytest.main([osp.basename(__file__), '-vv', '-rw'])
diff --git a/spyder/plugins/pylint/tests/test_pylint_config_dialog.py b/spyder/plugins/pylint/tests/test_pylint_config_dialog.py
--- a/spyder/plugins/pylint/tests/test_pylint_config_dialog.py
+++ b/spyder/plugins/pylint/tests/test_pylint_config_dialog.py
@@ -7,9 +7,9 @@
 """Tests for plugin config dialog."""
 
 try:
-    from unittest.mock import Mock
+    from unittest.mock import Mock, MagicMock
 except ImportError:
-    from mock import Mock  # Python 2
+    from mock import Mock, MagicMock  # Python 2
 
 # Third party imports
 from qtpy.QtCore import Signal, QObject
@@ -27,6 +27,7 @@ def __init__(self):
         super(MainWindowMock, self).__init__(None)
         self.editor = Mock()
         self.editor.sig_editor_focus_changed = self.sig_editor_focus_changed
+        self.projects = MagicMock()
 
 
 @pytest.mark.parametrize(