My PyQt Scribbles (Python and Qt) #6: Scroll widget and nested widgets

11 09 2009

Welcome to this new episode of “My PyQt scribbles”

It’s been a while since my last post on this subject and this was because of several causes. The biggest among these it’is me; yeah because not being a professional programmer
I find difficult to find the time to delve into python programming lately. We can say that my math is  x=1/y with x being my python hacking time and y my duties. And that’s self explanatory.
Anyway there’s a important reason why I think this will be the last article of the “season”. If you read one of my past article you know that Nokia decided to release the Qt library under
LGPL license enabling developers to use the library for free but in the scope of a commercial project (as far as they comply with LGPL of course). Soon after Nokia got in contact with
Riverbank Computing to work out an agreement (Riverbank business model on PyQt of selling the commercial use license for PyQt found in conflict with Nokia’s new license program).
As far as I read the agreement wasn’t reached and so Nokia asked OpenBossa to develop PySide, an alternative and official Python
implementation of Qt. As much as I am thankful to Riverbank for having ported Qt to Python thus making non commercial developers able to use Nokia’s powerful library, I think it will be more
more sensitive for the future to look at the upcoming official implementation as it will probably be more solid and better supported and stickier to the future official releases of Qt.
Coming back to our scribbles, this installment will delve a bit more in widgets arrangement, widget nesting and scrolling areas.
I must thank Christian Brugger for helping me out with a trick to nest dynamically generated widgets to a scroll widget (later you will see).
As usual I’ll post the whole code below and after that the parts with comments and explanation.

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class WizAndChipsCal(QWidget):
   def __init__(self, parent = None):
       QWidget.__init__(self)
       self.setWindowTitle("Wiz and Chips Calendar")
       self.setWindowIcon(QIcon("C:/Python26/PyQt/Icon/date.png"))
       self.setToolTip("Hello to this Wiz and Chips fancy calendar!")
       self.title = ("<font color=red size=3><b>"
                       "Wiz and Chips Pushable Calendar!"
                       "</b></font>")
       self.Label = QLabel(self.title)
       self.Label.setAlignment(Qt.AlignCenter | Qt.AlignJustify)

       self._labels = []
       self._edits = []
       self.CurrDate = QDate.currentDate()
       self.calendar = QCalendarWidget()
       self.calendar.setGridVisible(1)
       self.calendar.setMaximumHeight(200)
       self.calendar.setMaximumWidth(250)
       self.connect(self.calendar,
                        SIGNAL('selectionChanged()'),
                        self.SelDate)

       self.AddButton = QPushButton("&AddProject")
       self.AddButton.setMaximumSize(70, 25)
       self.AddButton.setToolTip("Press here to add a new job")
       self.connect(self.AddButton,
                        SIGNAL('pressed()'),
                        self.AddProj)

       self.summaryBox = QGroupBox("Project Management Layout")
       self.summaryBox.setMinimumHeight(300)
       self.summaryBox.setMinimumWidth(800)
       self.summaryBoxScroll = QScrollArea()
       self.summaryBoxScroll.setFrameStyle(QFrame.NoFrame)

       self.summaryBoxTopLayout = QVBoxLayout(self.summaryBox)
       self.summaryBoxTopLayout.setContentsMargins(1,1,1,1)
       self.summaryBoxTopLayout.addWidget(self.summaryBoxScroll)

       self.summaryBoxTopWidget = QWidget()
       self.summaryBoxScroll.setWidget(self.summaryBoxTopWidget)

       self.summaryBoxLayout = QFormLayout()
       self.summaryBoxLayout.setSpacing(1)
       self.summaryBoxLayout.setSizeConstraint(QLayout.SetFixedSize)

       self.summaryBoxLayout = QFormLayout(self.summaryBoxTopWidget)
       self.summaryBoxLayout.setSpacing(1)
       self.summaryBoxLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)

       self.CloseButton = QPushButton("&Quit")
       self.CloseButton.setToolTip("<font color=red size=2><b>"
                                            "Press here to Quit</b></font>")
       self.CloseButton.setMaximumSize(50, 25)

       GeneralLayout = QGridLayout()
       GeneralLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
       GeneralLayout.addWidget(self.Label, 0, 0)
       GeneralLayout.addWidget(self.AddButton, 1,0)
       GeneralLayout.setAlignment(self.AddButton, Qt.AlignTop)
       GeneralLayout.addWidget(self.calendar, 1, 1)
       GeneralLayout.addWidget(self.CloseButton, 4, 0)
       GeneralLayout.addWidget(self.summaryBox, 3, 0)
       self.setLayout(GeneralLayout)

       self.connect(self.CloseButton, SIGNAL("pressed()"),
                        self.close)

   def moveEvent(self, event):
       self.setWindowOpacity(0.7)
       QTimer.singleShot(50, self.opac)

   def opac(self):
       self.setWindowOpacity(1)

   def closeEvent(self, event):
       self.CloseDialog = QMessageBox.question(self,
               "The application is being closed",
               "Do you really want to quit?",
               QMessageBox.Save|QMessageBox.Yes|QMessageBox.Discard,
               QMessageBox.Discard)   

        if self.CloseDialog == QMessageBox.Yes:
           event.accept()
       elif self.CloseDialog == QMessageBox.Save or QMessageBox.Discard:
           event.ignore()   

   def SelDate(self):
       self.SelectedDate = self.calendar.selectedDate()
       print(self.SelectedDate)

   def AddProj(self):
       projLabelLayout = QHBoxLayout()
       projLabelLayout.setSpacing(3)
       label_list = []
       projTextBoxesLayout = QHBoxLayout()
       projTextBoxesLayout.setSpacing(3)
       edit_list = []

       for name, width in [('Job No.', 80), ('Cust.Job', 80),
               ('Cust.Ords.', 80), ('Product', 80), ('Q.ty', 80),
               ('Serial No', 80), ('Quality', 80), ('Packing', 80)]:
           label = QLabel(name)
           label.setMinimumWidth(width)
           projLabelLayout.addWidget(label)
           label_list.append(label)
           TextEdit = QTextEdit()
           TextEdit.setMaximumHeight(20)
           TextEdit.setMinimumWidth(width)
           TextEdit.setTabChangesFocus(1)
           projTextBoxesLayout.addWidget(TextEdit)
           edit_list.append(TextEdit)

       for name1, width1 in [('delivery', 80), ('drawings', 80), ('approval', 80),
                             ('cust. mat. deliv.',80), ('mat. delivery', 80), ('end prod.', 80),
                             ('test date', 80), ('ship date', 80)]:
          label1 = QLabel(name1)
          label1.setMinimumWidth(width1)
          projLabelLayout.addWidget(label1)
          label_list.append(label1)

          DateEdit = QDateEdit(self.CurrDate)
          DateEdit.setCalendarPopup(True)
          DateEdit.setMaximumHeight(20)
          DateEdit.setMinimumWidth(width)
          projTextBoxesLayout.addWidget(DateEdit)
          edit_list.append(DateEdit)

       self._labels.append(tuple(label_list))
       self._edits.append(tuple(edit_list))

       self.summaryBoxLayout.addRow(projLabelLayout)
       self.summaryBoxLayout.addRow(projTextBoxesLayout)

