Spendi Meno Quanto Basta Parte 5

In questa puntata vedremo come creare il controllo che useremo per inserire i dati, copiandoli dalla foto dello scontrino.

Che dati? Proviamo ad elencarli:

  • Dove: ovvero in che paese, località, centro commerciale ho fatto la spesa?
  • Negozio: qual è il nome dell’attività o dell’azienda che mi ha venduto qualcosa?
  • Quando: in che data ho fatto la spesa?
  • Spesa: quanto ho speso?
  • Valuta: in che valuta ho pagato? Questa idea mi è venuta perchè tra poco andrò ad Edimburgo per fare una vacanza, e per prenotare ho dovuto fare un bonifico in Sterline
  • Controvalore: questo è il campo con la spesa in Euro, sempre. Se la spesa è stata fatta in Sterline, beh allora questo valore lo andremo a calcolare.

Ma non mi vorrei fermare a questo! Uno degli aspetti che ho sempre trovato sbagliato nei vari software di gestione delle spese, è il fatto che non ti permettono di dividere l’esborso in quote diverse. Questo è accettabile se si tratta solo di fare il saldo ingressi uscite, ma è limitante se si vuole fare l’analisi delle spese, al fine di identificare quelle comprimibili e quelle non comprimibili.

Dettagliare le spese

Vediamo come gestire il dettaglio delle spese.

Il classico esempio è quando vai al supermercato e accanto a frutta, verdura, detersivi, sapone, comperi anche la lampadina che si è bruciata, i chiodi per appendere un quadro e i quaderni per la scuola dei figli. Non posso classificare i 100€ spesi allo stesso modo, non trovate?

L’altro caso che mi sono immaginato è quando si esce a cena con degli amici, uno paga per tutti e poi divide la spesa con gli altri. Pertanto lo scontrino contiene tutto l’esborso, e se ho pagato con il bancomat, il movimento contiene tutto il costo della cena, ma io non ho realmente pagato quella cifra perchè una parte mi è stata rimborsata.

La questione non è semplicissima, e spero di aver trovato un modo elegante per gestirla. La mia idea in questo caso è di inserire come dettaglio la mia quota e poi altri dettagli con la quota pagata dai vari amici, indicando come categoria: Spese Rimborsate.

In questo modo risolvo la questione relativa al bilanciamento della spesa, ma attenzione che, per bilanciare i soldi nel portafoglio, devo anche inserire l’entrata in contanti.

Per questo motivo ho introdotto il concetto di dettaglio, ovvero la possibilità di scomporre la spesa in diverse voci, ognuna con una sua classificazione. Questo sarà molto utile quando passeremo alla fase di analisi dei dati raccolti.

Cosa inseriamo nella form di dettaglio? Penso che 5 campi siano sufficienti. Vediamo quali:

  • Indice: indice del dettaglio, serve per visualizzare i dettagli nella sequenza voluta
  • Spesa: quanto ho speso per questa specifica voce?
  • Categoria: come la registriamo questa spesa? Per fare una classificazione efficace devono esserci delle categorie fisse, tipo Alimentari, Abbigliamento, Trasporti, Scuola, Casa, Lavoro, Viaggi, Finanziamenti. Vedremo più avanti come creare un elenco dinamico di categorie.
  • Sottocategorie: questa voce serve per dettagliare ulteriormente la spesa. Ad esempio Trasporti, come sottocategoria potrebbe avere Automobile, Pedaggi, Parcheggi
  • Descrizione: per finire, un campo di testo libero in cui inserire delle note, non può mancare.

SmqbExpensesDataWgt

Questo è il widget che affiancheremo a SmqbExpensesImagesWgt. Dopo aver aperto Qt Creator e il nostro progetto SpendiMenoQuantoBasta, usiamo File, New File or Project ed inseriamo una nuova Qt Designer Form Class, di tipo Widget, che chiamiamo SmqbExpensesDataWgt.

