IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre à utiliser le module Python PyGTK 2.0


précédentsommairesuivant

XXII. Chapitre 22. Le glisser-déposer (DND)

PyGTK possède un ensemble de fonctions de haut niveau de communication inter-processus via le système glisser-déposer. PyGTK peut utiliser le glisser-déposer par-dessus les protocoles de bas niveau de Xdnd et le glisser-déposer Motif.

XXII-A. Aperçu du DND

Une application capable du glisser-déposer doit d'abord définir et configurer le(s) widget(s) pour le glisser-déposer. Chacun des widgets peut être l'origine et/ou la destination du glisser-déposer. Il faut noter que ces widgets doivent posséder une fenêtre X associée.

Les widgets origine peuvent exporter des données du glisser, en permettant ainsi à l'utilisateur de tirer des objets à partir d'elles, pendant que les widgets de destination peuvent recevoir des données du glisser. Les destinations du glisser-déposer peuvent limiter de qui ils acceptent des données du glisser, par exemple la même application ou n'importe quelle application (y compris eux-mêmes).

Envoyer et recevoir des données du déposer utilisent des signaux. Déposer un élément sur un widget de destination exige à la fois une demande de données (pour le widget origine) et un gestionnaire de signal de réception de données (pour le widget destination). Des gestionnaires de signaux supplémentaires peuvent être connectés si on souhaite savoir quand un glisser commence (à l'instant même du début), jusqu'à la réalisation du déposer, et quand la procédure glisser-déposer entière s'est terminée (avec succès ou pas).

Votre application aura besoin de fournir des données aux widgets origine quand ce sera demandé, ce qui implique d'avoir un gestionnaire de signal de requête de données du glisser. Pour les widgets de destination, ils auront besoin d'un gestionnaire de signal de données du déposer reçues.

Un cycle de glisser-déposer typique se présentera ainsi :

  • le glisser commence. Le widget origine peut recevoir le signal "drag-begin". Il peut configurer une icône de glisser, etc. ;
  • le glisser se déplace au-dessus d'une zone de déposer. Le widget destination peut recevoir le signal "drag-motion" ;
  • le déposer est réalisé. Le widget destination peut recevoir le signal "drag-drop". Il doit demander les données de l'origine ;
  • requête des données du glisser (quand le déposer a lieu). Le widget origine peut recevoir le signal "drag-data-get" ;
  • données du déposer reçues (de la même application ou d'une autre). Le widget destination peut recevoir le signal "drag-data-received" ;
  • données du glisser supprimées (si le glisser était un déplacement). Le widget origine peut recevoir le signal "drag-data-delete". ;
  • procédure glisser-déposer effectuée. Le widget origine peut recevoir le signal "drag-end".

Il existe quelques étapes mineures qui peuvent s'intercaler ici où là, mais nous entrerons dans les détails plus tard.

XXII-B. Propriétés du glisser-déposer

Les données du glisser ont les propriétés suivantes :

  • type d'action de glisser (par exemple : ACTION_COPY, ACTION_MOVE) ;
  • type de glisser-déposer spécifique d'un client (un nom et une paire de nombres) ;
  • type de format des données envoyées et reçues.

Les actions de glisser sont relativement évidentes, elles indiquent si le widget peut effectuer le glisser avec l'action ou les actions indiquées, par exemple gtk.gdk.ACTION_COPY et/ou gtk.gdk.ACTION_MOVE. Un gtk.gdk.ACTION_COPY serait un glisser-déposer typique sans que les données de la source soient supprimées, alors qu'un gtk.gdk.ACTION_MOVE serait juste comme un gtk.gdk.ACTION_COPY,mais avec une "suggestion" de supprimer les données de la source après l'appel du gestionnaire de signal de réception. Il existe d'autres actions de glisser, comme gtk.gdk.ACTION_LINK, qu'il sera possible d'examiner quand on sera plus avancé dans la connaissance du glisser-déposer.

Le type de glisser-déposer indiqué pour le client est beaucoup plus flexible, parce que c'est l'application qui le définira et le vérifiera spécifiquement. Il sera nécessaire de définir les widgets de destination pour recevoir certains types de glisser-déposer en indiquant un nom et/ou un nombre. Il serait plus sûr d'utiliser un nom puisqu'une autre application peut juste utiliser le même nombre dans un sens entièrement différent.

Les types de formats de données envoyées et reçues (selection target : cible de sélection) entrent en jeu uniquement dans leurs fonctions de gestion des données réclamées et reçues. Le terme selection target (cible de sélection) induit en erreur. C'est un terme qui vient de la sélection GTK+ (couper/copier et coller). Ce que recouvre cible de sélection est le type de format des données (par exemple un gtk.gdk.Atom, un entier ou une chaine de caractères) qui est envoyé ou reçu. La fonction de gestion des données demandées exige de préciser le type (cible de sélection) des données qui sont envoyées et le gestionnaire des données reçues doit pouvoir gérer le type cible de sélection des données reçues.

XXII-C. Méthodes du glisser-déposer

XXII-C-1. Configurer le widget origine

La méthode drag_source_set() indique un ensemble de types de cibles pour une opération de glisser sur un widget.

 
Sélectionnez
  widget.drag_source_set(start_button_mask, targets, actions)

Les paramètres ont la signification suivante :

  • le paramètre widget indique le widget origine du glisser ;
  • le paramètre start_button_mask indique un masque de bits des boutons qui peuvent débuter le glisser (par exemple BUTTON1_MASK) ;
  • le paramètre targets indique une liste de types de données cibles gérées par le glisser ;
  • le paramètre actions indique un masque de bits des actions possibles pour un glisser depuis cette fenêtre.

Le paramètre targets est une liste de tuples semblable à :

 
Sélectionnez
  (target, flags, info)

target est une chaine de caractères représentant le type de glisser.

flags limite la portée du glisser. Il peut prendre la valeur 0 (pas de limitation de la portée) ou la valeur des constantes suivantes :

 
Sélectionnez
1.
2.
3.
4.
5.
  gtk.TARGET_SAME_APP    # La destination peut être sélectionnée pour un glisser à 
                         # l'intérieur de la même application

  gtk.TARGET_SAME_WIDGET # La destination peut être sélectionnée pour un glisser à 
                         # l'intérieur du même widget

info est un identifiant entier assigné à l'application.

S'il n'est plus nécessaire qu'un widget continue d'être l'origine d'une opération de glisser-déposer, la méthode drag_source_unset() peut être utilisée pour supprimer un ensemble de types de glisser-déposer.

 
Sélectionnez
  widget.drag_source_unset()

XXII-C-2. Les signaux du widget origine

Les signaux suivants sont envoyés au widget origine pendant une opération de glisser-déposer :

Tableau 22.1. Signaux du widget origine

drag_begin (début du glisser)

def drag_begin_cb(widget, drag_context, data):

drag_data_get (obtention des données du glisser)

def drag_data_get_cb(widget, drag_context, selection_data, info, time, data):

drag_data_delete (suppression des données du glisser)

def drag_data_delete_cb(widget, drag_context, data):

drag_end (fin du glisser)

def drag_end_cb(widget, drag_context, data):

Le gestionnaire du signal "drag-begin" peut être utilisé pour définir des conditions de départ telle que l'icône du glisser en utilisant une des méthodes suivantes de la classe Widget : drag_source_set_icon(), drag_source_set_icon_pixbuf(), drag_source_set_icon_stock(). Le gestionnaire du signal "drag-end" peut servir à annuler les actions du gestionnaire du signal "drag-begin".

Le gestionnaire du signal "drag-data-get" devrait renvoyer les données de glisser correspondantes à la destination indiquée par le paramètre info. Il fournit à gtk.gdk.SelectionData les données du glisser.

Le gestionnaire du signal "drag-delete" est utilisé pour supprimer les données du glisser d'une action gtk.gdk.ACTION_MOVE une fois la copie des données effectuée.

XXII-C-3. Configurer un widget destination

La méthode drag_dest_set() indique que ce widget peut recevoir le déposer et précise les types de déposer qu'il peut accepter.

La méthode drag_dest_unset() indique que ce widget ne peut plus maintenant recevoir de déposer.

 
Sélectionnez
1.
2.
3.
  widget.drag_dest_set(flags, targets, actions)

  widget.drag_dest_unset()

où le paramètre flags indique quelles actions GTK+ doit réaliser pour le widget pour y effectuer le dépôt. Voici les valeurs possibles pour flags :

gtk.DEST_DEFAULT_MOTION

Si activé pour un widget, GTK+ vérifiera, pendant le survol d'un glisser au-dessus de ce widget, si le glisser appartient à la liste des cibles et actions possibles de ce widget. Alors, GTK+ appellera la méthode drag_status() appropriée.

gtk.DEST_DEFAULT_HIGHLIGHT

Si activé pour un widget, GTK+ mettra en évidence ce widget pendant la durée du survol par le glisser, si l'action et le format du widget sont compatibles.

gtk.DEST_DEFAULT_DROP

Si activé pour un widget, au moment du déposer, GTK+ vérifiera si le glisser concorde avec la liste des cibles et action possibles de ce widget ; si oui, GTK+ appellera la méthode drag_get_data() pour le widget. Que le déposer soit réussi ou non, GTK+ appellera la méthode drag_finish(). Si l'action était un déplacement et que le glisser est réussi, le valeur TRUE sera attribuée au paramètre delete de la méthode drag_finish().

gtk.DEST_DEFAULT_ALL

Si activé, indique que toutes les actions précédentes doivent être réalisées.

Le paramètre targets est une liste de tuples d'information de cibles décrites précédemment.

Le paramètre actions est un masque de bits des actions possibles à réaliser lors d'un glisser sur ce widget. Les valeurs possibles, combinables avec l'opérateur OR, sont les suivantes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
  gtk.gdk.ACTION_DEFAULT
  gtk.gdk.ACTION_COPY
  gtk.gdk.ACTION_MOVE
  gtk.gdk.ACTION_LINK
  gtk.gdk.ACTION_PRIVATE
  gtk.gdk.ACTION_ASK

Les paramètres targets et actions sont ignorés si le paramètre flags ne contient pas gtk.DEST_DEFAULT_MOTION ou gtk.DEST_DEFAULT_DROP. Dans ce cas, l'application doit gérer les signaux "drag-motion" et "drag-drop".

Le gestionnaire du signal "drag-motion" doit déterminer si les données du glisser sont appropriées en comparant les cibles de destination avec les cibles gtk.gdk.DragContext. Il peut aussi étudier les données du glisser en appelant la méthode drag_get_data(). Il faut utiliser la méthode gtk.gdk.DragContext.drag_status() pour mettre à jour le statut du paramètre drag_context.

Le gestionnaire du signal "drag-drop" doit déterminer la cible qui correspond en utilisant la méthode drag_dest_find_target() du widget et ensuite réclamer les données du glisser en utilisant la méthode drag_get_data(). Les données seront disponibles dans le gestionnaire du signal "drag-data-received".

Le programme dragtargets.py affiche les cibles possibles d'une opération de glisser dans un widget étiquette.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
#!/usr/local/env python

import pygtk
pygtk.require('2.0')
import gtk

def motion_cb(wid, context, x, y, time):
    context.drag_status(gtk.gdk.ACTION_COPY, time)
    return True

def drop_cb(wid, context, x, y, time):
    l.set_text('\n'.join([str(t) for t in context.targets]))
    context.finish(True, False, time)
    return True

w = gtk.Window()
w.set_size_request(200, 150)
w.drag_dest_set(0, [], 0)
w.connect('drag_motion', motion_cb)
w.connect('drag_drop', drop_cb)
w.connect('destroy', lambda w: gtk.main_quit())
l = gtk.Label()
w.add(l)
w.show_all()

gtk.main()

Le programme crée une fenêtre et ensuite l'utilise comme destination d'un glisser sans cible, ni action en fixant les drapeaux à zéro. Les gestionnaires motion_cb() et drop_cb() sont connectés respectivement aux signaux "drag-motion" et "drag-drop". Le gestionnaire motion_cb() se contente de configurer l'état du glisser pour permettre un déposer. Le gestionnaire drop_cb() modifie le texte de l'étiquette pour indiquer les cibles du glisser et termine le déposer en laissant intacte la source des données.

XXII-C-4. Les signaux du widget destination

Pendant une opération de glisser-déposer, les signaux suivants sont transmis au widget destination.

Tableau 22.2. Signaux du widget destination

drag_motion (mouvement du glisser)

def drag_motion_cb(widget, drag_context, x, y, time, data):

drag_drop (glisser-déposer)

def drag_drop_cb(widget, drag_context, x, y, time, data):

drag_data_received (données reçues)

def drag_data_received_cb(widget, drag_context, x, y, selection_data, info, time, data):

Le programme dragndrop.py illustre l'utilisation du glisser-déposer dans une application. Un bouton avec une image (gtkxpm.py) constitue la source du glisser, il fournit en même temps un texte et une donnée xpm. Un widget layout constituera la destination du déposer du xpm, un bouton, la destination du texte. La Figure 22.1, « Exemple de glisser-déposer » montre le programme après le déposer du xpm et du texte, respectivement sur le layout et sur le bouton.

Image non disponible
Figure 22.1. Exemple de glisser-déposer

Voici le code de dragndrop.py :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # exemple dragndrop.py
    
    import pygtk
    pygtk.require('2.0')
    import gtk
    import string, time
    
    import gtkxpm
    
    class ExempleDragNDrop:
        HEIGHT = 600
        WIDTH = 600
        TARGET_TYPE_TEXT = 80
        TARGET_TYPE_PIXMAP = 81
        fromImage = [ ( "text/plain", 0, TARGET_TYPE_TEXT ),
                  ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
        toButton = [ ( "text/plain", 0, TARGET_TYPE_TEXT ) ]
        toCanvas = [ ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
    
        def layout_resize(self, widget, event):
            x, y, width, height = widget.get_allocation()
            if width > self.lwidth or height > self.lheight:
                self.lwidth = max(width, self.lwidth)
                self.lheight = max(height, self.lheight)
                widget.set_size(self.lwidth, self.lheight)
    
        def makeLayout(self):
            self.lwidth = self.WIDTH
            self.lheight = self.HEIGHT
            box = gtk.VBox(False,0)
            box.show()
            table = gtk.Table(2, 2, False)
            table.show()
            box.pack_start(table, True, True, 0)
            layout = gtk.Layout()
            self.layout = layout
            layout.set_size(self.lwidth, self.lheight)
            layout.connect("size-allocate", self.layout_resize)
            layout.show()
            table.attach(layout, 0, 1, 0, 1, gtk.FILL|gtk.EXPAND,
                         gtk.FILL|gtk.EXPAND, 0, 0)
            # créer les barres de défilement et les placer dans une table
            vScrollbar = gtk.VScrollbar(None)
            vScrollbar.show()
            table.attach(vScrollbar, 1, 2, 0, 1, gtk.FILL|gtk.SHRINK,
                         gtk.FILL|gtk.SHRINK, 0, 0)
            hScrollbar = gtk.HScrollbar(None)
            hScrollbar.show()
            table.attach(hScrollbar, 0, 1, 1, 2, gtk.FILL|gtk.SHRINK,
                         gtk.FILL|gtk.SHRINK,
                         0, 0)    
            # utiliser les ajustements du layout par les barres de défilement
            vAdjust = layout.get_vadjustment()
            vScrollbar.set_adjustment(vAdjust)
            hAdjust = layout.get_hadjustment()
            hScrollbar.set_adjustment(hAdjust)
            layout.connect("drag_data_received", self.receiveCallback)
            layout.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
                                      gtk.DEST_DEFAULT_HIGHLIGHT |
                                      gtk.DEST_DEFAULT_DROP,
                                      self.toCanvas, gtk.gdk.ACTION_COPY)
            self.addImage(gtkxpm.gtk_xpm, 0, 0)
            button = gtk.Button("Text Target")
            button.show()
            button.connect("drag_data_received", self.receiveCallback)
            button.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
                                 gtk.DEST_DEFAULT_HIGHLIGHT |
                                 gtk.DEST_DEFAULT_DROP,
                                 self.toButton, gtk.gdk.ACTION_COPY)
            box.pack_start(button, False, False, 0)
            return box
    
        def addImage(self, xpm, xd, yd):
            hadj = self.layout.get_hadjustment()
            vadj = self.layout.get_vadjustment()
            style = self.window.get_style()
            pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
                self.window.window, style.bg[gtk.STATE_NORMAL], xpm)
            image = gtk.Image()
            image.set_from_pixmap(pixmap, mask)
            button = gtk.Button()
            button.add(image)
            button.connect("drag_data_get", self.sendCallback)
            button.drag_source_set(gtk.gdk.BUTTON1_MASK, self.fromImage,
                                   gtk.gdk.ACTION_COPY)
            button.show_all()
            # s'adapter au défilement du layout - la localisation de l'événement
            # est relative à la zone visible et non à la taille du layout
            self.layout.put(button, int(xd+hadj.value), int(yd+vadj.value))
            return
    
        def sendCallback(self, widget, context, selection, targetType, eventTime):
            if targetType == self.TARGET_TYPE_TEXT:
                now = time.time()
                str = time.ctime(now)
                selection.set(selection.target, 8, str)
            elif targetType == self.TARGET_TYPE_PIXMAP:
            selection.set(selection.target, 8,
                          string.join(gtkxpm.gtk_xpm, '\n'))

    def receiveCallback(self, widget, context, x, y, selection, targetType,
                        time):
        if targetType == self.TARGET_TYPE_TEXT:
            label = widget.get_children()[0]
            label.set_text(selection.data)
        elif targetType == self.TARGET_TYPE_PIXMAP:
            self.addImage(string.split(selection.data, '\n'), x, y)

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
          self.window.set_default_size(300, 300)
        self.window.connect("destroy", lambda w: gtk.main_quit())
        self.window.show()
        layout = self.makeLayout()
        self.window.add(layout)

def main():
    gtk.main()

if __name__ == "__main__":
    ExempleDragNDrop()
    main()

précédentsommairesuivant

Copyright © 2005 John Finlay. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.