NFS: condivisione files

3 ottobre 2019 Nessun commento

Voglio condividere files da un server (Debian Buster) verso un client (Xubuntu 16.04).
Gli indirizzi IP dei dispositivi sono:

server: ip -> 192.168.1.2
client: ip -> 192.168.1.120

Configurazione lato SERVER

Aprire una finestra terminale ed installare il nesessario:

sudo apt-get install nfs-kernel-server

I comandi per controllare il demone nfs-kernel-server sono:

/etc/init.d/nfs-kernel-server start|stop|restart|status

Nota:

Il server dovrebbe avere un IP statico, per evitare che il DHCP assegni ogni volta un ip differente,
e si debba sempre riconfigurare il client.

Le directories che devono essere condivise, vanno inserite nel file etc/exports.
Aprire il suddetto file:

sudo nano /etc/exports

All’interno di questo file, va inserita una riga, per ogni directory che si vuole condividere,
con la seguente sintassi:

/dir_server_da_condividere IP_client(opzioni,)

dove:

/dir_server_da_condividere: la directory sul server, che dobbiamo condividere;
Ip_client: l’indirizzo IP del client (nel nostro scenario 192.168.1.120)
(opzioni,): sono le opzioni di condivisione che riguardano il client

tali opzioni si suddividono in:

ro: la directory condivisa è in sola lettura, quindi il client non può scrivere.
Questa è l’opzione di default
rw: il client può leggere e scrivere nella directory condivisa
no_root_squash: di default ogni azione fatta dall’utente root sul client, viene trattata come fosse l’utente nobody sul server.
Selezionare questa opzione invece, dà al root del client, gli stessi diritti del root sul server. Questo implica tutti i problemi di sicurezza del caso!
no_subtree_check: disabilita la routine subtree_check, che verifica se un file richiesto dal client, è presente nella parte di volume esportata. In caso di grandi porzioni di volume esportate, disabilitare la routine con questa opzione, velocizza molto il trasferimento dei file.
sync: di default per prevenire la perdita di dati in caso il server si riavvii, exportfs assume un comportamento asincrono. Questa opzione invece prevede un comportamento sincrono, ovvero forza la scrittura dei dati appena questi arrivano al server.

nel nostro caso volendo condividere la directory /home/banco/Immagini/backupfoto del server, avremo:

/home/banco/Immagini/backupfoto 192.168.1.120(rw,no_root_squash)

Per questo caso, concediamo al client i privilegi di root come fossimo sul server, con gli annessi rischi
nel caso volessimo estendere l’accesso a tutti i client di rete da 120 a 200, ma solo in lettura:

/home/banco/Immagini/backupfoto 192.168.1.120/200(ro)

Una volta configurato il file, esporare le condivisioni con il comando:

sudo exportfs -a

e riavviare il servizio nfs-kernel-server

sudo systemctl restart nfs-kernel-server

Questi due comandi sono da eseguire dopo ogni modifica del file etc/exports.

AVANZATE

Per definire gli accessi al server, in maniera più dettagliata, si possono configurare i file
/etc/host.deny ed /etc/host.allow.
Il primo setta i divieti, il secondo i permessi.
Per prima cosa infatti si setta sempre il deny, poi l’allow.

Per prima cosa quindi vietare l’accesso a tutti i client editando il file /etc/host.deny con il comando:

sudo nano /etc/hosts.deny

ed aggiungendo la stringa:

portmap mountd nfsd statd rquotad : ALL

In sostanza vietiamo a tutti i client i seguenti servizi:
portmap: è il demone di assegnazione della porta dinamica per servizi come NFS.
fmountd: il demone che implementa il lato server del protocollo NFS.
nfsd: è lo speciale filesystem che dà accesso ad un server NFS.
statd: è il demone che gestisce il file lock, che non sono persistenti nel
file system e pertanto vengono persi in caso di reboot.
rquotad: è un rpc server che ritorna la quota-disco assegnata allo user.

Ora che abbiamo vietato tutto, assegnamo i permessi al client desiderato, editando il file /etc/host.allow:

sudo nano /etc/hosts.allow

inserendo la stringa

portmap mountd nfsd statd rquotad : 192.168.1.2 192.168.1.120

nota:

Va inserito nel file anche l’indirizzo IP del server.
Terminati i settaggi è necessario riavviare il servizio NFS, con il comando:

sudo systemctl restart nfs-kernel-server

Ora passiamo a configurare il lato client.

Configurazione lato CLIENT

Installare il pacchetto necessario, in questo caso nfs-common:

sudo apt-get install nfs-common

Come in precedenza configurare i file hosts.deny e hosts.allow.

sudo nano /etc/hosts.deny

inserendo il divieto universale:

portmap : ALL

poi sui permessi:

sudo nano /etc/hosts.allow

aggiungendo l’IP del server con la sintassi:

portmap : 192.168.1.2

Ora creare una directory per il punto di mount della condivisione

sudo mkdir /mnt/nfsshared/backup_foto

e montare con il comando:

sudo mount -t nfs 192.168.1.2:/home/banco/Immagini/backupfoto /mnt/nfsshared/backup_foto

se è tutto ok, possiamo inserire il montaggio in /etc/fstab:

sudo nano /etc/fstab

inserendo

192.168.1.2:/home/banco/Immagini/backupfoto  /mnt/nfsshared/backup_foto nfs4 _netdev,bg,nofail,rw,relatime,rsize=65536,wsize=65536 0 0

dove le opzioni sono:
_netdev: il filesystem risiede su un dispositivo di rete quindi evita che il sistema (client) tenti di montare questo filesystem quando ancora la rete non è attiva sul sistema stesso.
bg: se il tentativo di mount fallisce, un child in background continuerà a tentare di montare l’export.
nofail: Il boot continuerà anche se questo mount fallirà (es. il server è spento).
rw: sono i diritti di lettura/scrittura.
relatime: aggiorna i tempi di accesso agli “inode” e può migliorare le prestazioni.
rsize, wsize: indica la quantità di bytes per parte di dato. Migliora la prestazione di NFS.

Per smontare la condivisione

sudo umount /mnt/nfsshared/backup_foto
Categorie:Linux, Ubuntu Tag: , ,

PyQt5: QPalette

25 giugno 2019 Commenti chiusi

Torna all’indice degli appunti

QPalette

In Qt, tutti i widgets contengono un palette che li disegna.
La classe QPalette contiene i 3 gruppi di colori che rappresentano i 3 stati di un widget: Active, Inactive e Disabled:

Active: gruppo di colori per la finestra che detiene il Focus;
Inactive: le altre finestre (che non hanno il Focus);
Disabled: gruppo di colori utilizzato per i widget (non le window) che sono disabilitati;

Sia le window attive, sia le inattive, possono contenere Disabled widgets.

Per ottenere un color/brush di un determinato ColorGroup, di un determinato ColorRole, si utilizza il metodo color(ColorGroup, ColorRole) o i metodi scorciatoia dell’oggetto QPalette: ad esempio window(), windowText(), base().

>>> from PyQt5.QtWidgets import QApplication, QPushButton
>>> from PyQt5.QtGui import QPalette
>>> app = QApplication([])
>>> button = QPushButton("OK")
>>> palette = button.palette()
>>> palette.brush(QPalette.Active, QPalette.Text) == palette.text()
True
>>> palette.text().color().name()
'#000000'
>>> palette.base().color().name()
'#ffffff'

Per settare un color/brush invece, si utilizza il metodo setColor(ColorGroup, ColorRole, QColor) o setBrush(ColorGroup, ColorRole, QBrush).
Se volessimo ad esempio settare il colore del testo di un bottone nel suo stato Active:

>>> from PyQt5.QtGui import QColor
>>> color = QColor(130, 111, 132)
>>> palette.setColor(QPalette.Normal, text_role, color)
>>> button.setPalette(palette)
>>> new_color = palette.color(QPalette.Normal, button.foregroundRole())
>>> new_color.getRgb()
(130, 111, 132, 255)

ColorRole

Gli enum ColorRole che definiscono i differenti colori utilizzati nella GUI, sono:

CostanteValoreDescrizione
QPalette.Window10Colore di background generico
QPalette.BackgroundWindowObsoleto, usare Window
QPalette.WindowText0Colore di Foreground generico
QPalette.ForegroundWindowTextObsoleto. Usare WindowText
QPalette.Base9Usato principalmente per il background color dei textentry widgets, ma anche come background dei combobox drop down lists. Generalmente bianco o di colore chiaro
QPalette.AlternateBase16background alternativo in views con righe di colore alternato(QAbstractItemView.setAlternatingRowColors())
QPalette.ToolTipBase18background per QToolTip e QWhatsThis. I Tool tips usano il color group Inactive di QPalette, perchè non sono active windows
QPalette.ToolTipText19foreground color per QToolTip e QWhatsThis. I Tool tips usano il color group Inactive di QPalette, perchè non sono active windows
QPalette.PlaceholderText20colore per il placeholder dei widget in inserimento testo
QPalette.Text6Il colore di foreground usato con Base
QPalette.Button1Il colore di background dei bottoni
QPalette.ButtonText8Il colore di foreground usato nei bottoni
QPalette.BrightText7Il colore del testo quando è necessario un contrasto elevato

ColorGroup

Gli enum ColorGroup che definiscono i differenti stati di un widget, sono:

Costante Valore Descrizione
QPalette.Disabled 1 stato disabled
QPalette.Active 0 stato Active
QPalette.Inactive 2 stato Inactive
QPalette.Normal Active sinonimo di Active

Per sapere quale ColorGroup è settato per l’oggetto QPalette, si utilizza il metodo currentColorGroup() che restituisce l’enum corrispondente.
Di default, il color group è Active/Normal:

>>> from PyQt5.QtWidgets import QApplication, QPushButton
>>> from PyQt5.QtGui import QPalette
>>> app = QApplication([])
>>> button = QPushButton()
>>> palette = button.palette()
>>> palette.currentColorGroup()
0

Per settare un color group differente, si utilizza il metodo setCurrentColorGroup(ColorGroup), dove ColorGroup è uno dei 4 enum della tabella precedente:

>>> palette.setCurrentColorGroup(QPalette.Disabled)
>>> palette.currentColorGroup()
1

Come anticipato, è possibile ottenere il color/brush di un determinato role, o con il metodo color(ColorGroup, role), o con i metodi specifici per il ColorGroup corrente, che sono:

base(): ritorna l’oggetto brush responsabile del base (background);

>>> palette.base()
<PyQt5.QtGui.QBrush object at 0x035FCD70>
>>> palette.base().texture()
<PyQt5.QtGui.QPixmap object at 0x035FCC70>
>>> palette.base().color()
<PyQt5.QtGui.QColor object at 0x035FCD30>
>>> palette.base().color().value()
240

alternateBase(): ritorna il brush dell’alternate base (background) per il ColorGroup corrente;
brightText(): ritorna il brush del bright text (foreground) per il ColorGroup corrente;
text(): ritorna l’oggetto brush del text (foreground) del ColorGroup corrente;
button(): ritorna l’oggetto brush responsabile del button del ColorGroup corrente;
buttonText(): ritorna l’oggetto brush responsabile del testo del button (foreground) del ColorGroup corrente;
dark(): ritorna il dark brush del ColorGroup corrente;
light(): ritorna il light brush del ColorGroup corrente;
highlight(): ritorna l’highlight brush del ColorGroup corrente;
highlightedText(): ritorna il brush di testi evidenziati del ColorGroup corrente;
link(): ritorna il brush per i link non visitati del ColorGroup corrente;
linkVisited(): ritorna il brush per i link visitati del ColorGroup corrente;
mid(): ritorna il mid brush del ColorGroup corrente;
placeHolderText(): ritorna il brush del place holder text (suggerimento) del ColorGroup corrente;
shadow(): ritorna il brush dell’ombra del ColorGroup corrente;
toolTipBase(): ritorna il brush del background del tooltip del ColorGroup corrente;
toolTipText(): ritorna il brush del testo del tooltip del ColorGroup corrente;
window(): ritorna il brush del backgorund generale della window del ColorGroup corrente;
windowText(): ritorna il brush del foreground generale della window del ColorGroup corrente;

Ecco un esempio di codice per la modifica del testo e del colore del bottone.

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QPushButton
from PyQt5.QtGui import QColor, QPalette
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QButton Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        t_color = QColor(190, 49, 54)
        b_color = QColor(90, 192, 54)
        button = QPushButton("OK", minimumHeight=48)
        palette = button.palette()
        text = button.foregroundRole()
        base = button.backgroundRole()
        button.setAutoFillBackground(True)
        palette.setColor(QPalette.Active, text, t_color)
        palette.setColor(QPalette.Active, base, b_color)
        button.setPalette(palette)
        layout.addWidget(button, 0)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

BACKGROUND BUTTON

Se il colore del background del bottone cambia solo nella cornice, bisogna utilizzare il metodo setStyleSheet(string) del widget.
E’ possibile infatti modificare l’aspetto del widget, inserendo una stringa con tutti i parametri, come fosse CSS.

Esempio:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QPushButton
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QButton Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        button = QPushButton("OK", objectName="custombutton", minimumHeight=48)
        button.setStyleSheet(StyleSheet)
        layout.addWidget(button, 0)


StyleSheet = """
#custombutton {
    border: 2px solid #666;
    max-height: 48px;
    border-top-right-radius:   20px;   
    border-bottom-left-radius: 20px;   
    background-color: #ff9800;
}
#custombutton:hover {
    background-color: #ffb74d;
}
#custombutton:pressed {
    background-color: #ffe0b2;
}
"""


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QBrush

19 giugno 2019 Commenti chiusi

Torna all’indice degli appunti

QBrush

La classe QBrush definisce il pattern di riempimento delle figure disegnate con un oggetto QPainter.
I 4 parametri che caratterizzano un oggetto QBrush sono lo stile, il colore, il gradiente e la texture.
Il costruttore QBrush quindi, prende come argomenti lo stile, il colore, il gradiente e la texture:
E’ possibile però costruire l’oggetto senza parametri e poi settarli in un secondo momento.
Un oggetto QBrush ha quindi 4 metodi fondamentali che lo identificano:

style(): indica il pattern di riempimento della figura disegnata;
color(): indica il colore del pattern di riempimento;
gradient(): indica il tipo di gradiente del pattern di riempimento;
texture(): indica il pixmap utilizzato come riempimento;

I corrispondenti setter, che modificano le caratteristiche dell’oggetto QBrush, sono ovviamente setStyle(Qt.BrushStyle), setColor(QColor), setTexture(pixmap). Non è disponibile un setter per il gradiente, ma bisogna passare l’enum corrispondente in fase di costruzione dell’oggetto.

Useremo questo codice per vedere le varie differenze nell’oggetto QBrush:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtGui import QPainter, QBrush, QColor
from PyQt5.QtCore import Qt
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QBrush Example")
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.painter = None

    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

BRUSH STYLE

Per sapere lo stile di QBrush, come già detto, si usa il metodo style(), invece setStyle(style) setta il tipo di pattern utilizzato;
style è un enum di tipo Qt.BrushStyle e può assumere i seguenti valori:

Costante valore descrizione
Qt.NoBrush 0 Nessuna pattern
Qt.SolidPattern 1 Pattern con colore uniforme
Qt.Dense1Pattern -> Qt.Dense7Pattern 2-8 Pattern con densità crescente da 1 a 7
Qt.HorPattern 9 Pattern con linee orizzontali
Qt.VerPattern 10 Pattern con linee verticali
Qt.CrossPattern 11 Pattern con linee orizzontali e verticali
Qt.BDiagPattern 12 Pattern con linee diagonali
Qt.FDiagPattern 13 Pattern con linee diagonali opposte
Qt.DiagCrossPattern 14 Pattern con linee diagonali incrociate
Qt.LinearGradientPattern 15 Pattern con gradient Lineare
Qt.RadialGradientPattern 16 Pattern con gradient Radial
Qt.ConicalGradientPattern 17 Pattern con gradient Conical
Qt.TexturePattern 24 Pattern personalizzato

In caso di creazione dell’oggetto QBrush senza parametri, come nel codice di esempio, di default lo stile sarà Qt.NoBrush:

>>> from PyQt5.QtGui import QBrush
>>> brush = QBrush()
>>> brush.style()
0

Nota:

Come nel codice, per vedere personalizzare QBrush è necessario reimplementare il metodo paintEvent().
Per modificare lo stile dopo la creazione dell’oggetto QBrush, si utilizza il metodo setStyle(Qt.BrushStyle):

es.:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

BRUSH COLOR

Il colore di default di un oggetto QBrush è il nero.
Per ottenere informazioni sul colore, si utilizza il metodo color():

>>> brush.color()
<PyQt5.QtGui.QColor object at 0x0339F6F0>
>>> brush.color().getRgb()
(0, 0, 0, 255)
>>> brush.color().name()
'#000000'

Per modificare il colore dopo la creazione dell’oggetto QBrush, si utilizza il metodo setColor(QColor):

>>> from PyQt5.QtGui import QColor
>>> new_color = QColor("#FF0000") 
>>> brush.setColor(new_color)
>>> brush.color().name()
'#FF0000'

Nel codice, basterà aggiungere una linea, sempre prima di settare l’oggetto QBrush nel QPainter:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        new_color = QColor("#FF0000")
        brush.setColor(new_color)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Nota:

Come mai il bordo del rettangolo è nero nonostante il colore del brush, sia stato modificato?
QBrush è responsabile del pattern di riempimento della forma/figura disegnata dall’oggetto QPainter, mentre per il contorno di tale forma, è necessario fare riferimento all’oggetto QPen. Modificando il colore del QPen associato al painter, cambierà pertanto il colore della cornice:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        brush = QBrush()
        brush.setStyle(Qt.DiagCrossPattern)
        new_color = QColor("#FF0000")
        brush.setColor(new_color)
        self.painter.setBrush(brush)
        pen = QPen(QColor("Red"))
        pen.setWidth(2)
        self.painter.setPen(pen)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

BRUSH GRADIENT

Come accennato precedentemente, è possibile sapere che tipo di gradiente viene utilizzato nell’oggetto QBrush, ma non è possibile modificarlo dopo la costruzione dell’oggetto.
Esistono 3 tipi di gradienti: QLinearGradient, QConicalGradient, and QRadialGradient. Si tratta quindi di ereditare da una di queste 3 classi e creare l’ggetto QBrush, con il gradiente personalizzato, passato come argomento.

QLinearGradient

Il gradiente di tipo Linear esegue l’interpolazione dei colori tra due punti, uno iniziale e uno finale. Fuori da questi punti, invece, i colori assegnati sono pieni.
il gradiente può assumere 3 differenti tipi di Spread: Padded, Reflected e Repeated.
Lo spread è il tipo di riempimento utilizzato fuori dall’area del gradiente.
Per conoscere il tipo di spread, si utilizza il metodo spread(), mentre per modificarlo si utilizza il metodo setSpread(spread), dove spread può assumere il valore di uno dei seguenti enum di tipo QGradiant.Spread:

Costante valore descrizione
QGradient.PadSpread 0 Valore di default. L’area è riempita con il colore di “stop” più vicino
QGradient.RepeatSpread 2 Fuori dall’area il gradiente viene ripetuto
QGradient.ReflectSpread 1 Fuori dall’area il gradiente viene riflesso

Per capire come funziona l’interpolazione, useremo i colori bianco e nero.
In sostanza si crea un oggetto gradient ereditando dalla classe prederita delle 3 sopra citate, passando come argomenti, le coordinate dello start point e quelle dell’end point dell’area del gradiente.
Poi si settano il colore di start (Nero) ed il colore di stop (bianco) e si passa l’oggetto gradient al costruttore del QBrush:

from PyQt5.QtGui import QGradient, QLinearGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QLinearGradient(10, 80, 180, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Le coordinate di start ed end in fase di costruzione dell’oggetto gradient, decidono anche la direzione della linea di interpolazione e la larghezza dell’area che la riguarda.
Se avviciniamo i punti delle coordinate, restringiamo l’area dell’interpolazione:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(80, 80, 100, 80)
...

Se invece di avere la stessa y, le coordinate avranno la stessa x, il gradiente sarà orizzontale invece che verticale:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(50, 20, 50, 80)
...

Giocando sulle coordinate si crea anche un gradiente diagonale:

...
    def paintEvent(self, event):
	...
        gradient = QLinearGradient(50, 20, 50, 80)
...

Queste possibilità, riguardano lo Spread di default, ovvero Qt.PadSpread.
Per cambiare il tipo, si utilizza il setter setSpread(spread), prima di passare l’oggetto gradient al costruttore di QBrush:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QLinearGradient(80, 80, 120, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        gradient.setSpread(QGradient.RepeatSpread)
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

startpoint e endpoint

per conoscere gli start point e gli end point di un oggetto gradient, si utilizzano i metodi start() e finalStop():

>>> from PyQt5.QtGui import QLinearGradient
>>> gradient = QLinearGradient(10, 80, 190, 80)
>>> gradient.start()
PyQt5.QtCore.QPointF(10.0, 80.0)
>>> gradient.finalStop()
PyQt5.QtCore.QPointF(190.0, 80.0)

QRadialGradient

Il gradiente di tipo Radial esegue l’interpolazione dei colori in maniera circolare tra un punto focale ed un end point.
Come per il LinearGradient, i tipi di spread sono sempre Padded, Reflected e Repeated.
Il costruttore del gradient radial, prende come argomenti: le coordinate del centro del cerchio che farà da interpolazione dei colori, il raggio del cerchio e le coordinate del focus.
Per il resto non cambia nulla:

from PyQt5.QtGui import QGradient, QRadialGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QRadialGradient(50, 50, 50, 80, 80)
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Se voglio rimpicciolire il cerchio, agisco sul terzo argomento del costruttore:

from PyQt5.QtGui import QGradient, QRadialGradient

...
    def paintEvent(self, event):
	...
        gradient = QRadialGradient(50, 50, 25, 80, 80)
...

Per spostare il centro del cerchio e le coordinate del focus, ovviamente si dovrà agire sugli altri 4 argomenti.
Con un raggio molto grande, si avrà l’effetto di coprire per intero il rettangolo:

...
    def paintEvent(self, event):
	...
        gradient = QRadialGradient(100, 50, 130, 20, 30)
...

QConicalGradient

Il gradiente di tipo Conical esegue l’interpolazione dei colori con una rotazione oraria attorno ad un punto.
Oltre alle coordinate del punto centrale, è possibile passare al costruttore del gradiente, l’angolo dal quale cominceremo a ruotare.
Come per il LinearGradient, i tipi di spread sono sempre Padded, Reflected e Repeated.

from PyQt5.QtGui import QGradient, QConicalGradient

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        gradient = QConicalGradient(50, 50, 45)  # 45 sono i gradi dell'angolo
        gradient.setColorAt(0, QColor("#000000"))
        gradient.setColorAt(1, QColor("#FFFFFF"))
        brush = QBrush(gradient)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

I parametri del gradiente di tipo Conical, sono recuperabili con gli appositi getter: angle() e center()

>>> from PyQt5.QtGui import QGradient, QConicalGradient
>>> gradient = QConicalGradient(50, 50, 45)
>>> gradient.angle()
45.0
>>> gradient.center()
PyQt5.QtCore.QPointF(50.0, 50.0)

Per modificare tali parametri invece, ci sono gli appositi setter: setAngle(angle) e setCenter(x, y):

>>> gradient.setAngle(55)
>>> gradient.angle()
55.0
>>> gradient.setCenter(60, 60)
>>> gradient.center()
PyQt5.QtCore.QPointF(60.0, 60.0)

BRUSH TEXTURE

Per utilizzare una texture nell’oggetto QBrush, per prima cosa si crea un oggetto QPixmap con l’immagine desiderata, poi lo si passa al costruttore dell’oggetto QBrush.
Una volta creato l’oggetto QBrush, si imposta lo style dello stesso a Qt.TexturePattern.
Oppure si crea l’oggetto QBrush senza argomenti, poi si associa il pixmap con l’apposito setter setTexture(pixmap).
Per sapere quale texture viene utilizzata sul brush, si utilizza il metodo texture():

from PyQt5.QtGui import QPixmap

...

    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        pixmap = QPixmap("Qtsmall.png")
        brush = QBrush()
        brush.setStyle(Qt.TexturePattern)
        brush.setTexture(pixmap)
        self.painter.setBrush(brush)
        self.parent().pos()
        self.painter.drawRect(10, 10, 180, 80)
        self.painter.end()
...

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QPen

14 giugno 2019 Commenti chiusi

Torna all’indice degli appunti

QPen

La classe QPen definisce come un QPainter dovrebbe disegnare linee e contorni di figure.
Il costruttore QPen prende come argomenti il colore, la larghezza del tratto e lo stile dello stesso, es: QPen(Qt.black, 2, Qt.SolidLine), ma anche il Cap Style ed il Join Style.
E’ possibile però costruire l’oggetto senza parametri e poi settarli in un secondo momento.
Un oggetto QPen ha 5 metodi fondamentali che lo identificano:

style(): indica il tipo di linea utilizzata;
brush(): indica il tipo di riempimento dei tratti generati da QPen;
width(): indica la larghezza del tratto della penna in pixel;
capStyle: indica la terminazione della linea, la parte finale che interrompe il tratto;
joinStyle: indica come viene effettuata l’unione di 2 linee (join);

I corrispondenti setter, che modificano le caratteristiche dell’oggetto QPen, sono ovviamente setStyle(Qt.PenStyle), setBrush(QBrush), setWidth(int), setCapStyle(Qt.PenCapStyle) e setJoinStyle(Qt.PenJoinStyle).

Useremo questo codice per vedere le varie differenze nell’oggetto QPen:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QPen Example")
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.painter = None

    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        self.draw_line(10, 50, 180, 50)
        self.painter.end()

    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

PEN STYLE

Per sapere lo stile di QPen, come già detto, si usa il metodo style(), invece setStyle(style) setta il tipo di linea utilizzata;
style è un enum di tipo Qt.PenStyle e può assumere i seguenti valori:

Costante valore descrizione
Qt.NoPen 0 Nessuna linea. Esempio: QPainter.drawRect() riempie ma non disegna nessuna linea di contorno
Qt.SolidLine 1 Una linea continua
Qt.DashLine 2 Linea tratteggiata
Qt.DotLine 3 Linea punteggiata
Qt.DashDotLine 4 Trattini alternati da punti
Qt.DashDotDotLine 5 Un trattino e 2 punti alternati
Qt.CustomDashLine 6 Linea con un pattern personalizzato definito usando il metodo QPainterPathStroker.setDashPattern()

In caso di creazione dell’oggetto QPen senza parametri, di default lo stile sarà Qt.SolidLine:

>>> from PyQt5.QtGui import QPen
>>> pen = QPen()
>>> pen.style()
1

Nota:

Come si nota dal codice di esempio, per vedere QPen in azione, è necessario reimplementare il metodo paintEvent(), che chiama a sua volta un metodo designato a creare un oggetto QPen e a tracciare un linea con lo stile desiderato.

Volendo cambiare ad esempio lo stile del tratto, in Qt.DashLine, basterà modificare la linea di codice:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

in

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.black, 2, Qt.DashLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

oppure utilizzare il setter, che sovrascriverà le impostazioni di creazione:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

Le altre opzioni sono:

Qt.DotLine

Qt.DashDotLine

Qt.DashDotDotLine

PEN WIDTH

Di default la larghezza del tratto di QPen equivale a 1 e si ottiene con il metodo width().
Con i setter setWidth(int), o setWidthF(float), in caso di numero decimale, si può modificare lo spessore del tratto:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        pen.setWidth(1)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

PEN BRUSH/COLOR

Con il metodo brush() si risale all’oggetto QBrush associato a QPen. QBrush è il responsabile della colorazione data alla linea tracciata dall’oggetto QPen.
Di default il colore associato a QPen è il nero, infatti:

>>> brush = pen.brush()
>>> pen_color = brush.color()
>>> pen_color.getRgb()
(0, 0, 0, 255)

In realtà esiste una scorciatoia nell’oggetto QPen, data da color, per raggiungere direttamente l’oggetto QColor associato al QBrush di QPen:

>>> pen.color().getRgb()
(0, 0, 0, 255)

Per settare una colorazione differente, utilizziamo il setter setBrush(QBrush) o direttamente la scorciatoia setColor(QColor).
I due metodi sono equivalenti:

>>> from PyQt5.QtGui import QColor
>>> pen.setColor(QColor("#008000"))
>>> pen.color().getRgb()
(0, 128, 0, 255)

o con l’oggetto QBrush:

>>> from PyQt5.QtGui import QBrush
>>> brush = QBrush(QColor("#008500"))
>>> pen.setBrush(brush)
>>> pen.color().getRgb()
(0, 133, 0, 255)

Nel codice di esempio è sufficiente aggiungere una riga di codice con uno dei due setter appena visti:

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 5, Qt.SolidLine)
        pen.setStyle(Qt.DashLine)
        pen.setWidth(5)
        pen.setColor(QColor("#008000"))
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

PEN CAPSTYLE

Il capstyle, decide come l’oggetto QPen deve disegnare il terminale della linea.
Tale terminale può essere di tre tipologie: Square, Flat e Round. Sono deinibili con i soliti enum di tipo Qt.PenCapStyle:

Costante valore descrizione
Qt.FlatCap 0x00 una linea “quadrata” che non copre il terminale della linea
Qt.SquareCap 0x10 una linea “quadrata” che copre il terminale della linea e si estende oltre la metà dello spessore della linea
Qt.RoundCap 0x20 il terminale della linea è arrotondato

I metodi getter e setter, sono ovviamente capStyle() per ottenere il valore associato all’oggetto QPen, che di default è Qt.SquareCap e setCapStyle(Qt.PenCapStyle), per modificare il valore:

>>> pen.capStyle()
16
>>> from PyQt5.QtCore import Qt
>>> pen.setCapStyle(Qt.RoundCap)
>>> pen.capStyle()
32

Per vedere la differenza, settiamo una larghezza del tratto importante.

...
    def draw_line(self, start_x, start_y, end_x, end_y):
        pen = QPen(Qt.red, 20, Qt.SolidLine)
        pen.setCapStyle(Qt.SquareCap)
        self.painter.setPen(pen)
        self.painter.drawLine(start_x, start_y, end_x, end_y)

lo stile Flat invece:

        pen.setCapStyle(Qt.FlatCap)

Anche se con difficoltà, si nota che la linea in realtà è più corta, perchè la modalità FLAT, non copre il terminale della linea come nella SQUARE, ma si ferma a metà.

infine il Round:

        pen.setCapStyle(Qt.RoundCap)

PEN JOINSTYLE

Il joinstyle, decide come l’oggetto QPen deve raccordare più linee tra loro.
Tale raccordo può essere di tre tipologie: Bevel, Miter e Round. Sono definibili con gli enum di tipo Qt.PenJoinStyle:

Costante valore descrizione
Qt.MiterJoin 0x00 i bordi più esterni delle linee vengono prolungati fino ad incontrarsi. L’angolo creatosi crea un’area che viene riempita
Qt.BevelJoin 0x40 il triangolo formato dalle linee viene riempito
Qt.RoundJoin 0x80 Un arco circolare raccorda le due linee e viene riempito

I metodi getter e setter, sono joinStyle() per ottenere il valore associato all’oggetto QPen, che di default è Qt.BevelJoin e setJoinStyle(Qt.PenJoinStyle), per modificare il valore:

>>> pen.joinStyle()
64
>>> pen.setJoinStyle(Qt.RoundJoin)
>>> pen.joinStyle()
128

ecco un esempio:

...
    def paintEvent(self, event):
        self.painter = QPainter()
        self.painter.begin(self)
        self.draw_symbol()
        self.painter.end()

    def draw_symbol(self):
        pen = QPen(Qt.red, 30, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin)
        self.painter.setPen(pen)
        self.painter.drawLine(20, 40, 50, 40)
        self.painter.drawLine(70, 40, 90, 80)
...

In caso di Round invece:

        pen = QPen(Qt.red, 30, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)

Nota:

Il Qt.RoundJoin funziona in coppia con il cap style Qt.RoundCap, ma bisogna anche raccordare le linee in modo che il join, sia ben fatto.
Correggendo le coordinate infatti si otterrà un effetto migliore:

        self.painter.drawLine(20, 40, 55, 40)
        self.painter.drawLine(60, 40, 90, 80)

DRAWING EXAMPLE

Ecco un esempio di come può essere utilizzato un oggetto QPen.
Sfruttiamo gli event handlers mousePressEvent, mouseMoveEvent e mouseReleaseEvent, per disegnare su un widget che mostra semplicemente un Pixmap.
Semplicemente reimplementandoli, controlliamo che venga premuto il tasto sinistro del mouse (event.button() == Qt.LeftButton), in tal caso, quando il mouse si muove (mouseMoveEvent), se il tasto è ancora premuto, settiamo l’oggetto QPen e, tenendo traccia dell’ultima posizione del mouse, disegnamo la linea.
Quando il pulsante del mouse viene rilasciato (mouseReleaseEvent), l’oggetto QPainter smette di disegnare poichè il flag is_drawable è stato settato a False.
Il flag is_drawable serve quindi per non continuare a disegnare senza che il pulsante sia premuto, durante i movimenti del mouse.

from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtGui import QPainter, QPen, QPixmap
import sys
from PyQt5.QtCore import Qt, QPoint


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Drawaing Example")
        self.image = QPixmap("sheet.png")
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)
        self.setMouseTracking(True)
        self.resize(self.image.width(), self.image.height())


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        self.last_mouse_pos = QPoint()
        self.is_drawable = False

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(self.rect(), self.parent().image)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawable = True
            self.last_mouse_pos = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton and self.is_drawable:
            painter = QPainter(self.parent().image)
            painter.setPen(QPen(Qt.red, 3, Qt.SolidLine))
            painter.drawLine(self.last_mouse_pos, event.pos())
            self.last_mouse_pos = event.pos()
            self.update()  # it calls paintEvent

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.is_drawable = False


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QColor

5 giugno 2019 Commenti chiusi

Torna all’indice degli appunti

QColor

Un colore viene normalmente specificato secondo una delle seguenti 3 specifiche:
RGB (Red, Green and Blue)

HSV (Hue, Saturation and Value)

CMYK (Cyan, Magenta, Yellow and Black)

RGB Color

QColor è la classe che gestisce i colori. Il costruttore crea sempre un oggetto QColor in termini RGB.
Per ottenere il colore secondo le altre due specifiche, si utilizzano i metodi toHsv() e toCmyb(), che ritornano una copia dell’oggetto, nel formato desiderato:

L’oggetto QColor può essere settato passando i valori Red, Green, Blue al costruttore; per gli esempi a seguire considereremo il colore “Verde”.
Consultando lo spazio di colori in figura, per un verde scuro prendiamo il valore a metà tra black (0) e light-green (255), quindi 128:

>>> from PyQt5.QtGui import QColor
>>> color = QColor(0, 128, 0)
>>> color.getRgb()
(0, 128, 0, 255)
>>> color.toCmyk()
<PyQt5.QtGui.QColor object at 0x033F15F0>
>>> color.toHsv()
<PyQt5.QtGui.QColor object at 0x033F1770>

E’ possibile passare al costruttore la stringa-colore esadecimale corrispondente:

>>> color_s = QColor("#008000")
>>> color_s.getRgb()
(0, 128, 0, 255)

Per recuperare i valori rgb si utilizza il metodo getRgb(), che ritorna una tupla con i valori: Red, Green, Blue e alpha-channel (trasparenza).
Per ottenere invece i singoli valori dei quattro precedenti parametri, si utilizzano gli omonimi metodi red(), green(), blue() ed alpha():

>>> color.red()
0
>>> color.green()
128
>>> color.blue()
0
>>> color.alpha()
255

Ovviamente i valori sono settabili in un colpo solo con il metodo setRgb(int, int, int, int), passando rispettivamente i valori red, green, blue e alpha, oppure singolarmente con i metodi setRed(int), setGreen(int), setBlue(int) e setAlpha(int):

>>> color_s.setBlue(20)
>>> color_s.blue()
20

Il costruttore QColor accetta anche stringhe predefinite contenenti i “nomi” dei colori esistenti:

>>> hex_color = QColor("#008000")
>>> n_color = QColor("Green")
>>> hex_color.getRgb() == n_color.getRgb()
True

Nota:
La stringa corrispondente al “green” (0, 128, 0) è rappresentata dai tre numeri in esadecimale, red “00” (0), green “80” (128) e blue “00” (0), che di seguito formano #008000″

se controlliamo su un qualsiasi color picker, noteremo la corrispondenza.

I nomi dei colori accettati dal costruttori sono reperibili con il metodo colorNames()

HSV Color

Come detto in precedenza, si costruisce un Qcolor in RGB e lo si converte in HSV con il metodo toHsv()
HSV è un sinonimo di HSL (Hue, Saturation, Lightness). Per ottenere i valori del colore secondo la specificha HSV, si utilizza il metodo getHsv(), che ritorna una tupla con i valori: Hue, Saturation, Value e alpha-channel (trasparenza).
Come nel caso della specifica RGB, è possibile ottenere i singoli valori con i metodi hue(), saturation(), value() e alpha(), e, in ugual misura, è possibile modificare tali valori con i setter corrispondenti: setHue(int), setSaturation(int), setValue(int) e
setAlpha(int):

>>> hsv = color.toHsv()
>>> hsv.getHsv()
(120, 255, 128, 255)
>>> hsv.value()
128

HSL Color

Variante della specifica HSV (o viceversa), dove “L” sta per Lightness.
Stesso principio: si crea una copia del colore RGB con il metodo toHsl() e si ottengono i valori con il metodo getHsl(), o singolarmente con i metodi hue(), saturation(), lightness(). Per la modifica degli singoli valori, si utilizzano i metodi setHue(int), setSaturation(int), setLightness(int):

>>> hsl.getHsl()
(120, 255, 64, 255)
>>> hsl.lightness()
64

CMYK Color

Ovviamente non fa differenze il colore secondo questa specifica. Si crea la copia di oggetto QColor RGB con il metodo toCmyk() e si ottiene la tupla dei valori, con il metodo getCmyk(), oppure i singoli valori con i metodi: cyan(), magenta(), yellow(), black() e alpha().
Il settaggio dei valori quindi avrà i propri setter: setCyan(), setMagenta(), setYellow(), setBlack() e setAlpha():

>>> cmyk = color.toCmyk()
>>> cmyk.getCmyk()
(255, 0, 255, 127, 255)
>>> cmyk.cyan()
255

Nota:

255 è il massimo valore che può raggiungere il byte che rappresenta il valore.
Quindi attenzione ai color picker che ragionano in percentuali inquanto 255 indica 100%.
Nell’ultimo caso il verde in CMYK ha un cyan = 100% (255), magenta = 0%, yellow = 100% (255) e black = 50% (127).

CREARE Color RGB da altri formati

E’ possibile fare anche il percorso inverso, ovvero ottenere un colore secondo le specifiche RGB, da un colore HSV, HSL o CMYK, utilizzando i metodi fromHsv(hue, saturation, value, alpha), fromHsl(hue, saturation, value, lightness) e fromCmyk(cyan, magenta, yellow, black, alpha):

>>> rgb_color = QColor.fromCmyk(255, 0, 255, 127, 255)
>>> rgb_color.getRgb() == color.getRgb()
True

VALIDATION

Quando costruiamo un colore passando una stringa, è possibile verificare che tale stringa esista tra i colori preconfezionati, utilizzando il metodo isValid(), o direttamente con il metodo isValidColor(color):

>>> QColor.isValidColor("Red")
True
>>> QColor.isValidColor("Foo")
False
>>> new_color = QColor("Foo")
>>> new_color.isValid()
False

Ma anche quando passiamo al costruttore valori errati:

>>> new_color = QColor(255, 255, 300, 300)
>>> new_color.isValid()
False
>>> new_color = QColor(255, 255, 255, 255)
>>> new_color.isValid()
True

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QPainter

31 maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QPainter

La classe QPainter è una delle tre classi cardine sulle quali si basa il painting system di Qt.
E’ la classe che esegue disegni di basso livello sui paint device, come ad esempio i widgets.
QPainter può disegnare di tutto, dalle semplici linee a forme complesse, testi e pixmaps.
Può genericamente operare su ogni oggetto che erediti dalla classe QPaintDevice

Le operazioni di disegno effettuate da QPainter devono avvenire all’interno del metodo paintEvent del widget.
L’iter è il seguente:

– si costruisce l’oggetto QPainter all’interno del metodo paintEvent(event);
– si personalizza l’oggetto QPainter, ad es. settando il tipo di QPen o QBrush;
– si disegnano gli elementi (linee, punti, forme complesse, ecc)

paintEvent

Quest è il metodo che deve essere reimplementato, quando si vuole personalizzare l’aspetto di un widget ed è ovviamente un event Handler.
Un paint event è una richiesta di ridisegnare tutto o parte di un widget. Questo evento si scatena ad esempio quando vengono invocati i metodi repaint() o update().
Per alcuni widget è molto semplice ridisegnare l’intera superficie (label, button), ma altri widget complessi possono aver bisogno di ridisegnare solo una sezione (QListWiew, QTableView).
Per questo si utilizzerà il metodo region() che ottimizzerà la velocità dell’operazione.

Nota:

Quando il metodo update() viene chiamato in successione o il window system manda molti paint events, Qt per comodità, unisce tutti questi eventi, sotto uno unico che riguarderà una regione vasta. Questa ottimizzazione non riguarda il metodo repaint(), pertanto è consigliato di utilizzare sempre il metodo update().

Vediamo un esempio pratico: costruiamo una label personalizzata dove poter agire su Font, colore, cornice e trama dello sfondo:

Il codice base è il seguente:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QLabel
import sys


class CustomLabel(QLabel):
    def __init__(self, text, parent):
        super(CustomLabel, self).__init__(text=text, parent=None)
        self.text = text


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Qpainter Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        label = CustomLabel("Bancaldo", self)
        layout.addWidget(label)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

FONT

Cominciamo ora a dare un aspetto personalizzato alla label reimplementando l’event Handler paintEvent:

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        pen = QPen(Qt.red, Qt.SolidLine)
        painter.setPen(pen)
        painter.setFont(QFont('Decorative', 20, italic=True))
        painter.drawText(event.rect(), Qt.AlignCenter, self.text)
        painter.end()

...

come detto in precedenza, creiamo l’oggetto QPainter, lo attiviamo con il metodo begin(), lo personalizziamo associando ad es. un oggetto QPen, settiamo il font desiderato e chiudiamo il painter con il metodo end().
Il risultato è:

CORNICE

Se invece volessimo aggiungere un riquadro tratteggiato alla label?
Possiamo creare un metodo che si occupi di modificare l’oggetto QPen e poi disegni un rettangolo tratteggiato sulla label.
L’importante è chiamare questo metodo prima che l’oggetto QPainter venga terminato con il metodo end():

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        ...
        self.paint_square(painter)
        painter.end()

    def paint_square(self, painter):
        sizer = self.size()
        pen = QPen(Qt.green, 5, Qt.DashLine)
        painter.setPen(pen)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

...

Nota:

sizer serve per ricavarmi le dimensioni della label in caso di resizeEvent.
Il resizeEvent infatti, riscatena automaticamente il paintEvent, che ogni volta ridisegnerà la cornice.

BACKGROUND

Con questo principio possiamo anche aggiungere un background alla forma geometrica che abbiamo costruito.
Per questo scopo si utilizza la classe QBrush.

In pratica si crea un oggetto QBrush dello stile desiderato e lo si associa all’oggetto painter:

...

class CustomLabel(QLabel):
    ...

    def paintEvent(self, event):
        ...
        self.paint_square(painter)
        self.paint_background(painter)
        painter.end()

    def paint_background(self, painter):
        sizer = self.size()
        brush = QBrush(Qt.Dense1Pattern)
        brush.setColor(Qt.lightGray)
        painter.setBrush(brush)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

...

Come mai non si legge più il testo?
Perchè l’ordine in cui disegnamo le cose, è importante.
Per prima cosa va disegnata la trama del background, poi si appone la cornice ed infine disegnamo il testo.
Per comodità di lettura codice, metteremo in un metodo a parte, anche la scrittura del testo:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget, QApplication, QBoxLayout, QLabel
from PyQt5.QtGui import QPainter, QFont, QPen, QBrush
from PyQt5.QtCore import Qt
import sys


class CustomLabel(QLabel):
    def __init__(self, text, parent):
        super(CustomLabel, self).__init__(text=text, parent=None)
        self.parent = parent
        self.text = text

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        self.paint_background(painter)
        self.paint_square(painter)
        self.paint_text(event, painter)
        painter.end()

    def paint_text(self, event, painter):
        pen = QPen(Qt.red, Qt.SolidLine)
        painter.setPen(pen)
        painter.setFont(QFont('Decorative', 25, italic=True))
        painter.drawText(event.rect(), Qt.AlignCenter, self.text)

    def paint_square(self, painter):
        sizer = self.size()
        pen = QPen(Qt.green, 5, Qt.DashLine)
        painter.setPen(pen)
        painter.drawRect(0, 0, sizer.width(), sizer.height())

    def paint_background(self, painter):
        sizer = self.size()
        brush = QBrush(Qt.Dense1Pattern)
        brush.setColor(Qt.lightGray)
        painter.setBrush(brush)
        painter.drawRect(0, 0, sizer.width(), sizer.height())


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("Qpainter Example")
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget)
        self.resize(200, 100)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)
        layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.setLayout(layout)
        label = CustomLabel("Bancaldo", self)
        layout.addWidget(label)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QApplication

29 maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QApplication

QApplication è la classe che gestisce i settaggi ed il flusso della UI della nostra applicazione.
La cosa fondamentale è che QApplication, contiene l’event_loop, dove vengono gestiti tutti gli eventi relativi alla main window e gestisce l’inizializzazione dell’applicazione. Ad ogni Applicazione con UI, corrisponde un oggetto QApplication.

Le responsabilità principali dell’oggetto QApplication sono:

– inizializzare l’applicazione con settaggi utente quali, palette, font e doubleClickInterval;
– esegue l’event-handling, ovvero riceve segnali dal window-system e li distribuisce ai widget interessati;
– accetta parametri da riga di comando e si regola in funzione di essi;
– può definire il look and feel dell’applicazione;
– fornisce la localizzazione delle stringhe visibili all’utente (traslate);
– mette a disposizione oggetti come desktop e clipboard;
– sa tutto delle finestre dell’applicazione: quale widget è in una determinata posizione, chi sono i TopLevel Widgets, può chiudere tutte le finestre, ecc;

SETTINGS

Come detto al punto 1, è possibile cambiare alcuni settaggi fondamentali dell’applicazione, come ad esempio palette, istanza della classe QPalette che contiene i gruppi di colori che indicano lo stato di ogni widget (Active, Inactive, Disabled).
Per ottenere l’oggetto palette dell’applicazione, si utilizza il metodo palette:

>>> from PyQt5.QtWidgets import QApplication
>>> from PyQt5.QtGui import QPalette, QColor
>>> app = QApplication([])
>>> palette = app.palette()

Con il metodo color(color_role), dove color_role è un QPalette ColorRole-enum, si ottiene l’oggetto QColor utilizzato dalla applicazione:

>>> palette.color(QPalette.Window).name()
'#f0f0f0'

per modificare il colore del role, si utilizza il metodo setColor(color_role, color), dove color_role è il ColorRole-enum, mentre color, l’oggetto QColor che vogliamo impostare:

>>> palette.setColor(QPalette.Window, QColor("aquamarine"))
>>> palette.color(QPalette.Window).name()
'#7fffd4'
>>> app.setPalette(palette)

In effetti, consideriamo il semplice codice:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QColor, QPalette
import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QApplication Example")
        self.resize(200, 150)
        self.central_widget = FormWidget(self)
        self.setCentralWidget(self.central_widget)


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow(parent=None)
    main_window.show()
    sys.exit(app.exec_())

modificando il QPalette.Window:

...

if __name__ == '__main__':
    app = QApplication(sys.argv)
    palette = app.palette()
    palette.setColor(QPalette.Window, QColor("aquamarine"))
    app.setPalette(palette)
    main_window = MainWindow(parent=None)
    main_window.show()
    sys.exit(app.exec_())

Lo stesso discorso vale per il settaggio di altri parametri, come ad esempio in font utilizzato dalla applicazione.

ISTANZA DI QApplication

Per ottenere l’oggetto QApplication ovunque all’interno del codice, è possibile chiamare il metodo instance() della classe QApplication:

>>> app.name_ = "bancaldo"
>>> app_ = QApplication.instance()
>>> app_ == app
True
>>> app_.name_
'bancaldo'

EVENT HANDLING

Per entrare nell’event loop si utilizza il metodo exec_(), da questo momento l’applicazione riceve gli eventi dal window-system e li distribuisce ai vari widgets. exec_() rimane nel main loop fino a chè non viene chiamato il metodo exit().

argv

Tutti i programmi Qt supportano i seguenti parametri da riga di comando:

-style= setta lo stile della UI dell’applicazione. I tipi di stile dipendono dalla configurazione di sistema;
-stylesheet= setta lo stylesheet della applicazione;
-reverse setta il layout dell’applicazione a Qt.RightToLeft;

per sapere quali argomenti sono settati per la app corrente, si utilizza il metodo arguments():

Lista Top Level Widgets

E’ possibile conoscere quali sono i Top Level Widgets, con il metodo topLevelWidgets() di QApplication:

>>> from PyQt5.QtWidgets import QApplication, QMainWindow
>>> app = QApplication([])
>>> mw = QMainWindow()
>>> app.topLevelWidgets()
[<PyQt5.QtWidgets.QMainWindow object at 0x0360E170>]

DESKTOP

il metodo desktop() ritorna un oggetto QDesktopWidget che rappresenta la root-window, ovvero il nostro desktop.
Può essere comodo conoscere le dimensioni del nostro desktop nel momento in cui dobbiamo dimensionare le finestre della nostra applicazione, sopratutto quando si ha a che fare con un doppio schermo e desktop esteso.
L’oggetto QDesktopWidget mette a disposizione due comodi metodi:

screenGeometry(): ritorna le dimensioni dello schermo;
availableGeometry(): ritorna le dimensioni diponibili (desktop);

>>> desktop = app.desktop()
>>> desktop.screenGeometry()
PyQt5.QtCore.QRect(0, 0, 1680, 1050)
>>> desktop.availableGeometry()
PyQt5.QtCore.QRect(0, 0, 1680, 1010)

CLIPBOARD

Con il metodo clipboard() otteniamo un oggetto QClipboard che ci dà l’accesso al clipboard di sistema.
E’ possibile ad esempio effettuare un semplice copia/incolla di dati, tra diverse applicazioni.
Testi, immagini, mimedata, pixmap, si portano sul clipboard con i metodi setText(text), setImage(image), setMimeData(data), setPixmap(pixmap).
Per recuperarli invece si utilizzano i metodi text(), image(), mimeData()e pixmap().

ABOUT QT

Chiamando il metodo aboutQt(), viene visualizzato un message box che indica la versione di Qt in uso.
Utile da inserire nel menu Help dell’applicazione.

ALL WIDGETS

Con il metodo allWidgets(), avremo una lista di tutti i widgets impiegati nella stessa:

>>> from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
>>> app = QApplication([])
>>> mw = QMainWindow()
>>> for n in range(5):
...     w = QWidget(parent=mw)
...     
>>> app.allWidgets()
[<PyQt5.QtWidgets.QWidget object at 0x0372E170>, ...
>>> len(app.allWidgets()
6

BEEP

Con il metodo beep() viene appunto emesso un beep con suono e volume di default.

CLOSE ALL WINDOWS

Per chiudere tutte le top level windows, si utilizza il metodo closeAllWindows()

WHEEL SCROLL LINES

Di default il numero di scroll lines è 3 e lo si può vedere con il metodo wheelScrollLines():

>>> app.wheelScrollLines()
3

Per modificare tale numero è sufficiente chiamare il metodo setWheelScrollLines(num):

>>> app.setWheelScrollLines(5)
>>> app.wheelScrollLines()
5

ICONA

E’ possibile modificare l’icona della finestra dell’applicazione, invocando il metodo setWindowIcon(icon), dove icon è un oggetto QIcon contenete l’immagine desiderata. Per ottenere invece l’icona dell’applicazione si utilizza il metodo windowIcon():

>>> from PyQt5.QtGui import QIcon
>>> icon = QIcon("qt.png")
>>> app.setWindowIcon(icon)
>>> app.windowIcon()
<PyQt5.QtGui.QIcon object at 0x0372E3A0>

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QMenuBar, QMenu

28 maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QMenuBar

La classe QMenuBar crea una barra orizzontale dove è possibile sistemare elementi detti pull-down Menu.
Il costruttore della menubar è QMenuBar:

>>> from PyQt5.QtWidgets import QApplication, QMenuBar
>>> app = QApplication([])
>>> menubar = QMenuBar()

Per aggiungere i menu sulla menubar appena creata, si utilizza il metodo addMenu(menu), dove menu è un oggetto QMenu, ovvero un widget che contiene una lista di elementi detti actions.

Vediamo nello specifico l’oggetto QMenu.

QMENU

Questa classe QMenu crea il widget che viene comunemente utilizzato nelle menubar, menu contestuali (es. bottone destro del mouse) e popup menu.
Come già anticipato, un oggetto QMenu, consiste in una lista di elementi “Action”.
Un elemento Action può avere una label, una icona ed una shortcut (scorciatoia da tastiera).
Il costruttore di un oggetto QMenu è appunto QMenu(text), dove text una stringa che rappresenta il nome del menu.
Per settare un’icona di menu, si utilizza il metodo setIcon(icon), dove icon è ovviamente un oggetto QIcon.
Aggiungiamo quindi alla menubar un menu “File” con tanto di Icona:

>>> from PyQt5.QtWidgets import QMenu
>>> from PyQt5.QtGui import QIcon
>>> menufile = QMenu("File")
>>> menufile.setIcon(QIcon("file.png"))
>>> menubar.addMenu(menufile)
<PyQt5.QtWidgets.QAction object at 0x0373D3A0>

Il metodo addMenu, ritorna l’oggetto QAction relativo al menu appena inserito.
L’oggetto qaction contiene tutte le informazioni relative al menu e può contenere appunto una icona, un testo, un tip.
E’ possibile ottenere le actions collegate ad una menubar, con il metodo actions()

>>> menubar.actions()
[<PyQt5.QtWidgets.QAction object at 0x0373D210>]
>>> fileaction = menubar.actions()[0]
>>> fileaction.icon()
<PyQt5.QtGui.QIcon object at 0x0373D3F0>
>>> fileaction.text()
'File' 

Ora che abbiamo creato un menu, dobbiamo creare le voci di menu.
Per fare questo, utilizziamo il metodo addAction(text), dove text è il nome della voce di menu.
E’ possibile anche inserire un’icona in fase di aggiunta della action, passandola al metodo precedente come primo parametro, ma è anche possibile farlo successivamente, utilizzando il metodo setIcon(icon).
In realtà, dal momento che l’utilizzo del metodo addAction(text), ritorna l’oggetto QAction, è possibile aggiungere tutti gli elementi in seguito, con i metodi:
setIcon(icon), setToolTip(tooltip):

>>> newtxt_action = menufile.addAction("New &Txt file")
>>> newtxt_action
<PyQt5.QtWidgets.QAction object at 0x0373D530>
>>> newtxt_action.setIcon(QIcon("txt.png"))
>>> menufile.addAction(QIcon("pdf.png"), "New &Pdf file")
<PyQt5.QtWidgets.QAction object at 0x0373D5D0>
>>> menufile.addAction(QIcon("doc.png"), "New &Doc file")
<PyQt5.QtWidgets.QAction object at 0x0373D670>
>>> menufile.actions()
[<PyQt5.QtWidgets.QAction object at 0x0373D530>, <PyQt5.QtWidgets.QAction object at 0x0373D5D0>, <PyQt5.QtWidgets.QAction object at 0x0373D670>]
>>> for action in menufile.actions():
...     print(action.text(), action.icon().isNull())
...     
New &Txt file False
New &Pdf file False
New &Doc file False

SEGNALI

I due segnali da tenere in considerazione sono

hovered: segnale emesso quando la voce di menu viene evidenziata al passaggio del mouse;
triggered: segnale emesso quando clicchiamo sulla voce di menu;

Entrambi i segnali rendono disponibile action, ovvero l’oggetto QAction che ha causato l’emissione del segnale.

Vediamo un esempio pratico:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication, QMenuBar, QMenu, QAction, QWidget
from PyQt5.QtGui import QIcon


import sys


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("QMenu Example")
        menubar = QMenuBar(parent=self)
        menufile = QMenu("File", parent=menubar)
        for text, ext, shortcut in [("New &Txt file", "txt", "CTRL+T"),
                                     ("New &Pdf file", "pdf", "CTRL+P"),
                                     ("New &Doc file", "doc", "CTRL+D")]:
            action = QAction(text, parent=menufile)
            action.setShortcut(shortcut)
            action.setToolTip("Create new %s file" % ext)
            action.setIcon(QIcon("%s.png" % ext))
            menufile.addAction(action)
        menufile.setToolTipsVisible(True)
        menubar.addMenu(menufile)
        self.setMenuBar(menubar)
        self.resize(250, 300)
        self.central_widget = FormWidget(self) 
        self.setCentralWidget(self.central_widget) 

        menufile.triggered.connect(self.on_menu)

    def on_menu(self, action):
        print("[SIG] %s menu clicked!" % action.text())


class FormWidget(QWidget):
    def __init__(self, parent):
        super(FormWidget, self).__init__(parent)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow(parent=None)
    main_window.show()
    sys.exit(app.exec_())

Nota:

Per far sì che i tooltips siano visibili, è fondamentale chiamare il metodo dell’oggetto QMenu, setToolTipsVisible(True).

TEAR-OFF

Quando un menu viene usato intensivamente, è possibile abilitarne la modalità TEAROFF, ovvero è possibile “staccare” una copia del menù e appoggiarla in una parte delle schermo a noi congeniale. Questa copia, rimane fissa e non si disattiva ad ogni click, rendendo più comode le operazioni ripetitive.
Per abilitare tale funzione si deve utilizzare il metodo setTearOffEnabled(True) dell’oggetto QMenu.
Basterà cliccare sul menu e sulla linea tratteggiata dello stesso, apparirà così un menu, in una finestra propria, da muovere a proprio piacimento.

ICON SIZE

L’oggetto QMenu non ha un metodo che consenta di modificare la dimensione delle icone nelle voci di menu.
Per avere delle icone maggiorate, bisogna appoggiarsi alla classe QProxyStyle.
La classe suddetta, permette il wrapping della classe QStyle, che gestisce lo stile di default del sistema, semplificando di fatto l’override degli elementi della classe Qstyle.
Il trucco sta nel creare una classe che erediti da QStyle ed eseguire l’override dell’elemento che ci interessa.

class CustomProxyStyle(QProxyStyle):
    def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
        if QStyle_PixelMetric == QStyle.PM_SmallIconSize and \
                isinstance(widget, QMenu):
            return 36
        else:
            return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option,
                                           widget)

L’override avviene proprio sul metodo pixelMetric di QStyle, che restituisce il pixel Metric relativo al QStyle enum passato come argomento e al widget.
Se il valore del QStyle enum equivale a PM_SmallIconSize e il widget di riferimento è un QMenu, allora il metodo ritorna il valore di pixel personalizzato, altrimenti ritorna quello di default.
Non resta che associare il nostro stile custom, alla nostra applicazione, con il metodo setStyle(style):
Per verificare che l’impostazione sia giusta, mettiamo nello slot dedicato al click su menu, due print che controllino i valori ritornati dal metodo pixeMetric del
nostro proxy style:

...
    def on_menu(self, action):
        menufile = self.sender()
        style = self.style()
        print("[SIG] %s menu clicked!" % action.text())
        print(style)
        print("QMenu: %s" % style.pixelMetric(QStyle.PM_SmallIconSize,
                                              widget=menufile))
        print("widgets: %s" % style.pixelMetric(QStyle.PM_SmallIconSize))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    custom_style = CustomProxyStyle('bancaldo')
    main_window = MainWindow(parent=None)
    main_window.show()
    app.setStyle(custom_style)
    sys.exit(app.exec_())

[SIG] New &Txt file menu clicked!
<__main__.CustomProxyStyle object at 0x010F3850>
QMenu: 36
widgets: 16

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QDir

27 maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QDir

La classe QDir permette di accedere ad una directory e al proprio contenuto.
L’oggetto QDir è usato per manipolare i nomi dei path, accedere ai path ed ai files, modificare il file system.
Il separatore di directory utilizzato da Qt è “/”.
Inoltre è possibile fare riferimento ad un file, sia con percorsi relativi, che assoluti.

Il costruttore è ovviamente QDir:

Se in fase di costruzione, non passiamo nessun path, verrà assunto come percorso, quello corrente, altrimenti si passa il path desiderato, al costruttore.
La struttura di directory e file che usiamo come riferimento, è molto semplice:

C:
|
|-temp
    |
    |-test.txt
    |-test.png
    |--example 
         |
         |-first.txt
         |-second.txt
         |-test.py
>>> from PyQt5.QtCore import QDir
>>> dir = QDir("C:/temp")

PATHS

Per sapere se il path passato al costruttore, esiste, si utilizza il metodo exists:

>>> dir.exists()
True

Per sapere se il path è relativo o assoluto, si utilizzano i metodi isRelative e isAbsolute:

>>> dir.isAbsolute()
True

Il path dell’oggetto QDir, si ottiene con il metodo path:

>>> dir.path()
'C:/temp'

Il percorso può essere modificato con i metodi cd(path) e cdUp() con il quale si risale di livello dalla directory corrente:

>>> dir.cd("example")
True
>>> dir.path()
'C:/temp/example'
>>> dir.cdUp()
True
>>> dir.path()
'C:/temp'

Come si nota, il metodo cd(path), restituisce anche un boolean indicante il buon esito del comando cd.
Qualora si tentasse di spostarsi in una directory inesistente, verrà ritornato False.

>>> dir.cd("notpresent")
False
>>> dir.path()
'C:/temp'

Per settare una nuova path nell’oggetto QDir, si utilizza il metodo setPath(path):

>>> dir.setPath("F:/bancaldo")
>>> dir.path()
'F:/bancaldo'

DIRECTORIES

Il nome della directory si ottiene con il metodo dirName

>>> dir.dirName()
'temp'

Una directory contiene le cosiddette entries, ovvero ulteriori directories, files e link simbolici.
Con il metodo count, si ottiene il numero di entries presenti nel path dell’oggetto QDir, mentre con il metodo entryList si ottiene una lista di stringhe, rappresentanti le entries.

>>> dir.count()
5
>>> dir.entryList()
['.', '..', 'example', 'test.png', 'test.txt']

Infine con entryInfoList si ottiene una lista di oggetti QFileInfo, ognuno dei quali contiene tutte
le informazioni di ogni entry:

>>> qf = dir.entryInfoList()[-1]
>>> qf.fileName()
'test.txt'
>>> qf.size()
0

creare una directory

Ovviamente le directory possono essere create, rimosse, rinominate.
Questo è possibile con i corrispondenti metodi mkdir(newdir), rmdir(dir_to_remove) e rename(oldname, newname).
Vediamo il caso di creazione di una nuova directory:

>>> dir.mkdir("new_dir")
True

Se la creazione non va a buon fine o la directory è già esistente, il metodo ritornerà False.
Una volta effettuata la modifica, bisogna chiamare il metodo refresh, che aggiorna le informazioni della directory rappresentata dal path.

>>> dir.mkdir("new_dir")
>>> dir.entryList()
['.', '..', 'example', 'test.png', 'test.txt']
>>> dir.refresh()
>>> dir.entryList()
['.', '..', 'example', 'new_dir', 'test.png', 'test.txt']

Rinominare una directory

Il metodo per rinominare una directory è rename(oldnamem, newname), dove oldname è la directory che vogliamo rinominare, mentre newname è appunto il nuovo nome da assegnare alla directory stessa.
Se l’operazione andrà a buon fine, riceveremo il valore True, altrimenti False:

>>> dir.rename("new_dir", "new_dir_2")
True
>>> dir.refresh()
>>> dir.entryList()
['.', '..', 'example', 'new_dir_2', 'test.png', 'test.txt']

Eliminare una directory

Il metodo è rmdir(dir_to_remove):

>>> dir.rmdir('new_dir_2')
True
>>> dir.refresh()
>>> dir.entryList()
['.', '..', 'example', 'test.png', 'test.txt']

FILES

Per verificare la presenza di un file all’interno del nostro path, si utilizza ancora exists(filename), passando come argomento il nome del file che stiamo cercando:

>>> dir.setPath("C:/temp")
>>> dir.exists("test.txt")
True
>>> dir.exists("test3.txt")
False

SPECIAL PATHS

Sono metodi che restituiscono oggetti QDir e le rispettive stringhe dei percorsi corrispondenti, di paths speciali, come la directory di lavoro dell’applicazione, la user directory, ecc.
Sono principalmente 4 coppie:

current() e currentPath(): Restituiscono rispettivamente l’oggetto QDir e il path della directory di lavoro dell’applicazione;

>>> QDir.current().currentPath()
'F:/bancaldo/python/Qt5'

home() e homePath(): Restituiscono rispettivamente l’oggetto QDir e il path della home directory dell’utente;

>>> QDir.home().homePath()
'C:/Users/bancaldo'

root() e root(): Restituiscono rispettivamente l’oggetto QDir e il path della root directory;

>>> QDir.root().rootPath()
'C:/'

temp() e tempPath(): Restituiscono rispettivamente l’oggetto QDir e il path della temp directory del sistema operativo;

>>> QDir.temp().tempPath()
'C:/Users/bancaldo/AppData/Local/Temp'

FILTER

I due metodi precedentemente visti entryList() e entryInfoList(), permettono di utilizzare dei filtri, per ottimizzare la ricerca effettuata.
Tali filtri, sono enum e possono essere combinati tramite operazione bitwise OR. Possono assumere i seguenti valori:

Dirs | Files | Drives

Elenca i file per i quali l’app ha accesso in scrittura; il valore Writable deve essere combinato con Dirs o Files

Costante Valore Descrizione
QDir.Dirs 0x001 Elenca le directories che corrispondono ai filtri sul nome
QDir.AllDirs 0x400 Elenca tutte le directories; non vengono applicati filtri al nome
QDir.Files 0x002 Elenca i files
QDir.Drives 0x004 Elenca i dischi
QDir.NoSymLinks 0x008 Non elenca i symbolic links
QDir.NoDotAndDotDot NoDot | NoDotDot Non elenca le special entries “.” e “..”
QDir.NoDot 0x2000 Non elenca la special entry “.”
QDir.NoDotDot 0x4000 Non elenca la special entry “..”
QDir.AllEntries

Elenca directories, files, drives e symlinks
QDir.Readable 0x010

Elenca i file per i quali l’app ha accesso in lettura; il valore Readable deve essere combinato con Dirs o Files
QDir.Writable 0x020

QDir.Executable 0x040 Elenca i file per i quali l’app ha accesso in esecuzione; il valore Executable deve essere combinato con Dirs o Files
QDir.Modified 0x080 Elenca solo i file che sono stati modificati
QDir.Hidden 0x100 Elenca i file nascosti (su Unix i file che cominciano con “.”)
QDir.System 0x200 Elenca i file di sistema
QDir.CaseSensitive 0x800 Il filtro è case sensitive

Per ottenere i filtri attivi su un oggetto QDir, si utilizza il metodo filter() mentre per aggiungerne uno, a quelli esistenti, si utilizza il metodo setFilter(filter).
Di default, un oggetto QDir utilizza come enum filter, QDir.AllEntries, cioè mostra mostra tutte le directory, tutti i files e tutti i drives.
Come da tabella, QDir.AllEntries equivale a:

QDir.Dirs | QDir.Files | QDir.Drives

QDir.AllEntries = 7 (1 + 2 + 4)

>>> dir.filter().__int__()
7

Se volessi ad esempio elencare solo i files, dovrei passare il filtro QDir.Files (valore 2) all’oggetto QDir:

>>> dir.entryList()
['.', '..', 'example', 'test.png', 'test.txt']
>>> dir.setFilter(QDir.Files)
>>> dir.setFilter(QDir.Files)
>>> dir.filter().__int__()
2
>>> dir.entryList()
['test.png', 'test.txt']

NAME FILTER

E’ possibile anche filtrare sui nomi, grazie al metodo setNameFilters(filters), dove filters è una lista di stringhe che contengono i caratteri “*” e “?”, a seconda dell’occorrenza:

>>> dir.setNameFilters(["*.txt"])
>>> dir.entryList()
['test.txt']
>>> dir.setNameFilters(["t??t.*"])
>>> dir.entryList()
['test.png', 'test.txt']

SORTING

E’ possibile definire l’ordinamento della lista delle entries, passando al metodo setSorting(sort_union), l’OR dei seguenti valori:

QDir.Name 0x00 Sort by name.

Costante Valore Descrizione
QDir.Name 0x00 Ordinamento per Nome
QDir.Time 0x01 Ordinamento in base all’orario di modifica (modification time)
QDir.Size 0x02 Ordinamento per dimensione del file
QDir.Type 0x80 Ordinamento per estensione file
QDir.Unsorted 0x03 Nessun ordinamento
QDir.NoSort -1 Nessun ordinamento (default)
QDir.DirsFirst 0x04 Ordina con prima le directories e poi i files
QDir.DirsLast 0x20 Ordina con prima i files e poi le directories
QDir.Reversed 0x08 Inverte l’ordine
QDir.IgnoreCase 0x10 Ordina in modalità case-insensitive
QDir.LocaleAware 0x40 Ordina gli elementi usando i settings “locale”

Per ottenere i valori di sorting dell’oggetto QDir, si può utilizzare il metodo sorting().
Di default, l’ordinamento di un oggetto QDir equivale all’OR degli enum Qdir.IgnoreCase e Qdir.Name, cioè un valore di 16:

>>> dir2.sorting().__int__()
16

Mettiamo di voler ordinare per nome, ma tenere le dirs in fondo; dobbiamo fare l’OR di:

QDir.DirsLast | QDir.Name

che hanno come valori rispettivamente, 32 e 0:

>>> dir.entryList()
['.', '..', 'first.txt', 'second.txt', 'test.py']
>>> dir.setSorting(QDir.DirsLast | QDir.Name)
>>> dir.sorting().__int__()
32
>>> dir.entryList()
['first.txt', 'second.txt', 'test.py', '.', '..']

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,

PyQt5: QFile

27 maggio 2019 Commenti chiusi

Torna all’indice degli appunti

QFile

La classe QFile mette a disposizione un’interfaccia per la lattura da file e la scrittura su file.
Il nome del file si può passare direttamente al costruttore, o è possibile settarlo in seguito con il metodo setFileName(finename).

>>> from PyQt5.QtCore import QFile
>>> f = QFile("C:/temp/test.txt")

L’esistenza di un file è verificabile con il metodo exists():

>>> f.exists()
True

OPEN FILE

Un file viene aperto con il metodo open(mode), dove mode è un enum QIODevice che indica appunto, in che modo il device è stato aperto e può assumere i seguenti valori:

Costante Valore Descrizione
QIODevice.NotOpen 0x0000 il dispoditivo non è aperto
QIODevice.ReadOnly 0x0001 il dispositivo è aperto in lettura
QIODevice.WriteOnly 0x0002 il dispositivo è aperto in scrittura
QIODevice.ReadWrite ReadOnly | WriteOnly Il dispositivo è aperto in lettura e scrittura
QIODevice.Append 0x0004 Il dispositivo è aperto in append mode, tutti i dati sono scritti alla fine del file
QIODevice.Truncate 0x0008 Se possibile, il dispositivo viene troncato prima di essere aperto. Tutti i contenuti precedenti sono perduti
QIODevice.Text 0x0010 In lettura gli end-of-line vengono tradotti in ‘\n’. In scrittura gli end-of-line sono tradotti secondo il local encoding (es.’\r\n’ per Win32)
QIODevice.Unbuffered 0x0020 Ogni buffer nel dispositivo è bypassato
QIODevice.NewOnly 0x0040 Fallisce se il file da aprire, esiste già. Crea e apre il file, solo se non esiste
QIODevice.ExistingOnly 0x0080 Fallisce se il file da aprire, non esiste

il tipo di mode utilizzato si ottiene con il metodo openMode()

Di default, non avendolo ancora aperto, il file restituirà enum QIODevice.NotOpen:

>>> f.openMode().__int__()
0

Ora voglio aprirlo in modalità lettura\scrittura, passando quindi come mode flag QIODevice.ReadWrite:

>>> from PyQt5.QtCore import QIODevice
>>> f.open(QIODevice.ReadWrite)
True
>>> f.openMode().__int__()
3

3 è il valore unione di QIODevice.ReadOnly = 1 e QIODevice.WriteOnly = 2.
Il boolean True, certifica il buon esito dell’operazione di apertura.

Per modificare il mode mentre il file è aperto, si utilizza il metodo setOpenMode(mode):

>>> f.setOpenMode(QIODevice.ReadOnly)
>>> f.openMode().__int__()
1

Per chiudere un file aperto si utilizza il metodo close().

Nota:

Se apriamo un nuovo file, non ancora esistente, questi non verrà creato fino a chè non verrà chiamato il metodo open:

>>> newfile = QFile("C:/temp/newfile.txt")
>>> newfile.open(QIODevice.ReadWrite)
True

Per rinominare un file, si utilizza il metodo rename(newname).
In caso di buon esito dell’operazione, il metodo ritornerà True, altrimenti, qualora esistesse già un file con il nuovo nome da assegnare, ritornerà False:

>>> newfile.rename("C:/temp/test.txt")
False
>>> newfile.rename("C:/temp/test2.txt")
True

Per rimuovere un file, si utilizza il metodo remove():

>>> newfile.remove()
True

LETTURA E SCRITTURA

I dati contenuti in un file, solitamente sono letti e scritti utilizzando QDataStream e QTextStream. La prima classe fornisce una serializzazione di dati binari al QIODevice, la seconda invece fornisce un’interfaccia per la lettura e la scrittura di testo.

Possiamo anche usare i metodi di QFile, ereditati dalla classe QIODevice, che sono:

read(maxsize): legge un quantità di dati, pari all’argomento maxsize;

>>> f.close()
>>> f.open(QIODevice.ReadOnly)
True
>>> f.read(11)
b'Lorem ipsum'
>>> f.read(11)
b' dolor sit '

Ad ogni chiamata del metodo read(maxsize), la posizione all’interno del file avanza.
Per conoscere la posizione all’interno del file, si utilizza il metodo pos():

>>> f.pos()
22
>>> f.read(11)
b'amet, conse'
>>> f.pos()
33

Per spostarsi all’interno del file, si utilizza il metodo seek(position), dove position indica la posizione sulla quale ci sposteremo:

>>> f.seek(0)
True
>>> f.read(11)
b'Lorem ipsum'

Per sapere se abbiamo raggiunto la fine del file, si utilizza il metodo atEnd():

>>> f.atEnd()
False
>>> f.seek(f.size())
True
>>> f.read(11)
b''
>>> f.atEnd()
True

readLine: legge una riga alla volta;

>>> f.seek(0)
True
>>> while not f.atEnd():
...     print(f.readLine())
...     
b'Lorem ipsum dolor sit amet, consectetur adipisci elit, \r\n'
b'sed eiusmod tempor incidunt ut labore et dolore magna aliqua. \r\n'
b'Ut enim ad minim veniam, \r\n'
b'quis nostrum exercitationem ullam corporis suscipit laboriosam, \r\n'
b'nisi ut aliquid ex ea commodi consequatur. \r\n'
b'Quis aute iure reprehenderit in voluptate velit esse cillum dolore\r\n'
b' eu fugiat nulla pariatur. \r\n'
b'Excepteur sint obcaecat cupiditat non proident, \r\n'
b'sunt in culpa qui officia deserunt mollit anim id est laborum.'

readAll: ritorna un oggetto QByteArray che contiene tutto il contenuto del file, dalla posizione corrente fino alla fine;
Il contenuto dell’oggetto QByteArray si ottiene con il metodo data():

>>> f.readAll().data()
b''
>>> f.seek(0)
True
>>> data = f.readAll()
>>> data.size()
468
>>> data.data()
b'Lorem ipsum dolor sit amet, cons...

write:

Apriamo un nuovo file e scriviamo su di esso alcune stringhe.
Utilizziamo oggetti QByteArray per rappresentare i testi ed il metodo write(QByteArray) per passarli all’oggetto QFile.
Per scrivere fisicamente i dati sul file, chiamiamo il metodo flush():

>>> fw = QFile("C:/temp/write_test.txt")
>>> fw.open(QIODevice.ReadWrite | QIODevice.Text)
True
>>> data = QByteArray(b"first line\n")
>>> data.append(b"second line\n")
PyQt5.QtCore.QByteArray(b'first line\nsecond line\n')
>>> data.append(b"third line\n")
PyQt5.QtCore.QByteArray(b'first line\nsecond line\nthird line\n')
>>> fw.write(data)
34
>>> fw.flush()
True
>>> fw.close()

Nota:
Se non usassimo la union QIODevice.ReadWrite | QIODevice.Text in fase di apertura file, i ‘\n’ verrebbero eliminati.

SIZE

Come visto precedentemente, il size di un file si ottiene con il metodo size().

>>> fw.size()
37

POSITION

Come anticipato dagli esempi, la posizione all’interno del file si ottiene con il metodo pos().

>>> fw.open(QIODevice.ReadOnly)
True
>>> fw.read(5)
b'first'
>>> fw.pos()
5

FLUSH

Chiamando il metodo flush(), si scrivono i dati, presenti nel buffer, che aspettano di essere scritti sul dispositivo, nel nostro caso sul file.
Il metodo ritorna True o False a seconda del buon esito della scrittura.

COPY

Per copiare un file, si utizza il metodo copy(newname). Se newname è un nome che esiste già, il metodo ritornerà False, altrimenti, in caso di buona riuscita della copia, True:

>>> fw.copy("C:/temp/copy.txt")
True

LINK

E’ possibile creare link ad un file (collegamento su Win o symbolic-link su Unix) semplicemente con il metodo link(linkname):

>>> fw.link("C:/temp/link_to_write_test")
True

Torna all’indice degli appunti

Categorie:PyQt5, python Tag: ,