Impostiamo il nome della classe e i nomi dei vari file:

  • SmqbExpensesDataWgt
  • SmqbExpensesDataWgt.h
  • SmqbExpensesDataWgt.cpp
  • SmqbExpensesDataWgt.ui

Ora completiamo la procedura di Qt Creator ed iniziamo ad editare il widget aggiungendo i seguenti controlli:

  • una QLabel che chiameremo lblWhere in cui scriveremo Dove
  • inseriamo un QLineEdit che chiamiamo txtWhere in cui metteremo la località
  • una QLabel chiamata lblVendor con testo: Negozio
  • inseriamo un QLineEdit che chiamiamo txtVendor ed in cui scriveremo il nome del negozio
  • una QLabel chiamata lblWhen con testo: Quando
  • un controllo QDateEdit chiamato txtWhen in cui mettere la data della spesa.
  • un pulsante QPushButton chiamato txtWhenToday con il titolo “Oggi”, che useremo per impostare la data di oggi.
  • una QLabel chiamata lblExpense con testo: Spesa
  • un QDoubleSpinBox chiamato txtExpense in cui metteremo il valore speso
  • una QComboBox chiamata txtCurrency in cui metteremo una lista di valute, rappresentate dal loro codice ISO. Iniziamo con EUR, GBP, USD.
  • una QLabel chiamata lblValue con testo: Controvalore
  • un QLineEdit txtValue in cui verrà scritto il valore in Euro
  • un pulsante QPushButton chiamato cmdValue e con titolo: “Calcola” che useremo per aggiornare il valore.
  • una QLabel chiamata lblDetails con testo: Dettagli
  • una QLabel chiamata lblBalance con testo: Mancano
  • un QLineEdit txtBalance in cui andremo a calcolare lo sbilanciamento. Naturalmente il senso di questo campo è di fare in modo che i dettagli si equivalgono alla spesa.

Distribuiamo i controlli

Organizziamo in un Layout a griglia i controlli che abbiamo creato. La griglia va fatta con 3 colonne, serve una attimo di attenzione per impostare i campi txtWhere e txtVendor in modo che prendano due colonne. Per ogni dubbio fate riferimento all’immagine seguente:

Organizzare bene questo widget in realtà è la vera sfida di questa puntata, e dopo innumerevoli tentativi sono giunto ad una forma soddisfacente. Ci sono molti controlli e soprattutto la parte del dettaglio di spesa ha un numero di elementi variabile. Serve un contenitore, altrimenti ogni volta che aggiungo un controllo, la dimensione della finestra di dialogo cresce. Vediamo come si fa…

Usiamo la scroll area

La mia idea è di usare un controllo QScrollArea come contenitore di quasi tutta la form, in modo che aggiungendo elementi di dettaglio (e più avanti vedremo come), tutti i controlli possano scorrere verso l’alto lasciando spazio ai dettagli.

Quindi inseriamo un controllo QScrollArea che chiamiamo scrollMain spostiamo il gruppo con tutti i controlli in questa area.

Poi aggiungiamo anche un vertical spacer in fondo, sempre dentro la scroll area, e distribuiamo il tutto il contenuto con un layout orizzontale. Ecco come esce:

Non abbiamo ancora finito… Adesso serve un segnaposto per la zona in cui andremo ad infilare i widget con le varie voci di spesa che formano il dettaglio.

Aggiungiamo un QVBoxLayout e chiamiamolo ctrDetails; mettiamoci dentro una QLabel che chiameremo lblPlaceHolder e in cui scriveremo “segnaposto”. Poi trasciniamo il tutto nella scroll area, appena sopra il vertical spacer. Abbiamo così creato l’area in cui metteremo i dettagli della spesa, e che essendo un QVBoxLayout si espanderà automaticamente in verticale, al crescere del numero di dettagli, ma questa espansione verrà assorbita dalla Scroll Area, non dalla dimensione della finestra di dialogo.

Aggiungi, Cancella, Salva