app = QApplication(sys.argv)
main_window = WizAndChipsCal()
main_window.show()
app.exec_()
self._labels = []
self._edits = []
self.CurrDate = QDate.currentDate()

We create two empty lists to store the information about the rows added to the job management application. This will become clear afterwards.
We retrive the current date from the system clock to use it in our application

self.AddButton = QPushButton("&AddProject")
self.AddButton.setMaximumSize(70, 25)
self.AddButton.setToolTip("Press here to add a new job")
self.connect(self.AddButton,
                          SIGNAL('pressed()'),
                          self.AddProj)

We create a button and then set it to add new rows in our job management overview project.
To do this we must connect its signal “pressed()” to our custom slot which in this case is a function conveniently called “AddProj”

Now we create the layouts and widgets which will become the core of our job management overview. This part is tricky and involves several widgets and layout being created.
The summary is:

- We create a QGroupBox widget as base container of the other widgets
- We create a QScrollArea widget
- We create a top layout (in the form of a QVBoxLayout) for our QGroupBox widget and assign the QScrollArea widget to it
- We create a top bare QWidget to link it later to the QScrollArea
- We create a top layout for the QGroupBox and the top widget

Now in detail:

self.summaryBox = QGroupBox("Project Management Layout")
self.summaryBox.setMinimumHeight(300)
self.summaryBox.setMinimumWidth(800)

This is the space containing the dynamic list of our jobs. So we create the main QGroupBox widget and we set the minimum dimensions to control the appearance of the whole area.

self.summaryBoxScroll = QScrollArea()
self.summaryBoxScroll.setFrameStyle(QFrame.NoFrame)

Now we create the scoller itself. To do this we use the QScrollArea() class which provides a scrolling view onto another widget.
Normally the QScrollArea() just needs to take care of the widget as explained in the API, that is to say:
label = QLabel(“image.png”)
scroller = QScrollArea()
scroller.setWidget(label)
This is very straightforward but in our case it doesn’t work because we have several widgets which are dynamically created and get nested inside another widget.
So we need a trick: we create a top widget and that’s the one we want to scroll. We assign this widget to the scroller.
Imagine a cupcake. We have the cake composed of many ingredients, and this represents our job widgets.
Then we have the cup which contains the cake, and this is our top widgets.
Finally we have the kid’s hand moving the cupcake to eat it, and that’s our scroller.

self.summaryBoxTopLayout = QVBoxLayout(self.summaryBox)
self.summaryBoxTopLayout.setContentsMargins(1,1,1,1)

This is the top layout which will contain the scroll widget to take care of the scroll area.

self.summaryBoxTopLayout.addWidget(self.summaryBoxScroll)

now we add the QScrollArea() to the top layout.

self.summaryBoxTopWidget = QWidget()
self.summaryBoxScroll.setWidget(self.summaryBoxTopWidget)

To scroll the area flawlessly we need to create a top widget which must then be assigned to the QScrollArea.
In this way the QScrollArea will take care of scrolling the top widget which contains all the other dynamically changed sub widgets

self.summaryBoxLayout = QFormLayout()
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetFixedSize)

This is the layout relevant to the summaryBox widget.
We assign our QGroupBox() to the layout, we set the internal spacing between the elements to be ‘1′ and finally we set the size constraint policy to be sure that the widget can’t
be resized at all with respect to its sizes defined at sizeHint()

self.summaryBoxLayout = QFormLayout(self.summaryBoxTopWidget)
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)

To the same general layout we assign also the top widget and specify the size constraints

GeneralLayout.addWidget(self.summaryBox, 3, 0)

The QGroupBox widget which represents our job management overview (and contains all the other widgets and layouts)is laid out.

Now it’s the time to discuss the function that we called AddProj and that will take care of the widgets generation inside the QGroupBox.

def AddProj(self):
                projLabelLayout = QHBoxLayout()
                projLabelLayout.setSpacing(3)

This function is called every time the user presses the AddButton.
The job management overview is composed of rows and columns. Each row represents a job and each column represents an element related to that job.
This is clearly a very simple way to provide an overview of a series of jobs within a company.

label_list = []
projTextBoxesLayout = QHBoxLayout()
projTextBoxesLayout.setSpacing(3)
edit_list = []

We create empty lists that will contain each additional label for every job element we decide to add.

for name, width in [('Job No.', 80), ('Cust.Job', 80),
               ('Cust.Ords.', 80), ('Product', 80), ('Q.ty', 80),
               ('Serial No', 80), ('Quality', 80), ('Packing', 80)]:
           label = QLabel(name)
           label.setMinimumWidth(width)
           projLabelLayout.addWidget(label)
           label_list.append(label)
           TextEdit = QTextEdit()
           TextEdit.setMaximumHeight(20)
           TextEdit.setMinimumWidth(width)
           TextEdit.setTabChangesFocus(1)
           projTextBoxesLayout.addWidget(TextEdit)
           edit_list.append(TextEdit)

Here we populate the label list that we have previously created. We use the ‘for’ statement so that we can easily add/remove future elements to our system without the need -at least in theory- to review our design.
Each label widget will get the name and the with in sequence from the list.
Then we add each label to the label layout and we append every label to the list which is hence populated.
We make the same process for the text boxes with only two additional staps. First of all we set a maximum height for the cell.
Secondly, we use the setTabChangesFocus() property so that we can switch from one cell to the next one using the tab key.

The ‘for’ statement used to populate the list of elements which are used to generates the widgets for the job management part, is convenient because it helps us to write a code more elegant and concise, but it has a major drawback. Using this systme we can generate only one type of widgets because this is closed inside a loop which will simply iterate through the list and create a specific
widget for each element of the list. As a matter of fact in order to include calendar edit widgets to our job management structure we must create a second list whose elements are sistematically used to create pop up calendars.

for name1, width1 in [('delivery', 80), ('drawings', 80), ('approval', 80),
                             ('cust. mat. deliv.',80), ('mat. delivery', 80), ('end prod.', 80),
                             ('test date', 80), ('ship date', 80)]:
          label1 = QLabel(name1)
          label1.setMinimumWidth(width1)
          projLabelLayout.addWidget(label1)
          label_list.append(label1)
          DateEdit = QDateEdit(self.CurrDate)
          DateEdit.setCalendarPopup(True)
          DateEdit.setMaximumHeight(20)
          DateEdit.setMinimumWidth(width)
          projTextBoxesLayout.addWidget(DateEdit)
          edit_list.append(DateEdit)

Given that this part of the job management application concerns dates, it’s more confortable for our user to input the dates by means of a pop up calendar than simply type them (with the correct format) inside text boxes. QDateEdit is the class which does the trick.
By setting .setCalendarPopup(True) we makes the calendar popping up from the widgets only when the user clicks on the specific box.

self._labels.append(tuple(label_list))
       self._edits.append(tuple(edit_list))

We append the label list and the edit list to two other lists but we convert them to tuple before.

Here below you can see screenshots of the Ubuntu and Windows XP version of the application

Calendar3_1(Win) Calendar3_1(Ubu)

Calendar3_2(Win) Calendar3_2(Ubu)





Qt 4.5 released: Kudos to Nokia for showing a smart way to steward a project

11 03 2009

qt_logostrap_cmykQt Software, formerly known as Trlltech, released the 4.5 version of their popular C++ user interface framework.
Up to today Qt has always been know especially for two things: to be a powerful and modern cross-platform tool and to be far to expensive for being used by independent and small software workshops.
On the summer of 2008 Nokia bought Trolltech and renamed it Qt Software. Qt 4.5 is the first release of the Nokia era and it’s already showing Nokia’s view for this award winning application.

License
The first and most visible changement is about licensing. Up to now Qt was traditionally available with two license versions: a commercial version (very expensive) and a GPL version (completely free). This basically meant that it addressed either to high-budget companies to produce commercial software or to anybody willing to produce and distribute completely open source software. This two-tail licensing cut completely off independent developers and small software houses which clearly hadn’t the budget to buy a commercial license. Competing frameworks filled this gap and what is worse user support for this application was only bound to the official forum as the popularity among the developers was low.
Nokia changed that by introducing a third license option: the Lesser General Public License (LGPL) version 2.1. By paraphrasing Qt Software’s own statement “This version of Qt is appropriate for the development of Qt applications (proprietary or open source) provided you can comply with the terms and conditions contained in the GNU LGPL version 2.1.” Thus, provided that you can comply with the LGPL license, you can have a free copy of Qt and produce and sell commercial software.
This is the first big improvement Nokia’s done to increase the software development market penetration of Qt. This is also a very good news for developers because Qt is an extremely powerful tool which will be a asset to companies. Often, open source software comes from developers who put their skills and time at the community’s service. Given that those skills are often derived from an educational or job environment, this new license means that more people are likely able to choose, learn and develop with Qt, which a payback to the community in terms of better software available to users.
Qt is ported to different languages such as Java, Python, C, Perl etc.. From this it comes to my mind one possible drawback of this new license option. PyQt, the Python binding, is currently made by a software house, named Riverbank Computing Limited, which lets you acquire either your free GPL version or the commercial one. The rationale is obviously that the comply with Qt policy by giving away the GPL version and make a living of the commercial PyQt version. Right now, however, I can imagine Riverbank guys being a bit concerned about the LGPL Qt license as they will surely suffer a purchases loss from those which are now satisfied with producing commercial products which comply with LGPL license. Right now there’s no 4.5 PyQt available and all we can do is basically see how the Riverbank’s guys decide to move.

New features
Qt 4.5 accomplish two major design goals: increasing performance, enabling hybrid web/native development.
graphicschildrenThe performance improvement involves the whole framework and can be seen in a faster runtime, enhanced performances of key functionalities as data handling and graphics syste. You can find more information here. Qt 4.5 fullfills now the goal of providing richer user experiences by enabling a better integration with WebKit rendering engine to blend local and web based content. To this aspect Qt applications can now include flash content (like the YouTube player) as well as high end web technologies like HTML5, CSS3 and SquirrelFish, WebKit’s JavaScript engine.
To Mac development Qt now supports the new Cocoa framework instead of the deprecated Carbon.
From this summary of improvements it is clear to me that the will of Qt, therefore Nokia, is to become a fundamental player in the new generation applications. The web integration is the aspect which I think it’s the more interesting. Right now there’s a lot of chatting about cloud computing and how the desktop based application are doomed to migrate to the web. I am one of those which are not completely comfortable with this scenario. Web applications are not the Saint Grail of modernity. Just like everything in the world,web infrastructure has its technical and usage pros and cons and to exasperate the web migration of just everything running on top of desktop machines it’s probably not the right approach. I tend to prefer an approach more focused in exploiting the best of the two worlds, desktop and web, by integrating, when it’s the case, web resources in desktop applications. This seems to be also the way Nokia’s seeing it and Qt 4.5 provides the tools to create applications which make use of the best web technologies available on the scene.

Qt Creator
This Qt version comes, as usual, with a bundle of tools among which is the new Qt Creator.
Qt Creator is a cross-platform IDE (integrated development environment) specifically design to work with Qt in a cross-platform scope. This is the first version of this tool so rough edges must be expected but, up to Ars Tecnica, the application is perfectly usable. It provides the usual functions and tools like auto completion, text highlights, fast search etc.. but specifically focused on the Qt framework. If I can see a defect on this, which is also referred to the framework in general, is the absence of support for languages other than C++. To provides branches for different languages is surely an asset to a framework focused on setting the benchmark of graphical user interfaces. I know that my opinion is probably biased but the fast coding and high productivity provided by Python for example could be a reason for a developer to use this language to produce an application which maybe doesn’t need the power and velocity of the C++. As already mentioned Qt has its own unofficial branches to several other languages, however it would be great a stronger support from Qt Software to the developers in order to give those developers working with other very popular languages to exploit the best of Qt.

My general opinion of Qt is very good and now with the introduction of the LGPL license, I can finally strongly advise to choose Qt as your framework. If you have some doubts regarding the power of Qt just know that software such as Google Earth, Adobe Photoshop Elements, KDE desktop environment, Skype and the popular VLC player are made with Qt. Qt library is used for projects in world wide leading companies like Lucasfilm LTD, Walt Disney Animation Studios, Next Limit Technologies etc.

Qt is currently available for Embedded Linux, Mac OS X, Windows, Linux/X11, Windows CE, S60*

*Qt on S60 is not released as a stable product yet, it’s still under development and currently Nokia only provides pre-releases





My PyQt Scribbles (Python and Qt) #5: Event-Handling Mechanism (QEvent: moveEvent() and closeEvent())

6 03 2009

In this installment of My PyQt Scribbles we will have a look to the event-handling mechanism.

As I already mentioned it’s my custom to explore new concepts through examples. What I call ‘useful examples’ are the implementation of concepts in plausibly real situation. This means that I won’t just write an example by detaching a particular mechanism from the whole app. I will not write something which is too abstract or that does something that replicate an useless function. This scribbles of mine are mostly an exercise and a tool I exploit to study a subject I like. As far as I can I will implement all the concepts into the building of a specific application which will slowly grow and increase its functionality as I explore further subjects.

Today we will make our application more fashionable by replicating the transparency feature you can see on KDE (with composition manager enabled), Mac OSX and Vista. Basically, our app will get transparent when we move it around the screen. We will also implement a dialog widget which pops up when we try to close the application to ask us if we’re sure to quit or not.

Events are members of the QEvent class and a sort of low level objects that represent things that have happened either in the outside environment or inside the application. They’re processed at different level by the Qt application.
Events can be divided in three different types:

Spontaneous events à they originate outside the application (window system) and are queued and processed by the event loop
Posted events à they are generated by Qt or the application and are queued and processed by the event loop
Sent events à they are generated by Qt or the application but instead to be managed by the loop they’re sent directly to the target object

When an event happens, Qt generates a specific object to represent that specific event. This event is then shipped to the children/parents chain for handling and, if no Object handles it in some way the event object is just discarded.
A corollary to this is that we usually don’t need to send (sendEvent() )or post (postEvent() ) events explicitly because most events are generated automatically by Qt or by the window system when they happen. If you want to “manually” send an event, you can call high-level functions to do this (i.e. update() and repaint()).

event

Events in Qt can be processed on five different levels but the first two are the most common and needed. The last three are rarely used so you don’t need to go deeper in the concept related to them unless you’re a Qt guru (in this case you’re probably wasting your time reading this ‘scrible’).

• Reimplementing a specific event handler.
QObject and QWidget provide many specific event handlers for different types of events (for example, moveEvent() for handling the movements).
• Reimplementing QObject::event().
The event() function is the ancestor of all the object’s events. The default behaviour in QObject and QWidget simply forward the events to the specific event handlers.
• Installing an event filter on a QObject.
An event filter is an object that receives another object’s events before they reach the intended target.
• Installing an event filter on qApp. Exceptionally, an event filter on qApp monitors all events sent to all objects in the application.
• Reimplementing QApplication::notify(). Qt’s event loop and sendEvent() call this function to dispatch events. By reimplementing it, you get to see events before anybody else.

As I said before some type of events can be propagated to the children/parents chain. In this situation, if a target object doesn’t process the event, Qt passes the event to other receivers until reaching the top-level parent object.

This is a brief explanation about what events are and how they work. If you find it confusing just try to grab the basic concepts by studying the code below. One approach I find very useful is not just to copy and paste the code as it is but try to modify it in your own way. You can maybe try to reimplement the basic concept to explore different events or handling them in a different way.
If you want to increase your knowledge you can check the following links and read the articles.

http://doc.trolltech.com/4.4/eventsandfilters.html

http://doc.trolltech.com/qq/qq11-events.html

So lets go on with the example.
As usual only the new parts are commented.

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class WizAndChipsCal(QWidget):

        def __init__(self, parent = None):
                QWidget.__init__(self)
                self.setWindowTitle("Wiz and Chips Calendar")
                self.setWindowIcon(QIcon("C:/Python26/PyQt/Icon/date.png"))
                self.setToolTip("Hello to this Wiz and Chips fancy calendar!")
                self.title = ("<font color=red size=3><b>"\
                              + "Wiz and Chips Pushable Calendar!"\
                              + "</b></font>")
                self.Label = QLabel(self.title)
                self.Label.setAlignment(Qt.AlignCenter | Qt.AlignJustify)

                self.calendar = QCalendarWidget()
                self.calendar.setGridVisible(1)
                self.calendar.setMinimumHeight(180)
                self.calendar.setMinimumWidth(110)

                self.check1 = QCheckBox("check1")
                self.check2 = QCheckBox("check2")
                self.TextBox = QTextEdit("type something here")
                self.TextBox.setMaximumHeight(50)

                self.dateLabel = QLabel("Date:")
                self.dateLabel.setMaximumWidth(80)
                CurrDate = QDate.currentDate()
                self.date = QDateEdit(CurrDate)
                self.date.setMaximumWidth(80)

                self.CloseButton = QPushButton("&Quit")
                self.CloseButton.setToolTip("<font color=red size=2><b>"\
                                            + "Press here to Quit"\
                                            + "</b></font>")
                self.CloseButton.setMaximumSize(50, 25)

                self.infobox = QGroupBox("Info Box")
                self.infobox.setCheckable(1)
                self.infobox.setChecked(0)

                dateLayout = QHBoxLayout()
                dateLayout.addWidget(self.dateLabel)
                dateLayout.addWidget(self.date)
                dateLayout.addSpacing(170)

                GBoxLayout = QVBoxLayout(self.infobox)
                GBoxLayout.setSpacing(1)

                GBoxLayout.addLayout(dateLayout)
                GBoxLayout.addWidget(self.check1)
                GBoxLayout.addWidget(self.check2)
                GBoxLayout.addWidget(self.TextBox)

                Layout = QGridLayout()
                Layout.addWidget(self.Label, 0, 0)
                Layout.addWidget(self.calendar, 1, 0)
                Layout.addWidget(self.CloseButton, 4, 0)
                Layout.addWidget(self.infobox, 3, 0)
                self.setLayout(Layout)

                self.connect(self.CloseButton,
                             SIGNAL("pressed()"),
                             self.close)
        def moveEvent(self, event):
                self.setWindowOpacity(0.7)
                QTimer.singleShot(50, self.opac)
        def opac(self):
                self.setWindowOpacity(1)

        def closeEvent(self, event):

                self.CloseDialog = QMessageBox.question(self, "The application is being closed",
                                                        "Do you really want to exit?",
                                                        QMessageBox.Save|QMessageBox.Yes|QMessageBox.Discard,
                                                        QMessageBox.Discard)
                if self.CloseDialog == QMessageBox.Yes:
                        event.accept()
                elif self.CloseDialog == QMessageBox.Save or QMessageBox.Discard:
                        event.ignore()

app = QApplication(sys.argv)
main_window = WizAndChipsCal()
main_window.show()
app.exec_()
def moveEvent(self, event):
self.setWindowOpacity(0.7)
QTimer.singleShot(50, self.opac)

Everytime the widget is moved (this includes the first time it’s painted on the screen) the function change the widget opacity to make it transparent.
moveEvent() event handler is implemented to receive widget move events which are passed in the event parameter. When the widget receives this event, it is already at the new position.
Once the movement is finished we call QTimer.singleShot which counts a certain number of milliseconds and then calls another function which sets the opacity to 1 thus making the widget opaque again. singleShot is needed to tell QTimer to count just one time. Otherwise the count is made at intervals

def opac(self):
self.setWindowOpacity(1)

setWindowOpacity(1) property holds the level of opacity for the window. The range is from 1.0 (completely opaque) to 0.0 (completely transparent).
Note: This feature is only available on Mac OS X, X11 platforms that support the Composite extension, and Windows 2000 and later.

         def closeEvent(self, event):
                self.CloseDialog = QMessageBox.question(self, "The application is being closed",
                                                        "Do you really want to exit?",
                                                        QMessageBox.Save|QMessageBox.Yes|QMessageBox.Discard,
                                                        QMessageBox.Discard)

This event handler is called with the given event when Qt receives a window close request for a top-level widget from the window system.
We create a dialog widget by implementing QmessageBox.question(). This class displays a dialog widget with an informative text for the user and provides standard buttons to enable the user to interact. By default QMessageBox support preformatted (layout, icons etc..) message boxes for questions (QMessageBox.question()), information (QMessageBox.information()), warning (QMessageBox.warning()) and critical (QMessageBox.critical()).

The configuration of our question box is: QMessageBox.question ( QWidget * parent, QString=title, Qstring=text, StandardButtons=buttons, StandardButton=defaultButton)
Our default button must be picked from the buttons already used, we pick discard in order to make our exit procedure safer. Note: save button will now have the same behaviour of ‘discard’. We will reserve the function for later usage.
There are several standard buttons already specified by the API.
For more information about message boxes see http://doc.trolltech.com/4.5/qmessagebox.html

         if self.CloseDialog == QMessageBox.Yes:
                        event.accept()
                elif self.CloseDialog == QMessageBox.Save or QMessageBox.Discard:
                        event.ignore()

By default, closeEvent eventt is accepted and the widget is closed. We instead reimplement this function to change the way the widget responds to the close request.
If the user press ‘Yes’ we call accept() and the window is closed. If the button pressed is ‘Save’ or ‘Discard’ we call ignore() and the event is thrown away.

calendar2 calendar2kubu

calendar2_1 calendar2_1kubu





Pimp My Widgets 2008

9 02 2009

Geeks are crazy, that’s no secret.

Guys at Qt Software are as well.

Every year they launch a contest for the best widget made using their wonderful and powerful Qt library. You want to spell Qt “cute” and that’s one adjective describing it, but others relevant are powerful, multi platform and versatile.

In the end of this contest the Widget Pimp set up a show to announce the winners.

image_mini

Here’s the video of the 2008 winners.

You can find more information about the winning widgets and download the source code at the contest’s web page.





My PyQt Scribbles (Python and Qt) #4: Layout Managers (QGridLayout() and more)

5 02 2009

Last time I made a calendar, a quite convenient and clean app in just a few lines of code. Despite its elegant look and feel the code was very basic. By the way this isn’t something bad, however the widgets arrangement was inelegant and that’s something we’re going to fix in this installment.

As usual we’ll focus only on a few objects in order to get a glimpse of a certain subject without exploring it in depth. Our purpose, at the moment, is to understand the basic concept which enable us to create something functional. In particular we will work only on certain classes and certain members of those classes. After having absorbed the concepts, and with the class reference at hand, you can further experiment and refine the subject of your interest.
Lets then put our hands on the calendar and restructure the widgets arrangement.

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class WizAndChipsCal(QWidget):

        def __init__(self, parent = None):
                QWidget.__init__(self)
                self.setWindowTitle("Wiz and Chips Calendar")
                self.setWindowIcon(QIcon("C:/Python26/PyQt/Icon/date.png"))
                self.setToolTip("Hello to this Wiz and Chips fancy calendar!")                

                self.title = ("<font color=red size=3><b>"+\
                             "Wiz and Chips Pushable Calendar!"+\
                             "</font></b>")
                self.Label = QLabel(self.title)
                self.Label.setAlignment(Qt.AlignCenter | Qt.AlignJustify)

                self.calendar = QCalendarWidget()
                self.calendar.setGridVisible(1)
                self.calendar.setMinimumHeight(180)
                self.calendar.setMinimumWidth(110)

                self.check1 = QCheckBox("check1")
                self.check2 = QCheckBox("check2")
                self.TextBox = QTextEdit("type something here")
                self.TextBox.setMaximumHeight(50)

                self.dateLabel = QLabel("Date:")
                self.dateLabel.setMaximumWidth(80)
                CurrDate = QDate.currentDate()
                self.date = QDateEdit(CurrDate)
                self.date.setMaximumWidth(80)

                self.CloseButton.setToolTip("<font color=red size=2><b>"+\
                                            "Press here to Quit"+\
                                            "</font></b>")
                self.CloseButton.setMaximumSize(50, 25)

                self.infobox = QGroupBox("Info Box")
                self.infobox.setCheckable(1)
                self.infobox.setChecked(0)

                dateLayout = QHBoxLayout()
                dateLayout.addWidget(self.dateLabel)
                dateLayout.addWidget(self.date)
                dateLayout.addSpacing(170)

                GBoxLayout = QVBoxLayout(self.infobox)
                GBoxLayout.setSpacing(1)
                GBoxLayout.addLayout(dateLayout)
                GBoxLayout.addWidget(self.check1)
                GBoxLayout.addWidget(self.check2)
                GBoxLayout.addWidget(self.TextBox)

                Layout = QGridLayout()
                Layout.addWidget(self.Label, 0, 0)
                Layout.addWidget(self.calendar, 1, 0)
                Layout.addWidget(self.CloseButton, 4, 0)
                Layout.addWidget(self.infobox, 3, 0)
                self.setLayout(Layout)
                self.connect(self.CloseButton,
                             SIGNAL("pressed()"),
                             self.close) 

app = QApplication(sys.argv)
main_window = WizAndChipsCal()
main_window.show()
app.exec_()

Now we comment the most important passsages.

class WizAndChipsCal(QWidget):

QWidget() is the base class of all user interface objects. QWidget is the core of the user interface: all the other widget or window classes inherit from it.

self.calendar = QCalendarWidget()
self.calendar.setGridVisible(1)
self.calendar.setMinimumHeight(180)
self.calendar.setMinimumWidth(110)

We begin by creating all the various widgets we need to compose our GUI.
We use the .setMinimumHeight and .setMinimumWidth methods to fix the minimum dimensions that we desire for our calendar widget. These method can hold integers.

self.check1 = QCheckBox("check1")
self.check2 = QCheckBox("check2")
self.TextBox = QTextEdit("type something here")
self.TextBox.setMaximumHeight(50)

We create two check boxes with QcheckBox() class to use them later. We create a box which can be filled of text by the user with QTextEdit() class. For layout convenience we fix the edit box’s maximum height with .setMaximumHeight method.

self.dateLabel = QLabel("Date:")
self.dateLabel.setMaximumWidth(80)
CurrDate = QDate.currentDate()
self.date = QDateEdit(CurrDate)
self.date.setMaximumWidth(80)

Now we create a plain label with QLabel() class and set the label maximum width. At this point we wanna create a date spinbox, an object which shows the date, in a certain format, and that can be managed by the user. The first thing we do is to create an object to hold the current date. .currentDate method, which belong to QDate() class,  gets the current date from the system clock.
Having done this we create the date spinbox with the QDateEdit() class and pass it the current date container we created before as argument of the class. At this point we begin to take care of the arrangement of the various widgets inside the parent QWidget window. To reach this general purpose we focus on the layout. In PyQt there are various classes which takes care of laying out the widgets. For exploration’s sake we’ll use three of them here.

self.infobox = QGroupBox("Info Box")
self.infobox.setCheckable(1)
self.infobox.setChecked(0)

QGroupBox class provides a box which contains child widgets. It’s an elegant way to organise the space inside a GUI. We call the .setCheckable and set it to true to turn the info box title in a check box. In this way the child widgets are accessible only if the QGroupBox is checked otherwise they’re greyed and inaccessible. By default checkable QgroupBox are checked. We don’t want it to be in this state thus we set the boolean parameter of .setChecked method to false.

dateLayout = QHBoxLayout()
dateLayout.addWidget(self.dateLabel)
dateLayout.addWidget(self.date)
dateLayout.addSpacing(170)

We use a specific layout manager to arrange the widgets relevant to the date subject. We want the date spinbox to have a label on its left. This is an horizontal arrangement so we call the QHBoxLayout() class which inherits from QBoxLayout().

qhboxlayout1

We add first the label widget then the date spinbox widget and finally a spacer, by means of .addSpacing method, and givie it a dimension in pixel. We need this spacer because the general layout of the GUI –which we will take care of very soon- will force this widgets to position themselves so to get all the space pre-sized for them by the largest widget in the GUI –which in our case is the calendar itself. The spacer is basically an invisible widget which we can comfortably use to obtain our desired arrangement.

GBoxLayout = QVBoxLayout(self.infobox)
GBoxLayout.setSpacing(1)
GBoxLayout.addLayout(dateLayout)
GBoxLayout.addWidget(self.check1)
GBoxLayout.addWidget(self.check2)
GBoxLayout.addWidget(self.TextBox)

At this point we arrange the full content of the QgroupBox. At first we create a layout and since we want to arrange all the widget vertically we now use the QVBoxLayout. Being a brother or sister of QHBoxLayout, QVBoxLayout provides a vertical layout arrangement for the contained objects.
qvboxlayout
We want the widgets to occupy the fewest possible space so we call the .setSpacing method set the vertical space between the widgets to the value of 1. Before adding widgets to the layout we add a complete layout. In this way we can nest the horizontal layout which we create before, into the vertical layout. Then we add the remaining widgets to the vertical layout container.

Layout = QGridLayout()
Layout.addWidget(self.Label, 0, 0)
Layout.addWidget(self.calendar, 1, 0)
Layout.addWidget(self.CloseButton, 4, 0)
Layout.addWidget(self.infobox, 3, 0)
self.setLayout(Layout)

At this point we take care of the general GUI layout (without mentioning the underlying main window). To to this we use the QGridLayout() class. QGridLayout() is a class meant to lay out widgets in a grid. It inherits from QLayout class which is the abstract base class of geometry managers. QGridLayout gets the available space (the parent layout or parent widget) and slice it in columns and rows thus forming cells. We can then easily place our widgets in the desired cells in order to structure our GUI space.

qgridlayout1

After creating the layout we call the .addWidget method to add the widgets to the grid at the desired row and column position. The first parameter of the method is the widget (or layout also) to be added, then we specify the column and row by means of integer numbers. After having finished to position the widgets we call .setLayout with our layout as argument to set the general layout to the parent widget. Here below you can see the result both in Windows XP and in Kubuntu 8.10 with KDE 4.2.

calendar1 calendar1kubu

calendar1_11 calendar1_1kubu2





My PyQt Scribbles (Python and Qt) #2: a plain window in PyQt

30 11 2008

Today we will create a main window, set a window title, an icon and a tooltip related to the window.

First of all let’s check that our PyQt is well installed and working. To do this just type: import PyQt4 from IDLE and press enter. If nothing happens we’re fine. If it shows an error message like “ImportError: No module named PyQt4″, try to check your installation (see My PyQt scribbles #1).

If everything is fine we can start.

I will first present the code as a whole and then proceed through it to analyse the various parts.

import sys
from PyQt4 import QtGui

class HelloWorldWindow(QtGui.QMainWindow):
        def __init__(self, parent = None):
                QtGui.QMainWindow.__init__(self, parent)
                self.setWindowTitle("My First Qt Window")
                self.setGeometry(300,300,250,150)
                self.setWindowIcon(QtGui.QIcon("C:/Python26/PyQt/Icon/gadu.png"))
                self.setToolTip("Hello to this Wiz and Chips example!")

app = QtGui.QApplication(sys.argv)
main_window = HelloWorldWindow()
main_window.show()
app.exec_()

We begin importing the needed modules.

import sys
from PyQt4 import QtGui

Sys is needed to initialise the QApplication class. This class manages the application control flow and the main settings. QApplication contains the main event loop (where all events from the window system and other sources are processed and dispatched), the application initialisation and finalization. For any GUI application based on Qt there must be one QApplication object, no matter how many windows are displayed.

QtGui is the module which contains the GUI classes. As a matter of fact extends the QtCore module with GUI functionality

We now create a main class:

class HelloWorldWindow(QtGui.QMainWindow):

The argument of our window class refers to another class, QMainWindow, which supplies a main application window.

main
The layout has a center widget area where any kind of widget can be placed. You can add QToolBars, QDockWidgets, a QMenuBar, and a QStatusBar. It’s not currently possible to create a main window without a central widget. You must have a central widget even if it is just a placeholder.

Now we create a function to initialise the class

def __init__(self, parent = None):
            QtGui.QMainWindow.__init__(self, parent)

this is to initialise the main window. __init__ is a special function in python which is used to set up the object using the arguments to make variables for the object. __init__ is called upon the object creation.

Now it’s time to shape a bit our window.

self.setWindowTitle(“My First Qt Window”)

The setWindowTitle() method sets the window title

self.setGeometry(300,300,250,150)

The setGeometry method sets the relative position (x,y) on the screen and the size (w,h) of the window

self.setWindowIcon(QtGui.QIcon(“C:/Python26/PyQt/Icon/gadu.png”))

This method sets the window icon. For my test I have used a crystal clear project icon (http://www.everaldo.com/crystal/). For windows user be sure to use “/” as folder separator instead of “\”. The latter is used for escapes in python.

self.setToolTip(“Hello to this Wiz and Chips example!”)

With this method we specify a tooltip related to our present object: the main window

app = QtGui.QApplication(sys.argv)

QApplication class manages the GUI control flow and main settings

main_window = HelloWorldWindow()
main_window.show()

The show() method displays the widget on the screen

app.exec_()

This is to enter the main loop

Here below you can see the result: a window with all the accessories we have defined both in Windows XP and Linux Kubuntu 8.10 KDE 4.1 fashion.

1stcode 1stcodekubu1

Pretty neat result isn’t it? And without too much effort also! At this very early point PyQt seems quite cool and easy to me. Let’s see what we can pull out of it.

As always.. stay tuned folks!

Disclaimer: I’m not a prefessional python programmer nor I pretend to be. I am just an amateur willing to learn and share its by-no-means-guarantee-as-it know-how with the reader. I nevertheless hope you will enjoy my scribbles and maybe find them somewhat stimulating and helpful.





My PyQt Scribbles (Python and Qt) #1

26 11 2008

In this first installment of My PyQt Scribbles I will briefly introduce the Qt (PyQt) framework.

We will work with PyQt4 and Python 2.6. I will try as much as possible to test the code either on Linux (Kubuntu 8.10 currently) or Windows XP.

Let’s start with a note on the installation.

For Windows users it’s quite trivial (as usual) to download the binary from this page of Riverbank website. If you intend to use same tools I’m using then be sure of downloading the package for Python 2.6 (PyQt-Py2.6-gpl-4.4.4-2.exe). For Linux unfortunately there’s no binary available but only the source for you to compile. My advice is to install the packages through Synaptict or Adept. Just look for python-qt4 in the repository.

PyQt, as binding of Nokia (former Trolltech) Qt, is a very complete library whose classes have been split is several extension modules.

The most important ones are the following:

modules

And directly from the PyQt reference guide…

QtGui module: This contains the majority of the GUI classes.

QtCore module: This contains the core non-GUI classes, including the event loop and Qt’s signal and slot mechanism. It also includes platform independent abstractions for Unicode, threads, mapped files, shared memory, regular expressions, and user and application settings.

QtNetwork module: This module contains classes for writing UDP and TCP clients and servers. It includes classes that implement FTP and HTTP clients and support DNS lookups.

QtXml module: This module contains classes that implement SAX and DOM interfaces to Qt’s XML parser.

QtSvg module: This module contains classes for displaying the contents of SVG files.

QtOpenGL module: This module contains classes that enable the use of OpenGL in rendering 3D graphics in PyQt applications.

QtSql module: This module contains classes that integrate with SQL databases. It includes editable data models for database tables that can be used with GUI classes. It also includes an implementation of SQLite.

phonon module: This module contains classes that implement a cross-platform multimedia framework that enables the use of audio and video content in PyQt applications.

Qt module: This module consolidates the classes contained in all of the modules described above into a single module. This has the advantage that you don’t have to worry about which underlying module contains a particular class. It has the disadvantage that it loads the whole of the Qt framework, thereby increasing the memory footprint of an application. Whether you use this consolidated module, or the individual component modules is down to personal taste.

We will begin referencing to QtGui directly -thus not calling PyQt directly-. As it happens to many voyages also my journey into PyQt has not an established trail or end. This means we will implement features together and possibly refer to many of the modules later.

As I always say, stay tuned folks!





My PyQt Scribbles (Python and Qt) #0

13 11 2008

My intention is to write a series of posts discussing practical ways of working with PyQt. I’ve recently thought about the fact that blog posts can be a formidable way to note down things I may need to refer to someday in the future. This is a sorta rediscovering the meaning of a journal: an organic (or not) bunch of entries and annotations which, by growing in time, become a precious reference for the writer.

This thought of mine got stuck to another mental post-it I had about learning PyQt which eventually lead me to shape this column.

qt_logostrap_cmykQt is an application development framework mostly used to create graphical applications (and also non GUI apps). It’s created and maintained by Qt Software (formerly Trolltech), a Norwegian company acquired by Nokia since June 2007. Qt is know to be a cutting edge library designed to supply advanced features which can be implemented with flashy code and deployed on several platforms like Linux, Windows, Mac OS, Windows CE and Embedded Linux. Qt has been used to create well-known apps like KDE, GoogleEarth, VLC, Skype, Photoshop Elements, Qtopia, Virtual Box, etc…

PyQt is the Python binding of the very famous library Qt and it’s created and maintained by the British company Riverbank.

python-logo-master-v3-tmPython is a high level, interpreted, object oriented (even if it supports other types of programming paradigms), open source programming language. It’s a fairly young language which is getting more and more consensus among the developers  mainly thanks to some features which makes Python the most desirable choice for a wide range of tasks. Python focuses on simplicity and elegance; the syntax and semantics are minimalistic: something which lead to an increased productivity and a less painful code maintenance. Python gets rid of twisted structure and useless fripperies like brackets, thus enhancing readability. Despite its minimalism Python offers a huge standard library.

Beyond all this it’s worth saying that Python’s also my favourite programming language.

For sure Python hasn’t the power, in terms of CPU squeezing, of languages like C or C++, but unless you are developing the engine for a real time 3D graphics application, like a cutting edge video game, then my opinion is that for 90% of your needs Python is simply unbeatable.

Stay tuned on this channel to read the first installment of My Journey Into PyQt

Note on licensing:

The Open Source Editions of Qt is freely available for the development of Open Source software governed by the GNU General Public License (GPL). For more information…

PyQt v4 is available on all platforms under a variety of licenses including the GNU GPL (v2 and v3). If your use of PyQt is compatible with the GPL then you do not need to buy a commercial PyQt license. Similarly you do not need to buy a commercial Qt license. For more information…

Python is licensed under The Python Software Foundation License (PSFL) which is a BSD-style, permissive free software license compatible with the GNU General Public License (GPL). For more information…