Introduction

En ce moment, je consumme mes heures de loisirs dans un jeu vidéo qui a pratiquement 10 ans. Il s'agit d'Oblivion, quatrième volet de la saga des Elders Scrolls. Outre l'intérêt du jeu, j'ai choisi délibérément de l'utiliser uniquement sous Debian. D'ailleurs, je n'ai pas de multiboot sur ma machine principale. Mais en 2015, force est de constater que Wine, le lanceur natif d'applications MS-Windows pour GNU/Linux, s'est considérablement amélioré. Je peux donc utiliser Oblivion directement sous Debian via Wine. Voici quelques détails sur ma configuration.

Configuration de Wine

Il faut commencer avec une configuration correcte de wine, notamment au niveau des lecteurs utilisés. Wine ayant tendance à tout fourrer dans ~/.wine/dosdevices, j'ai vite créé un lien symbolique vers un emplacement dédié (dans /media/data/gamez/Wine/drive_c) pour ne pas exploser la taille de mon répertoire home. Par ailleurs, il faut indiquer que le lecteur CD sera disponible sur un point de montage correct (/media/cdrom0 pour ma part).

On peut modifier ces emplacements avec l'utilitaire winecfg, dans l'onglet Lecteurs.

Installation

La première chose à faire est de copier l'image ISO du DVD. Pour cela, une simple commande de dd suffit:

$ dd if=/dev/dvd of=/media/data/games/Oblivion/oblivion.iso bs=1M

Une fois l'image iso capturée, il reste à la monter (vous savez faire) et à effectuer l'installation via wine (via wine setup.exe). Et c'est tout !

Configuration d'Oblivion

Le jeu stocke les données de configuration et les sauvegardes dans le répertoire équivalent MS-Windows: C:\USERS\$USER\Mes Documents\My Games\Oblivion\. Il faut savoir que par défaut, Wine créé un lien symbolique de tout ce qui concerne les répertoires Ma Musique, Mes Documents, Mes Vidéos, etc. vers le répertoire home (le vrai, celui du vrai OS) de l'utilisateur. Pour ma part, cela abouti à placer My Games\Oblivion dans ~/My Games/Oblivion.

Par défaut, Oblivion affiche les crédits et une vidéo de lancement et ce, à chaque démarrage. La première fois, c'est intéressant mais au bout de trois fois, on en a facilement marre. La solution consiste à modifier le fichier INI du jeu pour éviter ces lancements. Il s'agit du fichier Oblivion.ini situé dans le répertoire sus-nommé. La variable SIntroSequence permet d'indiquer ce qui sera utilisé comme séquence d'introduction. Il suffit de ne rien mettre dans cette variable pour annuler la séquence d'introduction. C'est la même chose pour les variables SMainMenuMovie et SMainMenuMovieIntro.

SIntroSequence=
SMainMenuMovieIntro=
SMainMenuMovie=

Script de lancement

Pour les jeux qui fonctionnent sous Wine (et il y en a un paquet), je créé un script de lancement dédié que je place dans ~/bin. Cela permet de lancer le jeu en une seule commande. Par ailleurs, la logique de lancement est souvent la même:

  • Vérifier si l'image ISO du DVD est lancée.
  • La monter
  • Se placer dans le bon répertoire
  • lancer wine
  • à la fin du jeu, démonter l'image DVD.

Pour Oblivion, j'avais un autre problème à gérer: la configuration des touches. Par défaut, je devais toujours reconfigurer la touche 'Z' et la touche 'Q' pour respectivement avancer et aller à gauche. En fait, on peut facilement corriger ce problème en indiquant au serveur X qu'on va utiliser un clavier US (via setxkbmap).

Voici donc le script qui fait le travail de lancement de l'application:

#!/bin/bash

# Script de Lancement d'Oblivion sous Wine

## On monte le DVD
fusermount -u /media/cdrom0
fuseiso /media/data/gamez/Oblivion/oblivion.iso /media/cdrom0