Ancora un piccolo sforzo e abbiamo finito! In fondo al nostro Widget andiamo a mettere 3 pulsanti, fuori dalla scroll area che saranno:

  • cmdAdd, con titolo Aggiungi, che poi sostiuiremo con una icona, servirà per aggiungere un dettaglio di spesa
  • cmdDelete, con titolo Cancella servirà per eliminare uno o più dettagli di spesa
  • cmdSave, con titolo Salva, servirà per salvare il record corrente nel database.

Naturalmente poi aggiungeremo le icone ai pulsanti, ma non adesso. Ora ci concentriamo sulla costruzione del widget.

Applichiamo il layout orizzontale a tutta la form, e poi applichiamo due piccole rifiniture. Dal momento che ci sono molti layout in cascata, e dal momento che si tratta di un widget da inglobare in una altra form, è opportuno ridurre a 0 i margini di SmqbExpensesDataWgt:

E anche quelli di scrollAreaWidgetContents:

In questo modo si risparmiano circa 50 pixel che altrimenti vanno sprecati senza motivo. Ecco come diventa la struttura del widget dopo questi tagli:

Ora possiamo integrare la widget nella pagina delle spese.

SmqbExpensesDataWgt alla riscossa…

Mamma mia quanto lavoro solo su Qt Creator, senza scrivere codice. Funzionerà? Proviamo.

Apriamo la form principale, selezioniamo la pagina delle spese, selezionare ctrData, tasto destro, Promote To …, ed inserire SmqbExpensesDataWgt come nome della classe e SmqbExpensesDataWgt.h nel campo Header File. Premere Add e Promote, compilare e il gioco è fatto, ma aspettate! Prima disabilitate il bordo rosso inserendo nel costruttore della classe:

     ui->ctrData->setStyleSheet("");

Ecco il risultato:

Tempo di creare i dettagli

Ora vorrei creare la classe SmqbExpensesDetailWgt in cui inserire i dettagli di spesa. Al solito si tratta di un Widget, pertanto aggiungiamo un nuovo file di tipo Qt Designer Form Class e poi inseriamo come nomi:

  • per la classe: SmqbExpensesDetailWgt
  • per il file .h: SmqbExpensesDetailWgt.h
  • per il file cpp: SmqbExpensesDetailWgt.cpp
  • per la user interface: SmqbExpensesDetailWgt.ui

Nella GUI inseriamo i campi in modo creare un widget abbastanza piccolo, organizzato su 3 colonne e 2 righe. Ecco in controlli da inserire:

  • Iniziamo con un oggetto grafico Horizontal Line, che chiamiamo ctrLine e che rappresenta una linea orizzontale che separa visivamente i dettagli di spesa. Per comodità di manipolazione, impostiamo lo spessore (lineWidth) a 10, poi lo rimpiccioliamo nel codice.
  • Inseriamo un QPushButton che chiamiamo cmdIndex e che useremo sia per mostrare l’indice del dettaglio corrente, sia per attivare la selezione del dettaglio. Come testo mettiamo 00. Inoltre modifichiamo sizePolicy cambiando Vertical Policy da Fixed a Expanding. Per finire attiviamo la property checkable in modo che quando clicchiamo resti selezionato
  • Aggiungiamo una QLabel che chiamiamo lblExpense, in cui inseriamo la parola: Spesa
  • Aggiungiamo QDoubleSpinBox che chiamiamo txtExpense e in cui metteremo il valore speso in euro. A tal proposito personalizziamo il campo suffix ed inseriamo €, e cambiamo il valore massimo (maximum) da 100 a 1.000.ooo (di Euro, credo basti, che dite?)
  • Aggiungiamo una QComboBox chiamata txtCategory in cui andremo a selezionare la categoria. Editiamo la lista degli elementi (basta fare doppio click e si apre l’editor) inserendo le voci: Rimborso, Alimentari, Abbigliamento, Trasporti, Scuola, Casa, Lavoro, Viaggi, Finanziamenti
  • Aggiungiamo una QLabel che chiamiamo lblDescription, ed in cui inseriamo Descrizione
  • Aggiungiamo un oggetto QLineEdit chiamato txtDescription in cui metteremo la descrizione del movimento.
  • Aggiungiamo un controllo QComboBox che chiamiamo: txtSubCategory e che useremo per la sotto-categoria. Dato che le opzioni possibili dipendono dalla categoria, per il momento non aggiungiamo alcuna voce.

Organizziamo i dettagli

Ora facciamo molta attenzione a disporre i dettagli nel modo corretto. Iniziamo creando un layout a griglia in cui disponiamo tutti i controlli tranne cmdIndex. Fate attenzione a mettere ctrLine nella prima riga e ad allargarlo in modo che copra tutte e 3 le colonne. Ecco come deve diventare:

Ora, tutta la form, la organizziamo con un layout orizzontale, e poi azzeriamo i margini:

Ecco il risultato finale:

Inseriamo il codice dei dettagli

Iniziamo modificando il costruttore aggiungendo index come parametro. Questo serve per assegnare un indice ad ogni dettaglio, già in fase di costruzione.

Poi aggiungiamo i metodi:

  • setIndex() che serve per impostare l’indice
  • index() che serve per leggere l’indice corrente
  • isSelected() che serve per sapere se il widget attuale è selezionato oppure no.

Ecco tutte le modifiche:

 public:
     explicit SmqbExpensesDetailWgt(int index,
                                    QWidget *parent = nullptr);
     ~SmqbExpensesDetailWgt();
     
     void setIndex(int index);
     int index() const;
     bool isSelected() const;

Ora passiamo a modificare il costruttore, ricordandoci che lo spessore della linea (ctrLine), che per comodità di gestione avevamo impostato a 10, va rimesso a 1. Quando all’indice, basta chiamare il metodo setIndex().

 SmqbExpensesDetailWgt::SmqbExpensesDetailWgt(int index,
                                              QWidget *parent) :
     QWidget(parent),
     ui(new Ui::SmqbExpensesDetailWgt)
 {
     ui->setupUi(this);
     ui->ctrLine->setLineWidth(1);
     setIndex(index);
 }

Ora concediamoci un attimo per implementare index e setIndex, andando a scrivere e leggere l’indice direttamente in cmdIndex.

 void SmqbExpensesDetailWgt::setIndex(int index)
 {
     ui->cmdIndex->setText(
         QString("%1").arg(index, 2, 10, QChar('0')));   
 }
 
 int SmqbExpensesDetailWgt::index() const
 {
     return ui->cmdIndex->text().toInt();
 }

Completiamo questo paragrafo implementando isSelected(), che nuovamente va a leggere lo stato direttamente sul pulsante:

 bool SmqbExpensesDetailWgt::isSelected() const
 {
     return ui->cmdIndex->isChecked();
 }

SmqbExpensesDataWgt: integriamo i dettagli

Bene, adesso possiamo finalmente inserire i dettagli nel nostro widget quando premiamo il pulsante Aggiungi, e cancellarli quando premiamo il pulsante Cancella.

Per prima cosa ci serve una lista di widget di tipo SmqbExpensesDetailWgt, pertanto, dopo aver aggiunto il riferimento alla classe, è tempo di creare una lista di elementi:

 #include "SmqbExpensesDetailWgt.h"
 
 private:
     Ui::SmqbExpensesDataWgt *ui;
     QList<SmqbExpensesDetailWgt*> m_detailList;

Ora implementare il gestore del pulsante Add è semplice se sai come fare! Calcolo index aggiungendo 1 all numero di elementi nella lista. Poi creo un nuovo oggetto SmqbExpensesDetailWgt in base all’indice e lo aggancio al controllo ui->ctrDetails. Notare che questo è un QVboxLayout, non un widget, pertanto mi aggancio al suo widget padre.

Per finire aggiungo l’ultimo widget creato a ctrDetails, il quale lo dispone in ordine verticale, subito sotto al dettaglio precedente. Solo 3 righe di codice, ma di un denso!

 void SmqbExpensesDataWgt::on_cmdAdd_clicked()
 {
     int index = m_detailsList.count() + 1;
     m_detailsList.append(new SmqbExpensesDetailWgt(
         index, ui->ctrDetails->parentWidget()));

     ui->ctrDetails->addWidget(m_detailsList.last());
 }

Eliminare i dettagli

Implementare il gestore del tasto Cancella è un poco più complesso. Prima dobbiamo creare due metodi di supporto:

  • updateGui_removeDetail() si occupa di rimuovere l’oggetto dal layout ctrDetails
  • updateGui_updateDetailsIndex() rimette a posto gli indici dei vari elementi, dopo averne eliminato uno o più.
 protected:
     void updateGui_removeDetail(SmqbExpensesDetailWgt* detail);
     void updateGui_updateDetailsIndex();

Iniziamo con updateGui_removeDetail() e vediamo come cancellare un widget dalla lista. Non è semplicissimo, ci sono due elementi da cancellare. Il primo è un oggetto QLayoutItem, che al suo interno incapsula il mio widget. Quindi devo far passare tutti i QLayoutItem alla ricerca di quello giusto, per poi toglierlo dalla lista, eliminare il widget contenuto ed eliminare il layout item. Per eliminare il widget ho usato deleteLater() in modo da cancellarlo quando tutti gli eventi sono stati gestiti.

 void SmqbExpensesDataWgt::updateGui_removeDetail(
    SmqbExpensesDetailWgt *detail)
 {
     // Scan the list till the one to delete
     for(int ii=0; ii<ui->ctrDetails->count(); ii++) {
         QLayoutItem *item = ui->ctrDetails->itemAt(ii);
         if(item->widget() == detail) {
             ui->ctrDetails->takeAt(ii);
             item->widget()->deleteLater();
             delete item;
         }
     }
 }

L’implementazione di updateGui_updateDetailsIndex() invece è più semplice, dato che deve solo scandire gli elementi della lista e numerarli in progressione usando il metodo setIndex():

 void SmqbExpensesDataWgt::updateGui_updateDetailsIndex()
 {
     int ii = 1;
     for (auto detail: m_detailsList) {
         detail->setIndex(ii++);
     }
 }

Ora possiamo creare il gestore del tasto Cancella. Il codice si articola in tre fasi: prima creiamo una lista di items selezionati, poi li cancelliamo con updateGui_removeDetail() e li togliamo dalla lista, e per finire rimettiamo a posto gli indici:

 void SmqbExpensesDataWgt::on_cmdDelete_clicked()
 {
     // List all selected
     QList<SmqbExpensesDetailWgt*> selected;
     for(auto detail : m_detailsList){
         if(detail->isSelected())
             selected.append(detail);
     }
     // Remove all selected
     for (auto detail : selected) {
         updateGui_removeDetail(detail);
         m_detailsList.removeOne(detail);
     }
     updateGui_updateDetailsIndex();
 }

Compilando…

Bene, anche per oggi siamo arrivati in fondo! Ecco come è diventata adesso la nostra pagina per inserire le spese.

Divertitevi pure ad aggiungere / togliere dettagli di spesa.

Concludendo

Il viaggio continua, e nella prossima puntata inizieremo a scrivere nel database. Molte delle cose che faremo le ho trattate al Qt Day 2019, pertanto, se volete, potete vedere la mia presentazione, in attesa della prossima puntata. A presto.

Autore: Gianbattista

Appassionato di tecnologia, sono l'autore di Qt5 Quanto Basta. Per lavoro mi occupo di elaborazione delle immagini per applicazioni industriali, ma nel tempo libero adoro creare applicazioni con Qt (www.qt.io)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *