Bonjour à tous,
Même si, en programmation, je me juge « amateur éclairé » (du latin amator, « celui qui aime »), je suis épouvantablement néophyte en Python. Mais je sais une chose : pour apprendre à coder, il faut … coder. Comme on forge pour devenir forgeron ; ou comme on menuise pour devenir menuisier.
Et accepter de se planter ; au départ, de se trouver sous-doué, ne pas viser à apprendre à conduire avec une Lamborghini, parce que caler avec une 2CV, c’est mieux. Rouler beaucoup en 2CV, à petite vitesse. Ces voyages-là font plus que « former la jeunesse » ; ils font comprendre la dynamique de la bête, beaucoup plus en profondeur que ce que ne permettent que mal les mécaniques trop complexes ou trop puissantes. Dans cette optique, s’attacher à NE PAS faire appel, ne serait-ce qu’une seule fois à ce qui se prétend intelligent ou artificiel (alors les deux ensemble, on oublie), c’est s’accorder à soi-même une confiance, un temps d’apprentissage, dont on sait déjà bien à quel point ce sera, in fine, beaucoup plus efficace et satisfaisant que si l’on était simplement contenté d’un survol consumériste, de ceux dont se profile chaque jour davantage la nécessité de la diminution. J’ai bien conscience d’être beaucoup plus pétri de réflexes « procéduraux » qu’« objets » ; je fais avec, pour l’instant. Par contre, même si l’on va ― forcément ― piocher à droite et à gauche, il y a une obligation à comprendre toutes les lignes de code que l’on prétend produire ; sauf à se persuader que ce soit une bonne chose de ne pas, soi-même, valoir plus qu’une machine ou un tas de ferraille, celui-là-même dont on est persuadé d’avoir pris le contrôle.
Suite à ma participation à ce fil, aussi épique qu’hippique, je me suis dit, au pesé du peu d’exemples disponibles en ligne en général et sur ce forum en particulier (à l’exception notable du fil créé, il y a plus de dix ans, par @ledudulela), de reprendre l’exemple évoqué dans ce fil qui pique, pour faire, non pas un script Bash, mais un script Python, puisqu’aussi bien il me semble que la syntaxe du constricteur, dans son ensemble, est quand même nettement moins étouffante (surprenant non ?)…
Comprendre les subtilités du passage des variables, depuis Nemo, en fonction du langage qui les reçoit demande un certain temps et moult attentions, à fortiori pour quelqu’un qui, comme moi, n’a que peu d’expérience de ces choses… Les fichiers à l’extension .nemo_action sont d’une susceptibilité crasse au regard de la casse ; ne pas y prendre rigoureusement garde, c’est faire caler la 2CV, déjà. Le présent fil ne pose donc aucune question, mais n’est seulement écrit que comme un témoignage de quelqu’un qui s’attache à aimer.
Par exemple, au cours de l’élaboration de mon script Python, le Shebang que j’y avais posé était :
Code : Tout sélectionner
#!/usr/bin/env pythonCode : Tout sélectionner
#!/usr/bin/python3Code : Tout sélectionner
Terminal=trueParce que c’est, pour moi, plus un script de démonstration et d’apprentissage, évidemment très souvent écrit à partir de choses glanées sur le wouaibe, le script est abondamment commenté. J’ai porté une attention particulière à nommer les variables avec une consonance française, parce que c’est un des rares points où l’on peut se servir de cette spécificité pour faire la différence entre ce qui relève de la stricte nativité du langage (ou des bibliothèques), de ce qui est établi par le scripteur. Il est tout à fait acquis que le fait de faire précéder les noms de variables par des déterminants possessifs (mon, ma, mes) est aussi lourdingue que verbeux. Toutefois, dans un contexte d’apprentissage du code, pour moi, ce n’est juste pas une option. Ainsi, lire
Code : Tout sélectionner
maFenetre = Tk()
monURL = Entry(width=60)
monEtiquette_01 = Label(text="Fichier : ")Code : Tout sélectionner
tk=Tk()
entry=Entry(width=60)
label=Label(text="Fichier : ")Évidemment, il ne faut pas attendre de la bibliothèque graphique Tkinter, livrée avec Python, plus que ce qu’elle est capable de faire : à savoir faire des choses basiques. On est (très) loin de ce que peut offrir GTK, voire (surtout) Qt. C’est, par contre, un très bon tremplin pour défricher les bases et il se trouve déjà là un nombre conséquent de contrôles qui, même si leur look frise l’antique, restent parfaitement fonctionnels. Il se trouve même un certain plaisir à faire vivre, encore, ces monuments historiques qui n’ont pas besoin de paraître plus que l’efficience décroissante qu’ils servent…
Même s’il m’est arrivé d’exprimer ailleurs ce principe, je reste toujours aussi émerveillé de voir à quel point il est facile de faire fruit de ce que propose l’UTF-8, tout de même à pas cher en matière de ressources. Les touches fléchées de la boite de dialogue sont ici données à titre d’exemple : je n’utilise pas de fichiers graphiques, mais évidemment bien de simples caractères. Cela présente évidemment le risque que cela s’affiche un peu différemment selon les configurations ; mais dans l’ensemble, c’est, là encore, bougrement efficace.
Le but de ce code est, aussi, de voir à quel point il est facile de récupérer la liste des fichiers sélectionnés pour, par exemple, en donner la possibilité de l’ordonnancement dans une liste déroulante. Ce truc-là est récupérable dans un nombre faramineux de contextes, et en Python, s’avère quand même très facile à mettre en place !
Le simple fait de laisser à l’utilisateur la possibilité de saisir l’URL du fichier de sortie en toutes lettres, autant manuellement, qu’avec la boite de dialogue, complexifie notablement la gestion Python. Mais, ici comme dans le cas de la gestion de la liste déroulante, l’exercice en vaut la peine parce qu’il est évidemment récupérable ailleurs, dans d’autres situations et pour d’autres usages.
Cette expérience m’aura aussi appris que j’ai encore beaucoup à apprendre sur la simple mise en place et disposition des contrôles d’une boite de dialogue TKinter. Parce que ça ne doit pas être insurmontable de faire mieux… Monter une boite de dialogue utilisée par une macro de LibreOffice est l’enfance de l’art ; tout se fait à la souris de manière graphique dans l’éditeur dédié. Là, c’est autre chose : on pourrait croire que c’est aussi simple que de monter un tableau HTML, mais en fait pas tant que ça…
L’installation.
Deux fichiers ; deux destinations :
- Créer le fichier
~/.local/share/nemo/actions/assembleImages.nemo_action
et le remplir du code suivant :Code : Tout sélectionner
[Nemo Action] Active=true Terminal=false Icon-Name=face-smile Name=Assembler les images sélectionnées Comment=Assembler, en un seul fichier graphique, les images actuellement sélectionnées. # ATTENTION : l'ordre de l'énoncé « %P %F » est ici IMPORTANT pour la récupération des données par Python. Ne pas mettre « %F %P ». Exec=<assembleImgSel/assembleImgSel.py %P %F> Selection=m Extensions=jpg;jpeg;png;bmp;gif;webp; Dependencies=python3;convert - Créer le répertoire
~/.local/share/nemo/actions/assembleImgSel - Créer le fichier
~/.local/share/nemo/actions/assembleImgSel/assembleImgSel.py
Le rendre exécutable :et le remplir du code suivant :Code : Tout sélectionner
chmod +x ~/.local/share/nemo/actions/assembleImgSel/assembleImgSel.pyCode : Tout sélectionner
#!/usr/bin/python3 # -*- coding: utf-8 -*- import os, sys from tkinter import * from tkinter import filedialog from pathlib import Path monNomFichierGenerique = "assemblage.png" monFichierIcone = Path("/usr/share/icons/gnome/48x48/emblems/emblem-photos.png") # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Fonction de centrage de la fenêtre sur l'écran principal. ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ def centrerAffichageFenetre(ecran : Tk, largeur : int, hauteur: int): largeurEcran = ecran.winfo_screenwidth() ; hauteurEcran = ecran.winfo_screenheight() x = int((largeurEcran / 2) - (largeur / 2)) ; y = int((hauteurEcran / 2) - (hauteur / 1.5)) return f"{largeur}x{hauteur}+{x}+{y}" # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Appel de la boite de dialogue de saisie du fichier à écrire. ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ def saisieMonFichierFinal() : # Récupérer le texte saisi dans le champ « monURL ». monCheminSaisi = monURL.get() ; monChemin = "" if Path(monCheminSaisi).is_file() or Path(monCheminSaisi).parent.is_dir() : monChemin = Path(monCheminSaisi).parent if Path(monCheminSaisi).is_dir() : monChemin = monCheminSaisi if monChemin == "" : monChemin = Path.home() # Attribution du nom du fichier initial. if Path(monCheminSaisi).is_dir() : monFichierInitial = monNomFichierGenerique else : monFichierInitial = Path(monCheminSaisi).name monFichierFinal = filedialog.asksaveasfilename( title = "Enregistrer sous…", filetypes = (("PNG", "*.png"), ("JpeG", "*.jpg"), ("BMP", "*.bmp"), ("TIFF", "*.tif"), ("Tous les fichiers", "*.*")), initialdir = monChemin, initialfile = monFichierInitial ) # Validation effective si la saisie est correcte. if monFichierFinal : monURL.delete(0, "end") ; monURL.insert(0, monFichierFinal) # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Monter et descendre un choix sélectionner dans la liste déroulante. ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ def maGestionBoutonsMonterDescendre(monEvenement) : maSelection = maListeFichiers.curselection() if maSelection : monIndex = maSelection[0] + 1 # Base de l'index : 1. monTotal = maListeFichiers.index("end") monBoutonMonter ["state"] = "normal" if monIndex > 1 else "disabled" monBoutonDescendre ["state"] = "normal" if monIndex < monTotal else "disabled" else : monBoutonMonter["state"] = "disabled" ; monBoutonDescendre["state"] = "disabled" def monterDescendre(monSens) : maSelection = maListeFichiers.curselection() if maSelection : monIndex = maSelection[0] # Base de l'index : 0. monTotal = maListeFichiers.index("end") - 1 monChoixActif = maListeFichiers.get(monIndex) maListeFichiers.delete(monIndex) # Monter [▲] et Descendre [▼]. maListeFichiers.insert(monIndex + monSens , monChoixActif) maListeFichiers.selection_set(first = monIndex + monSens) ; maListeFichiers.activate(monIndex + monSens) monIndex += monSens # Car, qui peut le plus, peut le moins. # Gestion de la disponibilité des boutons « Monter [▲] » et « Descendre [▼] ». monBoutonMonter ["state"] = "normal" if monIndex > 0 else "disabled" monBoutonDescendre ["state"] = "normal" if monIndex < monTotal else "disabled" # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Assemblage effectif des image avec le logiciel externe « ImageMagick ». ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ def assemblerImages() : # La forme de la commande sera « convert +append "*.jpg" "sortie.jpg" », avec des chemins absolus et sans caractères génériques. maCommande = f"convert {monOrientation.get()}append " # Avec « join », assemble en une seule chaine et, pour chaque fichier, rajoute « monDossierActif » (plus un slash) en encadrant le chemin total de guillemets. mesFichiersEnOrdre = f"\"{monDossierActif}/" + f"\" \"{monDossierActif}/".join(maListeFichiers.get(0, "end")) + "\"" # Rajout à « maCommande », non seulement « mesFichiersEnOrdre », mais aussi l'URL de la destination. maCommande += f"{mesFichiersEnOrdre} \"{monFichierFinalSaisi.get()}\"" # Appel du Shell. os.system(maCommande) # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Récupération des données passées en arguments via le clic droit. ║█ # ║ Il est IMPÉRATIF que le fichier appelant ait bien respecté l'ordre de l'énoncé « %P %F ». ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ mesArguments = [] monAppel = sys.argv[0] # Le nom-même du présent fichier ; inutile sans le cas présent. for monArgument in sys.argv : if monArgument == monAppel : continue else: mesArguments.append(monArgument) # Supprime mesArguments[0] car il est passé dans la variable « monDossierActif » et devenait gênant lors du remplissage de la Listbox « maListeFichiers ». monDossierActif = mesArguments[0] ; mesArguments.pop(0) monFichierFinal = Path(monDossierActif, monNomFichierGenerique).as_posix() # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗ # ║ Montage de la boite de dialogue avec « TKinter ». ║█ # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ # ╔════════════════════════════════════════════════════════════════════════════════════╗ # ║ Assemblage d'images. ║█ # ╠════════════════════════════════════════════════════════════════════════════════════╣█ # ║ ┌──────────────────────────────────────────────────────────────── monCadre_01 ─┐ ║█ # ║ │ Fichier de sortie : │ ║█ # ║ ├─────────────────────────────────────────────────────────┬────────────────────┤ ║█ # ║ │ ┌───────────────────────────────────────────────────┐ │ ┌──────────────┐ │ ║█ # ║ │ │ Saisie de l'URL. │ │ │ Choisir... │ │ ║█ # ║ │ └───────────────────────────────────────────────────┘ │ └──────────────┘ │ ║█ # ║ └─────────────────────────────────────────────────────────┴────────────────────┘ ║█ # ║ ┌──────────────────────────────────────────────────────────────── monCadre_02 ─┐ ║█ # ║ │ Dossier actif et fichiers sélectionnés : │ ║█ # ║ ├──────────────────────────────────────────────────────────────────────────────┤ ║█ # ║ │ URL des fichiers sélectionnés. │ ║█ # ║ ├──────────────────────────┬─────────┬─────────────────────────────────────────┤ ║█ # ║ │ │ ┌───┐ │ [0] Vertical │ ║█ # ║ │ Liste déroulante │ │ ▲ │ ├─────────────────────────────────────────┤ ║█ # ║ │ des fichiers │ └───┘ │ [O] Horizontal │ ║█ # ║ │ des fichiers ├─────────┼────────────────────┬────────────────────┤ ║█ # ║ │ graphiques │ ┌───┐ │ ┌──────────────┐ │ ┌──────────────┐ │ ║█ # ║ │ sélectionnés. │ │ ▼ │ │ │ Assembler │ │ │ Quitter │ │ ║█ # ║ │ │ └───┘ │ └──────────────┘ │ └──────────────┘ │ ║█ # ║ └──────────────────────────┴─────────┴────────────────────┴────────────────────┘ ║█ # ╚════════════════════════════════════════════════════════════════════════════════════╝█ # ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ # Création de la boite de dialogue principale. maFenetre = Tk() maFenetre.title("Assemblage d'images") maFenetre.geometry(centrerAffichageFenetre(maFenetre, 850, 410)) # Icône de la boite de dialogue SI le fichier est présent. if monFichierIcone.exists() : maFenetre.iconphoto(True, PhotoImage(file = monFichierIcone)) # Polices de caractères personnalisées. maPoliceDeCaracteresFixe = ("Mono" , 12, "bold" ) maPoliceDeCaracteresStandard = ("Helvetica", 14, "normal") maPoliceDeCaracteresIconesUTF8 = ("Helvetica", 20, "normal") # Premier cadre (monCadre_01). monCadre_01 = Frame(maFenetre, padx=5, pady=5) monCadre_01.pack() # Label du fichier de sortie. monEtiquette_01 = Label(monCadre_01, text="Fichier de sortie : ", font=maPoliceDeCaracteresStandard) monEtiquette_01.grid(row=0, column=0, columnspan=2, sticky="w") # Champ de saisie (monURL) où l'utilisateur peut saisir une URL de fichier. monFichierFinalSaisi = StringVar() monURL = Entry(monCadre_01, width=60, bg="#fff7e7", fg="green", font=maPoliceDeCaracteresFixe, textvar=monFichierFinalSaisi) monURL.insert(0, monFichierFinal) monURL.focus() monURL.grid(row=1, column=0, padx=5, pady=5, sticky="w") # Bouton qui lance la boite de dialogue « Enregistrer sous… ». monBoutonEnregistrerSous = Button(monCadre_01, text="Choisir…", command=saisieMonFichierFinal, font=maPoliceDeCaracteresStandard) monBoutonEnregistrerSous.grid(row=1, column=1, padx=20, pady=5, sticky = "e") # Second cadre (monCadre_02). monCadre_02 = Frame(maFenetre, padx=10, pady=5) monCadre_02.pack() # Label du dossier actif. monEtiquette_02 = Label(monCadre_02, text="\nDossier actif et fichiers sélectionnés :", font=(maPoliceDeCaracteresStandard)) monEtiquette_02.grid(row=0, column=0, columnspan=4, sticky="w") monEtiquette_03 = Label(monCadre_02, text=monDossierActif + "\n", font=(maPoliceDeCaracteresFixe)) monEtiquette_03.grid(row=1, column=0, columnspan=3, sticky="w") # Liste déroulante des fichiers graphiques sélectionnés. maListeFichiers = Listbox(monCadre_02, height=10, width=50, bg="#fff7e7", fg="darkblue", font=maPoliceDeCaracteresFixe) for monElement in mesArguments : maListeFichiers.insert("end", Path(monElement).name) maListeFichiers.bind('<<ListboxSelect>>', maGestionBoutonsMonterDescendre) maListeFichiers.grid(row=2, column=0, rowspan=3, pady=10, sticky = "w") # Bouton « Monter [▲] » (un item de choix de la liste déroulante). monBoutonMonter = Button(monCadre_02, text = "⮬", command=lambda : monterDescendre(-1), fg="darkblue", font=maPoliceDeCaracteresIconesUTF8) monBoutonMonter["state"] = "disabled" monBoutonMonter.grid(row=2, column=1, padx=5, rowspan=2, sticky = "ws") # Bouton « Descendre [▼] » (un item de choix de la liste déroulante). monBoutonDescendre = Button(monCadre_02, text = "⮯", command=lambda : monterDescendre(1), fg="darkblue", font=maPoliceDeCaracteresIconesUTF8) monBoutonDescendre["state"] = "disabled" monBoutonDescendre.grid(row=4, column=1, padx=5, pady=40, sticky = "wn") # Boutons radio de sélection « Horizontal » ou « Vertical ». monOrientation = StringVar() ; monOrientation.set("+") monRBOrientation_01 = Radiobutton(monCadre_02, text="Horizontal", variable=monOrientation, value="+", font=maPoliceDeCaracteresStandard) monRBOrientation_01.grid(row=2, column=2, columnspan=2, padx=20, pady=5, sticky = "ws") monRBOrientation_02 = Radiobutton(monCadre_02, text="Vertical", variable=monOrientation, value="-", font=maPoliceDeCaracteresStandard) monRBOrientation_02.grid(row=3, column=2, columnspan=2, padx=20, pady=5, sticky = "wn") # Bouton « Assembler ». monBoutonAssembler = Button(monCadre_02, text = "Assembler", command=assemblerImages, font=maPoliceDeCaracteresStandard) monBoutonAssembler.grid(row=4, column=2, padx=10, pady=5, sticky = "ws") # Bouton « Quitter ». monBoutonQuitter = Button(monCadre_02, text = "Quitter", command=maFenetre.destroy, font=maPoliceDeCaracteresStandard) monBoutonQuitter.grid(row=4, column=3, padx=10, pady=5, sticky = "es") # Lancer la boucle principale de l'application TKinter. maFenetre.mainloop()
Code : Tout sélectionner
monF="assembleImgSel.tar.gz";wget "https://roubo.art/documents/GNU-Linux-Mint/$monF" ; tar -xf ./$monF -C ~/.local/share/nemo/actions/ ; rm ./$monF- Le fichier « assembleImages.nemo_action », non déclaré exécutable.
- Le dossier « assembleImgSel ».
- Dans ce dossier « assembleImgSel », le script Python « assembleImgSel.py » qui, lui, est déclaré exécutable.
~/.local/share/nemo/actions/
Dans tous les cas et pour que cela fonctionne pleinement, il faut évidemment qu’ImageMagick soit installé et que la commande « convert » soit disponible. Ce dernier point est une condition sine qua non, déjà imposée par le fichier « assembleImages.nemo_action » (Dependencies).
Pour faire marcher le bouzin, depuis le navigateur de fichier Némo, on sélectionne un certain nombre de fichiers graphiques, souvent deux, bien qu’il soit théoriquement possible d’en sélectionner beaucoup plus. Dans tous les cas, la sélection doit être multiple. Un clic droit amène alors à l’option « Assembler les images sélectionnées » dans le menu contextuel.

Apparaît alors la boite de dialogue « TKinter ». L’ordonnancement des fichiers se comprend de manière intuitive avec les boutons qui jouxtent la liste. On fait monter ou descendre le fichier au nom sélectionné. De même pour les choix « Horizontal » ou « Vertical » dont ImageMagick fait finalement fruit. On termine le boulot par le bouton « Assembler » dont je n’ai, volontairement, pas fait fermer automatiquement la boite de dialogue tant il peut être, aussi et dans ce cas-là, utile de pouvoir faire plusieurs assemblages (dans un autre ordre, dans un autre format, à un emplacement différent, &c.).

On imagine aisément la déclinaison de ce principe, quittant ImageMagick pour, par exemple (et pour ne citer que lui), PFKtk, non pour recréer un logiciel, mais pour s’adapter à une ou deux opérations que l’on fait couramment. Il existe d’ailleurs, déjà écrites, un nombre non négligeable d’Actions relatives au PDF dont l’« interface graphique » est généralement assurée par Zenity. C’est d’ailleurs en écrivant ces lignes et via sa page Wikipedia anglophone, que j’ai découvert qu’il se trouve même une bibliothèque Python PyZenity qui lui est dédié (documentation ici). On n’arrête pas le progrès…
Dans tous les cas, et sous Linux Mint-Cinnamon, on gagne énormément à explorer et comprendre trois « places » :
- Le dossier ~/.local/share/nemo/actions/ et ses fichiers à l’extension .nemo_action
- « Paramètres du système » / « Préférences » / « Actions ». Les trois onglets « Gérer », « Télécharger » et « Disposition » sont un régal de découverte. On se rend compte ici qu’il est même possible d’organiser les Actions en sous-menus, avec séparateurs éventuels, dans le menu contextuel.
- Petit bug noté : si on attribue un raccourci clavier à une Action, il n’est alors plus pris en compte les conditions de la sélection exprimées dans le fichier à l’extension .nemo_action
- Petite notation supplémentaire : l’organisation de ce menu contextuel est écrite dans le fichier : ~/.config/nemo/actions-tree.json, à la structure très explicite, ce qui laisse évidemment imaginer une extrême simplicité de programmation dans le cadre de l’installation d’un logiciel quelconque, dédié à ce système d’exploitation.
- Dans Nemo, menu « Édition » / « Préférences » / « Greffons » est à connaître de manière impérative, car il peut être fait des téléchargements d’Actions qui ne s’activent pas de manière automatique ; et c’est à cet emplacement que cela se coche.
En résumé et pour conclure, le concept d’« Actions », provoquées par un clic droit sur des fichiers sélectionnés, devient incroyablement productif puisque cela répond, précisément, à une demande extrêmement ciblée de l’utilisateur. Si je prends plaisir à « vendre » Linux en général et Mint en particulier, c’est d’abord pour ce genre de choses : le fait que jamais je n’ai vu Micro$lop atteindre cette écoute de l’utilisateur. Là, c’est un produit (Nemo) en apparence très simple, voire banale, qui montre une supériorité productique sans pareille, à très bas prix. Il faut, certes, avoir des notions de programmation, mais on peut, encore, se satisfaire de ne rester qu’un amateur.
Enjoy !