## On met le clavier en US pour que le jeu fonctionne correctement
setxkbmap us

## On lance Oblivion avec Wine depuis le bon répertoire
cd /media/data/gamez/Wine/drive_c/Oblivion
wine ./Oblivion.exe

## on remet le clavier dans le bon format
setxkbmap fr latin9

## On démonte le DVD
fusermount -u /media/cdrom0
cd ~
exit 0

Conclusion

Il est plaisant de voir qu'en 2015, on peut jouer de manière fluide directement sous Wine à des jeux AAA d'il y a un peu moins de 10 ans. Par ailleurs, le jeu est vraiment très bon. J'en suis à plus de 70 heures et je suis loin d'être au bout. Je ne connaissais pas vraiment la série des Elders Scrolls mais le principe est assez intéressant: on incarne un personnage assez libre d'évoluer comme bon lui semble dans un monde assez élaboré. Les quêtes sont très nombreuses et très variées et on n'a pas vraiment le temps de s'ennuyer. Peut-être un bon tremplin vers Skyrim ?

Posted sam. 18 avril 2015 11:25:05

Introduction

Today I am going to inaugurate a new serie of articles about QGIS forms. QGIS is perhaps the best GIS software that ease the most attributes edition. You can build nearly complete GIS applications just by triggering some parameters on the QGIS fields dialog (and with a little bit of code, of course). I've made a whole application with QGIS forms. On the GIS side, it was not so hard: 8 layers to edit with simple geometries. But on the attributes side, it was a real challenge. Some facts about the database and the application:

  • About one hundred attributes tables stored on Oracle Database.
  • Some layers can have about 80 form controls.
  • n,n relations.
  • Specific tables to store tree data (more on this later).
  • Complete custom form controls.
  • We needed subforms to handle photos.
  • Of course you need a true search engine on all of the attribute fields.

Building such an application was not very easy even if you already know QGIS well. At the beginning of the project I was not really sure that QGIS could match the trick... ...But at last, I just want to say that QGis works perfectly well on this application even with a lot of data loaded.

When I started to implement features into this QGis project, I often faced QGIS lack of features on the forms. I've made some bug or features report on http://hub.qgis.org. In this serie of articles, I will try to show you how to circumvent the different problems I faced.

About QGIS forms

Before diving into code, let's have some basic informations about QGIS forms...

Forms are built by QGIS in order to edit (non-geometric) attributes for an object on a defined layer. You can read more about the process in the official QGis documentation.

You need to understand how forms are working in QGIS. To my mind, there are three parts on this subject:

  • Data fields: the goal of the form is to edit the values of the attributes in the layer. Data fields are named with the attributes name of the layer.
  • GUI: This is the part that QGIS show to the user to edit alphanumerical data. You can choose between three types: auto-generated (QGIS build everything, for basic forms), drag'n drop designed (you build the form using tabs and groupboxes inside QGIS) or customised (you need to use QtCreator to build a .ui file that will be shown by QGIS). To make link between form controls and fields values, there is a trivial rule: form controls are named like the field they represent.
  • Python code: QGIS allows you to add Python logic to the GUI part of the form. Whenever the form opens, you can call a Python function. This function can access to GUI objects using Qt functions but you also have the full power of QGIS Python API.

