Repository: ipython/ipython

Issue:
Provide an easy way to override the Qt widget used by qtconsole
We at Spyder are developing a better integration with qtconsole for our next release. In the process we found that we need a widget with more functionality than QTextEdit (to add to it our find widget for example). But it seems right now there is no easy way to provide a custom widget instead of it, which has forced us to copy/paste the _create_control method of ConsoleWidget, as can be seen here:

http://code.google.com/p/spyderlib/source/browse/spyderlib/widgets/ipython.py#106

Pierre suggested adding an additional attribute to the class, something like:

``` python
self.control_factory 
```

which could be used like this:

``` python
def _create_control(self):
    """ Creates and connects the underlying text widget.
    """
    # Create the underlying control.
    if self.control_factory:
        control = self.control_factory
    elif self.kind == 'plain':
        control = QtGui.QPlainTextEdit()
    elif self.kind == 'rich':
        control = QtGui.QTextEdit()
        control.setAcceptRichText(False)
```

I'm willing to do the pull request job but I wanted to ask you first what you consider is the best way to proceed. Or in case we are using your API in the wrong way, what would be the right one to achieve what we want?



Discussion:
I think it would help if you can explain what widget you are trying to use and what it offers.  Of course we'd be happy to make it possible for spyder to better integrate ipython, but it's possible that a different widget choice may even benefit ipython itself.  So why don't you explain the bigger context a little so we understand the situation better.

Thanks Fernando for the quick response. Our widget is not different from 
QtTextEdit, but it inherits from it and adds more functionality, as I 
said. In the link I mentioned you can find it under IPythonShellWidget 
and right now it has:
1. A find widget attached to qtconsole, so you can use Ctrl+F to start a 
   text search inside the widget
2. Traceback link support (when xmode=Plain for now) so the user can 
   click on any file path that appears on a traceback and have them open in 
   Spyder's Editor.
3.  Integration with our Object Inspector, so you can inspect docstrings 
   of the object before the cursor using Ctrl+I or writing '?' after it. 
   These actions will automatically obtain the docstring, pass it through 
   Sphinx and show the result on a QWebKit widget.

Perhaps the first addition will be beneficial for you (if you want to 
add our find widget to your tree) but I think the other ones are too 
tied to Spyder.

El 11/06/12 19:08, Fernando Perez escribió:

> I think it would help if you can explain what widget you are trying to use and what it offers.  Of course we'd be happy to make it possible for spyder to better integrate ipython, but it's possible that a different widget choice may even benefit ipython itself.  So why don't you explain the bigger context a little so we understand the situation better.
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/ipython/ipython/issues/1920#issuecomment-6259791

I forgot to mention we would like to see this in 0.13 (if possible of 
course). I raised the issue because I saw your email this morning 
calling for a new release.

El 11/06/12 19:30, Carlos Córdoba escribió:

> Thanks Fernando for the quick response. Our widget is not different 
> from QtTextEdit, but it inherits from it and adds more functionality, 
> as I said. In the link I mentioned you can find it under 
> IPythonShellWidget and right now it has:
> 1. A find widget attached to qtconsole, so you can use Ctrl+F to start 
>    a text search inside the widget
> 2. Traceback link support (when xmode=Plain for now) so the user can 
>    click on any file path that appears on a traceback and have them open 
>    in Spyder's Editor.
> 3.  Integration with our Object Inspector, so you can inspect 
>    docstrings of the object before the cursor using Ctrl+I or writing '?' 
>    after it. These actions will automatically obtain the docstring, pass 
>    it through Sphinx and show the result on a QWebKit widget.
> 
> Perhaps the first addition will be beneficial for you (if you want to 
> add our find widget to your tree) but I think the other ones are too 
> tied to Spyder.
> 
> El 11/06/12 19:08, Fernando Perez escribió:
> 
> > I think it would help if you can explain what widget you are trying 
> > to use and what it offers.  Of course we'd be happy to make it 
> > possible for spyder to better integrate ipython, but it's possible 
> > that a different widget choice may even benefit ipython itself.  So 
> > why don't you explain the bigger context a little so we understand 
> > the situation better.
> > 
> > ---
> > 
> > Reply to this email directly or view it on GitHub:
> > https://github.com/ipython/ipython/issues/1920#issuecomment-6259791

Actually, I think even #3 might be of interest to have in IPython, if it's not coupled to the spyder code too badly.  But we'd certainly be happy to review and integrate some refactored code that provides a richer widget and also makes things easier for sypder.

As for 0.13, that's going to be very tight: we are really trying to stabilize the code that exists _now_ in order to have a good release, rather than taking in much new complexity.  So I suspect we'd tell you to leave this for 0.14.

But obviously that conversation would depend on the actual PR. So your best course of action now would be to actually submit a PR with the changes you guys need.  If it turns out to be both very useful for spyder and simple enough for us to review and trust it won't cause problems, it might go in.

But we'll definitely want to bring any such discussion on to the full list, at this point we've made a shortlist of only 6 PRs that were 'nearly there' for merge, anything else (other than small bugfixes we've seen today) should first be discussed on the list before tagging it for 0.13 inclusion.

I understand that at this point (almost release time) you don't want to take care of disruptive changes. So we're not asking for integration of our improved widget with qtconsole (maybe for 0.14 :-), just the addition of the tiny variable I mentioned in my first post that would make our life a little bit easier. I'll send a pull request with the change so you guys can review it.

About #3: It's not too tied to Spyder but it has some rough edges I still have to solve. It could replace pinfo completely on the notebook and qtconsole but that would put a hard dep on Sphinx for you (which I thought you were against to). In our use case we've found Sphinx is quite fast, if maybe that's what worries you.

I don't think we want a hard dependency on Sphinx - more because of installed size than speed - but a soft dependency might be OK. As in: render to HTML if Sphinx is available, otherwise just display the plain rst.

The interface to inspect an object with a shortcut like Ctrl-I while typing could also be good, but I expect there's less code we could share there.

I thought you wanted html rendering always on (especially on the notebook), but there shouldn't be a problem to handle Sphinx as a soft dep. It's also a soft dep for us. 

Yep, Ctrl+I just required to add a new keyboard shortcut to the widget which fires up the right function on the Object Inspector. It'll be a matter to add something similar to your pager widget.


Resolving patch:
diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -158,6 +158,13 @@ def _font_family_default(self):
     # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
     # priority (when it has focus) over, e.g., window-level menu shortcuts.
     override_shortcuts = Bool(False)
+    
+    # ------ Custom Qt Widgets -------------------------------------------------
+    
+    # For other projects to easily override the Qt widgets used by the console
+    # (e.g. Spyder)
+    custom_control = None
+    custom_page_control = None
 
     #------ Signals ------------------------------------------------------------
 
@@ -989,7 +996,9 @@ def _create_control(self):
         """ Creates and connects the underlying text widget.
         """
         # Create the underlying control.
-        if self.kind == 'plain':
+        if self.custom_control:
+            control = self.custom_control()
+        elif self.kind == 'plain':
             control = QtGui.QPlainTextEdit()
         elif self.kind == 'rich':
             control = QtGui.QTextEdit()
@@ -1026,7 +1035,9 @@ def _create_control(self):
     def _create_page_control(self):
         """ Creates and connects the underlying paging widget.
         """
-        if self.kind == 'plain':
+        if self.custom_page_control:
+            control = self.custom_page_control()
+        elif self.kind == 'plain':
             control = QtGui.QPlainTextEdit()
         elif self.kind == 'rich':
             control = QtGui.QTextEdit()


Test patch: