Saturday, February 20, 2010

QTabWidget - Hiding the bar

It has been a while since my last post. I've been sick, my son has been sick and so has my girlfriend. Now I'm back and have been coding on a private project for a couple of days.

While I was toying and trying some ideas I found out how to create tab bars with Qt that are only visible if you have more than one tab. This is quite popular in browser for example. So without further ado, here's the code:
from PyQt4 import QtGui

class TabWidget(QtGui.QTabWidget):
    def __init__(self, parent=None):
        super (TabWidget, self).__init__(parent)
        self.setTabsClosable(True)
        self.tabCloseRequested.connect(self.removeTab)

    def tabInserted(self, index):
        self.tabBar().setVisible(self.count() > 1)

    def tabRemoved(self, index):
        self.tabBar().setVisible(self.count() > 1)
As you can see, it isn't rocket science. I did some googling but couldn't find anything about it, that's why I want to share my findings. The magic is to only hide the TabBar associated with the TabWidget, not the TabWidget itself.

You can use the following snippet to see the TabWidget in action:
import sys
from PyQt4 import QtGui

class TabWidget(QtGui.QTabWidget):
    def __init__(self, parent=None):
        super (TabWidget, self).__init__(parent)
        self.setTabsClosable(True)
        self.tabCloseRequested.connect(self.removeTab)

    def tabInserted(self, index):
        self.tabBar().setVisible(self.count() > 1)

    def tabRemoved(self, index):
        self.tabBar().setVisible(self.count() > 1)

qApp = QtGui.QApplication(sys.argv)

tab = TabWidget()

button = QtGui.QPushButton('Hello')
@button.clicked.connect
def clicked():
    tab.addTab(QtGui.QLabel('Hello'), 'Hello')

tab.addTab(button, 'Button')

layout = QtGui.QHBoxLayout()
layout.addWidget(tab)

window = QtGui.QWidget()
window.setLayout(layout)
window.resize(600, 400)
window.show()

qApp.exec_()

Saturday, January 30, 2010

Named tuple

I recently read a post on planet python and the author mentioned something about named tuple which made my curious.

So what's a named tuple?
The namedtuple was introduced in Python 2.6. A named tuple is created using a factory function from the collections module and it extends the basic tuple by assigning a name to each position in a tuple but can still be used as a regular tuple. This makes it possible to access fields by name instead of an index. The named tuple should not require more memory, according to the documentation, than regular tuples since they don't have a per instance dictionary.

The factory function signature is:
collections.namedtuple(typename, field_names[, verbose])
The first argument specifies the name of the new type, the second argument is a string (space or comma separated) containing the field names and finally if verbose is true the factory function will also print the class generated.

Enough theory, I'll show you an example.

Example
Say you have a tuple containing username and password. To access the username you get the item at position zero and the password is accessed at position one:
credential = ('mario', 'secret')
print 'Username:', credential[0]
print 'Password:', credential[1]
There's nothing wrong with this code but the tuple isn't self-documented. You have to find and read the documentation about the positioning of the fields in the tuple. This is where named tuple can enter the scene. We can rewrite the previous example as following:
import collections
# Create a new sub-tuple named Credential
Credential = collections.namedtuple('Credential', 'username, password')

credential = Credential(username='mario', password='secret')

print 'Username:', credential.username
print 'Password:', credential.password
Nice, don't you agree?

If you are interested of what the code looks like for the newly created Credential-type you can add verbose=True to the argument list when creating the type, in this particular case we get the following output:
import collections
Credential = collections.namedtuple('Credential', 'username, password', verbose=True)

class Credential(tuple):                                     
        'Credential(username, password)'                     

        __slots__ = () 

        _fields = ('username', 'password') 

        def __new__(_cls, username, password):
            return _tuple.__new__(_cls, (username, password)) 

        @classmethod
        def _make(cls, iterable, new=tuple.__new__, len=len):
            'Make a new Credential object from a sequence or iterable'
            result = new(cls, iterable)                               
            if len(result) != 2:                                      
                raise TypeError('Expected 2 arguments, got %d' % len(result))
            return result

        def __repr__(self):
            return 'Credential(username=%r, password=%r)' % self

        def _asdict(t):
            'Return a new dict which maps field names to their values'
            return {'username': t[0], 'password': t[1]}

        def _replace(_self, **kwds):
            'Return a new Credential object replacing specified fields with new values'
            result = _self._make(map(kwds.pop, ('username', 'password'), _self))
            if kwds:
                raise ValueError('Got unexpected field names: %r' % kwds.keys())
            return result

        def __getnewargs__(self):
            return tuple(self)

        username = _property(_itemgetter(0))
        password = _property(_itemgetter(1))
The named tuple doesn't only provide access to fields by name but also contains helper functions such as the _make() function which helps creating an Credential instance from a sequence or iterable. For example:
cred_tuple = ('mario', 'secret')
credential = Credential._make(cred_tuple)
There are more interesting use-cases and examples in the documentation, so I suggest that you take a peek.

Comments
I think the named tuple is useful. They remove the error-prone indexing in tuples by providing access to fields by name without adding any memory overhead. They are also regular Python classes which means you can do anything you can do with classes.

Wednesday, January 20, 2010

More fun with QWebKit

In the previous post I wrote about calling python methods and accessing properties from JavaScript executed in QWebKit. In this post I'll show you how to do the other way around, calling a JavaScript function from Python.

I would like to thank Rich Moore for commenting on the previous post and pointing out that you should re-add your python object  every time the javaScriptWindowObjectCleared() signal is emitted.

Actually, I think this is almost to simple to do a post about so I'll try doing something fun/useful with it.

QWebFrame contains a public slot:
QVariant evaluateJavaScript(const QString & scriptSource)
You simply pass the JavaScript-snippet to the function. The snippet will be evaluated using the frame as context and returns the result of the last executed statement. This makes it possible to 'click' submit buttons, fill form fields and other interesting stuff in the currently loaded frame. I'll give you an example on how you can create an auto-login GMail widget. The widget will automatically login the user by populating the login form with the user's credentials and 'click' the login button.
#!/usr/bin/env python
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtWebKit

javaScriptLogin = """
document.getElementsByName('Email').item(0).value='{email}';
document.getElementsByName('Passwd').item(0).value='{password}';
document.getElementsByName('signIn').item(0).click(); void(0);
"""

class GmailWebView(QtWebKit.QWebView):
    def __init__(self, parent=None):
        super(GmailWebView, self).__init__(parent)
        self.loggedIn = False

    def login(self, url, email, password):
        """Login to gmail."""
        self.url = QtCore.QUrl(url)
        self.email = email
        self.password = password  
        self.loadFinished.connect(self._loadFinished)
        self.load(self.url)

    def createWindow(self, windowType):
        """Load links in the same web-view."""
        return self

    def _loadFinished(self):
        if self.loggedIn:
            self.loadFinished.disconnect(self._loadFinished)

        self.loggedIn = True
        jscript = javaScriptLogin.format(email=self.email, password=self.password)
        self.page().mainFrame().evaluateJavaScript(jscript)

    def contextMenuEvent(self, event):
        """Add a 'Back to GMail' entry."""
        menu = self.page().createStandardContextMenu()
        menu.addSeparator()
        action = menu.addAction('Back to GMail')
        @action.triggered.connect
        def backToGMail():
            self.load(self.url)
        menu.exec_(QtGui.QCursor.pos())

def main():
    import sys
    qApp = QtGui.QApplication(sys.argv)

    # Prevents me from posting my password on the blog :)
    password, ok = QtGui.QInputDialog.getText(None, "Password request", "Enter password", QtGui.QLineEdit.Password)
    if not ok:
        return

    gmailWebView = GmailWebView()
    gmailWebView.login('https://mail.google.com/mail',
                       'mario.boikov',
                       password)
    gmailWebView.show()

    sys.exit(qApp.exec_())

if __name__ == "__main__":
    main()
This is just a quick hack, it lacks a bunch of checkings...

Maybe it's time to wipe the dust off my GMonitor-plasmoid and add support for opening my account in a new window based on the GMail widget above.