For Data fields definition, you have nothing to do: QGIS will use the layer definition to name attributes. For GUI, you have to configure (per-layer) the method (auto-generated/drag'n drop/custom) on the Fields tab of the layer properties dialog. You can read this introduction article to have further details...

For Python code, you just have to name the Python file (module) that will be used and the function that will be launched. Read this reference article to dig a little bit more.

Show a clickable URL on the form

Introduction

In QGIS forms, you can specify that a field will store a path to a file. You just have to use "File Name" for the Edit Widget. There is no option for this type of widget:

Fields dialog with File Name Edit Widget for DOCUMENT field

When you create a new feature on the layer, you will see the following control:

Choose a file dialog

Push the "..." button and you open a file choosing dialog (like every Qt file dialog). When you have chosen the file, the path (/home/medspx/clint_eastwood.ods) is stored in the line edit (a QLineEdit) right to the field name (DOCUMENT). But what if you want to open the file and not just store the path ? You have to trick QGIS a little bit.

Build a custom form like this

First of all you have to use custom form. Otherwise, QGis is using a QLineEdit to show the file path. We need to use a QLabel as only QLabel are able to show a URL link. In your .ui file just add the following to your layout:

QtCreator UI file for URL

As you can see, we have a QLabel to name the field ("Document"). Then you have another QLabel which contains "No file selected". Name this QLabel with a dedicated name (the objectName property will be DOCUMENT_URL). This QLabel will make the job of link URL presentation.Be careful to have a field which is named like the objectName otherwise, QGIS will not save the data into the layer.

You have to check two properties for this QLabel:

  • openExternalLinks: checked (otherwise, you will not be able to open the file with a click).
  • textInteractionFlags: LinksAccessibleByMouse.

QLabel properties

Then we have the QPushButton which will be named DOCUMENT_URL_B. And that's all for the ui part. Now, it is time to dive into Python...

First, the code:

def manageURL(dialog, layerid=None, featureid=None):
    '''General function to manage URL in subforms'''
    # Manage URL buttons
    for child in [f for f in dialog.findChildren(QPushButton) if u"URL_B" in f.objectName()]:
        try:
            child.clicked.disconnect()
        except:
            pass
        child.clicked.connect(partial(chooseFile, dialog, child.objectName()[:-2]))

    # Make URL fields always clickable (even in non-edit mode)
    for child in [f for f in dialog.findChildren(QLabel) if f.objectName()[-3:] == u"URL"]:
        child.setEnabled(True)

def chooseFile(dialog, field):
    '''Open a dedicated file picker form'''
    lineEdit = dialog.findChild(QLabel, field)
    if lineEdit is None:
        QgsMessageLog.logMessage(u"chooseFile: There is no QLabel for field {0} !".format(field), "DBPAT", QgsMessageLog.INFO)

    filename = QtGui.QFileDialog.getOpenFileName(dialog, u'Choisir un fichier...')
    if filename is not None:
        # parse the result to make a link
        basename = os.path.basename(filename)
        link = u"<a href=\"file:///{0}\">{1}</a>".format(filename,basename)
        lineEdit.setText(link)

When you want to display links, you just have to use manageURL function to do the job.

This function will try to find all of the QPushButton that are named like "SOMETHING_URL_B". Those buttons will be disconnected from their previous "clicked" signal. Then, we will re-connect the "clicked" signal to our "chooseFile" function. This function will do the job of building the link from the file path. manageURL ends with another trick: if you are not in edit mode, QLabel with links are not clickable. So we have to manually enable every QLabel that are links (ends with "URL" in our case).

chooseFile function opens a file chooser dialog (a standard QFileDialog from Qt). If the file path is valid, we extract the filename (the basename, only the last part of the path) from the file path. And then we build a link which is a simple <a>. At last, we update the QLabel with the link content.

Here is a rendered thing of the link:

URL link to open the file

You can click on the link and it will open the file with the default operating system defined viewer.

Conclusion

This first article just demonstrate that QGIS is so wide open to external code that you can do nearly everything you want with the forms. Embedding PyQt4 and giving access to the full QGIS API from Python is very interesting because you can go further than standard QGIS forms without recompiling QGIS.

As you can see, once you have written a dedicated function to handle URL links for File controls, it is very easy to expand it to any layer you want: just correctly name a form control on your custom form file and add the correct path to the function.

In the following article, we will focus on Photo edit widget and we will try to use it in a custom form with a little bit of code as an extra...

Posted dim. 19 avril 2015 15:59:55